Merge "Check project connectivity on port admin state"
diff --git a/.gitignore b/.gitignore
index 9292dbb..287db4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
ChangeLog
*.pyc
__pycache__/
+etc/accounts.yaml
etc/tempest.conf
etc/tempest.conf.sample
etc/logging.conf
diff --git a/README.rst b/README.rst
index fc4de5e..281516b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,3 +1,13 @@
+========================
+Team and repository tags
+========================
+
+.. image:: http://governance.openstack.org/badges/tempest.svg
+ :target: http://governance.openstack.org/reference/tags/index.html
+ :remote:
+
+.. Change things from this point on
+
Tempest - The OpenStack Integration Test Suite
==============================================
diff --git a/bindep.txt b/bindep.txt
new file mode 100644
index 0000000..6a28348
--- /dev/null
+++ b/bindep.txt
@@ -0,0 +1,11 @@
+# This file contains runtime (non-python) dependencies
+# More info at: http://docs.openstack.org/infra/bindep/readme.html
+
+libffi-dev [platform:dpkg]
+libffi-devel [platform:rpm]
+gcc [platform:rpm]
+gcc [platform:dpkg]
+python-dev [platform:dpkg]
+python-devel [platform:rpm]
+openssl-devel [platform:rpm]
+libssl-dev [platform:dpkg]
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 2edaddb..04ddfdf 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# Tempest documentation build configuration file, created by
# sphinx-quickstart on Tue May 21 17:43:32 2013.
#
diff --git a/doc/source/library/api_microversion_testing.rst b/doc/source/library/api_microversion_testing.rst
index b7a4ba8..8be924d 100644
--- a/doc/source/library/api_microversion_testing.rst
+++ b/doc/source/library/api_microversion_testing.rst
@@ -1,6 +1,6 @@
.. _api_microversion_testing:
-API Microversion Testing Support in Temepst
+API Microversion Testing Support in Tempest
===========================================
---------------------------------------------
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index dc73ef2..6b87b4d 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -236,6 +236,10 @@
.. _2.25: http://docs.openstack.org/developer/nova/api_microversion_history.html#maximum-in-mitaka
+ * `2.32`_
+
+ .. _2.32: http://docs.openstack.org/developer/nova/api_microversion_history.html#id29
+
* `2.37`_
.. _2.37: http://docs.openstack.org/developer/nova/api_microversion_history.html#id34
diff --git a/releasenotes/notes/add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml b/releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
similarity index 100%
rename from releasenotes/notes/add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
rename to releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
diff --git a/releasenotes/notes/add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml b/releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
similarity index 100%
rename from releasenotes/notes/add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
rename to releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
diff --git a/releasenotes/notes/add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml b/releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
similarity index 100%
rename from releasenotes/notes/add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
rename to releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
diff --git a/releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml b/releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml
new file mode 100644
index 0000000..7e40fd4
--- /dev/null
+++ b/releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml
@@ -0,0 +1,9 @@
+---
+features:
+ - |
+ As in the [doc]:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html,
+ there are some apis are not included, add them.
+
+ * namespace_properties_client(v2)
+
diff --git a/releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
new file mode 100644
index 0000000..a1edcc5
--- /dev/null
+++ b/releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Define the identity service role_assignments_client as a library.
+ Add role_assignments_client to the library interface so the other
+ projects can use this module as a stable library without any
+ maintenance changes.
diff --git a/releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml b/releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
new file mode 100644
index 0000000..b504c78
--- /dev/null
+++ b/releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - A Neutron Service Providers client is added to deal with resources
+ of the '/service-providers' route.
diff --git a/releasenotes/notes/add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml b/releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
similarity index 100%
rename from releasenotes/notes/add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
rename to releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
diff --git a/releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml b/releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml
rename to releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
diff --git a/releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml b/releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
new file mode 100644
index 0000000..9223ba5
--- /dev/null
+++ b/releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
@@ -0,0 +1,7 @@
+features:
+ - |
+ Define the Volume v3 service clients as library interfaces,
+ allowing other projects to use these modules as stable
+ libraries without maintenance changes.
+
+ * messages_client(v3)
diff --git a/releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml b/releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
new file mode 100644
index 0000000..033e147
--- /dev/null
+++ b/releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
@@ -0,0 +1,3 @@
+---
+features:
+ - The volume_limits client was added to tempest.lib.
diff --git a/releasenotes/notes/remo-stress-tests-81052b211ad95d2e.yaml b/releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
similarity index 100%
rename from releasenotes/notes/remo-stress-tests-81052b211ad95d2e.yaml
rename to releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
diff --git a/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml b/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
new file mode 100644
index 0000000..ca2635e
--- /dev/null
+++ b/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - All tests for the Ironic project have been removed from Tempest. Those
+ exist as a Tempest plugin in the Ironic project.
diff --git a/releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml b/releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
new file mode 100644
index 0000000..e6e53af
--- /dev/null
+++ b/releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+ - The *bootable* config option in the *volume_feature_enabled* group is
+ removed because the corresponding feature os-set_bootable has been
+ implemented 2.5 years ago and all OpenStack versions which are supported
+ by Tempest should support the feature.
diff --git a/releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml b/releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
new file mode 100644
index 0000000..a734d15
--- /dev/null
+++ b/releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+ - The Negative Tests Generator has been removed (it was not used by any
+ Tempest tests).
diff --git a/releasenotes/notes/remove-sahara-tests-1532c47c7df80e3a.yaml b/releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
similarity index 100%
rename from releasenotes/notes/remove-sahara-tests-1532c47c7df80e3a.yaml
rename to releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
diff --git a/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml b/releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
similarity index 82%
rename from releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
rename to releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
index 056e199..6babd93 100644
--- a/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
+++ b/releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
@@ -8,3 +8,5 @@
* volumes_client(v1)
* volumes_client(v2)
+ * capabilities_client(v2)
+ * scheduler_stats_client(v2)
diff --git a/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
new file mode 100644
index 0000000..1af1939
--- /dev/null
+++ b/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - Define the identity v3 service client domains_client as a library.
+ Add domains_client to the library interface so the other
+ projects can use this module as a stable library without any
+ maintenance changes.
diff --git a/requirements.txt b/requirements.txt
index ea73180..d9a9ebb 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.8 # Apache-2.0
-cliff>=2.2.0 # Apache-2.0
+cliff>=2.3.0 # Apache-2.0
jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
testtools>=1.4.0 # MIT
paramiko>=2.0 # LGPLv2.1+
diff --git a/tempest/api/baremetal/README.rst b/tempest/api/baremetal/README.rst
deleted file mode 100644
index 759c937..0000000
--- a/tempest/api/baremetal/README.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-Tempest Field Guide to Baremetal API tests
-==========================================
-
-
-What are these tests?
----------------------
-
-These tests stress the OpenStack baremetal provisioning API provided by
-Ironic.
-
-
-Why are these tests in tempest?
-------------------------------
-
-The purpose of these tests is to exercise the various APIs provided by Ironic
-for managing baremetal nodes.
-
-
-Scope of these tests
---------------------
-
-The baremetal API test perform basic CRUD operations on the Ironic node
-inventory. They do not actually perform hardware provisioning. It is important
-to note that all Ironic API actions are admin operations meant to be used
-either by cloud operators or other OpenStack services (i.e., Nova).
diff --git a/tempest/api/baremetal/admin/__init__.py b/tempest/api/baremetal/admin/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/baremetal/admin/__init__.py
+++ /dev/null
diff --git a/tempest/api/baremetal/admin/base.py b/tempest/api/baremetal/admin/base.py
deleted file mode 100644
index ac5986c..0000000
--- a/tempest/api/baremetal/admin/base.py
+++ /dev/null
@@ -1,206 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import functools
-
-from tempest.common.utils import data_utils
-from tempest import config
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-CONF = config.CONF
-
-
-# NOTE(adam_g): The baremetal API tests exercise operations such as enroll
-# node, power on, power off, etc. Testing against real drivers (ie, IPMI)
-# will require passing driver-specific data to Tempest (addresses,
-# credentials, etc). Until then, only support testing against the fake driver,
-# which has no external dependencies.
-SUPPORTED_DRIVERS = ['fake']
-
-# NOTE(jroll): resources must be deleted in a specific order, this list
-# defines the resource types to clean up, and the correct order.
-RESOURCE_TYPES = ['port', 'node', 'chassis']
-
-
-def creates(resource):
- """Decorator that adds resources to the appropriate cleanup list."""
-
- def decorator(f):
- @functools.wraps(f)
- def wrapper(cls, *args, **kwargs):
- resp, body = f(cls, *args, **kwargs)
-
- if 'uuid' in body:
- cls.created_objects[resource].add(body['uuid'])
-
- return resp, body
- return wrapper
- return decorator
-
-
-class BaseBaremetalTest(test.BaseTestCase):
- """Base class for Baremetal API tests."""
-
- credentials = ['admin']
-
- @classmethod
- def skip_checks(cls):
- super(BaseBaremetalTest, cls).skip_checks()
- if not CONF.service_available.ironic:
- skip_msg = ('%s skipped as Ironic is not available' % cls.__name__)
- raise cls.skipException(skip_msg)
-
- if CONF.baremetal.driver not in SUPPORTED_DRIVERS:
- skip_msg = ('%s skipped as Ironic driver %s is not supported for '
- 'testing.' %
- (cls.__name__, CONF.baremetal.driver))
- raise cls.skipException(skip_msg)
-
- @classmethod
- def setup_clients(cls):
- super(BaseBaremetalTest, cls).setup_clients()
- cls.client = cls.os_admin.baremetal_client
-
- @classmethod
- def resource_setup(cls):
- super(BaseBaremetalTest, cls).resource_setup()
-
- cls.driver = CONF.baremetal.driver
- cls.power_timeout = CONF.baremetal.power_timeout
- cls.created_objects = {}
- for resource in RESOURCE_TYPES:
- cls.created_objects[resource] = set()
-
- @classmethod
- def resource_cleanup(cls):
- """Ensure that all created objects get destroyed."""
-
- try:
- for resource in RESOURCE_TYPES:
- uuids = cls.created_objects[resource]
- delete_method = getattr(cls.client, 'delete_%s' % resource)
- for u in uuids:
- delete_method(u, ignore_errors=lib_exc.NotFound)
- finally:
- super(BaseBaremetalTest, cls).resource_cleanup()
-
- @classmethod
- @creates('chassis')
- def create_chassis(cls, description=None):
- """Wrapper utility for creating test chassis.
-
- :param description: A description of the chassis. If not supplied,
- a random value will be generated.
- :return: Created chassis.
-
- """
- description = description or data_utils.rand_name('test-chassis')
- resp, body = cls.client.create_chassis(description=description)
- return resp, body
-
- @classmethod
- @creates('node')
- def create_node(cls, chassis_id, cpu_arch='x86', cpus=8, local_gb=10,
- memory_mb=4096):
- """Wrapper utility for creating test baremetal nodes.
-
- :param chassis_id: The unique identifier of the chassis.
- :param cpu_arch: CPU architecture of the node. Default: x86.
- :param cpus: Number of CPUs. Default: 8.
- :param local_gb: Disk size. Default: 10.
- :param memory_mb: Available RAM. Default: 4096.
- :return: Created node.
-
- """
- resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch,
- cpus=cpus, local_gb=local_gb,
- memory_mb=memory_mb,
- driver=cls.driver)
-
- return resp, body
-
- @classmethod
- @creates('port')
- def create_port(cls, node_id, address, extra=None, uuid=None):
- """Wrapper utility for creating test ports.
-
- :param node_id: The unique identifier of the node.
- :param address: MAC address of the port.
- :param extra: Meta data of the port. If not supplied, an empty
- dictionary will be created.
- :param uuid: UUID of the port.
- :return: Created port.
-
- """
- extra = extra or {}
- resp, body = cls.client.create_port(address=address, node_id=node_id,
- extra=extra, uuid=uuid)
-
- return resp, body
-
- @classmethod
- def delete_chassis(cls, chassis_id):
- """Deletes a chassis having the specified UUID.
-
- :param chassis_id: The unique identifier of the chassis.
- :return: Server response.
-
- """
-
- resp, body = cls.client.delete_chassis(chassis_id)
-
- if chassis_id in cls.created_objects['chassis']:
- cls.created_objects['chassis'].remove(chassis_id)
-
- return resp
-
- @classmethod
- def delete_node(cls, node_id):
- """Deletes a node having the specified UUID.
-
- :param node_id: The unique identifier of the node.
- :return: Server response.
-
- """
-
- resp, body = cls.client.delete_node(node_id)
-
- if node_id in cls.created_objects['node']:
- cls.created_objects['node'].remove(node_id)
-
- return resp
-
- @classmethod
- def delete_port(cls, port_id):
- """Deletes a port having the specified UUID.
-
- :param port_id: The unique identifier of the port.
- :return: Server response.
-
- """
-
- resp, body = cls.client.delete_port(port_id)
-
- if port_id in cls.created_objects['port']:
- cls.created_objects['port'].remove(port_id)
-
- return resp
-
- def validate_self_link(self, resource, uuid, link):
- """Check whether the given self link formatted correctly."""
- expected_link = "{base}/{pref}/{res}/{uuid}".format(
- base=self.client.base_url,
- pref=self.client.uri_prefix,
- res=resource,
- uuid=uuid)
- self.assertEqual(expected_link, link)
diff --git a/tempest/api/baremetal/admin/test_api_discovery.py b/tempest/api/baremetal/admin/test_api_discovery.py
deleted file mode 100644
index 41388ad..0000000
--- a/tempest/api/baremetal/admin/test_api_discovery.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.baremetal.admin import base
-from tempest import test
-
-
-class TestApiDiscovery(base.BaseBaremetalTest):
- """Tests for API discovery features."""
-
- @test.idempotent_id('a3c27e94-f56c-42c4-8600-d6790650b9c5')
- def test_api_versions(self):
- _, descr = self.client.get_api_description()
- expected_versions = ('v1',)
- versions = [version['id'] for version in descr['versions']]
-
- for v in expected_versions:
- self.assertIn(v, versions)
-
- @test.idempotent_id('896283a6-488e-4f31-af78-6614286cbe0d')
- def test_default_version(self):
- _, descr = self.client.get_api_description()
- default_version = descr['default_version']
- self.assertEqual(default_version['id'], 'v1')
-
- @test.idempotent_id('abc0b34d-e684-4546-9728-ab7a9ad9f174')
- def test_version_1_resources(self):
- _, descr = self.client.get_version_description(version='v1')
- expected_resources = ('nodes', 'chassis',
- 'ports', 'links', 'media_types')
-
- for res in expected_resources:
- self.assertIn(res, descr)
diff --git a/tempest/api/baremetal/admin/test_chassis.py b/tempest/api/baremetal/admin/test_chassis.py
deleted file mode 100644
index 339aaea..0000000
--- a/tempest/api/baremetal/admin/test_chassis.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# -*- coding: utf-8 -*-
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import six
-
-from tempest.api.baremetal.admin import base
-from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-class TestChassis(base.BaseBaremetalTest):
- """Tests for chassis."""
-
- @classmethod
- def resource_setup(cls):
- super(TestChassis, cls).resource_setup()
- _, cls.chassis = cls.create_chassis()
-
- def _assertExpected(self, expected, actual):
- # Check if not expected keys/values exists in actual response body
- for key, value in six.iteritems(expected):
- if key not in ('created_at', 'updated_at'):
- self.assertIn(key, actual)
- self.assertEqual(value, actual[key])
-
- @test.idempotent_id('7c5a2e09-699c-44be-89ed-2bc189992d42')
- def test_create_chassis(self):
- descr = data_utils.rand_name('test-chassis')
- _, chassis = self.create_chassis(description=descr)
- self.assertEqual(chassis['description'], descr)
-
- @test.idempotent_id('cabe9c6f-dc16-41a7-b6b9-0a90c212edd5')
- def test_create_chassis_unicode_description(self):
- # Use a unicode string for testing:
- # 'We ♡ OpenStack in Ukraine'
- descr = u'В Україні ♡ OpenStack!'
- _, chassis = self.create_chassis(description=descr)
- self.assertEqual(chassis['description'], descr)
-
- @test.idempotent_id('c84644df-31c4-49db-a307-8942881f41c0')
- def test_show_chassis(self):
- _, chassis = self.client.show_chassis(self.chassis['uuid'])
- self._assertExpected(self.chassis, chassis)
-
- @test.idempotent_id('29c9cd3f-19b5-417b-9864-99512c3b33b3')
- def test_list_chassis(self):
- _, body = self.client.list_chassis()
- self.assertIn(self.chassis['uuid'],
- [i['uuid'] for i in body['chassis']])
-
- @test.idempotent_id('5ae649ad-22d1-4fe1-bbc6-97227d199fb3')
- def test_delete_chassis(self):
- _, body = self.create_chassis()
- uuid = body['uuid']
-
- self.delete_chassis(uuid)
- self.assertRaises(lib_exc.NotFound, self.client.show_chassis, uuid)
-
- @test.idempotent_id('cda8a41f-6be2-4cbf-840c-994b00a89b44')
- def test_update_chassis(self):
- _, body = self.create_chassis()
- uuid = body['uuid']
-
- new_description = data_utils.rand_name('new-description')
- _, body = (self.client.update_chassis(uuid,
- description=new_description))
- _, chassis = self.client.show_chassis(uuid)
- self.assertEqual(chassis['description'], new_description)
-
- @test.idempotent_id('76305e22-a4e2-4ab3-855c-f4e2368b9335')
- def test_chassis_node_list(self):
- _, node = self.create_node(self.chassis['uuid'])
- _, body = self.client.list_chassis_nodes(self.chassis['uuid'])
- self.assertIn(node['uuid'], [n['uuid'] for n in body['nodes']])
diff --git a/tempest/api/baremetal/admin/test_drivers.py b/tempest/api/baremetal/admin/test_drivers.py
deleted file mode 100644
index f08d7ab..0000000
--- a/tempest/api/baremetal/admin/test_drivers.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright 2014 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.baremetal.admin import base
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-
-class TestDrivers(base.BaseBaremetalTest):
- """Tests for drivers."""
- @classmethod
- def resource_setup(cls):
- super(TestDrivers, cls).resource_setup()
- cls.driver_name = CONF.baremetal.driver
-
- @test.idempotent_id('5aed2790-7592-4655-9b16-99abcc2e6ec5')
- def test_list_drivers(self):
- _, drivers = self.client.list_drivers()
- self.assertIn(self.driver_name,
- [d['name'] for d in drivers['drivers']])
-
- @test.idempotent_id('fb3287a3-c4d7-44bf-ae9d-1eef906d78ce')
- def test_show_driver(self):
- _, driver = self.client.show_driver(self.driver_name)
- self.assertEqual(self.driver_name, driver['name'])
diff --git a/tempest/api/baremetal/admin/test_nodes.py b/tempest/api/baremetal/admin/test_nodes.py
deleted file mode 100644
index 2c44665..0000000
--- a/tempest/api/baremetal/admin/test_nodes.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import six
-
-from tempest.api.baremetal.admin import base
-from tempest.common.utils import data_utils
-from tempest.common import waiters
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-class TestNodes(base.BaseBaremetalTest):
- """Tests for baremetal nodes."""
-
- def setUp(self):
- super(TestNodes, self).setUp()
-
- _, self.chassis = self.create_chassis()
- _, self.node = self.create_node(self.chassis['uuid'])
-
- def _assertExpected(self, expected, actual):
- # Check if not expected keys/values exists in actual response body
- for key, value in six.iteritems(expected):
- if key not in ('created_at', 'updated_at'):
- self.assertIn(key, actual)
- self.assertEqual(value, actual[key])
-
- def _associate_node_with_instance(self):
- self.client.set_node_power_state(self.node['uuid'], 'power off')
- waiters.wait_for_bm_node_status(self.client, self.node['uuid'],
- 'power_state', 'power off')
- instance_uuid = data_utils.rand_uuid()
- self.client.update_node(self.node['uuid'],
- instance_uuid=instance_uuid)
- self.addCleanup(self.client.update_node,
- uuid=self.node['uuid'], instance_uuid=None)
- return instance_uuid
-
- @test.idempotent_id('4e939eb2-8a69-4e84-8652-6fffcbc9db8f')
- def test_create_node(self):
- params = {'cpu_arch': 'x86_64',
- 'cpus': '12',
- 'local_gb': '10',
- 'memory_mb': '1024'}
-
- _, body = self.create_node(self.chassis['uuid'], **params)
- self._assertExpected(params, body['properties'])
-
- @test.idempotent_id('9ade60a4-505e-4259-9ec4-71352cbbaf47')
- def test_delete_node(self):
- _, node = self.create_node(self.chassis['uuid'])
-
- self.delete_node(node['uuid'])
-
- self.assertRaises(lib_exc.NotFound, self.client.show_node,
- node['uuid'])
-
- @test.idempotent_id('55451300-057c-4ecf-8255-ba42a83d3a03')
- def test_show_node(self):
- _, loaded_node = self.client.show_node(self.node['uuid'])
- self._assertExpected(self.node, loaded_node)
-
- @test.idempotent_id('4ca123c4-160d-4d8d-a3f7-15feda812263')
- def test_list_nodes(self):
- _, body = self.client.list_nodes()
- self.assertIn(self.node['uuid'],
- [i['uuid'] for i in body['nodes']])
-
- @test.idempotent_id('85b1f6e0-57fd-424c-aeff-c3422920556f')
- def test_list_nodes_association(self):
- _, body = self.client.list_nodes(associated=True)
- self.assertNotIn(self.node['uuid'],
- [n['uuid'] for n in body['nodes']])
-
- self._associate_node_with_instance()
-
- _, body = self.client.list_nodes(associated=True)
- self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']])
-
- _, body = self.client.list_nodes(associated=False)
- self.assertNotIn(self.node['uuid'], [n['uuid'] for n in body['nodes']])
-
- @test.idempotent_id('18c4ebd8-f83a-4df7-9653-9fb33a329730')
- def test_node_port_list(self):
- _, port = self.create_port(self.node['uuid'],
- data_utils.rand_mac_address())
- _, body = self.client.list_node_ports(self.node['uuid'])
- self.assertIn(port['uuid'],
- [p['uuid'] for p in body['ports']])
-
- @test.idempotent_id('72591acb-f215-49db-8395-710d14eb86ab')
- def test_node_port_list_no_ports(self):
- _, node = self.create_node(self.chassis['uuid'])
- _, body = self.client.list_node_ports(node['uuid'])
- self.assertEmpty(body['ports'])
-
- @test.idempotent_id('4fed270a-677a-4d19-be87-fd38ae490320')
- def test_update_node(self):
- props = {'cpu_arch': 'x86_64',
- 'cpus': '12',
- 'local_gb': '10',
- 'memory_mb': '128'}
-
- _, node = self.create_node(self.chassis['uuid'], **props)
-
- new_p = {'cpu_arch': 'x86',
- 'cpus': '1',
- 'local_gb': '10000',
- 'memory_mb': '12300'}
-
- _, body = self.client.update_node(node['uuid'], properties=new_p)
- _, node = self.client.show_node(node['uuid'])
- self._assertExpected(new_p, node['properties'])
-
- @test.idempotent_id('cbf1f515-5f4b-4e49-945c-86bcaccfeb1d')
- def test_validate_driver_interface(self):
- _, body = self.client.validate_driver_interface(self.node['uuid'])
- core_interfaces = ['power', 'deploy']
- for interface in core_interfaces:
- self.assertIn(interface, body)
-
- @test.idempotent_id('5519371c-26a2-46e9-aa1a-f74226e9d71f')
- def test_set_node_boot_device(self):
- self.client.set_node_boot_device(self.node['uuid'], 'pxe')
-
- @test.idempotent_id('9ea73775-f578-40b9-bc34-efc639c4f21f')
- def test_get_node_boot_device(self):
- body = self.client.get_node_boot_device(self.node['uuid'])
- self.assertIn('boot_device', body)
- self.assertIn('persistent', body)
- self.assertIsInstance(body['boot_device'], six.string_types)
- self.assertIsInstance(body['persistent'], bool)
-
- @test.idempotent_id('3622bc6f-3589-4bc2-89f3-50419c66b133')
- def test_get_node_supported_boot_devices(self):
- body = self.client.get_node_supported_boot_devices(self.node['uuid'])
- self.assertIn('supported_boot_devices', body)
- self.assertIsInstance(body['supported_boot_devices'], list)
-
- @test.idempotent_id('f63b6288-1137-4426-8cfe-0d5b7eb87c06')
- def test_get_console(self):
- _, body = self.client.get_console(self.node['uuid'])
- con_info = ['console_enabled', 'console_info']
- for key in con_info:
- self.assertIn(key, body)
-
- @test.idempotent_id('80504575-9b21-4670-92d1-143b948f9437')
- def test_set_console_mode(self):
- self.client.set_console_mode(self.node['uuid'], True)
-
- _, body = self.client.get_console(self.node['uuid'])
- self.assertEqual(True, body['console_enabled'])
-
- @test.idempotent_id('b02a4f38-5e8b-44b2-aed2-a69a36ecfd69')
- def test_get_node_by_instance_uuid(self):
- instance_uuid = self._associate_node_with_instance()
- _, body = self.client.show_node_by_instance_uuid(instance_uuid)
- self.assertEqual(len(body['nodes']), 1)
- self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']])
diff --git a/tempest/api/baremetal/admin/test_nodestates.py b/tempest/api/baremetal/admin/test_nodestates.py
deleted file mode 100644
index e74dd04..0000000
--- a/tempest/api/baremetal/admin/test_nodestates.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2014 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_utils import timeutils
-
-from tempest.api.baremetal.admin import base
-from tempest.lib import exceptions
-from tempest import test
-
-
-class TestNodeStates(base.BaseBaremetalTest):
- """Tests for baremetal NodeStates."""
-
- @classmethod
- def resource_setup(cls):
- super(TestNodeStates, cls).resource_setup()
- _, cls.chassis = cls.create_chassis()
- _, cls.node = cls.create_node(cls.chassis['uuid'])
-
- def _validate_power_state(self, node_uuid, power_state):
- # Validate that power state is set within timeout
- if power_state == 'rebooting':
- power_state = 'power on'
- start = timeutils.utcnow()
- while timeutils.delta_seconds(
- start, timeutils.utcnow()) < self.power_timeout:
- _, node = self.client.show_node(node_uuid)
- if node['power_state'] == power_state:
- return
- message = ('Failed to set power state within '
- 'the required time: %s sec.' % self.power_timeout)
- raise exceptions.TimeoutException(message)
-
- @test.idempotent_id('cd8afa5e-3f57-4e43-8185-beb83d3c9015')
- def test_list_nodestates(self):
- _, nodestates = self.client.list_nodestates(self.node['uuid'])
- for key in nodestates:
- self.assertEqual(nodestates[key], self.node[key])
-
- @test.idempotent_id('fc5b9320-0c98-4e5a-8848-877fe5a0322c')
- def test_set_node_power_state(self):
- _, node = self.create_node(self.chassis['uuid'])
- states = ["power on", "rebooting", "power off"]
- for state in states:
- # Set power state
- self.client.set_node_power_state(node['uuid'], state)
- # Check power state after state is set
- self._validate_power_state(node['uuid'], state)
diff --git a/tempest/api/baremetal/admin/test_ports.py b/tempest/api/baremetal/admin/test_ports.py
deleted file mode 100644
index ce519c1..0000000
--- a/tempest/api/baremetal/admin/test_ports.py
+++ /dev/null
@@ -1,266 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import six
-
-from tempest.api.baremetal.admin import base
-from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-class TestPorts(base.BaseBaremetalTest):
- """Tests for ports."""
-
- def setUp(self):
- super(TestPorts, self).setUp()
-
- _, self.chassis = self.create_chassis()
- _, self.node = self.create_node(self.chassis['uuid'])
- _, self.port = self.create_port(self.node['uuid'],
- data_utils.rand_mac_address())
-
- def _assertExpected(self, expected, actual):
- # Check if not expected keys/values exists in actual response body
- for key, value in six.iteritems(expected):
- if key not in ('created_at', 'updated_at'):
- self.assertIn(key, actual)
- self.assertEqual(value, actual[key])
-
- @test.idempotent_id('83975898-2e50-42ed-b5f0-e510e36a0b56')
- def test_create_port(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
-
- _, body = self.client.show_port(port['uuid'])
-
- self._assertExpected(port, body)
-
- @test.idempotent_id('d1f6b249-4cf6-4fe6-9ed6-a6e84b1bf67b')
- def test_create_port_specifying_uuid(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- uuid = data_utils.rand_uuid()
-
- _, port = self.create_port(node_id=node_id,
- address=address, uuid=uuid)
-
- _, body = self.client.show_port(uuid)
- self._assertExpected(port, body)
-
- @test.idempotent_id('4a02c4b0-6573-42a4-a513-2e36ad485b62')
- def test_create_port_with_extra(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'str': 'value', 'int': 123, 'float': 0.123,
- 'bool': True, 'list': [1, 2, 3], 'dict': {'foo': 'bar'}}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
-
- _, body = self.client.show_port(port['uuid'])
- self._assertExpected(port, body)
-
- @test.idempotent_id('1bf257a9-aea3-494e-89c0-63f657ab4fdd')
- def test_delete_port(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- _, port = self.create_port(node_id=node_id, address=address)
-
- self.delete_port(port['uuid'])
-
- self.assertRaises(lib_exc.NotFound, self.client.show_port,
- port['uuid'])
-
- @test.idempotent_id('9fa77ab5-ce59-4f05-baac-148904ba1597')
- def test_show_port(self):
- _, port = self.client.show_port(self.port['uuid'])
- self._assertExpected(self.port, port)
-
- @test.idempotent_id('7c1114ff-fc3f-47bb-bc2f-68f61620ba8b')
- def test_show_port_by_address(self):
- _, port = self.client.show_port_by_address(self.port['address'])
- self._assertExpected(self.port, port['ports'][0])
-
- @test.idempotent_id('bd773405-aea5-465d-b576-0ab1780069e5')
- def test_show_port_with_links(self):
- _, port = self.client.show_port(self.port['uuid'])
- self.assertIn('links', port.keys())
- self.assertEqual(2, len(port['links']))
- self.assertIn(port['uuid'], port['links'][0]['href'])
-
- @test.idempotent_id('b5e91854-5cd7-4a8e-bb35-3e0a1314606d')
- def test_list_ports(self):
- _, body = self.client.list_ports()
- self.assertIn(self.port['uuid'],
- [i['uuid'] for i in body['ports']])
- # Verify self links.
- for port in body['ports']:
- self.validate_self_link('ports', port['uuid'],
- port['links'][0]['href'])
-
- @test.idempotent_id('324a910e-2f80-4258-9087-062b5ae06240')
- def test_list_with_limit(self):
- _, body = self.client.list_ports(limit=3)
-
- next_marker = body['ports'][-1]['uuid']
- self.assertIn(next_marker, body['next'])
-
- @test.idempotent_id('8a94b50f-9895-4a63-a574-7ecff86e5875')
- def test_list_ports_details(self):
- node_id = self.node['uuid']
-
- uuids = [
- self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
- [1]['uuid'] for i in range(0, 5)]
-
- _, body = self.client.list_ports_detail()
-
- ports_dict = dict((port['uuid'], port) for port in body['ports']
- if port['uuid'] in uuids)
-
- for uuid in uuids:
- self.assertIn(uuid, ports_dict)
- port = ports_dict[uuid]
- self.assertIn('extra', port)
- self.assertIn('node_uuid', port)
- # never expose the node_id
- self.assertNotIn('node_id', port)
- # Verify self link.
- self.validate_self_link('ports', port['uuid'],
- port['links'][0]['href'])
-
- @test.idempotent_id('8a03f688-7d75-4ecd-8cbc-e06b8f346738')
- def test_list_ports_details_with_address(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- self.create_port(node_id=node_id, address=address)
- for i in range(0, 5):
- self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
-
- _, body = self.client.list_ports_detail(address=address)
- self.assertEqual(1, len(body['ports']))
- self.assertEqual(address, body['ports'][0]['address'])
-
- @test.idempotent_id('9c26298b-1bcb-47b7-9b9e-8bdd6e3c4aba')
- def test_update_port_replace(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
-
- new_address = data_utils.rand_mac_address()
- new_extra = {'key1': 'new-value1', 'key2': 'new-value2',
- 'key3': 'new-value3'}
-
- patch = [{'path': '/address',
- 'op': 'replace',
- 'value': new_address},
- {'path': '/extra/key1',
- 'op': 'replace',
- 'value': new_extra['key1']},
- {'path': '/extra/key2',
- 'op': 'replace',
- 'value': new_extra['key2']},
- {'path': '/extra/key3',
- 'op': 'replace',
- 'value': new_extra['key3']}]
-
- self.client.update_port(port['uuid'], patch)
-
- _, body = self.client.show_port(port['uuid'])
- self.assertEqual(new_address, body['address'])
- self.assertEqual(new_extra, body['extra'])
-
- @test.idempotent_id('d7e7fece-6ed9-460a-9ebe-9267217e8580')
- def test_update_port_remove(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
-
- # Removing one item from the collection
- self.client.update_port(port['uuid'],
- [{'path': '/extra/key2',
- 'op': 'remove'}])
- extra.pop('key2')
- _, body = self.client.show_port(port['uuid'])
- self.assertEqual(extra, body['extra'])
-
- # Removing the collection
- self.client.update_port(port['uuid'], [{'path': '/extra',
- 'op': 'remove'}])
- _, body = self.client.show_port(port['uuid'])
- self.assertEqual({}, body['extra'])
-
- # Assert nothing else was changed
- self.assertEqual(node_id, body['node_uuid'])
- self.assertEqual(address, body['address'])
-
- @test.idempotent_id('241288b3-e98a-400f-a4d7-d1f716146361')
- def test_update_port_add(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
-
- extra = {'key1': 'value1', 'key2': 'value2'}
-
- patch = [{'path': '/extra/key1',
- 'op': 'add',
- 'value': extra['key1']},
- {'path': '/extra/key2',
- 'op': 'add',
- 'value': extra['key2']}]
-
- self.client.update_port(port['uuid'], patch)
-
- _, body = self.client.show_port(port['uuid'])
- self.assertEqual(extra, body['extra'])
-
- @test.idempotent_id('5309e897-0799-4649-a982-0179b04c3876')
- def test_update_port_mixed_ops(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key1': 'value1', 'key2': 'value2'}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
-
- new_address = data_utils.rand_mac_address()
- new_extra = {'key1': 0.123, 'key3': {'cat': 'meow'}}
-
- patch = [{'path': '/address',
- 'op': 'replace',
- 'value': new_address},
- {'path': '/extra/key1',
- 'op': 'replace',
- 'value': new_extra['key1']},
- {'path': '/extra/key2',
- 'op': 'remove'},
- {'path': '/extra/key3',
- 'op': 'add',
- 'value': new_extra['key3']}]
-
- self.client.update_port(port['uuid'], patch)
-
- _, body = self.client.show_port(port['uuid'])
- self.assertEqual(new_address, body['address'])
- self.assertEqual(new_extra, body['extra'])
diff --git a/tempest/api/baremetal/admin/test_ports_negative.py b/tempest/api/baremetal/admin/test_ports_negative.py
deleted file mode 100644
index 5e3a33f..0000000
--- a/tempest/api/baremetal/admin/test_ports_negative.py
+++ /dev/null
@@ -1,338 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.baremetal.admin import base
-from tempest.common.utils import data_utils
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-
-class TestPortsNegative(base.BaseBaremetalTest):
- """Negative tests for ports."""
-
- def setUp(self):
- super(TestPortsNegative, self).setUp()
-
- _, self.chassis = self.create_chassis()
- _, self.node = self.create_node(self.chassis['uuid'])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('0a6ee1f7-d0d9-4069-8778-37f3aa07303a')
- def test_create_port_malformed_mac(self):
- node_id = self.node['uuid']
- address = 'malformed:mac'
-
- self.assertRaises(lib_exc.BadRequest,
- self.create_port, node_id=node_id, address=address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('30277ee8-0c60-4f1d-b125-0e51c2f43369')
- def test_create_port_nonexsistent_node_id(self):
- node_id = data_utils.rand_uuid()
- address = data_utils.rand_mac_address()
- self.assertRaises(lib_exc.BadRequest, self.create_port,
- node_id=node_id, address=address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('029190f6-43e1-40a3-b64a-65173ba653a3')
- def test_show_port_malformed_uuid(self):
- self.assertRaises(lib_exc.BadRequest, self.client.show_port,
- 'malformed:uuid')
-
- @test.attr(type=['negative'])
- @test.idempotent_id('0d00e13d-e2e0-45b1-bcbc-55a6d90ca793')
- def test_show_port_nonexistent_uuid(self):
- self.assertRaises(lib_exc.NotFound, self.client.show_port,
- data_utils.rand_uuid())
-
- @test.attr(type=['negative'])
- @test.idempotent_id('4ad85266-31e9-4942-99ac-751897dc9e23')
- def test_show_port_by_mac_not_allowed(self):
- self.assertRaises(lib_exc.BadRequest, self.client.show_port,
- data_utils.rand_mac_address())
-
- @test.attr(type=['negative'])
- @test.idempotent_id('89a34380-3c61-4c32-955c-2cd9ce94da21')
- def test_create_port_duplicated_port_uuid(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- uuid = data_utils.rand_uuid()
-
- self.create_port(node_id=node_id, address=address, uuid=uuid)
- self.assertRaises(lib_exc.Conflict, self.create_port, node_id=node_id,
- address=address, uuid=uuid)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('65e84917-733c-40ae-ae4b-96a4adff931c')
- def test_create_port_no_mandatory_field_node_id(self):
- address = data_utils.rand_mac_address()
-
- self.assertRaises(lib_exc.BadRequest, self.create_port, node_id=None,
- address=address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('bcea3476-7033-4183-acfe-e56a30809b46')
- def test_create_port_no_mandatory_field_mac(self):
- node_id = self.node['uuid']
-
- self.assertRaises(lib_exc.BadRequest, self.create_port,
- node_id=node_id, address=None)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('2b51cd18-fb95-458b-9780-e6257787b649')
- def test_create_port_malformed_port_uuid(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- uuid = 'malformed:uuid'
-
- self.assertRaises(lib_exc.BadRequest, self.create_port,
- node_id=node_id, address=address, uuid=uuid)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('583a6856-6a30-4ac4-889f-14e2adff8105')
- def test_create_port_malformed_node_id(self):
- address = data_utils.rand_mac_address()
- self.assertRaises(lib_exc.BadRequest, self.create_port,
- node_id='malformed:nodeid', address=address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('e27f8b2e-42c6-4a43-a3cd-accff716bc5c')
- def test_create_port_duplicated_mac(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- self.create_port(node_id=node_id, address=address)
- self.assertRaises(lib_exc.Conflict,
- self.create_port, node_id=node_id,
- address=address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('8907082d-ac5e-4be3-b05f-d072ede82020')
- def test_update_port_by_mac_not_allowed(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
-
- self.create_port(node_id=node_id, address=address, extra=extra)
-
- patch = [{'path': '/extra/key',
- 'op': 'replace',
- 'value': 'new-value'}]
-
- self.assertRaises(lib_exc.BadRequest,
- self.client.update_port, address,
- patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('df1ac70c-db9f-41d9-90f1-78cd6b905718')
- def test_update_port_nonexistent(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
- port_id = port['uuid']
-
- _, body = self.client.delete_port(port_id)
-
- patch = [{'path': '/extra/key',
- 'op': 'replace',
- 'value': 'new-value'}]
- self.assertRaises(lib_exc.NotFound,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('c701e315-aa52-41ea-817c-65c5ca8ca2a8')
- def test_update_port_malformed_port_uuid(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- self.create_port(node_id=node_id, address=address)
-
- new_address = data_utils.rand_mac_address()
- self.assertRaises(lib_exc.BadRequest, self.client.update_port,
- uuid='malformed:uuid',
- patch=[{'path': '/address', 'op': 'replace',
- 'value': new_address}])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('f8f15803-34d6-45dc-b06f-e5e04bf1b38b')
- def test_update_port_add_nonexistent_property(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id,
- [{'path': '/nonexistent', ' op': 'add',
- 'value': 'value'}])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('898ec904-38b1-4fcb-9584-1187d4263a2a')
- def test_update_port_replace_node_id_with_malformed(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- patch = [{'path': '/node_uuid',
- 'op': 'replace',
- 'value': 'malformed:node_uuid'}]
- self.assertRaises(lib_exc.BadRequest,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('2949f30f-5f59-43fa-a6d9-4eac578afab4')
- def test_update_port_replace_mac_with_duplicated(self):
- node_id = self.node['uuid']
- address1 = data_utils.rand_mac_address()
- address2 = data_utils.rand_mac_address()
-
- _, port1 = self.create_port(node_id=node_id, address=address1)
-
- _, port2 = self.create_port(node_id=node_id, address=address2)
- port_id = port2['uuid']
-
- patch = [{'path': '/address',
- 'op': 'replace',
- 'value': address1}]
- self.assertRaises(lib_exc.Conflict,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('97f6e048-6e4f-4eba-a09d-fbbc78b77a77')
- def test_update_port_replace_node_id_with_nonexistent(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- patch = [{'path': '/node_uuid',
- 'op': 'replace',
- 'value': data_utils.rand_uuid()}]
- self.assertRaises(lib_exc.BadRequest,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('375022c5-9e9e-4b11-9ca4-656729c0c9b2')
- def test_update_port_replace_mac_with_malformed(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- patch = [{'path': '/address',
- 'op': 'replace',
- 'value': 'malformed:mac'}]
-
- self.assertRaises(lib_exc.BadRequest,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('5722b853-03fc-4854-8308-2036a1b67d85')
- def test_update_port_replace_nonexistent_property(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}]
-
- self.assertRaises(lib_exc.BadRequest,
- self.client.update_port, port_id, patch)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('ae2696ca-930a-4a7f-918f-30ae97c60f56')
- def test_update_port_remove_mandatory_field_mac(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id,
- [{'path': '/address', 'op': 'remove'}])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('5392c1f0-2071-4697-9064-ec2d63019018')
- def test_update_port_remove_mandatory_field_port_uuid(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id,
- [{'path': '/uuid', 'op': 'remove'}])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('06b50d82-802a-47ef-b079-0a3311cf85a2')
- def test_update_port_remove_nonexistent_property(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- _, port = self.create_port(node_id=node_id, address=address)
- port_id = port['uuid']
-
- self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id,
- [{'path': '/nonexistent', 'op': 'remove'}])
-
- @test.attr(type=['negative'])
- @test.idempotent_id('03d42391-2145-4a6c-95bf-63fe55eb64fd')
- def test_delete_port_by_mac_not_allowed(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- self.create_port(node_id=node_id, address=address)
- self.assertRaises(lib_exc.BadRequest, self.client.delete_port, address)
-
- @test.attr(type=['negative'])
- @test.idempotent_id('0629e002-818e-4763-b25b-ae5e07b1cb23')
- def test_update_port_mixed_ops_integrity(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key1': 'value1', 'key2': 'value2'}
-
- _, port = self.create_port(node_id=node_id, address=address,
- extra=extra)
- port_id = port['uuid']
-
- new_address = data_utils.rand_mac_address()
- new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
-
- patch = [{'path': '/address',
- 'op': 'replace',
- 'value': new_address},
- {'path': '/extra/key1',
- 'op': 'replace',
- 'value': new_extra['key1']},
- {'path': '/extra/key2',
- 'op': 'remove'},
- {'path': '/extra/key3',
- 'op': 'add',
- 'value': new_extra['key3']},
- {'path': '/nonexistent',
- 'op': 'replace',
- 'value': 'value'}]
-
- self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id,
- patch)
-
- # patch should not be applied
- _, body = self.client.show_port(port_id)
- self.assertEqual(address, body['address'])
- self.assertEqual(extra, body['extra'])
diff --git a/tempest/api/compute/admin/test_agents.py b/tempest/api/compute/admin/test_agents.py
index 40cb523..1c37cad 100644
--- a/tempest/api/compute/admin/test_agents.py
+++ b/tempest/api/compute/admin/test_agents.py
@@ -12,14 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import test
-LOG = log.getLogger(__name__)
-
class AgentsAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Agents API"""
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 667d30b..1233a2b 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -18,9 +18,12 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
+from tempest import config
from tempest.lib.common.utils import test_utils
from tempest import test
+CONF = config.CONF
+
class AggregatesAdminTestJSON(base.BaseV2ComputeAdminTest):
"""Tests Aggregates API that require admin privileges"""
@@ -39,14 +42,22 @@
cls.host = None
hypers = cls.os_adm.hypervisor_client.list_hypervisors(
detail=True)['hypervisors']
+
+ if CONF.compute.hypervisor_type:
+ hypers = [hyper for hyper in hypers
+ if (hyper['hypervisor_type'] ==
+ CONF.compute.hypervisor_type)]
+
hosts_available = [hyper['service']['host'] for hyper in hypers
if (hyper['state'] == 'up' and
hyper['status'] == 'enabled')]
if hosts_available:
cls.host = hosts_available[0]
else:
- raise testtools.TestCase.failureException(
- "no available compute node found")
+ msg = "no available compute node found"
+ if CONF.compute.hypervisor_type:
+ msg += " for hypervisor_type %s" % CONF.compute.hypervisor_type
+ raise testtools.TestCase.failureException(msg)
@test.idempotent_id('0d148aa3-d54c-4317-aa8d-42040a475e20')
def test_aggregate_create_delete(self):
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
index 609eae6..85aa301 100644
--- a/tempest/api/compute/admin/test_aggregates_negative.py
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -177,6 +177,5 @@
def test_aggregate_remove_nonexistent_host(self):
aggregate = self._create_test_aggregate()
- non_exist_host = data_utils.rand_name('nonexist_host')
self.assertRaises(lib_exc.NotFound, self.client.remove_host,
- aggregate['id'], host=non_exist_host)
+ aggregate['id'], host='nonexist_host')
diff --git a/tempest/api/compute/admin/test_auto_allocate_network.py b/tempest/api/compute/admin/test_auto_allocate_network.py
index 4eb3376..8e481fd 100644
--- a/tempest/api/compute/admin/test_auto_allocate_network.py
+++ b/tempest/api/compute/admin/test_auto_allocate_network.py
@@ -19,8 +19,8 @@
from tempest.common import credentials_factory as credentials
from tempest.common import waiters
from tempest import config
-from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions as lib_excs
from tempest import test
CONF = config.CONF
@@ -82,14 +82,14 @@
nets = cls.networks_client.list_networks(
**search_opts).get('networks', [])
if nets:
- raise exceptions.TempestException(
+ raise lib_excs.TempestException(
'Found tenant networks: %s' % nets)
# (2) Retrieve shared network list.
search_opts = {'shared': True}
nets = cls.networks_client.list_networks(
**search_opts).get('networks', [])
if nets:
- raise exceptions.TempestException(
+ raise lib_excs.TempestException(
'Found shared networks: %s' % nets)
@classmethod
diff --git a/tempest/api/compute/admin/test_baremetal_nodes.py b/tempest/api/compute/admin/test_baremetal_nodes.py
index b764483..722d9a6 100644
--- a/tempest/api/compute/admin/test_baremetal_nodes.py
+++ b/tempest/api/compute/admin/test_baremetal_nodes.py
@@ -25,7 +25,7 @@
@classmethod
def resource_setup(cls):
super(BaremetalNodesAdminTestJSON, cls).resource_setup()
- if not CONF.service_available.ironic:
+ if not getattr(CONF.service_available, 'ironic', False):
skip_msg = ('%s skipped as Ironic is not available' % cls.__name__)
raise cls.skipException(skip_msg)
cls.client = cls.os_adm.baremetal_nodes_client
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index b0ab9be..7eb4bbf 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -102,11 +102,10 @@
@test.attr(type=['negative'])
@test.idempotent_id('440b9f3f-3c7f-4293-a106-0ceda350f8de')
def test_flavor_unset_nonexistent_key(self):
- nonexistent_key = data_utils.rand_name('flavor_key')
self.assertRaises(lib_exc.NotFound,
self.client.unset_flavor_extra_spec,
self.flavor['id'],
- nonexistent_key)
+ 'nonexistent_key')
@test.attr(type=['negative'])
@test.idempotent_id('329a7be3-54b2-48be-8052-bf2ce4afd898')
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
index 55134b1..1f043dc 100644
--- a/tempest/api/compute/admin/test_hypervisor.py
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -87,7 +87,7 @@
ironic_only = True
hypers_without_ironic = []
for hyper in hypers:
- details = (self.client.show_hypervisor(hypers[0]['id'])
+ details = (self.client.show_hypervisor(hyper['id'])
['hypervisor'])
if details['hypervisor_type'] != 'ironic':
hypers_without_ironic.append(hyper)
diff --git a/tempest/api/compute/admin/test_hypervisor_negative.py b/tempest/api/compute/admin/test_hypervisor_negative.py
index 220ea39..0f35e14 100644
--- a/tempest/api/compute/admin/test_hypervisor_negative.py
+++ b/tempest/api/compute/admin/test_hypervisor_negative.py
@@ -122,12 +122,10 @@
@test.attr(type=['negative'])
@test.idempotent_id('19a45cc1-1000-4055-b6d2-28e8b2ec4faa')
def test_search_nonexistent_hypervisor(self):
- nonexistent_hypervisor_name = data_utils.rand_name('test_hypervisor')
-
self.assertRaises(
lib_exc.NotFound,
self.client.search_hypervisor,
- nonexistent_hypervisor_name)
+ 'nonexistent_hypervisor_name')
@test.attr(type=['negative'])
@test.idempotent_id('5b6a6c79-5dc1-4fa5-9c58-9c8085948e74')
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 4f075eb..2dd2e89 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -31,7 +31,6 @@
super(MigrationsAdminTest, cls).setup_clients()
cls.client = cls.os_adm.migrations_client
cls.flavors_admin_client = cls.os_adm.flavors_client
- cls.admin_hosts_client = cls.os_adm.hosts_client
cls.admin_servers_client = cls.os_adm.servers_client
@test.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
@@ -47,12 +46,7 @@
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
- self.servers_client.resize_server(server_id, self.flavor_ref_alt)
- waiters.wait_for_server_status(self.servers_client,
- server_id, 'VERIFY_RESIZE')
- self.servers_client.confirm_resize_server(server_id)
- waiters.wait_for_server_status(self.servers_client,
- server_id, 'ACTIVE')
+ self.resize_server(server_id, self.flavor_ref_alt)
body = self.client.list_migrations()['migrations']
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index efa55d5..a8a8b83 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -29,7 +29,6 @@
super(ServersAdminTestJSON, cls).setup_clients()
cls.client = cls.os_adm.servers_client
cls.non_admin_client = cls.servers_client
- cls.flavors_client = cls.os_adm.flavors_client
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 23b16e7..206260f 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -72,8 +72,8 @@
raise self.skipException("ram quota set is -1,"
" cannot test overlimit")
ram += 1
- vcpus = 8
- disk = 10
+ vcpus = 1
+ disk = 5
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
ram=ram, vcpus=vcpus,
disk=disk,
@@ -93,7 +93,6 @@
self.useFixture(fixtures.LockFixture('compute_quotas'))
flavor_name = data_utils.rand_name("flavor")
flavor_id = self._get_unused_flavor_id()
- ram = 512
quota_set = self.quotas_client.show_quota_set(
self.tenant_id)['quota_set']
vcpus = int(quota_set['cores'])
@@ -101,7 +100,8 @@
raise self.skipException("cores quota set is -1,"
" cannot test overlimit")
vcpus += 1
- disk = 10
+ ram = 512
+ disk = 5
flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
ram=ram, vcpus=vcpus,
disk=disk,
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index d8294f7..d77ea90 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -75,7 +75,6 @@
cls.os.compute_security_group_rules_client)
cls.security_groups_client = cls.os.compute_security_groups_client
cls.quotas_client = cls.os.quotas_client
- cls.quota_classes_client = cls.os.quota_classes_client
cls.compute_networks_client = cls.os.compute_networks_client
cls.limits_client = cls.os.limits_client
cls.volumes_extensions_client = cls.os.volumes_extensions_client
@@ -140,15 +139,15 @@
test_utils.call_and_ignore_notfound_exc(
cls.servers_client.delete_server, server['id'])
except Exception:
- LOG.exception('Deleting server %s failed' % server['id'])
+ LOG.exception('Deleting server %s failed', server['id'])
for server in cls.servers:
try:
waiters.wait_for_server_termination(cls.servers_client,
server['id'])
except Exception:
- LOG.exception('Waiting for deletion of server %s failed'
- % server['id'])
+ LOG.exception('Waiting for deletion of server %s failed',
+ server['id'])
@classmethod
def server_check_teardown(cls):
@@ -180,7 +179,7 @@
test_utils.call_and_ignore_notfound_exc(
cls.compute_images_client.delete_image, image_id)
except Exception:
- LOG.exception('Exception raised deleting image %s' % image_id)
+ LOG.exception('Exception raised deleting image %s', image_id)
@classmethod
def clear_security_groups(cls):
@@ -284,7 +283,7 @@
volumes_client.wait_for_resource_deletion(volume_id)
except lib_exc.NotFound:
LOG.warning("Unable to delete volume '%s' since it was not found. "
- "Maybe it was already deleted?" % volume_id)
+ "Maybe it was already deleted?", volume_id)
@classmethod
def prepare_instance_network(cls):
@@ -319,12 +318,7 @@
def rebuild_server(cls, server_id, validatable=False, **kwargs):
# Destroy an existing server and creates a new one
if server_id:
- try:
- cls.servers_client.delete_server(server_id)
- waiters.wait_for_server_termination(cls.servers_client,
- server_id)
- except Exception:
- LOG.exception('Failed to delete server %s' % server_id)
+ cls.delete_server(server_id)
cls.password = data_utils.rand_password()
server = cls.create_test_server(
@@ -342,7 +336,16 @@
waiters.wait_for_server_termination(cls.servers_client,
server_id)
except Exception:
- LOG.exception('Failed to delete server %s' % server_id)
+ LOG.exception('Failed to delete server %s', server_id)
+
+ @classmethod
+ def resize_server(cls, server_id, new_flavor_id, **kwargs):
+ """resize and confirm_resize an server, waits for it to be ACTIVE."""
+ cls.servers_client.resize_server(server_id, new_flavor_id, **kwargs)
+ waiters.wait_for_server_status(cls.servers_client, server_id,
+ 'VERIFY_RESIZE')
+ cls.servers_client.confirm_resize_server(server_id)
+ waiters.wait_for_server_status(cls.servers_client, server_id, 'ACTIVE')
@classmethod
def delete_volume(cls, volume_id):
@@ -373,14 +376,21 @@
self.request_microversion))
@classmethod
- def create_volume(cls):
+ def create_volume(cls, image_ref=None, **kwargs):
"""Create a volume and wait for it to become 'available'.
+ :param image_ref: Specify an image id to create a bootable volume.
+ :**kwargs: other parameters to create volume.
:returns: The available volume.
"""
- vol_name = data_utils.rand_name(cls.__name__ + '-volume')
- volume = cls.volumes_client.create_volume(
- size=CONF.volume.volume_size, display_name=vol_name)['volume']
+ if 'size' not in kwargs:
+ kwargs['size'] = CONF.volume.volume_size
+ if 'display_name' not in kwargs:
+ vol_name = data_utils.rand_name(cls.__name__ + '-volume')
+ kwargs['display_name'] = vol_name
+ if image_ref is not None:
+ kwargs['imageRef'] = image_ref
+ volume = cls.volumes_client.create_volume(**kwargs)['volume']
cls.volumes.append(volume)
waiters.wait_for_volume_status(cls.volumes_client,
volume['id'], 'available')
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index fdf1e93..dcb2d2c 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.api.compute.floating_ips import base
-from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
@@ -111,9 +110,7 @@
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
# Create server so as to use for Multiple association
- body = self.create_test_server()
- waiters.wait_for_server_status(self.servers_client,
- body['id'], 'ACTIVE')
+ body = self.create_test_server(wait_until='ACTIVE')
self.new_server_id = body['id']
self.addCleanup(self.servers_client.delete_server, self.new_server_id)
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
index f71f046..31cf39c 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -23,7 +23,6 @@
class FloatingIPsNegativeTestJSON(base.BaseFloatingIPsTest):
- server_id = None
@classmethod
def setup_clients(cls):
@@ -38,15 +37,14 @@
server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
# Generating a nonexistent floatingIP id
- cls.floating_ip_ids = []
body = cls.client.list_floating_ips()['floating_ips']
- for i in range(len(body)):
- cls.floating_ip_ids.append(body[i]['id'])
+ floating_ip_ids = [floating_ip['id'] for floating_ip in body]
while True:
- cls.non_exist_id = data_utils.rand_int_id(start=999)
if CONF.service_available.neutron:
cls.non_exist_id = data_utils.rand_uuid()
- if cls.non_exist_id not in cls.floating_ip_ids:
+ else:
+ cls.non_exist_id = data_utils.rand_int_id(start=999)
+ if cls.non_exist_id not in floating_ip_ids:
break
@test.attr(type=['negative'])
diff --git a/tempest/api/compute/images/test_image_metadata_negative.py b/tempest/api/compute/images/test_image_metadata_negative.py
index 9cb9e03..489bfbc 100644
--- a/tempest/api/compute/images/test_image_metadata_negative.py
+++ b/tempest/api/compute/images/test_image_metadata_negative.py
@@ -19,11 +19,11 @@
from tempest import test
-class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+class ImagesMetadataNegativeTestJSON(base.BaseV2ComputeTest):
@classmethod
def setup_clients(cls):
- super(ImagesMetadataTestJSON, cls).setup_clients()
+ super(ImagesMetadataNegativeTestJSON, cls).setup_clients()
cls.client = cls.compute_images_client
@test.attr(type=['negative'])
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index 7035401..549262e 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -46,7 +46,7 @@
# An image should not be created if the server instance is removed
server = self.create_test_server(wait_until='ACTIVE')
- # Delete server before trying to create server
+ # Delete server before trying to create image
self.servers_client.delete_server(server['id'])
waiters.wait_for_server_termination(self.servers_client, server['id'])
# Create a new image after server is deleted
@@ -96,7 +96,7 @@
def test_delete_non_existent_image(self):
# Return an error while trying to delete a non-existent image
- non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+ non_existent_image_id = data_utils.rand_uuid()
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
non_existent_image_id)
@@ -110,9 +110,9 @@
@test.idempotent_id('924540c3-f1f1-444c-8f58-718958b6724e')
def test_delete_image_non_hex_string_id(self):
# Return an error while trying to delete an image with non hex id
- image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+ invalid_image_id = data_utils.rand_uuid()[:-1] + "j"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- image_id)
+ invalid_image_id)
@test.attr(type=['negative'])
@test.idempotent_id('68e2c175-bd26-4407-ac0f-4ea9ce2139ea')
@@ -122,7 +122,8 @@
@test.attr(type=['negative'])
@test.idempotent_id('b340030d-82cd-4066-a314-c72fb7c59277')
- def test_delete_image_id_is_over_35_character_limit(self):
+ def test_delete_image_with_id_over_character_limit(self):
# Return an error while trying to delete image with id over limit
+ invalid_image_id = data_utils.rand_uuid() + "1"
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
+ invalid_image_id)
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 19e2880..0b4a2a8 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
-
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
@@ -23,7 +21,6 @@
from tempest import test
CONF = config.CONF
-LOG = logging.getLogger(__name__)
class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index d9b80e1..cd71de7 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -47,8 +47,8 @@
waiters.wait_for_server_status(self.servers_client, self.server_id,
'ACTIVE')
except Exception:
- LOG.exception('server %s timed out to become ACTIVE. rebuilding'
- % self.server_id)
+ LOG.exception('server %s timed out to become ACTIVE. rebuilding',
+ self.server_id)
# Rebuild server if cannot reach the ACTIVE state
# Usually it means the server had a serious accident
self._reset_server()
@@ -69,11 +69,6 @@
raise cls.skipException(skip_msg)
@classmethod
- def setup_credentials(cls):
- cls.prepare_instance_network()
- super(ImagesOneServerNegativeTestJSON, cls).setup_credentials()
-
- @classmethod
def setup_clients(cls):
super(ImagesOneServerNegativeTestJSON, cls).setup_clients()
cls.client = cls.compute_images_client
@@ -98,9 +93,9 @@
@test.attr(type=['negative'])
@test.idempotent_id('3d24d11f-5366-4536-bd28-cff32b748eca')
def test_create_image_specify_metadata_over_limits(self):
- # Return an error when creating image with meta data over 256 chars
+ # Return an error when creating image with meta data over 255 chars
snapshot_name = data_utils.rand_name('test-snap')
- meta = {'a' * 260: 'b' * 260}
+ meta = {'a' * 256: 'b' * 256}
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
self.server_id, name=snapshot_name, metadata=meta)
@@ -123,10 +118,10 @@
@test.attr(type=['negative'])
@test.idempotent_id('084f0cbc-500a-4963-8a4e-312905862581')
- def test_create_image_specify_name_over_256_chars(self):
- # Return an error if snapshot name over 256 characters is passed
+ def test_create_image_specify_name_over_character_limit(self):
+ # Return an error if snapshot name over 255 characters is passed
- snapshot_name = data_utils.rand_name('a' * 260)
+ snapshot_name = ('a' * 256)
self.assertRaises(lib_exc.BadRequest, self.client.create_image,
self.server_id, name=snapshot_name)
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index 69811f4..6cc722c 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -35,9 +35,10 @@
'maxTotalFloatingIps', 'maxSecurityGroups',
'maxSecurityGroupRules', 'maxTotalInstances',
'maxTotalKeypairs', 'maxTotalRAMSize',
+ 'maxServerGroups', 'maxServerGroupMembers',
'totalCoresUsed', 'totalFloatingIpsUsed',
'totalSecurityGroupsUsed', 'totalInstancesUsed',
- 'totalRAMUsed']
+ 'totalRAMUsed', 'totalServerGroupsUsed']
# check whether all expected elements exist
missing_elements =\
[ele for ele in expected_elements if ele not in absolute_limits]
diff --git a/tempest/api/compute/security_groups/test_security_group_rules_negative.py b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
index 4f53663..32b3ea3 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules_negative.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules_negative.py
@@ -35,7 +35,6 @@
@classmethod
def setup_clients(cls):
super(SecurityGroupRulesNegativeTestJSON, cls).setup_clients()
- cls.client = cls.security_groups_client
cls.rules_client = cls.security_group_rules_client
@test.attr(type=['negative'])
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 755336f..4184afa 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -94,10 +94,8 @@
# Create server and add the security group created
# above to the server we just created
- server = self.create_test_server()
+ server = self.create_test_server(wait_until='ACTIVE')
server_id = server['id']
- waiters.wait_for_server_status(self.servers_client, server_id,
- 'ACTIVE')
self.servers_client.add_security_group(server_id, name=sg['name'])
# Check that we are not able to delete the security
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index fc6a20f..a21ce94 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -46,34 +46,9 @@
@classmethod
def setup_clients(cls):
super(AttachInterfacesTestJSON, cls).setup_clients()
- cls.networks_client = cls.os.networks_client
cls.subnets_client = cls.os.subnets_client
cls.ports_client = cls.os.ports_client
- def wait_for_interface_status(self, server, port_id, status):
- """Waits for an interface to reach a given status."""
- body = (self.interfaces_client.show_interface(server, port_id)
- ['interfaceAttachment'])
- interface_status = body['port_state']
- start = int(time.time())
-
- while(interface_status != status):
- time.sleep(self.build_interval)
- body = (self.interfaces_client.show_interface(server, port_id)
- ['interfaceAttachment'])
- interface_status = body['port_state']
-
- timed_out = int(time.time()) - start >= self.build_timeout
-
- if interface_status != status and timed_out:
- message = ('Interface %s failed to reach %s status '
- '(current %s) within the required time (%s s).' %
- (port_id, status, interface_status,
- self.build_timeout))
- raise lib_exc.TimeoutException(message)
-
- 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.
@@ -118,16 +93,16 @@
server = self.create_test_server(wait_until='ACTIVE')
ifs = (self.interfaces_client.list_interfaces(server['id'])
['interfaceAttachments'])
- body = self.wait_for_interface_status(
- server['id'], ifs[0]['port_id'], 'ACTIVE')
+ body = waiters.wait_for_interface_status(
+ self.interfaces_client, server['id'], ifs[0]['port_id'], 'ACTIVE')
ifs[0]['port_state'] = body['port_state']
return server, ifs
def _test_create_interface(self, server):
iface = (self.interfaces_client.create_interface(server['id'])
['interfaceAttachment'])
- iface = self.wait_for_interface_status(
- server['id'], iface['port_id'], 'ACTIVE')
+ iface = waiters.wait_for_interface_status(
+ self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface)
return iface
@@ -135,8 +110,8 @@
network_id = ifs[0]['net_id']
iface = self.interfaces_client.create_interface(
server['id'], net_id=network_id)['interfaceAttachment']
- iface = self.wait_for_interface_status(
- server['id'], iface['port_id'], 'ACTIVE')
+ iface = waiters.wait_for_interface_status(
+ self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface, network_id=network_id)
return iface
@@ -147,8 +122,8 @@
self.addCleanup(self.ports_client.delete_port, port_id)
iface = self.interfaces_client.create_interface(
server['id'], port_id=port_id)['interfaceAttachment']
- iface = self.wait_for_interface_status(
- server['id'], iface['port_id'], 'ACTIVE')
+ iface = waiters.wait_for_interface_status(
+ self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface, port_id=port_id)
return iface
@@ -166,8 +141,8 @@
server['id'], net_id=network_id,
fixed_ips=fixed_ips)['interfaceAttachment']
self.addCleanup(self.ports_client.delete_port, iface['port_id'])
- iface = self.wait_for_interface_status(
- server['id'], iface['port_id'], 'ACTIVE')
+ iface = waiters.wait_for_interface_status(
+ self.interfaces_client, server['id'], iface['port_id'], 'ACTIVE')
self._check_interface(iface, fixed_ip=ip_list[0])
return iface
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 2f43157..2dcacb7 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -19,7 +19,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
-from tempest.common import waiters
from tempest import config
from tempest import test
@@ -178,12 +177,7 @@
# when trying to delete the subnet. The tear down in the base class
# will try to delete the server and get a 404 but it's ignored so
# we're OK.
- def cleanup_server():
- self.client.delete_server(server_multi_nics['id'])
- waiters.wait_for_server_termination(self.client,
- server_multi_nics['id'])
-
- self.addCleanup(cleanup_server)
+ self.addCleanup(self.delete_server, server_multi_nics['id'])
addresses = (self.client.list_addresses(server_multi_nics['id'])
['addresses'])
@@ -215,13 +209,7 @@
server_multi_nics = self.create_test_server(
networks=networks, wait_until='ACTIVE')
-
- def cleanup_server():
- self.client.delete_server(server_multi_nics['id'])
- waiters.wait_for_server_termination(self.client,
- server_multi_nics['id'])
-
- self.addCleanup(cleanup_server)
+ self.addCleanup(self.delete_server, server_multi_nics['id'])
addresses = (self.client.list_addresses(server_multi_nics['id'])
['addresses'])
@@ -265,24 +253,18 @@
self.flavor_ref)['flavor']
def create_flavor_with_ephemeral(ephem_disk):
- flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
+ flavor_id = data_utils.rand_int_id(start=1000)
+ name = 'flavor_with_ephemeral_%s' % ephem_disk
+ flavor_name = data_utils.rand_name(name)
ram = flavor_base['ram']
vcpus = flavor_base['vcpus']
disk = flavor_base['disk']
- if ephem_disk > 0:
- # Create a flavor with ephemeral disk
- flavor_name = data_utils.rand_name('eph_flavor')
- flavor = self.flavor_client.create_flavor(
- name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
- id=flavor_with_eph_disk_id, ephemeral=ephem_disk)['flavor']
- else:
- # Create a flavor without ephemeral disk
- flavor_name = data_utils.rand_name('no_eph_flavor')
- flavor = self.flavor_client.create_flavor(
- name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
- id=flavor_with_eph_disk_id)['flavor']
+ # Create a flavor with ephemeral disk
+ flavor = self.flavor_client.create_flavor(
+ name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
+ id=flavor_id, ephemeral=ephem_disk)['flavor']
self.addCleanup(flavor_clean_up, flavor['id'])
return flavor['id']
@@ -312,7 +294,7 @@
self.validation_resources['keypair']['private_key'],
server=server_no_eph_disk,
servers_client=self.client)
- partition_num = len(linux_client.get_partitions().split('\n'))
+ disks_num = len(linux_client.get_disks().split('\n'))
# Explicit server deletion necessary for Juno compatibility
self.client.delete_server(server_no_eph_disk['id'])
@@ -332,8 +314,8 @@
self.validation_resources['keypair']['private_key'],
server=server_with_eph_disk,
servers_client=self.client)
- partition_num_emph = len(linux_client.get_partitions().split('\n'))
- self.assertEqual(partition_num + 1, partition_num_emph)
+ disks_num_eph = len(linux_client.get_disks().split('\n'))
+ self.assertEqual(disks_num + 1, disks_num_eph)
class ServersTestManualDisk(ServersTestJSON):
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
new file mode 100644
index 0000000..b2d5ae7
--- /dev/null
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -0,0 +1,266 @@
+# Copyright (C) 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.
+
+import json
+
+from oslo_log import log as logging
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common.utils.linux import remote_client
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import exceptions
+from tempest import test
+
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class DeviceTaggingTest(base.BaseV2ComputeTest):
+
+ min_microversion = '2.32'
+ max_microversion = 'latest'
+
+ @classmethod
+ def skip_checks(cls):
+ super(DeviceTaggingTest, cls).skip_checks()
+ if not CONF.service_available.neutron:
+ raise cls.skipException('Neutron is required')
+ if not CONF.validation.run_validation:
+ raise cls.skipException('Validation must be enabled')
+ if (not CONF.compute_feature_enabled.config_drive
+ and not CONF.compute_feature_enabled.metadata_service):
+ raise cls.skipException('One of metadata or config drive must be '
+ 'enabled')
+
+ @classmethod
+ def setup_clients(cls):
+ super(DeviceTaggingTest, cls).setup_clients()
+ cls.networks_client = cls.os.networks_client
+ cls.ports_client = cls.os.ports_client
+ cls.subnets_client = cls.os.subnets_client
+ cls.interfaces_client = cls.os.interfaces_client
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources(network=True, subnet=True, router=True,
+ dhcp=True)
+ super(DeviceTaggingTest, cls).setup_credentials()
+
+ @classmethod
+ def resource_setup(cls):
+ cls.set_validation_resources()
+ super(DeviceTaggingTest, cls).resource_setup()
+
+ def verify_device_metadata(self, md_json):
+ md_dict = json.loads(md_json)
+ for d in md_dict['devices']:
+ if d['type'] == 'nic':
+ if d['mac'] == self.port1['mac_address']:
+ self.assertEqual(d['tags'], ['port-1'])
+ if d['mac'] == self.port2['mac_address']:
+ self.assertEqual(d['tags'], ['port-2'])
+ if d['mac'] == self.net_2_100_mac:
+ self.assertEqual(d['tags'], ['net-2-100'])
+ if d['mac'] == self.net_2_200_mac:
+ self.assertEqual(d['tags'], ['net-2-200'])
+
+ found_devices = [d['tags'][0] for d in md_dict['devices']]
+ self.assertItemsEqual(found_devices, ['port-1', 'port-2', 'net-1',
+ 'net-2-100', 'net-2-200',
+ 'boot', 'other'])
+
+ @test.idempotent_id('a2e65a6c-66f1-4442-aaa8-498c31778d96')
+ @test.services('network', 'volume', 'image')
+ def test_device_tagging(self):
+ # Create volumes
+ # The create_volume methods waits for the volumes to be available and
+ # the base class will clean them up on tearDown.
+ boot_volume = self.create_volume(CONF.compute.image_ref)
+ other_volume = self.create_volume()
+ untagged_volume = self.create_volume()
+
+ # Create networks
+ net1 = self.networks_client.create_network(
+ name=data_utils.rand_name('device-tagging-net1'))['network']
+ self.addCleanup(self.networks_client.delete_network, net1['id'])
+
+ net2 = self.networks_client.create_network(
+ name=data_utils.rand_name('device-tagging-net2'))['network']
+ self.addCleanup(self.networks_client.delete_network, net2['id'])
+
+ # Create subnets
+ subnet1 = self.subnets_client.create_subnet(
+ network_id=net1['id'],
+ cidr='10.1.1.0/24',
+ ip_version=4)['subnet']
+ self.addCleanup(self.subnets_client.delete_subnet, subnet1['id'])
+
+ subnet2 = self.subnets_client.create_subnet(
+ network_id=net2['id'],
+ cidr='10.2.2.0/24',
+ ip_version=4)['subnet']
+ self.addCleanup(self.subnets_client.delete_subnet, subnet2['id'])
+
+ # Create ports
+ self.port1 = self.ports_client.create_port(
+ network_id=net1['id'],
+ fixed_ips=[{'subnet_id': subnet1['id']}])['port']
+ self.addCleanup(self.ports_client.delete_port, self.port1['id'])
+
+ self.port2 = self.ports_client.create_port(
+ network_id=net1['id'],
+ fixed_ips=[{'subnet_id': subnet1['id']}])['port']
+ self.addCleanup(self.ports_client.delete_port, self.port2['id'])
+
+ # Create server
+ admin_pass = data_utils.rand_password()
+ config_drive_enabled = CONF.compute_feature_enabled.config_drive
+
+ server = self.create_test_server(
+ validatable=True,
+ config_drive=config_drive_enabled,
+ adminPass=admin_pass,
+ name=data_utils.rand_name('device-tagging-server'),
+ networks=[
+ # Validation network for ssh
+ {
+ 'uuid': self.get_tenant_network()['id']
+ },
+ # Different tags for different ports
+ {
+ 'port': self.port1['id'],
+ 'tag': 'port-1'
+ },
+ {
+ 'port': self.port2['id'],
+ 'tag': 'port-2'
+ },
+ # Two nics on same net, one tagged one not
+ {
+ 'uuid': net1['id'],
+ 'tag': 'net-1'
+ },
+ {
+ 'uuid': net1['id']
+ },
+ # Two nics on same net, different IP
+ {
+ 'uuid': net2['id'],
+ 'fixed_ip': '10.2.2.100',
+ 'tag': 'net-2-100'
+ },
+ {
+ 'uuid': net2['id'],
+ 'fixed_ip': '10.2.2.200',
+ 'tag': 'net-2-200'
+ }
+ ],
+ block_device_mapping_v2=[
+ # Boot volume
+ {
+ 'uuid': boot_volume['id'],
+ 'source_type': 'volume',
+ 'destination_type': 'volume',
+ 'boot_index': 0,
+ 'tag': 'boot'
+ },
+ # Other volume
+ {
+ 'uuid': other_volume['id'],
+ 'source_type': 'volume',
+ 'destination_type': 'volume',
+ 'boot_index': 1,
+ 'tag': 'other'
+ },
+ # Untagged volume
+ {
+ 'uuid': untagged_volume['id'],
+ 'source_type': 'volume',
+ 'destination_type': 'volume',
+ 'boot_index': 2
+ }
+ ])
+
+ self.addCleanup(waiters.wait_for_server_termination,
+ self.servers_client, server['id'])
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+
+ self.ssh_client = remote_client.RemoteClient(
+ self.get_server_ip(server),
+ CONF.validation.image_ssh_user,
+ admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=server,
+ servers_client=self.servers_client)
+
+ # Find the MAC addresses of our fixed IPs
+ self.net_2_100_mac = None
+ self.net_2_200_mac = None
+ ifaces = self.interfaces_client.list_interfaces(server['id'])
+ for iface in ifaces['interfaceAttachments']:
+ if 'fixed_ips' in iface:
+ for ip in iface['fixed_ips']:
+ if ip['ip_address'] == '10.2.2.100':
+ self.net_2_100_mac = iface['mac_addr']
+ if ip['ip_address'] == '10.2.2.200':
+ self.net_2_200_mac = iface['mac_addr']
+ # Make sure we have the MACs we need, there's no reason for some to be
+ # missing
+ self.assertTrue(self.net_2_100_mac)
+ self.assertTrue(self.net_2_200_mac)
+
+ # Verify metadata from metadata service
+ if CONF.compute_feature_enabled.metadata_service:
+ md_url = 'http://169.254.169.254/openstack/latest/meta_data.json'
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the metadata service: %s', server['id'], md_url)
+
+ def get_and_verify_metadata():
+ try:
+ self.ssh_client.exec_command('curl -V')
+ except exceptions.SSHExecCommandFailed:
+ if not CONF.compute_feature_enabled.config_drive:
+ raise self.skipException('curl not found in guest '
+ 'and config drive is '
+ 'disabled')
+ LOG.warning('curl was not found in the guest, device '
+ 'tagging metadata was not checked in the '
+ 'metadata API')
+ return True
+ cmd = 'curl %s' % md_url
+ md_json = self.ssh_client.exec_command(cmd)
+ self.verify_device_metadata(md_json)
+ return True
+
+ if not test.call_until_true(get_and_verify_metadata,
+ CONF.compute.build_timeout,
+ CONF.compute.build_interval):
+ raise exceptions.TimeoutException('Timeout while verifying '
+ 'metadata on server.')
+
+ # Verify metadata on config drive
+ if CONF.compute_feature_enabled.config_drive:
+ cmd_blkid = 'blkid -t LABEL=config-2 -o device'
+ LOG.info('Attempting to verify tagged devices in server %s via '
+ 'the config drive.', server['id'])
+ 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)
+ cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
+ md_json = self.ssh_client.exec_command(cmd_md)
+ self.verify_device_metadata(md_json)
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index aba0240..ff8ea6e 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -94,12 +94,8 @@
self._update_server_with_disk_config(server['id'],
disk_config='MANUAL')
# Resize with auto option
- self.client.resize_server(server['id'], self.flavor_ref_alt,
- disk_config='AUTO')
- waiters.wait_for_server_status(self.client, server['id'],
- 'VERIFY_RESIZE')
- self.client.confirm_resize_server(server['id'])
- waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
+ self.resize_server(server['id'], self.flavor_ref_alt,
+ disk_config='AUTO')
server = self.client.show_server(server['id'])['server']
self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
@@ -114,12 +110,8 @@
self._update_server_with_disk_config(server['id'],
disk_config='AUTO')
# Resize with manual option
- self.client.resize_server(server['id'], self.flavor_ref_alt,
- disk_config='MANUAL')
- waiters.wait_for_server_status(self.client, server['id'],
- 'VERIFY_RESIZE')
- self.client.confirm_resize_server(server['id'])
- waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
+ self.resize_server(server['id'], self.flavor_ref_alt,
+ disk_config='MANUAL')
server = self.client.show_server(server['id'])['server']
self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index fcd5a24..3e408d2 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -40,14 +40,9 @@
srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
- srv = cls.create_test_server()
+ srv = cls.create_test_server(wait_until='ACTIVE')
cls.client.delete_server(srv['id'])
- # We ignore errors on termination because the server may
- # be put into ERROR status on a quick spawn, then delete,
- # as the compute node expects the instance local status
- # to be spawning, not deleted. See LP Bug#1061167
- waiters.wait_for_server_termination(cls.client, srv['id'],
- ignore_error=True)
+ waiters.wait_for_server_termination(cls.client, srv['id'])
cls.deleted_fixtures.append(srv)
@test.attr(type=['negative'])
@@ -66,8 +61,7 @@
@test.idempotent_id('ff01387d-c7ad-47b4-ae9e-64fa214638fe')
def test_list_servers_by_non_existing_image(self):
# Listing servers for a non existing image returns empty list
- non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde'
- body = self.client.list_servers(image=non_existing_image)
+ body = self.client.list_servers(image='non_existing_image')
servers = body['servers']
self.assertEqual([], servers)
@@ -75,8 +69,7 @@
@test.idempotent_id('5913660b-223b-44d4-a651-a0fbfd44ca75')
def test_list_servers_by_non_existing_flavor(self):
# Listing servers by non existing flavor returns empty list
- non_existing_flavor = 1234
- body = self.client.list_servers(flavor=non_existing_flavor)
+ body = self.client.list_servers(flavor='non_existing_flavor')
servers = body['servers']
self.assertEqual([], servers)
@@ -84,8 +77,7 @@
@test.idempotent_id('e2c77c4a-000a-4af3-a0bd-629a328bde7c')
def test_list_servers_by_non_existing_server_name(self):
# Listing servers for a non existent server name returns empty list
- non_existing_name = 'junk_server_1234'
- body = self.client.list_servers(name=non_existing_name)
+ body = self.client.list_servers(name='non_existing_server_name')
servers = body['servers']
self.assertEqual([], servers)
@@ -93,8 +85,7 @@
@test.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
def test_list_servers_status_non_existing(self):
# Return an empty list when invalid status is specified
- non_existing_status = 'BALONEY'
- body = self.client.list_servers(status=non_existing_status)
+ body = self.client.list_servers(status='non_existing_status')
servers = body['servers']
self.assertEqual([], servers)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 50910ec..0334eff 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -13,8 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
+from oslo_log import log as logging
from six.moves.urllib import parse as urlparse
import testtools
@@ -351,7 +350,7 @@
else:
LOG.warning("Deletion of oldest backup %s should not have "
"been successful as it should have been "
- "deleted during rotation." % oldest_backup)
+ "deleted during rotation.", oldest_backup)
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index e5ad7b4..ab291b4 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -118,10 +118,12 @@
raise self.skipException("No limit for personality files")
person = []
for i in range(0, int(max_file_limit)):
- path = '/etc/test' + str(i) + '.txt'
+ # NOTE(andreaf) The cirros disk image is blank before boot
+ # so we can only inject safely to /
+ path = '/test' + str(i) + '.txt'
person.append({
'path': path,
- 'contents': base64.encode_as_text(file_contents),
+ 'contents': base64.encode_as_text(file_contents + str(i)),
})
password = data_utils.rand_password()
created_server = self.create_test_server(personality=person,
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 6e57aff..d171cd5 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -42,7 +42,7 @@
raise self.skipException('There are not any extensions configured')
# Log extensions list
extension_list = map(lambda x: x['alias'], extensions)
- LOG.debug("Nova extensions: %s" % ','.join(extension_list))
+ LOG.debug("Nova extensions: %s", ','.join(extension_list))
@test.idempotent_id('05762f39-bdfa-4cdb-9b46-b78f8e78e2fd')
@test.requires_ext(extension='os-consoles', service='compute')
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/test_live_block_migration_negative.py
index bd2b185..f072b81 100644
--- a/tempest/api/compute/test_live_block_migration_negative.py
+++ b/tempest/api/compute/test_live_block_migration_negative.py
@@ -33,7 +33,6 @@
@classmethod
def setup_clients(cls):
super(LiveBlockMigrationNegativeTestJSON, cls).setup_clients()
- cls.admin_hosts_client = cls.os_adm.hosts_client
cls.admin_servers_client = cls.os_adm.servers_client
def _migrate_server_to(self, server_id, dest_host):
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 122e7cc..b9e0c35 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -48,7 +48,8 @@
'fixed_ips', 'key_pairs',
'injected_file_path_bytes',
'instances', 'security_group_rules',
- 'cores', 'security_groups'))
+ 'cores', 'security_groups',
+ 'server_group_members', 'server_groups'))
@test.idempotent_id('f1ef0a97-dbbb-4cca-adc5-c9fbc4f76107')
def test_get_quotas(self):
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index d4831b1..30549ec 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import logging
import testtools
from tempest.api.compute import base
@@ -20,18 +21,17 @@
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
class AttachVolumeTestJSON(base.BaseV2ComputeTest):
max_microversion = '2.19'
- def __init__(self, *args, **kwargs):
- super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
- self.attachment = None
-
@classmethod
def skip_checks(cls):
super(AttachVolumeTestJSON, cls).skip_checks()
@@ -47,50 +47,52 @@
@classmethod
def resource_setup(cls):
cls.set_validation_resources()
-
super(AttachVolumeTestJSON, cls).resource_setup()
cls.device = CONF.compute.volume_device_name
- def _detach(self, server_id, volume_id):
- if self.attachment:
- self.servers_client.detach_volume(server_id, volume_id)
- waiters.wait_for_volume_status(self.volumes_client,
- volume_id, 'available')
-
def _create_server(self):
# Start a server and wait for it to become ready
server = self.create_test_server(
validatable=True,
wait_until='ACTIVE',
adminPass=self.image_ssh_password)
-
# Record addresses so that we can ssh later
server['addresses'] = self.servers_client.list_addresses(
server['id'])['addresses']
return server
- def _create_and_attach_volume(self, server):
- # Create a volume and wait for it to become ready
- volume = self.create_volume()
- self.addCleanup(self.delete_volume, volume['id'])
+ def _detach_volume(self, server_id, volume_id):
+ try:
+ self.servers_client.detach_volume(server_id, volume_id)
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume_id, 'available')
+ except lib_exc.NotFound:
+ LOG.warning("Unable to detach volume %s from server %s "
+ "possibly it was already detached", volume_id,
+ server_id)
+ def _attach_volume(self, server_id, volume_id, device=None):
# Attach the volume to the server
- self.attachment = self.servers_client.attach_volume(
- server['id'],
- volumeId=volume['id'],
- device='/dev/%s' % self.device)['volumeAttachment']
+ kwargs = {'volumeId': volume_id}
+ if device:
+ kwargs.update({'device': '/dev/%s' % device})
+ attachment = self.servers_client.attach_volume(
+ server_id, **kwargs)['volumeAttachment']
waiters.wait_for_volume_status(self.volumes_client,
- volume['id'], 'in-use')
+ volume_id, 'in-use')
+ self.addCleanup(self._detach_volume, server_id,
+ volume_id)
- self.addCleanup(self._detach, server['id'], volume['id'])
- return volume
+ return attachment
@test.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
def test_attach_detach_volume(self):
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
server = self._create_server()
- volume = self._create_and_attach_volume(server)
+ volume = self.create_volume()
+ attachment = self._attach_volume(server['id'], volume['id'],
+ device=self.device)
self.servers_client.stop_server(server['id'])
waiters.wait_for_server_status(self.servers_client, server['id'],
@@ -109,12 +111,11 @@
server=server,
servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- device_name_to_match = ' ' + self.device + '\n'
- self.assertIn(device_name_to_match, partitions)
+ disks = linux_client.get_disks()
+ device_name_to_match = '\n' + self.device + ' '
+ self.assertIn(device_name_to_match, disks)
- self._detach(server['id'], volume['id'])
- self.attachment = None
+ self._detach_volume(server['id'], attachment['volumeId'])
self.servers_client.stop_server(server['id'])
waiters.wait_for_server_status(self.servers_client, server['id'],
'SHUTOFF')
@@ -132,28 +133,58 @@
server=server,
servers_client=self.servers_client)
- partitions = linux_client.get_partitions()
- self.assertNotIn(device_name_to_match, partitions)
+ disks = linux_client.get_disks()
+ self.assertNotIn(device_name_to_match, disks)
@test.idempotent_id('7fa563fe-f0f7-43eb-9e22-a1ece036b513')
def test_list_get_volume_attachments(self):
- # Create Server, Volume and attach that Volume to Server
+ # List volume attachment of the server
server = self._create_server()
- volume = self._create_and_attach_volume(server)
-
- # List Volume attachment of the server
+ volume = self.create_volume()
+ attachment = self._attach_volume(server['id'], volume['id'],
+ device=self.device)
body = self.servers_client.list_volume_attachments(
server['id'])['volumeAttachments']
self.assertEqual(1, len(body))
- self.assertIn(self.attachment, body)
+ self.assertIn(attachment, body)
- # Get Volume attachment of the server
+ # Get volume attachment of the server
body = self.servers_client.show_volume_attachment(
server['id'],
- self.attachment['id'])['volumeAttachment']
+ attachment['id'])['volumeAttachment']
self.assertEqual(server['id'], body['serverId'])
self.assertEqual(volume['id'], body['volumeId'])
- self.assertEqual(self.attachment['id'], body['id'])
+ self.assertEqual(attachment['id'], body['id'])
+
+ @test.idempotent_id('757d488b-a951-4bc7-b3cd-f417028da08a')
+ def test_list_get_two_volume_attachments(self):
+ # NOTE: This test is using the volume device auto-assignment
+ # without specifying the device ("/dev/sdb", etc). The feature
+ # is supported since Nova Liberty release or later. So this should
+ # be skipped on older clouds.
+ server = self._create_server()
+ volume_1st = self.create_volume()
+ volume_2nd = self.create_volume()
+ attachment_1st = self._attach_volume(server['id'], volume_1st['id'])
+ attachment_2nd = self._attach_volume(server['id'], volume_2nd['id'])
+
+ body = self.servers_client.list_volume_attachments(
+ server['id'])['volumeAttachments']
+ self.assertEqual(2, len(body))
+
+ body = self.servers_client.show_volume_attachment(
+ server['id'],
+ attachment_1st['id'])['volumeAttachment']
+ self.assertEqual(server['id'], body['serverId'])
+ self.assertEqual(attachment_1st['volumeId'], body['volumeId'])
+ self.assertEqual(attachment_1st['id'], body['id'])
+
+ body = self.servers_client.show_volume_attachment(
+ server['id'],
+ attachment_2nd['id'])['volumeAttachment']
+ self.assertEqual(server['id'], body['serverId'])
+ self.assertEqual(attachment_2nd['volumeId'], body['volumeId'])
+ self.assertEqual(attachment_2nd['id'], body['id'])
class AttachVolumeShelveTestJSON(AttachVolumeTestJSON):
@@ -219,19 +250,21 @@
# Create server, count number of volumes on it, shelve
# server and attach pre-created volume to shelved server
server = self._create_server()
+ volume = self.create_volume()
num_vol = self._count_volumes(server)
self._shelve_server(server)
- self._create_and_attach_volume(server)
+ attachment = self._attach_volume(server['id'], volume['id'],
+ device=self.device)
# Unshelve the instance and check that attached volume exists
self._unshelve_server_and_check_volumes(server, num_vol + 1)
- # Get Volume attachment of the server
+ # Get volume attachment of the server
volume_attachment = self.servers_client.show_volume_attachment(
server['id'],
- self.attachment['id'])['volumeAttachment']
+ attachment['id'])['volumeAttachment']
self.assertEqual(server['id'], volume_attachment['serverId'])
- self.assertEqual(self.attachment['id'], volume_attachment['id'])
+ self.assertEqual(attachment['id'], volume_attachment['id'])
# Check the mountpoint is not None after unshelve server even in
# case of shelved_offloaded.
self.assertIsNotNone(volume_attachment['device'])
@@ -240,16 +273,15 @@
@testtools.skipUnless(CONF.compute_feature_enabled.shelve,
'Shelve is not available.')
def test_detach_volume_shelved_or_offload_server(self):
- # Create server, count number of volumes on it, shelve
+ # Count number of volumes on instance, shelve
# server and attach pre-created volume to shelved server
server = self._create_server()
+ volume = self.create_volume()
num_vol = self._count_volumes(server)
self._shelve_server(server)
- volume = self._create_and_attach_volume(server)
-
+ self._attach_volume(server['id'], volume['id'], device=self.device)
# Detach the volume
- self._detach(server['id'], volume['id'])
- self.attachment = None
+ self._detach_volume(server['id'], volume['id'])
# Unshelve the instance and check that we have the expected number of
# volume(s)
diff --git a/tempest/api/compute/volumes/test_attach_volume_negative.py b/tempest/api/compute/volumes/test_attach_volume_negative.py
index b7fa0fe..1f18bfe 100644
--- a/tempest/api/compute/volumes/test_attach_volume_negative.py
+++ b/tempest/api/compute/volumes/test_attach_volume_negative.py
@@ -29,6 +29,7 @@
skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
raise cls.skipException(skip_msg)
+ @test.related_bug('1630783', status_code=500)
@test.idempotent_id('a313b5cd-fbd0-49cc-94de-870e99f763c7')
def test_delete_attached_volume(self):
server = self.create_test_server(wait_until='ACTIVE')
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
index 460c882..01718cc 100644
--- a/tempest/api/compute/volumes/test_volume_snapshots.py
+++ b/tempest/api/compute/volumes/test_volume_snapshots.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.common import waiters
@@ -39,6 +41,8 @@
cls.snapshots_client = cls.snapshots_extensions_client
@test.idempotent_id('cd4ec87d-7825-450d-8040-6e2068f2da8f')
+ @testtools.skipUnless(CONF.volume_feature_enabled.snapshot,
+ 'Cinder volume snapshots are disabled')
def test_volume_snapshot_create_get_list_delete(self):
volume = self.create_volume()
self.addCleanup(self.delete_volume, volume['id'])
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index c60fcca..82cc653 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -14,8 +14,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils import data_utils
-from tempest.common import waiters
from tempest import config
from tempest import test
@@ -48,39 +46,11 @@
cls.volume_list = []
cls.volume_id_list = []
for i in range(3):
- v_name = data_utils.rand_name(cls.__name__ + '-volume')
metadata = {'Type': 'work'}
- try:
- volume = cls.client.create_volume(size=CONF.volume.volume_size,
- display_name=v_name,
- metadata=metadata)['volume']
- waiters.wait_for_volume_status(cls.client,
- volume['id'], 'available')
- volume = cls.client.show_volume(volume['id'])['volume']
- cls.volume_list.append(volume)
- cls.volume_id_list.append(volume['id'])
- except Exception:
- if cls.volume_list:
- # We could not create all the volumes, though we were able
- # to create *some* of the volumes. This is typically
- # because the backing file size of the volume group is
- # too small. So, here, we clean up whatever we did manage
- # to create and raise a SkipTest
- for volume in cls.volume_list:
- cls.delete_volume(volume['id'])
- msg = ("Failed to create ALL necessary volumes to run "
- "test. This typically means that the backing file "
- "size of the nova-volumes group is too small to "
- "create the 3 volumes needed by this test case")
- raise cls.skipException(msg)
- raise
-
- @classmethod
- def resource_cleanup(cls):
- # Delete the created Volumes
- for volume in cls.volume_list:
- cls.delete_volume(volume['id'])
- super(VolumesTestJSON, cls).resource_cleanup()
+ volume = cls.create_volume(metadata=metadata)
+ volume = cls.client.show_volume(volume['id'])['volume']
+ cls.volume_list.append(volume)
+ cls.volume_id_list.append(volume['id'])
@test.idempotent_id('bc2dd1a0-15af-48e5-9990-f2e75a48325d')
def test_volume_list(self):
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index 5fe4cb3..c4041cb 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -66,7 +66,7 @@
@test.attr(type=['negative'])
@test.idempotent_id('131cb3a1-75cc-4d40-b4c3-1317f64719b0')
- def test_create_volume_with_out_passing_size(self):
+ def test_create_volume_without_passing_size(self):
# Negative: Should not be able to create volume without passing size
# in request
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
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 59ffc19..361ff31 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -36,7 +36,8 @@
def test_default_project_id(self):
# create a domain
dom_name = data_utils.rand_name('dom')
- domain_body = self.domains_client.create_domain(dom_name)['domain']
+ domain_body = self.domains_client.create_domain(
+ name=dom_name)['domain']
dom_id = domain_body['id']
self.addCleanup(self._delete_domain, dom_id)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index cbf1439..e71341f 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -32,7 +32,7 @@
cls.setup_domains = list()
for i in range(3):
domain = cls.domains_client.create_domain(
- data_utils.rand_name('domain'),
+ name=data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'),
enabled=i < 2)['domain']
cls.setup_domains.append(domain)
@@ -67,7 +67,7 @@
# List domains filtering by name
params = {'name': self.setup_domains[0]['name']}
fetched_domains = self.domains_client.list_domains(
- params=params)['domains']
+ **params)['domains']
# Verify the filtered list is correct, domain names are unique
# so exactly one domain should be found with the provided name
self.assertEqual(1, len(fetched_domains))
@@ -79,7 +79,7 @@
# List domains filtering by enabled domains
params = {'enabled': True}
fetched_domains = self.domains_client.list_domains(
- params=params)['domains']
+ **params)['domains']
# Verify the filtered list is correct
self.assertIn(self.setup_domains[0], fetched_domains)
self.assertIn(self.setup_domains[1], fetched_domains)
@@ -93,7 +93,7 @@
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
domain = self.domains_client.create_domain(
- d_name, description=d_desc)['domain']
+ name=d_name, description=d_desc)['domain']
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self._delete_domain, domain['id'])
self.assertIn('id', domain)
@@ -138,7 +138,7 @@
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
domain = self.domains_client.create_domain(
- d_name, description=d_desc, enabled=False)['domain']
+ name=d_name, description=d_desc, enabled=False)['domain']
self.addCleanup(self.domains_client.delete_domain, domain['id'])
self.assertEqual(d_name, domain['name'])
self.assertFalse(domain['enabled'])
@@ -148,11 +148,15 @@
def test_create_domain_without_description(self):
# Create domain only with name
d_name = data_utils.rand_name('domain')
- domain = self.domains_client.create_domain(d_name)['domain']
+ domain = self.domains_client.create_domain(name=d_name)['domain']
self.addCleanup(self._delete_domain, domain['id'])
self.assertIn('id', domain)
expected_data = {'name': d_name, 'enabled': True}
- self.assertIsNone(domain['description'])
+ # TODO(gmann): there is bug in keystone liberty version where
+ # description is not being returned if it is not being passed in
+ # request. Bug#1649245. Once bug is fixed then we can enable the below
+ # check.
+ # self.assertEqual('', domain['description'])
self.assertDictContainsSubset(expected_data, domain)
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 4330cab..100a121 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -28,7 +28,7 @@
d_name = data_utils.rand_name('domain')
d_desc = data_utils.rand_name('domain-desc')
domain = self.domains_client.create_domain(
- d_name,
+ name=d_name,
description=d_desc)['domain']
domain_id = domain['id']
@@ -51,7 +51,8 @@
# Domain name length should not ne greater than 64 characters
d_name = 'a' * 65
self.assertRaises(lib_exc.BadRequest,
- self.domains_client.create_domain, d_name)
+ self.domains_client.create_domain,
+ name=d_name)
@test.attr(type=['negative'])
@test.idempotent_id('43781c07-764f-4cf2-a405-953c1916f605')
@@ -64,9 +65,10 @@
@test.idempotent_id('e6f9e4a2-4f36-4be8-bdbc-4e199ae29427')
def test_domain_create_duplicate(self):
domain_name = data_utils.rand_name('domain-dup')
- domain = self.domains_client.create_domain(domain_name)['domain']
+ domain = self.domains_client.create_domain(name=domain_name)['domain']
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
# Domain name should be unique
self.assertRaises(
- lib_exc.Conflict, self.domains_client.create_domain, domain_name)
+ lib_exc.Conflict, self.domains_client.create_domain,
+ name=domain_name)
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 50cf258..655e4ef 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -23,7 +23,6 @@
@classmethod
def setup_clients(cls):
super(EndPointsTestJSON, cls).setup_clients()
- cls.identity_client = cls.client
cls.client = cls.endpoints_client
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index b16605e..f0f8707 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -25,7 +25,6 @@
@classmethod
def setup_clients(cls):
super(EndpointsNegativeTestJSON, cls).setup_clients()
- cls.identity_client = cls.client
cls.client = cls.endpoints_client
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 955b6fb..33fce8d 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -31,7 +31,7 @@
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_name('pass-')
cls.domain = cls.domains_client.create_domain(
- data_utils.rand_name('domain-'),
+ name=data_utils.rand_name('domain-'),
description=data_utils.rand_name('domain-desc-'))['domain']
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project-'),
@@ -168,15 +168,16 @@
self.domain['id'], self.user['id'], src_role['id'])
# List "effective" role assignments from user on the parent project
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- self.project['id'], self.user['id']))['role_assignments']
+ params = {'scope.project.id': self.project['id'],
+ 'user.id': self.user['id']}
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertNotEmpty(assignments)
# List "effective" role assignments from user on the leaf project
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- leaf_project['id'], self.user['id']))['role_assignments']
+ params['scope.project.id'] = leaf_project['id']
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertNotEmpty(assignments)
# Revoke role from domain
@@ -185,16 +186,16 @@
# List "effective" role assignments from user on the parent project
# should return an empty list
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- self.project['id'], self.user['id']))['role_assignments']
+ params['scope.project.id'] = self.project['id']
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertEmpty(assignments)
# List "effective" role assignments from user on the leaf project
# should return an empty list
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- leaf_project['id'], self.user['id']))['role_assignments']
+ params['scope.project.id'] = leaf_project['id']
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertEmpty(assignments)
@test.idempotent_id('9f02ccd9-9b57-46b4-8f77-dd5a736f3a06')
@@ -217,9 +218,10 @@
self.project['id'], self.user['id'], src_role['id'])
# List "effective" role assignments from user on the leaf project
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- leaf_project['id'], self.user['id']))['role_assignments']
+ params = {'scope.project.id': leaf_project['id'],
+ 'user.id': self.user['id']}
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertNotEmpty(assignments)
# Revoke role from parent project
@@ -228,7 +230,6 @@
# List "effective" role assignments from user on the leaf project
# should return an empty list
- assignments = (
- self.role_assignments.list_user_project_effective_assignments(
- leaf_project['id'], self.user['id']))['role_assignments']
+ assignments = self.role_assignments.list_role_assignments(
+ effective=True, **params)['role_assignments']
self.assertEmpty(assignments)
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index f5bf923..670cb2f 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -34,7 +34,7 @@
u_email = '%s@testmail.tm' % u_name
cls.u_password = data_utils.rand_password()
cls.domain = cls.domains_client.create_domain(
- data_utils.rand_name('domain'),
+ name=data_utils.rand_name('domain'),
description=data_utils.rand_name('domain-desc'))['domain']
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project'),
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index fd2683e..3ec4ff1 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -15,11 +15,17 @@
import time
+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 UsersV3TestJSON(base.BaseIdentityV3AdminTest):
@test.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e')
@@ -152,3 +158,30 @@
user = self.setup_test_user()
fetched_user = self.users_client.show_user(user['id'])['user']
self.assertEqual(user['id'], fetched_user['id'])
+
+ @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+ 'Security compliance not available.')
+ @test.idempotent_id('568cd46c-ee6c-4ab4-a33a-d3791931979e')
+ def test_password_history_not_enforced_in_admin_reset(self):
+ old_password = self.os.credentials.password
+ user_id = self.os.credentials.user_id
+
+ new_password = data_utils.rand_password()
+ self.users_client.update_user(user_id, password=new_password)
+ # To be safe, we add this cleanup to restore the original password in
+ # case something goes wrong before it is restored later.
+ self.addCleanup(
+ self.users_client.update_user, user_id, password=old_password)
+
+ # Check authorization with new password
+ self.token.auth(user_id=user_id, password=new_password)
+
+ if CONF.identity.user_unique_last_password_count > 1:
+ # The password history is not enforced via the admin reset route.
+ # We can set the same password.
+ self.users_client.update_user(user_id, password=new_password)
+
+ # Restore original password
+ self.users_client.update_user(user_id, password=old_password)
+ # Check authorization with old password
+ self.token.auth(user_id=user_id, password=old_password)
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 33d212c..bafb1f2 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -16,11 +16,15 @@
import time
from tempest.api.identity import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
from tempest import test
+CONF = config.CONF
+
+
class IdentityUsersTest(base.BaseIdentityV2Test):
@classmethod
@@ -31,36 +35,10 @@
cls.password = cls.creds.password
cls.tenant_name = cls.creds.tenant_name
- @test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
- def test_user_update_own_password(self):
-
- def _restore_password(client, user_id, old_pass, new_pass):
- # Reset auth to get a new token with the new password
- client.auth_provider.clear_auth()
- client.auth_provider.credentials.password = new_pass
- client.update_user_own_password(user_id, password=old_pass,
- original_password=new_pass)
- # Reset auth again to verify the password restore does work.
- # Clear auth restores the original credentials and deletes
- # cached auth data
- client.auth_provider.clear_auth()
- # NOTE(lbragstad): Fernet tokens are not subsecond aware and
- # Keystone should only be precise to the second. Sleep to ensure we
- # are passing the second boundary before attempting to
- # authenticate.
- time.sleep(1)
- client.auth_provider.set_auth()
-
- old_pass = self.creds.password
- new_pass = data_utils.rand_password()
- user_id = self.creds.user_id
- # to change password back. important for allow_tenant_isolation = false
- self.addCleanup(_restore_password, self.non_admin_users_client,
- user_id, old_pass=old_pass, new_pass=new_pass)
-
- # user updates own password
+ def _update_password(self, user_id, original_password, password):
self.non_admin_users_client.update_user_own_password(
- user_id, password=new_pass, original_password=old_pass)
+ user_id, password=password, original_password=original_password)
+
# 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.
@@ -68,13 +46,55 @@
# check authorization with new password
self.non_admin_token_client.auth(self.username,
- new_pass,
+ password,
self.tenant_name)
+ # Reset auth to get a new token with the new password
+ self.non_admin_users_client.auth_provider.clear_auth()
+ self.non_admin_users_client.auth_provider.credentials.password = (
+ password)
+
+ def _restore_password(self, user_id, old_pass, new_pass):
+ if CONF.identity_feature_enabled.security_compliance:
+ # First we need to clear the password history
+ unique_count = CONF.identity.user_unique_last_password_count
+ for i in range(unique_count):
+ random_pass = data_utils.rand_password()
+ self._update_password(
+ user_id, original_password=new_pass, password=random_pass)
+ new_pass = random_pass
+
+ self._update_password(
+ user_id, original_password=new_pass, password=old_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ self.non_admin_users_client.auth_provider.clear_auth()
+ # NOTE(lbragstad): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure we
+ # are passing the second boundary before attempting to
+ # authenticate.
+ time.sleep(1)
+ self.non_admin_users_client.auth_provider.set_auth()
+
+ @test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
+ def test_user_update_own_password(self):
+ old_pass = self.creds.password
+ old_token = self.non_admin_users_client.token
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.user_id
+
+ # to change password back. important for allow_tenant_isolation = false
+ self.addCleanup(self._restore_password, user_id, old_pass, new_pass)
+
+ # user updates own password
+ self._update_password(
+ user_id, original_password=old_pass, password=new_pass)
+
# authorize with old token should lead to Unauthorized
self.assertRaises(exceptions.Unauthorized,
self.non_admin_token_client.auth_token,
- self.non_admin_users_client.token)
+ old_token)
# authorize with old password should lead to Unauthorized
self.assertRaises(exceptions.Unauthorized,
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index d5bed96..b410da6 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -34,6 +34,7 @@
# it to be 'default'
token_id, resp = self.non_admin_token.get_token(
user_id=user_id,
+ username=username,
user_domain_id=user_domain_id,
password=password,
auth_data=True)
@@ -49,9 +50,19 @@
self.assertGreater(expires_at, now)
subject_id = resp['user']['id']
- self.assertEqual(subject_id, user_id)
+ if user_id:
+ self.assertEqual(subject_id, user_id)
+ else:
+ # Expect a user ID, but don't know what it will be.
+ self.assertGreaterEqual(len(subject_id), 0,
+ 'Expected user ID in token.')
subject_name = resp['user']['name']
- self.assertEqual(subject_name, username)
+ if username:
+ self.assertEqual(subject_name, username)
+ else:
+ # Expect a user name, but don't know what it will be.
+ self.assertGreaterEqual(len(subject_name), 0,
+ 'Expected user name in token.')
self.assertEqual(resp['methods'][0], 'password')
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 1a38f3a..f5b357c 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -15,12 +15,18 @@
import time
+import testtools
+
from tempest.api.identity import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
from tempest import test
+CONF = config.CONF
+
+
class IdentityV3UsersTest(base.BaseIdentityV3Test):
@classmethod
@@ -31,36 +37,11 @@
cls.username = cls.creds.username
cls.password = cls.creds.password
- @test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
- def test_user_update_own_password(self):
-
- def _restore_password(client, user_id, old_pass, new_pass):
- # Reset auth to get a new token with the new password
- client.auth_provider.clear_auth()
- client.auth_provider.credentials.password = new_pass
- client.update_user_password(user_id, password=old_pass,
- original_password=new_pass)
- # Reset auth again to verify the password restore does work.
- # Clear auth restores the original credentials and deletes
- # cached auth data
- client.auth_provider.clear_auth()
- # NOTE(lbragstad): Fernet tokens are not subsecond aware and
- # Keystone should only be precise to the second. Sleep to ensure we
- # are passing the second boundary before attempting to
- # authenticate.
- time.sleep(1)
- client.auth_provider.set_auth()
-
- old_pass = self.creds.password
- new_pass = data_utils.rand_password()
- user_id = self.creds.user_id
- # to change password back. important for allow_tenant_isolation = false
- self.addCleanup(_restore_password, self.non_admin_users_client,
- user_id, old_pass=old_pass, new_pass=new_pass)
-
- # user updates own password
+ def _update_password(self, original_password, password):
self.non_admin_users_client.update_user_password(
- user_id, password=new_pass, original_password=old_pass)
+ self.user_id,
+ password=password,
+ original_password=original_password)
# NOTE(morganfainberg): Fernet tokens are not subsecond aware and
# Keystone should only be precise to the second. Sleep to ensure
@@ -68,15 +49,112 @@
time.sleep(1)
# check authorization with new password
- self.non_admin_token.auth(user_id=self.user_id, password=new_pass)
+ self.non_admin_token.auth(user_id=self.user_id, password=password)
+
+ # Reset auth to get a new token with the new password
+ self.non_admin_users_client.auth_provider.clear_auth()
+ self.non_admin_users_client.auth_provider.credentials.password = (
+ password)
+
+ def _restore_password(self, old_pass, new_pass):
+ if CONF.identity_feature_enabled.security_compliance:
+ # First we need to clear the password history
+ unique_count = CONF.identity.user_unique_last_password_count
+ for i in range(unique_count):
+ random_pass = data_utils.rand_password()
+ self._update_password(
+ original_password=new_pass, password=random_pass)
+ new_pass = random_pass
+
+ self._update_password(original_password=new_pass, password=old_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ self.non_admin_users_client.auth_provider.clear_auth()
+ # NOTE(lbragstad): Fernet tokens are not subsecond aware and
+ # Keystone should only be precise to the second. Sleep to ensure we
+ # are passing the second boundary before attempting to
+ # authenticate.
+ time.sleep(1)
+ self.non_admin_users_client.auth_provider.set_auth()
+
+ @test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
+ def test_user_update_own_password(self):
+ old_pass = self.creds.password
+ old_token = self.non_admin_client.token
+ new_pass = data_utils.rand_password()
+
+ # to change password back. important for allow_tenant_isolation = false
+ self.addCleanup(self._restore_password, old_pass, new_pass)
+
+ # user updates own password
+ self._update_password(original_password=old_pass, password=new_pass)
# authorize with old token should lead to IdentityError (404 code)
self.assertRaises(exceptions.IdentityError,
self.non_admin_token.auth,
- token=self.non_admin_client.token)
+ token=old_token)
# authorize with old password should lead to Unauthorized
self.assertRaises(exceptions.Unauthorized,
self.non_admin_token.auth,
user_id=self.user_id,
password=old_pass)
+
+ @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+ 'Security compliance not available.')
+ @test.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
+ def test_password_history_check_self_service_api(self):
+ old_pass = self.creds.password
+ new_pass1 = data_utils.rand_password()
+ new_pass2 = data_utils.rand_password()
+
+ self.addCleanup(self._restore_password, old_pass, new_pass2)
+
+ # Update password
+ self._update_password(original_password=old_pass, password=new_pass1)
+
+ if CONF.identity.user_unique_last_password_count > 1:
+ # Can not reuse a previously set password
+ self.assertRaises(exceptions.BadRequest,
+ self.non_admin_users_client.update_user_password,
+ self.user_id,
+ password=new_pass1,
+ original_password=new_pass1)
+
+ self.assertRaises(exceptions.BadRequest,
+ self.non_admin_users_client.update_user_password,
+ self.user_id,
+ password=old_pass,
+ original_password=new_pass1)
+
+ # A different password can be set
+ self._update_password(original_password=new_pass1, password=new_pass2)
+
+ @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+ 'Security compliance not available.')
+ @test.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
+ def test_user_account_lockout(self):
+ password = self.creds.password
+
+ # First, we login using the correct credentials
+ self.non_admin_token.auth(user_id=self.user_id, password=password)
+
+ # Lock user account by using the wrong password to login
+ bad_password = data_utils.rand_password()
+ for i in range(CONF.identity.user_lockout_failure_attempts):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_token.auth,
+ user_id=self.user_id,
+ password=bad_password)
+
+ # The user account must be locked, so now it is not possible to login
+ # even using the correct password
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_token.auth,
+ user_id=self.user_id,
+ password=password)
+
+ # If we wait the required time, the user account will be unlocked
+ time.sleep(CONF.identity.user_lockout_duration + 1)
+ self.token.auth(user_id=self.user_id, password=password)
diff --git a/tempest/api/image/admin/v2/test_images.py b/tempest/api/image/admin/v2/test_images.py
index 9844a67..f22f321 100644
--- a/tempest/api/image/admin/v2/test_images.py
+++ b/tempest/api/image/admin/v2/test_images.py
@@ -34,11 +34,10 @@
def test_admin_deactivate_reactivate_image(self):
# Create image by non-admin tenant
image_name = data_utils.rand_name('image')
- image = self.client.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private')
- self.addCleanup(self.client.delete_image, image['id'])
+ image = self.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ visibility='private')
# upload an image file
content = data_utils.random_bytes()
image_file = six.BytesIO(content)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index 1cc3fa2..812c436 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -141,6 +141,7 @@
cls.client = cls.os.image_client_v2
cls.namespaces_client = cls.os.namespaces_client
cls.resource_types_client = cls.os.resource_types_client
+ cls.namespace_properties_client = cls.os.namespace_properties_client
cls.schemas_client = cls.os.schemas_client
def create_namespace(cls, namespace_name=None, visibility='public',
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 50f0926..9c211ef 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -14,6 +14,7 @@
from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
from tempest import test
@@ -54,3 +55,5 @@
body = self.image_member_client.list_image_members(image_id)
members = body['members']
self.assertEqual(0, len(members), str(members))
+ self.assertRaises(
+ lib_exc.NotFound, self.alt_img_cli.show_image, image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 9fbdcd7..0caaa67 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -36,7 +36,7 @@
msg = ("The container format and the disk format don't match. "
"Container format: %(container)s, Disk format: %(disk)s." %
{'container': container_format, 'disk': disk_format})
- raise exceptions.InvalidConfiguration(message=msg)
+ raise exceptions.InvalidConfiguration(msg)
return container_format, disk_format
@@ -54,7 +54,6 @@
disk_format=disk_format,
is_public=False,
properties=properties)
- self.assertIn('id', image)
self.assertEqual('New Name', image.get('name'))
self.assertFalse(image.get('is_public'))
self.assertEqual('queued', image.get('status'))
@@ -77,7 +76,6 @@
location=CONF.image.http_image,
properties={'key1': 'value1',
'key2': 'value2'})
- self.assertIn('id', body)
self.assertEqual('New Remote Image', body.get('name'))
self.assertFalse(body.get('is_public'))
self.assertEqual('active', body.get('status'))
@@ -92,7 +90,6 @@
container_format=container_format,
disk_format=disk_format, is_public=False,
copy_from=CONF.image.http_image)
- self.assertIn('id', image)
self.assertEqual('New Http Image', image.get('name'))
self.assertFalse(image.get('is_public'))
waiters.wait_for_image_status(self.client, image['id'], 'active')
@@ -109,7 +106,6 @@
is_public=False,
min_ram=40,
properties=properties)
- self.assertIn('id', body)
self.assertEqual('New_image_with_min_ram', body.get('name'))
self.assertFalse(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 5cf8084..453bb34 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -49,7 +49,6 @@
disk_format=disk_format,
visibility='private',
ramdisk_id=uuid)
- self.assertIn('id', image)
self.assertIn('name', image)
self.assertEqual(image_name, image['name'])
self.assertIn('visibility', image)
@@ -83,10 +82,10 @@
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
- image = self.client.create_image(name=image_name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private')
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private')
# Delete Image
self.client.delete_image(image['id'])
self.client.wait_for_resource_deletion(image['id'])
@@ -105,11 +104,10 @@
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
- image = self.client.create_image(name=image_name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private')
- self.addCleanup(self.client.delete_image, image['id'])
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private')
self.assertEqual('queued', image['status'])
# Now try uploading an image file
@@ -129,7 +127,6 @@
class ListImagesTest(base.BaseV2ImageTest):
- """Here we test the listing of image information"""
@classmethod
def resource_setup(cls):
@@ -157,23 +154,49 @@
"""
size = random.randint(1024, 4096)
image_file = six.BytesIO(data_utils.random_bytes(size))
+ tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
image = cls.create_image(container_format=container_format,
disk_format=disk_format,
- visibility='private')
+ visibility='private',
+ tags=tags)
cls.client.store_image_file(image['id'], data=image_file)
+ # Keep the data of one test image so it can be used to filter lists
+ cls.test_data = image
+ cls.test_data['size'] = size
return image['id']
+
+class ListUserImagesTest(ListImagesTest):
+ """Here we test the listing of image information"""
+
def _list_by_param_value_and_assert(self, params):
"""Perform list action with given params and validates result."""
-
+ # Retrieve the list of images that meet the filter
images_list = self.client.list_images(params=params)['images']
# Validating params of fetched images
+ msg = 'No images were found that met the filter criteria.'
+ self.assertNotEmpty(images_list, msg)
for image in images_list:
for key in params:
msg = "Failed to list images by %s" % key
self.assertEqual(params[key], image[key], msg)
+ def _list_sorted_by_image_size_and_assert(self, params, desc=False):
+ """Validate an image list that has been sorted by size
+
+ Perform list action with given params and validates the results are
+ sorted by image size in either ascending or descending order.
+ """
+ # Retrieve the list of images that meet the filter
+ images_list = self.client.list_images(params=params)['images']
+ # Validate that the list was fetched sorted accordingly
+ msg = 'No images were found that met the filter criteria.'
+ self.assertNotEmpty(images_list, msg)
+ sorted_list = [image['size'] for image in images_list]
+ msg = 'The list of images was not sorted correctly.'
+ self.assertEqual(sorted(sorted_list, reverse=desc), sorted_list, msg)
+
@test.idempotent_id('1e341d7a-90a9-494c-b143-2cdf2aeb6aee')
def test_list_no_params(self):
# Simple test to see all fixture images returned
@@ -185,8 +208,8 @@
@test.idempotent_id('9959ca1d-1aa7-4b7a-a1ea-0fff0499b37e')
def test_list_images_param_container_format(self):
- # Test to get all images with container_format='bare'
- params = {"container_format": "bare"}
+ # Test to get all images with a specific container_format
+ params = {"container_format": self.test_data['container_format']}
self._list_by_param_value_and_assert(params)
@test.idempotent_id('4a4735a7-f22f-49b6-b0d9-66e1ef7453eb')
@@ -254,6 +277,37 @@
params = {"owner": image['owner']}
self._list_by_param_value_and_assert(params)
+ @test.idempotent_id('55c8f5f5-bfed-409d-a6d5-4caeda985d7b')
+ def test_list_images_param_name(self):
+ # Test to get images by name
+ params = {'name': self.test_data['name']}
+ self._list_by_param_value_and_assert(params)
+
+ @test.idempotent_id('aa8ac4df-cff9-418b-8d0f-dd9c67b072c9')
+ def test_list_images_param_tag(self):
+ # Test to get images matching a tag
+ params = {'tag': self.test_data['tags'][0]}
+ images_list = self.client.list_images(params=params)['images']
+ # Validating properties of fetched images
+ self.assertNotEmpty(images_list)
+ for image in images_list:
+ msg = ("The image {image_name} does not have the expected tag "
+ "{expected_tag} among its tags: {observerd_tags}."
+ .format(image_name=image['name'],
+ expected_tag=self.test_data['tags'][0],
+ observerd_tags=image['tags']))
+ self.assertIn(self.test_data['tags'][0], image['tags'], msg)
+
+ @test.idempotent_id('eeadce49-04e0-43b7-aec7-52535d903e7a')
+ def test_list_images_param_sort(self):
+ params = {'sort': 'size:desc'}
+ self._list_sorted_by_image_size_and_assert(params, desc=True)
+
+ @test.idempotent_id('9faaa0c2-c3a5-43e1-8f61-61c54b409a49')
+ def test_list_images_param_sort_key_dir(self):
+ params = {'sort_key': 'size', 'sort_dir': 'desc'}
+ self._list_sorted_by_image_size_and_assert(params, desc=True)
+
@test.idempotent_id('622b925c-479f-4736-860d-adeaf13bc371')
def test_get_image_schema(self):
# Test to get image schema
@@ -267,3 +321,32 @@
schema = "images"
body = self.schemas_client.show_schema(schema)
self.assertEqual("images", body['name'])
+
+
+class ListSharedImagesTest(ListImagesTest):
+ """Here we test the listing of a shared image information"""
+
+ credentials = ['primary', 'alt']
+
+ @classmethod
+ def setup_clients(cls):
+ super(ListSharedImagesTest, cls).setup_clients()
+ cls.image_member_client = cls.os.image_member_client_v2
+ cls.alt_img_client = cls.os_alt.image_client_v2
+
+ @test.idempotent_id('3fa50be4-8e38-4c02-a8db-7811bb780122')
+ def test_list_images_param_member_status(self):
+ # Share one of the images created with the alt user
+ self.image_member_client.create_image_member(
+ image_id=self.test_data['id'],
+ member=self.alt_img_client.tenant_id)
+ # Update the info on the test data so it remains accurate
+ self.test_data['updated_at'] = self.client.show_image(
+ self.test_data['id'])['updated_at']
+ # As an image consumer you need to provide the member_status parameter
+ # along with the visibility=shared parameter in order for it to show
+ # results
+ params = {'member_status': 'pending', 'visibility': 'shared'}
+ fetched_images = self.alt_img_client.list_images(params)['images']
+ self.assertEqual(1, len(fetched_images))
+ self.assertEqual(self.test_data['id'], fetched_images[0]['id'])
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index fe8dd65..8a4b334 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -21,6 +21,8 @@
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
+ self.addCleanup(self.image_member_client.delete_image_member,
+ image_id, self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
@@ -42,6 +44,8 @@
image_id = self._create_image()
member = self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
+ self.addCleanup(self.image_member_client.delete_image_member,
+ image_id, self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
@@ -56,6 +60,8 @@
image_id = self._create_image()
self.image_member_client.create_image_member(
image_id, member=self.alt_tenant_id)
+ self.addCleanup(self.image_member_client.delete_image_member,
+ image_id, self.alt_tenant_id)
self.alt_image_member_client.update_image_member(image_id,
self.alt_tenant_id,
status='accepted')
diff --git a/tempest/api/image/v2/test_images_metadefs_namespace_properties.py b/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
new file mode 100644
index 0000000..7113db4
--- /dev/null
+++ b/tempest/api/image/v2/test_images_metadefs_namespace_properties.py
@@ -0,0 +1,57 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class MetadataNamespacePropertiesTest(base.BaseV2ImageTest):
+ """Test the Metadata definition namespace property basic functionality"""
+
+ @test.idempotent_id('b1a3765e-3a5d-4f6d-a3a7-3ca3476ae768')
+ def test_basic_meta_def_namespace_property(self):
+ # Get the available resource types and use one resource_type
+ body = self.resource_types_client.list_resource_types()
+ resource_name = body['resource_types'][0]['name']
+ enum = ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"]
+ # Create a namespace
+ namespace = self.create_namespace()
+ # Create resource type association
+ body = self.resource_types_client.create_resource_type_association(
+ namespace['namespace'], name=resource_name)
+ # Create a property
+ property_title = data_utils.rand_name('property')
+ body = self.namespace_properties_client.create_namespace_property(
+ namespace=namespace['namespace'], title=property_title,
+ name=resource_name, type="string", enum=enum)
+ self.assertEqual(property_title, body['title'])
+ # Show namespace property
+ body = self.namespace_properties_client.show_namespace_properties(
+ namespace['namespace'], resource_name)
+ self.assertEqual(resource_name, body['name'])
+ # Update namespace property
+ update_property_title = data_utils.rand_name('update-property')
+ body = self.namespace_properties_client.update_namespace_properties(
+ namespace['namespace'], resource_name,
+ title=update_property_title, type="string",
+ enum=enum, name=resource_name)
+ self.assertEqual(update_property_title, body['title'])
+ # Delete namespace property
+ self.namespace_properties_client.delete_namespace_property(
+ namespace['namespace'], resource_name)
+ # List namespace properties and validate deletion
+ namespace_property = [
+ namespace_property['title'] for namespace_property in
+ self.namespace_properties_client.list_namespace_properties(
+ namespace['namespace'])['properties']]
+ self.assertNotIn(update_property_title, namespace_property)
diff --git a/tempest/api/image/v2/test_images_metadefs_schema.py b/tempest/api/image/v2/test_images_metadefs_schema.py
new file mode 100644
index 0000000..7edf1af
--- /dev/null
+++ b/tempest/api/image/v2/test_images_metadefs_schema.py
@@ -0,0 +1,81 @@
+# Copyright 2016 EasyStack.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.image import base
+from tempest import test
+
+
+class MetadataSchemaTest(base.BaseV2ImageTest):
+ """Test to get metadata schema"""
+
+ @test.idempotent_id('e9e44891-3cb8-3b40-a532-e0a39fea3dab')
+ def test_get_metadata_namespace_schema(self):
+ # Test to get namespace schema
+ body = self.schemas_client.show_schema("metadefs/namespace")
+ self.assertEqual("namespace", body['name'])
+
+ @test.idempotent_id('ffe44891-678b-3ba0-a3e2-e0a3967b3aeb')
+ def test_get_metadata_namespaces_schema(self):
+ # Test to get namespaces schema
+ body = self.schemas_client.show_schema("metadefs/namespaces")
+ self.assertEqual("namespaces", body['name'])
+
+ @test.idempotent_id('fde34891-678b-3b40-ae32-e0a3e67b6beb')
+ def test_get_metadata_resource_type_schema(self):
+ # Test to get resource_type schema
+ body = self.schemas_client.show_schema("metadefs/resource_type")
+ self.assertEqual("resource_type_association", body['name'])
+
+ @test.idempotent_id('dfe4a891-b38b-3bf0-a3b2-e03ee67b3a3a')
+ def test_get_metadata_resources_types_schema(self):
+ # Test to get resource_types schema
+ body = self.schemas_client.show_schema("metadefs/resource_types")
+ self.assertEqual("resource_type_associations", body['name'])
+
+ @test.idempotent_id('dff4a891-b38b-3bf0-a3b2-e03ee67b3a3b')
+ def test_get_metadata_object_schema(self):
+ # Test to get object schema
+ body = self.schemas_client.show_schema("metadefs/object")
+ self.assertEqual("object", body['name'])
+
+ @test.idempotent_id('dee4a891-b38b-3bf0-a3b2-e03ee67b3a3c')
+ def test_get_metadata_objects_schema(self):
+ # Test to get objects schema
+ body = self.schemas_client.show_schema("metadefs/objects")
+ self.assertEqual("objects", body['name'])
+
+ @test.idempotent_id('dae4a891-b38b-3bf0-a3b2-e03ee67b3a3d')
+ def test_get_metadata_property_schema(self):
+ # Test to get property schema
+ body = self.schemas_client.show_schema("metadefs/property")
+ self.assertEqual("property", body['name'])
+
+ @test.idempotent_id('dce4a891-b38b-3bf0-a3b2-e03ee67b3a3e')
+ def test_get_metadata_properties_schema(self):
+ # Test to get properties schema
+ body = self.schemas_client.show_schema("metadefs/properties")
+ self.assertEqual("properties", body['name'])
+
+ @test.idempotent_id('dde4a891-b38b-3bf0-a3b2-e03ee67b3a3e')
+ def test_get_metadata_tag_schema(self):
+ # Test to get tag schema
+ body = self.schemas_client.show_schema("metadefs/tag")
+ self.assertEqual("tag", body['name'])
+
+ @test.idempotent_id('cde4a891-b38b-3bf0-a3b2-e03ee67b3a3a')
+ def test_get_metadata_tags_schema(self):
+ # Test to get tags schema
+ body = self.schemas_client.show_schema("metadefs/tags")
+ self.assertEqual("tags", body['name'])
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 7b037d5..c256b5b 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -38,11 +38,6 @@
msg = "quotas extension not enabled."
raise cls.skipException(msg)
- @classmethod
- def setup_clients(cls):
- super(QuotasNegativeTest, cls).setup_clients()
- cls.identity_admin_client = cls.os_adm.identity_client
-
@test.idempotent_id('644f4e1b-1bf9-4af0-9fd8-eb56ac0f51cf')
def test_network_quota_exceeding(self):
# Set the network quota to two
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index 3a264ff..978fb8f 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -43,11 +43,6 @@
msg = "quotas extension not enabled."
raise cls.skipException(msg)
- @classmethod
- def setup_clients(cls):
- super(QuotasTest, cls).setup_clients()
- cls.identity_admin_client = cls.os_adm.identity_client
-
def _check_quotas(self, new_quotas):
# Add a project to conduct the test
project = data_utils.rand_name('test_project_')
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 629926d..c2c42bb 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -81,6 +81,7 @@
cls.security_group_rules_client = (
cls.os.security_group_rules_client)
cls.network_versions_client = cls.os.network_versions_client
+ cls.service_providers_client = cls.os.service_providers_client
@classmethod
def resource_setup(cls):
@@ -135,12 +136,12 @@
super(BaseNetworkTest, cls).resource_cleanup()
@classmethod
- def create_network(cls, network_name=None):
+ def create_network(cls, network_name=None, **kwargs):
"""Wrapper utility that returns a test network."""
network_name = network_name or data_utils.rand_name(
- cls.__name__ + "-network")
+ cls.__name__ + '-test-network')
- body = cls.networks_client.create_network(name=network_name)
+ body = cls.networks_client.create_network(name=network_name, **kwargs)
network = body['network']
cls.networks.append(network)
return network
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index d2056c4..acac22b 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -209,12 +209,16 @@
def test_show_network_fields(self):
# Verify specific fields of a network
fields = ['id', 'name']
+ if test.is_extension_enabled('net-mtu', 'network'):
+ fields.append('mtu')
body = self.networks_client.show_network(self.network['id'],
fields=fields)
network = body['network']
self.assertEqual(sorted(network.keys()), sorted(fields))
for field_name in fields:
self.assertEqual(network[field_name], self.network[field_name])
+ self.assertNotIn('tenant_id', network)
+ self.assertNotIn('project_id', network)
@test.attr(type='smoke')
@test.idempotent_id('f7ffdeda-e200-4a7a-bcbe-05716e86bf43')
@@ -229,6 +233,8 @@
def test_list_networks_fields(self):
# Verify specific fields of the networks
fields = ['id', 'name']
+ if test.is_extension_enabled('net-mtu', 'network'):
+ fields.append('mtu')
body = self.networks_client.list_networks(fields=fields)
networks = body['networks']
self.assertNotEmpty(networks, "Network list returned is empty")
@@ -385,6 +391,21 @@
network_id=CONF.network.public_network_id)
self.assertEmpty(body['subnets'], "Public subnets visible")
+ @test.idempotent_id('c72c1c0c-2193-4aca-ccc4-b1442640bbbb')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_create_update_network_description(self):
+ body = self.create_network(description='d1')
+ self.assertEqual('d1', body['description'])
+ net_id = body['id']
+ body = self.networks_client.list_networks(id=net_id)['networks'][0]
+ self.assertEqual('d1', body['description'])
+ body = self.networks_client.update_network(body['id'],
+ description='d2')
+ self.assertEqual('d2', body['network']['description'])
+ body = self.networks_client.list_networks(id=net_id)['networks'][0]
+ self.assertEqual('d2', body['description'])
+
class BulkNetworkOpsTest(base.BaseNetworkTest):
"""Tests the following operations in the Neutron API:
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 15d289d..e7153f0 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -108,7 +108,7 @@
if ((address.version == 4 and address.prefixlen >= 30) or
(address.version == 6 and address.prefixlen >= 126)):
msg = ("Subnet %s isn't large enough for the test" % address.cidr)
- raise exceptions.InvalidConfiguration(message=msg)
+ raise exceptions.InvalidConfiguration(msg)
allocation_pools = {'allocation_pools': [{'start': str(address[2]),
'end': str(address[-2])}]}
subnet = self.create_subnet(network, cidr=address,
@@ -360,11 +360,6 @@
class PortsAdminExtendedAttrsTestJSON(base.BaseAdminNetworkTest):
@classmethod
- def setup_clients(cls):
- super(PortsAdminExtendedAttrsTestJSON, cls).setup_clients()
- cls.identity_client = cls.os_adm.identity_client
-
- @classmethod
def resource_setup(cls):
super(PortsAdminExtendedAttrsTestJSON, cls).resource_setup()
cls.network = cls.create_network()
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index ed6a302..f2170ad 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -34,11 +34,6 @@
raise cls.skipException(msg)
@classmethod
- def setup_clients(cls):
- super(RoutersTest, cls).setup_clients()
- cls.identity_admin_client = cls.os_adm.identity_client
-
- @classmethod
def resource_setup(cls):
super(RoutersTest, cls).resource_setup()
cls.tenant_cidr = (CONF.network.project_network_cidr
@@ -49,41 +44,31 @@
@test.idempotent_id('f64403e2-8483-4b34-8ccd-b09a87bcc68c')
def test_create_show_list_update_delete_router(self):
# Create a router
- # NOTE(salv-orlando): Do not invoke self.create_router
- # as we need to check the response code
- name = data_utils.rand_name('router-')
- create_body = self.routers_client.create_router(
- name=name, external_gateway_info={
- "network_id": CONF.network.public_network_id},
- admin_state_up=False)
- self.addCleanup(self._delete_router, create_body['router']['id'])
- self.assertEqual(create_body['router']['name'], name)
+ router = self._create_router(
+ admin_state_up=False,
+ external_network_id=CONF.network.public_network_id)
+ self.assertEqual(router['admin_state_up'], False)
self.assertEqual(
- create_body['router']['external_gateway_info']['network_id'],
+ router['external_gateway_info']['network_id'],
CONF.network.public_network_id)
- self.assertEqual(create_body['router']['admin_state_up'], False)
# Show details of the created router
- show_body = self.routers_client.show_router(
- create_body['router']['id'])
- self.assertEqual(show_body['router']['name'], name)
+ router_show = self.routers_client.show_router(
+ router['id'])['router']
+ self.assertEqual(router_show['name'], router['name'])
self.assertEqual(
- show_body['router']['external_gateway_info']['network_id'],
+ router_show['external_gateway_info']['network_id'],
CONF.network.public_network_id)
- self.assertEqual(show_body['router']['admin_state_up'], False)
# List routers and verify if created router is there in response
- list_body = self.routers_client.list_routers()
- routers_list = list()
- for router in list_body['routers']:
- routers_list.append(router['id'])
- self.assertIn(create_body['router']['id'], routers_list)
+ routers = self.routers_client.list_routers()['routers']
+ self.assertIn(router['id'], map(lambda x: x['id'], routers))
# Update the name of router and verify if it is updated
- updated_name = 'updated ' + name
- update_body = self.routers_client.update_router(
- create_body['router']['id'], name=updated_name)
- self.assertEqual(update_body['router']['name'], updated_name)
- show_body = self.routers_client.show_router(
- create_body['router']['id'])
- self.assertEqual(show_body['router']['name'], updated_name)
+ updated_name = 'updated' + router['name']
+ router_update = self.routers_client.update_router(
+ router['id'], name=updated_name)['router']
+ self.assertEqual(router_update['name'], updated_name)
+ router_show = self.routers_client.show_router(
+ router['id'])['router']
+ self.assertEqual(router_show['name'], updated_name)
@test.idempotent_id('e54dd3a3-4352-4921-b09d-44369ae17397')
def test_create_router_setting_project_id(self):
diff --git a/tempest/api/network/test_service_providers.py b/tempest/api/network/test_service_providers.py
new file mode 100644
index 0000000..be17b3e
--- /dev/null
+++ b/tempest/api/network/test_service_providers.py
@@ -0,0 +1,28 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import testtools
+
+from tempest.api.network import base
+from tempest import test
+
+
+class ServiceProvidersTest(base.BaseNetworkTest):
+
+ @test.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6')
+ @testtools.skipUnless(
+ test.is_extension_enabled('service-type', 'network'),
+ 'service-type extension not enabled.')
+ def test_service_providers_list(self):
+ body = self.service_providers_client.list_service_providers()
+ self.assertIn('service_providers', body)
+ self.assertIsInstance(body['service_providers'], list)
diff --git a/tempest/api/network/test_service_type_management.py b/tempest/api/network/test_service_type_management.py
deleted file mode 100644
index f49f082..0000000
--- a/tempest/api/network/test_service_type_management.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.api.network import base
-from tempest.lib import decorators
-from tempest import test
-
-
-class ServiceTypeManagementTestJSON(base.BaseNetworkTest):
-
- @classmethod
- def skip_checks(cls):
- super(ServiceTypeManagementTestJSON, cls).skip_checks()
- if not test.is_extension_enabled('service-type', 'network'):
- msg = "Neutron Service Type Management not enabled."
- raise cls.skipException(msg)
-
- @decorators.skip_because(bug="1400370")
- @test.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6')
- def test_service_provider_list(self):
- body = self.client.list_service_providers()
- self.assertIsInstance(body['service_providers'], list)
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 1b1ffd1..535137e 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from tempest.common import custom_matchers
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -23,6 +25,38 @@
CONF = config.CONF
+def delete_containers(containers, container_client, object_client):
+ """Remove containers and all objects in them.
+
+ The containers should be visible from the container_client given.
+ Will not throw any error if the containers don't exist.
+ Will not check that object and container deletions succeed.
+ After delete all the objects from a container, it will wait 2
+ seconds before delete the container itself, in order to deployments
+ using HA proxy sync the deletion properly, otherwise, the container
+ might fail to be deleted because it's not empty.
+
+ :param containers: List of containers to be deleted
+ :param container_client: Client to be used to delete containers
+ :param object_client: Client to be used to delete objects
+ """
+ for cont in containers:
+ try:
+ params = {'limit': 9999, 'format': 'json'}
+ resp, objlist = container_client.list_container_contents(
+ cont, params)
+ # delete every object in the container
+ for obj in objlist:
+ test_utils.call_and_ignore_notfound_exc(
+ object_client.delete_object, cont, obj['name'])
+ # sleep 2 seconds to sync the deletion of the objects
+ # in HA deployment
+ time.sleep(2)
+ container_client.delete_container(cont)
+ except lib_exc.NotFound:
+ pass
+
+
class BaseObjectTest(tempest.test.BaseTestCase):
credentials = [['operator', CONF.object_storage.operator_role]]
@@ -48,6 +82,7 @@
cls.object_client = cls.os.object_client
cls.container_client = cls.os.container_client
cls.account_client = cls.os.account_client
+ cls.capabilities_client = cls.os.capabilities_client
@classmethod
def resource_setup(cls):
@@ -63,7 +98,7 @@
cls.policies = None
if CONF.object_storage_feature_enabled.discoverability:
- _, body = cls.account_client.list_extensions()
+ _, body = cls.capabilities_client.list_capabilities()
if 'swift' in body and 'policies' in body['swift']:
cls.policies = body['swift']['policies']
@@ -95,35 +130,12 @@
return object_name, data
@classmethod
- def delete_containers(cls, container_client=None,
- object_client=None):
- """Remove containers and all objects in them.
-
- The containers should be visible from the container_client given.
- Will not throw any error if the containers don't exist.
- Will not check that object and container deletions succeed.
-
- :param container_client: if None, use cls.container_client, this means
- that the default testing user will be used (see 'username' in
- 'etc/tempest.conf')
- :param object_client: if None, use cls.object_client
- """
+ def delete_containers(cls, container_client=None, object_client=None):
if container_client is None:
container_client = cls.container_client
if object_client is None:
object_client = cls.object_client
- for cont in cls.containers:
- try:
- params = {'limit': 9999, 'format': 'json'}
- resp, objlist = container_client.list_container_contents(
- cont, params)
- # delete every object in the container
- for obj in objlist:
- test_utils.call_and_ignore_notfound_exc(
- object_client.delete_object, cont, obj['name'])
- container_client.delete_container(cont)
- except lib_exc.NotFound:
- pass
+ delete_containers(cls.containers, container_client, object_client)
def assertHeaders(self, resp, target, method):
"""Check the existence and the format of response headers"""
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index a75ed98..1eda49a 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -27,7 +27,10 @@
self.containers = []
def tearDown(self):
- self.delete_containers()
+ # NOTE(andreaf) BulkTests needs to cleanup containers after each
+ # test is executed.
+ base.delete_containers(self.containers, self.container_client,
+ self.object_client)
super(BulkTest, self).tearDown()
def _create_archive(self):
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index eda4568..59129e5 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -15,6 +15,7 @@
import random
+import six
import testtools
from tempest.api.object_storage import base
@@ -60,8 +61,10 @@
self.assertHeaders(resp, 'Account', 'GET')
self.assertIsNotNone(container_list)
+
for container_name in self.containers:
- self.assertIn(container_name, container_list)
+ self.assertIn(six.text_type(container_name).encode('utf-8'),
+ container_list)
@test.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
def test_list_no_containers(self):
@@ -132,7 +135,7 @@
not CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_list_extensions(self):
- resp, extensions = self.account_client.list_extensions()
+ resp, extensions = self.capabilities_client.list_capabilities()
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
@@ -246,7 +249,8 @@
params=params)
self.assertHeaders(resp, 'Account', 'GET')
for container in container_list:
- self.assertEqual(True, container.startswith(prefix))
+ self.assertEqual(True, container.decode(
+ 'utf-8').startswith(prefix))
@test.attr(type='smoke')
@test.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80')
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index 7049db0..f63c518 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -13,11 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.object_storage import base
+from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
from tempest import test
+CONF = config.CONF
+
class ContainerNegativeTest(base.BaseObjectTest):
@@ -25,12 +30,16 @@
def resource_setup(cls):
super(ContainerNegativeTest, cls).resource_setup()
- # use /info to get default constraints
- _, body = cls.account_client.list_extensions()
- cls.constraints = body['swift']
+ if CONF.object_storage_feature_enabled.discoverability:
+ # use /info to get default constraints
+ _, body = cls.capabilities_client.list_capabilities()
+ cls.constraints = body['swift']
@test.attr(type=["negative"])
@test.idempotent_id('30686921-4bed-4764-a038-40d741ed4e78')
+ @testtools.skipUnless(
+ CONF.object_storage_feature_enabled.discoverability,
+ 'Discoverability function is disabled')
def test_create_container_name_exceeds_max_length(self):
# Attempts to create a container name that is longer than max
max_length = self.constraints['max_container_name_length']
@@ -44,6 +53,9 @@
@test.attr(type=["negative"])
@test.idempotent_id('41e645bf-2e68-4f84-bf7b-c71aa5cd76ce')
+ @testtools.skipUnless(
+ CONF.object_storage_feature_enabled.discoverability,
+ 'Discoverability function is disabled')
def test_create_container_metadata_name_exceeds_max_length(self):
# Attempts to create container with metadata name
# that is longer than max.
@@ -58,6 +70,9 @@
@test.attr(type=["negative"])
@test.idempotent_id('81e36922-326b-4b7c-8155-3bbceecd7a82')
+ @testtools.skipUnless(
+ CONF.object_storage_feature_enabled.discoverability,
+ 'Discoverability function is disabled')
def test_create_container_metadata_value_exceeds_max_length(self):
# Attempts to create container with metadata value
# that is longer than max.
@@ -72,6 +87,9 @@
@test.attr(type=["negative"])
@test.idempotent_id('ac666539-d566-4f02-8ceb-58e968dfb732')
+ @testtools.skipUnless(
+ CONF.object_storage_feature_enabled.discoverability,
+ 'Discoverability function is disabled')
def test_create_container_metadata_exceeds_overall_metadata_count(self):
# Attempts to create container with metadata that exceeds the
# default count
@@ -92,47 +110,39 @@
def test_get_metadata_headers_with_invalid_container_name(self):
# Attempts to retrieve metadata headers with an invalid
# container name.
- invalid_name = data_utils.rand_name(name="TestInvalidContainer")
-
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_metadata,
- invalid_name)
+ 'invalid_container_name')
@test.attr(type=["negative"])
@test.idempotent_id('125a24fa-90a7-4cfc-b604-44e49d788390')
def test_update_metadata_with_nonexistent_container_name(self):
# Attempts to update metadata using a nonexistent container name.
- nonexistent_name = data_utils.rand_name(
- name="TestNonexistentContainer")
metadata = {'animal': 'penguin'}
self.assertRaises(exceptions.NotFound,
self.container_client.update_container_metadata,
- nonexistent_name, metadata)
+ 'nonexistent_container_name', metadata)
@test.attr(type=["negative"])
@test.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba')
def test_delete_with_nonexistent_container_name(self):
# Attempts to delete metadata using a nonexistent container name.
- nonexistent_name = data_utils.rand_name(
- name="TestNonexistentContainer")
metadata = {'animal': 'penguin'}
self.assertRaises(exceptions.NotFound,
self.container_client.delete_container_metadata,
- nonexistent_name, metadata)
+ 'nonexistent_container_name', metadata)
@test.attr(type=["negative"])
@test.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5')
def test_list_all_container_objects_with_nonexistent_container(self):
# Attempts to get a listing of all objects on a container
# that doesn't exist.
- nonexistent_name = data_utils.rand_name(
- name="TestNonexistentContainer")
params = {'limit': 9999, 'format': 'json'}
self.assertRaises(exceptions.NotFound,
self.container_client.list_container_contents,
- nonexistent_name, params)
+ 'nonexistent_container_name', params)
@test.attr(type=["negative"])
@test.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e')
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index a3792b4..6d27502 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -10,10 +10,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-
-import logging
-
import netaddr
+from oslo_log import log as logging
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
@@ -79,7 +77,7 @@
cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
resources = (cls.client.list_resources(cls.stack_identifier)
['resources'])
- except exceptions.TimeoutException as e:
+ except exceptions.TimeoutException:
if CONF.compute_feature_enabled.console_output:
# attempt to log the server console to help with debugging
# the cause of the server not signalling the waitcondition
@@ -91,7 +89,7 @@
output = cls.servers_client.get_console_output(
server_id)['output']
LOG.debug(output)
- raise e
+ raise
cls.test_resources = {}
for resource in resources:
diff --git a/tempest/api/volume/admin/test_snapshots_actions.py b/tempest/api/volume/admin/test_snapshots_actions.py
index 5af83b3..29a161b 100644
--- a/tempest/api/volume/admin/test_snapshots_actions.py
+++ b/tempest/api/volume/admin/test_snapshots_actions.py
@@ -28,11 +28,6 @@
raise cls.skipException("Cinder snapshot feature disabled")
@classmethod
- def setup_clients(cls):
- super(SnapshotsActionsV2Test, cls).setup_clients()
- cls.client = cls.snapshots_client
-
- @classmethod
def resource_setup(cls):
super(SnapshotsActionsV2Test, cls).resource_setup()
@@ -59,7 +54,7 @@
reset_snapshot_status(temp_snapshot['id'], status)
self.admin_snapshots_client.\
force_delete_snapshot(temp_snapshot['id'])
- self.client.wait_for_resource_deletion(temp_snapshot['id'])
+ self.snapshots_client.wait_for_resource_deletion(temp_snapshot['id'])
def _get_progress_alias(self):
return 'os-extended-snapshot-attributes:progress'
@@ -85,8 +80,9 @@
progress = '80%'
status = 'error'
progress_alias = self._get_progress_alias()
- self.client.update_snapshot_status(self.snapshot['id'],
- status=status, progress=progress)
+ self.snapshots_client.update_snapshot_status(self.snapshot['id'],
+ status=status,
+ progress=progress)
snapshot_get = self.admin_snapshots_client.show_snapshot(
self.snapshot['id'])['snapshot']
self.assertEqual(status, snapshot_get['status'])
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 3098cab..6b2acc6 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -93,7 +93,6 @@
"vendor_name": vendor}
body = self.create_volume_type(description=description, name=name,
extra_specs=extra_specs)
- self.assertIn('id', body)
self.assertIn('name', body)
self.assertEqual(name, body['name'],
"The created volume_type name is not equal "
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index e7a3f62..a63cbf0 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -14,32 +14,11 @@
# under the License.
from tempest.api.volume import base
-from tempest import config
from tempest import test
-CONF = config.CONF
-
class VolumesActionsV2Test(base.BaseVolumeAdminTest):
- @classmethod
- def setup_clients(cls):
- super(VolumesActionsV2Test, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @classmethod
- def resource_setup(cls):
- super(VolumesActionsV2Test, cls).resource_setup()
-
- # Create a test shared volume for tests
- cls.volume = cls.create_volume()
-
- def tearDown(self):
- # Set volume's status to available after test
- self.admin_volume_client.reset_volume_status(
- self.volume['id'], status='available')
- super(VolumesActionsV2Test, self).tearDown()
-
def _create_reset_and_force_delete_temp_volume(self, status=None):
# Create volume, reset volume status, and force delete temp volume
temp_volume = self.create_volume()
@@ -47,16 +26,18 @@
self.admin_volume_client.reset_volume_status(
temp_volume['id'], status=status)
self.admin_volume_client.force_delete_volume(temp_volume['id'])
- self.client.wait_for_resource_deletion(temp_volume['id'])
+ self.volumes_client.wait_for_resource_deletion(temp_volume['id'])
@test.idempotent_id('d063f96e-a2e0-4f34-8b8a-395c42de1845')
def test_volume_reset_status(self):
# test volume reset status : available->error->available
- self.admin_volume_client.reset_volume_status(
- self.volume['id'], status='error')
- volume_get = self.admin_volume_client.show_volume(
- self.volume['id'])['volume']
- self.assertEqual('error', volume_get['status'])
+ volume = self.create_volume()
+ for status in ['error', 'available']:
+ self.admin_volume_client.reset_volume_status(
+ volume['id'], status=status)
+ volume_get = self.admin_volume_client.show_volume(
+ volume['id'])['volume']
+ self.assertEqual(status, volume_get['status'])
@test.idempotent_id('21737d5a-92f2-46d7-b009-a0cc0ee7a570')
def test_volume_force_delete_when_volume_is_creating(self):
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 73f1f8f..61d4ba7 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -33,12 +33,6 @@
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
- @classmethod
- def resource_setup(cls):
- super(VolumesBackupsAdminV2Test, cls).resource_setup()
-
- cls.volume = cls.create_volume()
-
def _delete_backup(self, backup_id):
self.admin_backups_client.delete_backup(backup_id)
self.admin_backups_client.wait_for_resource_deletion(backup_id)
@@ -62,14 +56,13 @@
Cinder allows exporting DB backup information through its API so it can
be imported back in case of a DB loss.
"""
+ volume = self.create_volume()
# Create backup
backup_name = data_utils.rand_name(self.__class__.__name__ + '-Backup')
- backup = (self.admin_backups_client.create_backup(
- volume_id=self.volume['id'], name=backup_name)['backup'])
- self.addCleanup(self._delete_backup, backup['id'])
+ backup = (self.create_backup(backup_client=self.admin_backups_client,
+ volume_id=volume['id'],
+ name=backup_name))
self.assertEqual(backup_name, backup['name'])
- waiters.wait_for_backup_status(self.admin_backups_client,
- backup['id'], 'available')
# Export Backup
export_backup = (self.admin_backups_client.export_backup(backup['id'])
@@ -126,16 +119,15 @@
@test.idempotent_id('47a35425-a891-4e13-961c-c45deea21e94')
def test_volume_backup_reset_status(self):
+ # Create a volume
+ volume = self.create_volume()
# Create a backup
backup_name = data_utils.rand_name(
self.__class__.__name__ + '-Backup')
- backup = self.admin_backups_client.create_backup(
- volume_id=self.volume['id'], name=backup_name)['backup']
- self.addCleanup(self.admin_backups_client.delete_backup,
- backup['id'])
+ backup = self.create_backup(backup_client=self.admin_backups_client,
+ volume_id=volume['id'],
+ name=backup_name)
self.assertEqual(backup_name, backup['name'])
- waiters.wait_for_backup_status(self.admin_backups_client,
- backup['id'], 'available')
# Reset backup status to error
self.admin_backups_client.reset_backup_status(backup_id=backup['id'],
status="error")
diff --git a/tempest/api/volume/admin/test_backends_capabilities.py b/tempest/api/volume/admin/v2/test_backends_capabilities.py
similarity index 90%
rename from tempest/api/volume/admin/test_backends_capabilities.py
rename to tempest/api/volume/admin/v2/test_backends_capabilities.py
index 8a21853..fc9066c 100644
--- a/tempest/api/volume/admin/test_backends_capabilities.py
+++ b/tempest/api/volume/admin/v2/test_backends_capabilities.py
@@ -38,13 +38,13 @@
# Get host list, formation: host@backend-name
cls.hosts = [
pool['name'] for pool in
- cls.admin_volume_client.show_pools()['pools']
+ cls.admin_scheduler_stats_client.list_pools()['pools']
]
@test.idempotent_id('3750af44-5ea2-4cd4-bc3e-56e7e6caf854')
def test_get_capabilities_backend(self):
# Test backend properties
- backend = self.admin_volume_client.show_backend_capabilities(
+ backend = self.admin_capabilities_client.show_backend_capabilities(
self.hosts[0])
# Verify getting capabilities parameters from a backend
@@ -62,12 +62,12 @@
# Get list backend capabilities using show_pools
cinder_pools = [
pool['capabilities'] for pool in
- self.admin_volume_client.show_pools(detail=True)['pools']
+ self.admin_scheduler_stats_client.list_pools(detail=True)['pools']
]
# Get list backends capabilities using show_backend_capabilities
capabilities = [
- self.admin_volume_client.show_backend_capabilities(
+ self.admin_capabilities_client.show_backend_capabilities(
host=host) for host in self.hosts
]
diff --git a/tempest/api/volume/admin/v2/test_volume_pools.py b/tempest/api/volume/admin/v2/test_volume_pools.py
index c662e8c..8544a6a 100644
--- a/tempest/api/volume/admin/v2/test_volume_pools.py
+++ b/tempest/api/volume/admin/v2/test_volume_pools.py
@@ -25,19 +25,18 @@
# Create a test shared volume for tests
cls.volume = cls.create_volume()
- @test.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
- def test_get_pools_without_details(self):
- volume_info = self.admin_volume_client. \
- show_volume(self.volume['id'])['volume']
- cinder_pools = self.admin_volume_client.show_pools()['pools']
+ def _assert_host_volume_in_pools(self, with_detail=False):
+ volume_info = self.admin_volume_client.show_volume(
+ self.volume['id'])['volume']
+ cinder_pools = self.admin_volume_client.show_pools(
+ detail=with_detail)['pools']
self.assertIn(volume_info['os-vol-host-attr:host'],
[pool['name'] for pool in cinder_pools])
+ @test.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
+ def test_get_pools_without_details(self):
+ self._assert_host_volume_in_pools()
+
@test.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced')
def test_get_pools_with_details(self):
- volume_info = self.admin_volume_client. \
- show_volume(self.volume['id'])['volume']
- cinder_pools = self.admin_volume_client.\
- show_pools(detail=True)['pools']
- self.assertIn(volume_info['os-vol-host-attr:host'],
- [pool['name'] for pool in cinder_pools])
+ self._assert_host_volume_in_pools(with_detail=True)
diff --git a/tempest/api/volume/api_microversion_fixture.py b/tempest/api/volume/api_microversion_fixture.py
index 6817eaa..64bc537 100644
--- a/tempest/api/volume/api_microversion_fixture.py
+++ b/tempest/api/volume/api_microversion_fixture.py
@@ -13,7 +13,7 @@
import fixtures
-from tempest.services.volume.base import base_v3_client
+from tempest.lib.services.volume.v3 import base_client
class APIMicroversionFixture(fixtures.Fixture):
@@ -23,8 +23,8 @@
def _setUp(self):
super(APIMicroversionFixture, self)._setUp()
- base_v3_client.VOLUME_MICROVERSION = self.volume_microversion
+ base_client.VOLUME_MICROVERSION = self.volume_microversion
self.addCleanup(self._reset_volume_microversion)
def _reset_volume_microversion(self):
- base_v3_client.VOLUME_MICROVERSION = None
+ base_client.VOLUME_MICROVERSION = None
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 6db3807..90dc7f4 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -51,7 +51,7 @@
raise cls.skipException(msg)
else:
msg = ("Invalid Cinder API version (%s)" % cls._api_version)
- raise exceptions.InvalidConfiguration(message=msg)
+ raise exceptions.InvalidConfiguration(msg)
@classmethod
def setup_credentials(cls):
@@ -73,6 +73,7 @@
cls.volumes_extension_client = cls.os.volumes_extension_client
cls.availability_zone_client = (
cls.os.volume_availability_zone_client)
+ cls.volume_limits_client = cls.os.volume_limits_client
else:
cls.snapshots_client = cls.os.snapshots_v2_client
cls.volumes_client = cls.os.volumes_v2_client
@@ -80,6 +81,7 @@
cls.volumes_extension_client = cls.os.volumes_v2_extension_client
cls.availability_zone_client = (
cls.os.volume_v2_availability_zone_client)
+ cls.volume_limits_client = cls.os.volume_v2_limits_client
@classmethod
def resource_setup(cls):
@@ -116,6 +118,12 @@
if 'size' not in kwargs:
kwargs['size'] = CONF.volume.volume_size
+ if 'imageRef' in kwargs:
+ image = cls.compute_images_client.show_image(
+ kwargs['imageRef'])['image']
+ min_disk = image.get('minDisk')
+ kwargs['size'] = max(kwargs['size'], min_disk)
+
name_field = cls.special_fields['name_field']
if name_field not in kwargs:
name = data_utils.rand_name(cls.__name__ + '-Volume')
@@ -142,6 +150,18 @@
snapshot['id'], 'available')
return snapshot
+ def create_backup(self, volume_id, backup_client=None, **kwargs):
+ """Wrapper utility that returns a test backup."""
+ if backup_client is None:
+ backup_client = self.backups_client
+
+ backup = backup_client.create_backup(
+ volume_id=volume_id, **kwargs)['backup']
+ self.addCleanup(backup_client.delete_backup, backup['id'])
+ waiters.wait_for_backup_status(backup_client, backup['id'],
+ 'available')
+ return backup
+
# NOTE(afazekas): these create_* and clean_* could be defined
# only in a single location in the source, and could be more general.
@@ -151,6 +171,18 @@
client.delete_volume(volume_id)
client.wait_for_resource_deletion(volume_id)
+ def attach_volume(self, server_id, volume_id):
+ """Attachs a volume to a server"""
+ self.servers_client.attach_volume(
+ server_id, volumeId=volume_id,
+ device='/dev/%s' % CONF.compute.volume_device_name)
+ waiters.wait_for_volume_status(self.volumes_client,
+ volume_id, 'in-use')
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ volume_id, 'available')
+ self.addCleanup(self.servers_client.detach_volume, server_id,
+ volume_id)
+
@classmethod
def clear_volumes(cls):
for volume in cls.volumes:
@@ -168,16 +200,13 @@
@classmethod
def clear_snapshots(cls):
for snapshot in cls.snapshots:
- try:
- cls.snapshots_client.delete_snapshot(snapshot['id'])
- except Exception:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.snapshots_client.delete_snapshot, snapshot['id'])
for snapshot in cls.snapshots:
- try:
- cls.snapshots_client.wait_for_resource_deletion(snapshot['id'])
- except Exception:
- pass
+ test_utils.call_and_ignore_notfound_exc(
+ cls.snapshots_client.wait_for_resource_deletion,
+ snapshot['id'])
def create_server(self, **kwargs):
name = kwargs.pop(
@@ -220,6 +249,7 @@
cls.admin_encryption_types_client = \
cls.os_adm.encryption_types_client
cls.admin_quotas_client = cls.os_adm.volume_quotas_client
+ cls.admin_volume_limits_client = cls.os_adm.volume_limits_client
elif cls._api_version == 2:
cls.admin_volume_qos_client = cls.os_adm.volume_qos_v2_client
cls.admin_volume_services_client = \
@@ -232,6 +262,11 @@
cls.admin_encryption_types_client = \
cls.os_adm.encryption_types_v2_client
cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
+ cls.admin_volume_limits_client = cls.os_adm.volume_v2_limits_client
+ cls.admin_capabilities_client = \
+ cls.os_adm.volume_capabilities_v2_client
+ cls.admin_scheduler_stats_client = \
+ cls.os_adm.volume_scheduler_stats_v2_client
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index cce9ace..f044124 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -35,7 +35,7 @@
if len(CONF.volume_feature_enabled.api_extensions) == 0:
raise self.skipException('There are not any extensions configured')
extension_list = [extension.get('alias') for extension in extensions]
- LOG.debug("Cinder extensions: %s" % ','.join(extension_list))
+ LOG.debug("Cinder extensions: %s", ','.join(extension_list))
ext = CONF.volume_feature_enabled.api_extensions[0]
if ext == 'all':
self.assertIn('Hosts', map(lambda x: x['name'], extensions))
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index 688baf5..d0fa07e 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -30,70 +30,53 @@
raise cls.skipException("Cinder snapshot feature disabled")
@classmethod
- def setup_clients(cls):
- super(SnapshotV2MetadataTestJSON, cls).setup_clients()
- cls.client = cls.snapshots_client
-
- @classmethod
def resource_setup(cls):
super(SnapshotV2MetadataTestJSON, cls).resource_setup()
# Create a volume
cls.volume = cls.create_volume()
# Create a snapshot
cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id'])
- cls.snapshot_id = cls.snapshot['id']
def tearDown(self):
# Update the metadata to {}
- self.client.update_snapshot_metadata(self.snapshot_id, metadata={})
+ self.snapshots_client.update_snapshot_metadata(
+ self.snapshot['id'], metadata={})
super(SnapshotV2MetadataTestJSON, self).tearDown()
@test.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c')
- def test_create_get_delete_snapshot_metadata(self):
+ def test_crud_snapshot_metadata(self):
# Create metadata for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
"key3": "value3"}
- expected = {"key2": "value2",
- "key3": "value3"}
- body = self.client.create_snapshot_metadata(
- self.snapshot_id, metadata)['metadata']
- # Get the metadata of the snapshot
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
-
- # Delete one item metadata of the snapshot
- self.client.delete_snapshot_metadata_item(
- self.snapshot_id, "key1")
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(expected.items()))
- self.assertNotIn("key1", body)
-
- @test.idempotent_id('bd2363bc-de92-48a4-bc98-28943c6e4be1')
- def test_update_snapshot_metadata(self):
- # Update metadata for the snapshot
- metadata = {"key1": "value1",
- "key2": "value2",
- "key3": "value3"}
update = {"key3": "value3_update",
"key4": "value4"}
- # Create metadata for the snapshot
- body = self.client.create_snapshot_metadata(
- self.snapshot_id, metadata)['metadata']
- # Get the metadata of the snapshot
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
+ expect = {"key4": "value4"}
+ # Create metadata
+ body = self.snapshots_client.create_snapshot_metadata(
+ self.snapshot['id'], metadata)['metadata']
- # Update metadata item
- body = self.client.update_snapshot_metadata(
- self.snapshot_id, metadata=update)['metadata']
# Get the metadata of the snapshot
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
- self.assertEqual(update, body)
+ body = self.snapshots_client.show_snapshot_metadata(
+ self.snapshot['id'])['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()),
+ 'Create snapshot metadata failed')
+
+ # Update metadata
+ body = self.snapshots_client.update_snapshot_metadata(
+ self.snapshot['id'], metadata=update)['metadata']
+ body = self.snapshots_client.show_snapshot_metadata(
+ self.snapshot['id'])['metadata']
+ self.assertEqual(update, body, 'Update snapshot metadata failed')
+
+ # Delete one item metadata of the snapshot
+ self.snapshots_client.delete_snapshot_metadata_item(
+ self.snapshot['id'], "key3")
+ body = self.snapshots_client.show_snapshot_metadata(
+ self.snapshot['id'])['metadata']
+ self.assertThat(body.items(), matchers.ContainsAll(expect.items()),
+ 'Delete one item metadata of the snapshot failed')
+ self.assertNotIn("key3", body)
@test.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
def test_update_snapshot_metadata_item(self):
@@ -106,18 +89,18 @@
"key2": "value2",
"key3": "value3_update"}
# Create metadata for the snapshot
- body = self.client.create_snapshot_metadata(
- self.snapshot_id, metadata)['metadata']
+ body = self.snapshots_client.create_snapshot_metadata(
+ self.snapshot['id'], metadata)['metadata']
# Get the metadata of the snapshot
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
+ body = self.snapshots_client.show_snapshot_metadata(
+ self.snapshot['id'])['metadata']
self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
# Update metadata item
- body = self.client.update_snapshot_metadata_item(
- self.snapshot_id, "key3", meta=update_item)['meta']
+ body = self.snapshots_client.update_snapshot_metadata_item(
+ self.snapshot['id'], "key3", meta=update_item)['meta']
# Get the metadata of the snapshot
- body = self.client.show_snapshot_metadata(
- self.snapshot_id)['metadata']
+ body = self.snapshots_client.show_snapshot_metadata(
+ self.snapshot['id'])['metadata']
self.assertThat(body.items(), matchers.ContainsAll(expect.items()))
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
new file mode 100644
index 0000000..35e0d56
--- /dev/null
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -0,0 +1,52 @@
+# 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 AbsoluteLimitsV2Tests(base.BaseVolumeTest):
+
+ # avoid existing volumes of pre-defined tenant
+ force_tenant_isolation = True
+
+ @classmethod
+ def resource_setup(cls):
+ super(AbsoluteLimitsV2Tests, cls).resource_setup()
+ # Create a shared volume for tests
+ cls.volume = cls.create_volume()
+
+ @test.idempotent_id('8e943f53-e9d6-4272-b2e9-adcf2f7c29ad')
+ def test_get_volume_absolute_limits(self):
+ # get volume limit for a tenant
+ absolute_limits = \
+ self.volume_limits_client.show_limits(
+ )['limits']['absolute']
+
+ # verify volume limits and defaults per tenants
+ self.assertEqual(absolute_limits['totalGigabytesUsed'],
+ CONF.volume.volume_size)
+ self.assertEqual(absolute_limits['totalVolumesUsed'], 1)
+ self.assertEqual(absolute_limits['totalSnapshotsUsed'], 0)
+ self.assertEqual(absolute_limits['totalBackupsUsed'], 0)
+ self.assertEqual(absolute_limits['totalBackupGigabytesUsed'], 0)
+
+
+class AbsoluteLimitsV1Tests(AbsoluteLimitsV2Tests):
+ _api_version = 1
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
index ee1744d..c125bb8 100644
--- a/tempest/api/volume/test_volume_metadata.py
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -33,52 +33,39 @@
super(VolumesV2MetadataTest, self).tearDown()
@test.idempotent_id('6f5b125b-f664-44bf-910f-751591fe5769')
- def test_create_get_delete_volume_metadata(self):
+ def test_crud_volume_metadata(self):
# Create metadata for the volume
metadata = {"key1": "value1",
"key2": "value2",
"key3": "value3",
"key4": "<value&special_chars>"}
+ update = {"key4": "value4",
+ "key1": "value1_update"}
+ expected = {"key4": "value4"}
body = self.volumes_client.create_volume_metadata(self.volume['id'],
metadata)['metadata']
# Get the metadata of the volume
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
+ self.assertThat(body.items(), matchers.ContainsAll(metadata.items()),
+ 'Create metadata for the volume failed')
+
+ # Update metadata
+ body = self.volumes_client.update_volume_metadata(
+ self.volume['id'], update)['metadata']
+ body = self.volumes_client.show_volume_metadata(
+ self.volume['id'])['metadata']
+ self.assertEqual(update, body, 'Update metadata failed')
+
# Delete one item metadata of the volume
self.volumes_client.delete_volume_metadata_item(
self.volume['id'], "key1")
body = self.volumes_client.show_volume_metadata(
self.volume['id'])['metadata']
self.assertNotIn("key1", body)
- del metadata["key1"]
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
-
- @test.idempotent_id('774d2918-9beb-4f30-b3d1-2a4e8179ec0a')
- def test_update_volume_metadata(self):
- # Update metadata for the volume
- metadata = {"key1": "value1",
- "key2": "value2",
- "key3": "value3"}
-
- update = {"key4": "value4",
- "key1": "value1_update"}
-
- # Create metadata for the volume
- body = self.volumes_client.create_volume_metadata(
- self.volume['id'], metadata)['metadata']
- # Get the metadata of the volume
- body = self.volumes_client.show_volume_metadata(
- self.volume['id'])['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
- # Update metadata
- body = self.volumes_client.update_volume_metadata(
- self.volume['id'], update)['metadata']
- # Get the metadata of the volume
- body = self.volumes_client.show_volume_metadata(
- self.volume['id'])['metadata']
- self.assertEqual(update, body)
+ self.assertThat(body.items(), matchers.ContainsAll(expected.items()),
+ 'Delete one item metadata of the volume failed')
@test.idempotent_id('862261c5-8df4-475a-8c21-946e50e36a20')
def test_update_volume_metadata_item(self):
@@ -93,7 +80,8 @@
# Create metadata for the volume
body = self.volumes_client.create_volume_metadata(
self.volume['id'], metadata)['metadata']
- self.assertThat(body.items(), matchers.ContainsAll(metadata.items()))
+ self.assertThat(body.items(),
+ matchers.ContainsAll(metadata.items()))
# Update metadata item
body = self.volumes_client.update_volume_metadata_item(
self.volume['id'], "key3", update_item)['meta']
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 737ce5e..d8d6b9a 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -12,7 +12,6 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
from tempest.api.volume import base
from tempest.common.utils import data_utils
@@ -67,8 +66,6 @@
self.volume['id'], 'available')
@test.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
- @testtools.skipUnless(CONF.volume_feature_enabled.bootable,
- 'Update bootable status of a volume is not enabled.')
def test_volume_bootable(self):
# Verify that a volume bootable flag is retrieved
for bool_bootable in [True, False]:
@@ -77,8 +74,11 @@
fetched_volume = self.client.show_volume(
self.volume['id'])['volume']
# Get Volume information
- bool_flag = self._is_true(fetched_volume['bootable'])
- self.assertEqual(bool_bootable, bool_flag)
+ # NOTE(masayukig): 'bootable' is "true" or "false" in the current
+ # cinder implementation. So we need to cast boolean values to str
+ # and make it lower to compare here.
+ self.assertEqual(str(bool_bootable).lower(),
+ fetched_volume['bootable'])
@test.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371')
@test.services('compute')
@@ -139,9 +139,6 @@
body = self.client.show_volume(self.volume['id'])['volume']
self.assertIn('available', body['status'])
- def _is_true(self, val):
- return val in ['true', 'True', True]
-
@test.idempotent_id('fff74e1e-5bd3-4b33-9ea9-24c103bc3f59')
def test_volume_readonly_update(self):
for readonly in [True, False]:
@@ -151,8 +148,11 @@
# Get Volume information
fetched_volume = self.client.show_volume(
self.volume['id'])['volume']
- bool_flag = self._is_true(fetched_volume['metadata']['readonly'])
- self.assertEqual(readonly, bool_flag)
+ # NOTE(masayukig): 'readonly' is "True" or "False" in the current
+ # cinder implementation. So we need to cast boolean values to str
+ # to compare here.
+ self.assertEqual(str(readonly),
+ fetched_volume['metadata']['readonly'])
class VolumesV1ActionsTest(VolumesV2ActionsTest):
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 141336f..70b3c58 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -30,6 +30,22 @@
if not CONF.volume_feature_enabled.backup:
raise cls.skipException("Cinder backup feature disabled")
+ def restore_backup(self, backup_id):
+ # Restore a backup
+ restored_volume = self.backups_client.restore_backup(
+ backup_id)['restore']
+
+ # Delete backup
+ self.addCleanup(self.volumes_client.delete_volume,
+ restored_volume['volume_id'])
+ self.assertEqual(backup_id, restored_volume['backup_id'])
+ waiters.wait_for_backup_status(self.backups_client,
+ backup_id, 'available')
+ waiters.wait_for_volume_status(self.volumes_client,
+ restored_volume['volume_id'],
+ 'available')
+ return restored_volume
+
@test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
def test_volume_backup_create_get_detailed_list_restore_delete(self):
# Create backup
@@ -38,20 +54,18 @@
volume['id'])
backup_name = data_utils.rand_name(
self.__class__.__name__ + '-Backup')
- create_backup = self.backups_client.create_backup
- backup = create_backup(volume_id=volume['id'],
- name=backup_name)['backup']
- self.addCleanup(self.backups_client.delete_backup,
- backup['id'])
+ description = data_utils.rand_name("volume-backup-description")
+ backup = self.create_backup(volume_id=volume['id'],
+ name=backup_name,
+ description=description)
self.assertEqual(backup_name, backup['name'])
waiters.wait_for_volume_status(self.volumes_client,
volume['id'], 'available')
- waiters.wait_for_backup_status(self.backups_client,
- backup['id'], 'available')
# Get a given backup
backup = self.backups_client.show_backup(backup['id'])['backup']
self.assertEqual(backup_name, backup['name'])
+ self.assertEqual(description, backup['description'])
# Get all backups with detail
backups = self.backups_client.list_backups(
@@ -59,18 +73,7 @@
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
- # Restore backup
- restore = self.backups_client.restore_backup(
- backup['id'])['restore']
-
- # Delete backup
- self.addCleanup(self.volumes_client.delete_volume,
- restore['volume_id'])
- self.assertEqual(backup['id'], restore['backup_id'])
- waiters.wait_for_backup_status(self.backups_client,
- backup['id'], 'available')
- waiters.wait_for_volume_status(self.volumes_client,
- restore['volume_id'], 'available')
+ self.restore_backup(backup['id'])
@test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
@test.services('compute')
@@ -97,14 +100,32 @@
# Create backup using force flag
backup_name = data_utils.rand_name(
self.__class__.__name__ + '-Backup')
- backup = self.backups_client.create_backup(
- volume_id=volume['id'],
- name=backup_name, force=True)['backup']
- self.addCleanup(self.backups_client.delete_backup, backup['id'])
- waiters.wait_for_backup_status(self.backups_client,
- backup['id'], 'available')
+ backup = self.create_backup(volume_id=volume['id'],
+ name=backup_name, force=True)
self.assertEqual(backup_name, backup['name'])
+ @test.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
+ def test_bootable_volume_backup_and_restore(self):
+ # Create volume from image
+ img_uuid = CONF.compute.image_ref
+ volume = self.create_volume(imageRef=img_uuid)
+
+ volume_details = self.volumes_client.show_volume(
+ volume['id'])['volume']
+ self.assertEqual('true', volume_details['bootable'])
+
+ # Create a backup
+ backup = self.create_backup(volume_id=volume['id'])
+
+ # Restore the backup
+ restored_volume_id = self.restore_backup(backup['id'])['volume_id']
+
+ # Verify the restored backup volume is bootable
+ restored_volume_info = self.volumes_client.show_volume(
+ restored_volume_id)['volume']
+
+ self.assertEqual('true', restored_volume_info['bootable'])
+
class VolumesBackupsV1Test(VolumesBackupsV2Test):
_api_version = 1
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
index 7529dc2..2cedb4e 100644
--- a/tempest/api/volume/test_volumes_clone.py
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -21,11 +21,11 @@
CONF = config.CONF
-class VolumesCloneTest(base.BaseVolumeTest):
+class VolumesV2CloneTest(base.BaseVolumeTest):
@classmethod
def skip_checks(cls):
- super(VolumesCloneTest, cls).skip_checks()
+ super(VolumesV2CloneTest, cls).skip_checks()
if not CONF.volume_feature_enabled.clone:
raise cls.skipException("Cinder volume clones are disabled")
@@ -45,6 +45,22 @@
self.assertEqual(volume['source_volid'], src_vol['id'])
self.assertEqual(int(volume['size']), src_size + 1)
+ @test.idempotent_id('cbbcd7c6-5a6c-481a-97ac-ca55ab715d16')
+ def test_create_from_bootable_volume(self):
+ # Create volume from image
+ img_uuid = CONF.compute.image_ref
+ src_vol = self.create_volume(imageRef=img_uuid)
-class VolumesV1CloneTest(VolumesCloneTest):
+ # Create a volume from the bootable volume
+ cloned_vol = self.create_volume(source_volid=src_vol['id'])
+ cloned_vol_details = self.volumes_client.show_volume(
+ cloned_vol['id'])['volume']
+
+ # Verify cloned volume creation as expected
+ self.assertEqual('true', cloned_vol_details['bootable'])
+ self.assertEqual(src_vol['id'], cloned_vol_details['source_volid'])
+ self.assertEqual(src_vol['size'], cloned_vol_details['size'])
+
+
+class VolumesV1CloneTest(VolumesV2CloneTest):
_api_version = 1
diff --git a/tempest/api/volume/test_volumes_clone_negative.py b/tempest/api/volume/test_volumes_clone_negative.py
index d1bedb4..5c54e1e 100644
--- a/tempest/api/volume/test_volumes_clone_negative.py
+++ b/tempest/api/volume/test_volumes_clone_negative.py
@@ -22,11 +22,11 @@
CONF = config.CONF
-class VolumesCloneTest(base.BaseVolumeTest):
+class VolumesV2CloneNegativeTest(base.BaseVolumeTest):
@classmethod
def skip_checks(cls):
- super(VolumesCloneTest, cls).skip_checks()
+ super(VolumesV2CloneNegativeTest, cls).skip_checks()
if not CONF.volume_feature_enabled.clone:
raise cls.skipException("Cinder volume clones are disabled")
@@ -44,5 +44,5 @@
source_volid=src_vol['id'])
-class VolumesV1CloneTest(VolumesCloneTest):
+class VolumesV1CloneNegativeTest(VolumesV2CloneNegativeTest):
_api_version = 1
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index 7aea1c4..c3d6dbb 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -20,20 +20,16 @@
class VolumesV2ExtendTest(base.BaseVolumeTest):
- @classmethod
- def setup_clients(cls):
- super(VolumesV2ExtendTest, cls).setup_clients()
- cls.client = cls.volumes_client
-
@test.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e')
def test_volume_extend(self):
# Extend Volume Test.
self.volume = self.create_volume()
extend_size = int(self.volume['size']) + 1
- self.client.extend_volume(self.volume['id'], new_size=extend_size)
- waiters.wait_for_volume_status(self.client,
+ self.volumes_client.extend_volume(self.volume['id'],
+ new_size=extend_size)
+ waiters.wait_for_volume_status(self.volumes_client,
self.volume['id'], 'available')
- volume = self.client.show_volume(self.volume['id'])['volume']
+ volume = self.volumes_client.show_volume(self.volume['id'])['volume']
self.assertEqual(int(volume['size']), extend_size)
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 51de2be..65e461c 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -27,39 +27,31 @@
class VolumesV2GetTest(base.BaseVolumeTest):
- @classmethod
- def setup_clients(cls):
- super(VolumesV2GetTest, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @classmethod
- def resource_setup(cls):
- super(VolumesV2GetTest, cls).resource_setup()
-
- cls.name_field = cls.special_fields['name_field']
- cls.descrip_field = cls.special_fields['descrip_field']
-
def _volume_create_get_update_delete(self, **kwargs):
+ name_field = self.special_fields['name_field']
+ descrip_field = self.special_fields['descrip_field']
+
# Create a volume, Get it's details and Delete the volume
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'Test'}
# Create a volume
- kwargs[self.name_field] = v_name
+ kwargs[name_field] = v_name
kwargs['metadata'] = metadata
- volume = self.client.create_volume(**kwargs)['volume']
+ volume = self.volumes_client.create_volume(**kwargs)['volume']
self.assertIn('id', volume)
- self.addCleanup(self.delete_volume, self.client, volume['id'])
- waiters.wait_for_volume_status(self.client, volume['id'], 'available')
- self.assertIn(self.name_field, volume)
- self.assertEqual(volume[self.name_field], v_name,
+ self.addCleanup(self.delete_volume, self.volumes_client, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+ self.assertIn(name_field, volume)
+ self.assertEqual(volume[name_field], v_name,
"The created volume name is not equal "
"to the requested name")
- self.assertIsNotNone(volume['id'],
- "Field volume id is empty or not found.")
+
# Get Volume information
- fetched_volume = self.client.show_volume(volume['id'])['volume']
+ fetched_volume = self.volumes_client.show_volume(
+ volume['id'])['volume']
self.assertEqual(v_name,
- fetched_volume[self.name_field],
+ fetched_volume[name_field],
'The fetched Volume name is different '
'from the created Volume')
self.assertEqual(volume['id'],
@@ -73,53 +65,56 @@
if 'imageRef' in kwargs:
self.assertEqual('true', fetched_volume['bootable'])
- if 'imageRef' not in kwargs:
+ else:
self.assertEqual('false', fetched_volume['bootable'])
# Update Volume
# Test volume update when display_name is same with original value
- params = {self.name_field: v_name}
- self.client.update_volume(volume['id'], **params)
+ params = {name_field: v_name}
+ self.volumes_client.update_volume(volume['id'], **params)
# Test volume update when display_name is new
new_v_name = data_utils.rand_name(
self.__class__.__name__ + '-new-Volume')
new_desc = 'This is the new description of volume'
- params = {self.name_field: new_v_name,
- self.descrip_field: new_desc}
- update_volume = self.client.update_volume(
+ params = {name_field: new_v_name,
+ descrip_field: new_desc}
+ update_volume = self.volumes_client.update_volume(
volume['id'], **params)['volume']
# Assert response body for update_volume method
- self.assertEqual(new_v_name, update_volume[self.name_field])
- self.assertEqual(new_desc, update_volume[self.descrip_field])
+ self.assertEqual(new_v_name, update_volume[name_field])
+ self.assertEqual(new_desc, update_volume[descrip_field])
# Assert response body for show_volume method
- updated_volume = self.client.show_volume(volume['id'])['volume']
+ updated_volume = self.volumes_client.show_volume(
+ volume['id'])['volume']
self.assertEqual(volume['id'], updated_volume['id'])
- self.assertEqual(new_v_name, updated_volume[self.name_field])
- self.assertEqual(new_desc, updated_volume[self.descrip_field])
+ self.assertEqual(new_v_name, updated_volume[name_field])
+ self.assertEqual(new_desc, updated_volume[descrip_field])
self.assertThat(updated_volume['metadata'].items(),
matchers.ContainsAll(metadata.items()),
'The fetched Volume metadata misses data '
'from the created Volume')
+
# Test volume create when display_name is none and display_description
# contains specific characters,
# then test volume update if display_name is duplicated
new_v_desc = data_utils.rand_name('@#$%^* description')
- params = {self.descrip_field: new_v_desc,
+ params = {descrip_field: new_v_desc,
'availability_zone': volume['availability_zone'],
'size': CONF.volume.volume_size}
- new_volume = self.client.create_volume(**params)['volume']
+ new_volume = self.volumes_client.create_volume(**params)['volume']
self.assertIn('id', new_volume)
- self.addCleanup(self.delete_volume, self.client, new_volume['id'])
- waiters.wait_for_volume_status(self.client,
+ self.addCleanup(self.delete_volume, self.volumes_client,
+ new_volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client,
new_volume['id'], 'available')
- params = {self.name_field: volume[self.name_field],
- self.descrip_field: volume[self.descrip_field]}
- self.client.update_volume(new_volume['id'], **params)
+ params = {name_field: volume[name_field],
+ descrip_field: volume[descrip_field]}
+ self.volumes_client.update_volume(new_volume['id'], **params)
if 'imageRef' in kwargs:
self.assertEqual('true', updated_volume['bootable'])
- if 'imageRef' not in kwargs:
+ else:
self.assertEqual('false', updated_volume['bootable'])
@test.attr(type='smoke')
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 40793ec..030ea6c 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -55,11 +55,6 @@
[str_vol(v) for v in fetched_list]))
@classmethod
- def setup_clients(cls):
- super(VolumesV2ListTestJSON, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @classmethod
def resource_setup(cls):
super(VolumesV2ListTestJSON, cls).resource_setup()
cls.name = cls.VOLUME_FIELDS[1]
@@ -68,16 +63,17 @@
cls.metadata = {'Type': 'work'}
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
- volume = cls.client.show_volume(volume['id'])['volume']
+ volume = cls.volumes_client.show_volume(volume['id'])['volume']
cls.volume_list.append(volume)
def _list_by_param_value_and_assert(self, params, with_detail=False):
"""list or list_details with given params and validates result"""
if with_detail:
fetched_vol_list = \
- self.client.list_volumes(detail=True, params=params)['volumes']
+ self.volumes_client.list_volumes(detail=True,
+ params=params)['volumes']
else:
- fetched_vol_list = self.client.list_volumes(
+ fetched_vol_list = self.volumes_client.list_volumes(
params=params)['volumes']
# Validating params of fetched volumes
@@ -103,7 +99,7 @@
def test_volume_list(self):
# Get a list of Volumes
# Fetch all volumes
- fetched_list = self.client.list_volumes()['volumes']
+ fetched_list = self.volumes_client.list_volumes()['volumes']
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -111,14 +107,15 @@
def test_volume_list_with_details(self):
# Get a list of Volumes with details
# Fetch all Volumes
- fetched_list = self.client.list_volumes(detail=True)['volumes']
+ fetched_list = self.volumes_client.list_volumes(detail=True)['volumes']
self.assertVolumesIn(fetched_list, self.volume_list)
@test.idempotent_id('a28e8da4-0b56-472f-87a8-0f4d3f819c02')
def test_volume_list_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {self.name: volume[self.name]}
- fetched_vol = self.client.list_volumes(params=params)['volumes']
+ fetched_vol = self.volumes_client.list_volumes(
+ params=params)['volumes']
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
self.assertEqual(fetched_vol[0][self.name],
volume[self.name])
@@ -127,7 +124,7 @@
def test_volume_list_details_by_name(self):
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
params = {self.name: volume[self.name]}
- fetched_vol = self.client.list_volumes(
+ fetched_vol = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
self.assertEqual(1, len(fetched_vol), str(fetched_vol))
self.assertEqual(fetched_vol[0][self.name],
@@ -136,7 +133,8 @@
@test.idempotent_id('39654e13-734c-4dab-95ce-7613bf8407ce')
def test_volumes_list_by_status(self):
params = {'status': 'available'}
- fetched_list = self.client.list_volumes(params=params)['volumes']
+ fetched_list = self.volumes_client.list_volumes(
+ params=params)['volumes']
self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -144,7 +142,7 @@
@test.idempotent_id('2943f712-71ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_status(self):
params = {'status': 'available'}
- fetched_list = self.client.list_volumes(
+ fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
for volume in fetched_list:
self.assertEqual('available', volume['status'])
@@ -158,7 +156,8 @@
in volume_list are not a bootable volume.
"""
params = {'bootable': 'false'}
- fetched_list = self.client.list_volumes(params=params)['volumes']
+ fetched_list = self.volumes_client.list_volumes(
+ params=params)['volumes']
self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -166,7 +165,7 @@
@test.idempotent_id('2016a939-72ec-482a-bf49-d5ca06216b9f')
def test_volumes_list_details_by_bootable(self):
params = {'bootable': 'false'}
- fetched_list = self.client.list_volumes(
+ fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
for volume in fetched_list:
self.assertEqual('false', volume['bootable'])
@@ -177,7 +176,8 @@
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
- fetched_list = self.client.list_volumes(params=params)['volumes']
+ fetched_list = self.volumes_client.list_volumes(
+ params=params)['volumes']
self._list_by_param_value_and_assert(params)
self.assertVolumesIn(fetched_list, self.volume_list,
fields=self.VOLUME_FIELDS)
@@ -187,7 +187,7 @@
volume = self.volume_list[data_utils.rand_int_id(0, 2)]
zone = volume['availability_zone']
params = {'availability_zone': zone}
- fetched_list = self.client.list_volumes(
+ fetched_list = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
for volume in fetched_list:
self.assertEqual(zone, volume['availability_zone'])
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index fda0dda..c45ace6 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -22,11 +22,6 @@
class VolumesV2NegativeTest(base.BaseVolumeTest):
@classmethod
- def setup_clients(cls):
- super(VolumesV2NegativeTest, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @classmethod
def resource_setup(cls):
super(VolumesV2NegativeTest, cls).resource_setup()
@@ -40,14 +35,14 @@
@test.idempotent_id('f131c586-9448-44a4-a8b0-54ca838aa43e')
def test_volume_get_nonexistent_volume_id(self):
# Should not be able to get a non-existent volume
- self.assertRaises(lib_exc.NotFound, self.client.show_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29')
def test_volume_delete_nonexistent_volume_id(self):
# Should not be able to delete a non-existent Volume
- self.assertRaises(lib_exc.NotFound, self.client.delete_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_uuid())
@test.attr(type=['negative'])
@@ -57,17 +52,19 @@
# in request
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@test.idempotent_id('9387686f-334f-4d31-a439-33494b9e2683')
- def test_create_volume_with_out_passing_size(self):
+ def test_create_volume_without_passing_size(self):
# Should not be able to create volume without passing size
# in request
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
size='', display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -76,7 +73,8 @@
# Should not be able to create volume with size zero
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
size='0', display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -85,7 +83,8 @@
# Should not be able to create volume with size negative
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.BadRequest, self.client.create_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.create_volume,
size='-1', display_name=v_name, metadata=metadata)
@test.attr(type=['negative'])
@@ -94,7 +93,7 @@
# Should not be able to create volume with non-existent volume type
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.create_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size='1', volume_type=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@@ -104,7 +103,7 @@
# Should not be able to create volume with non-existent snapshot
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.create_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size='1', snapshot_id=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@@ -114,7 +113,7 @@
# Should not be able to create volume with non-existent source volume
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.create_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size='1', source_volid=data_utils.rand_uuid(),
display_name=v_name, metadata=metadata)
@@ -123,7 +122,7 @@
def test_update_volume_with_nonexistent_volume_id(self):
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.update_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_uuid(),
display_name=v_name,
metadata=metadata)
@@ -133,7 +132,7 @@
def test_update_volume_with_invalid_volume_id(self):
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.update_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id='#$%%&^&^', display_name=v_name,
metadata=metadata)
@@ -142,7 +141,7 @@
def test_update_volume_with_empty_volume_id(self):
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
metadata = {'Type': 'work'}
- self.assertRaises(lib_exc.NotFound, self.client.update_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id='', display_name=v_name,
metadata=metadata)
@@ -150,27 +149,29 @@
@test.idempotent_id('30799cfd-7ee4-446c-b66c-45b383ed211b')
def test_get_invalid_volume_id(self):
# Should not be able to get volume with invalid id
- self.assertRaises(lib_exc.NotFound, self.client.show_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
'#$%%&^&^')
@test.attr(type=['negative'])
@test.idempotent_id('c6c3db06-29ad-4e91-beb0-2ab195fe49e3')
def test_get_volume_without_passing_volume_id(self):
# Should not be able to get volume when empty ID is passed
- self.assertRaises(lib_exc.NotFound, self.client.show_volume, '')
+ self.assertRaises(lib_exc.NotFound,
+ self.volumes_client.show_volume, '')
@test.attr(type=['negative'])
@test.idempotent_id('1f035827-7c32-4019-9240-b4ec2dbd9dfd')
def test_delete_invalid_volume_id(self):
# Should not be able to delete volume when invalid ID is passed
- self.assertRaises(lib_exc.NotFound, self.client.delete_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
'!@#$%^&*()')
@test.attr(type=['negative'])
@test.idempotent_id('441a1550-5d44-4b30-af0f-a6d402f52026')
def test_delete_volume_without_passing_volume_id(self):
# Should not be able to delete volume when empty ID is passed
- self.assertRaises(lib_exc.NotFound, self.client.delete_volume, '')
+ self.assertRaises(lib_exc.NotFound,
+ self.volumes_client.delete_volume, '')
@test.attr(type=['negative'])
@test.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@@ -179,7 +180,7 @@
server = self.create_server(wait_until='ACTIVE')
self.assertRaises(lib_exc.NotFound,
- self.client.attach_volume,
+ self.volumes_client.attach_volume,
data_utils.rand_uuid(),
instance_uuid=server['id'],
mountpoint=self.mountpoint)
@@ -188,7 +189,7 @@
@test.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a')
def test_detach_volumes_with_invalid_volume_id(self):
self.assertRaises(lib_exc.NotFound,
- self.client.detach_volume,
+ self.volumes_client.detach_volume,
'xxx')
@test.attr(type=['negative'])
@@ -196,7 +197,8 @@
def test_volume_extend_with_size_smaller_than_original_size(self):
# Extend volume with smaller size than original size.
extend_size = 0
- self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.extend_volume,
self.volume['id'], new_size=extend_size)
@test.attr(type=['negative'])
@@ -204,7 +206,8 @@
def test_volume_extend_with_non_number_size(self):
# Extend volume when size is non number.
extend_size = 'abc'
- self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.extend_volume,
self.volume['id'], new_size=extend_size)
@test.attr(type=['negative'])
@@ -212,7 +215,8 @@
def test_volume_extend_with_None_size(self):
# Extend volume with None size.
extend_size = None
- self.assertRaises(lib_exc.BadRequest, self.client.extend_volume,
+ self.assertRaises(lib_exc.BadRequest,
+ self.volumes_client.extend_volume,
self.volume['id'], new_size=extend_size)
@test.attr(type=['negative'])
@@ -220,7 +224,7 @@
def test_volume_extend_with_nonexistent_volume_id(self):
# Extend volume size when volume is nonexistent.
extend_size = int(self.volume['size']) + 1
- self.assertRaises(lib_exc.NotFound, self.client.extend_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
data_utils.rand_uuid(), new_size=extend_size)
@test.attr(type=['negative'])
@@ -228,41 +232,42 @@
def test_volume_extend_without_passing_volume_id(self):
# Extend volume size when passing volume id is None.
extend_size = int(self.volume['size']) + 1
- self.assertRaises(lib_exc.NotFound, self.client.extend_volume,
+ self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
None, new_size=extend_size)
@test.attr(type=['negative'])
@test.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2')
def test_reserve_volume_with_nonexistent_volume_id(self):
self.assertRaises(lib_exc.NotFound,
- self.client.reserve_volume,
+ self.volumes_client.reserve_volume,
data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c')
def test_unreserve_volume_with_nonexistent_volume_id(self):
self.assertRaises(lib_exc.NotFound,
- self.client.unreserve_volume,
+ self.volumes_client.unreserve_volume,
data_utils.rand_uuid())
@test.attr(type=['negative'])
@test.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
def test_reserve_volume_with_negative_volume_status(self):
# Mark volume as reserved.
- self.client.reserve_volume(self.volume['id'])
+ self.volumes_client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
self.assertRaises(lib_exc.BadRequest,
- self.client.reserve_volume,
+ self.volumes_client.reserve_volume,
self.volume['id'])
# Unmark volume as reserved.
- self.client.unreserve_volume(self.volume['id'])
+ self.volumes_client.unreserve_volume(self.volume['id'])
@test.attr(type=['negative'])
@test.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f')
def test_list_volumes_with_nonexistent_name(self):
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {self.name_field: v_name}
- fetched_volume = self.client.list_volumes(params=params)['volumes']
+ fetched_volume = self.volumes_client.list_volumes(
+ params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative'])
@@ -271,14 +276,16 @@
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {self.name_field: v_name}
fetched_volume = \
- self.client.list_volumes(detail=True, params=params)['volumes']
+ self.volumes_client.list_volumes(
+ detail=True, params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative'])
@test.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c')
def test_list_volumes_with_invalid_status(self):
params = {'status': 'null'}
- fetched_volume = self.client.list_volumes(params=params)['volumes']
+ fetched_volume = self.volumes_client.list_volumes(
+ params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
@test.attr(type=['negative'])
@@ -286,7 +293,8 @@
def test_list_volumes_detail_with_invalid_status(self):
params = {'status': 'null'}
fetched_volume = \
- self.client.list_volumes(detail=True, params=params)['volumes']
+ self.volumes_client.list_volumes(detail=True,
+ params=params)['volumes']
self.assertEqual(0, len(fetched_volume))
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 3c05d3e..6f85891 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -12,7 +12,6 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils
-from tempest.common import waiters
from tempest import config
from tempest import test
@@ -46,21 +45,53 @@
# Create a snapshot when volume status is in-use
# Create a test instance
server = self.create_server(wait_until='ACTIVE')
- self.servers_client.attach_volume(
- server['id'], volumeId=self.volume_origin['id'],
- device='/dev/%s' % CONF.compute.volume_device_name)
- waiters.wait_for_volume_status(self.volumes_client,
- self.volume_origin['id'], 'in-use')
- self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
- self.volume_origin['id'], 'available')
- self.addCleanup(self.servers_client.detach_volume, server['id'],
- self.volume_origin['id'])
+ self.attach_volume(server['id'], self.volume_origin['id'])
+
# Snapshot a volume even if it's attached to an instance
snapshot = self.create_snapshot(self.volume_origin['id'],
force=True)
# Delete the snapshot
self.cleanup_snapshot(snapshot)
+ @test.idempotent_id('8567b54c-4455-446d-a1cf-651ddeaa3ff2')
+ @test.services('compute')
+ def test_snapshot_delete_with_volume_in_use(self):
+ # Create a test instance
+ server = self.create_server(wait_until='ACTIVE')
+ self.attach_volume(server['id'], self.volume_origin['id'])
+
+ # Snapshot a volume attached to an instance
+ snapshot1 = self.create_snapshot(self.volume_origin['id'], force=True)
+ snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
+ snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True)
+
+ # Delete the snapshots. Some snapshot implementations can take
+ # different paths according to order they are deleted.
+ self.cleanup_snapshot(snapshot1)
+ self.cleanup_snapshot(snapshot3)
+ self.cleanup_snapshot(snapshot2)
+
+ @test.idempotent_id('5210a1de-85a0-11e6-bb21-641c676a5d61')
+ @test.services('compute')
+ def test_snapshot_create_offline_delete_online(self):
+
+ # Create a snapshot while it is not attached
+ snapshot1 = self.create_snapshot(self.volume_origin['id'])
+
+ # Create a server and attach it
+ server = self.create_server(wait_until='ACTIVE')
+ self.attach_volume(server['id'], self.volume_origin['id'])
+
+ # Now that the volume is attached, create another snapshots
+ snapshot2 = self.create_snapshot(self.volume_origin['id'], force=True)
+ snapshot3 = self.create_snapshot(self.volume_origin['id'], force=True)
+
+ # Delete the snapshots. Some snapshot implementations can take
+ # different paths according to order they are deleted.
+ self.cleanup_snapshot(snapshot3)
+ self.cleanup_snapshot(snapshot1)
+ self.cleanup_snapshot(snapshot2)
+
@test.idempotent_id('2a8abbe4-d871-46db-b049-c41f5af8216e')
def test_snapshot_create_get_list_update_delete(self):
# Create a snapshot
@@ -109,6 +140,14 @@
# Destination volume bigger than source snapshot
dst_vol = self.create_volume(snapshot_id=src_snap['id'],
size=src_size + 1)
+ # NOTE(zhufl): dst_vol is created based on snapshot, so dst_vol
+ # should be deleted before deleting snapshot, otherwise deleting
+ # snapshot will end with status 'error-deleting'. This depends on
+ # the implementation mechanism of vendors, generally speaking,
+ # some verdors will use "virtual disk clone" which will promote
+ # disk clone speed, and in this situation the "disk clone"
+ # is just a relationship between volume and snapshot.
+ self.addCleanup(self.delete_volume, self.volumes_client, dst_vol['id'])
volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
# Should allow
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 03996af..fb8c65d 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -33,11 +33,6 @@
"""
@classmethod
- def setup_clients(cls):
- super(VolumesV2ListTestJSON, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @classmethod
def resource_setup(cls):
super(VolumesV2ListTestJSON, cls).resource_setup()
@@ -45,7 +40,7 @@
cls.metadata = {'Type': 'work'}
# NOTE(zhufl): When using pre-provisioned credentials, the project
# may have volumes other than those created below.
- existing_volumes = cls.client.list_volumes()['volumes']
+ existing_volumes = cls.volumes_client.list_volumes()['volumes']
cls.volume_id_list = [vol['id'] for vol in existing_volumes]
for i in range(3):
volume = cls.create_volume(metadata=cls.metadata)
@@ -63,7 +58,7 @@
'sort_dir': sort_dir,
'sort_key': sort_key
}
- fetched_volume = self.client.list_volumes(
+ fetched_volume = self.volumes_client.list_volumes(
detail=True, params=params)['volumes']
self.assertEqual(limit, len(fetched_volume),
"The count of volumes is %s, expected:%s " %
@@ -192,8 +187,8 @@
params = {'marker': random_volume}
# Running volume list using marker parameter
- vol_with_marker = self.client.list_volumes(detail=True,
- params=params)['volumes']
+ vol_with_marker = self.volumes_client.list_volumes(
+ detail=True, params=params)['volumes']
# Fetching the index of the random volume from volume_id_list
index_marker = self.volume_id_list.index(random_volume)
diff --git a/tempest/clients.py b/tempest/clients.py
index d131dc4..e3466e5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -13,20 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
-
from oslo_log import log as logging
-from tempest.common import negative_rest_client
from tempest import config
from tempest.lib import auth
from tempest.lib import exceptions as lib_exc
from tempest.lib.services import clients
-from tempest.services import baremetal
-from tempest.services import identity
+from tempest.lib.services import identity
from tempest.services import object_storage
from tempest.services import orchestration
-from tempest.services import volume
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -37,8 +32,8 @@
default_params = config.service_client_config()
- # TODO(andreaf) This is only used by baremetal clients,
- # and should be removed once they are out of Tempest
+ # TODO(jordanP): remove this once no Tempest plugin use that class
+ # variable.
default_params_with_timeout_values = {
'build_interval': CONF.compute.build_interval,
'build_timeout': CONF.compute.build_timeout
@@ -68,12 +63,6 @@
self._set_image_clients()
self._set_network_clients()
- self.baremetal_client = baremetal.BaremetalClient(
- self.auth_provider,
- CONF.baremetal.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.baremetal.endpoint_type,
- **self.default_params_with_timeout_values)
self.orchestration_client = orchestration.OrchestrationClient(
self.auth_provider,
CONF.orchestration.catalog_type,
@@ -82,8 +71,6 @@
build_interval=CONF.orchestration.build_interval,
build_timeout=CONF.orchestration.build_timeout,
**self.default_params)
- 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
@@ -110,7 +97,7 @@
config.service_client_config(service_for_config))
except lib_exc.UnknownServiceClient:
LOG.warning(
- 'Could not load configuration for service %s' % service)
+ 'Could not load configuration for service %s', service)
return configuration
@@ -131,6 +118,7 @@
self.network.SecurityGroupRulesClient())
self.security_groups_client = self.network.SecurityGroupsClient()
self.network_versions_client = self.network.NetworkVersionsClient()
+ self.service_providers_client = self.network.ServiceProvidersClient()
def _set_image_clients(self):
if CONF.service_available.glance:
@@ -141,6 +129,8 @@
self.namespaces_client = self.image_v2.NamespacesClient()
self.resource_types_client = self.image_v2.ResourceTypesClient()
self.schemas_client = self.image_v2.SchemasClient()
+ self.namespace_properties_client = \
+ self.image_v2.NamespacePropertiesClient()
def _set_compute_clients(self):
self.agents_client = self.compute.AgentsClient()
@@ -178,7 +168,6 @@
self.instance_usages_audit_log_client = (
self.compute.InstanceUsagesAuditLogClient())
self.tenant_networks_client = self.compute.TenantNetworksClient()
- self.baremetal_nodes_client = self.compute.BaremetalNodesClient()
# NOTE: The following client needs special timeout values because
# the API is a proxy for the other component.
@@ -195,67 +184,52 @@
**params_volume)
def _set_identity_clients(self):
- params = self.parameters['identity']
-
# Clients below use the admin endpoint type of Keystone API v2
- 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)
- self.identity_client = identity.v2.IdentityClient(self.auth_provider,
- **params_v2_admin)
- self.tenants_client = identity.v2.TenantsClient(self.auth_provider,
- **params_v2_admin)
- self.roles_client = identity.v2.RolesClient(self.auth_provider,
- **params_v2_admin)
- self.users_client = identity.v2.UsersClient(self.auth_provider,
- **params_v2_admin)
- self.identity_services_client = identity.v2.ServicesClient(
- self.auth_provider, **params_v2_admin)
+ params_v2_admin = {
+ 'endpoint_type': CONF.identity.v2_admin_endpoint_type}
+ self.endpoints_client = self.identity_v2.EndpointsClient(
+ **params_v2_admin)
+ self.identity_client = self.identity_v2.IdentityClient(
+ **params_v2_admin)
+ self.tenants_client = self.identity_v2.TenantsClient(
+ **params_v2_admin)
+ self.roles_client = self.identity_v2.RolesClient(**params_v2_admin)
+ self.users_client = self.identity_v2.UsersClient(**params_v2_admin)
+ self.identity_services_client = self.identity_v2.ServicesClient(
+ **params_v2_admin)
# Clients below use the public endpoint type of Keystone API v2
- params_v2_public = copy.copy(params)
- params_v2_public['endpoint_type'] = (
- CONF.identity.v2_public_endpoint_type)
- self.identity_public_client = identity.v2.IdentityClient(
- self.auth_provider, **params_v2_public)
- self.tenants_public_client = identity.v2.TenantsClient(
- self.auth_provider, **params_v2_public)
- self.users_public_client = identity.v2.UsersClient(
- self.auth_provider, **params_v2_public)
+ params_v2_public = {
+ 'endpoint_type': CONF.identity.v2_public_endpoint_type}
+ self.identity_public_client = self.identity_v2.IdentityClient(
+ **params_v2_public)
+ self.tenants_public_client = self.identity_v2.TenantsClient(
+ **params_v2_public)
+ self.users_public_client = self.identity_v2.UsersClient(
+ **params_v2_public)
# 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)
- self.identity_v3_client = identity.v3.IdentityClient(
- self.auth_provider, **params_v3)
- self.trusts_client = identity.v3.TrustsClient(self.auth_provider,
- **params_v3)
- self.users_v3_client = identity.v3.UsersClient(self.auth_provider,
- **params_v3)
- self.endpoints_v3_client = identity.v3.EndPointsClient(
- self.auth_provider, **params_v3)
- self.roles_v3_client = identity.v3.RolesClient(self.auth_provider,
- **params_v3)
- self.inherited_roles_client = identity.v3.InheritedRolesClient(
- self.auth_provider, **params_v3)
- self.role_assignments_client = identity.v3.RoleAssignmentsClient(
- self.auth_provider, **params_v3)
- self.identity_services_v3_client = identity.v3.ServicesClient(
- self.auth_provider, **params_v3)
- self.policies_client = identity.v3.PoliciesClient(self.auth_provider,
- **params_v3)
- self.projects_client = identity.v3.ProjectsClient(self.auth_provider,
- **params_v3)
- self.regions_client = identity.v3.RegionsClient(self.auth_provider,
- **params_v3)
- self.credentials_client = identity.v3.CredentialsClient(
- self.auth_provider, **params_v3)
- self.groups_client = identity.v3.GroupsClient(self.auth_provider,
- **params_v3)
+ params_v3 = {'endpoint_type': CONF.identity.v3_endpoint_type}
+ self.domains_client = self.identity_v3.DomainsClient(**params_v3)
+ self.identity_v3_client = self.identity_v3.IdentityClient(**params_v3)
+ self.trusts_client = self.identity_v3.TrustsClient(**params_v3)
+ self.users_v3_client = self.identity_v3.UsersClient(**params_v3)
+ self.endpoints_v3_client = self.identity_v3.EndPointsClient(
+ **params_v3)
+ self.roles_v3_client = self.identity_v3.RolesClient(**params_v3)
+ self.inherited_roles_client = self.identity_v3.InheritedRolesClient(
+ **params_v3)
+ self.role_assignments_client = self.identity_v3.RoleAssignmentsClient(
+ **params_v3)
+ self.identity_services_v3_client = self.identity_v3.ServicesClient(
+ **params_v3)
+ self.policies_client = self.identity_v3.PoliciesClient(**params_v3)
+ self.projects_client = self.identity_v3.ProjectsClient(**params_v3)
+ self.regions_client = self.identity_v3.RegionsClient(**params_v3)
+ self.credentials_client = self.identity_v3.CredentialsClient(
+ **params_v3)
+ self.groups_client = self.identity_v3.GroupsClient(**params_v3)
# Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding
@@ -276,8 +250,6 @@
raise lib_exc.InvalidConfiguration(msg)
def _set_volume_clients(self):
- # Mandatory parameters (always defined)
- params = self.parameters['volume']
self.volume_qos_client = self.volume_v1.QosSpecsClient()
self.volume_qos_v2_client = self.volume_v2.QosSpecsClient()
@@ -292,8 +264,7 @@
self.snapshots_v2_client = self.volume_v2.SnapshotsClient()
self.volumes_client = self.volume_v1.VolumesClient()
self.volumes_v2_client = self.volume_v2.VolumesClient()
- self.volume_v3_messages_client = volume.v3.MessagesClient(
- self.auth_provider, **params)
+ self.volume_v3_messages_client = self.volume_v3.MessagesClient()
self.volume_types_client = self.volume_v1.TypesClient()
self.volume_types_v2_client = self.volume_v2.TypesClient()
self.volume_hosts_client = self.volume_v1.HostsClient()
@@ -306,6 +277,12 @@
self.volume_v1.AvailabilityZoneClient()
self.volume_v2_availability_zone_client = \
self.volume_v2.AvailabilityZoneClient()
+ self.volume_limits_client = self.volume_v1.LimitsClient()
+ self.volume_v2_limits_client = self.volume_v2.LimitsClient()
+ self.volume_capabilities_v2_client = \
+ self.volume_v2.CapabilitiesClient()
+ self.volume_scheduler_stats_v2_client = \
+ self.volume_v2.SchedulerStatsClient()
def _set_object_storage_clients(self):
# Mandatory parameters (always defined)
@@ -313,6 +290,8 @@
self.account_client = object_storage.AccountClient(self.auth_provider,
**params)
+ self.capabilities_client = object_storage.CapabilitiesClient(
+ self.auth_provider, **params)
self.container_client = object_storage.ContainerClient(
self.auth_provider, **params)
self.object_client = object_storage.ObjectClient(self.auth_provider,
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 3d38e25..172d9e1 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -205,7 +205,7 @@
os.rename(account_file, '.'.join((account_file, 'bak')))
with open(account_file, 'w') as f:
yaml.safe_dump(accounts, f, default_flow_style=False)
- LOG.info('%s generated successfully!' % account_file)
+ LOG.info('%s generated successfully!', account_file)
def _parser_add_args(parser):
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index af86fe3..ec76103 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -149,8 +149,8 @@
def _remove_admin_user_roles(self):
tenant_ids = self.admin_role_added
- LOG.debug("Removing admin user roles where needed for tenants: %s"
- % tenant_ids)
+ LOG.debug("Removing admin user roles where needed for tenants: %s",
+ tenant_ids)
for tenant_id in tenant_ids:
self._remove_admin_role(tenant_id)
@@ -236,13 +236,13 @@
needs_role = False
LOG.debug("User already had admin privilege for this tenant")
if needs_role:
- LOG.debug("Adding admin privilege for : %s" % tenant_id)
+ LOG.debug("Adding admin privilege for : %s", tenant_id)
rl_cl.create_user_role_on_project(tenant_id, self.admin_id,
self.admin_role_id)
self.admin_role_added.append(tenant_id)
def _remove_admin_role(self, tenant_id):
- LOG.debug("Remove admin user role for tenant: %s" % tenant_id)
+ LOG.debug("Remove admin user role for tenant: %s", tenant_id)
# Must initialize AdminManager for each user role
# Otherwise authentication exception is thrown, weird
id_cl = credentials.AdminManager().identity_client
@@ -253,16 +253,16 @@
self.admin_role_id)
except Exception as ex:
LOG.exception("Failed removing role from tenant which still"
- "exists, exception: %s" % ex)
+ "exists, exception: %s", ex)
def _tenant_exists(self, tenant_id):
tn_cl = self.admin_mgr.tenants_client
try:
t = tn_cl.show_tenant(tenant_id)
- LOG.debug("Tenant is: %s" % str(t))
+ LOG.debug("Tenant is: %s", str(t))
return True
except Exception as ex:
- LOG.debug("Tenant no longer exists? %s" % ex)
+ LOG.debug("Tenant no longer exists? %s", ex)
return False
def _init_state(self):
@@ -290,8 +290,8 @@
except IOError as ex:
LOG.exception("Failed loading saved state, please be sure you"
" have first run cleanup with --init-saved-state "
- "flag prior to running tempest. Exception: %s" % ex)
+ "flag prior to running tempest. Exception: %s", ex)
sys.exit(ex)
except Exception as ex:
- LOG.exception("Exception parsing saved state json : %s" % ex)
+ LOG.exception("Exception parsing saved state json : %s", ex)
sys.exit(ex)
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 32b0ebb..a632726 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -144,7 +144,7 @@
def list(self):
client = self.client
snaps = client.list_snapshots()['snapshots']
- LOG.debug("List count, %s Snapshots" % len(snaps))
+ LOG.debug("List count, %s Snapshots", len(snaps))
return snaps
def delete(self):
@@ -171,7 +171,7 @@
client = self.client
servers_body = client.list_servers()
servers = servers_body['servers']
- LOG.debug("List count, %s Servers" % len(servers))
+ LOG.debug("List count, %s Servers", len(servers))
return servers
def delete(self):
@@ -193,7 +193,7 @@
def list(self):
client = self.server_groups_client
sgs = client.list_server_groups()['server_groups']
- LOG.debug("List count, %s Server Groups" % len(sgs))
+ LOG.debug("List count, %s Server Groups", len(sgs))
return sgs
def delete(self):
@@ -218,7 +218,7 @@
def list(self):
client = self.client
stacks = client.list_stacks()['stacks']
- LOG.debug("List count, %s Stacks" % len(stacks))
+ LOG.debug("List count, %s Stacks", len(stacks))
return stacks
def delete(self):
@@ -243,7 +243,7 @@
def list(self):
client = self.client
keypairs = client.list_keypairs()['keypairs']
- LOG.debug("List count, %s Keypairs" % len(keypairs))
+ LOG.debug("List count, %s Keypairs", len(keypairs))
return keypairs
def delete(self):
@@ -270,7 +270,7 @@
client = self.client
secgrps = client.list_security_groups()['security_groups']
secgrp_del = [grp for grp in secgrps if grp['name'] != 'default']
- LOG.debug("List count, %s Security Groups" % len(secgrp_del))
+ LOG.debug("List count, %s Security Groups", len(secgrp_del))
return secgrp_del
def delete(self):
@@ -295,7 +295,7 @@
def list(self):
client = self.client
floating_ips = client.list_floating_ips()['floating_ips']
- LOG.debug("List count, %s Floating IPs" % len(floating_ips))
+ LOG.debug("List count, %s Floating IPs", len(floating_ips))
return floating_ips
def delete(self):
@@ -320,7 +320,7 @@
def list(self):
client = self.client
vols = client.list_volumes()['volumes']
- LOG.debug("List count, %s Volumes" % len(vols))
+ LOG.debug("List count, %s Volumes", len(vols))
return vols
def delete(self):
@@ -402,7 +402,7 @@
if self.is_preserve:
networks = [network for network in networks
if network['id'] not in CONF_NETWORKS]
- LOG.debug("List count, %s Networks" % networks)
+ LOG.debug("List count, %s Networks", networks)
return networks
def delete(self):
@@ -425,7 +425,7 @@
client = self.floating_ips_client
flips = client.list_floatingips(**self.tenant_filter)
flips = flips['floatingips']
- LOG.debug("List count, %s Network Floating IPs" % len(flips))
+ LOG.debug("List count, %s Network Floating IPs", len(flips))
return flips
def delete(self):
@@ -452,7 +452,7 @@
routers = [router for router in routers
if router['id'] != CONF_PUB_ROUTER]
- LOG.debug("List count, %s Routers" % len(routers))
+ LOG.debug("List count, %s Routers", len(routers))
return routers
def delete(self):
@@ -483,7 +483,7 @@
hms = client.list_health_monitors()
hms = hms['health_monitors']
hms = self._filter_by_tenant_id(hms)
- LOG.debug("List count, %s Health Monitors" % len(hms))
+ LOG.debug("List count, %s Health Monitors", len(hms))
return hms
def delete(self):
@@ -507,7 +507,7 @@
members = client.list_members()
members = members['members']
members = self._filter_by_tenant_id(members)
- LOG.debug("List count, %s Members" % len(members))
+ LOG.debug("List count, %s Members", len(members))
return members
def delete(self):
@@ -531,7 +531,7 @@
vips = client.list_vips()
vips = vips['vips']
vips = self._filter_by_tenant_id(vips)
- LOG.debug("List count, %s VIPs" % len(vips))
+ LOG.debug("List count, %s VIPs", len(vips))
return vips
def delete(self):
@@ -555,7 +555,7 @@
pools = client.list_pools()
pools = pools['pools']
pools = self._filter_by_tenant_id(pools)
- LOG.debug("List count, %s Pools" % len(pools))
+ LOG.debug("List count, %s Pools", len(pools))
return pools
def delete(self):
@@ -579,7 +579,7 @@
rules = client.list_metering_label_rules()
rules = rules['metering_label_rules']
rules = self._filter_by_tenant_id(rules)
- LOG.debug("List count, %s Metering Label Rules" % len(rules))
+ LOG.debug("List count, %s Metering Label Rules", len(rules))
return rules
def delete(self):
@@ -603,7 +603,7 @@
labels = client.list_metering_labels()
labels = labels['metering_labels']
labels = self._filter_by_tenant_id(labels)
- LOG.debug("List count, %s Metering Labels" % len(labels))
+ LOG.debug("List count, %s Metering Labels", len(labels))
return labels
def delete(self):
@@ -632,7 +632,7 @@
if self.is_preserve:
ports = self._filter_by_conf_networks(ports)
- LOG.debug("List count, %s Ports" % len(ports))
+ LOG.debug("List count, %s Ports", len(ports))
return ports
def delete(self):
@@ -660,7 +660,7 @@
if self.is_preserve:
secgroups = self._filter_by_conf_networks(secgroups)
- LOG.debug("List count, %s security_groups" % len(secgroups))
+ LOG.debug("List count, %s security_groups", len(secgroups))
return secgroups
def delete(self):
@@ -685,7 +685,7 @@
subnets = subnets['subnets']
if self.is_preserve:
subnets = self._filter_by_conf_networks(subnets)
- LOG.debug("List count, %s Subnets" % len(subnets))
+ LOG.debug("List count, %s Subnets", len(subnets))
return subnets
def delete(self):
@@ -719,7 +719,7 @@
if self.is_preserve:
flavors = [flavor for flavor in flavors
if flavor['id'] not in CONF_FLAVORS]
- LOG.debug("List count, %s Flavors after reconcile" % len(flavors))
+ LOG.debug("List count, %s Flavors after reconcile", len(flavors))
return flavors
def delete(self):
@@ -756,7 +756,7 @@
if self.is_preserve:
images = [image for image in images
if image['id'] not in CONF_IMAGES]
- LOG.debug("List count, %s Images after reconcile" % len(images))
+ LOG.debug("List count, %s Images after reconcile", len(images))
return images
def delete(self):
@@ -806,7 +806,7 @@
users = [user for user in users if user['name'] !=
CONF.auth.admin_username]
- LOG.debug("List count, %s Users after reconcile" % len(users))
+ LOG.debug("List count, %s Users after reconcile", len(users))
return users
def delete(self):
@@ -843,7 +843,7 @@
(role['id'] not in
self.saved_state_json['roles'].keys()
and role['name'] != CONF.identity.admin_role)]
- LOG.debug("List count, %s Roles after reconcile" % len(roles))
+ LOG.debug("List count, %s Roles after reconcile", len(roles))
return roles
except Exception:
LOG.exception("Cannot retrieve Roles.")
@@ -885,7 +885,7 @@
tenants = [tenant for tenant in tenants if tenant['name']
not in CONF_TENANTS]
- LOG.debug("List count, %s Tenants after reconcile" % len(tenants))
+ LOG.debug("List count, %s Tenants after reconcile", len(tenants))
return tenants
def delete(self):
@@ -920,7 +920,7 @@
domains = [domain for domain in domains if domain['id']
not in self.saved_state_json['domains'].keys()]
- LOG.debug("List count, %s Domains after reconcile" % len(domains))
+ LOG.debug("List count, %s Domains after reconcile", len(domains))
return domains
def delete(self):
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 99185d2..7634d9e 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -120,7 +120,7 @@
if os.path.isdir(config_dir):
shutil.copytree(config_dir, etc_dir)
else:
- LOG.warning("Global config dir %s can't be found" % config_dir)
+ LOG.warning("Global config dir %s can't be found", config_dir)
def generate_sample_config(self, local_dir):
conf_generator = os.path.join(os.path.dirname(__file__),
@@ -131,14 +131,14 @@
output_file])
else:
LOG.warning("Skipping sample config generation because global "
- "config file %s can't be found" % conf_generator)
+ "config file %s can't be found", conf_generator)
def create_working_dir(self, local_dir, config_dir):
# make sure we are working with abspath however tempest init is called
local_dir = os.path.abspath(local_dir)
# Create local dir if missing
if not os.path.isdir(local_dir):
- LOG.debug('Creating local working dir: %s' % local_dir)
+ LOG.debug('Creating local working dir: %s', local_dir)
os.mkdir(local_dir)
elif not os.listdir(local_dir) == []:
raise OSError("Directory you are trying to initialize already "
@@ -151,11 +151,11 @@
testr_dir = os.path.join(local_dir, '.testrepository')
# Create lock dir
if not os.path.isdir(lock_dir):
- LOG.debug('Creating lock dir: %s' % lock_dir)
+ LOG.debug('Creating lock dir: %s', lock_dir)
os.mkdir(lock_dir)
# Create log dir
if not os.path.isdir(log_dir):
- LOG.debug('Creating log dir: %s' % log_dir)
+ LOG.debug('Creating log dir: %s', log_dir)
os.mkdir(log_dir)
# Create and copy local etc dir
self.copy_config(etc_dir, config_dir)
diff --git a/tempest/cmd/main.py b/tempest/cmd/main.py
index 641d11c..1090c41 100644
--- a/tempest/cmd/main.py
+++ b/tempest/cmd/main.py
@@ -11,11 +11,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import sys
from cliff import app
from cliff import commandmanager
+from oslo_log import log as logging
from pbr import version
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 381f3df..0a1881c 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -169,7 +169,7 @@
'nova': os.extensions_client,
'cinder': os.volumes_extension_client,
'neutron': os.network_extensions_client,
- 'swift': os.account_client,
+ 'swift': os.capabilities_client,
}
# NOTE (e0ne): Use Cinder API v2 by default because v1 is deprecated
if CONF.volume_feature_enabled.api_v2:
@@ -201,7 +201,7 @@
if service != 'swift':
resp = extensions_client.list_extensions()
else:
- __, resp = extensions_client.list_extensions()
+ __, resp = extensions_client.list_capabilities()
# For Nova, Cinder and Neutron we use the alias name rather than the
# 'name' field because the alias is considered to be the canonical
# name.
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 318eb10..4f2fe67 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -163,33 +163,34 @@
clients.servers_client.delete_server(
server['id'])
except Exception:
- LOG.exception('Deleting server %s failed'
- % server['id'])
+ LOG.exception('Deleting server %s failed',
+ server['id'])
return body, servers
-def shelve_server(client, server_id, force_shelve_offload=False):
+def shelve_server(servers_client, server_id, force_shelve_offload=False):
"""Common wrapper utility to shelve server.
This method is a common wrapper to make server in 'SHELVED'
or 'SHELVED_OFFLOADED' state.
+ :param servers_clients: Compute servers client instance.
:param server_id: Server to make in shelve state
:param force_shelve_offload: Forcefully offload shelve server if it
is configured not to offload server
automatically after offload time.
"""
- client.shelve_server(server_id)
+ servers_client.shelve_server(server_id)
offload_time = CONF.compute.shelved_offload_time
if offload_time >= 0:
- waiters.wait_for_server_status(client, server_id,
+ waiters.wait_for_server_status(servers_client, server_id,
'SHELVED_OFFLOADED',
extra_timeout=offload_time)
else:
- waiters.wait_for_server_status(client, server_id, 'SHELVED')
+ waiters.wait_for_server_status(servers_client, server_id, 'SHELVED')
if force_shelve_offload:
- client.shelve_offload_server(server_id)
- waiters.wait_for_server_status(client, server_id,
+ servers_client.shelve_offload_server(server_id)
+ waiters.wait_for_server_status(servers_client, server_id,
'SHELVED_OFFLOADED')
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 5634958..bf8d30e 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -88,7 +88,7 @@
project_network_mask_bits=CONF.network.project_network_mask_bits,
public_network_id=CONF.network.public_network_id,
create_networks=(CONF.auth.create_isolated_networks and not
- CONF.baremetal.driver_enabled),
+ CONF.network.shared_physical_network),
resource_prefix=CONF.resources_prefix,
**get_dynamic_provider_params())
else:
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index b6ff241..8410541 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -85,7 +85,7 @@
return NonExistentHeader('x-account-container-count')
if 'x-account-object-count' not in actual:
return NonExistentHeader('x-account-object-count')
- if actual['x-account-container-count'] > 0:
+ if int(actual['x-account-container-count']) > 0:
acct_header = "x-account-storage-policy-"
matched_policy_count = 0
diff --git a/tempest/common/dynamic_creds.py b/tempest/common/dynamic_creds.py
index 2763d16..632a876 100644
--- a/tempest/common/dynamic_creds.py
+++ b/tempest/common/dynamic_creds.py
@@ -254,7 +254,7 @@
msg = "There was an exception trying to setup network " \
"resources for tenant %s, and this error happened " \
"trying to clean them up: %s"
- LOG.warning(msg % (tenant_id, cleanup_exception))
+ LOG.warning(msg, tenant_id, cleanup_exception)
raise
return network, subnet, router
@@ -316,8 +316,7 @@
credentials = self._create_creds(roles=credential_type)
self._creds[str(credential_type)] = credentials
# Maintained until tests are ported
- LOG.info("Acquired dynamic creds:\n credentials: %s"
- % credentials)
+ LOG.info("Acquired dynamic creds:\n credentials: %s", credentials)
if (self.neutron_available and
self.create_networks):
network, subnet, router = self._create_network_resources(
@@ -325,7 +324,7 @@
credentials.set_resources(network=network, subnet=subnet,
router=router)
LOG.info("Created isolated network resources for : \n"
- + " credentials: %s" % credentials)
+ + " credentials: %s", credentials)
return credentials
def get_primary_creds(self):
@@ -356,7 +355,7 @@
try:
client.delete_router(router_id)
except lib_exc.NotFound:
- LOG.warning('router with name: %s not found for delete' %
+ LOG.warning('router with name: %s not found for delete',
router_name)
def _clear_isolated_subnet(self, subnet_id, subnet_name):
@@ -364,7 +363,7 @@
try:
client.delete_subnet(subnet_id)
except lib_exc.NotFound:
- LOG.warning('subnet with name: %s not found for delete' %
+ LOG.warning('subnet with name: %s not found for delete',
subnet_name)
def _clear_isolated_network(self, network_id, network_name):
@@ -372,7 +371,7 @@
try:
net_client.delete_network(network_id)
except lib_exc.NotFound:
- LOG.warning('network with name: %s not found for delete' %
+ LOG.warning('network with name: %s not found for delete',
network_name)
def _cleanup_default_secgroup(self, tenant):
@@ -384,8 +383,8 @@
try:
nsg_client.delete_security_group(secgroup['id'])
except lib_exc.NotFound:
- LOG.warning('Security group %s, id %s not found for clean-up' %
- (secgroup['name'], secgroup['id']))
+ LOG.warning('Security group %s, id %s not found for clean-up',
+ secgroup['name'], secgroup['id'])
def _clear_isolated_net_resources(self):
client = self.routers_admin_client
@@ -405,7 +404,7 @@
creds.router['id'],
subnet_id=creds.subnet['id'])
except lib_exc.NotFound:
- LOG.warning('router with name: %s not found for delete' %
+ LOG.warning('router with name: %s not found for delete',
creds.router['name'])
self._clear_isolated_router(creds.router['id'],
creds.router['name'])
@@ -426,7 +425,7 @@
try:
self.creds_client.delete_user(creds.user_id)
except lib_exc.NotFound:
- LOG.warning("user with name: %s not found for delete" %
+ LOG.warning("user with name: %s not found for delete",
creds.username)
# NOTE(zhufl): Only when neutron's security_group ext is
# enabled, _cleanup_default_secgroup will not raise error. But
@@ -437,12 +436,12 @@
if self.neutron_available:
self._cleanup_default_secgroup(creds.tenant_id)
except lib_exc.NotFound:
- LOG.warning("failed to cleanup tenant %s's secgroup" %
+ LOG.warning("failed to cleanup tenant %s's secgroup",
creds.tenant_name)
try:
self.creds_client.delete_project(creds.tenant_id)
except lib_exc.NotFound:
- LOG.warning("tenant with name: %s not found for delete" %
+ LOG.warning("tenant with name: %s not found for delete",
creds.tenant_name)
self._creds = {}
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index f57c18a..f50edbd 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -122,5 +122,5 @@
params.update({"networks": [{'uuid': network['id']}]})
else:
LOG.warning('The provided network dict: %s was invalid and did '
- 'not contain an id' % network)
+ 'not contain an id', network)
return params
diff --git a/tempest/common/generator/__init__.py b/tempest/common/generator/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/common/generator/__init__.py
+++ /dev/null
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
deleted file mode 100644
index 0647edb..0000000
--- a/tempest/common/generator/base_generator.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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 functools
-
-import jsonschema
-import six
-
-
-def _check_for_expected_result(name, schema):
- expected_result = None
- if "results" in schema:
- if name in schema["results"]:
- expected_result = schema["results"][name]
- return expected_result
-
-
-def generator_type(*args, **kwargs):
- def wrapper(func):
- func.types = args
- for key in kwargs:
- setattr(func, key, kwargs[key])
- return func
- return wrapper
-
-
-def simple_generator(fn):
- """Decorator for simple generators that return one value"""
- @functools.wraps(fn)
- def wrapped(self, schema):
- result = fn(self, schema)
- if result is not None:
- expected_result = _check_for_expected_result(fn.__name__, schema)
- return (fn.__name__, result, expected_result)
- return
- return wrapped
-
-
-class BasicGeneratorSet(object):
- _instance = None
-
- schema = {
- "type": "object",
- "properties": {
- "name": {"type": "string"},
- "http-method": {
- "enum": ["GET", "PUT", "HEAD",
- "POST", "PATCH", "DELETE", 'COPY']
- },
- "admin_client": {"type": "boolean"},
- "url": {"type": "string"},
- "default_result_code": {"type": "integer"},
- "json-schema": {},
- "resources": {
- "type": "array",
- "items": {
- "oneOf": [
- {"type": "string"},
- {
- "type": "object",
- "properties": {
- "name": {"type": "string"},
- "expected_result": {"type": "integer"}
- }
- }
- ]
- }
- },
- "results": {
- "type": "object",
- "properties": {}
- }
- },
- "required": ["name", "http-method", "url"],
- "additionalProperties": False,
- }
-
- def __init__(self):
- self.types_dict = {}
- for m in dir(self):
- if callable(getattr(self, m)) and not'__' in m:
- method = getattr(self, m)
- if hasattr(method, "types"):
- for type in method.types:
- if type not in self.types_dict:
- self.types_dict[type] = []
- self.types_dict[type].append(method)
-
- def validate_schema(self, schema):
- if "json-schema" in schema:
- jsonschema.Draft4Validator.check_schema(schema['json-schema'])
- jsonschema.validate(schema, self.schema)
-
- def generate_scenarios(self, schema, path=None):
- """Generate scenario (all possible test cases) out of the given schema
-
- :param schema: a dict style schema (see ``BasicGeneratorSet.schema``)
- :param path: the schema path if the given schema is a subschema
- """
- schema_type = schema['type']
- scenarios = []
-
- if schema_type == 'object':
- properties = schema["properties"]
- for attribute, definition in six.iteritems(properties):
- current_path = copy.copy(path)
- if path is not None:
- current_path.append(attribute)
- else:
- current_path = [attribute]
- scenarios.extend(
- self.generate_scenarios(definition, current_path))
- elif isinstance(schema_type, list):
- if "integer" in schema_type:
- schema_type = "integer"
- else:
- raise Exception("non-integer list types not supported")
- for generator in self.types_dict[schema_type]:
- if hasattr(generator, "needed_property"):
- prop = generator.needed_property
- if (prop not in schema or
- schema[prop] is None or
- schema[prop] is False):
- continue
-
- name = generator.__name__
- if ("exclude_tests" in schema and
- name in schema["exclude_tests"]):
- continue
- if path is not None:
- name = "%s_%s" % ("_".join(path), name)
- scenarios.append({
- "_negtest_name": name,
- "_negtest_generator": generator,
- "_negtest_schema": schema,
- "_negtest_path": path})
- return scenarios
-
- def generate_payload(self, test, schema):
- """Generates one jsonschema out of the given test.
-
- It's mandatory to use generate_scenarios before to register all needed
- variables to the test.
-
- :param test: A test object (scenario) with all _negtest variables on it
- :param schema: schema for the test
- """
- generator = test._negtest_generator
- ret = generator(test._negtest_schema)
- path = copy.copy(test._negtest_path)
- expected_result = None
-
- if ret is not None:
- generator_result = generator(test._negtest_schema)
- invalid_snippet = generator_result[1]
- expected_result = generator_result[2]
- element = path.pop()
- if len(path) > 0:
- schema_snip = six.moves.reduce(dict.get, path, schema)
- schema_snip[element] = invalid_snippet
- else:
- schema[element] = invalid_snippet
- return expected_result
diff --git a/tempest/common/generator/negative_generator.py b/tempest/common/generator/negative_generator.py
deleted file mode 100644
index 67ace54..0000000
--- a/tempest/common/generator/negative_generator.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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 tempest.common.generator.base_generator as base
-import tempest.common.generator.valid_generator as valid
-
-
-class NegativeTestGenerator(base.BasicGeneratorSet):
- @base.generator_type("string")
- @base.simple_generator
- def gen_int(self, _):
- return 4
-
- @base.generator_type("integer")
- @base.simple_generator
- def gen_string(self, _):
- return "XXXXXX"
-
- @base.generator_type("integer", "string")
- def gen_none(self, schema):
- # Note(mkoderer): it's not using the decorator otherwise it'd be
- # filtered
- expected_result = base._check_for_expected_result('gen_none', schema)
- return ('gen_none', None, expected_result)
-
- @base.generator_type("string")
- @base.simple_generator
- def gen_str_min_length(self, schema):
- min_length = schema.get("minLength", 0)
- if min_length > 0:
- return "x" * (min_length - 1)
-
- @base.generator_type("string", needed_property="maxLength")
- @base.simple_generator
- def gen_str_max_length(self, schema):
- max_length = schema.get("maxLength", -1)
- return "x" * (max_length + 1)
-
- @base.generator_type("integer", needed_property="minimum")
- @base.simple_generator
- def gen_int_min(self, schema):
- minimum = schema["minimum"]
- if "exclusiveMinimum" not in schema:
- minimum -= 1
- return minimum
-
- @base.generator_type("integer", needed_property="maximum")
- @base.simple_generator
- def gen_int_max(self, schema):
- maximum = schema["maximum"]
- if "exclusiveMaximum" not in schema:
- maximum += 1
- return maximum
-
- @base.generator_type("object", needed_property="additionalProperties")
- @base.simple_generator
- def gen_obj_add_attr(self, schema):
- valid_schema = valid.ValidTestGenerator().generate_valid(schema)
- new_valid = copy.deepcopy(valid_schema)
- new_valid["$$$$$$$$$$"] = "xxx"
- return new_valid
diff --git a/tempest/common/generator/valid_generator.py b/tempest/common/generator/valid_generator.py
deleted file mode 100644
index 3070489..0000000
--- a/tempest/common/generator/valid_generator.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import six
-
-import tempest.common.generator.base_generator as base
-
-
-class ValidTestGenerator(base.BasicGeneratorSet):
- @base.generator_type("string")
- @base.simple_generator
- def generate_valid_string(self, schema):
- size = schema.get("minLength", 1)
- # TODO(dkr mko): handle format and pattern
- return "x" * size
-
- @base.generator_type("integer")
- @base.simple_generator
- def generate_valid_integer(self, schema):
- # TODO(dkr mko): handle multipleOf
- if "minimum" in schema:
- minimum = schema["minimum"]
- if "exclusiveMinimum" not in schema:
- return minimum
- else:
- return minimum + 1
- if "maximum" in schema:
- maximum = schema["maximum"]
- if "exclusiveMaximum" not in schema:
- return maximum
- else:
- return maximum - 1
- return 0
-
- @base.generator_type("object")
- @base.simple_generator
- def generate_valid_object(self, schema):
- obj = {}
- for k, v in six.iteritems(schema["properties"]):
- obj[k] = self.generate_valid(v)
- return obj
-
- def generate(self, schema):
- schema_type = schema["type"]
- if isinstance(schema_type, list):
- if "integer" in schema_type:
- schema_type = "integer"
- else:
- raise Exception("non-integer list types not supported")
- result = []
- if schema_type not in self.types_dict:
- raise TypeError("generator (%s) doesn't support type: %s"
- % (self.__class__.__name__, schema_type))
- for generator in self.types_dict[schema_type]:
- ret = generator(schema)
- if ret is not None:
- if isinstance(ret, list):
- result.extend(ret)
- elif isinstance(ret, tuple):
- result.append(ret)
- else:
- raise Exception("generator (%s) returns invalid result: %s"
- % (generator, ret))
- return result
-
- def generate_valid(self, schema):
- return self.generate(schema)[0][1]
diff --git a/tempest/common/negative_rest_client.py b/tempest/common/negative_rest_client.py
deleted file mode 100644
index 3495a24..0000000
--- a/tempest/common/negative_rest_client.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# (c) 2014 Deutsche Telekom AG
-# Copyright 2014 Red Hat, Inc.
-# 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 import config
-from tempest.lib.common import rest_client
-
-CONF = config.CONF
-
-
-class NegativeRestClient(rest_client.RestClient):
- """Version of RestClient that does not raise exceptions."""
- def __init__(self, auth_provider, service, **kwargs):
- region, endpoint_type = self._get_region_and_endpoint_type(service)
- super(NegativeRestClient, self).__init__(
- auth_provider, service, region, endpoint_type=endpoint_type,
- **kwargs)
-
- def _get_region_and_endpoint_type(self, service):
- """Returns the region for a specific service"""
- service_region = None
- service_endpoint_type = None
- for cfgname in dir(CONF._config):
- # Find all config.FOO.catalog_type and assume FOO is a service.
- cfg = getattr(CONF, cfgname)
- catalog_type = getattr(cfg, 'catalog_type', None)
- if catalog_type == service:
- service_region = getattr(cfg, 'region', None)
- service_endpoint_type = getattr(cfg, 'endpoint_type', None)
- if not service_region:
- service_region = CONF.identity.region
- return service_region, service_endpoint_type
-
- def _error_checker(self, method, url,
- headers, body, resp, resp_body):
- pass
-
- def send_request(self, method, url_template, resources, body=None):
- url = url_template % tuple(resources)
- if method == "GET":
- resp, body = self.get(url)
- elif method == "POST":
- resp, body = self.post(url, body)
- elif method == "PUT":
- resp, body = self.put(url, body)
- elif method == "PATCH":
- resp, body = self.patch(url, body)
- elif method == "HEAD":
- resp, body = self.head(url)
- elif method == "DELETE":
- resp, body = self.delete(url)
- elif method == "COPY":
- resp, body = self.copy(url)
- else:
- assert False
-
- return resp, body
diff --git a/tempest/common/preprov_creds.py b/tempest/common/preprov_creds.py
index 5e23696..0b0f692 100644
--- a/tempest/common/preprov_creds.py
+++ b/tempest/common/preprov_creds.py
@@ -158,8 +158,10 @@
if resource == 'network':
hash_dict['networks'][temp_hash_key] = resources[resource]
else:
- LOG.warning('Unknown resource type %s, ignoring this field'
- % resource)
+ LOG.warning(
+ 'Unknown resource type %s, ignoring this field',
+ resource
+ )
return hash_dict
def is_multi_user(self):
@@ -245,7 +247,7 @@
free_hash = self._get_free_hash(useable_hashes)
clean_creds = self._sanitize_creds(
self.hash_dict['creds'][free_hash])
- LOG.info('%s allocated creds:\n%s' % (self.name, clean_creds))
+ LOG.info('%s allocated creds:\n%s', self.name, clean_creds)
return self._wrap_creds_with_network(free_hash)
@lockutils.synchronized('test_accounts_io', external=True)
@@ -253,7 +255,7 @@
hash_path = os.path.join(self.accounts_dir, hash_string)
if not os.path.isfile(hash_path):
LOG.warning('Expected an account lock file %s to remove, but '
- 'one did not exist' % hash_path)
+ 'one did not exist', hash_path)
else:
os.remove(hash_path)
if not os.listdir(self.accounts_dir):
@@ -278,7 +280,7 @@
_hash = self.get_hash(creds)
clean_creds = self._sanitize_creds(self.hash_dict['creds'][_hash])
self.remove_hash(_hash)
- LOG.info("%s returned allocated creds:\n%s" % (self.name, clean_creds))
+ LOG.info("%s returned allocated creds:\n%s", self.name, clean_creds)
def get_primary_creds(self):
if self._creds.get('primary'):
@@ -304,7 +306,9 @@
if exist_creds and not force_new:
return exist_creds
elif exist_creds and force_new:
- new_index = six.text_type(roles).encode('utf-8') + '-' + \
+ # NOTE(andreaf) In py3.x encode returns bytes, and b'' is bytes
+ # In py2.7 encode returns strings, and b'' is still string
+ new_index = six.text_type(roles).encode('utf-8') + b'-' + \
six.text_type(len(self._creds)).encode('utf-8')
self._creds[new_index] = exist_creds
net_creds = self._get_creds(roles=roles)
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 9ec217f..009812e 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -87,7 +87,7 @@
# Shell options below add more clearness on failures,
# path is extended for some non-cirros guest oses (centos7)
cmd = CONF.validation.ssh_shell_prologue + " " + cmd
- LOG.debug("Remote command: %s" % cmd)
+ LOG.debug("Remote command: %s", cmd)
return self.ssh_client.exec_command(cmd)
@debug_ssh
@@ -112,11 +112,22 @@
output = self.exec_command('grep -c ^processor /proc/cpuinfo')
return int(output)
- def get_partitions(self):
- # Return the contents of /proc/partitions
- command = 'cat /proc/partitions'
+ def get_disks(self):
+ # Select root disk devices as shown by lsblk
+ command = 'lsblk -lb --nodeps'
output = self.exec_command(command)
- return output
+ selected = []
+ pos = None
+ for l in output.splitlines():
+ if pos is None and l.find("TYPE") > 0:
+ pos = l.find("TYPE")
+ # Show header line too
+ selected.append(l)
+ # lsblk lists disk type in a column right-aligned with TYPE
+ elif pos > 0 and l[pos:pos + 4] == "disk":
+ selected.append(l)
+
+ return "\n".join(selected)
def get_boot_time(self):
cmd = 'cut -f1 -d. /proc/uptime'
@@ -237,5 +248,5 @@
except tempest.lib.exceptions.SSHExecCommandFailed:
LOG.error("Couldn't mke2fs")
cmd_why = 'sudo ls -lR /dev'
- LOG.info("Contents of /dev: %s" % self.exec_command(cmd_why))
+ LOG.info("Contents of /dev: %s", self.exec_command(cmd_why))
raise
diff --git a/tempest/common/validation_resources.py b/tempest/common/validation_resources.py
index a55ee32..88697c4 100644
--- a/tempest/common/validation_resources.py
+++ b/tempest/common/validation_resources.py
@@ -60,8 +60,7 @@
parent_group_id=security_group['id'], ip_protocol='icmp',
from_port=-1, to_port=-1)
LOG.debug("SSH Validation resource security group with tcp and icmp "
- "rules %s created"
- % sg_name)
+ "rules %s created", sg_name)
return security_group
@@ -73,7 +72,7 @@
keypair_name = data_utils.rand_name('keypair')
validation_data.update(os.keypairs_client.create_keypair(
name=keypair_name))
- LOG.debug("Validation resource key %s created" % keypair_name)
+ LOG.debug("Validation resource key %s created", keypair_name)
add_rule = False
if validation_resources['security_group']:
if validation_resources['security_group_rules']:
@@ -98,11 +97,13 @@
try:
keypair_client.delete_keypair(keypair_name)
except lib_exc.NotFound:
- LOG.warning("Keypair %s is not found when attempting to delete"
- % keypair_name)
+ LOG.warning(
+ "Keypair %s is not found when attempting to delete",
+ keypair_name
+ )
except Exception as exc:
- LOG.exception('Exception raised while deleting key %s'
- % keypair_name)
+ LOG.exception('Exception raised while deleting key %s',
+ keypair_name)
if not has_exception:
has_exception = exc
if 'security_group' in validation_data:
@@ -113,15 +114,15 @@
security_group_client.wait_for_resource_deletion(sec_id)
except lib_exc.NotFound:
LOG.warning("Security group %s is not found when attempting "
- "to delete" % sec_id)
+ "to delete", sec_id)
except lib_exc.Conflict as exc:
LOG.exception('Conflict while deleting security '
- 'group %s VM might not be deleted ' % sec_id)
+ 'group %s VM might not be deleted', sec_id)
if not has_exception:
has_exception = exc
except Exception as exc:
LOG.exception('Exception raised while deleting security '
- 'group %s ' % sec_id)
+ 'group %s', sec_id)
if not has_exception:
has_exception = exc
if 'floating_ip' in validation_data:
@@ -131,10 +132,9 @@
floating_client.delete_floating_ip(fip_id)
except lib_exc.NotFound:
LOG.warning('Floating ip %s not found while attempting to '
- 'delete' % fip_id)
+ 'delete', fip_id)
except Exception as exc:
- LOG.exception('Exception raised while deleting ip %s '
- % fip_id)
+ LOG.exception('Exception raised while deleting ip %s', fip_id)
if not has_exception:
has_exception = exc
if has_exception:
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 0cf8154..fe648a0 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -231,35 +231,6 @@
raise lib_exc.TimeoutException(message)
-def wait_for_bm_node_status(client, node_id, attr, status):
- """Waits for a baremetal node attribute to reach given status.
-
- The client should have a show_node(node_uuid) method to get the node.
- """
- _, node = client.show_node(node_id)
- start = int(time.time())
-
- while node[attr] != status:
- time.sleep(client.build_interval)
- _, node = client.show_node(node_id)
- status_curr = node[attr]
- if status_curr == status:
- return
-
- if int(time.time()) - start >= client.build_timeout:
- message = ('Node %(node_id)s failed to reach %(attr)s=%(status)s '
- 'within the required time (%(timeout)s s).' %
- {'node_id': node_id,
- 'attr': attr,
- 'status': status,
- 'timeout': client.build_timeout})
- message += ' Current state of %s: %s.' % (attr, status_curr)
- caller = test_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise lib_exc.TimeoutException(message)
-
-
def wait_for_qos_operations(client, qos_id, operation, args=None):
"""Waits for a qos operations to be completed.
@@ -290,3 +261,28 @@
if int(time.time()) - start_time >= client.build_timeout:
raise lib_exc.TimeoutException
time.sleep(client.build_interval)
+
+
+def wait_for_interface_status(client, server, port_id, status):
+ """Waits for an interface to reach a given status."""
+ body = (client.show_interface(server, port_id)
+ ['interfaceAttachment'])
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(client.build_interval)
+ body = (client.show_interface(server, port_id)
+ ['interfaceAttachment'])
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= client.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status '
+ '(current %s) within the required time (%s s).' %
+ (port_id, status, interface_status,
+ client.build_timeout))
+ raise lib_exc.TimeoutException(message)
+
+ return body
diff --git a/tempest/config.py b/tempest/config.py
index bc9215c..0e45f2e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -171,7 +171,20 @@
cfg.BoolOpt('admin_domain_scope',
default=False,
help="Whether keystone identity v3 policy required "
- "a domain scoped token to use admin APIs")
+ "a domain scoped token to use admin APIs"),
+ # Security Compliance (PCI-DSS)
+ cfg.IntOpt('user_lockout_failure_attempts',
+ default=2,
+ help="The number of unsuccessful login attempts the user is "
+ "allowed before having the account locked."),
+ cfg.IntOpt('user_lockout_duration',
+ default=5,
+ help="The number of seconds a user account will remain "
+ "locked."),
+ cfg.IntOpt('user_unique_last_password_count',
+ default=2,
+ help="The number of passwords for a user that must be unique "
+ "before an old password can be reused."),
]
service_clients_group = cfg.OptGroup(name='service-clients',
@@ -208,7 +221,11 @@
# of life.
cfg.BoolOpt('reseller',
default=False,
- help='Does the environment support reseller?')
+ help='Does the environment support reseller?'),
+ cfg.BoolOpt('security_compliance',
+ default=False,
+ help='Does the environment have the security compliance '
+ 'settings enabled?')
]
compute_group = cfg.OptGroup(name='compute',
@@ -278,6 +295,11 @@
'be utilized by some multinode specific tests to ensure '
'that requests match the expected size of the cluster '
'you are testing with.')),
+ cfg.StrOpt('hypervisor_type',
+ default=None,
+ help="Hypervisor type of the test target on heterogeneous "
+ "compute environment. The value can be 'QEMU', 'xen' or "
+ "something."),
cfg.StrOpt('min_microversion',
default=None,
help="Lower version of the test target microversion range. "
@@ -397,7 +419,10 @@
cfg.BoolOpt('nova_cert',
default=False,
help='Does the test environment have the nova cert running?',
- deprecated_for_removal=True),
+ deprecated_for_removal=True,
+ deprecated_reason="On Nova side, the nova-cert service is "
+ "deprecated and the service will be removed "
+ "as early as Ocata."),
cfg.BoolOpt('personality',
default=False,
help='Does the test environment support server personality'),
@@ -414,10 +439,16 @@
default=['all'],
help="A list of enabled filters that nova will accept as hints"
" to the scheduler when creating a server. A special "
- "entry 'all' indicates all filters are enabled. Empty "
- "list indicates all filters are disabled. The full "
- "available list of filters is in nova.conf: "
- "DEFAULT.scheduler_available_filters"),
+ "entry 'all' indicates all filters that are included "
+ "with nova are enabled. Empty list indicates all filters "
+ "are disabled. The full list of available filters is in "
+ "nova.conf: DEFAULT.scheduler_available_filters. If the "
+ "default value is overridden in nova.conf by the test "
+ "environment (which means that a different set of "
+ "filters is enabled than what is included in Nova by "
+ "default) then, this option must be configured to "
+ "contain the same filters that Nova uses in the test "
+ "environment."),
cfg.BoolOpt('swap_volume',
default=False,
help='Does the test environment support in-place swapping of '
@@ -557,6 +588,10 @@
default=["1.0.0.0/16", "2.0.0.0/16"],
help="List of ip pools"
" for subnetpools creation"),
+ cfg.BoolOpt('shared_physical_network',
+ default=False,
+ help="The environment does not support network separation "
+ "between tenants."),
# TODO(ylobankov): Delete this option once the Liberty release is EOL.
cfg.BoolOpt('dvr_extra_resources',
default=True,
@@ -780,11 +815,6 @@
cfg.BoolOpt('api_v3',
default=False,
help="Is the v3 volume API enabled"),
- cfg.BoolOpt('bootable',
- default=True,
- help='Update bootable status of a volume '
- 'Not implemented on icehouse ',
- deprecated_for_removal=True),
# TODO(ynesenenko): Remove volume_services once liberty-eol happens.
cfg.BoolOpt('volume_services',
default=False,
@@ -966,9 +996,6 @@
cfg.BoolOpt('sahara',
default=False,
help="Whether or not Sahara is expected to be available"),
- cfg.BoolOpt('ironic',
- default=False,
- help="Whether or not Ironic is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1023,64 +1050,6 @@
deprecated_for_removal=True),
]
-
-baremetal_group = cfg.OptGroup(name='baremetal',
- title='Baremetal provisioning service options',
- help='When enabling baremetal tests, Nova '
- 'must be configured to use the Ironic '
- 'driver. The following parameters for the '
- '[compute] section must be disabled: '
- 'console_output, interface_attach, '
- 'live_migration, pause, rescue, resize '
- 'shelve, snapshot, and suspend')
-
-
-# NOTE(deva): Ironic tests have been ported to tempest.lib. New config options
-# should be added to ironic/ironic_tempest_plugin/config.py.
-# However, these options need to remain here for testing stable
-# branches until Liberty release reaches EOL.
-BaremetalGroup = [
- cfg.StrOpt('catalog_type',
- default='baremetal',
- help="Catalog type of the baremetal provisioning service"),
- cfg.BoolOpt('driver_enabled',
- default=False,
- help="Whether the Ironic nova-compute driver is enabled"),
- cfg.StrOpt('driver',
- default='fake',
- help="Driver name which Ironic uses"),
- cfg.StrOpt('endpoint_type',
- default='publicURL',
- choices=['public', 'admin', 'internal',
- 'publicURL', 'adminURL', 'internalURL'],
- help="The endpoint type to use for the baremetal provisioning "
- "service"),
- cfg.IntOpt('active_timeout',
- default=300,
- help="Timeout for Ironic node to completely provision"),
- cfg.IntOpt('association_timeout',
- default=30,
- help="Timeout for association of Nova instance and Ironic "
- "node"),
- cfg.IntOpt('power_timeout',
- default=60,
- help="Timeout for Ironic power transitions."),
- cfg.IntOpt('unprovision_timeout',
- default=300,
- help="Timeout for unprovisioning an Ironic node. "
- "Takes longer since Kilo as Ironic performs an extra "
- "step in Node cleaning.")
-]
-
-negative_group = cfg.OptGroup(name='negative', title="Negative Test Options")
-
-NegativeGroup = [
- cfg.StrOpt('test_generator',
- default='tempest.common.' +
- 'generator.negative_generator.NegativeTestGenerator',
- help="Test generator class for all negative tests"),
-]
-
DefaultGroup = [
cfg.StrOpt('resources_prefix',
default='tempest',
@@ -1110,9 +1079,7 @@
(scenario_group, ScenarioGroup),
(service_available_group, ServiceAvailableGroup),
(debug_group, DebugGroup),
- (baremetal_group, BaremetalGroup),
(input_scenario_group, InputScenarioGroup),
- (negative_group, NegativeGroup),
(None, DefaultGroup)
]
@@ -1174,9 +1141,7 @@
self.scenario = _CONF.scenario
self.service_available = _CONF.service_available
self.debug = _CONF.debug
- self.baremetal = _CONF.baremetal
self.input_scenario = _CONF['input-scenario']
- self.negative = _CONF.negative
logging.tempest_set_log_file('tempest.log')
def __init__(self, parse_conf=True, config_path=None):
@@ -1218,7 +1183,7 @@
logging.setup(_CONF, 'tempest')
LOG = logging.getLogger('tempest')
- LOG.info("Using tempest config file %s" % path)
+ LOG.info("Using tempest config file %s", path)
register_opts()
self._set_attrs()
if parse_conf:
diff --git a/tempest/lib/cli/base.py b/tempest/lib/cli/base.py
index a43d002..5d7fbe3 100644
--- a/tempest/lib/cli/base.py
+++ b/tempest/lib/cli/base.py
@@ -29,7 +29,7 @@
def execute(cmd, action, flags='', params='', fail_ok=False,
- merge_stderr=False, cli_dir='/usr/bin'):
+ merge_stderr=False, cli_dir='/usr/bin', prefix=''):
"""Executes specified command for the given action.
:param cmd: command to be executed
@@ -48,10 +48,13 @@
:type merge_stderr: boolean
:param cli_dir: The path where the cmd can be executed
:type cli_dir: string
+ :param prefix: prefix to insert before command
+ :type prefix: string
"""
- cmd = ' '.join([os.path.join(cli_dir, cmd),
+ cmd = ' '.join([prefix, os.path.join(cli_dir, cmd),
flags, action, params])
- LOG.info("running: '%s'" % cmd)
+ cmd = cmd.strip()
+ LOG.info("running: '%s'", cmd)
if six.PY2:
cmd = cmd.encode('utf-8')
cmd = shlex.split(cmd)
@@ -88,10 +91,12 @@
:type cli_dir: string
:param insecure: if True, --insecure is passed to python client binaries.
:type insecure: boolean
+ :param prefix: prefix to insert before commands
+ :type prefix: string
"""
def __init__(self, username='', password='', tenant_name='', uri='',
- cli_dir='', insecure=False, *args, **kwargs):
+ cli_dir='', insecure=False, prefix='', *args, **kwargs):
"""Initialize a new CLIClient object."""
super(CLIClient, self).__init__()
self.cli_dir = cli_dir if cli_dir else '/usr/bin'
@@ -100,6 +105,7 @@
self.password = password
self.uri = uri
self.insecure = insecure
+ self.prefix = prefix
def nova(self, action, flags='', params='', fail_ok=False,
endpoint_type='publicURL', merge_stderr=False):
@@ -365,7 +371,7 @@
else:
flags = creds + ' ' + flags
return execute(cmd, action, flags, params, fail_ok, merge_stderr,
- self.cli_dir)
+ self.cli_dir, prefix=self.prefix)
class ClientTestBase(base.BaseTestCase):
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index 0313505..c71c7a7 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -112,7 +112,7 @@
if label is None:
label = line
else:
- LOG.warning('Invalid line between tables: %s' % line)
+ LOG.warning('Invalid line between tables: %s', line)
if len(table_) > 0:
LOG.warning('Missing end of table')
@@ -140,7 +140,7 @@
columns = _table_columns(line)
continue
if '|' not in line:
- LOG.warning('skipping invalid table line: %s' % line)
+ LOG.warning('skipping invalid table line: %s', line)
continue
row = []
for col in columns:
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 1239ac5..88ce775 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -23,6 +23,7 @@
import unittest
import uuid
+from oslo_utils import uuidutils
import six.moves.urllib.parse as urlparse
DECORATOR_MODULE = 'test'
@@ -61,7 +62,7 @@
if filename not in self.source_files:
with open(filename) as f:
self.source_files[filename] = self._quote(f.read())
- patch_id = str(uuid.uuid4())
+ patch_id = uuidutils.generate_uuid()
if not patch.endswith('\n'):
patch += '\n'
self.patches[patch_id] = self._quote(patch)
diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py
index ad968f1..ea06011 100644
--- a/tempest/lib/common/cred_client.py
+++ b/tempest/lib/common/cred_client.py
@@ -78,8 +78,8 @@
user['id'],
role['id'])
except lib_exc.Conflict:
- LOG.debug("Role %s already assigned on project %s for user %s" % (
- role['id'], project['id'], user['id']))
+ LOG.debug("Role %s already assigned on project %s for user %s",
+ role['id'], project['id'], user['id'])
@abc.abstractmethod
def get_credentials(self, user, project, password):
@@ -140,7 +140,7 @@
# Domain names must be unique, in any case a list is returned,
# selecting the first (and only) element
self.creds_domain = self.domains_client.list_domains(
- params={'name': domain_name})['domains'][0]
+ name=domain_name)['domains'][0]
except lib_exc.NotFound:
# TODO(andrea) we could probably create the domain on the fly
msg = "Requested domain %s could not be found" % domain_name
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 2d2771f..31d2ba5 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -406,8 +406,8 @@
def _log_request_start(self, method, req_url):
caller_name = test_utils.find_test_caller()
if self.trace_requests and re.search(self.trace_requests, caller_name):
- self.LOG.debug('Starting Request (%s): %s %s' %
- (caller_name, method, req_url))
+ self.LOG.debug('Starting Request (%s): %s %s', caller_name,
+ method, req_url)
def _log_request_full(self, resp, req_headers=None, req_body=None,
resp_body=None, extra=None):
@@ -423,11 +423,11 @@
Body: %s"""
self.LOG.debug(
- log_fmt % (
- str(req_headers),
- self._safe_body(req_body),
- str(resp_log),
- self._safe_body(resp_body)),
+ log_fmt,
+ str(req_headers),
+ self._safe_body(req_body),
+ str(resp_log),
+ self._safe_body(resp_body),
extra=extra)
def _log_request(self, method, req_url, resp,
@@ -445,12 +445,12 @@
if secs:
secs = " %.3fs" % secs
self.LOG.info(
- 'Request (%s): %s %s %s%s' % (
- caller_name,
- resp['status'],
- method,
- req_url,
- secs),
+ 'Request (%s): %s %s %s%s',
+ caller_name,
+ resp['status'],
+ method,
+ req_url,
+ secs,
extra=extra)
# Also look everything at DEBUG if you want to filter this
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 4095c77..75c2e51 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -21,6 +21,7 @@
from debtcollector import removals
from oslo_utils import netutils
+from oslo_utils import uuidutils
import six.moves
@@ -30,7 +31,7 @@
:return: a random UUID (e.g. '1dc12c7d-60eb-4b61-a7a2-17cf210155b6')
:rtype: string
"""
- return str(uuid.uuid4())
+ return uuidutils.generate_uuid()
def rand_uuid_hex():
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index 3b28701..bd0db7c 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -74,7 +74,7 @@
# prevents frame leaks
del frame
if caller_name is None:
- LOG.debug("Sane call name not found in %s" % names)
+ LOG.debug("Sane call name not found in %s", names)
return caller_name
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index a6c01bb..108ba70 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -71,54 +71,70 @@
message = "The success code is different than the expected one"
-class NotFound(ClientRestClientException):
- message = "Object not found"
+class BadRequest(ClientRestClientException):
+ status_code = 400
+ message = "Bad request"
class Unauthorized(ClientRestClientException):
+ status_code = 401
message = 'Unauthorized'
class Forbidden(ClientRestClientException):
+ status_code = 403
message = "Forbidden"
-class TimeoutException(OtherRestClientException):
- message = "Request timed out"
-
-
-class BadRequest(ClientRestClientException):
- message = "Bad request"
-
-
-class UnprocessableEntity(ClientRestClientException):
- message = "Unprocessable entity"
-
-
-class RateLimitExceeded(ClientRestClientException):
- message = "Rate limit exceeded"
-
-
-class OverLimit(ClientRestClientException):
- message = "Request entity is too large"
-
-
-class ServerFault(ServerRestClientException):
- message = "Got server fault"
-
-
-class NotImplemented(ServerRestClientException):
- message = "Got NotImplemented error"
+class NotFound(ClientRestClientException):
+ status_code = 404
+ message = "Object not found"
class Conflict(ClientRestClientException):
+ status_code = 409
message = "An object with that identifier already exists"
class Gone(ClientRestClientException):
+ status_code = 410
message = "The requested resource is no longer available"
+class RateLimitExceeded(ClientRestClientException):
+ status_code = 413
+ message = "Rate limit exceeded"
+
+
+class OverLimit(ClientRestClientException):
+ status_code = 413
+ message = "Request entity is too large"
+
+
+class InvalidContentType(ClientRestClientException):
+ status_code = 415
+ message = "Invalid content type provided"
+
+
+class UnprocessableEntity(ClientRestClientException):
+ status_code = 422
+ message = "Unprocessable entity"
+
+
+class ServerFault(ServerRestClientException):
+ status_code = 500
+ message = "Got server fault"
+
+
+class NotImplemented(ServerRestClientException):
+ status_code = 501
+ message = "Got NotImplemented error"
+
+
+class TimeoutException(OtherRestClientException):
+ message = "Request timed out"
+
+
class ResponseWithNonEmptyBody(OtherRestClientException):
message = ("RFC Violation! Response with %(status)d HTTP Status Code "
"MUST NOT have a body")
@@ -137,10 +153,6 @@
message = "HTTP response header is invalid"
-class InvalidContentType(ClientRestClientException):
- message = "Invalid content type provided"
-
-
class UnexpectedContentType(OtherRestClientException):
message = "Unexpected content type provided"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 2e5c457..262a894 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -23,6 +23,7 @@
from tempest.lib.common.utils import misc
from tempest.lib import exceptions
from tempest.lib.services import compute
+from tempest.lib.services import identity
from tempest.lib.services import image
from tempest.lib.services import network
from tempest.lib.services import volume
@@ -39,11 +40,14 @@
"""
return {
'compute': compute,
+ 'identity.v2': identity.v2,
+ 'identity.v3': identity.v3,
'image.v1': image.v1,
'image.v2': image.v2,
'network': network,
'volume.v1': volume.v1,
- 'volume.v2': volume.v2
+ 'volume.v2': volume.v2,
+ 'volume.v3': volume.v3
}
@@ -52,7 +56,7 @@
# NOTE(andreaf) This list will exists only as long the remain clients
# are migrated to tempest.lib, and it will then be deleted without
# deprecation or advance notice
- return set(['identity.v2', 'identity.v3', 'object-storage', 'volume.v3'])
+ return set(['object-storage'])
def available_modules():
@@ -370,7 +374,7 @@
except Exception:
LOG.exception(
'Failed to register service client from plugin %s '
- 'with parameters %s' % (plugin, service_client))
+ 'with parameters %s', plugin, service_client)
raise
def register_service_client_module(self, name, service_version,
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 4d1044b..a83c68b 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -67,9 +67,9 @@
API reference:
http://developer.openstack.org/api-ref-compute-v2.1.html#createFlavor
"""
- if kwargs.get('ephemeral'):
+ if 'ephemeral' in kwargs:
kwargs['OS-FLV-EXT-DATA:ephemeral'] = kwargs.pop('ephemeral')
- if kwargs.get('is_public'):
+ if 'is_public' in kwargs:
kwargs['os-flavor-access:is_public'] = kwargs.pop('is_public')
post_body = json.dumps({'flavor': kwargs})
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 24557d8..597e815 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -100,7 +100,7 @@
any changes.
:param disk_config: The name is changed to OS-DCF:diskConfig
"""
- if kwargs.get('disk_config'):
+ if 'disk_config' in kwargs:
kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
post_body = json.dumps({'server': kwargs})
diff --git a/tempest/lib/services/identity/__init__.py b/tempest/lib/services/identity/__init__.py
index e69de29..941a10e 100644
--- a/tempest/lib/services/identity/__init__.py
+++ b/tempest/lib/services/identity/__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.identity import v2
+from tempest.lib.services.identity import v3
+
+__all__ = ['v2', 'v3']
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
index a5d7c86..c4fd483 100644
--- a/tempest/lib/services/identity/v2/token_client.py
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -117,8 +117,8 @@
LOG = logging.getLogger(__name__)
def _warn(self):
- self.LOG.warning("%s class was deprecated and renamed to %s" %
- (self.__class__.__name__, 'TokenClient'))
+ self.LOG.warning("%s class was deprecated and renamed to %s",
+ self.__class__.__name__, 'TokenClient')
def __init__(self, *args, **kwargs):
self._warn()
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index e69de29..8058d51 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from tempest.lib.services.identity.v3.credentials_client import \
+ CredentialsClient
+from tempest.lib.services.identity.v3.domains_client import DomainsClient
+from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
+from tempest.lib.services.identity.v3.groups_client import GroupsClient
+from tempest.lib.services.identity.v3.identity_client import IdentityClient
+from tempest.lib.services.identity.v3.inherited_roles_client import \
+ InheritedRolesClient
+from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.projects_client import ProjectsClient
+from tempest.lib.services.identity.v3.regions_client import RegionsClient
+from tempest.lib.services.identity.v3.role_assignments_client import \
+ RoleAssignmentsClient
+from tempest.lib.services.identity.v3.roles_client import RolesClient
+from tempest.lib.services.identity.v3.services_client import ServicesClient
+from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.identity.v3.trusts_client import TrustsClient
+from tempest.lib.services.identity.v3.users_client import UsersClient
+
+__all__ = ['CredentialsClient', 'DomainsClient', 'EndPointsClient',
+ 'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
+ 'PoliciesClient', 'ProjectsClient', 'RegionsClient',
+ 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
+ 'V3TokenClient', 'TrustsClient', 'UsersClient', ]
diff --git a/tempest/services/identity/v3/json/domains_client.py b/tempest/lib/services/identity/v3/domains_client.py
similarity index 60%
rename from tempest/services/identity/v3/json/domains_client.py
rename to tempest/lib/services/identity/v3/domains_client.py
index fe929a5..43cb62c 100644
--- a/tempest/services/identity/v3/json/domains_client.py
+++ b/tempest/lib/services/identity/v3/domains_client.py
@@ -21,29 +21,36 @@
class DomainsClient(rest_client.RestClient):
api_version = "v3"
- def create_domain(self, name, **kwargs):
- """Creates a domain."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
+ def create_domain(self, **kwargs):
+ """Creates a domain.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#create-domain
+ """
+ post_body = json.dumps({'domain': kwargs})
resp, body = self.post('domains', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def delete_domain(self, domain_id):
- """Deletes a domain."""
+ """Deletes a domain.
+
+ For APi details, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#delete-domain
+ """
resp, body = self.delete('domains/%s' % domain_id)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_domains(self, params=None):
- """List Domains."""
+ def list_domains(self, **params):
+ """List Domains.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#list-domains
+ """
url = 'domains'
if params:
url += '?%s' % urllib.urlencode(params)
@@ -53,24 +60,24 @@
return rest_client.ResponseBody(resp, body)
def update_domain(self, domain_id, **kwargs):
- """Updates a domain."""
- body = self.show_domain(domain_id)['domain']
- description = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- name = kwargs.get('name', body['name'])
- post_body = {
- 'description': description,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'domain': post_body})
+ """Updates a domain.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#update-domain
+ """
+ post_body = json.dumps({'domain': kwargs})
resp, body = self.patch('domains/%s' % domain_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
def show_domain(self, domain_id):
- """Get Domain details."""
+ """Get Domain details.
+
+ For API details, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/identity/v3/index.html#show-domain-details
+ """
resp, body = self.get('domains/%s' % domain_id)
self.expected_success(200, resp.status)
body = json.loads(body)
diff --git a/tempest/lib/services/identity/v3/role_assignments_client.py b/tempest/lib/services/identity/v3/role_assignments_client.py
new file mode 100644
index 0000000..10de03f
--- /dev/null
+++ b/tempest/lib/services/identity/v3/role_assignments_client.py
@@ -0,0 +1,48 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class RoleAssignmentsClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def list_role_assignments(self, effective=False, **kwargs):
+ """List role assignments.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/identity/v3/?expanded=list-effective-role-assignments-detail
+
+ :param effective: If True, returns the effective assignments, including
+ any assignments gained by virtue of group membership
+ or inherited roles.
+ """
+ url = 'role_assignments'
+ if kwargs:
+ # NOTE(rodrigods): "effective" is a key-only query parameter and
+ # is treated below.
+ if 'effective' in kwargs:
+ del kwargs['effective']
+ url += '?%s' % urllib.urlencode(kwargs)
+ if effective:
+ url += '&effective'
+
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index c1f7e7b..06927f4 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -179,8 +179,8 @@
LOG = logging.getLogger(__name__)
def _warn(self):
- self.LOG.warning("%s class was deprecated and renamed to %s" %
- (self.__class__.__name__, 'V3TokenClient'))
+ self.LOG.warning("%s class was deprecated and renamed to %s",
+ self.__class__.__name__, 'V3TokenClient')
def __init__(self, *args, **kwargs):
self._warn()
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
index e67a547..03f4c4b 100644
--- a/tempest/lib/services/image/v1/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -115,7 +115,7 @@
if detail:
url += '/detail'
- if kwargs.get('changes_since'):
+ if 'changes_since' in kwargs:
kwargs['changes-since'] = kwargs.pop('changes_since')
if len(kwargs) > 0:
diff --git a/tempest/lib/services/image/v2/__init__.py b/tempest/lib/services/image/v2/__init__.py
index 32bad8b..d359d4b 100644
--- a/tempest/lib/services/image/v2/__init__.py
+++ b/tempest/lib/services/image/v2/__init__.py
@@ -15,10 +15,12 @@
from tempest.lib.services.image.v2.image_members_client import \
ImageMembersClient
from tempest.lib.services.image.v2.images_client import ImagesClient
+from tempest.lib.services.image.v2.namespace_properties_client import \
+ NamespacePropertiesClient
from tempest.lib.services.image.v2.namespaces_client import NamespacesClient
from tempest.lib.services.image.v2.resource_types_client import \
ResourceTypesClient
from tempest.lib.services.image.v2.schemas_client import SchemasClient
-__all__ = ['ImageMembersClient', 'ImagesClient', 'NamespacesClient',
- 'ResourceTypesClient', 'SchemasClient']
+__all__ = ['ImageMembersClient', 'ImagesClient', 'NamespacePropertiesClient',
+ 'NamespacesClient', 'ResourceTypesClient', 'SchemasClient']
diff --git a/tempest/lib/services/image/v2/namespace_properties_client.py b/tempest/lib/services/image/v2/namespace_properties_client.py
new file mode 100644
index 0000000..1236b2b
--- /dev/null
+++ b/tempest/lib/services/image/v2/namespace_properties_client.py
@@ -0,0 +1,91 @@
+# Copyright 2016 EasyStack.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class NamespacePropertiesClient(rest_client.RestClient):
+ api_version = "v2"
+
+ def list_namespace_properties(self, namespace):
+ """Lists property definitions in a namespace.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#list-properties
+ """
+ url = 'metadefs/namespaces/%s/properties' % namespace
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def create_namespace_property(self, namespace, **kwargs):
+ """Creates a property definition in a namespace.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#create-property
+ """
+ url = 'metadefs/namespaces/%s/properties' % namespace
+ data = json.dumps(kwargs)
+ resp, body = self.post(url, data)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_namespace_properties(self, namespace, property_name):
+ """Shows the definition for a property.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#show-property-definition
+ """
+ url = 'metadefs/namespaces/%s/properties/%s' % (namespace,
+ property_name)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_namespace_properties(self, namespace, property_name, **kwargs):
+ """Updates a property definition.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#update-property-definition
+ """
+ url = 'metadefs/namespaces/%s/properties/%s' % (namespace,
+ property_name)
+ data = json.dumps(kwargs)
+ resp, body = self.put(url, data)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_namespace_property(self, namespace, property_name):
+ """Removes a property definition from a namespace.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/image/v2/metadefs-index.html#remove-property-definition
+ """
+ url = 'metadefs/namespaces/%s/properties/%s' % (namespace,
+ property_name)
+ resp, _ = self.delete(url)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index c466f07..19e5463 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -27,6 +27,8 @@
SecurityGroupRulesClient
from tempest.lib.services.network.security_groups_client import \
SecurityGroupsClient
+from tempest.lib.services.network.service_providers_client import \
+ ServiceProvidersClient
from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
from tempest.lib.services.network.subnets_client import SubnetsClient
from tempest.lib.services.network.versions_client import NetworkVersionsClient
@@ -35,4 +37,5 @@
'MeteringLabelRulesClient', 'MeteringLabelsClient',
'NetworksClient', 'PortsClient', 'QuotasClient', 'RoutersClient',
'SecurityGroupRulesClient', 'SecurityGroupsClient',
- 'SubnetpoolsClient', 'SubnetsClient', 'NetworkVersionsClient']
+ 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient',
+ 'NetworkVersionsClient']
diff --git a/tempest/lib/services/network/service_providers_client.py b/tempest/lib/services/network/service_providers_client.py
new file mode 100644
index 0000000..0ee9bc3
--- /dev/null
+++ b/tempest/lib/services/network/service_providers_client.py
@@ -0,0 +1,21 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.network import base
+
+
+class ServiceProvidersClient(base.BaseNetworkClient):
+
+ def list_service_providers(self, **filters):
+ """Lists service providers."""
+ uri = '/service-providers'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/volume/__init__.py b/tempest/lib/services/volume/__init__.py
index 11da06c..6855d8e 100644
--- a/tempest/lib/services/volume/__init__.py
+++ b/tempest/lib/services/volume/__init__.py
@@ -14,5 +14,6 @@
from tempest.lib.services.volume import v1
from tempest.lib.services.volume import v2
+from tempest.lib.services.volume import v3
-__all__ = ['v1', 'v2']
+__all__ = ['v1', 'v2', 'v3']
diff --git a/tempest/lib/services/volume/v1/__init__.py b/tempest/lib/services/volume/v1/__init__.py
index 9c98542..7b5991f 100644
--- a/tempest/lib/services/volume/v1/__init__.py
+++ b/tempest/lib/services/volume/v1/__init__.py
@@ -19,6 +19,7 @@
EncryptionTypesClient
from tempest.lib.services.volume.v1.extensions_client import ExtensionsClient
from tempest.lib.services.volume.v1.hosts_client import HostsClient
+from tempest.lib.services.volume.v1.limits_client import LimitsClient
from tempest.lib.services.volume.v1.qos_client import QosSpecsClient
from tempest.lib.services.volume.v1.quotas_client import QuotasClient
from tempest.lib.services.volume.v1.services_client import ServicesClient
@@ -28,4 +29,5 @@
__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
- 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient']
+ 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
+ 'LimitsClient']
diff --git a/tempest/lib/services/volume/v1/limits_client.py b/tempest/lib/services/volume/v1/limits_client.py
new file mode 100644
index 0000000..e14b2dc
--- /dev/null
+++ b/tempest/lib/services/volume/v1/limits_client.py
@@ -0,0 +1,32 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+ """Volume V1 limits client."""
+
+ api_version = "v1"
+
+ def show_limits(self):
+ """Returns the details of a volume absolute limits."""
+ url = "limits"
+ 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/lib/services/volume/v1/snapshots_client.py b/tempest/lib/services/volume/v1/snapshots_client.py
index 1881078..3433e68 100644
--- a/tempest/lib/services/volume/v1/snapshots_client.py
+++ b/tempest/lib/services/volume/v1/snapshots_client.py
@@ -25,8 +25,9 @@
def list_snapshots(self, detail=False, **params):
"""List all the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#listSnapshots
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#list-snapshots-with-details-v1
"""
url = 'snapshots'
if detail:
@@ -42,8 +43,9 @@
def show_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#showSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-details-v1
"""
url = "snapshots/%s" % snapshot_id
resp, body = self.get(url)
@@ -54,8 +56,9 @@
def create_snapshot(self, **kwargs):
"""Creates a new snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#createSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#create-snapshot-v1
"""
post_body = json.dumps({'snapshot': kwargs})
resp, body = self.post('snapshots', post_body)
@@ -66,8 +69,9 @@
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#deleteSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#delete-snapshot-v1
"""
resp, body = self.delete("snapshots/%s" % snapshot_id)
self.expected_success(202, resp.status)
@@ -117,9 +121,9 @@
def update_snapshot(self, snapshot_id, **kwargs):
"""Updates a snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#
- updateSnapshotMetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-v1
"""
put_body = json.dumps({'snapshot': kwargs})
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
@@ -130,9 +134,9 @@
def show_snapshot_metadata(self, snapshot_id):
"""Get metadata of the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#
- showSnapshotMetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#show-snapshot-metadata-v1
"""
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.get(url)
@@ -143,9 +147,9 @@
def update_snapshot_metadata(self, snapshot_id, **kwargs):
"""Update metadata for the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#
- updateSnapshotMetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#update-snapshot-metadata-v1
"""
put_body = json.dumps(kwargs)
url = "snapshots/%s/metadata" % snapshot_id
diff --git a/tempest/lib/services/volume/v1/types_client.py b/tempest/lib/services/volume/v1/types_client.py
index dce728d..4ae9935 100644
--- a/tempest/lib/services/volume/v1/types_client.py
+++ b/tempest/lib/services/volume/v1/types_client.py
@@ -38,8 +38,9 @@
def list_volume_types(self, **params):
"""List all the volume_types created.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#listVolumeTypes
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-types-v1
"""
url = 'types'
if params:
@@ -53,8 +54,9 @@
def show_volume_type(self, volume_type_id):
"""Returns the details of a single volume_type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#showVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#show-volume-type-v1
"""
url = "types/%s" % volume_type_id
resp, body = self.get(url)
@@ -65,8 +67,9 @@
def create_volume_type(self, **kwargs):
"""Create volume type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#createVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-type-v1
"""
post_body = json.dumps({'volume_type': kwargs})
resp, body = self.post('types', post_body)
@@ -77,8 +80,9 @@
def delete_volume_type(self, volume_type_id):
"""Deletes the Specified Volume_type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v1.html#deleteVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#delete-volume-type-v1
"""
resp, body = self.delete("types/%s" % volume_type_id)
self.expected_success(202, resp.status)
@@ -131,8 +135,9 @@
def update_volume_type(self, volume_type_id, **kwargs):
"""Updates volume type name, description, and/or is_public.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#update-volume-type-v1
"""
put_body = json.dumps({'volume_type': kwargs})
resp, body = self.put('types/%s' % volume_type_id, put_body)
@@ -148,9 +153,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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#update-extra-specs-for-a-volume-type-v1
"""
url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
put_body = json.dumps(extra_specs)
diff --git a/tempest/lib/services/volume/v1/volumes_client.py b/tempest/lib/services/volume/v1/volumes_client.py
index cc98c91..7a25697 100644
--- a/tempest/lib/services/volume/v1/volumes_client.py
+++ b/tempest/lib/services/volume/v1/volumes_client.py
@@ -61,8 +61,9 @@
def create_volume(self, **kwargs):
"""Creates a new Volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#create-volume
"""
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('volumes', post_body)
@@ -73,8 +74,9 @@
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#update-volume
"""
put_body = json.dumps({'volume': kwargs})
resp, body = self.put('volumes/%s' % volume_id, put_body)
@@ -100,8 +102,9 @@
def attach_volume(self, volume_id, **kwargs):
"""Attaches a volume to a given instance on a given mountpoint.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#attachVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#attach-volume
"""
post_body = json.dumps({'os-attach': kwargs})
url = 'volumes/%s/action' % (volume_id)
@@ -156,8 +159,9 @@
def extend_volume(self, volume_id, **kwargs):
"""Extend a volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#extendVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#extend-volume
"""
post_body = json.dumps({'os-extend': kwargs})
url = 'volumes/%s/action' % (volume_id)
@@ -168,8 +172,9 @@
def reset_volume_status(self, volume_id, **kwargs):
"""Reset the Specified Volume's Status.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#resetVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#reset-volume-status
"""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
@@ -179,8 +184,9 @@
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#create-volume-transfer
"""
post_body = json.dumps({'transfer': kwargs})
resp, body = self.post('os-volume-transfer', post_body)
@@ -199,8 +205,9 @@
def list_volume_transfers(self, **params):
"""List all the volume transfers created.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#listVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#list-volume-transfers
"""
url = 'os-volume-transfer'
if params:
@@ -219,8 +226,9 @@
def accept_volume_transfer(self, transfer_id, **kwargs):
"""Accept a volume transfer.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#acceptVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v1/#accept-volume-transfer
"""
url = 'os-volume-transfer/%s/accept' % transfer_id
post_body = json.dumps({'accept': kwargs})
diff --git a/tempest/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
index f547d7d..837b4f6 100644
--- a/tempest/lib/services/volume/v2/__init__.py
+++ b/tempest/lib/services/volume/v2/__init__.py
@@ -15,12 +15,17 @@
from tempest.lib.services.volume.v2.availability_zone_client \
import AvailabilityZoneClient
from tempest.lib.services.volume.v2.backups_client import BackupsClient
+from tempest.lib.services.volume.v2.capabilities_client import \
+ CapabilitiesClient
from tempest.lib.services.volume.v2.encryption_types_client import \
EncryptionTypesClient
from tempest.lib.services.volume.v2.extensions_client import ExtensionsClient
from tempest.lib.services.volume.v2.hosts_client import HostsClient
+from tempest.lib.services.volume.v2.limits_client import LimitsClient
from tempest.lib.services.volume.v2.qos_client import QosSpecsClient
from tempest.lib.services.volume.v2.quotas_client import QuotasClient
+from tempest.lib.services.volume.v2.scheduler_stats_client import \
+ SchedulerStatsClient
from tempest.lib.services.volume.v2.services_client import ServicesClient
from tempest.lib.services.volume.v2.snapshots_client import SnapshotsClient
from tempest.lib.services.volume.v2.types_client import TypesClient
@@ -28,4 +33,5 @@
__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
- 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient']
+ 'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
+ 'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient']
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index 61f865d..ab5eefd 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -26,8 +26,9 @@
def create_backup(self, **kwargs):
"""Creates a backup of volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createBackup
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref-blockstorage-v2.html#createBackup
"""
post_body = json.dumps({'backup': kwargs})
resp, body = self.post('backups', post_body)
@@ -38,8 +39,9 @@
def restore_backup(self, backup_id, **kwargs):
"""Restore volume from backup.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#restoreBackup
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref-blockstorage-v2.html#restoreBackup
"""
post_body = json.dumps({'restore': kwargs})
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
diff --git a/tempest/lib/services/volume/v2/capabilities_client.py b/tempest/lib/services/volume/v2/capabilities_client.py
new file mode 100644
index 0000000..b6de5b9
--- /dev/null
+++ b/tempest/lib/services/volume/v2/capabilities_client.py
@@ -0,0 +1,34 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class CapabilitiesClient(rest_client.RestClient):
+
+ def show_backend_capabilities(self, host):
+ """Shows capabilities for a storage back end.
+
+ Output params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html
+ #showBackendCapabilities
+ """
+ url = 'capabilities/%s' % host
+ resp, body = self.get(url)
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v2/limits_client.py b/tempest/lib/services/volume/v2/limits_client.py
new file mode 100644
index 0000000..ce9fba9
--- /dev/null
+++ b/tempest/lib/services/volume/v2/limits_client.py
@@ -0,0 +1,32 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class LimitsClient(rest_client.RestClient):
+ """Volume V2 limits client."""
+
+ api_version = "v2"
+
+ def show_limits(self):
+ """Returns the details of a volume absolute limits."""
+ url = "limits"
+ 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/lib/services/volume/v2/scheduler_stats_client.py b/tempest/lib/services/volume/v2/scheduler_stats_client.py
new file mode 100644
index 0000000..637254b
--- /dev/null
+++ b/tempest/lib/services/volume/v2/scheduler_stats_client.py
@@ -0,0 +1,35 @@
+# 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class SchedulerStatsClient(rest_client.RestClient):
+
+ def list_pools(self, detail=False):
+ """List all the volumes pools (hosts).
+
+ Output params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#listPools
+ """
+ url = 'scheduler-stats/get_pools'
+ if detail:
+ url += '?detail=True'
+ 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/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index c84e557..6f51b51 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -25,8 +25,9 @@
def list_snapshots(self, detail=False, **params):
"""List all the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#listSnapshots
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-snapshots-v2
"""
url = 'snapshots'
if detail:
@@ -42,8 +43,9 @@
def show_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#showSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-v2
"""
url = "snapshots/%s" % snapshot_id
resp, body = self.get(url)
@@ -54,8 +56,9 @@
def create_snapshot(self, **kwargs):
"""Creates a new snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-snapshot
"""
post_body = json.dumps({'snapshot': kwargs})
resp, body = self.post('snapshots', post_body)
@@ -66,8 +69,9 @@
def update_snapshot(self, snapshot_id, **kwargs):
"""Updates a snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-v2
"""
put_body = json.dumps({'snapshot': kwargs})
resp, body = self.put('snapshots/%s' % snapshot_id, put_body)
@@ -78,8 +82,9 @@
def delete_snapshot(self, snapshot_id):
"""Delete Snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#deleteSnapshot
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#delete-snapshot-v2
"""
resp, body = self.delete("snapshots/%s" % snapshot_id)
self.expected_success(202, resp.status)
@@ -129,9 +134,9 @@
def show_snapshot_metadata(self, snapshot_id):
"""Get metadata of the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#
- showSnapshotMetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#show-snapshot-metadata-v2
"""
url = "snapshots/%s/metadata" % snapshot_id
resp, body = self.get(url)
@@ -142,9 +147,9 @@
def update_snapshot_metadata(self, snapshot_id, **kwargs):
"""Update metadata for the snapshot.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#
- updateSnapshotMetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-snapshot-metadata-v2
"""
put_body = json.dumps(kwargs)
url = "snapshots/%s/metadata" % snapshot_id
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index d399e99..31597d7 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -39,8 +39,9 @@
def list_volume_types(self, **params):
"""List all the volume_types created.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#showVolumeTypes
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-types-v2
"""
url = 'types'
if params:
@@ -54,8 +55,9 @@
def show_volume_type(self, volume_type_id):
"""Returns the details of a single volume_type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#showVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-v2
"""
url = "types/%s" % volume_type_id
resp, body = self.get(url)
@@ -66,8 +68,9 @@
def create_volume_type(self, **kwargs):
"""Create volume type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-v2
"""
post_body = json.dumps({'volume_type': kwargs})
resp, body = self.post('types', post_body)
@@ -78,8 +81,9 @@
def delete_volume_type(self, volume_type_id):
"""Deletes the Specified Volume_type.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#deleteVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#delete-volume-type-v2
"""
resp, body = self.delete("types/%s" % volume_type_id)
self.expected_success(202, resp.status)
@@ -132,8 +136,9 @@
def update_volume_type(self, volume_type_id, **kwargs):
"""Updates volume type name, description, and/or is_public.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateVolumeType
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type-v2
"""
put_body = json.dumps({'volume_type': kwargs})
resp, body = self.put('types/%s' % volume_type_id, put_body)
@@ -149,9 +154,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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-type-extra-specs-v2
"""
url = "types/%s/extra_specs/%s" % (volume_type_id, extra_spec_name)
put_body = json.dumps(extra_specs)
@@ -163,9 +168,9 @@
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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#add-type-access-v2
"""
post_body = json.dumps({'addProjectAccess': kwargs})
url = 'types/%s/action' % volume_type_id
@@ -176,9 +181,9 @@
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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#remove-type-access-v2
"""
post_body = json.dumps({'removeProjectAccess': kwargs})
url = 'types/%s/action' % volume_type_id
@@ -189,9 +194,9 @@
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
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-type-access-v2
"""
url = 'types/%s/os-volume-type-access' % volume_type_id
resp, body = self.get(url)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index b1930e1..8b8e249 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from debtcollector import removals
from oslo_serialization import jsonutils as json
import six
from six.moves.urllib import parse as urllib
@@ -62,8 +63,9 @@
def create_volume(self, **kwargs):
"""Creates a new Volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-v2
"""
post_body = json.dumps({'volume': kwargs})
resp, body = self.post('volumes', post_body)
@@ -74,8 +76,9 @@
def update_volume(self, volume_id, **kwargs):
"""Updates the Specified Volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#updateVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-v2
"""
put_body = json.dumps({'volume': kwargs})
resp, body = self.put('volumes/%s' % volume_id, put_body)
@@ -101,8 +104,9 @@
def attach_volume(self, volume_id, **kwargs):
"""Attaches a volume to a given instance on a given mountpoint.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#attachVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#attach-volume-v2
"""
post_body = json.dumps({'os-attach': kwargs})
url = 'volumes/%s/action' % (volume_id)
@@ -157,8 +161,9 @@
def extend_volume(self, volume_id, **kwargs):
"""Extend a volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#extendVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#extend-volume-v2
"""
post_body = json.dumps({'os-extend': kwargs})
url = 'volumes/%s/action' % (volume_id)
@@ -169,8 +174,9 @@
def reset_volume_status(self, volume_id, **kwargs):
"""Reset the Specified Volume's Status.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#resetVolume
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#reset-volume-status-v2
"""
post_body = json.dumps({'os-reset_status': kwargs})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
@@ -180,8 +186,9 @@
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-transfer-v2
"""
post_body = json.dumps({'transfer': kwargs})
resp, body = self.post('os-volume-transfer', post_body)
@@ -200,8 +207,9 @@
def list_volume_transfers(self, **params):
"""List all the volume transfers created.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#listVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-transfers-v2
"""
url = 'os-volume-transfer'
if params:
@@ -220,8 +228,9 @@
def accept_volume_transfer(self, transfer_id, **kwargs):
"""Accept a volume transfer.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#acceptVolumeTransfer
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#accept-volume-transfer-v2
"""
url = 'os-volume-transfer/%s/accept' % transfer_id
post_body = json.dumps({'accept': kwargs})
@@ -296,9 +305,9 @@
def update_volume_image_metadata(self, volume_id, **kwargs):
"""Update image metadata for the volume.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html
- #setVolumeimagemetadata
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#update-volume-image-metadata-v2
"""
post_body = json.dumps({'os-set_image_metadata': {'metadata': kwargs}})
url = "volumes/%s/action" % (volume_id)
@@ -315,6 +324,8 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ @removals.remove(message="use show_pools from tempest.lib.services."
+ "volume.v2.scheduler_stats_client")
def show_pools(self, detail=False):
# List all the volumes pools (hosts)
url = 'scheduler-stats/get_pools'
@@ -326,12 +337,14 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ @removals.remove(message="use show_backend_capabilities from tempest.lib."
+ "services.volume.v2.capabilities_client")
def show_backend_capabilities(self, host):
"""Shows capabilities for a storage back end.
- Output params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html
- #showBackendCapabilities
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/#show_backend_capabilities-v2
"""
url = 'capabilities/%s' % host
resp, body = self.get(url)
diff --git a/tempest/services/volume/v3/__init__.py b/tempest/lib/services/volume/v3/__init__.py
similarity index 77%
rename from tempest/services/volume/v3/__init__.py
rename to tempest/lib/services/volume/v3/__init__.py
index d50098c..a4600a8 100644
--- a/tempest/services/volume/v3/__init__.py
+++ b/tempest/lib/services/volume/v3/__init__.py
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations under
# the License.
-from tempest.services.volume.v3.json.messages_client import MessagesClient
+from tempest.lib.services.volume.v3.base_client import BaseClient
+from tempest.lib.services.volume.v3.messages_client import MessagesClient
-__all__ = ['MessagesClient']
+__all__ = ['MessagesClient', 'BaseClient']
diff --git a/tempest/services/volume/base/base_v3_client.py b/tempest/lib/services/volume/v3/base_client.py
similarity index 90%
rename from tempest/services/volume/base/base_v3_client.py
rename to tempest/lib/services/volume/v3/base_client.py
index ad6f760..958212a 100644
--- a/tempest/services/volume/base/base_v3_client.py
+++ b/tempest/lib/services/volume/v3/base_client.py
@@ -19,13 +19,13 @@
VOLUME_MICROVERSION = None
-class BaseV3Client(rest_client.RestClient):
+class BaseClient(rest_client.RestClient):
"""Base class to handle Cinder v3 client microversion support."""
api_version = 'v3'
api_microversion_header_name = 'Openstack-Api-Version'
def get_headers(self, accept_type=None, send_type=None):
- headers = super(BaseV3Client, self).get_headers(
+ headers = super(BaseClient, self).get_headers(
accept_type=accept_type, send_type=send_type)
if VOLUME_MICROVERSION:
headers[self.api_microversion_header_name] = ('volume %s' %
@@ -35,7 +35,7 @@
def request(self, method, url, extra_headers=False, headers=None,
body=None, chunked=False):
- resp, resp_body = super(BaseV3Client, self).request(
+ resp, resp_body = super(BaseClient, self).request(
method, url, extra_headers, headers, body, chunked)
if (VOLUME_MICROVERSION and
VOLUME_MICROVERSION != api_version_utils.LATEST_MICROVERSION):
diff --git a/tempest/services/volume/v3/json/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
similarity index 94%
rename from tempest/services/volume/v3/json/messages_client.py
rename to tempest/lib/services/volume/v3/messages_client.py
index 6be6d59..8a01864 100644
--- a/tempest/services/volume/v3/json/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -17,10 +17,10 @@
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
-from tempest.services.volume.base import base_v3_client
+from tempest.lib.services.volume.v3 import base_client
-class MessagesClient(base_v3_client.BaseV3Client):
+class MessagesClient(base_client.BaseClient):
"""Client class to send user messages API requests."""
def show_message(self, message_id):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 8b86267..052f175 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -157,7 +157,7 @@
# Convert security group names to security group ids
# to pass to create_port
if 'security_groups' in kwargs:
- security_groups =\
+ security_groups = \
clients.security_groups_client.list_security_groups(
).get('security_groups')
sec_dict = dict([(s['name'], s['id'])
@@ -219,6 +219,10 @@
imageRef=None, volume_type=None):
if size is None:
size = CONF.volume.volume_size
+ if imageRef:
+ image = self.compute_images_client.show_image(imageRef)['image']
+ min_disk = image.get('minDisk')
+ size = max(size, min_disk)
if name is None:
name = data_utils.rand_name(self.__class__.__name__ + "-volume")
kwargs = {'display_name': name,
@@ -376,9 +380,9 @@
img_disk_format = CONF.scenario.img_disk_format
img_properties = CONF.scenario.img_properties
LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
- "properties: %s, ami: %s, ari: %s, aki: %s" %
- (img_path, img_container_format, img_disk_format,
- img_properties, ami_img_path, ari_img_path, aki_img_path))
+ "properties: %s, ami: %s, ari: %s, aki: %s",
+ img_path, img_container_format, img_disk_format,
+ img_properties, ami_img_path, ari_img_path, aki_img_path)
try:
image = self._image_create('scenario-img',
img_container_format,
@@ -393,7 +397,7 @@
image = self._image_create('scenario-ami', 'ami',
path=ami_img_path,
properties=properties)
- LOG.debug("image:%s" % image)
+ LOG.debug("image:%s", image)
return image
@@ -526,14 +530,14 @@
caller = test_utils.find_test_caller()
LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
- ' expected result is %(should_succeed)s' % {
+ ' expected result is %(should_succeed)s', **{
'caller': caller, 'ip': ip_address, 'timeout': timeout,
'should_succeed':
'reachable' if should_succeed else 'unreachable'
})
result = test_utils.call_until_true(ping, timeout, 1)
LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
- 'ping result is %(result)s' % {
+ 'ping result is %(result)s', **{
'caller': caller, 'ip': ip_address, 'timeout': timeout,
'result': 'expected' if result else 'unexpected'
})
@@ -574,8 +578,8 @@
msg=None, servers=None, mtu=None):
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
- LOG.debug('checking network connections to IP %s with user: %s' %
- (ip_address, username))
+ LOG.debug('checking network connections to IP %s with user: %s',
+ ip_address, username)
try:
self.check_vm_connectivity(ip_address,
username,
@@ -813,7 +817,7 @@
# servers. Neutron does not bind ports for Ironic instances, as a
# result the port remains in the DOWN state.
# TODO(vsaienko) remove once bug: #1599836 is resolved.
- if CONF.service_available.ironic:
+ if getattr(CONF.service_available, 'ironic', False):
p_status.append('DOWN')
port_map = [(p["id"], fxip["ip_address"])
for p in ports
@@ -944,7 +948,7 @@
source.ping_host(dest, nic=nic)
except lib_exc.SSHExecCommandFailed:
LOG.warning('Failed to ping IP: %s via a ssh connection '
- 'from: %s.' % (dest, source.ssh_client.host))
+ 'from: %s.', dest, source.ssh_client.host)
return not should_succeed
return should_succeed
@@ -1185,7 +1189,7 @@
:param dns_nameservers: list of dns servers to send to subnet.
:returns: network, subnet, router
"""
- if CONF.baremetal.driver_enabled:
+ if CONF.network.shared_physical_network:
# NOTE(Shrews): This exception is for environments where tenant
# credential isolation is available, but network separation is
# not (the current baremetal case). Likely can be removed when
@@ -1226,151 +1230,6 @@
return network, subnet, router
-# power/provision states as of icehouse
-class BaremetalPowerStates(object):
- """Possible power states of an Ironic node."""
- POWER_ON = 'power on'
- POWER_OFF = 'power off'
- REBOOT = 'rebooting'
- SUSPEND = 'suspended'
-
-
-class BaremetalProvisionStates(object):
- """Possible provision states of an Ironic node."""
- NOSTATE = None
- INIT = 'initializing'
- ACTIVE = 'active'
- BUILDING = 'building'
- DEPLOYWAIT = 'wait call-back'
- DEPLOYING = 'deploying'
- DEPLOYFAIL = 'deploy failed'
- DEPLOYDONE = 'deploy complete'
- DELETING = 'deleting'
- DELETED = 'deleted'
- ERROR = 'error'
-
-
-class BaremetalScenarioTest(ScenarioTest):
-
- credentials = ['primary', 'admin']
-
- @classmethod
- def skip_checks(cls):
- super(BaremetalScenarioTest, cls).skip_checks()
- if (not CONF.service_available.ironic or
- not CONF.baremetal.driver_enabled):
- msg = 'Ironic not available or Ironic compute driver not enabled'
- raise cls.skipException(msg)
-
- @classmethod
- def setup_clients(cls):
- super(BaremetalScenarioTest, cls).setup_clients()
-
- cls.baremetal_client = cls.admin_manager.baremetal_client
-
- @classmethod
- def resource_setup(cls):
- super(BaremetalScenarioTest, cls).resource_setup()
- # allow any issues obtaining the node list to raise early
- cls.baremetal_client.list_nodes()
-
- def _node_state_timeout(self, node_id, state_attr,
- target_states, timeout=10, interval=1):
- if not isinstance(target_states, list):
- target_states = [target_states]
-
- def check_state():
- node = self.get_node(node_id=node_id)
- if node.get(state_attr) in target_states:
- return True
- return False
-
- if not test_utils.call_until_true(
- check_state, timeout, interval):
- msg = ("Timed out waiting for node %s to reach %s state(s) %s" %
- (node_id, state_attr, target_states))
- raise lib_exc.TimeoutException(msg)
-
- def wait_provisioning_state(self, node_id, state, timeout):
- self._node_state_timeout(
- node_id=node_id, state_attr='provision_state',
- target_states=state, timeout=timeout)
-
- def wait_power_state(self, node_id, state):
- self._node_state_timeout(
- node_id=node_id, state_attr='power_state',
- target_states=state, timeout=CONF.baremetal.power_timeout)
-
- def wait_node(self, instance_id):
- """Waits for a node to be associated with instance_id."""
-
- def _get_node():
- node = test_utils.call_and_ignore_notfound_exc(
- self.get_node, instance_id=instance_id)
- return node is not None
-
- if not test_utils.call_until_true(
- _get_node, CONF.baremetal.association_timeout, 1):
- msg = ('Timed out waiting to get Ironic node by instance id %s'
- % instance_id)
- raise lib_exc.TimeoutException(msg)
-
- def get_node(self, node_id=None, instance_id=None):
- if node_id:
- _, body = self.baremetal_client.show_node(node_id)
- return body
- elif instance_id:
- _, body = self.baremetal_client.show_node_by_instance_uuid(
- instance_id)
- if body['nodes']:
- return body['nodes'][0]
-
- def get_ports(self, node_uuid):
- ports = []
- _, body = self.baremetal_client.list_node_ports(node_uuid)
- for port in body['ports']:
- _, p = self.baremetal_client.show_port(port['uuid'])
- ports.append(p)
- return ports
-
- def add_keypair(self):
- self.keypair = self.create_keypair()
-
- def boot_instance(self):
- self.instance = self.create_server(
- key_name=self.keypair['name'])
-
- self.wait_node(self.instance['id'])
- self.node = self.get_node(instance_id=self.instance['id'])
-
- self.wait_power_state(self.node['uuid'], BaremetalPowerStates.POWER_ON)
-
- self.wait_provisioning_state(
- self.node['uuid'],
- [BaremetalProvisionStates.DEPLOYWAIT,
- BaremetalProvisionStates.ACTIVE],
- timeout=15)
-
- self.wait_provisioning_state(self.node['uuid'],
- BaremetalProvisionStates.ACTIVE,
- timeout=CONF.baremetal.active_timeout)
-
- waiters.wait_for_server_status(self.servers_client,
- self.instance['id'], 'ACTIVE')
- self.node = self.get_node(instance_id=self.instance['id'])
- self.instance = (self.servers_client.show_server(self.instance['id'])
- ['server'])
-
- def terminate_instance(self):
- self.servers_client.delete_server(self.instance['id'])
- self.wait_power_state(self.node['uuid'],
- BaremetalPowerStates.POWER_OFF)
- self.wait_provisioning_state(
- self.node['uuid'],
- BaremetalProvisionStates.NOSTATE,
- timeout=CONF.baremetal.unprovision_timeout)
-
-
class EncryptionScenarioTest(ScenarioTest):
"""Base class for encryption scenario tests"""
@@ -1456,7 +1315,7 @@
self.container_client.create_container(name)
# look for the container to assure it is created
self.list_and_check_container_objects(name)
- LOG.debug('Container %s created' % (name))
+ LOG.debug('Container %s created', name)
self.addCleanup(test_utils.call_and_ignore_notfound_exc,
self.container_client.delete_container,
name)
@@ -1464,7 +1323,7 @@
def delete_container(self, container_name):
self.container_client.delete_container(container_name)
- LOG.debug('Container %s deleted' % (container_name))
+ LOG.debug('Container %s deleted', container_name)
def upload_object_to_container(self, container_name, obj_name=None):
obj_name = obj_name or data_utils.rand_name('swift-scenario-object')
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
deleted file mode 100644
index 45c38f6..0000000
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_log import log as logging
-
-from tempest.scenario import manager
-from tempest import test
-
-LOG = logging.getLogger(__name__)
-
-
-class BaremetalBasicOps(manager.BaremetalScenarioTest):
- """This test tests the pxe_ssh Ironic driver.
-
- It follows this basic set of operations:
- * Creates a keypair
- * Boots an instance using the keypair
- * Monitors the associated Ironic node for power and
- expected state transitions
- * Validates Ironic node's port data has been properly updated
- * Verifies SSH connectivity using created keypair via fixed IP
- * Associates a floating ip
- * Verifies SSH connectivity using created keypair via floating IP
- * Deletes instance
- * Monitors the associated Ironic node for power and
- expected state transitions
- """
- def verify_partition(self, client, label, mount, gib_size):
- """Verify a labeled partition's mount point and size."""
- LOG.info("Looking for partition %s mounted on %s" % (label, mount))
-
- # Validate we have a device with the given partition label
- cmd = "/sbin/blkid | grep '%s' | cut -d':' -f1" % label
- device = client.exec_command(cmd).rstrip('\n')
- LOG.debug("Partition device is %s" % device)
- self.assertNotEqual('', device)
-
- # Validate the mount point for the device
- cmd = "mount | grep '%s' | cut -d' ' -f3" % device
- actual_mount = client.exec_command(cmd).rstrip('\n')
- LOG.debug("Partition mount point is %s" % actual_mount)
- self.assertEqual(actual_mount, mount)
-
- # Validate the partition size matches what we expect
- numbers = '0123456789'
- devnum = device.replace('/dev/', '')
- cmd = "cat /sys/block/%s/%s/size" % (devnum.rstrip(numbers), devnum)
- num_bytes = client.exec_command(cmd).rstrip('\n')
- num_bytes = int(num_bytes) * 512
- actual_gib_size = num_bytes / (1024 * 1024 * 1024)
- LOG.debug("Partition size is %d GiB" % actual_gib_size)
- self.assertEqual(actual_gib_size, gib_size)
-
- def get_flavor_ephemeral_size(self):
- """Returns size of the ephemeral partition in GiB."""
- f_id = self.instance['flavor']['id']
- flavor = self.flavors_client.show_flavor(f_id)['flavor']
- ephemeral = flavor.get('OS-FLV-EXT-DATA:ephemeral')
- if not ephemeral or ephemeral == 'N/A':
- return None
- return int(ephemeral)
-
- def validate_ports(self):
- for port in self.get_ports(self.node['uuid']):
- n_port_id = port['extra']['vif_port_id']
- body = self.ports_client.show_port(n_port_id)
- n_port = body['port']
- self.assertEqual(n_port['device_id'], self.instance['id'])
- self.assertEqual(n_port['mac_address'], port['address'])
-
- @test.idempotent_id('549173a5-38ec-42bb-b0e2-c8b9f4a08943')
- @test.services('baremetal', 'compute', 'image', 'network')
- def test_baremetal_server_ops(self):
- self.add_keypair()
- self.boot_instance()
- self.validate_ports()
- ip_address = self.get_server_ip(self.instance)
- self.get_remote_client(ip_address).validate_authentication()
- vm_client = self.get_remote_client(ip_address)
-
- # We expect the ephemeral partition to be mounted on /mnt and to have
- # the same size as our flavor definition.
- eph_size = self.get_flavor_ephemeral_size()
- if eph_size:
- self.verify_partition(vm_client, 'ephemeral0', '/mnt', eph_size)
- # Create the test file
- self.create_timestamp(
- ip_address, private_key=self.keypair['private_key'])
-
- self.terminate_instance()
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 3ac6759..c454ae2 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -16,8 +16,8 @@
from tempest.common import custom_matchers
from tempest.common import waiters
from tempest import config
-from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
from tempest.scenario import manager
from tempest import test
@@ -66,10 +66,10 @@
waiters.wait_for_server_status(self.servers_client,
server['id'], 'ACTIVE')
- def check_partitions(self):
+ def check_disks(self):
# NOTE(andreaf) The device name may be different on different guest OS
- partitions = self.linux_client.get_partitions()
- self.assertEqual(1, partitions.count(CONF.compute.volume_device_name))
+ disks = self.linux_client.get_disks()
+ self.assertEqual(1, disks.count(CONF.compute.volume_device_name))
def create_and_add_security_group_to_server(self, server):
secgroup = self._create_security_group()
@@ -145,7 +145,7 @@
self.linux_client = self.get_remote_client(
floating_ip['ip'], private_key=keypair['private_key'])
- self.check_partitions()
+ self.check_disks()
# delete the floating IP, this should refresh the server addresses
self.compute_floating_ips_client.delete_floating_ip(floating_ip['id'])
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 0605902..1279484 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -198,12 +198,11 @@
@test.idempotent_id('a4858f6c-401e-4155-9a49-d5cd053d1a2f')
@testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
'Cold migration is not available.')
+ @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+ 'Less than 2 compute nodes, skipping multinode '
+ 'tests.')
@test.services('compute', 'network')
def test_server_connectivity_cold_migration(self):
- if CONF.compute.min_compute_nodes < 2:
- msg = "Less than 2 compute nodes, skipping multinode tests."
- raise self.skipException(msg)
-
keypair = self.create_keypair()
server = self._setup_server(keypair)
floating_ip = self._setup_network(server, keypair)
@@ -220,3 +219,28 @@
dst_host = self._get_host_for_server(server['id'])
self.assertNotEqual(src_host, dst_host)
+
+ @test.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
+ @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+ 'Cold migration is not available.')
+ @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+ 'Less than 2 compute nodes, skipping multinode '
+ 'tests.')
+ @test.services('compute', 'network')
+ def test_server_connectivity_cold_migration_revert(self):
+ keypair = self.create_keypair()
+ server = self._setup_server(keypair)
+ floating_ip = self._setup_network(server, keypair)
+ src_host = self._get_host_for_server(server['id'])
+ self._wait_server_status_and_check_network_connectivity(
+ server, keypair, floating_ip)
+
+ self.admin_servers_client.migrate_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
+ 'VERIFY_RESIZE')
+ self.servers_client.revert_resize_server(server['id'])
+ self._wait_server_status_and_check_network_connectivity(
+ server, keypair, floating_ip)
+ dst_host = self._get_host_for_server(server['id'])
+
+ self.assertEqual(src_host, dst_host)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index b0a3207..f9aa3e7 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -417,8 +417,9 @@
should_connect=True, mtu=self.network['mtu'])
@test.idempotent_id('1546850e-fbaa-42f5-8b5f-03d8a6a95f15')
- @testtools.skipIf(CONF.baremetal.driver_enabled,
- 'Baremetal relies on a shared physical network.')
+ @testtools.skipIf(CONF.network.shared_physical_network,
+ 'Connectivity can only be tested when in a '
+ 'multitenant network environment')
@decorators.skip_because(bug="1610994")
@test.services('compute', 'network')
def test_connectivity_between_vms_on_different_networks(self):
@@ -492,9 +493,9 @@
self._check_network_internal_connectivity(network=self.new_net)
@test.idempotent_id('04b9fe4e-85e8-4aea-b937-ea93885ac59f')
- @testtools.skipIf(CONF.baremetal.driver_enabled,
- 'Router state cannot be altered on a shared baremetal '
- 'network')
+ @testtools.skipIf(CONF.network.shared_physical_network,
+ 'Router state can be altered only with multitenant '
+ 'networks capabilities')
@test.services('compute', 'network')
def test_update_router_admin_state(self):
"""Test to update admin state up of router
@@ -524,8 +525,8 @@
"admin_state_up of router to True")
@test.idempotent_id('d8bb918e-e2df-48b2-97cd-b73c95450980')
- @testtools.skipIf(CONF.baremetal.driver_enabled,
- 'network isolation not available for baremetal nodes')
+ @testtools.skipIf(CONF.network.shared_physical_network,
+ 'network isolation not available')
@testtools.skipUnless(CONF.scenario.dhcp_client,
"DHCP client is not available.")
@test.services('compute', 'network')
@@ -607,9 +608,6 @@
"new DNS nameservers")
@test.idempotent_id('f5dfcc22-45fd-409f-954c-5bd500d7890b')
- @testtools.skipIf(CONF.baremetal.driver_enabled,
- 'admin_state of instance ports cannot be altered '
- 'for baremetal nodes')
@testtools.skipUnless(CONF.network_feature_enabled.port_admin_state_change,
"Changing a port's admin state is not supported "
"by the test environment")
@@ -763,7 +761,6 @@
self.check_public_network_connectivity(
should_connect=False,
msg='after router unscheduling',
- should_check_floating_ip_status=False
)
# schedule resource to new agent
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 6700236..40b3317 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -50,8 +50,8 @@
msg = ('Either project_networks_reachable must be "true", or '
'public_network_id must be defined.')
raise cls.skipException(msg)
- if CONF.baremetal.driver_enabled:
- msg = ('Baremetal does not currently support network isolation')
+ if CONF.network.shared_physical_network:
+ msg = 'Deployment uses a shared physical network'
raise cls.skipException(msg)
@classmethod
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 32f5d9f..f8c5c0a 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -15,7 +15,6 @@
from oslo_log import log
import testtools
-from tempest import clients
from tempest.common.utils import data_utils
from tempest.common.utils import net_info
from tempest import config
@@ -113,9 +112,9 @@
access point
"""
- def __init__(self, credentials):
- self.manager = clients.Manager(credentials)
+ def __init__(self, clients):
# Credentials from manager are filled with both names and IDs
+ self.manager = clients
self.creds = self.manager.credentials
self.network = None
self.subnet = None
@@ -131,9 +130,6 @@
@classmethod
def skip_checks(cls):
super(TestSecurityGroupsBasicOps, cls).skip_checks()
- if CONF.baremetal.driver_enabled:
- msg = ('Not currently supported by baremetal.')
- raise cls.skipException(msg)
if CONF.network.port_vnic_type in ['direct', 'macvtap']:
msg = ('Not currently supported when using vnic_type'
' direct or macvtap')
@@ -146,17 +142,16 @@
if not test.is_extension_enabled('security-group', 'network'):
msg = "security-group extension not enabled."
raise cls.skipException(msg)
+ if CONF.network.shared_physical_network:
+ msg = ('Deployment uses a shared physical network, security '
+ 'groups not supported')
+ raise cls.skipException(msg)
@classmethod
def setup_credentials(cls):
# Create no network resources for these tests.
cls.set_network_resources()
super(TestSecurityGroupsBasicOps, cls).setup_credentials()
- # TODO(mnewby) Consider looking up entities as needed instead
- # of storing them as collections on the class.
-
- # Credentials from the manager are filled with both IDs and Names
- cls.alt_creds = cls.alt_manager.credentials
@classmethod
def resource_setup(cls):
@@ -171,9 +166,8 @@
cls.floating_ips = {}
cls.tenants = {}
- creds = cls.manager.credentials
- cls.primary_tenant = cls.TenantProperties(creds)
- cls.alt_tenant = cls.TenantProperties(cls.alt_creds)
+ cls.primary_tenant = cls.TenantProperties(cls.os)
+ cls.alt_tenant = cls.TenantProperties(cls.os_alt)
for tenant in [cls.primary_tenant, cls.alt_tenant]:
cls.tenants[tenant.creds.tenant_id] = tenant
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index c66128d..2d2f7df 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -16,9 +16,10 @@
import json
import re
+from tempest.common import waiters
from tempest import config
-from tempest import exceptions
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
from tempest.scenario import manager
from tempest import test
@@ -134,3 +135,5 @@
self.verify_metadata_on_config_drive()
self.verify_networkdata_on_config_drive()
self.servers_client.delete_server(self.instance['id'])
+ waiters.wait_for_server_termination(
+ self.servers_client, self.instance['id'], ignore_error=False)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 0f2c78c..b10be11 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -85,9 +85,9 @@
ssh = self.get_remote_client(ip_address, private_key=private_key)
def _func():
- part = ssh.get_partitions()
- LOG.debug("Partitions:%s" % part)
- return CONF.compute.volume_device_name in part
+ disks = ssh.get_disks()
+ LOG.debug("Disks: %s", disks)
+ return CONF.compute.volume_device_name in disks
if not test_utils.call_until_true(_func,
CONF.compute.build_timeout,
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index db5e009..2c8b618 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -118,43 +118,46 @@
volume_origin = self._create_volume_from_image()
instance_1st = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
- LOG.info("Booted first instance: %s" % instance_1st)
+ LOG.info("Booted first instance: %s", instance_1st)
# write content to volume on instance
- LOG.info("Setting timestamp in instance %s" % instance_1st)
+ LOG.info("Setting timestamp in instance %s", instance_1st)
ip_instance_1st = self.get_server_ip(instance_1st)
timestamp = self.create_timestamp(ip_instance_1st,
private_key=keypair['private_key'])
# delete instance
- LOG.info("Deleting first instance: %s" % instance_1st)
+ LOG.info("Deleting first instance: %s", instance_1st)
self._delete_server(instance_1st)
# create a 2nd instance from volume
instance_2nd = self._boot_instance_from_volume(volume_origin['id'],
keypair, security_group)
- LOG.info("Booted second instance %s" % instance_2nd)
+ LOG.info("Booted second instance %s", instance_2nd)
# check the content of written file
- LOG.info("Getting timestamp in instance %s" % instance_2nd)
+ LOG.info("Getting timestamp in instance %s", instance_2nd)
ip_instance_2nd = self.get_server_ip(instance_2nd)
timestamp2 = self.get_timestamp(ip_instance_2nd,
private_key=keypair['private_key'])
self.assertEqual(timestamp, timestamp2)
# snapshot a volume
- LOG.info("Creating snapshot from volume: %s" % volume_origin['id'])
+ LOG.info("Creating snapshot from volume: %s", volume_origin['id'])
snapshot = self._create_snapshot_from_volume(volume_origin['id'])
# create a 3rd instance from snapshot
- LOG.info("Creating third instance from snapshot: %s" % snapshot['id'])
- volume = self.create_volume(snapshot_id=snapshot['id'])
+ LOG.info("Creating third instance from snapshot: %s", snapshot['id'])
+ volume = self.create_volume(snapshot_id=snapshot['id'],
+ size=snapshot['size'])
+ LOG.info("Booting third instance from snapshot")
server_from_snapshot = (
self._boot_instance_from_volume(volume['id'],
keypair, security_group))
+ LOG.info("Booted third instance %s", server_from_snapshot)
# check the content of written file
- LOG.info("Logging into third instance to get timestamp: %s" %
+ LOG.info("Logging into third instance to get timestamp: %s",
server_from_snapshot)
server_from_snapshot_ip = self.get_server_ip(server_from_snapshot)
timestamp3 = self.get_timestamp(server_from_snapshot_ip,
diff --git a/tempest/services/baremetal/__init__.py b/tempest/services/baremetal/__init__.py
deleted file mode 100644
index 390f40a..0000000
--- a/tempest/services/baremetal/__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.services.baremetal.v1.json.baremetal_client import \
- BaremetalClient
-
-__all__ = ['BaremetalClient']
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
deleted file mode 100644
index 2bdd092..0000000
--- a/tempest/services/baremetal/base.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import 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
-
-
-def handle_errors(f):
- """A decorator that allows to ignore certain types of errors."""
-
- @functools.wraps(f)
- def wrapper(*args, **kwargs):
- param_name = 'ignore_errors'
- ignored_errors = kwargs.get(param_name, tuple())
-
- if param_name in kwargs:
- del kwargs[param_name]
-
- try:
- return f(*args, **kwargs)
- except ignored_errors:
- # Silently ignore errors
- pass
-
- return wrapper
-
-
-class BaremetalClient(rest_client.RestClient):
- """Base Tempest REST client for Ironic API."""
-
- uri_prefix = ''
-
- def serialize(self, object_dict):
- """Serialize an Ironic object."""
-
- return json.dumps(object_dict)
-
- def deserialize(self, object_str):
- """Deserialize an Ironic object."""
-
- return json.loads(object_str)
-
- def _get_uri(self, resource_name, uuid=None, permanent=False):
- """Get URI for a specific resource or object.
-
- :param resource_name: The name of the REST resource, e.g., 'nodes'.
- :param uuid: The unique identifier of an object in UUID format.
- :returns: Relative URI for the resource or object.
-
- """
- prefix = self.uri_prefix if not permanent else ''
-
- return '{pref}/{res}{uuid}'.format(pref=prefix,
- res=resource_name,
- uuid='/%s' % uuid if uuid else '')
-
- def _make_patch(self, allowed_attributes, **kwargs):
- """Create a JSON patch according to RFC 6902.
-
- :param allowed_attributes: An iterable object that contains a set of
- allowed attributes for an object.
- :param **kwargs: Attributes and new values for them.
- :returns: A JSON path that sets values of the specified attributes to
- the new ones.
-
- """
- def get_change(kwargs, path='/'):
- for name, value in six.iteritems(kwargs):
- if isinstance(value, dict):
- for ch in get_change(value, path + '%s/' % name):
- yield ch
- else:
- if value is None:
- yield {'path': path + name,
- 'op': 'remove'}
- else:
- yield {'path': path + name,
- 'value': value,
- 'op': 'replace'}
-
- patch = [ch for ch in get_change(kwargs)
- if ch['path'].lstrip('/') in allowed_attributes]
-
- return patch
-
- def _list_request(self, resource, permanent=False, **kwargs):
- """Get the list of objects of the specified type.
-
- :param resource: The name of the REST resource, e.g., 'nodes'.
- :param **kwargs: Parameters for the request.
- :returns: A tuple with the server response and deserialized JSON list
- of objects
-
- """
- uri = self._get_uri(resource, permanent=permanent)
- if kwargs:
- uri += "?%s" % urllib.urlencode(kwargs)
-
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
-
- return resp, self.deserialize(body)
-
- def _show_request(self, resource, uuid, permanent=False, **kwargs):
- """Gets a specific object of the specified type.
-
- :param uuid: Unique identifier of the object in UUID format.
- :returns: Serialized object as a dictionary.
-
- """
- if 'uri' in kwargs:
- uri = kwargs['uri']
- else:
- uri = self._get_uri(resource, uuid=uuid, permanent=permanent)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
-
- return resp, self.deserialize(body)
-
- def _create_request(self, resource, object_dict):
- """Create an object of the specified type.
-
- :param resource: The name of the REST resource, e.g., 'nodes'.
- :param object_dict: A Python dict that represents an object of the
- specified type.
- :returns: A tuple with the server response and the deserialized created
- object.
-
- """
- body = self.serialize(object_dict)
- uri = self._get_uri(resource)
-
- resp, body = self.post(uri, body=body)
- self.expected_success(201, resp.status)
-
- return resp, self.deserialize(body)
-
- def _delete_request(self, resource, uuid):
- """Delete specified object.
-
- :param resource: The name of the REST resource, e.g., 'nodes'.
- :param uuid: The unique identifier of an object in UUID format.
- :returns: A tuple with the server response and the response body.
-
- """
- uri = self._get_uri(resource, uuid)
-
- resp, body = self.delete(uri)
- self.expected_success(204, resp.status)
- return resp, body
-
- def _patch_request(self, resource, uuid, patch_object):
- """Update specified object with JSON-patch.
-
- :param resource: The name of the REST resource, e.g., 'nodes'.
- :param uuid: The unique identifier of an object in UUID format.
- :returns: A tuple with the server response and the serialized patched
- object.
-
- """
- uri = self._get_uri(resource, uuid)
- patch_body = json.dumps(patch_object)
-
- resp, body = self.patch(uri, body=patch_body)
- self.expected_success(200, resp.status)
- return resp, self.deserialize(body)
-
- @handle_errors
- def get_api_description(self):
- """Retrieves all versions of the Ironic API."""
-
- return self._list_request('', permanent=True)
-
- @handle_errors
- def get_version_description(self, version='v1'):
- """Retrieves the desctription of the API.
-
- :param version: The version of the API. Default: 'v1'.
- :returns: Serialized description of API resources.
-
- """
- return self._list_request(version, permanent=True)
-
- def _put_request(self, resource, put_object):
- """Update specified object with JSON-patch."""
- uri = self._get_uri(resource)
- put_body = json.dumps(put_object)
-
- resp, body = self.put(uri, body=put_body)
- self.expected_success([202, 204], resp.status)
- return resp, body
diff --git a/tempest/services/baremetal/v1/__init__.py b/tempest/services/baremetal/v1/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/baremetal/v1/__init__.py
+++ /dev/null
diff --git a/tempest/services/baremetal/v1/json/__init__.py b/tempest/services/baremetal/v1/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/baremetal/v1/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/baremetal/v1/json/baremetal_client.py b/tempest/services/baremetal/v1/json/baremetal_client.py
deleted file mode 100644
index 7405871..0000000
--- a/tempest/services/baremetal/v1/json/baremetal_client.py
+++ /dev/null
@@ -1,362 +0,0 @@
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.baremetal import base
-
-
-class BaremetalClient(base.BaremetalClient):
- """Base Tempest REST client for Ironic API v1."""
- version = '1'
- uri_prefix = 'v1'
-
- @base.handle_errors
- def list_nodes(self, **kwargs):
- """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
- def list_chassis(self):
- """List all existing chassis."""
- return self._list_request('chassis')
-
- @base.handle_errors
- def list_chassis_nodes(self, chassis_uuid):
- """List all nodes associated with a chassis."""
- return self._list_request('/chassis/%s/nodes' % chassis_uuid)
-
- @base.handle_errors
- def list_ports(self, **kwargs):
- """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
- def list_node_ports(self, uuid):
- """List all ports associated with the node."""
- return self._list_request('/nodes/%s/ports' % uuid)
-
- @base.handle_errors
- def list_nodestates(self, uuid):
- """List all existing states."""
- return self._list_request('/nodes/%s/states' % uuid)
-
- @base.handle_errors
- def list_ports_detail(self, **kwargs):
- """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
- def list_drivers(self):
- """List all existing drivers."""
- return self._list_request('drivers')
-
- @base.handle_errors
- def show_node(self, uuid):
- """Gets a specific node.
-
- :param uuid: Unique identifier of the node in UUID format.
- :return: Serialized node as a dictionary.
-
- """
- return self._show_request('nodes', uuid)
-
- @base.handle_errors
- def show_node_by_instance_uuid(self, instance_uuid):
- """Gets a node associated with given instance uuid.
-
- :param instance_uuid: Unique identifier of the instance in UUID format.
- :return: Serialized node as a dictionary.
-
- """
- uri = '/nodes/detail?instance_uuid=%s' % instance_uuid
-
- return self._show_request('nodes',
- uuid=None,
- uri=uri)
-
- @base.handle_errors
- def show_chassis(self, uuid):
- """Gets a specific chassis.
-
- :param uuid: Unique identifier of the chassis in UUID format.
- :return: Serialized chassis as a dictionary.
-
- """
- return self._show_request('chassis', uuid)
-
- @base.handle_errors
- def show_port(self, uuid):
- """Gets a specific port.
-
- :param uuid: Unique identifier of the port in UUID format.
- :return: Serialized port as a dictionary.
-
- """
- return self._show_request('ports', uuid)
-
- @base.handle_errors
- def show_port_by_address(self, address):
- """Gets a specific port by address.
-
- :param address: MAC address of the port.
- :return: Serialized port as a dictionary.
-
- """
- uri = '/ports/detail?address=%s' % address
-
- return self._show_request('ports', uuid=None, uri=uri)
-
- def show_driver(self, driver_name):
- """Gets a specific driver.
-
- :param driver_name: Name of driver.
- :return: Serialized driver as a dictionary.
- """
- return self._show_request('drivers', driver_name)
-
- @base.handle_errors
- def create_node(self, chassis_id=None, **kwargs):
- """Create a baremetal node with the specified parameters.
-
- :param chassis_id: The unique identifier of the chassis.
- :param cpu_arch: CPU architecture of the node. Default: x86_64.
- :param cpus: Number of CPUs. Default: 8.
- :param local_gb: Disk size. Default: 1024.
- :param memory_mb: Available RAM. Default: 4096.
- :param driver: Driver name. Default: "fake"
- :return: A tuple with the server response and the created node.
-
- """
- node = {'chassis_uuid': chassis_id,
- 'properties': {'cpu_arch': kwargs.get('cpu_arch', 'x86_64'),
- 'cpus': kwargs.get('cpus', 8),
- 'local_gb': kwargs.get('local_gb', 1024),
- 'memory_mb': kwargs.get('memory_mb', 4096)},
- 'driver': kwargs.get('driver', 'fake')}
-
- return self._create_request('nodes', node)
-
- @base.handle_errors
- def create_chassis(self, **kwargs):
- """Create a chassis with the specified parameters.
-
- :param description: The description of the chassis.
- Default: test-chassis
- :return: A tuple with the server response and the created chassis.
-
- """
- chassis = {'description': kwargs.get('description', 'test-chassis')}
-
- return self._create_request('chassis', chassis)
-
- @base.handle_errors
- def create_port(self, node_id, **kwargs):
- """Create a port with the specified parameters.
-
- :param node_id: The ID of the node which owns the port.
- :param address: MAC address of the port.
- :param extra: Meta data of the port. Default: {'foo': 'bar'}.
- :param uuid: UUID of the port.
- :return: A tuple with the server response and the created port.
-
- """
- port = {'extra': kwargs.get('extra', {'foo': 'bar'}),
- 'uuid': kwargs['uuid']}
-
- if node_id is not None:
- port['node_uuid'] = node_id
-
- if kwargs['address'] is not None:
- port['address'] = kwargs['address']
-
- return self._create_request('ports', port)
-
- @base.handle_errors
- def delete_node(self, uuid):
- """Deletes a node having the specified UUID.
-
- :param uuid: The unique identifier of the node.
- :return: A tuple with the server response and the response body.
-
- """
- return self._delete_request('nodes', uuid)
-
- @base.handle_errors
- def delete_chassis(self, uuid):
- """Deletes a chassis having the specified UUID.
-
- :param uuid: The unique identifier of the chassis.
- :return: A tuple with the server response and the response body.
-
- """
- return self._delete_request('chassis', uuid)
-
- @base.handle_errors
- def delete_port(self, uuid):
- """Deletes a port having the specified UUID.
-
- :param uuid: The unique identifier of the port.
- :return: A tuple with the server response and the response body.
-
- """
- return self._delete_request('ports', uuid)
-
- @base.handle_errors
- def update_node(self, uuid, **kwargs):
- """Update the specified node.
-
- :param uuid: The unique identifier of the node.
- :return: A tuple with the server response and the updated node.
-
- """
- node_attributes = ('properties/cpu_arch',
- 'properties/cpus',
- 'properties/local_gb',
- 'properties/memory_mb',
- 'driver',
- 'instance_uuid')
-
- patch = self._make_patch(node_attributes, **kwargs)
-
- return self._patch_request('nodes', uuid, patch)
-
- @base.handle_errors
- def update_chassis(self, uuid, **kwargs):
- """Update the specified chassis.
-
- :param uuid: The unique identifier of the chassis.
- :return: A tuple with the server response and the updated chassis.
-
- """
- chassis_attributes = ('description',)
- patch = self._make_patch(chassis_attributes, **kwargs)
-
- return self._patch_request('chassis', uuid, patch)
-
- @base.handle_errors
- def update_port(self, uuid, patch):
- """Update the specified port.
-
- :param uuid: The unique identifier of the port.
- :param patch: List of dicts representing json patches.
- :return: A tuple with the server response and the updated port.
-
- """
-
- return self._patch_request('ports', uuid, patch)
-
- @base.handle_errors
- def set_node_power_state(self, node_uuid, state):
- """Set power state of the specified node.
-
- :param node_uuid: The unique identifier of the node.
- :param state: desired state to set (on/off/reboot).
-
- """
- target = {'target': state}
- return self._put_request('nodes/%s/states/power' % node_uuid,
- target)
-
- @base.handle_errors
- def validate_driver_interface(self, node_uuid):
- """Get all driver interfaces of a specific node.
-
- :param node_uuid: Unique identifier of the node in UUID format.
-
- """
-
- uri = '{pref}/{res}/{uuid}/{postf}'.format(pref=self.uri_prefix,
- res='nodes',
- uuid=node_uuid,
- postf='validate')
-
- return self._show_request('nodes', node_uuid, uri=uri)
-
- @base.handle_errors
- def set_node_boot_device(self, node_uuid, boot_device, persistent=False):
- """Set the boot device of the specified node.
-
- :param node_uuid: The unique identifier of the node.
- :param boot_device: The boot device name.
- :param persistent: Boolean value. True if the boot device will
- persist to all future boots, False if not.
- Default: False.
-
- """
- request = {'boot_device': boot_device, 'persistent': persistent}
- resp, body = self._put_request('nodes/%s/management/boot_device' %
- node_uuid, request)
- self.expected_success(204, resp.status)
- return body
-
- @base.handle_errors
- def get_node_boot_device(self, node_uuid):
- """Get the current boot device of the specified node.
-
- :param node_uuid: The unique identifier of the node.
-
- """
- path = 'nodes/%s/management/boot_device' % node_uuid
- resp, body = self._list_request(path)
- self.expected_success(200, resp.status)
- return body
-
- @base.handle_errors
- def get_node_supported_boot_devices(self, node_uuid):
- """Get the supported boot devices of the specified node.
-
- :param node_uuid: The unique identifier of the node.
-
- """
- path = 'nodes/%s/management/boot_device/supported' % node_uuid
- resp, body = self._list_request(path)
- self.expected_success(200, resp.status)
- return body
-
- @base.handle_errors
- def get_console(self, node_uuid):
- """Get connection information about the console.
-
- :param node_uuid: Unique identifier of the node in UUID format.
-
- """
-
- resp, body = self._show_request('nodes/states/console', node_uuid)
- self.expected_success(200, resp.status)
- return resp, body
-
- @base.handle_errors
- def set_console_mode(self, node_uuid, enabled):
- """Start and stop the node console.
-
- :param node_uuid: Unique identifier of the node in UUID format.
- :param enabled: Boolean value; whether to enable or disable the
- console.
-
- """
-
- enabled = {'enabled': enabled}
- resp, body = self._put_request('nodes/%s/states/console' % node_uuid,
- enabled)
- self.expected_success(202, resp.status)
- return resp, body
diff --git a/tempest/services/identity/__init__.py b/tempest/services/identity/__init__.py
deleted file mode 100644
index 53c223f..0000000
--- a/tempest/services/identity/__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.identity import v2
-from tempest.services.identity import v3
-
-__all__ = ['v2', 'v3']
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
deleted file mode 100644
index 9b40b77..0000000
--- a/tempest/services/identity/v3/__init__.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may not
-# use this file except in compliance with the License. You may obtain a copy of
-# the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations under
-# the License.
-
-from tempest.lib.services.identity.v3.credentials_client import \
- CredentialsClient
-from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
-from tempest.lib.services.identity.v3.groups_client import GroupsClient
-from tempest.lib.services.identity.v3.identity_client import IdentityClient
-from tempest.lib.services.identity.v3.inherited_roles_client import \
- InheritedRolesClient
-from tempest.lib.services.identity.v3.policies_client import PoliciesClient
-from tempest.lib.services.identity.v3.projects_client import ProjectsClient
-from tempest.lib.services.identity.v3.regions_client import RegionsClient
-from tempest.lib.services.identity.v3.roles_client import RolesClient
-from tempest.lib.services.identity.v3.services_client import ServicesClient
-from tempest.lib.services.identity.v3.token_client import V3TokenClient
-from tempest.lib.services.identity.v3.trusts_client import TrustsClient
-from tempest.lib.services.identity.v3.users_client import UsersClient
-from tempest.services.identity.v3.json.domains_client import DomainsClient
-from tempest.services.identity.v3.json.role_assignments_client import \
- RoleAssignmentsClient
-
-__all__ = ['CredentialsClient', 'EndPointsClient', 'GroupsClient',
- 'IdentityClient', 'InheritedRolesClient', 'PoliciesClient',
- 'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
- 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
- 'UsersClient', 'DomainsClient', ]
diff --git a/tempest/services/identity/v3/json/__init__.py b/tempest/services/identity/v3/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/identity/v3/json/__init__.py
+++ /dev/null
diff --git a/tempest/services/identity/v3/json/role_assignments_client.py b/tempest/services/identity/v3/json/role_assignments_client.py
deleted file mode 100644
index 9fd7736..0000000
--- a/tempest/services/identity/v3/json/role_assignments_client.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright 2016 Red Hat, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.lib.common import rest_client
-
-
-class RoleAssignmentsClient(rest_client.RestClient):
- api_version = "v3"
-
- def list_user_project_effective_assignments(
- self, project_id, user_id):
- """List the effective role assignments for a user in a project."""
- resp, body = self.get(
- "role_assignments?scope.project.id=%s&user.id=%s&effective" %
- (project_id, user_id))
- self.expected_success(200, resp.status)
- body = json.loads(body)
- return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/object_storage/__init__.py b/tempest/services/object_storage/__init__.py
index 96fe4a3..d1a61d6 100644
--- a/tempest/services/object_storage/__init__.py
+++ b/tempest/services/object_storage/__init__.py
@@ -13,7 +13,10 @@
# the License.
from tempest.services.object_storage.account_client import AccountClient
+from tempest.services.object_storage.capabilities_client import \
+ CapabilitiesClient
from tempest.services.object_storage.container_client import ContainerClient
from tempest.services.object_storage.object_client import ObjectClient
-__all__ = ['AccountClient', 'ContainerClient', 'ObjectClient']
+__all__ = ['AccountClient', 'CapabilitiesClient', 'ContainerClient',
+ 'ObjectClient']
diff --git a/tempest/services/object_storage/account_client.py b/tempest/services/object_storage/account_client.py
index 6012a92..9932b4a 100644
--- a/tempest/services/object_storage/account_client.py
+++ b/tempest/services/object_storage/account_client.py
@@ -144,13 +144,3 @@
body = body.strip().splitlines()
self.expected_success([200, 204], resp.status)
return resp, body
-
- def list_extensions(self):
- self.skip_path()
- try:
- resp, body = self.get('info')
- finally:
- self.reset_path()
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return resp, body
diff --git a/tempest/services/object_storage/capabilities_client.py b/tempest/services/object_storage/capabilities_client.py
new file mode 100644
index 0000000..0fe437f
--- /dev/null
+++ b/tempest/services/object_storage/capabilities_client.py
@@ -0,0 +1,31 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class CapabilitiesClient(rest_client.RestClient):
+
+ def list_capabilities(self):
+ self.skip_path()
+ try:
+ resp, body = self.get('info')
+ finally:
+ self.reset_path()
+ body = json.loads(body)
+ self.expected_success(200, resp.status)
+ return resp, body
diff --git a/tempest/services/volume/__init__.py b/tempest/services/volume/__init__.py
deleted file mode 100644
index c62dd53..0000000
--- a/tempest/services/volume/__init__.py
+++ /dev/null
@@ -1,17 +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.volume import v3
-
-__all__ = ['v3']
diff --git a/tempest/services/volume/base/__init__.py b/tempest/services/volume/base/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/volume/base/__init__.py
+++ /dev/null
diff --git a/tempest/services/volume/v3/json/__init__.py b/tempest/services/volume/v3/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/volume/v3/json/__init__.py
+++ /dev/null
diff --git a/tempest/test.py b/tempest/test.py
index 93fbed3..17dc94c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -64,7 +64,6 @@
service_list = {
'compute': CONF.service_available.nova,
'image': CONF.service_available.glance,
- 'baremetal': CONF.service_available.ironic,
'volume': CONF.service_available.cinder,
'network': True,
'identity': True,
@@ -141,8 +140,38 @@
return False
+def related_bug(bug, status_code=None):
+ """A decorator useful to know solutions from launchpad bug reports
+
+ @param bug: The launchpad bug number causing the test
+ @param status_code: The status code related to the bug report
+ """
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ try:
+ return f(self, *func_args, **func_kwargs)
+ except Exception as exc:
+ exc_status_code = getattr(exc, 'status_code', None)
+ if status_code is None or status_code == exc_status_code:
+ LOG.error('Hints: This test was made for the bug %s. '
+ 'The failure could be related to '
+ 'https://launchpad.net/bugs/%s', bug, bug)
+ raise exc
+ return wrapper
+ return decorator
+
+
def is_scheduler_filter_enabled(filter_name):
- """Check the list of enabled compute scheduler filters from config. """
+ """Check the list of enabled compute scheduler filters from config.
+
+ This function checks whether the given compute scheduler filter is
+ available and configured in the config file. If the
+ scheduler_available_filters option is set to 'all' (Default value. which
+ means default filters are configured in nova) in tempest.conf then, this
+ function returns True with assumption that requested filter 'filter_name'
+ is one of available filter in nova ("nova.scheduler.filters.all_filters").
+ """
filters = CONF.compute_feature_enabled.scheduler_available_filters
if len(filters) == 0:
@@ -234,8 +263,8 @@
cls.resource_setup()
except Exception:
etype, value, trace = sys.exc_info()
- LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass." % (
- etype, cls.__name__))
+ LOG.info("%s raised in %s.setUpClass. Invoking tearDownClass.",
+ etype, cls.__name__)
cls.tearDownClass()
try:
six.reraise(etype, value, trace)
@@ -267,9 +296,9 @@
# resources that were successfully setup in resource_cleanup,
# log AttributeError as info instead of exception.
if tetype is AttributeError and name == 'resources':
- LOG.info("tearDownClass of %s failed: %s" % (name, te))
+ LOG.info("tearDownClass of %s failed: %s", name, te)
else:
- LOG.exception("teardown of %s failed: %s" % (name, te))
+ LOG.exception("teardown of %s failed: %s", name, te)
if not etype:
etype, value, trace = sys_exec_info
# If exceptions were raised during teardown, and not before, re-raise
@@ -504,8 +533,12 @@
else:
raise lib_exc.InvalidCredentials(
"Invalid credentials type %s" % credential_type)
- return cls.client_manager(credentials=creds.credentials,
- service=cls._service)
+ manager = cls.client_manager(credentials=creds.credentials,
+ service=cls._service)
+ # NOTE(andreaf) Ensure credentials have user and project id fields.
+ # It may not be the case when using pre-provisioned credentials.
+ manager.auth_provider.set_auth()
+ return manager
@classmethod
def clear_credentials(cls):
@@ -532,18 +565,17 @@
"""
if not CONF.validation.run_validation:
return
+
if keypair is None:
- if CONF.validation.auth_method.lower() == "keypair":
- keypair = True
- else:
- keypair = False
+ keypair = (CONF.validation.auth_method.lower() == "keypair")
+
if floating_ip is None:
- if CONF.validation.connect_method.lower() == "floating":
- floating_ip = True
- else:
- floating_ip = False
+ floating_ip = (CONF.validation.connect_method.lower() ==
+ "floating")
+
if security_group is None:
security_group = CONF.validation.security_group
+
if security_group_rules is None:
security_group_rules = CONF.validation.security_group_rules
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index f8d5d9d..e9f59af 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -143,7 +143,7 @@
plug.obj.register_opts(conf)
except Exception:
LOG.exception('Plugin %s raised an exception trying to run '
- 'register_opts' % plug.name)
+ 'register_opts', plug.name)
def get_plugin_options_list(self):
plugin_options = []
@@ -163,4 +163,4 @@
plug.name, service_clients)
except Exception:
LOG.exception('Plugin %s raised an exception trying to run '
- 'get_service_clients' % plug.name)
+ 'get_service_clients', plug.name)
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index b08954f..6773b2f 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -75,7 +75,7 @@
fake_domain_list = {'domains': [{'id': 'fake_domain',
'name': 'Fake_Domain'}]}
self.useFixture(fixtures.MockPatch(''.join([
- 'tempest.services.identity.v3.json.domains_client.'
+ 'tempest.lib.services.identity.v3.domains_client.'
'DomainsClient.list_domains']),
return_value=fake_domain_list))
self.useFixture(fixtures.MockPatch(
@@ -121,7 +121,7 @@
super(TestAccountGeneratorV3, self).setUp()
fake_domain_list = {'domains': [{'id': 'fake_domain'}]}
self.useFixture(fixtures.MockPatch(''.join([
- 'tempest.services.identity.v3.json.domains_client.'
+ 'tempest.lib.services.identity.v3.domains_client.'
'DomainsClient.list_domains']),
return_value=fake_domain_list))
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 00b4542..1af0d95 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -401,7 +401,7 @@
'not_fake': 'metadata',
'swift': 'metadata'})
fake_os = mock.MagicMock()
- fake_os.account_client.list_extensions = fake_list_extensions
+ fake_os.capabilities_client.list_capabilities = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['fake1', 'fake2', 'fake3'])))
@@ -423,7 +423,7 @@
'not_fake': 'metadata',
'swift': 'metadata'})
fake_os = mock.MagicMock()
- fake_os.account_client.list_extensions = fake_list_extensions
+ fake_os.capabilities_client.list_capabilities = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, 'get_enabled_extensions',
return_value=(['all'])))
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index a90ca8a..b4fbd50 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -27,6 +27,7 @@
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 domains_client
from tempest.lib.services.identity.v3 import identity_client as v3_iden_client
from tempest.lib.services.identity.v3 import projects_client as \
v3_projects_client
@@ -35,7 +36,6 @@
from tempest.lib.services.identity.v3 import users_client as \
v3_users_client
from tempest.lib.services.network import routers_client
-from tempest.services.identity.v3.json import domains_client
from tempest.tests import base
from tempest.tests import fake_config
from tempest.tests.lib import fake_http
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index e59e08f..e4f4c04 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -107,13 +107,20 @@
self.assertEqual(self.conn.get_number_of_vcpus(), 16)
self._assert_exec_called_with('grep -c ^processor /proc/cpuinfo')
- def test_get_partitions(self):
- proc_partitions = """major minor #blocks name
+ def test_get_disks(self):
+ output_lsblk = """\
+NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
+sda 8:0 0 128035676160 0 disk
+sdb 8:16 0 1000204886016 0 disk
+sr0 11:0 1 1073741312 0 rom"""
+ result = """\
+NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
+sda 8:0 0 128035676160 0 disk
+sdb 8:16 0 1000204886016 0 disk"""
-8 0 1048576 vda"""
- self.ssh_mock.mock.exec_command.return_value = proc_partitions
- self.assertEqual(self.conn.get_partitions(), proc_partitions)
- self._assert_exec_called_with('cat /proc/partitions')
+ self.ssh_mock.mock.exec_command.return_value = output_lsblk
+ self.assertEqual(self.conn.get_disks(), result)
+ self._assert_exec_called_with('lsblk -lb --nodeps')
def test_get_boot_time(self):
booted_at = 10000
diff --git a/tempest/tests/lib/cli/test_execute.py b/tempest/tests/lib/cli/test_execute.py
index cc9c94c..aaeb6f4 100644
--- a/tempest/tests/lib/cli/test_execute.py
+++ b/tempest/tests/lib/cli/test_execute.py
@@ -11,7 +11,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-
import mock
import subprocess
@@ -74,3 +73,20 @@
self.assertRaises(exceptions.CommandFailed, cli_base.execute,
"/bin/ls", action="tempest", flags="--foobar",
merge_stderr=True)
+
+ def test_execute_with_prefix(self):
+ result = cli_base.execute("env", action="",
+ prefix="env NEW_VAR=1")
+ self.assertIsInstance(result, str)
+ self.assertIn("NEW_VAR=1", result)
+
+
+class TestCLIClient(base.TestCase):
+
+ @mock.patch.object(cli_base, 'execute')
+ def test_execute_with_prefix(self, mock_execute):
+ cli = cli_base.CLIClient(prefix='env LAC_ALL=C')
+ cli.glance('action')
+ self.assertEqual(mock_execute.call_count, 1)
+ self.assertEqual(mock_execute.call_args[1],
+ {'prefix': 'env LAC_ALL=C'})
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index 831f8b5..8bae34f 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -55,6 +55,7 @@
},
"user": {
"id": "fake_alt_user_id",
+ "password_expires_at": None,
},
"serviceCatalog": CATALOG_V2,
},
@@ -71,6 +72,7 @@
},
"user": {
"id": "fake_user_id",
+ "password_expires_at": None,
},
"serviceCatalog": CATALOG_V2,
},
@@ -83,18 +85,21 @@
"id": "first_compute_fake_service",
"interface": "public",
"region": "NoMatchRegion",
+ "region_id": "NoMatchRegion",
"url": "http://fake_url/v3/first_endpoint/api"
},
{
"id": "second_fake_service",
"interface": "public",
"region": "FakeRegion",
+ "region_id": "FakeRegion",
"url": "http://fake_url/v3/second_endpoint/api"
},
{
"id": "third_fake_service",
"interface": "admin",
"region": "MiddleEarthRegion",
+ "region_id": "MiddleEarthRegion",
"url": "http://fake_url/v3/third_endpoint/api"
}
@@ -108,6 +113,7 @@
IDENTITY_V3_RESPONSE = {
"token": {
+ "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
"methods": [
"token",
"password"
@@ -127,7 +133,8 @@
"name": "domain_name"
},
"id": "fake_user_id",
- "name": "username"
+ "name": "username",
+ "password_expires_at": None,
},
"issued_at": "2013-05-29T16:55:21.468960Z",
"catalog": CATALOG_V3
@@ -136,6 +143,7 @@
IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = {
"token": {
+ "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
"methods": [
"token",
"password"
@@ -151,7 +159,8 @@
"name": "domain_name"
},
"id": "fake_user_id",
- "name": "username"
+ "name": "username",
+ "password_expires_at": None,
},
"issued_at": "2013-05-29T16:55:21.468960Z",
"catalog": CATALOG_V3
@@ -160,6 +169,7 @@
IDENTITY_V3_RESPONSE_NO_SCOPE = {
"token": {
+ "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
"methods": [
"token",
"password"
@@ -171,7 +181,8 @@
"name": "domain_name"
},
"id": "fake_user_id",
- "name": "username"
+ "name": "username",
+ "password_expires_at": None,
},
"issued_at": "2013-05-29T16:55:21.468960Z",
}
diff --git a/tempest/tests/lib/services/identity/v3/test_domains_client.py b/tempest/tests/lib/services/identity/v3/test_domains_client.py
new file mode 100644
index 0000000..f89ced7
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_domains_client.py
@@ -0,0 +1,138 @@
+# 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 domains_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestDomainsClient(base.BaseServiceTest):
+ FAKE_CREATE_DOMAIN = {
+ "domain": {
+ "description": "Domain description",
+ "enabled": True,
+ "name": "myDomain"
+ }
+ }
+
+ FAKE_DOMAIN_INFO = {
+ "domain": {
+ "description": "Used for swift functional testing",
+ "enabled": True,
+ "id": "5a75994a3",
+ "links": {
+ "self": "http://example.com/identity/v3/domains/5a75994a3"
+ },
+ "name": "swift_test"
+ }
+ }
+
+ FAKE_LIST_DOMAINS = {
+ "domains": [
+ {
+ "description": "Used for swift functional testing",
+ "enabled": True,
+ "id": "5a75994a3",
+ "links": {
+ "self": "http://example.com/identity/v3/domains/5a75994a3"
+ },
+ "name": "swift_test"
+ },
+ {
+ "description": "Owns users and tenants available on " +
+ "Identity API",
+ "enabled": True,
+ "id": "default",
+ "links": {
+ "self": "http://example.com/identity/v3/domains/default"
+ },
+ "name": "Default"
+ }
+ ],
+ "links": {
+ "next": None,
+ "previous": None,
+ "self": "http://example.com/identity/v3/domains"
+ }
+ }
+
+ def setUp(self):
+ super(TestDomainsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = domains_client.DomainsClient(fake_auth,
+ 'identity',
+ 'regionOne')
+
+ def _test_create_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_domain,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_DOMAIN,
+ bytes_body,
+ status=201)
+
+ def _test_show_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_domain,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_DOMAIN_INFO,
+ bytes_body,
+ domain_id="5a75994a3")
+
+ def _test_list_domains(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_domains,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_DOMAINS,
+ bytes_body)
+
+ def _test_update_domain(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_domain,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_DOMAIN_INFO,
+ bytes_body,
+ domain_id="5a75994a3")
+
+ def test_create_domain_with_str_body(self):
+ self._test_create_domain()
+
+ def test_create_domain_with_bytes_body(self):
+ self._test_create_domain(bytes_body=True)
+
+ def test_show_domain_with_str_body(self):
+ self._test_show_domain()
+
+ def test_show_domain_with_bytes_body(self):
+ self._test_show_domain(bytes_body=True)
+
+ def test_list_domain_with_str_body(self):
+ self._test_list_domains()
+
+ def test_list_domain_with_bytes_body(self):
+ self._test_list_domains(bytes_body=True)
+
+ def test_update_domain_with_str_body(self):
+ self._test_update_domain()
+
+ def test_update_domain_with_bytes_body(self):
+ self._test_update_domain(bytes_body=True)
+
+ def test_delete_domain(self):
+ self.check_service_client_function(
+ self.client.delete_domain,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ domain_id="5a75994a3",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_role_assignments_client.py b/tempest/tests/lib/services/identity/v3/test_role_assignments_client.py
new file mode 100644
index 0000000..7d304c1
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_role_assignments_client.py
@@ -0,0 +1,206 @@
+# 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 role_assignments_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestRoleAssignmentsClient(base.BaseServiceTest):
+
+ FAKE_USER_ID = "313234"
+ FAKE_GROUP_ID = "101112"
+
+ FAKE_ROLE1_ID = "123456"
+ FAKE_ROLE2_ID = "123457"
+
+ FAKE_PROJECT_ID = "456789"
+ FAKE_DOMAIN_ID = "102030"
+
+ FAKE_USER_PROJECT_ASSIGNMENT = {
+ "links": {
+ "assignment": "http://example.com/identity/v3/projects/"
+ "%s/users/%s/roles/%s" % (FAKE_PROJECT_ID,
+ FAKE_USER_ID,
+ FAKE_ROLE2_ID)
+ },
+ "role": {
+ "id": FAKE_ROLE2_ID
+ },
+ "scope": {
+ "project": {
+ "id": FAKE_PROJECT_ID
+ }
+ },
+ "user": {
+ "id": FAKE_USER_ID
+ }
+ }
+
+ FAKE_GROUP_PROJECT_ASSIGNMENT = {
+ "links": {
+ "assignment": "http://example.com/identity/v3/projects/"
+ "%s/groups/%s/roles/%s" % (FAKE_PROJECT_ID,
+ FAKE_GROUP_ID,
+ FAKE_ROLE1_ID)
+ },
+ "role": {
+ "id": FAKE_ROLE1_ID
+ },
+ "scope": {
+ "project": {
+ "id": FAKE_PROJECT_ID
+ }
+ },
+ "group": {
+ "id": FAKE_GROUP_ID
+ }
+ }
+
+ FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENT = {
+ "links": {
+ "assignment": "http://example.com/identity/v3/projects/"
+ "%s/groups/%s/roles/%s" % (FAKE_PROJECT_ID,
+ FAKE_GROUP_ID,
+ FAKE_ROLE1_ID),
+ "membership": "http://example.com/identity/v3/groups/"
+ "%s/users/%s" % (FAKE_GROUP_ID, FAKE_USER_ID)
+ },
+ "role": {
+ "id": FAKE_ROLE1_ID
+ },
+ "scope": {
+ "project": {
+ "id": FAKE_PROJECT_ID
+ }
+ },
+ "user": {
+ "id": FAKE_USER_ID
+ }
+ }
+
+ FAKE_USER_DOMAIN_ASSIGNMENT = {
+ "links": {
+ "assignment": "http://example.com/identity/v3/domains/"
+ "%s/users/%s/roles/%s" % (FAKE_DOMAIN_ID,
+ FAKE_USER_ID,
+ FAKE_ROLE1_ID)
+ },
+ "role": {
+ "id": FAKE_ROLE1_ID
+ },
+ "scope": {
+ "domain": {
+ "id": FAKE_DOMAIN_ID
+ }
+ },
+ "user": {
+ "id": FAKE_USER_ID
+ }
+ }
+
+ FAKE_GROUP_PROJECT_ASSIGNMENTS = {
+ "role_assignments": [
+ FAKE_GROUP_PROJECT_ASSIGNMENT
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/role_assignments?"
+ "scope.project.id=%s&group.id=%s&effective" % (
+ FAKE_PROJECT_ID, FAKE_GROUP_ID),
+ "previous": None,
+ "next": None
+ }
+ }
+
+ FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENTS = {
+ "role_assignments": [
+ FAKE_USER_PROJECT_ASSIGNMENT,
+ FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENT
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/role_assignments?"
+ "scope.project.id=%s&user.id=%s&effective" % (
+ FAKE_PROJECT_ID, FAKE_USER_ID),
+ "previous": None,
+ "next": None
+ }
+ }
+
+ FAKE_USER_DOMAIN_ASSIGNMENTS = {
+ "role_assignments": [
+ FAKE_USER_DOMAIN_ASSIGNMENT
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/role_assignments?"
+ "scope.domain.id=%s&user.id=%s&effective" % (
+ FAKE_DOMAIN_ID, FAKE_USER_ID),
+ "previous": None,
+ "next": None
+ }
+ }
+
+ def setUp(self):
+ super(TestRoleAssignmentsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = role_assignments_client.RoleAssignmentsClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_list_user_project_effective_assignments(self, bytes_body=False):
+ params = {'scope.project.id': self.FAKE_PROJECT_ID,
+ 'user.id': self.FAKE_USER_ID}
+ self.check_service_client_function(
+ self.client.list_role_assignments,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_PROJECT_EFFECTIVE_ASSIGNMENTS,
+ bytes_body,
+ effective=True,
+ **params)
+
+ def test_list_user_project_effective_assignments_with_str_body(self):
+ self._test_list_user_project_effective_assignments()
+
+ def test_list_user_project_effective_assignments_with_bytes_body(self):
+ self._test_list_user_project_effective_assignments(bytes_body=True)
+
+ def _test_list_group_project_assignments(self, bytes_body=False):
+ params = {'scope.project.id': self.FAKE_PROJECT_ID,
+ 'group.id': self.FAKE_GROUP_ID}
+ self.check_service_client_function(
+ self.client.list_role_assignments,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_GROUP_PROJECT_ASSIGNMENTS,
+ bytes_body,
+ **params)
+
+ def test_list_group_project_assignments_with_str_body(self):
+ self._test_list_group_project_assignments()
+
+ def test_list_group_project_assignments_with_bytes_body(self):
+ self._test_list_group_project_assignments(bytes_body=True)
+
+ def _test_list_user_domain_assignments(self, bytes_body=False):
+ params = {'scope.domain.id': self.FAKE_DOMAIN_ID,
+ 'user.id': self.FAKE_USER_ID}
+ self.check_service_client_function(
+ self.client.list_role_assignments,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_GROUP_PROJECT_ASSIGNMENTS,
+ bytes_body,
+ **params)
+
+ def test_list_user_domain_assignments_with_str_body(self):
+ self._test_list_user_domain_assignments()
+
+ def test_list_user_domain_assignments_with_bytes_body(self):
+ self._test_list_user_domain_assignments(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
index 9f4b4cc..38e8c4a 100644
--- a/tempest/tests/lib/services/identity/v3/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -20,7 +20,7 @@
from tempest.lib import exceptions
from tempest.lib.services.identity.v3 import token_client
from tempest.tests import base
-from tempest.tests.lib import fake_http
+from tempest.tests.lib import fake_identity
class TestTokenClientV3(base.TestCase):
@@ -31,10 +31,8 @@
def test_auth(self):
token_client_v3 = token_client.V3TokenClient('fake_url')
- response = fake_http.fake_http_response(
- None, status=201,
- )
- body = {'access': {'token': 'fake_token'}}
+ response, body_text = fake_identity._fake_v3_response(None, None)
+ body = json.loads(body_text)
with mock.patch.object(token_client_v3, 'post') as post_mock:
post_mock.return_value = response, body
@@ -60,10 +58,8 @@
def test_auth_with_project_id_and_domain_id(self):
token_client_v3 = token_client.V3TokenClient('fake_url')
- response = fake_http.fake_http_response(
- None, status=201,
- )
- body = {'access': {'token': 'fake_token'}}
+ response, body_text = fake_identity._fake_v3_response(None, None)
+ body = json.loads(body_text)
with mock.patch.object(token_client_v3, 'post') as post_mock:
post_mock.return_value = response, body
@@ -103,10 +99,8 @@
def test_auth_with_tenant(self):
token_client_v3 = token_client.V3TokenClient('fake_url')
- response = fake_http.fake_http_response(
- None, status=201,
- )
- body = {'access': {'token': 'fake_token'}}
+ response, body_text = fake_identity._fake_v3_response(None, None)
+ body = json.loads(body_text)
with mock.patch.object(token_client_v3, 'post') as post_mock:
post_mock.return_value = response, body
@@ -138,13 +132,10 @@
def test_request_with_str_body(self):
token_client_v3 = token_client.V3TokenClient('fake_url')
- response = fake_http.fake_http_response(
- None, status=200,
- )
- body = str('{"access": {"token": "fake_token"}}')
with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
- mock_raw_r.return_value = response, body
+ mock_raw_r.return_value = (
+ fake_identity._fake_v3_response(None, None))
resp, body = token_client_v3.request('GET', 'fake_uri')
self.assertIsInstance(body, dict)
@@ -152,10 +143,8 @@
def test_request_with_bytes_body(self):
token_client_v3 = token_client.V3TokenClient('fake_url')
- response = fake_http.fake_http_response(
- None, status=200,
- )
- body = b'{"access": {"token": "fake_token"}}'
+ response, body_text = fake_identity._fake_v3_response(None, None)
+ body = body_text.encode('utf-8')
with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
mock_raw_r.return_value = response, body
diff --git a/tempest/tests/lib/services/image/v2/test_namespace_properties_client.py b/tempest/tests/lib/services/image/v2/test_namespace_properties_client.py
new file mode 100644
index 0000000..1d56db6
--- /dev/null
+++ b/tempest/tests/lib/services/image/v2/test_namespace_properties_client.py
@@ -0,0 +1,191 @@
+# Copyright 2016 EasyStack. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.services.image.v2 import namespace_properties_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestNamespacePropertiesClient(base.BaseServiceTest):
+ FAKE_CREATE_SHOW_NAMESPACE_PROPERTY = {
+ "description": "property",
+ "enum": ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"],
+ "name": "OS::Glance::Image",
+ "title": "Hypervisor Type",
+ "type": "string"
+ }
+
+ FAKE_LIST_NAMESPACE_PROPERTY = {
+ "properties": {
+ "hw_disk_bus": {
+ "description": "property.",
+ "enum": ["scsi", "virtio", "uml", "xen", "ide", "usb"],
+ "title": "Disk Bus",
+ "type": "string"
+ },
+ "hw_machine_type": {
+ "description": "desc.",
+ "title": "Machine Type",
+ "type": "string"
+ },
+ "hw_qemu_guest_agent": {
+ "description": "desc.",
+ "enum": [
+ "yes",
+ "no"
+ ],
+ "title": "QEMU Guest Agent",
+ "type": "string"
+ },
+ "hw_rng_model": {
+ "default": "virtio",
+ "description": "desc",
+ "title": "Random Number Generator Device",
+ "type": "string"
+ },
+ "hw_scsi_model": {
+ "default": "virtio-scsi",
+ "description": "desc.",
+ "title": "SCSI Model",
+ "type": "string"
+ },
+ "hw_video_model": {
+ "description": "The video image driver used.",
+ "enum": [
+ "vga",
+ "cirrus",
+ "vmvga",
+ "xen",
+ "qxl"
+ ],
+ "title": "Video Model",
+ "type": "string"
+ },
+ "hw_video_ram": {
+ "description": "desc.",
+ "title": "Max Video Ram",
+ "type": "integer"
+ },
+ "hw_vif_model": {
+ "description": "desc.",
+ "enum": ["e1000",
+ "ne2k_pci",
+ "pcnet",
+ "rtl8139",
+ "virtio",
+ "e1000",
+ "e1000e",
+ "VirtualE1000",
+ "VirtualE1000e",
+ "VirtualPCNet32",
+ "VirtualSriovEthernetCard",
+ "VirtualVmxnet",
+ "netfront",
+ "ne2k_pci"
+ ],
+ "title": "Virtual Network Interface",
+ "type": "string"
+ },
+ "os_command_line": {
+ "description": "desc.",
+ "title": "Kernel Command Line",
+ "type": "string"
+ }
+ }
+ }
+
+ FAKE_UPDATE_NAMESPACE_PROPERTY = {
+ "description": "property",
+ "enum": ["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"],
+ "name": "OS::Glance::Image",
+ "title": "update Hypervisor Type",
+ "type": "string"
+ }
+
+ def setUp(self):
+ super(TestNamespacePropertiesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = namespace_properties_client.NamespacePropertiesClient(
+ fake_auth, 'image', 'regionOne')
+
+ def _test_create_namespace_property(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_namespace_property,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_CREATE_SHOW_NAMESPACE_PROPERTY,
+ bytes_body, status=201,
+ namespace="OS::Compute::Hypervisor",
+ title="Hypervisor Type", name="OS::Glance::Image",
+ type="string",
+ enum=["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"])
+
+ def _test_list_namespace_property(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_namespace_properties,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_NAMESPACE_PROPERTY,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor")
+
+ def _test_show_namespace_property(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_namespace_properties,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_CREATE_SHOW_NAMESPACE_PROPERTY,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor",
+ property_name="OS::Glance::Image")
+
+ def _test_update_namespace_property(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_namespace_properties,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_UPDATE_NAMESPACE_PROPERTY,
+ bytes_body,
+ namespace="OS::Compute::Hypervisor",
+ property_name="OS::Glance::Image",
+ title="update Hypervisor Type", type="string",
+ enum=["xen", "qemu", "kvm", "lxc", "uml", "vmware", "hyperv"],
+ name="OS::Glance::Image")
+
+ def test_create_namespace_property_with_str_body(self):
+ self._test_create_namespace_property()
+
+ def test_create_namespace_property_with_bytes_body(self):
+ self._test_create_namespace_property(bytes_body=True)
+
+ def test_list_namespace_property_with_str_body(self):
+ self._test_list_namespace_property()
+
+ def test_list_namespace_property_with_bytes_body(self):
+ self._test_list_namespace_property(bytes_body=True)
+
+ def test_show_namespace_property_with_str_body(self):
+ self._test_show_namespace_property()
+
+ def test_show_namespace_property_with_bytes_body(self):
+ self._test_show_namespace_property(bytes_body=True)
+
+ def test_update_namespace_property_with_str_body(self):
+ self._test_update_namespace_property()
+
+ def test_update_namespace_property_with_bytes_body(self):
+ self._test_update_namespace_property(bytes_body=True)
+
+ def test_delete_namespace(self):
+ self.check_service_client_function(
+ self.client.delete_namespace_property,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {}, namespace="OS::Compute::Hypervisor",
+ property_name="OS::Glance::Image", status=204)
diff --git a/tempest/tests/lib/services/network/test_service_providers_client.py b/tempest/tests/lib/services/network/test_service_providers_client.py
new file mode 100644
index 0000000..ae11ef0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_service_providers_client.py
@@ -0,0 +1,37 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.lib.services.network import service_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServiceProvidersClient(base.BaseServiceTest):
+ def setUp(self):
+ super(TestServiceProvidersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = service_providers_client.ServiceProvidersClient(
+ fake_auth, 'network', 'regionOne')
+
+ def _test_list_service_providers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_service_providers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {"service_providers": []},
+ bytes_body)
+
+ def test_list_service_providers_with_str_body(self):
+ self._test_list_service_providers()
+
+ def test_list_service_providers_with_bytes_body(self):
+ self._test_list_service_providers(bytes_body=True)
diff --git a/tempest/api/baremetal/__init__.py b/tempest/tests/lib/services/volume/v3/__init__.py
similarity index 100%
rename from tempest/api/baremetal/__init__.py
rename to tempest/tests/lib/services/volume/v3/__init__.py
diff --git a/tempest/tests/lib/services/volume/v3/test_user_messages_client.py b/tempest/tests/lib/services/volume/v3/test_user_messages_client.py
new file mode 100644
index 0000000..4aeed5f
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v3/test_user_messages_client.py
@@ -0,0 +1,92 @@
+# Copyright 2016 Red Hat. 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.volume.v3 import messages_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestUserMessagesClient(base.BaseServiceTest):
+ USER_MESSAGE_INFO = {
+ "created_at": "2016-11-21T06:16:34.000000",
+ "guaranteed_until": "2016-12-21T06:16:34.000000",
+ "user_message": "No storage could be allocated for this volume "
+ "request. You may be able to try another size or"
+ " volume type.",
+ "resource_uuid": "c570b406-bf0b-4067-9398-f0bb09a7d9d7",
+ "request_id": "req-8f68681e-9b6b-4009-b94c-ac0811595451",
+ "message_level": "ERROR",
+ "id": "9a7dafbd-a156-4540-8996-50e71b5dcadf",
+ "resource_type": "VOLUME",
+ "links": [
+ {"href": "http://192.168.100.230:8776/v3/"
+ "a678cb65f701462ea2257245cd640829/messages/"
+ "9a7dafbd-a156-4540-8996-50e71b5dcadf",
+ "rel": "self"},
+ {"href": "http://192.168.100.230:8776/"
+ "a678cb65f701462ea2257245cd640829/messages/"
+ "9a7dafbd-a156-4540-8996-50e71b5dcadf",
+ "rel": "bookmark"}]
+ }
+ FAKE_SHOW_USER_MESSAGE = {
+ "message": dict(event_id="000002", **USER_MESSAGE_INFO)}
+
+ FAKE_LIST_USER_MESSAGES = {
+ "messages": [
+ dict(event_id="000003", **USER_MESSAGE_INFO),
+ dict(event_id="000004", **USER_MESSAGE_INFO)
+ ]
+ }
+
+ def setUp(self):
+ super(TestUserMessagesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = messages_client.MessagesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_show_user_message(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_message,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_USER_MESSAGE,
+ bytes_body,
+ message_id="9a7dafbd-a156-4540-8996-50e71b5dcadf")
+
+ def _test_list_user_message(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_messages,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_USER_MESSAGES,
+ bytes_body)
+
+ def test_list_user_message_with_str_body(self):
+ self._test_list_user_message()
+
+ def test_list_user_message_with_bytes_body(self):
+ self._test_list_user_message(bytes_body=True)
+
+ def test_show_user_message_with_str_body(self):
+ self._test_show_user_message()
+
+ def test_show_user_message_with_bytes_body(self):
+ self._test_show_user_message(bytes_body=True)
+
+ def test_delete_user_message(self):
+ self.check_service_client_function(
+ self.client.delete_message,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ message_id="9a7dafbd-a156-4540-8996-50e71b5dcadf",
+ status=204)
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index 6da7e41..2f975d2 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -458,6 +458,56 @@
expected = 'http://fake_url/v2.0'
self._test_base_url_helper(expected, filters, ('token', auth_data))
+ def test_base_url_with_extra_path_endpoint(self):
+ auth_data = {
+ 'serviceCatalog': [
+ {
+ 'type': 'compute',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'publicURL': 'http://fake_url/some_path/v2.0'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/some_path/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
+ def test_base_url_with_unversioned_extra_path_endpoint(self):
+ auth_data = {
+ 'serviceCatalog': [
+ {
+ 'type': 'compute',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'publicURL': 'http://fake_url/some_path'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/some_path/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
def test_token_not_expired(self):
expiry_data = datetime.datetime.utcnow() + datetime.timedelta(days=1)
self._verify_expiry(expiry_data=expiry_data, should_be_expired=False)
@@ -592,6 +642,58 @@
expected = 'http://fake_url/v3'
self._test_base_url_helper(expected, filters, ('token', auth_data))
+ def test_base_url_with_extra_path_endpoint(self):
+ auth_data = {
+ 'catalog': [
+ {
+ 'type': 'compute',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'url': 'http://fake_url/some_path/v2.0',
+ 'interface': 'public'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/some_path/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
+ def test_base_url_with_unversioned_extra_path_endpoint(self):
+ auth_data = {
+ 'catalog': [
+ {
+ 'type': 'compute',
+ 'endpoints': [
+ {
+ 'region': 'FakeRegion',
+ 'url': 'http://fake_url/some_path',
+ 'interface': 'public'
+ }
+ ]
+ }
+ ]
+ }
+
+ filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion',
+ 'api_version': 'v2.0'
+ }
+
+ expected = 'http://fake_url/some_path/v2.0'
+ self._test_base_url_helper(expected, filters, ('token', auth_data))
+
# Base URL test with scope only for V3
def test_base_url_scope_project(self):
self.auth_provider.scope = 'project'
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
deleted file mode 100644
index 7e1ee2c..0000000
--- a/tempest/tests/negative/test_negative_generators.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# Copyright 2014 Deutsche Telekom AG
-# 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 jsonschema
-import mock
-import six
-
-from tempest.common.generator import base_generator
-from tempest.common.generator import negative_generator
-from tempest.common.generator import valid_generator
-from tempest.tests import base
-
-
-class TestNegativeBasicGenerator(base.TestCase):
- valid_desc = {
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- "json-schema": {
- "type": "object",
- "properties": {
- "minRam": {"type": "integer"},
- "minDisk": {"type": "integer"}
- }
- },
- "resources": ["flavor", "volume", "image"]
- }
-
- minimal_desc = {
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- }
-
- add_prop_desc = {
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- "unknown_field": [12]
- }
-
- invalid_json_schema_desc = {
- "name": "list-flavors-with-detail",
- "http-method": "GET",
- "url": "flavors/detail",
- "json-schema": {"type": "NotExistingType"}
- }
-
- def setUp(self):
- super(TestNegativeBasicGenerator, self).setUp()
- self.generator = base_generator.BasicGeneratorSet()
-
- def _assert_valid_jsonschema_call(self, jsonschema_mock, desc):
- self.assertEqual(jsonschema_mock.call_count, 1)
- jsonschema_mock.assert_called_with(desc, self.generator.schema)
-
- @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
- def test_validate_schema_with_valid_input(self, jsonschema_mock):
- self.generator.validate_schema(self.valid_desc)
- self._assert_valid_jsonschema_call(jsonschema_mock, self.valid_desc)
-
- @mock.patch('jsonschema.validate', wraps=jsonschema.validate)
- def test_validate_schema_with_minimal_input(self, jsonschema_mock):
- self.generator.validate_schema(self.minimal_desc)
- self._assert_valid_jsonschema_call(jsonschema_mock, self.minimal_desc)
-
- def test_validate_schema_with_invalid_input(self):
- self.assertRaises(jsonschema.ValidationError,
- self.generator.validate_schema, self.add_prop_desc)
- self.assertRaises(jsonschema.SchemaError,
- self.generator.validate_schema,
- self.invalid_json_schema_desc)
-
-
-class BaseNegativeGenerator(object):
- types = ['string', 'integer', 'object']
-
- fake_input_obj = {"type": "object",
- "properties": {"minRam": {"type": "integer"},
- "diskName": {"type": "string"},
- "maxRam": {"type": "integer", }
- }
- }
-
- unknown_type_schema = {
- "type": "not_defined"
- }
-
- class fake_test_class(object):
- def __init__(self, scenario):
- for k, v in six.iteritems(scenario):
- setattr(self, k, v)
-
- def _validate_result(self, valid_schema, invalid_schema):
- for k, v in six.iteritems(valid_schema):
- self.assertIn(k, invalid_schema)
-
- def test_generator_mandatory_functions(self):
- for data_type in self.types:
- self.assertIn(data_type, self.generator.types_dict)
-
- def test_generate_with_unknown_type(self):
- self.assertRaises(TypeError, self.generator.generate_payload,
- self.unknown_type_schema)
-
-
-class TestNegativeValidGenerator(base.TestCase, BaseNegativeGenerator):
- def setUp(self):
- super(TestNegativeValidGenerator, self).setUp()
- self.generator = valid_generator.ValidTestGenerator()
-
- def test_generate_valid(self):
- result = self.generator.generate_valid(self.fake_input_obj)
- self.assertIn("minRam", result)
- self.assertIsInstance(result["minRam"], int)
- self.assertIn("diskName", result)
- self.assertIsInstance(result["diskName"], str)
-
-
-class TestNegativeNegativeGenerator(base.TestCase, BaseNegativeGenerator):
- def setUp(self):
- super(TestNegativeNegativeGenerator, self).setUp()
- self.generator = negative_generator.NegativeTestGenerator()
-
- def test_generate_obj(self):
- schema = self.fake_input_obj
- scenarios = self.generator.generate_scenarios(schema)
- for scenario in scenarios:
- test = self.fake_test_class(scenario)
- valid_schema = \
- valid_generator.ValidTestGenerator().generate_valid(schema)
- schema_under_test = copy.copy(valid_schema)
- expected_result = \
- self.generator.generate_payload(test, schema_under_test)
- self.assertIsNone(expected_result)
- self._validate_result(valid_schema, schema_under_test)
diff --git a/tempest/tests/test_negative_rest_client.py b/tempest/tests/test_negative_rest_client.py
deleted file mode 100644
index 05f9f3e..0000000
--- a/tempest/tests/test_negative_rest_client.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# (c) 2015 Deutsche Telekom AG
-# Copyright 2015 Red Hat, Inc.
-# Copyright 2015 NEC Corporation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import mock
-from oslotest import mockpatch
-
-from tempest.common import negative_rest_client
-from tempest import config
-from tempest.tests import base
-from tempest.tests import fake_config
-from tempest.tests.lib import fake_auth_provider
-
-
-class TestNegativeRestClient(base.TestCase):
-
- url = 'fake_endpoint'
-
- def setUp(self):
- super(TestNegativeRestClient, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.patchobject(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
- self.negative_rest_client = negative_rest_client.NegativeRestClient(
- fake_auth_provider.FakeAuthProvider(), None)
- self.useFixture(mockpatch.PatchObject(self.negative_rest_client,
- '_log_request'))
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.post',
- return_value=(mock.Mock(), mock.Mock()))
- def test_post(self, mock_post):
- __, return_dict = self.negative_rest_client.send_request('POST',
- self.url,
- [], {})
- mock_post.assert_called_once_with(self.url, {})
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.get',
- return_value=(mock.Mock(), mock.Mock()))
- def test_get(self, mock_get):
- __, return_dict = self.negative_rest_client.send_request('GET',
- self.url,
- [])
- mock_get.assert_called_once_with(self.url)
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.delete',
- return_value=(mock.Mock(), mock.Mock()))
- def test_delete(self, mock_delete):
- __, return_dict = self.negative_rest_client.send_request('DELETE',
- self.url,
- [])
- mock_delete.assert_called_once_with(self.url)
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.patch',
- return_value=(mock.Mock(), mock.Mock()))
- def test_patch(self, mock_patch):
- __, return_dict = self.negative_rest_client.send_request('PATCH',
- self.url,
- [], {})
- mock_patch.assert_called_once_with(self.url, {})
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.put',
- return_value=(mock.Mock(), mock.Mock()))
- def test_put(self, mock_put):
- __, return_dict = self.negative_rest_client.send_request('PUT',
- self.url,
- [], {})
- mock_put.assert_called_once_with(self.url, {})
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.head',
- return_value=(mock.Mock(), mock.Mock()))
- def test_head(self, mock_head):
- __, return_dict = self.negative_rest_client.send_request('HEAD',
- self.url,
- [])
- mock_head.assert_called_once_with(self.url)
-
- @mock.patch('tempest.lib.common.rest_client.RestClient.copy',
- return_value=(mock.Mock(), mock.Mock()))
- def test_copy(self, mock_copy):
- __, return_dict = self.negative_rest_client.send_request('COPY',
- self.url,
- [])
- mock_copy.assert_called_once_with(self.url)
-
- def test_other(self):
- self.assertRaises(AssertionError,
- self.negative_rest_client.send_request,
- 'OTHER', self.url, [])
diff --git a/test-requirements.txt b/test-requirements.txt
index 3260915..475fb16 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
# 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.12.0,<0.13 # Apache-2.0
+hacking<0.13,>=0.12.0 # Apache-2.0
# needed for doc build
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
oslosphinx>=4.7.0 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index 82dba92..46823d8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -143,10 +143,10 @@
# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/
# E123 skipped because it is ignored by default in the default pep8
# E129 skipped because it is too limiting when combined with other rules
-# Skipped because of new hacking 0.9: H405
ignore = E125,E123,E129
show-source = True
exclude = .git,.venv,.tox,dist,doc,*egg
+enable-extensions = H106,H203,H904
[testenv:releasenotes]
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
@@ -161,3 +161,12 @@
commands=
pip-extra-reqs -d --ignore-file=tempest/tests/* tempest
pip-missing-reqs -d --ignore-file=tempest/tests/* tempest
+
+
+[testenv:bindep]
+# Do not install any requirements. We want this to be fast and work even if
+# system dependencies are missing, since it's used to tell you what system
+# dependencies are missing! This also means that bindep must be installed
+# separately, outside of the requirements files.
+deps = bindep
+commands = bindep test