Merge "Remove install unnecessary packages"
diff --git a/.zuul.yaml b/.zuul.yaml
index 9c53ba9..5e3f33a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -13,6 +13,8 @@
roles: &base_roles
- zuul: opendev.org/openstack/devstack
vars: &base_vars
+ # TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+ tempest_black_regex: "(tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume|tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON|tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached)"
devstack_services:
tempest: true
devstack_local_conf:
@@ -146,7 +148,7 @@
- job:
name: tempest-full-parallel
- parent: tempest-full
+ parent: tempest-full-py3
voting: false
branches:
- master
@@ -159,8 +161,6 @@
tox_envlist: full-parallel
run_tempest_cleanup: true
run_tempest_dry_cleanup: true
- devstack_localrc:
- USE_PYTHON3: True
- job:
name: tempest-full-py3
@@ -218,6 +218,7 @@
- job:
name: tempest-integrated-compute
parent: devstack-tempest
+ nodeset: openstack-single-node-bionic
branches: ^(?!stable/ocata).*$
description: |
This job runs integration tests for compute. This is
@@ -225,6 +226,7 @@
and Glance related tests. This is meant to be run on Nova gate only.
vars:
tox_envlist: integrated-compute
+ tempest_black_regex: ""
devstack_localrc:
USE_PYTHON3: true
FORCE_CONFIG_DRIVE: true
@@ -341,9 +343,28 @@
- job:
name: tempest-multinode-full
parent: tempest-multinode-full-base
+ nodeset: openstack-two-node-focal
+ # This job runs on Focal from stable/victoria on.
+ branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train|ussuri)).*$
+ vars:
+ devstack_localrc:
+ USE_PYTHON3: False
+ group-vars:
+ subnode:
+ devstack_localrc:
+ USE_PYTHON3: False
+
+- job:
+ name: tempest-multinode-full
+ parent: tempest-multinode-full-base
nodeset: openstack-two-node-bionic
- # This job runs on Bionic from stable/stein on.
- branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
+ # This job runs on Bionic and on python2. This is for stable/stein and stable/train.
+ # This job is prepared to make sure all stable branches from stable/stein till stable/train
+ # will keep running on bionic. This can be removed once stable/train is EOL.
+ branches:
+ - stable/stein
+ - stable/train
+ - stable/ussuri
vars:
devstack_localrc:
USE_PYTHON3: False
@@ -454,16 +475,19 @@
- job:
name: tempest-full-ussuri-py3
parent: tempest-full-py3
+ nodeset: openstack-single-node-bionic
override-checkout: stable/ussuri
- job:
name: tempest-full-train-py3
parent: tempest-full-py3
+ nodeset: openstack-single-node-bionic
override-checkout: stable/train
- job:
name: tempest-full-stein-py3
parent: tempest-full-py3
+ nodeset: openstack-single-node-bionic
override-checkout: stable/stein
- job:
@@ -471,7 +495,7 @@
parent: tox
description: |
Run tempest plugin sanity check script using tox.
- nodeset: ubuntu-bionic
+ nodeset: ubuntu-focal
vars:
tox_envlist: plugin-sanity-check
timeout: 5000
diff --git a/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
new file mode 100644
index 0000000..2b407ae
--- /dev/null
+++ b/releasenotes/notes/add-keystone-ep-clients-eeefd0904fbbe151.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added missing client methods for keystone's OS-ENDPOINT-POLICY and
+ OS-EP-FILTER APIs.
diff --git a/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml b/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml
new file mode 100644
index 0000000..ab8d748
--- /dev/null
+++ b/releasenotes/notes/add-keystone-v3-ec2-tests-d959b7d36f0bd7fc.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Added missing clients and tests for keystone's v3 EC2 API which already
+ existed for keystone v2.
diff --git a/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml b/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml
new file mode 100644
index 0000000..9e6d49a
--- /dev/null
+++ b/releasenotes/notes/new-placement-client-methods-e35c473e29494928.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ Add ``placement`` API methods for testing Routed Provider Networks feature.
+ The following API calls are available for tempest from now in the new
+ resource_providers_client:
+
+ * GET /resource_providers
+ * GET /resource_providers/{uuid}
+ * GET /resource_providers/{uuid}/inventories
+ * GET /resource_providers/{uuid}/aggregates
diff --git a/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
index bb91213..574f6d9 100644
--- a/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
+++ b/releasenotes/notes/tempest-victoria-release-27000c02edc5a112.yaml
@@ -1,3 +1,4 @@
+---
prelude: |
This release is to tag the Tempest for OpenStack Victoria release.
This release marks the start of Victoria release support in Tempest.
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index 37f5aec..89152d6 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -94,6 +94,16 @@
# Now boot a server with the copied flavor.
server = self.create_test_server(
wait_until='ACTIVE', flavor=flavor['id'])
+ server = self.servers_client.show_server(server['id'])['server']
+
+ # If 'id' not in server['flavor'], we can only compare the flavor
+ # details, so here we should save the to-be-deleted flavor's details,
+ # for the flavor comparison after the server resizing.
+ if not server['flavor'].get('id'):
+ pre_flavor = {}
+ body = self.flavors_client.show_flavor(flavor['id'])['flavor']
+ for key in ['name', 'ram', 'vcpus', 'disk']:
+ pre_flavor[key] = body[key]
# Delete the flavor we used to boot the instance.
self._flavor_clean_up(flavor['id'])
@@ -110,7 +120,18 @@
'ACTIVE')
server = self.servers_client.show_server(server['id'])['server']
- self.assert_flavor_equal(flavor['id'], server['flavor'])
+ if server['flavor'].get('id'):
+ msg = ('server flavor is not same as flavor!')
+ self.assertEqual(flavor['id'], server['flavor']['id'], msg)
+ else:
+ self.assertEqual(pre_flavor['name'],
+ server['flavor']['original_name'],
+ "original_name in server flavor is not same as "
+ "flavor name!")
+ for key in ['ram', 'vcpus', 'disk']:
+ msg = ('attribute %s in server flavor is not same as '
+ 'flavor!' % key)
+ self.assertEqual(pre_flavor[key], server['flavor'][key], msg)
def _test_cold_migrate_server(self, revert=False):
if CONF.compute.min_compute_nodes < 2:
diff --git a/tempest/api/compute/servers/test_server_metadata_negative.py b/tempest/api/compute/servers/test_server_metadata_negative.py
index a697b95..655909c 100644
--- a/tempest/api/compute/servers/test_server_metadata_negative.py
+++ b/tempest/api/compute/servers/test_server_metadata_negative.py
@@ -45,7 +45,7 @@
# Attempt to start a server with a meta-data key that is > 255
# characters
- # Tryset_server_metadata_item a few values
+ # Try create a server with the metadata for a few values
for sz in [256, 257, 511, 1023]:
key = "k" * sz
meta = {key: 'data1'}
@@ -86,11 +86,15 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0025fbd6-a4ba-4cde-b8c2-96805dcfdabc')
- def test_wrong_key_passed_in_body(self):
+ def test_set_metadata_invalid_key(self):
"""Test setting server metadata item with wrong key in body
Raise BadRequest if key in uri does not match the key passed in body.
"""
+ if not CONF.compute_feature_enabled.xenapi_apis:
+ raise self.skipException(
+ 'Metadata is read-only on non-Xen-based deployments.')
+
meta = {'testkey': 'testvalue'}
self.assertRaises(lib_exc.BadRequest,
self.client.set_server_metadata_item,
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index ba2adbb..8a05e7a 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -29,6 +29,7 @@
class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
"""Test servers with injected files"""
+ max_microversion = '2.56'
@classmethod
def setup_credentials(cls):
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 282343c..5722f0e 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -192,6 +192,7 @@
cls.os_primary.identity_versions_v3_client
cls.non_admin_app_creds_client = \
cls.os_primary.application_credentials_client
+ cls.non_admin_access_rules_client = cls.os_primary.access_rules_client
class BaseIdentityV3AdminTest(BaseIdentityV3Test):
diff --git a/tempest/api/identity/v3/test_access_rules.py b/tempest/api/identity/v3/test_access_rules.py
new file mode 100644
index 0000000..608eb59
--- /dev/null
+++ b/tempest/api/identity/v3/test_access_rules.py
@@ -0,0 +1,84 @@
+# Copyright 2019 SUSE LLC
+#
+# 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.identity import base
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+CONF = config.CONF
+
+
+class AccessRulesV3Test(base.BaseIdentityV3Test):
+
+ @classmethod
+ def skip_checks(cls):
+ super(AccessRulesV3Test, cls).skip_checks()
+ if not CONF.identity_feature_enabled.access_rules:
+ raise cls.skipException("Application credential access rules are "
+ "not available in this environment")
+
+ @classmethod
+ def resource_setup(cls):
+ super(AccessRulesV3Test, cls).resource_setup()
+ cls.user_id = cls.os_primary.credentials.user_id
+ cls.project_id = cls.os_primary.credentials.project_id
+
+ def setUp(self):
+ super(AccessRulesV3Test, self).setUp()
+ ac = self.non_admin_app_creds_client
+ access_rules = [
+ {
+ "path": "/v2.1/servers/*/ips",
+ "method": "GET",
+ "service": "compute"
+ }
+ ]
+ self.app_cred = ac.create_application_credential(
+ self.user_id,
+ name=data_utils.rand_name('application_credential'),
+ access_rules=access_rules
+ )['application_credential']
+
+ @decorators.idempotent_id('2354c498-5119-4ba5-9f0d-44f16f78fb0e')
+ def test_list_access_rules(self):
+ ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+ self.assertEqual(1, len(ar['access_rules']))
+
+ @decorators.idempotent_id('795dd507-ca1e-40e9-ba90-ff0a08689ba4')
+ def test_show_access_rule(self):
+ access_rule_id = self.app_cred['access_rules'][0]['id']
+ self.non_admin_access_rules_client.show_access_rule(
+ self.user_id, access_rule_id)
+
+ @decorators.idempotent_id('278757e9-e193-4bf8-adf2-0b0a229a17d0')
+ def test_delete_access_rule(self):
+ access_rule_id = self.app_cred['access_rules'][0]['id']
+ app_cred_id = self.app_cred['id']
+ self.assertRaises(
+ lib_exc.Forbidden,
+ self.non_admin_access_rules_client.delete_access_rule,
+ self.user_id,
+ access_rule_id)
+ self.non_admin_app_creds_client.delete_application_credential(
+ self.user_id, app_cred_id)
+ ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+ self.assertEqual(1, len(ar['access_rules']))
+ self.non_admin_access_rules_client.delete_access_rule(
+ self.user_id, access_rule_id)
+ ar = self.non_admin_access_rules_client.list_access_rules(self.user_id)
+ self.assertEqual(0, len(ar['access_rules']))
diff --git a/tempest/api/identity/v3/test_application_credentials.py b/tempest/api/identity/v3/test_application_credentials.py
index ef1bbdf..77ad720 100644
--- a/tempest/api/identity/v3/test_application_credentials.py
+++ b/tempest/api/identity/v3/test_application_credentials.py
@@ -19,8 +19,11 @@
from oslo_utils import timeutils
from tempest.api.identity import base
+from tempest import config
from tempest.lib import decorators
+CONF = config.CONF
+
class ApplicationCredentialsV3Test(base.BaseApplicationCredentialsV3Test):
"""Test application credentials"""
@@ -65,6 +68,24 @@
expires_str = expires_at.isoformat()
self.assertEqual(expires_str, app_cred['expires_at'])
+ @decorators.idempotent_id('529936eb-aa5d-463d-9f79-01c113d3b88f')
+ def test_create_application_credential_access_rules(self):
+ if not CONF.identity_feature_enabled.access_rules:
+ raise self.skipException("Application credential access rules are "
+ "not available in this environment")
+ access_rules = [
+ {
+ "path": "/v2.1/servers/*/ips",
+ "method": "GET",
+ "service": "compute"
+ }
+ ]
+ app_cred = self.create_application_credential(
+ access_rules=access_rules)
+ access_rule_resp = app_cred['access_rules'][0]
+ access_rule_resp.pop('id')
+ self.assertDictEqual(access_rules[0], access_rule_resp)
+
@decorators.idempotent_id('ff0cd457-6224-46e7-b79e-0ada4964a8a6')
def test_list_application_credentials(self):
"""Test listing application credentials"""
diff --git a/tempest/api/identity/v3/test_ec2_credentials.py b/tempest/api/identity/v3/test_ec2_credentials.py
new file mode 100644
index 0000000..a2cbc4a
--- /dev/null
+++ b/tempest/api/identity/v3/test_ec2_credentials.py
@@ -0,0 +1,113 @@
+# Copyright 2020 SUSE LLC
+# 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.identity import base
+from tempest.common import utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+
+class EC2CredentialsTest(base.BaseIdentityV3Test):
+
+ @classmethod
+ def skip_checks(cls):
+ super(EC2CredentialsTest, cls).skip_checks()
+ if not utils.is_extension_enabled('OS-EC2', 'identity'):
+ msg = "OS-EC2 identity extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(EC2CredentialsTest, cls).resource_setup()
+ cls.creds = cls.os_primary.credentials
+
+ @decorators.idempotent_id('b0f55a29-54e5-4166-999d-712347e0c920')
+ def test_create_ec2_credential(self):
+ """Create user ec2 credential."""
+ resp = self.non_admin_users_client.create_user_ec2_credential(
+ self.creds.user_id,
+ tenant_id=self.creds.tenant_id)["credential"]
+ access = resp['access']
+ self.addCleanup(
+ self.non_admin_users_client.delete_user_ec2_credential,
+ self.creds.user_id, access)
+ self.assertNotEmpty(resp['access'])
+ self.assertNotEmpty(resp['secret'])
+ self.assertEqual(self.creds.user_id, resp['user_id'])
+ self.assertEqual(self.creds.tenant_id, resp['tenant_id'])
+
+ @decorators.idempotent_id('897813f0-160c-4fdc-aabc-24ee635ce4a9')
+ def test_list_ec2_credentials(self):
+ """Get the list of user ec2 credentials."""
+ created_creds = []
+ # create first ec2 credentials
+ creds1 = self.non_admin_users_client.create_user_ec2_credential(
+ self.creds.user_id,
+ tenant_id=self.creds.tenant_id)["credential"]
+ created_creds.append(creds1['access'])
+ self.addCleanup(
+ self.non_admin_users_client.delete_user_ec2_credential,
+ self.creds.user_id, creds1['access'])
+
+ # create second ec2 credentials
+ creds2 = self.non_admin_users_client.create_user_ec2_credential(
+ self.creds.user_id,
+ tenant_id=self.creds.tenant_id)["credential"]
+ created_creds.append(creds2['access'])
+ self.addCleanup(
+ self.non_admin_users_client.delete_user_ec2_credential,
+ self.creds.user_id, creds2['access'])
+
+ # get the list of user ec2 credentials
+ resp = self.non_admin_users_client.list_user_ec2_credentials(
+ self.creds.user_id)["credentials"]
+ fetched_creds = [cred['access'] for cred in resp]
+ # created credentials should be in a fetched list
+ missing = [cred for cred in created_creds
+ if cred not in fetched_creds]
+ self.assertEmpty(missing,
+ "Failed to find ec2_credentials %s in fetched list" %
+ ', '.join(cred for cred in missing))
+
+ @decorators.idempotent_id('8b8d1010-5958-48df-a6cd-5e3df72e6bcf')
+ def test_show_ec2_credential(self):
+ """Get the definite user ec2 credential."""
+ resp = self.non_admin_users_client.create_user_ec2_credential(
+ self.creds.user_id,
+ tenant_id=self.creds.tenant_id)["credential"]
+ self.addCleanup(
+ self.non_admin_users_client.delete_user_ec2_credential,
+ self.creds.user_id, resp['access'])
+
+ ec2_creds = self.non_admin_users_client.show_user_ec2_credential(
+ self.creds.user_id, resp['access']
+ )["credential"]
+ for key in ['access', 'secret', 'user_id', 'tenant_id']:
+ self.assertEqual(ec2_creds[key], resp[key])
+
+ @decorators.idempotent_id('9408d61b-8be0-4a8d-9b85-14f61edb456b')
+ def test_delete_ec2_credential(self):
+ """Delete user ec2 credential."""
+ resp = self.non_admin_users_client.create_user_ec2_credential(
+ self.creds.user_id,
+ tenant_id=self.creds.tenant_id)["credential"]
+ access = resp['access']
+ self.non_admin_users_client.delete_user_ec2_credential(
+ self.creds.user_id, access)
+ self.assertRaises(
+ lib_exc.NotFound,
+ self.non_admin_users_client.show_user_ec2_credential,
+ self.creds.user_id,
+ access)
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index ae7b3e4..d3dc19a 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -18,6 +18,7 @@
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
import tempest.test
CONF = config.CONF
@@ -155,6 +156,15 @@
namespace_name)
return namespace
+ @classmethod
+ def get_available_stores(cls):
+ stores = []
+ try:
+ stores = cls.client.info_stores()['stores']
+ except exceptions.NotFound:
+ pass
+ return stores
+
class BaseV2MemberImageTest(BaseV2ImageTest):
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index c1a7211..28299a4 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -20,6 +20,7 @@
from oslo_log import log as logging
from tempest.api.image import base
+from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -113,6 +114,95 @@
self.client.wait_for_resource_activation(image['id'])
+class MultiStoresImportImagesTest(base.BaseV2ImageTest):
+ """Test importing image in multiple stores"""
+ @classmethod
+ def skip_checks(cls):
+ super(MultiStoresImportImagesTest, cls).skip_checks()
+ if not CONF.image_feature_enabled.import_image:
+ skip_msg = (
+ "%s skipped as image import is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(MultiStoresImportImagesTest, cls).resource_setup()
+ cls.available_import_methods = cls.client.info_import()[
+ 'import-methods']['value']
+ if not cls.available_import_methods:
+ raise cls.skipException('Server does not support '
+ 'any import method')
+
+ # NOTE(pdeore): Skip if glance-direct import method and mutlistore
+ # are not enabled/configured, or only one store is configured in
+ # multiple stores setup.
+ cls.available_stores = cls.get_available_stores()
+ if ('glance-direct' not in cls.available_import_methods or
+ not len(cls.available_stores) > 1):
+ raise cls.skipException(
+ 'Either glance-direct import method not present in %s or '
+ 'None or only one store is '
+ 'configured %s' % (cls.available_import_methods,
+ cls.available_stores))
+
+ def _create_and_stage_image(self, all_stores=False):
+ """Create Image & stage image file for glance-direct import method."""
+ image_name = data_utils.rand_name('test-image')
+ container_format = CONF.image.container_formats[0]
+ disk_format = CONF.image.disk_formats[0]
+ image = self.create_image(name=image_name,
+ container_format=container_format,
+ disk_format=disk_format,
+ visibility='private')
+ self.assertEqual('queued', image['status'])
+
+ self.client.stage_image_file(
+ image['id'],
+ six.BytesIO(data_utils.random_bytes(10485760)))
+ # Check image status is 'uploading'
+ body = self.client.show_image(image['id'])
+ self.assertEqual(image['id'], body['id'])
+ self.assertEqual('uploading', body['status'])
+
+ if all_stores:
+ stores_list = ','.join([store['id']
+ for store in self.available_stores])
+ else:
+ stores = [store['id'] for store in self.available_stores]
+ stores_list = stores[::len(stores) - 1]
+
+ return body, stores_list
+
+ @decorators.idempotent_id('bf04ff00-3182-47cb-833a-f1c6767b47fd')
+ def test_glance_direct_import_image_to_all_stores(self):
+ """Test image is imported in all available stores
+
+ Create image, import image to all available stores using glance-direct
+ import method and verify that import succeeded.
+ """
+ image, stores = self._create_and_stage_image(all_stores=True)
+
+ self.client.image_import(
+ image['id'], method='glance-direct', all_stores=True)
+
+ waiters.wait_for_image_imported_to_stores(self.client,
+ image['id'], stores)
+
+ @decorators.idempotent_id('82fb131a-dd2b-11ea-aec7-340286b6c574')
+ def test_glance_direct_import_image_to_specific_stores(self):
+ """Test image is imported in all available stores
+
+ Create image, import image to specified store(s) using glance-direct
+ import method and verify that import succeeded.
+ """
+ image, stores = self._create_and_stage_image()
+ self.client.image_import(image['id'], method='glance-direct',
+ stores=stores)
+
+ waiters.wait_for_image_imported_to_stores(self.client, image['id'],
+ (','.join(stores)))
+
+
class BasicOperationsImagesTest(base.BaseV2ImageTest):
"""Here we test the basic operations of images"""
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index c5c30e3..da8ad66 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -28,6 +28,7 @@
class AccountTest(base.BaseObjectTest):
+ """Test account metadata and containers"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -54,7 +55,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('3499406a-ae53-4f8c-b43a-133d4dc6fe3f')
def test_list_containers(self):
- # list of all containers should not be empty
+ """Test listing containers"""
resp, container_list = self.account_client.list_account_containers()
self.assertHeaders(resp, 'Account', 'GET')
@@ -66,11 +67,10 @@
@decorators.idempotent_id('884ec421-fbad-4fcc-916b-0580f2699565')
def test_list_no_containers(self):
- # List request to empty account
+ """Test listing containers for an account without container"""
# To test listing no containers, create new user other than
# the base user of this instance.
-
resp, container_list = \
self.os_operator.account_client.list_account_containers()
@@ -103,7 +103,7 @@
@decorators.idempotent_id('1c7efa35-e8a2-4b0b-b5ff-862c7fd83704')
def test_list_containers_with_format_json(self):
- # list containers setting format parameter to 'json'
+ """Test listing containers setting format parameter to 'json'"""
params = {'format': 'json'}
resp, container_list = self.account_client.list_account_containers(
params=params)
@@ -115,7 +115,7 @@
@decorators.idempotent_id('4477b609-1ca6-4d4b-b25d-ad3f01086089')
def test_list_containers_with_format_xml(self):
- # list containers setting format parameter to 'xml'
+ """Test listing containers setting format parameter to 'xml'"""
params = {'format': 'xml'}
resp, container_list = self.account_client.list_account_containers(
params=params)
@@ -133,13 +133,18 @@
not CONF.object_storage_feature_enabled.discoverability,
'Discoverability function is disabled')
def test_list_extensions(self):
+ """Test listing capabilities"""
resp = self.capabilities_client.list_capabilities()
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
@decorators.idempotent_id('5cfa4ab2-4373-48dd-a41f-a532b12b08b2')
def test_list_containers_with_limit(self):
- # list containers one of them, half of them then all of them
+ """Test listing containers with limit parameter
+
+ Listing containers limited to one of them, half of them, and then all
+ of them.
+ """
for limit in (1, self.containers_count // 2,
self.containers_count):
params = {'limit': limit}
@@ -151,10 +156,11 @@
@decorators.idempotent_id('638f876d-6a43-482a-bbb3-0840bca101c6')
def test_list_containers_with_marker(self):
- # list containers using marker param
- # first expect to get 0 container as we specified last
- # the container as marker
- # second expect to get the bottom half of the containers
+ """Test listing containers with marker parameter
+
+ First expect to get 0 container as we specified the last container
+ as marker, second expect to get the bottom half of the containers.
+ """
params = {'marker': self.containers[-1]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
@@ -172,10 +178,11 @@
@decorators.idempotent_id('5ca164e4-7bde-43fa-bafb-913b53b9e786')
def test_list_containers_with_end_marker(self):
- # list containers using end_marker param
- # first expect to get 0 container as we specified first container as
- # end_marker
- # second expect to get the top half of the containers
+ """Test listing containers with end_marker parameter
+
+ First expect to get 0 container as we specified first container as
+ end_marker, second expect to get the top half of the containers
+ """
params = {'end_marker': self.containers[0]}
resp, container_list = \
self.account_client.list_account_containers(params=params)
@@ -190,7 +197,12 @@
@decorators.idempotent_id('ac8502c2-d4e4-4f68-85a6-40befea2ef5e')
def test_list_containers_with_marker_and_end_marker(self):
- # list containers combining marker and end_marker param
+ """Test listing containers with marker and end_marker parameter
+
+ If we use the first container as marker, and the last container as
+ end_marker, then we should get all containers excluding the first one
+ and the last one.
+ """
params = {'marker': self.containers[0],
'end_marker': self.containers[self.containers_count - 1]}
resp, container_list = self.account_client.list_account_containers(
@@ -200,8 +212,10 @@
@decorators.idempotent_id('f7064ae8-dbcc-48da-b594-82feef6ea5af')
def test_list_containers_with_limit_and_marker(self):
- # list containers combining marker and limit param
- # result are always limitated by the limit whatever the marker
+ """Test listing containers combining marker and limit parameter
+
+ Result are always limited by the limit whatever the marker.
+ """
for marker in random.choice(self.containers):
limit = random.randint(0, self.containers_count - 1)
params = {'marker': marker,
@@ -215,6 +229,10 @@
@decorators.idempotent_id('888a3f0e-7214-4806-8e50-5e0c9a69bb5e')
def test_list_containers_with_limit_and_end_marker(self):
+ """Test listing containers combining end_marker and limit parameter
+
+ Result are always limited by the limit whatever the end_marker.
+ """
# list containers combining limit and end_marker param
limit = random.randint(1, self.containers_count)
params = {'limit': limit,
@@ -227,7 +245,11 @@
@decorators.idempotent_id('8cf98d9c-e3a0-4e44-971b-c87656fdddbd')
def test_list_containers_with_limit_and_marker_and_end_marker(self):
- # list containers combining limit, marker and end_marker param
+ """Test listing containers combining marker and end_marker and limit
+
+ Result are always limited by the limit whatever the marker and the
+ end_marker.
+ """
limit = random.randint(1, self.containers_count)
params = {'limit': limit,
'marker': self.containers[0],
@@ -240,7 +262,7 @@
@decorators.idempotent_id('365e6fc7-1cfe-463b-a37c-8bd08d47b6aa')
def test_list_containers_with_prefix(self):
- # list containers that have a name that starts with a prefix
+ """Test listing containers that have a name starting with a prefix"""
prefix = 'tempest-a'
params = {'prefix': prefix}
resp, container_list = self.account_client.list_account_containers(
@@ -252,7 +274,7 @@
@decorators.idempotent_id('b1811cff-d1ed-4c15-a52e-efd8de41cf34')
def test_list_containers_reverse_order(self):
- # list containers in reverse order
+ """Test listing containers in reverse order"""
_, orig_container_list = self.account_client.list_account_containers()
params = {'reverse': True}
@@ -265,8 +287,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('4894c312-6056-4587-8d6f-86ffbf861f80')
def test_list_account_metadata(self):
- # list all account metadata
-
+ """Test listing account metadata"""
# set metadata to account
metadata = {'test-account-meta1': 'Meta1',
'test-account-meta2': 'Meta2'}
@@ -282,14 +303,14 @@
@decorators.idempotent_id('b904c2e3-24c2-4dba-ad7d-04e90a761be5')
def test_list_no_account_metadata(self):
- # list no account metadata
+ """Test listing account metadata for account without metadata"""
resp, _ = self.account_client.list_account_metadata()
self.assertHeaders(resp, 'Account', 'HEAD')
self.assertNotIn('x-account-meta-', str(resp))
@decorators.idempotent_id('e2a08b5f-3115-4768-a3ee-d4287acd6c08')
def test_update_account_metadata_with_create_metadata(self):
- # add metadata to account
+ """Test adding metadata to account"""
metadata = {'test-account-meta1': 'Meta1'}
resp, _ = self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata)
@@ -305,7 +326,7 @@
@decorators.idempotent_id('9f60348d-c46f-4465-ae06-d51dbd470953')
def test_update_account_metadata_with_delete_metadata(self):
- # delete metadata from account
+ """Test deleting metadata from account"""
metadata = {'test-account-meta1': 'Meta1'}
self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata)
@@ -318,8 +339,11 @@
@decorators.idempotent_id('64fd53f3-adbd-4639-af54-436e4982dbfb')
def test_update_account_metadata_with_create_metadata_key(self):
- # if the value of metadata is not set, the metadata is not
- # registered at a server
+ """Test adding metadata to account with empty value
+
+ Adding metadata with empty value to account, the metadata is not
+ registered.
+ """
metadata = {'test-account-meta1': ''}
resp, _ = self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata)
@@ -330,8 +354,11 @@
@decorators.idempotent_id('d4d884d3-4696-4b85-bc98-4f57c4dd2bf1')
def test_update_account_metadata_with_delete_metadata_key(self):
- # Although the value of metadata is not set, the feature of
- # deleting metadata is valid
+ """Test deleting metadata from account with empty value
+
+ Although the value of metadata is not set, the feature of deleting
+ metadata is valid, so the metadata is removed from account.
+ """
metadata_1 = {'test-account-meta1': 'Meta1'}
self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata_1)
@@ -345,7 +372,11 @@
@decorators.idempotent_id('8e5fc073-59b9-42ee-984a-29ed11b2c749')
def test_update_account_metadata_with_create_and_delete_metadata(self):
- # Send a request adding and deleting metadata requests simultaneously
+ """Test adding and deleting metadata simultaneously
+
+ Send a request adding and deleting metadata requests simultaneously,
+ both adding and deleting of metadata will succeed.
+ """
metadata_1 = {'test-account-meta1': 'Meta1'}
self.account_client.create_update_or_delete_account_metadata(
create_update_metadata=metadata_1)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index bdcb5ae..c5334a9 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -33,6 +33,8 @@
class ContainerSyncTest(base.BaseObjectTest):
+ """Test container synchronization"""
+
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -129,6 +131,7 @@
not CONF.object_storage_feature_enabled.container_sync,
'Old-style container sync function is disabled')
def test_container_synchronization(self):
+ """Test container synchronization"""
def make_headers(cont, cont_client):
# tell first container to synchronize to a second
client_proxy_ip = \
diff --git a/tempest/api/object_storage/test_container_sync_middleware.py b/tempest/api/object_storage/test_container_sync_middleware.py
index e77b079..db6cfa4 100644
--- a/tempest/api/object_storage/test_container_sync_middleware.py
+++ b/tempest/api/object_storage/test_container_sync_middleware.py
@@ -27,6 +27,7 @@
class ContainerSyncMiddlewareTest(test_container_sync.ContainerSyncTest):
+ """Test containers synchronization specifying realm and cluster"""
@classmethod
def resource_setup(cls):
@@ -41,6 +42,7 @@
@decorators.idempotent_id('ea4645a1-d147-4976-82f7-e5a7a3065f80')
@utils.requires_ext(extension='container_sync', service='object')
def test_container_synchronization(self):
+ """Test container synchronization specifying realm and cluster"""
def make_headers(cont, cont_client):
# tell first container to synchronize to a second
account_name = cont_client.base_url.split('/')[-1]
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index 1567e06..c611ed6 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -19,6 +19,7 @@
class CrossdomainTest(base.BaseObjectTest):
+ """Test crossdomain policy"""
@classmethod
def resource_setup(cls):
@@ -37,6 +38,7 @@
@decorators.idempotent_id('d1b8b031-b622-4010-82f9-ff78a9e915c7')
@utils.requires_ext(extension='crossdomain', service='object')
def test_get_crossdomain_policy(self):
+ """Test getting crossdomain policy"""
url = self.account_client._get_base_version_url() + "crossdomain.xml"
resp, body = self.account_client.raw_request(url, "GET")
self.account_client._error_checker(resp, body)
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 86f7c8c..6f6e32f 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -21,6 +21,8 @@
class ObjectExpiryTest(base.BaseObjectTest):
+ """Test object expiry"""
+
@classmethod
def resource_setup(cls):
super(ObjectExpiryTest, cls).resource_setup()
@@ -83,6 +85,7 @@
@decorators.idempotent_id('fb024a42-37f3-4ba5-9684-4f40a7910b41')
def test_get_object_after_expiry_time(self):
+ """Test object is expired after x-delete-after time"""
# the 10s is important, because the get calls can take 3s each
# some times
metadata = {'X-Delete-After': '10'}
@@ -90,5 +93,6 @@
@decorators.idempotent_id('e592f18d-679c-48fe-9e36-4be5f47102c5')
def test_get_object_at_expiry_time(self):
+ """Test object is expired at x-delete-at time"""
metadata = {'X-Delete-At': str(int(time.time()) + 10)}
self._test_object_expiry(metadata)
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index acb578d..4ecbcad 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -29,6 +29,7 @@
class ObjectTest(base.BaseObjectTest):
+ """Test storage object"""
@classmethod
def resource_setup(cls):
@@ -78,6 +79,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('5b4ce26f-3545-46c9-a2ba-5754358a4c62')
def test_create_object(self):
+ """Test creating object and checking the object's uploaded content"""
# create object
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
@@ -97,7 +99,7 @@
@decorators.idempotent_id('5daebb1d-f0d5-4dc9-b541-69672eff00b0')
def test_create_object_with_content_disposition(self):
- # create object with content_disposition
+ """Test creating object with content-disposition"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata = {}
@@ -119,7 +121,7 @@
@decorators.idempotent_id('605f8317-f945-4bee-ae91-013f1da8f0a0')
def test_create_object_with_content_encoding(self):
- # create object with content_encoding
+ """Test creating object with content-encoding"""
object_name = data_utils.rand_name(name='TestObject')
# put compressed string
@@ -146,7 +148,7 @@
@decorators.idempotent_id('73820093-0503-40b1-a478-edf0e69c7d1f')
def test_create_object_with_etag(self):
- # create object with etag
+ """Test creating object with Etag"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
md5 = hashlib.md5(data).hexdigest()
@@ -165,8 +167,7 @@
@decorators.idempotent_id('84dafe57-9666-4f6d-84c8-0814d37923b8')
def test_create_object_with_expect_continue(self):
- # create object with expect_continue
-
+ """Test creating object with expect_continue"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
@@ -182,7 +183,7 @@
@decorators.idempotent_id('4f84422a-e2f2-4403-b601-726a4220b54e')
def test_create_object_with_transfer_encoding(self):
- # create object with transfer_encoding
+ """Test creating object with transfer_encoding"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes(1024)
headers = {'Transfer-Encoding': 'chunked'}
@@ -202,7 +203,10 @@
@decorators.idempotent_id('0f3d62a6-47e3-4554-b0e5-1a5dc372d501')
def test_create_object_with_x_fresh_metadata(self):
- # create object with x_fresh_metadata
+ """Test creating object with x-fresh-metadata
+
+ The previous added metadata will be cleared.
+ """
object_name_base = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata_1 = {'X-Object-Meta-test-meta': 'Meta'}
@@ -228,7 +232,7 @@
@decorators.idempotent_id('1c7ed3e4-2099-406b-b843-5301d4811baf')
def test_create_object_with_x_object_meta(self):
- # create object with object_meta
+ """Test creating object with x-object-meta"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -247,7 +251,7 @@
@decorators.idempotent_id('e4183917-33db-4153-85cc-4dacbb938865')
def test_create_object_with_x_object_metakey(self):
- # create object with the blank value of metadata
+ """Test creating object with the blank value of metadata"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata = {'X-Object-Meta-test-meta': ''}
@@ -266,7 +270,10 @@
@decorators.idempotent_id('ce798afc-b278-45de-a5ce-2ea124b98b99')
def test_create_object_with_x_remove_object_meta(self):
- # create object with x_remove_object_meta
+ """Test creating object with x-remove-object-meta
+
+ The metadata will be removed from the object.
+ """
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
@@ -289,7 +296,11 @@
@decorators.idempotent_id('ad21e342-7916-4f9e-ab62-a1f885f2aaf9')
def test_create_object_with_x_remove_object_metakey(self):
- # create object with the blank value of remove metadata
+ """Test creating object with the blank value of remove metadata
+
+ Creating object with blank metadata 'X-Remove-Object-Meta-test-meta',
+ metadata 'x-object-meta-test-meta' will be removed from the object.
+ """
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata_add = {'X-Object-Meta-test-meta': 'Meta'}
@@ -312,7 +323,7 @@
@decorators.idempotent_id('17738d45-03bd-4d45-9e0b-7b2f58f98687')
def test_delete_object(self):
- # create object
+ """Test deleting object"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
resp, _ = self.object_client.create_object(self.container_name,
@@ -325,7 +336,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('7a94c25d-66e6-434c-9c38-97d4e2c29945')
def test_update_object_metadata(self):
- # update object metadata
+ """Test updating object metadata"""
object_name, _ = self.create_object(self.container_name)
metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -343,7 +354,7 @@
@decorators.idempotent_id('48650ed0-c189-4e1e-ad6b-1d4770c6e134')
def test_update_object_metadata_with_remove_metadata(self):
- # update object metadata with remove metadata
+ """Test updating object metadata with remove metadata"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
create_metadata = {'X-Object-Meta-test-meta1': 'Meta1'}
@@ -366,6 +377,11 @@
@decorators.idempotent_id('f726174b-2ded-4708-bff7-729d12ce1f84')
def test_update_object_metadata_with_create_and_remove_metadata(self):
+ """Test updating object with creation and deletion of metadata
+
+ Update object with creation and deletion of metadata with one
+ request, both operations will succeed.
+ """
# creation and deletion of metadata with one request
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
@@ -392,8 +408,7 @@
@decorators.idempotent_id('08854588-6449-4bb7-8cca-f2e1040f5e6f')
def test_update_object_metadata_with_x_object_manifest(self):
- # update object metadata with x_object_manifest
-
+ """Test updating object metadata with x_object_manifest"""
# uploading segments
object_name, _ = self._upload_segments()
# creating a manifest file
@@ -418,7 +433,7 @@
@decorators.idempotent_id('0dbbe89c-6811-4d84-a2df-eca2bdd40c0e')
def test_update_object_metadata_with_x_object_metakey(self):
- # update object metadata with a blank value of metadata
+ """Test updating object metadata with a blank value of metadata"""
object_name, _ = self.create_object(self.container_name)
update_metadata = {'X-Object-Meta-test-meta': ''}
@@ -436,7 +451,7 @@
@decorators.idempotent_id('9a88dca4-b684-425b-806f-306cd0e57e42')
def test_update_object_metadata_with_x_remove_object_metakey(self):
- # update object metadata with a blank value of remove metadata
+ """Test updating object metadata with blank remove metadata value"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.arbitrary_string()
create_metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -460,7 +475,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('9a447cf6-de06-48de-8226-a8c6ed31caf2')
def test_list_object_metadata(self):
- # get object metadata
+ """Test listing object metadata"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -478,7 +493,7 @@
@decorators.idempotent_id('170fb90e-f5c3-4b1f-ae1b-a18810821172')
def test_list_no_object_metadata(self):
- # get empty list of object metadata
+ """Test listing object metadata for object without metadata"""
object_name, _ = self.create_object(self.container_name)
resp, _ = self.object_client.list_object_metadata(
@@ -489,8 +504,7 @@
@decorators.idempotent_id('23a3674c-d6de-46c3-86af-ff92bfc8a3da')
def test_list_object_metadata_with_x_object_manifest(self):
- # get object metadata with x_object_manifest
-
+ """Test getting object metadata with x_object_manifest"""
# uploading segments
object_name, _ = self._upload_segments()
# creating a manifest file
@@ -530,7 +544,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('02610ba7-86b7-4272-9ed8-aa8d417cb3cd')
def test_get_object(self):
- # retrieve object's data (in response body)
+ """Test retrieving object's data (in response body)"""
# create object
object_name, data = self.create_object(self.container_name)
@@ -543,7 +557,7 @@
@decorators.idempotent_id('005f9bf6-e06d-41ec-968e-96c78e0b1d82')
def test_get_object_with_metadata(self):
- # get object with metadata
+ """Test getting object with metadata"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
metadata = {'X-Object-Meta-test-meta': 'Meta'}
@@ -562,7 +576,7 @@
@decorators.idempotent_id('05a1890e-7db9-4a6c-90a8-ce998a2bddfa')
def test_get_object_with_range(self):
- # get object with range
+ """Test getting object with range"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes(100)
self.object_client.create_object(self.container_name,
@@ -580,7 +594,7 @@
@decorators.idempotent_id('11b4515b-7ba7-4ca8-8838-357ded86fc10')
def test_get_object_with_x_object_manifest(self):
- # get object with x_object_manifest
+ """Test getting object with x_object_manifest"""
# uploading segments
object_name, data_segments = self._upload_segments()
@@ -623,7 +637,7 @@
@decorators.idempotent_id('c05b4013-e4de-47af-be84-e598062b16fc')
def test_get_object_with_if_match(self):
- # get object with if_match
+ """Test getting object with if_match"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes(10)
create_md5 = hashlib.md5(data).hexdigest()
@@ -643,7 +657,7 @@
@decorators.idempotent_id('be133639-e5d2-4313-9b1f-2d59fc054a16')
def test_get_object_with_if_modified_since(self):
- # get object with if_modified_since
+ """Test getting object with if_modified_since"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
time_now = time.time()
@@ -663,7 +677,7 @@
@decorators.idempotent_id('641500d5-1612-4042-a04d-01fc4528bc30')
def test_get_object_with_if_none_match(self):
- # get object with if_none_match
+ """Test getting object with if_none_match"""
object_name = data_utils.rand_name(name='TestObject')
data = data_utils.random_bytes()
create_md5 = hashlib.md5(data).hexdigest()
@@ -685,7 +699,7 @@
@decorators.idempotent_id('0aa1201c-10aa-467a-bee7-63cbdd463152')
def test_get_object_with_if_unmodified_since(self):
- # get object with if_unmodified_since
+ """Test getting object with if_unmodified_since"""
object_name, data = self.create_object(self.container_name)
time_now = time.time()
@@ -700,7 +714,7 @@
@decorators.idempotent_id('94587078-475f-48f9-a40f-389c246e31cd')
def test_get_object_with_x_newest(self):
- # get object with x_newest
+ """Test getting object with x_newest"""
object_name, data = self.create_object(self.container_name)
list_metadata = {'X-Newest': 'true'}
@@ -713,6 +727,7 @@
@decorators.idempotent_id('1a9ab572-1b66-4981-8c21-416e2a5e6011')
def test_copy_object_in_same_container(self):
+ """Test copying object to another object in same container"""
# create source object
src_object_name = data_utils.rand_name(name='SrcObject')
src_data = data_utils.random_bytes(size=len(src_object_name) * 2)
@@ -742,7 +757,7 @@
@decorators.idempotent_id('2248abba-415d-410b-9c30-22dff9cd6e67')
def test_copy_object_to_itself(self):
- # change the content type of an existing object
+ """Test changing the content type of an existing object"""
# create object
object_name, _ = self.create_object(self.container_name)
@@ -769,6 +784,7 @@
@decorators.idempotent_id('06f90388-2d0e-40aa-934c-e9a8833e958a')
def test_copy_object_2d_way(self):
+ """Test copying object's data to the new object using COPY"""
# create source object
src_object_name = data_utils.rand_name(name='SrcObject')
src_data = data_utils.random_bytes(size=len(src_object_name) * 2)
@@ -793,6 +809,7 @@
@decorators.idempotent_id('aa467252-44f3-472a-b5ae-5b57c3c9c147')
def test_copy_object_across_containers(self):
+ """Test copying object to another container"""
# create a container to use as a source container
src_container_name = data_utils.rand_name(name='TestSourceContainer')
self.container_client.update_container(src_container_name)
@@ -837,6 +854,7 @@
@decorators.idempotent_id('5a9e2cc6-85b6-46fc-916d-0cbb7a88e5fd')
def test_copy_object_with_x_fresh_metadata(self):
+ """Test copying objectwith x_fresh_metadata"""
# create source object
metadata = {'x-object-meta-src': 'src_value'}
src_object_name, data = self.create_object(self.container_name,
@@ -858,6 +876,7 @@
@decorators.idempotent_id('a28a8b99-e701-4d7e-9d84-3b66f121460b')
def test_copy_object_with_x_object_metakey(self):
+ """Test copying object with x_object_metakey"""
# create source object
metadata = {'x-object-meta-src': 'src_value'}
src_obj_name, data = self.create_object(self.container_name,
@@ -881,6 +900,7 @@
@decorators.idempotent_id('edabedca-24c3-4322-9b70-d6d9f942a074')
def test_copy_object_with_x_object_meta(self):
+ """Test copying object with x_object_meta"""
# create source object
metadata = {'x-object-meta-src': 'src_value'}
src_obj_name, data = self.create_object(self.container_name,
@@ -904,6 +924,7 @@
@decorators.idempotent_id('e3e6a64a-9f50-4955-b987-6ce6767c97fb')
def test_object_upload_in_segments(self):
+ """Test uploading object in segments"""
# create object
object_name = data_utils.rand_name(name='LObject')
data = data_utils.arbitrary_string()
@@ -947,10 +968,13 @@
@decorators.idempotent_id('50d01f12-526f-4360-9ac2-75dd508d7b68')
def test_get_object_if_different(self):
- # http://en.wikipedia.org/wiki/HTTP_ETag
- # Make a conditional request for an object using the If-None-Match
- # header, it should get downloaded only if the local file is different,
- # otherwise the response code should be 304 Not Modified
+ """Test getting object content only when the local file is different
+
+ http://en.wikipedia.org/wiki/HTTP_ETag
+ Make a conditional request for an object using the If-None-Match
+ header, it should get downloaded only if the local file is different,
+ otherwise the response code should be 304 Not Modified
+ """
object_name, data = self.create_object(self.container_name)
# local copy is identical, no download
md5 = hashlib.md5(data).hexdigest()
@@ -975,6 +999,7 @@
class PublicObjectTest(base.BaseObjectTest):
+ """Test public storage object"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -1000,9 +1025,11 @@
@decorators.idempotent_id('07c9cf95-c0d4-4b49-b9c8-0ef2c9b27193')
def test_access_public_container_object_without_using_creds(self):
- # make container public-readable and access an object in it object
- # anonymously, without using credentials
+ """Test accessing public container object without using credentials
+ Make container public-readable and access an object in it object
+ anonymously, without using credentials.
+ """
# update container metadata to make it publicly readable
cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
resp_meta, body = (
@@ -1040,8 +1067,11 @@
@decorators.idempotent_id('54e2a2fe-42dc-491b-8270-8e4217dd4cdc')
def test_access_public_object_with_another_user_creds(self):
- # make container public-readable and access an object in it using
- # another user's credentials
+ """Test accessing public object with another user's credentials
+
+ Make container public-readable and access an object in it using
+ another user's credentials.
+ """
cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
resp_meta, body = (
self.container_client.create_update_or_delete_container_metadata(
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 8bb2e6e..7e553ca 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -27,6 +27,7 @@
class ObjectSloTest(base.BaseObjectTest):
+ """Test static large object"""
def setUp(self):
super(ObjectSloTest, self).setUp()
@@ -108,7 +109,7 @@
@decorators.idempotent_id('2c3f24a6-36e8-4711-9aa2-800ee1fc7b5b')
@utils.requires_ext(extension='slo', service='object')
def test_upload_manifest(self):
- # create static large object from multipart manifest
+ """Test creating static large object from multipart manifest"""
manifest = self._create_manifest()
params = {'multipart-manifest': 'put'}
@@ -123,7 +124,10 @@
@decorators.idempotent_id('e69ad766-e1aa-44a2-bdd2-bf62c09c1456')
@utils.requires_ext(extension='slo', service='object')
def test_list_large_object_metadata(self):
- # list static large object metadata using multipart manifest
+ """Test listing static large object metadata
+
+ List static large object metadata using multipart manifest
+ """
object_name = self._create_large_object()
resp, _ = self.object_client.list_object_metadata(
@@ -135,7 +139,7 @@
@decorators.idempotent_id('49bc49bc-dd1b-4c0f-904e-d9f10b830ee8')
@utils.requires_ext(extension='slo', service='object')
def test_retrieve_large_object(self):
- # list static large object using multipart manifest
+ """Test listing static large object using multipart manifest"""
object_name = self._create_large_object()
resp, body = self.object_client.get_object(
@@ -150,7 +154,7 @@
@decorators.idempotent_id('87b6dfa1-abe9-404d-8bf0-6c3751e6aa77')
@utils.requires_ext(extension='slo', service='object')
def test_delete_large_object(self):
- # delete static large object using multipart manifest
+ """Test deleting static large object using multipart manifest"""
object_name = self._create_large_object()
params_del = {'multipart-manifest': 'delete'}
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 76c22f0..389d3be 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -127,14 +127,14 @@
def test_update_volume_with_nonexistent_volume_id(self):
"""Test updating non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
- volume_id=data_utils.rand_uuid())
+ volume_id=data_utils.rand_uuid(), name="n")
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
def test_update_volume_with_invalid_volume_id(self):
"""Test updating volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
- volume_id=data_utils.rand_name('invalid'))
+ volume_id=data_utils.rand_name('invalid'), name="n")
@decorators.attr(type=['negative'])
@decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
diff --git a/tempest/clients.py b/tempest/clients.py
index 1db93a0..6d19a0c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -44,7 +44,7 @@
self._set_object_storage_clients()
self._set_image_clients()
self._set_network_clients()
- self.placement_client = self.placement.PlacementClient()
+ self._set_placement_clients()
# TODO(andreaf) This is maintained for backward compatibility
# with plugins, but it should removed eventually, since it was
# never a stable interface and it's not useful anyways
@@ -139,6 +139,11 @@
self.snapshots_extensions_client = self.compute.SnapshotsClient(
**params_volume)
+ def _set_placement_clients(self):
+ self.placement_client = self.placement.PlacementClient()
+ self.resource_providers_client = \
+ self.placement.ResourceProvidersClient()
+
def _set_identity_clients(self):
# Clients below use the admin endpoint type of Keystone API v2
params_v2_admin = {
@@ -203,6 +208,8 @@
**params_v3)
self.application_credentials_client = \
self.identity_v3.ApplicationCredentialsClient(**params_v3)
+ self.access_rules_client = \
+ self.identity_v3.AccessRulesClient(**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
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index fc25914..cc8778b 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -187,6 +187,28 @@
raise lib_exc.TimeoutException(message)
+def wait_for_image_imported_to_stores(client, image_id, stores):
+ """Waits for an image to be imported to all requested stores.
+
+ The client should also have build_interval and build_timeout attributes.
+ """
+
+ start = int(time.time())
+ while int(time.time()) - start < client.build_timeout:
+ image = client.show_image(image_id)
+ if image['status'] == 'active' and image['stores'] == stores:
+ return
+
+ time.sleep(client.build_interval)
+
+ message = ('Image %(image_id)s failed to import '
+ 'on stores: %s' % str(image['os_glance_failed_import']))
+ caller = test_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise lib_exc.TimeoutException(message)
+
+
def wait_for_volume_resource_status(client, resource_id, status):
"""Waits for a volume resource to reach a given status.
diff --git a/tempest/config.py b/tempest/config.py
index 36ad405..2f2c2e9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -250,6 +250,11 @@
default=False,
help='Does the environment have application credentials '
'enabled?'),
+ # Access rules for application credentials is a default feature in Train.
+ # This config option can removed once Stein is EOL.
+ cfg.BoolOpt('access_rules',
+ default=False,
+ help='Does the environment have access rules enabled?'),
cfg.BoolOpt('immutable_user_source',
default=False,
help='Set to True if the environment has a read-only '
@@ -1194,7 +1199,7 @@
The best use case is investigating used resources of one test.
A test can be run as follows:
- $ ostestr --pdb TEST_ID
+ $ stestr run --pdb TEST_ID
or
$ python -m testtools.run TEST_ID"""),
]
diff --git a/tempest/lib/api_schema/response/compute/v2_71/servers.py b/tempest/lib/api_schema/response/compute/v2_71/servers.py
index 5cf0f8a..f4c01ee 100644
--- a/tempest/lib/api_schema/response/compute/v2_71/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_71/servers.py
@@ -79,3 +79,6 @@
check_tag_existence = copy.deepcopy(servers270.check_tag_existence)
update_tag = copy.deepcopy(servers270.update_tag)
delete_tag = copy.deepcopy(servers270.delete_tag)
+attach_volume = copy.deepcopy(servers270.attach_volume)
+show_volume_attachment = copy.deepcopy(servers270.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers270.list_volume_attachments)
diff --git a/tempest/lib/api_schema/response/compute/v2_73/servers.py b/tempest/lib/api_schema/response/compute/v2_73/servers.py
index 6e491e9..ae7ebc4 100644
--- a/tempest/lib/api_schema/response/compute/v2_73/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_73/servers.py
@@ -76,3 +76,6 @@
check_tag_existence = copy.deepcopy(servers271.check_tag_existence)
update_tag = copy.deepcopy(servers271.update_tag)
delete_tag = copy.deepcopy(servers271.delete_tag)
+attach_volume = copy.deepcopy(servers271.attach_volume)
+show_volume_attachment = copy.deepcopy(servers271.show_volume_attachment)
+list_volume_attachments = copy.deepcopy(servers271.list_volume_attachments)
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 71ecb32..b34066f 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -16,6 +16,7 @@
import argparse
import ast
+import contextlib
import importlib
import inspect
import os
@@ -28,7 +29,7 @@
DECORATOR_MODULE = 'decorators'
DECORATOR_NAME = 'idempotent_id'
-DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
+DECORATOR_IMPORT = 'tempest.lib.%s' % DECORATOR_MODULE
IMPORT_LINE = 'from tempest.lib import %s' % DECORATOR_MODULE
DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
DECORATOR_NAME)
@@ -180,34 +181,125 @@
elif isinstance(node, ast.ImportFrom):
return '%s.%s' % (node.module, node.names[0].name)
+ @contextlib.contextmanager
+ def ignore_site_packages_paths(self):
+ """Removes site-packages directories from the sys.path
+
+ Source:
+ - StackOverflow: https://stackoverflow.com/questions/22195382/
+ - Author: https://stackoverflow.com/users/485844/
+ """
+
+ paths = sys.path
+ # remove all third-party paths
+ # so that only stdlib imports will succeed
+ sys.path = list(filter(
+ None,
+ filter(lambda i: 'site-packages' not in i, sys.path)
+ ))
+ yield
+ sys.path = paths
+
+ def is_std_lib(self, module):
+ """Checks whether the module is part of the stdlib or not
+
+ Source:
+ - StackOverflow: https://stackoverflow.com/questions/22195382/
+ - Author: https://stackoverflow.com/users/485844/
+ """
+
+ if module in sys.builtin_module_names:
+ return True
+
+ with self.ignore_site_packages_paths():
+ imported_module = sys.modules.pop(module, None)
+ try:
+ importlib.import_module(module)
+ except ImportError:
+ return False
+ else:
+ return True
+ finally:
+ if imported_module:
+ sys.modules[module] = imported_module
+
def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
- with open(source_path) as f:
- src_lines = f.read().split('\n')
- line_no = 0
- tempest_imports = [node for node in src_parsed.body
+ import_list = [node for node in src_parsed.body
+ if isinstance(node, ast.Import) or
+ isinstance(node, ast.ImportFrom)]
+
+ if not import_list:
+ print("(WARNING) %s: The file is not valid as it does not contain "
+ "any import line! Therefore the import needed by "
+ "@decorators.idempotent_id is not added!" % source_path)
+ return
+
+ tempest_imports = [node for node in import_list
if self._import_name(node) and
'tempest.' in self._import_name(node)]
- if not tempest_imports:
- import_snippet = '\n'.join(('', IMPORT_LINE, ''))
- else:
- for node in tempest_imports:
- if self._import_name(node) < DECORATOR_IMPORT:
- continue
- else:
- line_no = node.lineno
- import_snippet = IMPORT_LINE
- break
+
+ for node in tempest_imports:
+ if self._import_name(node) < DECORATOR_IMPORT:
+ continue
else:
- line_no = tempest_imports[-1].lineno
- while True:
- if (not src_lines[line_no - 1] or
- getattr(self._next_node(src_parsed.body,
- tempest_imports[-1]),
- 'lineno') == line_no or
- line_no == len(src_lines)):
- break
- line_no += 1
- import_snippet = '\n'.join((IMPORT_LINE, ''))
+ line_no = node.lineno
+ break
+ else:
+ if tempest_imports:
+ line_no = tempest_imports[-1].lineno + 1
+
+ # Insert import line between existing tempest imports
+ if tempest_imports:
+ patcher.add_patch(source_path, IMPORT_LINE, line_no)
+ return
+
+ # Group space separated imports together
+ grouped_imports = {}
+ first_import_line = import_list[0].lineno
+ for idx, import_line in enumerate(import_list, first_import_line):
+ group_no = import_line.lineno - idx
+ group = grouped_imports.get(group_no, [])
+ group.append(import_line)
+ grouped_imports[group_no] = group
+
+ if len(grouped_imports) > 3:
+ print("(WARNING) %s: The file contains more than three import "
+ "groups! This is not valid according to the PEP8 "
+ "style guide. " % source_path)
+
+ # Divide grouped_imports into groupes based on PEP8 style guide
+ pep8_groups = {}
+ package_name = self.package.__name__.split(".")[0]
+ for key in grouped_imports:
+ module = self._import_name(grouped_imports[key][0]).split(".")[0]
+ if module.startswith(package_name):
+ group = pep8_groups.get('3rd_group', [])
+ pep8_groups['3rd_group'] = group + grouped_imports[key]
+ elif self.is_std_lib(module):
+ group = pep8_groups.get('1st_group', [])
+ pep8_groups['1st_group'] = group + grouped_imports[key]
+ else:
+ group = pep8_groups.get('2nd_group', [])
+ pep8_groups['2nd_group'] = group + grouped_imports[key]
+
+ for node in pep8_groups.get('2nd_group', []):
+ if self._import_name(node) < DECORATOR_IMPORT:
+ continue
+ else:
+ line_no = node.lineno
+ import_snippet = IMPORT_LINE
+ break
+ else:
+ if pep8_groups.get('2nd_group', []):
+ line_no = pep8_groups['2nd_group'][-1].lineno + 1
+ import_snippet = IMPORT_LINE
+ elif pep8_groups.get('1st_group', []):
+ line_no = pep8_groups['1st_group'][-1].lineno + 1
+ import_snippet = '\n' + IMPORT_LINE
+ else:
+ line_no = pep8_groups['3rd_group'][0].lineno
+ import_snippet = IMPORT_LINE + '\n\n'
+
patcher.add_patch(source_path, import_snippet, line_no)
def get_tests(self):
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index da1c51c..86fa991 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations under
# the License.
+from tempest.lib.services.identity.v3.access_rules_client import \
+ AccessRulesClient
from tempest.lib.services.identity.v3.application_credentials_client import \
ApplicationCredentialsClient
from tempest.lib.services.identity.v3.catalog_client import \
@@ -48,9 +50,10 @@
from tempest.lib.services.identity.v3.users_client import UsersClient
from tempest.lib.services.identity.v3.versions_client import VersionsClient
-__all__ = ['ApplicationCredentialsClient', 'CatalogClient',
- 'CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
- 'EndPointGroupsClient', 'EndPointsClient', 'EndPointsFilterClient',
+__all__ = ['AccessRulesClient', 'ApplicationCredentialsClient',
+ 'CatalogClient', 'CredentialsClient', 'DomainsClient',
+ 'DomainConfigurationClient', 'EndPointGroupsClient',
+ 'EndPointsClient', 'EndPointsFilterClient',
'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
'OAUTHConsumerClient', 'OAUTHTokenClient', 'PoliciesClient',
'ProjectsClient', 'ProjectTagsClient', 'RegionsClient',
diff --git a/tempest/lib/services/identity/v3/access_rules_client.py b/tempest/lib/services/identity/v3/access_rules_client.py
new file mode 100644
index 0000000..4f13e47
--- /dev/null
+++ b/tempest/lib/services/identity/v3/access_rules_client.py
@@ -0,0 +1,68 @@
+# Copyright 2019 SUSE LLC
+#
+# 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.
+
+"""
+https://docs.openstack.org/api-ref/identity/v3/index.html#application-credentials
+"""
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class AccessRulesClient(rest_client.RestClient):
+ api_version = "v3"
+
+ def show_access_rule(self, user_id, access_rule_id):
+ """Gets details of an access rule.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3/index.html#show-access-rule-details
+ """
+ resp, body = self.get('users/%s/access_rules/%s' %
+ (user_id, access_rule_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_access_rules(self, user_id, **params):
+ """Lists out all of a user's access rules.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3/index.html#list-access-rules
+ """
+ url = 'users/%s/access_rules' % user_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_access_rule(self, user_id, access_rule_id):
+ """Deletes an access rule.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3/index.html#delete-access-rule
+ """
+ resp, body = self.delete('users/%s/access_rules/%s' %
+ (user_id, access_rule_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/endpoint_filter_client.py b/tempest/lib/services/identity/v3/endpoint_filter_client.py
index ce84869..2d5c8c9 100644
--- a/tempest/lib/services/identity/v3/endpoint_filter_client.py
+++ b/tempest/lib/services/identity/v3/endpoint_filter_client.py
@@ -66,3 +66,57 @@
% (project_id, endpoint_id))
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def list_endpoint_groups_for_project(self, project_id):
+ """List Endpoint Groups Associated with Project."""
+ resp, body = self.get(
+ self.ep_filter + '/projects/%s/endpoint_groups'
+ % project_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_projects_for_endpoint_group(self, endpoint_group_id):
+ """List Projects Associated with Endpoint Group."""
+ resp, body = self.get(
+ self.ep_filter + '/endpoint_groups/%s/projects'
+ % endpoint_group_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_endpoints_for_endpoint_group(self, endpoint_group_id):
+ """List Endpoints Associated with Endpoint Group."""
+ resp, body = self.get(
+ self.ep_filter + '/endpoint_groups/%s/endpoints'
+ % endpoint_group_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+ """Create Endpoint Group to Project Association."""
+ body = None
+ resp, body = self.put(
+ self.ep_filter + '/endpoint_groups/%s/projects/%s'
+ % (endpoint_group_id, project_id), body)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_endpoint_group_for_project(self, endpoint_group_id, project_id):
+ """Get Endpoint Group to Project Association."""
+ resp, body = self.get(
+ self.ep_filter + '/endpoint_groups/%s/projects/%s'
+ % (endpoint_group_id, project_id))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_endpoint_group_from_project(
+ self, endpoint_group_id, project_id):
+ """Delete Endpoint Group to Project Association."""
+ resp, body = self.delete(
+ self.ep_filter + '/endpoint_groups/%s/projects/%s'
+ % (endpoint_group_id, project_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v3/policies_client.py b/tempest/lib/services/identity/v3/policies_client.py
index 31c0d18..41def38 100644
--- a/tempest/lib/services/identity/v3/policies_client.py
+++ b/tempest/lib/services/identity/v3/policies_client.py
@@ -185,3 +185,27 @@
resp, body = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+
+ def list_endpoints_for_policy(self, policy_id):
+ """List policy and service endpoint associations.
+
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/#list-policy-and-service-endpoint-associations
+ """
+ url = "policies/{0}/OS-ENDPOINT-POLICY/endpoints".format(policy_id)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_policy_for_endpoint(self, endpoint_id):
+ """Show the effective policy associated with an endpoint
+
+ API reference:
+ https://docs.openstack.org/api-ref/identity/v3-ext/#show-the-effective-policy-associated-with-an-endpoint
+ """
+ url = "endpoints/{0}/OS-ENDPOINT-POLICY/policy".format(endpoint_id)
+ 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/users_client.py b/tempest/lib/services/identity/v3/users_client.py
index f47730f..bba02a4 100644
--- a/tempest/lib/services/identity/v3/users_client.py
+++ b/tempest/lib/services/identity/v3/users_client.py
@@ -118,3 +118,30 @@
self.expected_success(200, resp.status)
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+
+ def create_user_ec2_credential(self, user_id, **kwargs):
+ post_body = json.dumps(kwargs)
+ resp, body = self.post('/users/%s/credentials/OS-EC2' % user_id,
+ post_body)
+ self.expected_success(201, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_user_ec2_credential(self, user_id, access):
+ resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
+ (user_id, access))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_user_ec2_credentials(self, user_id):
+ resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_user_ec2_credential(self, user_id, access):
+ resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
+ (user_id, access))
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/placement/__init__.py b/tempest/lib/services/placement/__init__.py
index 5c20c57..daeaeab 100644
--- a/tempest/lib/services/placement/__init__.py
+++ b/tempest/lib/services/placement/__init__.py
@@ -14,5 +14,7 @@
from tempest.lib.services.placement.placement_client import \
PlacementClient
+from tempest.lib.services.placement.resource_providers_client import \
+ ResourceProvidersClient
-__all__ = ['PlacementClient']
+__all__ = ['PlacementClient', 'ResourceProvidersClient']
diff --git a/tempest/lib/services/placement/resource_providers_client.py b/tempest/lib/services/placement/resource_providers_client.py
new file mode 100644
index 0000000..56f6409
--- /dev/null
+++ b/tempest/lib/services/placement/resource_providers_client.py
@@ -0,0 +1,82 @@
+# 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
+from tempest.lib.services.placement import base_placement_client
+
+
+class ResourceProvidersClient(base_placement_client.BasePlacementClient):
+ """Client class for resource provider related methods
+
+ This client class aims to support read-only API operations for resource
+ providers. The following resources are supported:
+ * resource providers
+ * resource provider inventories
+ * resource provider aggregates
+ """
+
+ def list_resource_providers(self, **params):
+ """List resource providers.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#list-resource-providers
+ """
+ url = '/resource_providers'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def show_resource_provider(self, rp_uuid):
+ """Show resource provider.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#show-resource-provider
+ """
+ url = '/resource_providers/%s' % rp_uuid
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_resource_provider_inventories(self, rp_uuid):
+ """List resource provider inventories.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#list-resource-provider-inventories
+ """
+ url = '/resource_providers/%s/inventories' % rp_uuid
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_resource_provider_aggregates(self, rp_uuid):
+ """List resource provider aggregates.
+
+ For full list of available parameters, please refer to the official
+ API reference:
+ https://docs.openstack.org/api-ref/placement/#list-resource-provider-aggregates
+ """
+ url = '/resource_providers/%s/aggregates' % rp_uuid
+ 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/scenario/test_minbw_allocation_placement.py b/tempest/scenario/test_minbw_allocation_placement.py
index 5eab1da..74d4ed9 100644
--- a/tempest/scenario/test_minbw_allocation_placement.py
+++ b/tempest/scenario/test_minbw_allocation_placement.py
@@ -178,7 +178,13 @@
for rp, resources in allocations['allocations'].items():
if self.INGRESS_RESOURCE_CLASS in resources['resources']:
bw_resource_in_alloc = True
+ allocation_rp = rp
self.assertTrue(bw_resource_in_alloc)
+ # Check that binding_profile of the port is not empty and equals with
+ # the rp uuid
+ port = self.os_admin.ports_client.show_port(valid_port['id'])
+ self.assertEqual(allocation_rp,
+ port['port']['binding:profile']['allocation'])
# boot another vm with max int bandwidth
not_valid_port = self.create_port(
@@ -196,3 +202,6 @@
server2 = self.servers_client.show_server(server2['id'])
self.assertIn('fault', server2['server'])
self.assertIn('No valid host', server2['server']['fault']['message'])
+ # Check that binding_profile of the port is empty
+ port = self.os_admin.ports_client.show_port(not_valid_port['id'])
+ self.assertEqual(0, len(port['port']['binding:profile']))
diff --git a/tempest/tests/lib/cmd/test_check_uuid.py b/tempest/tests/lib/cmd/test_check_uuid.py
index 28ebca1..428e047 100644
--- a/tempest/tests/lib/cmd/test_check_uuid.py
+++ b/tempest/tests/lib/cmd/test_check_uuid.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import ast
import importlib
import os
import sys
@@ -95,6 +96,8 @@
class TestTestChecker(base.TestCase):
+ IMPORT_LINE = "from tempest.lib import decorators\n"
+
def _test_add_uuid_to_test(self, source_file):
class Fake_test_node():
lineno = 1
@@ -127,55 +130,69 @@
" pass")
self._test_add_uuid_to_test(source_file)
+ @staticmethod
+ def get_mocked_ast_object(lineno, col_offset, module, name, object_type):
+ ast_object = mock.Mock(spec=object_type)
+ name_obj = mock.Mock()
+ ast_object.lineno = lineno
+ ast_object.col_offset = col_offset
+ name_obj.name = name
+ ast_object.module = module
+ ast_object.names = [name_obj]
+
+ return ast_object
+
def test_add_import_for_test_uuid_no_tempest(self):
patcher = check_uuid.SourcePatcher()
checker = check_uuid.TestChecker(importlib.import_module('tempest'))
- fake_file = tempfile.NamedTemporaryFile("w+t")
+ fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+ source_code = "from unittest import mock\n"
+ fake_file.write(source_code)
+ fake_file.close()
class Fake_src_parsed():
- body = ['test_node']
- checker._import_name = mock.Mock(return_value='fake_module')
+ body = [TestTestChecker.get_mocked_ast_object(
+ 1, 4, 'unittest', 'mock', ast.ImportFrom)]
- checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+ checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
fake_file.name)
- (patch_id, patch), = patcher.patches.items()
- self.assertEqual(patcher._quote('\n' + check_uuid.IMPORT_LINE + '\n'),
- patch)
- self.assertEqual('{%s:s}' % patch_id,
- patcher.source_files[fake_file.name])
+ patcher.apply_patches()
+
+ with open(fake_file.name, "r") as f:
+ expected_result = source_code + '\n' + TestTestChecker.IMPORT_LINE
+ self.assertTrue(expected_result == f.read())
def test_add_import_for_test_uuid_tempest(self):
patcher = check_uuid.SourcePatcher()
checker = check_uuid.TestChecker(importlib.import_module('tempest'))
fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
- test1 = (" def test_test():\n"
- " pass\n")
- test2 = (" def test_another_test():\n"
- " pass\n")
- source_code = test1 + test2
+ source_code = "from tempest import a_fake_module\n"
fake_file.write(source_code)
fake_file.close()
- def fake_import_name(node):
- return node.name
- checker._import_name = fake_import_name
+ class Fake_src_parsed:
+ body = [TestTestChecker.get_mocked_ast_object(
+ 1, 4, 'tempest', 'a_fake_module', ast.ImportFrom)]
- class Fake_node():
- def __init__(self, lineno, col_offset, name):
- self.lineno = lineno
- self.col_offset = col_offset
- self.name = name
-
- class Fake_src_parsed():
- body = [Fake_node(1, 4, 'tempest.a_fake_module'),
- Fake_node(3, 4, 'another_fake_module')]
-
- checker._add_import_for_test_uuid(patcher, Fake_src_parsed(),
+ checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
fake_file.name)
- (patch_id, patch), = patcher.patches.items()
- self.assertEqual(patcher._quote(check_uuid.IMPORT_LINE + '\n'),
- patch)
- expected_source = patcher._quote(test1) + '{' + patch_id + ':s}' +\
- patcher._quote(test2)
- self.assertEqual(expected_source,
- patcher.source_files[fake_file.name])
+ patcher.apply_patches()
+
+ with open(fake_file.name, "r") as f:
+ expected_result = source_code + TestTestChecker.IMPORT_LINE
+ self.assertTrue(expected_result == f.read())
+
+ def test_add_import_no_import(self):
+ patcher = check_uuid.SourcePatcher()
+ patcher.add_patch = mock.Mock()
+ checker = check_uuid.TestChecker(importlib.import_module('tempest'))
+ fake_file = tempfile.NamedTemporaryFile("w+t", delete=False)
+ fake_file.close()
+
+ class Fake_src_parsed:
+ body = []
+
+ checker._add_import_for_test_uuid(patcher, Fake_src_parsed,
+ fake_file.name)
+
+ self.assertTrue(not patcher.add_patch.called)
diff --git a/tempest/tests/lib/services/identity/v3/test_access_rules_client.py b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py
new file mode 100644
index 0000000..71c9cde
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_access_rules_client.py
@@ -0,0 +1,97 @@
+# Copyright 2019 SUSE LLC
+#
+# 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 access_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestAccessRulesClient(base.BaseServiceTest):
+ FAKE_LIST_ACCESS_RULES = {
+ "links": {
+ "self": "https://example.com/identity/v3/users/" +
+ "3e0716ae/access_rules",
+ "previous": None,
+ "next": None
+ },
+ "access_rules": [
+ {
+ "path": "/v2.0/metrics",
+ "links": {
+ "self": "https://example.com/identity/v3/access_rules/" +
+ "07d719df00f349ef8de77d542edf010c"
+ },
+ "id": "07d719df00f349ef8de77d542edf010c",
+ "service": "monitoring",
+ "method": "GET"
+ }
+ ]
+ }
+
+ FAKE_ACCESS_RULE_INFO = {
+ "access_rule": {
+ "path": "/v2.0/metrics",
+ "links": {
+ "self": "https://example.com/identity/v3/access_rules/" +
+ "07d719df00f349ef8de77d542edf010c"
+ },
+ "id": "07d719df00f349ef8de77d542edf010c",
+ "service": "monitoring",
+ "method": "GET"
+ }
+ }
+
+ def setUp(self):
+ super(TestAccessRulesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = access_rules_client.AccessRulesClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_show_access_rule(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_access_rule,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ACCESS_RULE_INFO,
+ bytes_body,
+ user_id="123456",
+ access_rule_id="5499a186")
+
+ def _test_list_access_rules(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_access_rules,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ACCESS_RULES,
+ bytes_body,
+ user_id="123456")
+
+ def test_show_access_rule_with_str_body(self):
+ self._test_show_access_rule()
+
+ def test_show_access_rule_with_bytes_body(self):
+ self._test_show_access_rule(bytes_body=True)
+
+ def test_list_access_rule_with_str_body(self):
+ self._test_list_access_rules()
+
+ def test_list_access_rule_with_bytes_body(self):
+ self._test_list_access_rules(bytes_body=True)
+
+ def test_delete_access_rule(self):
+ self.check_service_client_function(
+ self.client.delete_access_rule,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id="123456",
+ access_rule_id="5499a186",
+ status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
index 7faf6a0..e5f7a66 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
@@ -83,6 +83,36 @@
}
}
+ FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT = {
+ "endpoint_groups": [
+ {
+ "endpoint_group": {
+ "description": "endpoint group description #2",
+ "filters": {
+ "interface": "admin"
+ },
+ "id": "3de68c",
+ "name": "endpoint group name #2"
+ }
+ }
+ ],
+ "links": {
+ "self": "https://url/identity/v3/OS-EP-FILTER/endpoint_groups",
+ }
+ }
+
+ FAKE_PROJECT_INFO = {
+ "project": {
+ "domain_id": "1789d1",
+ "id": "263fd9",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/263fd9"
+ },
+ "name": "project name #1",
+ "description": "project description #1"
+ }
+ }
+
def setUp(self):
super(TestEndPointsFilterClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -137,6 +167,52 @@
project_id=3,
endpoint_id=4)
+ def _test_list_endpoint_groups_for_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoint_groups_for_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINT_GROUPS_FOR_PROJECT,
+ bytes_body,
+ status=200,
+ project_id=3)
+
+ def _test_list_projects_for_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_projects_for_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS,
+ bytes_body,
+ status=200,
+ endpoint_group_id=5)
+
+ def _test_list_endpoints_for_endpoint_group(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints_for_endpoint_group,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS,
+ bytes_body,
+ status=200,
+ endpoint_group_id=5)
+
+ def _test_add_endpoint_group_to_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_endpoint_group_to_project,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ status=204,
+ endpoint_group_id=5,
+ project_id=6)
+
+ def _test_show_endpoint_group_for_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_endpoint_group_for_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_PROJECT_INFO,
+ bytes_body,
+ endpoint_group_id=5,
+ project_id=6)
+
def test_add_endpoint_to_project_with_str_body(self):
self._test_add_endpoint_to_project()
@@ -163,3 +239,43 @@
def test_delete_endpoint_from_project(self):
self._test_delete_endpoint_from_project()
+
+ def test_list_endpoint_groups_for_project_with_str_body(self):
+ self._test_list_endpoint_groups_for_project()
+
+ def test_list_endpoint_groups_for_project_with_bytes_body(self):
+ self._test_list_endpoint_groups_for_project(bytes_body=True)
+
+ def test_list_projects_for_endpoint_group_with_str_body(self):
+ self._test_list_projects_for_endpoint_group()
+
+ def test_list_projects_for_endpoint_group_with_bytes_body(self):
+ self._test_list_projects_for_endpoint_group(bytes_body=True)
+
+ def test_list_endpoints_for_endpoint_group_with_str_body(self):
+ self._test_list_endpoints_for_endpoint_group()
+
+ def test_list_endpoints_for_endpoint_group_with_bytes_body(self):
+ self._test_list_endpoints_for_endpoint_group(bytes_body=True)
+
+ def test_add_endpoint_group_to_project_with_str_body(self):
+ self._test_add_endpoint_group_to_project()
+
+ def test_add_endpoint_group_to_project_with_bytes_body(self):
+ self._test_add_endpoint_group_to_project(bytes_body=True)
+
+ def test_show_endpoint_group_for_project_with_str_body(self):
+ self._test_show_endpoint_group_for_project()
+
+ def test_show_endpoint_group_for_project_with_bytes_body(self):
+ self._test_show_endpoint_group_for_project(bytes_body=True)
+
+ def test_delete_endpoint_group_from_project(self):
+ self.check_service_client_function(
+ self.client.delete_endpoint_group_from_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ False,
+ status=204,
+ endpoint_group_id=5,
+ project_id=5)
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
index ca15dd1..0efc462 100644
--- a/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_endpoints_client.py
@@ -54,12 +54,44 @@
}
FAKE_SERVICE_ID = "a4dc5060-f757-4662-b658-edd2aefbb41d"
+ FAKE_ENDPOINT_ID = "b335d394-cdb9-4519-b95d-160b7706e54ew"
+
+ FAKE_UPDATE_ENDPOINT = {
+ "endpoint": {
+ "id": "828384",
+ "interface": "internal",
+ "links": {
+ "self": "http://example.com/identity/v3/"
+ "endpoints/828384"
+ },
+ "region_id": "north",
+ "service_id": "686766",
+ "url": "http://example.com/identity/v3/"
+ "endpoints/828384"
+ }
+ }
+
+ FAKE_SHOW_ENDPOINT = {
+ "endpoint": {
+ "enabled": True,
+ "id": "01c3d5b92f7841ac83fb4b26173c12c7",
+ "interface": "admin",
+ "links": {
+ "self": "http://example.com/identity/v3/"
+ "endpoints/828384"
+ },
+ "region": "RegionOne",
+ "region_id": "RegionOne",
+ "service_id": "3b2d6ad7e02c4cde8498a547601f1b8f",
+ "url": "http://23.253.211.234:9696/"
+ }
+ }
def setUp(self):
super(TestEndpointsClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = endpoints_client.EndPointsClient(fake_auth,
- 'identity', 'regionOne')
+ self.client = endpoints_client.EndPointsClient(
+ fake_auth, 'identity', 'regionOne')
def _test_create_endpoint(self, bytes_body=False):
self.check_service_client_function(
@@ -84,6 +116,38 @@
mock_args=[mock_args],
**params)
+ def _test_update_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.patch',
+ self.FAKE_UPDATE_ENDPOINT,
+ bytes_body,
+ endpoint_id=self.FAKE_ENDPOINT_ID,
+ interface="public",
+ region_id="north",
+ url="http://example.com/identity/v3/endpoints/828384",
+ service_id=self.FAKE_SERVICE_ID)
+
+ def _test_show_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_SHOW_ENDPOINT,
+ bytes_body,
+ endpoint_id="3456")
+
+ def test_update_endpoint_with_str_body(self):
+ self._test_update_endpoint()
+
+ def test_update_endpoint_with_bytes_body(self):
+ self._test_update_endpoint(bytes_body=True)
+
+ def test_show_endpoint_with_str_body(self):
+ self._test_show_endpoint()
+
+ def test_show_endpoint_with_bytes_body(self):
+ self._test_show_endpoint(bytes_body=True)
+
def test_create_endpoint_with_str_body(self):
self._test_create_endpoint()
diff --git a/tempest/tests/lib/services/identity/v3/test_policies_client.py b/tempest/tests/lib/services/identity/v3/test_policies_client.py
index 0237475..4fc800a 100644
--- a/tempest/tests/lib/services/identity/v3/test_policies_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_policies_client.py
@@ -44,6 +44,34 @@
}
}
+ FAKE_ENDPOINT_INFO = {
+ "endpoints": [
+ {
+ "id": "1",
+ "interface": "public",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/1"
+ },
+ "region": "north",
+ "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+ "url": "http://example.com/identity/"
+ },
+ {
+ "id": "1",
+ "interface": "internal",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/1"
+ },
+ "region": "south",
+ "service_id": "9242e05f0c23467bbd1cf1f7a6e5e596",
+ "url": "http://example.com/identity/"
+ }
+ ],
+ "links": {
+ "self": "http://exmp.com/identity/v3/OS-ENDPOINT-POLICY/policies/1"
+ }
+ }
+
FAKE_LIST_POLICIES = {
"links": {
"next": None,
@@ -238,3 +266,33 @@
service_id=self.FAKE_SERVICE_ID,
region_id=self.FAKE_REGION_ID,
status=204)
+
+ def _test_list_endpoints_for_policy(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints_for_policy,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_ENDPOINT_INFO,
+ bytes_body,
+ policy_id=self.FAKE_POLICY_ID,
+ status=200)
+
+ def test_list_endpoints_for_policy_with_str_body(self):
+ self._test_list_endpoints_for_policy()
+
+ def test_list_endpoints_for_policy_with_bytes_body(self):
+ self._test_list_endpoints_for_policy(bytes_body=True)
+
+ def _test_list_policy_for_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_policy_for_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_POLICY_INFO,
+ bytes_body,
+ endpoint_id=self.FAKE_ENDPOINT_ID,
+ status=200)
+
+ def test_list_policy_for_endpoint_with_str_body(self):
+ self._test_list_policy_for_endpoint()
+
+ def test_list_policy_for_endpoint_with_bytes_body(self):
+ self._test_list_policy_for_endpoint(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v3/test_users_client.py b/tempest/tests/lib/services/identity/v3/test_users_client.py
index c0dfdae..7be0480 100644
--- a/tempest/tests/lib/services/identity/v3/test_users_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_users_client.py
@@ -141,6 +141,35 @@
]
}
+ FAKE_USER_EC2_CREDENTIAL_INFO = {
+ "credential": {
+ 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+ 'access': '79abf59acc77492a86170cbe2f1feafa',
+ 'secret': 'c4e7d3a691fd4563873d381a40320f46',
+ 'trust_id': None,
+ 'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+ }
+ }
+
+ FAKE_LIST_USER_EC2_CREDENTIALS = {
+ "credentials": [
+ {
+ 'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+ 'access': '79abf59acc77492a86170cbe2f1feafa',
+ 'secret': 'c4e7d3a691fd4563873d381a40320f46',
+ 'trust_id': None,
+ 'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+ },
+ {
+ 'user_id': '3beb0e12f3e5416db8d7cccfc785de4r',
+ 'access': '45abf59acc77492a86170cbe2f1fesde',
+ 'secret': 'g4e7d3a691fd4563873d381a40320e45',
+ 'trust_id': None,
+ 'tenant_id': '123557269d7b4dd78631a602eb9f112f'
+ }
+ ]
+ }
+
def setUp(self):
super(TestUsersClient, self).setUp()
fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -201,6 +230,33 @@
user_id='817fb3c23fd7465ba6d7fe1b1320121d',
)
+ def _test_create_user_ec2_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ self.FAKE_USER_EC2_CREDENTIAL_INFO,
+ bytes_body,
+ status=201,
+ user_id="1",
+ tenant_id="123")
+
+ def _test_show_user_ec2_credential(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_USER_EC2_CREDENTIAL_INFO,
+ bytes_body,
+ user_id="1",
+ access="123")
+
+ def _test_list_user_ec2_credentials(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_ec2_credentials,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_USER_EC2_CREDENTIALS,
+ bytes_body,
+ user_id="1")
+
def test_create_user_with_string_body(self):
self._test_create_user()
@@ -255,3 +311,30 @@
user_id='817fb3c23fd7465ba6d7fe1b1320121d',
password='NewTempestPassword',
original_password='OldTempestPassword')
+
+ def test_create_user_ec2_credential_with_str_body(self):
+ self._test_create_user_ec2_credential()
+
+ def test_create_user_ec2_credential_with_bytes_body(self):
+ self._test_create_user_ec2_credential(bytes_body=True)
+
+ def test_show_user_ec2_credential_with_str_body(self):
+ self._test_show_user_ec2_credential()
+
+ def test_show_user_ec2_credential_with_bytes_body(self):
+ self._test_show_user_ec2_credential(bytes_body=True)
+
+ def test_list_user_ec2_credentials_with_str_body(self):
+ self._test_list_user_ec2_credentials()
+
+ def test_list_user_ec2_credentials_with_bytes_body(self):
+ self._test_list_user_ec2_credentials(bytes_body=True)
+
+ def test_delete_user_ec2_credential(self):
+ self.check_service_client_function(
+ self.client.delete_user_ec2_credential,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id="123",
+ access="1234",
+ status=204)
diff --git a/tempest/tests/lib/services/network/test_floating_ips_client.py b/tempest/tests/lib/services/network/test_floating_ips_client.py
index c5b1845..e8f2e5a 100644
--- a/tempest/tests/lib/services/network/test_floating_ips_client.py
+++ b/tempest/tests/lib/services/network/test_floating_ips_client.py
@@ -27,6 +27,8 @@
{
"router_id": "d23abc8d-2991-4a55-ba98-2aaea84cc72f",
"description": "for test",
+ "dns_domain": "my-domain.org.",
+ "dns_name": "myfip",
"created_at": "2016-12-21T10:55:50Z",
"updated_at": "2016-12-21T10:55:53Z",
"revision_number": 1,
@@ -37,11 +39,24 @@
"floating_ip_address": "172.24.4.228",
"port_id": "ce705c24-c1ef-408a-bda3-7bbd946164ab",
"id": "2f245a7b-796b-4f26-9cf9-9e82d248fda7",
- "status": "ACTIVE"
+ "status": "ACTIVE",
+ "port_details": {
+ "status": "ACTIVE",
+ "name": "",
+ "admin_state_up": True,
+ "network_id": "02dd8479-ef26-4398-a102-d19d0a7b3a1f",
+ "device_owner": "compute:nova",
+ "mac_address": "fa:16:3e:b1:3b:30",
+ "device_id": "8e3941b4-a6e9-499f-a1ac-2a4662025cba"
+ },
+ "tags": ["tag1,tag2"],
+ "port_forwardings": []
},
{
"router_id": None,
"description": "for test",
+ "dns_domain": "my-domain.org.",
+ "dns_name": "myfip2",
"created_at": "2016-12-21T11:55:50Z",
"updated_at": "2016-12-21T11:55:53Z",
"revision_number": 2,
@@ -52,7 +67,10 @@
"floating_ip_address": "172.24.4.227",
"port_id": None,
"id": "61cea855-49cb-4846-997d-801b70c71bdd",
- "status": "DOWN"
+ "status": "DOWN",
+ "port_details": None,
+ "tags": ["tag1,tag2"],
+ "port_forwardings": []
}
]
}
diff --git a/tempest/tests/lib/services/network/test_networks_client.py b/tempest/tests/lib/services/network/test_networks_client.py
index 078f4b0..17233bc 100644
--- a/tempest/tests/lib/services/network/test_networks_client.py
+++ b/tempest/tests/lib/services/network/test_networks_client.py
@@ -31,12 +31,17 @@
"nova"
],
"created_at": "2016-03-08T20:19:41",
+ "dns_domain": "my-domain.org.",
"id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+ "ipv4_address_scope": None,
+ "ipv6_address_scope": None,
+ "l2_adjacency": False,
"mtu": 0,
"name": "net1",
"port_security_enabled": True,
"project_id": "4fd44f30292945e481c7b8a0c8908869",
"qos_policy_id": "6a8454ade84346f59e8d40665f878b2e",
+ "revision_number": 1,
"router:external": False,
"shared": False,
"status": "ACTIVE",
@@ -46,7 +51,8 @@
"tenant_id": "4fd44f30292945e481c7b8a0c8908869",
"updated_at": "2016-03-08T20:19:41",
"vlan_transparent": True,
- "description": ""
+ "description": "",
+ "is_default": False
},
{
"admin_state_up": True,
@@ -54,12 +60,18 @@
"availability_zones": [
"nova"
],
+ "created_at": "2016-03-08T20:19:41",
+ "dns_domain": "my-domain.org.",
"id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+ "ipv4_address_scope": None,
+ "ipv6_address_scope": None,
+ "l2_adjacency": False,
"mtu": 0,
"name": "net2",
"port_security_enabled": True,
"project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
"qos_policy_id": "bfdb6c39f71e4d44b1dfbda245c50819",
+ "revision_number": 3,
"router:external": False,
"shared": False,
"status": "ACTIVE",
@@ -69,7 +81,8 @@
"tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
"updated_at": "2016-03-08T20:19:41",
"vlan_transparent": False,
- "description": ""
+ "description": "",
+ "is_default": False
}
]
}
@@ -108,6 +121,7 @@
"alive": True,
"topic": "dhcp_agent",
"host": "osboxes",
+ "ha_state": None,
"agent_type": "DHCP agent",
"resource_versions": {},
"created_at": "2017-06-19 21:39:51",
diff --git a/tempest/tests/lib/services/placement/test_resource_providers_client.py b/tempest/tests/lib/services/placement/test_resource_providers_client.py
new file mode 100644
index 0000000..11aeaf2
--- /dev/null
+++ b/tempest/tests/lib/services/placement/test_resource_providers_client.py
@@ -0,0 +1,119 @@
+# 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.placement import resource_providers_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestResourceProvidersClient(base.BaseServiceTest):
+ FAKE_RESOURCE_PROVIDER_UUID = '3722a86e-a563-11e9-9abb-c3d41b6d3abf'
+ FAKE_ROOT_PROVIDER_UUID = '4a6a57c8-a563-11e9-914e-f3e0478fce53'
+ FAKE_RESOURCE_PROVIDER = {
+ 'generation': 0,
+ 'name': 'Ceph Storage Pool',
+ 'uuid': FAKE_RESOURCE_PROVIDER_UUID,
+ 'parent_provider_uuid': FAKE_ROOT_PROVIDER_UUID,
+ 'root_provider_uuid': FAKE_ROOT_PROVIDER_UUID
+ }
+
+ FAKE_RESOURCE_PROVIDERS = {
+ 'resource_providers': [FAKE_RESOURCE_PROVIDER]
+ }
+
+ FAKE_RESOURCE_PROVIDER_INVENTORIES = {
+ 'inventories': {
+ 'DISK_GB': {
+ 'allocation_ratio': 1.0,
+ 'max_unit': 35,
+ 'min_unit': 1,
+ 'reserved': 0,
+ 'step_size': 1,
+ 'total': 35
+ }
+ },
+ 'resource_provider_generation': 7
+ }
+
+ FAKE_AGGREGATE_UUID = '1166be40-a567-11e9-9f2a-53827f9311fa'
+ FAKE_RESOURCE_PROVIDER_AGGREGATES = {
+ 'aggregates': [FAKE_AGGREGATE_UUID]
+ }
+
+ def setUp(self):
+ super(TestResourceProvidersClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = resource_providers_client.ResourceProvidersClient(
+ fake_auth, 'placement', 'regionOne')
+
+ def _test_list_resource_providers(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_resource_providers,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_RESOURCE_PROVIDERS,
+ to_utf=bytes_body,
+ status=200
+ )
+
+ def test_list_resource_providers_with_bytes_body(self):
+ self._test_list_resource_providers()
+
+ def test_list_resource_providers_with_str_body(self):
+ self._test_list_resource_providers(bytes_body=True)
+
+ def _test_show_resource_provider(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.show_resource_provider,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_RESOURCE_PROVIDER,
+ to_utf=bytes_body,
+ status=200,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+ )
+
+ def test_show_resource_provider_with_str_body(self):
+ self._test_show_resource_provider()
+
+ def test_show_resource_provider_with_bytes_body(self):
+ self._test_show_resource_provider(bytes_body=True)
+
+ def _test_list_resource_provider_inventories(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_resource_provider_inventories,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_RESOURCE_PROVIDER_INVENTORIES,
+ to_utf=bytes_body,
+ status=200,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+ )
+
+ def test_list_resource_provider_inventories_with_str_body(self):
+ self._test_list_resource_provider_inventories()
+
+ def test_list_resource_provider_inventories_with_bytes_body(self):
+ self._test_list_resource_provider_inventories(bytes_body=True)
+
+ def _test_list_resource_provider_aggregates(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_resource_provider_aggregates,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_RESOURCE_PROVIDER_AGGREGATES,
+ to_utf=bytes_body,
+ status=200,
+ rp_uuid=self.FAKE_RESOURCE_PROVIDER_UUID
+ )
+
+ def test_list_resource_provider_aggregates_with_str_body(self):
+ self._test_list_resource_provider_aggregates()
+
+ def test_list_resource_provider_aggregates_with_bytes_body(self):
+ self._test_list_resource_provider_aggregates(bytes_body=True)
diff --git a/tools/tempest-integrated-gate-networking-blacklist.txt b/tools/tempest-integrated-gate-networking-blacklist.txt
index 263b2e4..97808d9 100644
--- a/tools/tempest-integrated-gate-networking-blacklist.txt
+++ b/tools/tempest-integrated-gate-networking-blacklist.txt
@@ -17,3 +17,8 @@
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-placement-blacklist.txt b/tools/tempest-integrated-gate-placement-blacklist.txt
index efba796..657bda2 100644
--- a/tools/tempest-integrated-gate-placement-blacklist.txt
+++ b/tools/tempest-integrated-gate-placement-blacklist.txt
@@ -17,3 +17,8 @@
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_boot_server_from_encrypted_volume_luks
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_image_defined_boot_from_volume
tempest.scenario.test_volume_boot_pattern.TestVolumeBootPattern.test_create_server_from_volume_snapshot
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached
diff --git a/tools/tempest-integrated-gate-storage-blacklist.txt b/tools/tempest-integrated-gate-storage-blacklist.txt
index 1ef6bb5..cbd3e9d 100644
--- a/tools/tempest-integrated-gate-storage-blacklist.txt
+++ b/tools/tempest-integrated-gate-storage-blacklist.txt
@@ -12,3 +12,8 @@
tempest.scenario.test_network_basic_ops.TestNetworkBasicOps
tempest.scenario.test_network_v6.TestGettingAddress
tempest.scenario.test_security_groups_basic_ops.TestSecurityGroupsBasicOps
+
+# TODO(gmann): Remove these test skip once nova bug #1882521 is solved
+tempest.api.compute.volumes.test_attach_volume.AttachVolumeMultiAttachTest.test_resize_server_with_multiattached_volume
+tempest.api.compute.servers.test_server_rescue_negative.ServerRescueNegativeTestJSON
+tempest.api.compute.servers.test_server_rescue.ServerStableDeviceRescueTest.test_stable_device_rescue_disk_virtio_with_volume_attached