Merge "Re-use common methods in ServerRescueNegativeTestJSON"
diff --git a/HACKING.rst b/HACKING.rst
index 432db7d..7ab420b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -166,8 +166,33 @@
suite, Tempest is not suitable for handling all negative test cases, as
the wide variety and complexity of negative tests can lead to long test
runs and knowledge of internal implementation details. The bulk of
-negative testing should be handled with project function tests. The
-exception to this rule is API tests used for interoperability testing.
+negative testing should be handled with project function tests.
+All negative tests should be based on `API-WG guideline`_ . Such negative
+tests can block any changes from accurate failure code to invalid one.
+
+.. _API-WG guideline: https://github.com/openstack/api-wg/blob/master/guidelines/http.rst#failure-code-clarifications
+
+If facing some gray area which is not clarified on the above guideline, propose
+a new guideline to the API-WG. With a proposal to the API-WG we will be able to
+build a consensus across all OpenStack projects and improve the quality and
+consistency of all the APIs.
+
+In addition, we have some guidelines for additional negative tests.
+
+- About BadRequest(HTTP400) case: We can add a single negative tests of
+ BadRequest for each resource and method(POST, PUT).
+ Please don't implement more negative tests on the same combination of
+ resource and method even if API request parameters are different from
+ the existing test.
+- About NotFound(HTTP404) case: We can add a single negative tests of
+ NotFound for each resource and method(GET, PUT, DELETE, HEAD).
+ Please don't implement more negative tests on the same combination
+ of resource and method.
+
+The above guidelines don't cover all cases and we will grow these guidelines
+organically over time. Patches outside of the above guidelines are left up to
+the reviewers' discretion and if we face some conflicts between reviewers, we
+will expand the guideline based on our discussion and experience.
Test skips because of Known Bugs
--------------------------------
diff --git a/releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml b/releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml
index 1157a4f..7a1fc36 100644
--- a/releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml
+++ b/releasenotes/notes/12.1.0-remove-trove-tests-666522e9113549f9.yaml
@@ -1,4 +1,4 @@
---
upgrade:
- All tests for the Trove project have been removed from tempest. They now
- live as a tempest plugin in the the trove project.
+ live as a tempest plugin in the trove project.
diff --git a/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml b/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
index 4886f16..9cfce0d 100644
--- a/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
+++ b/releasenotes/notes/add-volume-clients-as-a-library-d05b6bc35e66c6ef.yaml
@@ -6,6 +6,7 @@
so the other projects can use these modules as stable libraries without
any maintenance changes.
+ * backups_client
* encryption_types_client (v1)
* encryption_types_client (v2)
* qos_clients (v1)
diff --git a/requirements.txt b/requirements.txt
index 0a5268c..9b47e77 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.6 # Apache-2.0
-cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
+cliff>=2.2.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+
@@ -16,11 +16,11 @@
six>=1.9.0 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
testscenarios>=0.4 # Apache-2.0/BSD
-PyYAML>=3.1.0 # MIT
+PyYAML>=3.10.0 # MIT
python-subunit>=0.0.18 # Apache-2.0/BSD
stevedore>=1.16.0 # Apache-2.0
-PrettyTable<0.8,>=0.7 # BSD
-os-testr>=0.7.0 # Apache-2.0
+PrettyTable<0.8,>=0.7.1 # BSD
+os-testr>=0.8.0 # Apache-2.0
urllib3>=1.15.1 # MIT
debtcollector>=1.2.0 # Apache-2.0
unittest2 # BSD
diff --git a/tempest/api/image/admin/v2/test_images.py b/tempest/api/image/admin/v2/test_images.py
index c719b7a..9844a67 100644
--- a/tempest/api/image/admin/v2/test_images.py
+++ b/tempest/api/image/admin/v2/test_images.py
@@ -34,27 +34,26 @@
def test_admin_deactivate_reactivate_image(self):
# Create image by non-admin tenant
image_name = data_utils.rand_name('image')
- body = self.client.create_image(name=image_name,
- container_format='bare',
- disk_format='raw',
- visibility='private')
- image_id = body['id']
- self.addCleanup(self.client.delete_image, image_id)
+ image = self.client.create_image(name=image_name,
+ container_format='bare',
+ disk_format='raw',
+ visibility='private')
+ self.addCleanup(self.client.delete_image, image['id'])
# upload an image file
content = data_utils.random_bytes()
image_file = six.BytesIO(content)
- self.client.store_image_file(image_id, image_file)
+ self.client.store_image_file(image['id'], image_file)
# deactivate image
- self.admin_client.deactivate_image(image_id)
- body = self.client.show_image(image_id)
+ self.admin_client.deactivate_image(image['id'])
+ body = self.client.show_image(image['id'])
self.assertEqual("deactivated", body['status'])
# non-admin user unable to download deactivated image
self.assertRaises(lib_exc.Forbidden, self.client.show_image_file,
- image_id)
+ image['id'])
# reactivate image
- self.admin_client.reactivate_image(image_id)
- body = self.client.show_image(image_id)
+ self.admin_client.reactivate_image(image['id'])
+ body = self.client.show_image(image['id'])
self.assertEqual("active", body['status'])
# non-admin user able to download image after reactivation by admin
- body = self.client.show_image_file(image_id)
+ body = self.client.show_image_file(image['id'])
self.assertEqual(content, body.data)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index f74f97b..26b88b0 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -123,8 +123,7 @@
disk_format='raw',
is_public=False,
data=image_file)
- image_id = image['id']
- return image_id
+ return image['id']
class BaseV2ImageTest(BaseImageTest):
@@ -183,9 +182,8 @@
image = self.client.create_image(name=name,
container_format='bare',
disk_format='raw')
- image_id = image['id']
- self.addCleanup(self.client.delete_image, image_id)
- return image_id
+ self.addCleanup(self.client.delete_image, image['id'])
+ return image['id']
class BaseV1ImageAdminTest(BaseImageTest):
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 712b34b..695efb5 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -49,22 +49,21 @@
# Register, then upload an image
properties = {'prop1': 'val1'}
container_format, disk_format = get_container_and_disk_format()
- body = self.create_image(name='New Name',
- container_format=container_format,
- disk_format=disk_format,
- is_public=False,
- properties=properties)
- self.assertIn('id', body)
- image_id = body.get('id')
- self.assertEqual('New Name', body.get('name'))
- self.assertFalse(body.get('is_public'))
- self.assertEqual('queued', body.get('status'))
+ image = self.create_image(name='New Name',
+ container_format=container_format,
+ 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'))
for key, val in properties.items():
- self.assertEqual(val, body.get('properties')[key])
+ self.assertEqual(val, image.get('properties')[key])
# Now try uploading an image file
image_file = six.BytesIO(data_utils.random_bytes())
- body = self.client.update_image(image_id, data=image_file)['image']
+ body = self.client.update_image(image['id'], data=image_file)['image']
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -89,16 +88,15 @@
@test.idempotent_id('6d0e13a7-515b-460c-b91f-9f4793f09816')
def test_register_http_image(self):
container_format, disk_format = get_container_and_disk_format()
- body = self.create_image(name='New Http Image',
- container_format=container_format,
- disk_format=disk_format, is_public=False,
- copy_from=CONF.image.http_image)
- self.assertIn('id', body)
- image_id = body.get('id')
- self.assertEqual('New Http Image', body.get('name'))
- self.assertFalse(body.get('is_public'))
- waiters.wait_for_image_status(self.client, image_id, 'active')
- self.client.show_image(image_id)
+ image = self.create_image(name='New Http Image',
+ 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')
+ self.client.show_image(image['id'])
@test.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
def test_register_image_with_min_ram(self):
@@ -188,8 +186,7 @@
disk_format=disk_format,
is_public=False,
location=location)
- image_id = image['id']
- return image_id
+ return image['id']
@classmethod
def _create_standard_image(cls, name, container_format,
@@ -205,8 +202,7 @@
container_format=container_format,
disk_format=disk_format,
is_public=False, data=image_file)
- image_id = image['id']
- return image_id
+ return image['id']
@test.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d')
def test_index_no_params(self):
@@ -301,8 +297,7 @@
disk_format=disk_format,
is_public=False, data=image_file,
properties={'key1': 'value1'})
- image_id = image['id']
- return image_id
+ return image['id']
@test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
def test_list_image_metadata(self):
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index f363d34..aff8a78 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -44,35 +44,34 @@
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
- body = self.create_image(name=image_name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private',
- ramdisk_id=uuid)
- self.assertIn('id', body)
- image_id = body.get('id')
- self.assertIn('name', body)
- self.assertEqual(image_name, body['name'])
- self.assertIn('visibility', body)
- self.assertEqual('private', body['visibility'])
- self.assertIn('status', body)
- self.assertEqual('queued', body['status'])
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ 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)
+ self.assertEqual('private', image['visibility'])
+ self.assertIn('status', image)
+ self.assertEqual('queued', image['status'])
# Now try uploading an image file
file_content = data_utils.random_bytes()
image_file = six.BytesIO(file_content)
- self.client.store_image_file(image_id, image_file)
+ self.client.store_image_file(image['id'], image_file)
# Now try to get image details
- body = self.client.show_image(image_id)
- self.assertEqual(image_id, body['id'])
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
self.assertEqual(image_name, body['name'])
self.assertEqual(uuid, body['ramdisk_id'])
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
# Now try get image file
- body = self.client.show_image_file(image_id)
+ body = self.client.show_image_file(image['id'])
self.assertEqual(file_content, body.data)
@test.attr(type='smoke')
@@ -84,20 +83,18 @@
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
- body = self.client.create_image(name=image_name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private')
- image_id = body['id']
-
+ image = self.client.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)
+ self.client.delete_image(image['id'])
+ self.client.wait_for_resource_deletion(image['id'])
# Verifying deletion
images = self.client.list_images()['images']
images_id = [item['id'] for item in images]
- self.assertNotIn(image_id, images_id)
+ self.assertNotIn(image['id'], images_id)
@test.attr(type='smoke')
@test.idempotent_id('f66891a7-a35c-41a8-b590-a065c2a1caa6')
@@ -108,27 +105,26 @@
image_name = data_utils.rand_name('image')
container_format = CONF.image.container_formats[0]
disk_format = CONF.image.disk_formats[0]
- body = self.client.create_image(name=image_name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private')
- self.addCleanup(self.client.delete_image, body['id'])
- self.assertEqual('queued', body['status'])
- image_id = body['id']
+ 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'])
+ self.assertEqual('queued', image['status'])
# Now try uploading an image file
image_file = six.BytesIO(data_utils.random_bytes())
- self.client.store_image_file(image_id, image_file)
+ self.client.store_image_file(image['id'], image_file)
# Update Image
new_image_name = data_utils.rand_name('new-image')
- body = self.client.update_image(image_id, [
+ body = self.client.update_image(image['id'], [
dict(replace='/name', value=new_image_name)])
# Verifying updating
- body = self.client.show_image(image_id)
- self.assertEqual(image_id, body['id'])
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
self.assertEqual(new_image_name, body['name'])
@@ -162,14 +158,13 @@
size = random.randint(1024, 4096)
image_file = six.BytesIO(data_utils.random_bytes(size))
name = data_utils.rand_name('image')
- body = cls.create_image(name=name,
- container_format=container_format,
- disk_format=disk_format,
- visibility='private')
- image_id = body['id']
- cls.client.store_image_file(image_id, data=image_file)
+ image = cls.create_image(name=name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private')
+ cls.client.store_image_file(image['id'], data=image_file)
- return image_id
+ return image['id']
def _list_by_param_value_and_assert(self, params):
"""Perform list action with given params and validates result."""
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index f60fb0c..cd1bca0 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -53,19 +53,19 @@
def test_get_delete_deleted_image(self):
# get and delete the deleted image
# create and delete image
- body = self.client.create_image(name='test',
- container_format='bare',
- disk_format='raw')
- image_id = body['id']
- self.client.delete_image(image_id)
- self.client.wait_for_resource_deletion(image_id)
+ image = self.client.create_image(name='test',
+ container_format='bare',
+ disk_format='raw')
+ self.client.delete_image(image['id'])
+ self.client.wait_for_resource_deletion(image['id'])
# get the deleted image
- self.assertRaises(lib_exc.NotFound, self.client.show_image, image_id)
+ self.assertRaises(lib_exc.NotFound,
+ self.client.show_image, image['id'])
# delete the deleted image
self.assertRaises(lib_exc.NotFound, self.client.delete_image,
- image_id)
+ image['id'])
@test.attr(type=['negative'])
@test.idempotent_id('6fe40f1c-57bd-4918-89cc-8500f850f3de')
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 42a4b87..03f29bd 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -21,19 +21,18 @@
@test.idempotent_id('10407036-6059-4f95-a2cd-cbbbee7ed329')
def test_update_delete_tags_for_image(self):
- body = self.create_image(container_format='bare',
- disk_format='raw',
- visibility='private')
- image_id = body['id']
+ image = self.create_image(container_format='bare',
+ disk_format='raw',
+ visibility='private')
tag = data_utils.rand_name('tag')
- self.addCleanup(self.client.delete_image, image_id)
+ self.addCleanup(self.client.delete_image, image['id'])
# Creating image tag and verify it.
- self.client.add_image_tag(image_id, tag)
- body = self.client.show_image(image_id)
+ self.client.add_image_tag(image['id'], tag)
+ body = self.client.show_image(image['id'])
self.assertIn(tag, body['tags'])
# Deleting image tag and verify it.
- self.client.delete_image_tag(image_id, tag)
- body = self.client.show_image(image_id)
+ self.client.delete_image_tag(image['id'], tag)
+ body = self.client.show_image(image['id'])
self.assertNotIn(tag, body['tags'])
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index dd5650f..af4ffcf 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -33,12 +33,11 @@
@test.idempotent_id('39c023a2-325a-433a-9eea-649bf1414b19')
def test_delete_non_existing_tag(self):
# Delete non existing tag.
- body = self.create_image(container_format='bare',
- disk_format='raw',
- visibility='private'
- )
- image_id = body['id']
+ image = self.create_image(container_format='bare',
+ disk_format='raw',
+ visibility='private'
+ )
tag = data_utils.rand_name('non-exist-tag')
- self.addCleanup(self.client.delete_image, image_id)
+ self.addCleanup(self.client.delete_image, image['id'])
self.assertRaises(lib_exc.NotFound, self.client.delete_image_tag,
- image_id, tag)
+ image['id'], tag)
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
new file mode 100644
index 0000000..ed99eb2
--- /dev/null
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -0,0 +1,167 @@
+# 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.object_storage import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
+from tempest import test
+
+
+class ContainerNegativeTest(base.BaseObjectTest):
+
+ @classmethod
+ 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']
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('30686921-4bed-4764-a038-40d741ed4e78')
+ 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']
+ # create a container with long name
+ container_name = data_utils.arbitrary_string(size=max_length + 1)
+ ex = self.assertRaises(exceptions.BadRequest,
+ self.container_client.create_container,
+ container_name)
+ self.assertIn('Container name length of ' + str(max_length + 1) +
+ ' longer than ' + str(max_length), str(ex))
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('41e645bf-2e68-4f84-bf7b-c71aa5cd76ce')
+ def test_create_container_metadata_name_exceeds_max_length(self):
+ # Attempts to create container with metadata name
+ # that is longer than max.
+ max_length = self.constraints['max_meta_name_length']
+ container_name = data_utils.rand_name(name='TestContainer')
+ metadata_name = data_utils.arbitrary_string(size=max_length + 1)
+ metadata = {metadata_name: 'penguin'}
+ ex = self.assertRaises(exceptions.BadRequest,
+ self.container_client.create_container,
+ container_name, metadata=metadata)
+ self.assertIn('Metadata name too long', str(ex))
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('81e36922-326b-4b7c-8155-3bbceecd7a82')
+ def test_create_container_metadata_value_exceeds_max_length(self):
+ # Attempts to create container with metadata value
+ # that is longer than max.
+ max_length = self.constraints['max_meta_value_length']
+ container_name = data_utils.rand_name(name='TestContainer')
+ metadata_value = data_utils.arbitrary_string(size=max_length + 1)
+ metadata = {'animal': metadata_value}
+ ex = self.assertRaises(exceptions.BadRequest,
+ self.container_client.create_container,
+ container_name, metadata=metadata)
+ self.assertIn('Metadata value longer than ' + str(max_length), str(ex))
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('ac666539-d566-4f02-8ceb-58e968dfb732')
+ def test_create_container_metadata_exceeds_overall_metadata_count(self):
+ # Attempts to create container with metadata that exceeds the
+ # default count
+ max_count = self.constraints['max_meta_count']
+ container_name = data_utils.rand_name(name='TestContainer')
+ metadata = {}
+ for i in range(max_count + 1):
+ metadata['animal-' + str(i)] = 'penguin'
+
+ ex = self.assertRaises(exceptions.BadRequest,
+ self.container_client.create_container,
+ container_name, metadata=metadata)
+ self.assertIn('Too many metadata items; max ' + str(max_count),
+ str(ex))
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('1a95ab2e-b712-4a98-8a4d-8ce21b7557d6')
+ 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)
+
+ @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)
+
+ @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)
+
+ @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")
+
+ self.assertRaises(exceptions.NotFound,
+ self.container_client.list_all_container_objects,
+ nonexistent_name)
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('86b2ab08-92d5-493d-acd2-85f0c848819e')
+ def test_list_all_container_objects_on_deleted_container(self):
+ # Attempts to get a listing of all objects on a container
+ # that was deleted.
+ container_name = self.create_container()
+ # delete container
+ resp, _ = self.container_client.delete_container(container_name)
+ self.assertHeaders(resp, 'Container', 'DELETE')
+
+ self.assertRaises(exceptions.NotFound,
+ self.container_client.list_all_container_objects,
+ container_name)
+
+ @test.attr(type=["negative"])
+ @test.idempotent_id('42da116e-1e8c-4c96-9e06-2f13884ed2b1')
+ def test_delete_non_empty_container(self):
+ # create a container and an object within it
+ # attempt to delete a container that isn't empty.
+ container_name = self.create_container()
+ self.addCleanup(self.container_client.delete_container,
+ container_name)
+ object_name, _ = self.create_object(container_name)
+ self.addCleanup(self.object_client.delete_object,
+ container_name, object_name)
+
+ ex = self.assertRaises(exceptions.Conflict,
+ self.container_client.delete_container,
+ container_name)
+ self.assertIn('An object with that identifier already exists',
+ str(ex))
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 8e9f0b0..318eb10 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -129,7 +129,6 @@
**kwargs)
# handle the case of multiple servers
- servers = []
if multiple_create_request:
# Get servers created which name match with name param.
body_servers = clients.servers_client.list_servers()
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 272f6e3..da32693 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -53,10 +53,6 @@
message = "Snapshot %(snapshot_id)s failed to build and is in ERROR status"
-class VolumeBackupException(exceptions.TempestException):
- message = "Volume backup %(backup_id)s failed and is in ERROR status"
-
-
class StackBuildErrorException(exceptions.TempestException):
message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
"due to '%(stack_status_reason)s'")
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index e3f25e6..a5c6b1b 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -239,3 +239,7 @@
class PluginRegistrationException(TempestException):
message = "Error registering plugin %(name)s: %(detailed_error)s"
+
+
+class VolumeBackupException(TempestException):
+ message = "Volume backup %(backup_id)s failed and is in ERROR status"
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/lib/services/volume/v1/backups_client.py
similarity index 96%
rename from tempest/services/volume/base/base_backups_client.py
rename to tempest/lib/services/volume/v1/backups_client.py
index 1b35feb..2728c67 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/lib/services/volume/v1/backups_client.py
@@ -19,8 +19,9 @@
from tempest.lib import exceptions as lib_exc
-class BaseBackupsClient(rest_client.RestClient):
- """Client class to send CRUD Volume backup API requests"""
+class BackupsClient(rest_client.RestClient):
+ """Volume V1 Backups client"""
+ api_version = "v1"
def create_backup(self, **kwargs):
"""Creates a backup of volume.
diff --git a/tempest/services/volume/base/base_backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
similarity index 96%
copy from tempest/services/volume/base/base_backups_client.py
copy to tempest/lib/services/volume/v2/backups_client.py
index 1b35feb..61f865d 100644
--- a/tempest/services/volume/base/base_backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -19,8 +19,9 @@
from tempest.lib import exceptions as lib_exc
-class BaseBackupsClient(rest_client.RestClient):
- """Client class to send CRUD Volume backup API requests"""
+class BackupsClient(rest_client.RestClient):
+ """Volume V2 Backups client"""
+ api_version = "v2"
def create_backup(self, **kwargs):
"""Creates a backup of volume.
diff --git a/tempest/lib/services/volume/v2/qos_client.py b/tempest/lib/services/volume/v2/qos_client.py
index 5fac00f..40d4a3f 100644
--- a/tempest/lib/services/volume/v2/qos_client.py
+++ b/tempest/lib/services/volume/v2/qos_client.py
@@ -41,8 +41,11 @@
def create_qos(self, **kwargs):
"""Create a QoS Specification.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#createQoSSpec
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/index.html
+ ?expanded=create-qos-specification-detail
+ #quality-of-service-qos-specifications-qos-specs
"""
post_body = json.dumps({'qos_specs': kwargs})
resp, body = self.post('qos-specs', post_body)
@@ -76,8 +79,11 @@
def set_qos_key(self, qos_id, **kwargs):
"""Set the specified keys/values of QoS specification.
- Available params: see http://developer.openstack.org/
- api-ref-blockstorage-v2.html#setQoSKey
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/index.html
+ ?expanded=set-keys-in-qos-specification-detail
+ #quality-of-service-qos-specifications-qos-specs
"""
put_body = json.dumps({"qos_specs": kwargs})
resp, body = self.put('qos-specs/%s' % qos_id, put_body)
@@ -90,7 +96,11 @@
:param keys: keys to delete from the QoS specification.
- TODO(jordanP): Add a link once LP #1524877 is fixed.
+ For a full list of available parameters, please refer to the official
+ API reference:
+ http://developer.openstack.org/api-ref/block-storage/v2/index.html
+ ?expanded=unset-keys-in-qos-specification-detail
+ #quality-of-service-qos-specifications-qos-specs
"""
put_body = json.dumps({'keys': keys})
resp, body = self.put('qos-specs/%s/delete_keys' % qos_id, put_body)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3f6d9c4..44ad136 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -24,18 +24,6 @@
class TestVolumeBootPattern(manager.ScenarioTest):
- """This test case attempts to reproduce the following steps:
-
- * Create in Cinder some bootable volume importing a Glance image
- * Boot an instance from the bootable volume
- * Write content to the volume
- * Delete an instance and Boot a new instance from the volume
- * Check written content in the instance
- * Create a volume snapshot while the instance is running
- * Boot an additional instance from the new snapshot based volume
- * Check written content in the instance booted from snapshot
- """
-
# Boot from volume scenario is quite slow, and needs extra
# breathing room to get through deletes in the time allotted.
TIMEOUT_SCALING_FACTOR = 2
@@ -113,6 +101,19 @@
@test.attr(type='smoke')
@test.services('compute', 'volume', 'image')
def test_volume_boot_pattern(self):
+
+ """This test case attempts to reproduce the following steps:
+
+ * Create in Cinder some bootable volume importing a Glance image
+ * Boot an instance from the bootable volume
+ * Write content to the volume
+ * Delete an instance and Boot a new instance from the volume
+ * Check written content in the instance
+ * Create a volume snapshot while the instance is running
+ * Boot an additional instance from the new snapshot based volume
+ * Check written content in the instance booted from snapshot
+ """
+
LOG.info("Creating keypair and security group")
keypair = self.create_keypair()
security_group = self._create_security_group()
diff --git a/tempest/services/volume/v1/__init__.py b/tempest/services/volume/v1/__init__.py
index e386faf..376ab72 100644
--- a/tempest/services/volume/v1/__init__.py
+++ b/tempest/services/volume/v1/__init__.py
@@ -14,6 +14,7 @@
from tempest.lib.services.volume.v1.availability_zone_client import \
AvailabilityZoneClient
+from tempest.lib.services.volume.v1.backups_client import BackupsClient
from tempest.lib.services.volume.v1.encryption_types_client import \
EncryptionTypesClient
from tempest.lib.services.volume.v1.extensions_client import ExtensionsClient
@@ -23,7 +24,6 @@
from tempest.lib.services.volume.v1.services_client import ServicesClient
from tempest.lib.services.volume.v1.snapshots_client import SnapshotsClient
from tempest.lib.services.volume.v1.types_client import TypesClient
-from tempest.services.volume.v1.json.backups_client import BackupsClient
from tempest.services.volume.v1.json.volumes_client import VolumesClient
__all__ = ['AvailabilityZoneClient', 'EncryptionTypesClient',
diff --git a/tempest/services/volume/v1/json/backups_client.py b/tempest/services/volume/v1/json/backups_client.py
deleted file mode 100644
index ac6db6a..0000000
--- a/tempest/services/volume/v1/json/backups_client.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base import base_backups_client
-
-
-class BackupsClient(base_backups_client.BaseBackupsClient):
- """Volume V1 Backups client"""
diff --git a/tempest/services/volume/v2/__init__.py b/tempest/services/volume/v2/__init__.py
index fcc34f9..5774977 100644
--- a/tempest/services/volume/v2/__init__.py
+++ b/tempest/services/volume/v2/__init__.py
@@ -14,6 +14,7 @@
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.encryption_types_client import \
EncryptionTypesClient
from tempest.lib.services.volume.v2.extensions_client import ExtensionsClient
@@ -23,10 +24,9 @@
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
-from tempest.services.volume.v2.json.backups_client import BackupsClient
from tempest.services.volume.v2.json.volumes_client import VolumesClient
-__all__ = ['AvailabilityZoneClient', 'EncryptionTypesClient',
- 'ExtensionsClient', 'HostsClient', 'QosSpecsClient',
- 'QuotasClient', 'ServicesClient', 'SnapshotsClient',
- 'TypesClient', 'BackupsClient', 'VolumesClient', ]
+__all__ = ['AvailabilityZoneClient', 'BackupsClient', 'EncryptionTypesClient',
+ 'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
+ 'ServicesClient', 'SnapshotsClient', 'TypesClient',
+ 'VolumesClient', ]
diff --git a/tempest/services/volume/v2/json/backups_client.py b/tempest/services/volume/v2/json/backups_client.py
deleted file mode 100644
index 78bab82..0000000
--- a/tempest/services/volume/v2/json/backups_client.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.services.volume.base import base_backups_client
-
-
-class BackupsClient(base_backups_client.BaseBackupsClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
diff --git a/tox.ini b/tox.ini
index 7096e60..02eef78 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,7 +26,7 @@
-r{toxinidir}/test-requirements.txt
commands =
find . -type f -name "*.pyc" -delete
- bash tools/pretty_tox.sh '{posargs}'
+ ostestr {posargs}
[testenv:genconfig]
commands = oslo-config-generator --config-file tempest/cmd/config-generator.tempest.conf