Merge "Migrate backups clients to tempest.lib"
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/requirements.txt b/requirements.txt
index 4655b9f..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
+unittest2 # BSD
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/volumes/test_volume_snapshots.py b/tempest/api/compute/volumes/test_volume_snapshots.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
old mode 100755
new mode 100644
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
old mode 100755
new mode 100644
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
old mode 100755
new mode 100644
index f74f97b..26b88b0
--- 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/volume/admin/test_qos.py b/tempest/api/volume/admin/test_qos.py
old mode 100755
new mode 100644
index 98139e7..9f2d453
--- a/tempest/api/volume/admin/test_qos.py
+++ b/tempest/api/volume/admin/test_qos.py
@@ -14,6 +14,7 @@
from tempest.api.volume import base
from tempest.common.utils import data_utils as utils
+from tempest.common import waiters
from tempest import test
@@ -119,7 +120,9 @@
self.admin_volume_qos_client.unset_qos_key(self.created_qos['id'],
keys)
operation = 'qos-key-unset'
- self.wait_for_qos_operations(self.created_qos['id'], operation, keys)
+ waiters.wait_for_qos_operations(self.admin_volume_qos_client,
+ self.created_qos['id'],
+ operation, keys)
body = self.admin_volume_qos_client.show_qos(
self.created_qos['id'])['qos_specs']
self.assertNotIn(keys[0], body['specs'])
@@ -153,8 +156,9 @@
self.admin_volume_qos_client.disassociate_qos(
self.created_qos['id'], vol_type[0]['id'])
operation = 'disassociate'
- self.wait_for_qos_operations(self.created_qos['id'],
- operation, vol_type[0]['id'])
+ waiters.wait_for_qos_operations(self.admin_volume_qos_client,
+ self.created_qos['id'], operation,
+ vol_type[0]['id'])
associations = self._test_get_association_qos()
self.assertNotIn(vol_type[0]['id'], associations)
@@ -162,7 +166,8 @@
self.admin_volume_qos_client.disassociate_all_qos(
self.created_qos['id'])
operation = 'disassociate-all'
- self.wait_for_qos_operations(self.created_qos['id'], operation)
+ waiters.wait_for_qos_operations(self.admin_volume_qos_client,
+ self.created_qos['id'], operation)
associations = self._test_get_association_qos()
self.assertEmpty(associations)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index ada55f7..b49a126 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,15 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
-
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest.lib.common.utils import test_utils
-from tempest.lib import exceptions as lib_exc
import tempest.test
CONF = config.CONF
@@ -277,35 +274,3 @@
test_utils.call_and_ignore_notfound_exc(
cls.admin_volume_types_client.wait_for_resource_deletion,
vol_type)
-
- def wait_for_qos_operations(self, qos_id, operation, args=None):
- """Waits for a qos operations to be completed.
-
- NOTE : operation value is required for wait_for_qos_operations()
- operation = 'qos-key' / 'disassociate' / 'disassociate-all'
- args = keys[] when operation = 'qos-key'
- args = volume-type-id disassociated when operation = 'disassociate'
- args = None when operation = 'disassociate-all'
- """
- start_time = int(time.time())
- client = self.admin_volume_qos_client
- while True:
- if operation == 'qos-key-unset':
- body = client.show_qos(qos_id)['qos_specs']
- if not any(key in body['specs'] for key in args):
- return
- elif operation == 'disassociate':
- body = client.show_association_qos(qos_id)['qos_associations']
- if not any(args in body[i]['id'] for i in range(0, len(body))):
- return
- elif operation == 'disassociate-all':
- body = client.show_association_qos(qos_id)['qos_associations']
- if not body:
- return
- else:
- msg = (" operation value is either not defined or incorrect.")
- raise lib_exc.UnprocessableEntity(msg)
-
- if int(time.time()) - start_time >= self.build_timeout:
- raise exceptions.TimeoutException
- time.sleep(self.build_interval)
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
old mode 100755
new mode 100644
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
old mode 100755
new mode 100644
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 57769b9..fa951b5 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -258,3 +258,35 @@
if caller:
message = '(%s) %s' % (caller, message)
raise exceptions.TimeoutException(message)
+
+
+def wait_for_qos_operations(client, qos_id, operation, args=None):
+ """Waits for a qos operations to be completed.
+
+ NOTE : operation value is required for wait_for_qos_operations()
+ operation = 'qos-key' / 'disassociate' / 'disassociate-all'
+ args = keys[] when operation = 'qos-key'
+ args = volume-type-id disassociated when operation = 'disassociate'
+ args = None when operation = 'disassociate-all'
+ """
+ start_time = int(time.time())
+ while True:
+ if operation == 'qos-key-unset':
+ body = client.show_qos(qos_id)['qos_specs']
+ if not any(key in body['specs'] for key in args):
+ return
+ elif operation == 'disassociate':
+ body = client.show_association_qos(qos_id)['qos_associations']
+ if not any(args in body[i]['id'] for i in range(0, len(body))):
+ return
+ elif operation == 'disassociate-all':
+ body = client.show_association_qos(qos_id)['qos_associations']
+ if not body:
+ return
+ else:
+ msg = (" operation value is either not defined or incorrect.")
+ raise lib_exc.UnprocessableEntity(msg)
+
+ if int(time.time()) - start_time >= client.build_timeout:
+ raise exceptions.TimeoutException
+ time.sleep(client.build_interval)
diff --git a/tempest/lib/services/compute/agents_client.py b/tempest/lib/services/compute/agents_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/floating_ips_client.py b/tempest/lib/services/compute/floating_ips_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/keypairs_client.py b/tempest/lib/services/compute/keypairs_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/security_groups_client.py b/tempest/lib/services/compute/security_groups_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/compute/volumes_client.py b/tempest/lib/services/compute/volumes_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/identity/v2/services_client.py b/tempest/lib/services/identity/v2/services_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/floating_ips_client.py b/tempest/lib/services/network/floating_ips_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/metering_labels_client.py b/tempest/lib/services/network/metering_labels_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/ports_client.py b/tempest/lib/services/network/ports_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/routers_client.py b/tempest/lib/services/network/routers_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/security_group_rules_client.py b/tempest/lib/services/network/security_group_rules_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/security_groups_client.py b/tempest/lib/services/network/security_groups_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/subnetpools_client.py b/tempest/lib/services/network/subnetpools_client.py
old mode 100755
new mode 100644
diff --git a/tempest/lib/services/network/subnets_client.py b/tempest/lib/services/network/subnets_client.py
old mode 100755
new mode 100644
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
old mode 100755
new mode 100644
diff --git a/tempest/services/baremetal/v1/json/baremetal_client.py b/tempest/services/baremetal/v1/json/baremetal_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/volume/base/base_volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
old mode 100755
new mode 100644
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
old mode 100755
new mode 100644
diff --git a/test-requirements.txt b/test-requirements.txt
index 567cf20..2d15daa 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,7 @@
# process, which may cause wedges in the gate later.
hacking<0.12,>=0.11.0 # Apache-2.0
# needed for doc build
-sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
+sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD
oslosphinx>=4.7.0 # Apache-2.0
reno>=1.8.0 # Apache2
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