Merge "Modify --endpoint-type to --os-endpoint-type for nova"
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index bcb1e3e..d610dc5 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -27,21 +27,13 @@
- Generate test credentials on the fly (see `Dynamic Credentials`_)
Tempest allows for configuring pre-provisioned test credentials as well.
-This can be done in two different ways.
-
-One is to provide credentials is using the accounts.yaml file (see
+This can be done using the accounts.yaml file (see
`Pre-Provisioned Credentials`_). This file is used to specify an arbitrary
number of users available to run tests with.
You can specify the location of the file in the ``auth`` section in the
tempest.conf file. To see the specific format used in the file please refer to
the accounts.yaml.sample file included in Tempest.
-A second way - now deprecated - is a set of configuration options in the
-tempest.conf file (see `Legacy Credentials`_). These options are clearly
-labelled in the ``identity`` section and let you specify a set of credentials
-for a regular user and an alternate user, consisting of a username, password,
-project and domain name.
-
Keystone Connection Info
^^^^^^^^^^^^^^^^^^^^^^^^
In order for Tempest to be able to talk to your OpenStack deployment you need
@@ -134,44 +126,6 @@
Pre-Provisioned Credentials are also know as accounts.yaml or accounts file.
-Legacy Credentials
-""""""""""""""""""
-**Starting in the Liberty release this mechanism was deprecated; it will be
-removed in a future release.**
-
-When Tempest was refactored to allow for locking test accounts, the original
-non-project isolated case was converted to internally work similarly to the
-accounts.yaml file. This mechanism was then called the legacy test accounts
-provider. To use the legacy test accounts provider you can specify the sets of
-credentials in the configuration file as detailed above with following nine
-options in the ``identity`` section:
-
- #. ``username``
- #. ``password``
- #. ``project_name``
- #. ``alt_username``
- #. ``alt_password``
- #. ``alt_project_name``
-
-If using Identity API v3, use the ``domain_name`` option to specify a
-domain other than the default domain. The ``auth_version`` setting is
-used to switch between v2 (``v2``) or v3 (``v3``) versions of the Identity
-API.
-
-And in the ``auth`` section:
-
- #. ``use_dynamic_credentials = False``
- #. Comment out ``test_accounts_file`` or keep it empty.
-
-It only makes sense to use this if parallel execution isn't needed, since
-Tempest won't be able to properly isolate tests using this. Additionally, using
-the traditional config options for credentials is not able to provide
-credentials to tests requiring specific roles on accounts. This is because the
-config options do not give sufficient flexibility to describe the roles assigned
-to a user for running the tests. There are additional limitations with regard to
-network configuration when using this credential provider mechanism - see the
-`Networking`_ section below.
-
Compute
-------
diff --git a/doc/source/library/auth.rst b/doc/source/library/auth.rst
new file mode 100644
index 0000000..e1d92ed
--- /dev/null
+++ b/doc/source/library/auth.rst
@@ -0,0 +1,11 @@
+.. _auth:
+
+Authentication Framework Usage
+==============================
+
+---------------
+The auth module
+---------------
+
+.. automodule:: tempest.lib.auth
+ :members:
diff --git a/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
new file mode 100644
index 0000000..297279f
--- /dev/null
+++ b/releasenotes/notes/add-scope-to-auth-b5a82493ea89f41e.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - Tempest library auth interface now supports scope. Scope allows to control
+ the scope of tokens requested via the identity API. Identity V2 supports
+ unscoped and project scoped tokens, but only the latter are implemented.
+ Identity V3 supports unscoped, project and domain scoped token, all three
+ are available.
\ No newline at end of file
diff --git a/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
new file mode 100644
index 0000000..89b3f41
--- /dev/null
+++ b/releasenotes/notes/remove-legacy-credential-providers-3d653ac3ba1ada2b.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - The deprecated legacy credential provider has been removed. The only way to
+ configure credentials in tempest now is to use the dynamic or preprovisioned
+ credential providers
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f01657b..66aec84 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -226,6 +226,35 @@
self.client.start_server(self.server_id)
+ @test.idempotent_id('b68bd8d6-855d-4212-b59b-2e704044dace')
+ @test.services('volume')
+ def test_rebuild_server_with_volume_attached(self):
+ # create a new volume and attach it to the server
+ volume = self.volumes_client.create_volume(
+ size=CONF.volume.volume_size)
+ volume = volume['volume']
+ self.addCleanup(self.volumes_client.delete_volume, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'available')
+
+ self.client.attach_volume(self.server_id, volumeId=volume['id'])
+ self.addCleanup(waiters.wait_for_volume_status, self.volumes_client,
+ volume['id'], 'available')
+ self.addCleanup(self.client.detach_volume,
+ self.server_id, volume['id'])
+ waiters.wait_for_volume_status(self.volumes_client, volume['id'],
+ 'in-use')
+
+ # run general rebuild test
+ self.test_rebuild_server()
+
+ # make sure the volume is attached to the instance after rebuild
+ vol_after_rebuild = self.volumes_client.show_volume(volume['id'])
+ vol_after_rebuild = vol_after_rebuild['volume']
+ self.assertEqual('in-use', vol_after_rebuild['status'])
+ self.assertEqual(self.server_id,
+ vol_after_rebuild['attachments'][0]['server_id'])
+
def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
@@ -312,7 +341,8 @@
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
- self.os.image_client.wait_for_image_status(image1_id, 'active')
+ waiters.wait_for_image_status(self.os.image_client,
+ image1_id, 'active')
backup2 = data_utils.rand_name('backup-2')
waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
@@ -322,7 +352,8 @@
name=backup2).response
image2_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(self.os.image_client.delete_image, image2_id)
- self.os.image_client.wait_for_image_status(image2_id, 'active')
+ waiters.wait_for_image_status(self.os.image_client,
+ image2_id, 'active')
# verify they have been created
properties = {
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 46ecba1..5fda4c14 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -83,13 +83,14 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.create_user,
self.alt_user, self.alt_password,
self.data.tenant['id'], self.alt_email)
- # Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
-
@test.attr(type=['negative'])
@test.idempotent_id('23a2f3da-4a1a-41da-abdd-632328a861ad')
def test_create_user_with_enabled_non_bool(self):
@@ -119,11 +120,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.update_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('424868d5-18a7-43e1-8903-a64f95ee3aac')
@@ -159,11 +161,12 @@
token = self.client.auth_provider.get_token()
# Delete the token from database
self.client.delete_token(token)
- self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
- self.alt_user)
# Unset the token to allow further tests to generate a new token
- self.client.auth_provider.clear_auth()
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
+ self.assertRaises(lib_exc.Unauthorized, self.users_client.delete_user,
+ self.alt_user)
@test.attr(type=['negative'])
@test.idempotent_id('593a4981-f6d4-460a-99a1-57a78bf20829')
@@ -229,8 +232,11 @@
# Request to get list of users without a valid token should fail
token = self.client.auth_provider.get_token()
self.client.delete_token(token)
+
+ # Unset the token to allow further tests to generate a new token
+ self.addCleanup(self.client.auth_provider.clear_auth)
+
self.assertRaises(lib_exc.Unauthorized, self.users_client.list_users)
- self.client.auth_provider.clear_auth()
@test.attr(type=['negative'])
@test.idempotent_id('f5d39046-fc5f-425c-b29e-bac2632da28e')
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 62ddead..79f2576 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -13,13 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
from tempest.api.identity import base
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
-from tempest import manager
from tempest import test
@@ -35,23 +33,26 @@
@test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity Client with new credentials, since
- # current non_admin_client token will be revoked after updating
- # password
- self.non_admin_users_client_for_cleanup = copy.copy(
- self.non_admin_users_client)
- self.non_admin_users_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
- old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_own_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
+ old_pass = self.creds.credentials.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_users_client_for_cleanup.update_user_own_password,
- user_id, original_password=new_pass, password=old_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
+
# user updates own password
self.non_admin_users_client.update_user_own_password(
user_id, password=new_pass, original_password=old_pass)
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 60fbe12..76b46c0 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -13,13 +13,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import copy
import time
from tempest.api.identity import base
from tempest.lib.common.utils import data_utils
from tempest.lib import exceptions
-from tempest import manager
from tempest import test
@@ -35,24 +33,25 @@
@test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
def test_user_update_own_password(self):
- self.new_creds = copy.copy(self.creds.credentials)
- self.new_creds.password = data_utils.rand_password()
- # we need new non-admin Identity V3 Client with new credentials, since
- # current non_admin_users_client token will be revoked after updating
- # password
- self.non_admin_users_client_for_cleanup = (
- copy.copy(self.non_admin_users_client))
- self.non_admin_users_client_for_cleanup.auth_provider = (
- manager.get_auth_provider(self.new_creds))
- user_id = self.creds.credentials.user_id
+
+ def _restore_password(client, user_id, old_pass, new_pass):
+ # Reset auth to get a new token with the new password
+ client.auth_provider.clear_auth()
+ client.auth_provider.credentials.password = new_pass
+ client.update_user_password(user_id, password=old_pass,
+ original_password=new_pass)
+ # Reset auth again to verify the password restore does work.
+ # Clear auth restores the original credentials and deletes
+ # cached auth data
+ client.auth_provider.clear_auth()
+ client.auth_provider.set_auth()
+
old_pass = self.creds.credentials.password
- new_pass = self.new_creds.password
+ new_pass = data_utils.rand_password()
+ user_id = self.creds.credentials.user_id
# to change password back. important for allow_tenant_isolation = false
- self.addCleanup(
- self.non_admin_users_client_for_cleanup.update_user_password,
- user_id,
- password=old_pass,
- original_password=new_pass)
+ self.addCleanup(_restore_password, self.non_admin_users_client,
+ user_id, old_pass=old_pass, new_pass=new_pass)
# user updates own password
self.non_admin_users_client.update_user_password(
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index cad22f3..6d5559d 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -17,6 +17,7 @@
from tempest.api.image import base
from tempest.common.utils import data_utils
+from tempest.common import waiters
from tempest import config
from tempest import exceptions
from tempest import test
@@ -95,7 +96,7 @@
image_id = body.get('id')
self.assertEqual('New Http Image', body.get('name'))
self.assertFalse(body.get('is_public'))
- self.client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(self.client, image_id, 'active')
self.client.show_image(image_id)
@test.idempotent_id('05b19d55-140c-40d0-b36b-fafd774d421b')
@@ -305,7 +306,7 @@
@test.idempotent_id('01752c1c-0275-4de3-9e5b-876e44541928')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'value1'}
self.assertEqual(expected, resp_metadata['properties'])
@@ -313,12 +314,12 @@
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key2': 'value2'}
- metadata = self.client.get_image_meta(self.image_id)
+ metadata = self.client.check_image(self.image_id)
self.assertEqual(metadata['properties'], {'key1': 'value1'})
metadata['properties'].update(req_metadata)
metadata = self.client.update_image(
self.image_id, properties=metadata['properties'])['image']
- resp_metadata = self.client.get_image_meta(self.image_id)
+ resp_metadata = self.client.check_image(self.image_id)
expected = {'key1': 'alt1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata['properties'])
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index e52216f..5975231 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -125,7 +125,7 @@
disk_format=CONF.volume.disk_format)['os-volume_upload_image']
image_id = body["image_id"]
self.addCleanup(self._cleanup_image, image_id)
- self.image_client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(self.image_client, image_id, 'active')
waiters.wait_for_volume_status(self.client,
self.volume['id'], 'available')
diff --git a/tempest/clients.py b/tempest/clients.py
index 2ad1733..19f1a2a 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -131,7 +131,8 @@
from tempest.services.identity.v3.json.users_clients import \
UsersClient as UsersV3Client
from tempest.services.image.v1.json.images_client import ImagesClient
-from tempest.services.image.v2.json.images_client import ImagesClientV2
+from tempest.services.image.v2.json.images_client import \
+ ImagesClient as ImagesV2Client
from tempest.services.network.json.routers_client import RoutersClient
from tempest.services.object_storage.account_client import AccountClient
from tempest.services.object_storage.container_client import ContainerClient
@@ -197,14 +198,15 @@
}
default_params_with_timeout_values.update(default_params)
- def __init__(self, credentials, service=None):
+ def __init__(self, credentials, service=None, scope='project'):
"""Initialization of Manager class.
Setup all services clients and make them available for tests cases.
:param credentials: type Credentials or TestResources
:param service: Service name
+ :param scope: default scope for tokens produced by the auth provider
"""
- super(Manager, self).__init__(credentials=credentials)
+ super(Manager, self).__init__(credentials=credentials, scope=scope)
self._set_compute_clients()
self._set_database_clients()
self._set_identity_clients()
@@ -330,7 +332,7 @@
build_interval=CONF.image.build_interval,
build_timeout=CONF.image.build_timeout,
**self.default_params)
- self.image_client_v2 = ImagesClientV2(
+ self.image_client_v2 = ImagesV2Client(
self.auth_provider,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 9b3cac7..17ee618 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -233,7 +233,7 @@
**object_storage_params)
self.containers = container_client.ContainerClient(
_auth, **object_storage_params)
- self.images = images_client.ImagesClientV2(
+ self.images = images_client.ImagesClient(
_auth,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 6cb43f3..7c73ada 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -14,7 +14,6 @@
from oslo_concurrency import lockutils
from tempest import clients
-from tempest.common import cred_provider
from tempest.common import dynamic_creds
from tempest.common import preprov_creds
from tempest import config
@@ -62,89 +61,6 @@
]))
-class LegacyCredentialProvider(cred_provider.CredentialProvider):
-
- def __init__(self, identity_version):
- """Credentials provider which returns credentials from tempest.conf
-
- Credentials provider which always returns the first and second
- configured accounts as primary and alt users.
- Credentials from tempest.conf are deprecated, and this credential
- provider is also accordingly.
-
- This credential provider can be used in case of serial test execution
- to preserve the current behaviour of the serial tempest run.
-
- :param identity_version: Version of the identity API
- :return: CredentialProvider
- """
- super(LegacyCredentialProvider, self).__init__(
- identity_version=identity_version)
- self._creds = {}
-
- def _unique_creds(self, cred_arg=None):
- """Verify that the configured credentials are valid and distinct """
- try:
- user = self.get_primary_creds()
- alt_user = self.get_alt_creds()
- return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
- except exceptions.InvalidCredentials as ic:
- msg = "At least one of the configured credentials is " \
- "not valid: %s" % ic.message
- raise exceptions.InvalidConfiguration(msg)
-
- def is_multi_user(self):
- return self._unique_creds('username')
-
- def is_multi_tenant(self):
- return self._unique_creds('tenant_id')
-
- def get_primary_creds(self):
- if self._creds.get('primary'):
- return self._creds.get('primary')
- primary_credential = get_configured_credentials(
- credential_type='user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['primary'] = cred_provider.TestResources(
- primary_credential)
- return self._creds['primary']
-
- def get_alt_creds(self):
- if self._creds.get('alt'):
- return self._creds.get('alt')
- alt_credential = get_configured_credentials(
- credential_type='alt_user', fill_in=False,
- identity_version=self.identity_version)
- self._creds['alt'] = cred_provider.TestResources(
- alt_credential)
- return self._creds['alt']
-
- def clear_creds(self):
- self._creds = {}
-
- def get_admin_creds(self):
- if self._creds.get('admin'):
- return self._creds.get('admin')
- creds = get_configured_credentials(
- "identity_admin", fill_in=False)
- self._creds['admin'] = cred_provider.TestResources(creds)
- return self._creds['admin']
-
- def get_creds_by_roles(self, roles, force_new=False):
- msg = "Credentials being specified through the config file can not be"\
- " used with tests that specify using credentials by roles. "\
- "Either exclude/skip the tests doing this or use either a "\
- "test_accounts_file or dynamic credentials."
- raise exceptions.InvalidConfiguration(msg)
-
- def is_role_available(self, role):
- # NOTE(andreaf) LegacyCredentialProvider does not support credentials
- # by role, so returning always False.
- # Test that rely on credentials by role should use this to skip
- # when this is credential provider is used
- return False
-
-
# Return the right implementation of CredentialProvider based on config
# Dropping interface and password, as they are never used anyways
# TODO(andreaf) Drop them from the CredentialsProvider interface completely
@@ -172,9 +88,8 @@
name=name, identity_version=identity_version,
**_get_preprov_provider_params())
else:
- # Dynamic credentials are disabled, and the account file is not
- # defined - we fall back on credentials configured in tempest.conf
- return LegacyCredentialProvider(identity_version=identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
# We want a helper function here to check and see if admin credentials
@@ -218,7 +133,9 @@
identity_version=identity_version, name='check_alt',
**_get_preprov_provider_params())
else:
- check_accounts = LegacyCredentialProvider(identity_version)
+ raise exceptions.InvalidConfiguration(
+ 'A valid credential provider is needed')
+
try:
if not check_accounts.is_multi_user():
return False
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 95305f3..23d7f88 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -19,6 +19,7 @@
from tempest import exceptions
from tempest.lib.common.utils import misc as misc_utils
from tempest.lib import exceptions as lib_exc
+from tempest.services.image.v1.json import images_client as images_v1_client
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -122,42 +123,44 @@
The client should have a show_image(image_id) method to get the image.
The client should also have build_interval and build_timeout attributes.
"""
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
- if 'image' in image:
- image = image['image']
- start = int(time.time())
+ if isinstance(client, images_v1_client.ImagesClient):
+ # The 'check_image' method is used here because the show_image method
+ # returns image details plus the image itself which is very expensive.
+ # The 'check_image' method returns just image details.
+ show_image = client.check_image
+ else:
+ show_image = client.show_image
- while image['status'] != status:
- time.sleep(client.build_interval)
- image = client.show_image(image_id)
- # Compute image client return response wrapped in 'image' element
- # which is not case with glance image client.
+ current_status = 'An unknown status'
+ start = int(time.time())
+ while int(time.time()) - start < client.build_timeout:
+ image = show_image(image_id)
+ # Compute image client returns response wrapped in 'image' element
+ # which is not case with Glance image client.
if 'image' in image:
image = image['image']
- status_curr = image['status']
- if status_curr == 'ERROR':
+
+ current_status = image['status']
+ if current_status == status:
+ return
+ if current_status.lower() == 'killed':
+ raise exceptions.ImageKilledException(image_id=image_id,
+ status=status)
+ if current_status.lower() == 'error':
raise exceptions.AddImageException(image_id=image_id)
- # check the status again to avoid a false negative where we hit
- # the timeout at the same time that the image reached the expected
- # status
- if status_curr == status:
- return
+ time.sleep(client.build_interval)
- if int(time.time()) - start >= client.build_timeout:
- message = ('Image %(image_id)s failed to reach %(status)s state'
- '(current state %(status_curr)s) '
- 'within the required time (%(timeout)s s).' %
- {'image_id': image_id,
- 'status': status,
- 'status_curr': status_curr,
- 'timeout': client.build_timeout})
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
+ message = ('Image %(image_id)s failed to reach %(status)s state '
+ '(current state %(current_status)s) within the required '
+ 'time (%(timeout)s s).' % {'image_id': image_id,
+ 'status': status,
+ 'current_status': current_status,
+ 'timeout': client.build_timeout})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ raise exceptions.TimeoutException(message)
def wait_for_volume_status(client, volume_id, status):
diff --git a/tempest/config.py b/tempest/config.py
index 6360c3e..f5125b5 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -160,41 +160,9 @@
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for OpenStack Identity "
"(Keystone) API v3"),
- cfg.StrOpt('username',
- help="Username to use for Nova API requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('project_name',
- deprecated_name='tenant_name',
- help="Project name to use for Nova API requests.",
- deprecated_for_removal=True),
cfg.StrOpt('admin_role',
default='admin',
help="Role required to administrate keystone."),
- cfg.StrOpt('password',
- help="API key to use when authenticating.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('domain_name',
- help="Domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_username',
- help="Username of alternate user to use for Nova API "
- "requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_project_name',
- deprecated_name='alt_tenant_name',
- help="Alternate user's Project name to use for Nova API "
- "requests.",
- deprecated_for_removal=True),
- cfg.StrOpt('alt_password',
- help="API key to use when authenticating as alternate user.",
- secret=True,
- deprecated_for_removal=True),
- cfg.StrOpt('alt_domain_name',
- help="Alternate domain name for authentication (Keystone V3)."
- "The same domain applies to user and project",
- deprecated_for_removal=True),
cfg.StrOpt('default_domain_id',
default='default',
help="ID of the default domain"),
@@ -1250,12 +1218,6 @@
self.baremetal = _CONF.baremetal
self.input_scenario = _CONF['input-scenario']
self.negative = _CONF.negative
- _CONF.set_default('domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
- _CONF.set_default('alt_domain_name',
- self.auth.default_credentials_domain_name,
- group='identity')
logging.tempest_set_log_file('tempest.log')
def __init__(self, parse_conf=True, config_path=None):
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index a6833be..ffcc4fb 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -68,10 +68,16 @@
class AuthProvider(object):
"""Provide authentication"""
- def __init__(self, credentials):
+ SCOPES = set(['project'])
+
+ def __init__(self, credentials, scope='project'):
"""Auth provider __init__
:param credentials: credentials for authentication
+ :param scope: the default scope to be used by the credential providers
+ when requesting a token. Valid values depend on the
+ AuthProvider class implementation, and are defined in
+ the set SCOPES. Default value is 'project'.
"""
if self.check_credentials(credentials):
self.credentials = credentials
@@ -88,6 +94,8 @@
raise TypeError("credentials object is of type %s, which is"
" not a valid Credentials object type." %
credentials.__class__.__name__)
+ self._scope = None
+ self.scope = scope
self.cache = None
self.alt_auth_data = None
self.alt_part = None
@@ -123,8 +131,14 @@
@property
def auth_data(self):
+ """Auth data for set scope"""
return self.get_auth()
+ @property
+ def scope(self):
+ """Scope used in auth requests"""
+ return self._scope
+
@auth_data.deleter
def auth_data(self):
self.clear_auth()
@@ -139,7 +153,7 @@
"""Forces setting auth.
Forces setting auth, ignores cache if it exists.
- Refills credentials
+ Refills credentials.
"""
self.cache = self._get_auth()
self._fill_credentials(self.cache[1])
@@ -222,6 +236,19 @@
"""Extracts the base_url based on provided filters"""
return
+ @scope.setter
+ def scope(self, value):
+ """Set the scope to be used in token requests
+
+ :param scope: scope to be used. If the scope is different, clear caches
+ """
+ if value not in self.SCOPES:
+ raise exceptions.InvalidScope(
+ scope=value, auth_provider=self.__class__.__name__)
+ if value != self.scope:
+ self.clear_auth()
+ self._scope = value
+
class KeystoneAuthProvider(AuthProvider):
@@ -231,17 +258,18 @@
def __init__(self, credentials, auth_url,
disable_ssl_certificate_validation=None,
- ca_certs=None, trace_requests=None):
- super(KeystoneAuthProvider, self).__init__(credentials)
+ ca_certs=None, trace_requests=None, scope='project'):
+ super(KeystoneAuthProvider, self).__init__(credentials, scope)
self.dsvm = disable_ssl_certificate_validation
self.ca_certs = ca_certs
self.trace_requests = trace_requests
+ self.auth_url = auth_url
self.auth_client = self._auth_client(auth_url)
def _decorate_request(self, filters, method, url, headers=None, body=None,
auth_data=None):
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _ = auth_data
base_url = self.base_url(filters=filters, auth_data=auth_data)
# build authenticated request
@@ -265,6 +293,11 @@
@abc.abstractmethod
def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ By default all fields available in Credentials are passed to the
+ token request. Scope may affect this.
+ """
return
def _get_auth(self):
@@ -292,10 +325,17 @@
return expiry
def get_token(self):
- return self.auth_data[0]
+ return self.get_auth()[0]
class KeystoneV2AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V2 API
+
+ The Keystone Identity V2 API defines both unscoped and project scoped
+ tokens. This auth provider only implements 'project'.
+ """
+
+ SCOPES = set(['project'])
def _auth_client(self, auth_url):
return json_v2id.TokenClient(
@@ -303,6 +343,10 @@
ca_certs=self.ca_certs, trace_requests=self.trace_requests)
def _auth_params(self):
+ """Auth parameters to be passed to the token request
+
+ All fields available in Credentials are passed to the token request.
+ """
return dict(
user=self.credentials.username,
password=self.credentials.password,
@@ -332,7 +376,7 @@
- skip_path: take just the base URL
"""
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _auth_data = auth_data
service = filters.get('service')
region = filters.get('region')
@@ -365,6 +409,9 @@
class KeystoneV3AuthProvider(KeystoneAuthProvider):
+ """Provides authentication based on the Identity V3 API"""
+
+ SCOPES = set(['project', 'domain', 'unscoped', None])
def _auth_client(self, auth_url):
return json_v3id.V3TokenClient(
@@ -372,20 +419,36 @@
ca_certs=self.ca_certs, trace_requests=self.trace_requests)
def _auth_params(self):
- return dict(
+ """Auth parameters to be passed to the token request
+
+ Fields available in Credentials are passed to the token request,
+ depending on the value of scope. Valid values for scope are: "project",
+ "domain". Any other string (e.g. "unscoped") or None will lead to an
+ unscoped token request.
+ """
+
+ auth_params = dict(
user_id=self.credentials.user_id,
username=self.credentials.username,
- password=self.credentials.password,
- project_id=self.credentials.project_id,
- project_name=self.credentials.project_name,
user_domain_id=self.credentials.user_domain_id,
user_domain_name=self.credentials.user_domain_name,
- project_domain_id=self.credentials.project_domain_id,
- project_domain_name=self.credentials.project_domain_name,
- domain_id=self.credentials.domain_id,
- domain_name=self.credentials.domain_name,
+ password=self.credentials.password,
auth_data=True)
+ if self.scope == 'project':
+ auth_params.update(
+ project_domain_id=self.credentials.project_domain_id,
+ project_domain_name=self.credentials.project_domain_name,
+ project_id=self.credentials.project_id,
+ project_name=self.credentials.project_name)
+
+ if self.scope == 'domain':
+ auth_params.update(
+ domain_id=self.credentials.domain_id,
+ domain_name=self.credentials.domain_name)
+
+ return auth_params
+
def _fill_credentials(self, auth_data_body):
# project or domain, depending on the scope
project = auth_data_body.get('project', None)
@@ -422,6 +485,10 @@
def base_url(self, filters, auth_data=None):
"""Base URL from catalog
+ If scope is not 'project', it may be that there is not catalog in
+ the auth_data. In such case, as long as the requested service is
+ 'identity', we can use the original auth URL to build the base_url.
+
Filters can be:
- service: compute, image, etc
- region: the service region
@@ -430,7 +497,7 @@
- skip_path: take just the base URL
"""
if auth_data is None:
- auth_data = self.auth_data
+ auth_data = self.get_auth()
token, _auth_data = auth_data
service = filters.get('service')
region = filters.get('region')
@@ -442,14 +509,20 @@
if 'URL' in endpoint_type:
endpoint_type = endpoint_type.replace('URL', '')
_base_url = None
- catalog = _auth_data['catalog']
+ catalog = _auth_data.get('catalog', [])
# Select entries with matching service type
service_catalog = [ep for ep in catalog if ep['type'] == service]
if len(service_catalog) > 0:
service_catalog = service_catalog[0]['endpoints']
else:
- # No matching service
- raise exceptions.EndpointNotFound(service)
+ if len(catalog) == 0 and service == 'identity':
+ # NOTE(andreaf) If there's no catalog at all and the service
+ # is identity, it's a valid use case. Having a non-empty
+ # catalog with no identity in it is not valid instead.
+ return apply_url_filters(self.auth_url, filters)
+ else:
+ # No matching service
+ raise exceptions.EndpointNotFound(service)
# Filter by endpoint type (interface)
filtered_catalog = [ep for ep in service_catalog if
ep['interface'] == endpoint_type]
@@ -465,7 +538,7 @@
# There should be only one match. If not take the first.
_base_url = filtered_catalog[0].get('url', None)
if _base_url is None:
- raise exceptions.EndpointNotFound(service)
+ raise exceptions.EndpointNotFound(service)
return apply_url_filters(_base_url, filters)
def is_expired(self, auth_data):
@@ -669,7 +742,7 @@
def is_valid(self):
"""Check of credentials (no API call)
- Valid combinations of v3 credentials (excluding token, scope)
+ Valid combinations of v3 credentials (excluding token)
- User id, password (optional domain)
- User name, password and its domain id/name
For the scope, valid combinations are:
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index b9b2ae9..259bbbb 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -207,6 +207,10 @@
message = "Invalid Credentials"
+class InvalidScope(TempestException):
+ message = "Invalid Scope %(scope)s for %(auth_provider)s"
+
+
class SSHTimeout(TempestException):
message = ("Connection to the %(host)s via SSH timed out.\n"
"User: %(user)s, Password: %(password)s")
diff --git a/tempest/manager.py b/tempest/manager.py
index c97e0d1..cafa5b9 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -28,13 +28,14 @@
and a client object for a test case to use in performing actions.
"""
- def __init__(self, credentials):
+ def __init__(self, credentials, scope='project'):
"""Initialization of base manager class
Credentials to be used within the various client classes managed by the
Manager object must be defined.
:param credentials: type Credentials or TestResources
+ :param scope: default scope for tokens produced by the auth provider
"""
self.credentials = credentials
# Check if passed or default credentials are valid
@@ -48,7 +49,8 @@
else:
creds = self.credentials
# Creates an auth provider for the credentials
- self.auth_provider = get_auth_provider(creds, pre_auth=True)
+ self.auth_provider = get_auth_provider(creds, pre_auth=True,
+ scope=scope)
def get_auth_provider_class(credentials):
@@ -58,7 +60,7 @@
return auth.KeystoneV2AuthProvider, CONF.identity.uri
-def get_auth_provider(credentials, pre_auth=False):
+def get_auth_provider(credentials, pre_auth=False, scope='project'):
default_params = {
'disable_ssl_certificate_validation':
CONF.identity.disable_ssl_certificate_validation,
@@ -71,6 +73,7 @@
auth_provider_class, auth_url = get_auth_provider_class(
credentials)
_auth_provider = auth_provider_class(credentials, auth_url,
+ scope=scope,
**default_params)
if pre_auth:
_auth_provider.set_auth()
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 956fe88..65677a0 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -459,13 +459,13 @@
LOG.debug("Creating a snapshot image for server: %s", server['name'])
image = _images_client.create_image(server['id'], name=name)
image_id = image.response['location'].split('images/')[1]
- _image_client.wait_for_image_status(image_id, 'active')
+ waiters.wait_for_image_status(_image_client, image_id, 'active')
self.addCleanup_with_wait(
waiter_callable=_image_client.wait_for_resource_deletion,
thing_id=image_id, thing_id_param='id',
cleanup_callable=self.delete_wrapper,
cleanup_args=[_image_client.delete_image, image_id])
- snapshot_image = _image_client.get_image_meta(image_id)
+ snapshot_image = _image_client.check_image(image_id)
bdm = snapshot_image.get('properties', {}).get('block_device_mapping')
if bdm:
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index b9fdd18..f0ae223 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -754,10 +754,10 @@
The test steps are :
1. Create a new network.
2. Connect (hotplug) the VM to a new network.
- 3. Check the VM can ping the DHCP interface of this network.
+ 3. Check the VM can ping a server on the new network ("peer")
4. Spoof the mac address of the new VM interface.
5. Check the Security Group enforces mac spoofing and blocks pings via
- spoofed interface (VM cannot ping the DHCP interface).
+ spoofed interface (VM cannot ping the peer).
6. Disable port-security of the spoofed port- set the flag to false.
7. Retest 3rd step and check that the Security Group allows pings via
the spoofed interface.
@@ -778,18 +778,18 @@
ssh_client = self.get_remote_client(fip.floating_ip_address,
private_key=private_key)
spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
- dhcp_ports = self._list_ports(device_owner="network:dhcp",
- network_id=self.new_net["id"])
- new_net_dhcp = dhcp_ports[0]["fixed_ips"][0]["ip_address"]
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ name = data_utils.rand_name('peer')
+ peer = self._create_server(name, self.new_net)
+ peer_address = peer['addresses'][self.new_net.name][0]['addr']
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
ssh_client.set_mac_address(spoof_nic, spoof_mac)
new_mac = ssh_client.get_mac_address(nic=spoof_nic)
self.assertEqual(spoof_mac, new_mac)
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=False)
self.ports_client.update_port(spoof_port["id"],
port_security_enabled=False,
security_groups=[])
- self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ self._check_remote_connectivity(ssh_client, dest=peer_address,
nic=spoof_nic, should_succeed=True)
diff --git a/tempest/services/identity/v3/json/projects_client.py b/tempest/services/identity/v3/json/projects_client.py
index dc553d0..97e43df 100644
--- a/tempest/services/identity/v3/json/projects_client.py
+++ b/tempest/services/identity/v3/json/projects_client.py
@@ -23,17 +23,15 @@
api_version = "v3"
def create_project(self, name, **kwargs):
- """Creates a project."""
- description = kwargs.get('description', None)
- en = kwargs.get('enabled', True)
- domain_id = kwargs.get('domain_id', 'default')
- post_body = {
- 'description': description,
- 'domain_id': domain_id,
- 'enabled': en,
- 'name': name
- }
- post_body = json.dumps({'project': post_body})
+ """Create a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#createProject
+
+ """
+ # Include the project name to the kwargs parameters
+ kwargs['name'] = name
+ post_body = json.dumps({'project': kwargs})
resp, body = self.post('projects', post_body)
self.expected_success(201, resp.status)
body = json.loads(body)
@@ -49,19 +47,13 @@
return rest_client.ResponseBody(resp, body)
def update_project(self, project_id, **kwargs):
- body = self.show_project(project_id)['project']
- name = kwargs.get('name', body['name'])
- desc = kwargs.get('description', body['description'])
- en = kwargs.get('enabled', body['enabled'])
- domain_id = kwargs.get('domain_id', body['domain_id'])
- post_body = {
- 'id': project_id,
- 'name': name,
- 'description': desc,
- 'enabled': en,
- 'domain_id': domain_id,
- }
- post_body = json.dumps({'project': post_body})
+ """Update a Project.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-identity-v3.html#updateProject
+
+ """
+ post_body = json.dumps({'project': kwargs})
resp, body = self.patch('projects/%s' % project_id, post_body)
self.expected_success(200, resp.status)
body = json.loads(body)
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index e29ff89..a36075f 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -16,7 +16,6 @@
import copy
import errno
import os
-import time
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
@@ -24,9 +23,7 @@
from six.moves.urllib import parse as urllib
from tempest.common import glance_http
-from tempest import exceptions
from tempest.lib.common import rest_client
-from tempest.lib.common.utils import misc as misc_utils
from tempest.lib import exceptions as lib_exc
LOG = logging.getLogger(__name__)
@@ -198,7 +195,8 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
- def get_image_meta(self, image_id):
+ def check_image(self, image_id):
+ """Check image metadata."""
url = 'v1/images/%s' % image_id
resp, __ = self.head(url)
self.expected_success(200, resp.status)
@@ -206,6 +204,7 @@
return rest_client.ResponseBody(resp, body)
def show_image(self, image_id):
+ """Get image details plus the image itself."""
url = 'v1/images/%s' % image_id
resp, body = self.get(url)
self.expected_success(200, resp.status)
@@ -213,7 +212,7 @@
def is_resource_deleted(self, id):
try:
- if self.get_image_meta(id)['status'] == 'deleted':
+ if self.check_image(id)['status'] == 'deleted':
return True
except lib_exc.NotFound:
return True
@@ -256,40 +255,3 @@
resp, __ = self.delete(url)
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
-
- # NOTE(afazekas): just for the wait function
- def _get_image_status(self, image_id):
- meta = self.get_image_meta(image_id)
- status = meta['status']
- return status
-
- # NOTE(afazkas): Wait reinvented again. It is not in the correct layer
- def wait_for_image_status(self, image_id, status):
- """Waits for a Image to reach a given status."""
- start_time = time.time()
- old_value = value = self._get_image_status(image_id)
- while True:
- dtime = time.time() - start_time
- time.sleep(self.build_interval)
- if value != old_value:
- LOG.info('Value transition from "%s" to "%s"'
- 'in %d second(s).', old_value,
- value, dtime)
- if value == status:
- return value
-
- if value == 'killed':
- raise exceptions.ImageKilledException(image_id=image_id,
- status=status)
- if dtime > self.build_timeout:
- message = ('Time Limit Exceeded! (%ds)'
- 'while waiting for %s, '
- 'but we got %s.' %
- (self.build_timeout, status, value))
- caller = misc_utils.find_test_caller()
- if caller:
- message = '(%s) %s' % (caller, message)
- raise exceptions.TimeoutException(message)
- time.sleep(self.build_interval)
- old_value = value
- value = self._get_image_status(image_id)
diff --git a/tempest/services/image/v2/json/images_client.py b/tempest/services/image/v2/json/images_client.py
index 4e037af..a61f31d 100644
--- a/tempest/services/image/v2/json/images_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -21,10 +21,10 @@
from tempest.lib import exceptions as lib_exc
-class ImagesClientV2(rest_client.RestClient):
+class ImagesClient(rest_client.RestClient):
def __init__(self, auth_provider, catalog_type, region, **kwargs):
- super(ImagesClientV2, self).__init__(
+ super(ImagesClient, self).__init__(
auth_provider, catalog_type, region, **kwargs)
self._http = None
self.dscv = kwargs.get("disable_ssl_certificate_validation")
diff --git a/tempest/tests/common/test_alt_available.py b/tempest/tests/common/test_alt_available.py
index d4cfab6..27db95c 100644
--- a/tempest/tests/common/test_alt_available.py
+++ b/tempest/tests/common/test_alt_available.py
@@ -49,28 +49,6 @@
else:
self.useFixture(mockpatch.Patch('os.path.isfile',
return_value=False))
- cred_prefix = ['', 'alt_']
- for ii in range(0, 2):
- if len(creds) > ii:
- username = 'u%s' % creds[ii]
- project = 't%s' % creds[ii]
- password = 'p'
- domain = 'd'
- else:
- username = None
- project = None
- password = None
- domain = None
-
- cfg.CONF.set_default('%susername' % cred_prefix[ii], username,
- group='identity')
- cfg.CONF.set_default('%sproject_name' % cred_prefix[ii],
- project, group='identity')
- cfg.CONF.set_default('%spassword' % cred_prefix[ii], password,
- group='identity')
- cfg.CONF.set_default('%sdomain_name' % cred_prefix[ii], domain,
- group='identity')
-
expected = len(set(creds)) > 1 or dynamic_creds
observed = credentials.is_alt_available(
identity_version=self.identity_version)
@@ -97,21 +75,6 @@
use_accounts_file=True,
creds=['1', '1'])
- def test__no_dynamic_creds__no_accounts_file__one_user(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '2'])
-
- def test__no_dynamic_creds__no_accounts_file__two_users_identical(self):
- self.run_test(dynamic_creds=False,
- use_accounts_file=False,
- creds=['1', '1'])
-
class TestAltAvailableV3(TestAltAvailable):
diff --git a/tempest/tests/common/test_configured_creds.py b/tempest/tests/common/test_configured_creds.py
deleted file mode 100644
index 3c242b3..0000000
--- a/tempest/tests/common/test_configured_creds.py
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_config import cfg
-
-from tempest.common import credentials_factory as common_creds
-from tempest.common import tempest_fixtures as fixtures
-from tempest import config
-from tempest.lib import auth
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.identity.v2 import token_client as v2_client
-from tempest.lib.services.identity.v3 import token_client as v3_client
-from tempest.tests import base
-from tempest.tests import fake_config
-from tempest.tests.lib import fake_identity
-
-
-class ConfiguredV2CredentialsTests(base.TestCase):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'tenant_name': 'fake_tenant_name'
- }
-
- identity_response = fake_identity._fake_v2_response
- credentials_class = auth.KeystoneV2Credentials
- tokenclient_class = v2_client.TokenClient
- identity_version = 'v2'
-
- def setUp(self):
- super(ConfiguredV2CredentialsTests, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.patchobject(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
- self.patchobject(self.tokenclient_class, 'raw_request',
- self.identity_response)
-
- def _get_credentials(self, attributes=None):
- if attributes is None:
- attributes = self.attributes
- return self.credentials_class(**attributes)
-
- def _check(self, credentials, credentials_class, filled):
- # Check the right version of credentials has been returned
- self.assertIsInstance(credentials, credentials_class)
- # Check the id attributes are filled in
- attributes = [x for x in credentials.ATTRIBUTES if (
- '_id' in x and x != 'domain_id')]
- for attr in attributes:
- if filled:
- self.assertIsNotNone(getattr(credentials, attr))
- else:
- self.assertIsNone(getattr(credentials, attr))
-
- def _verify_credentials(self, credentials_class, filled=True,
- identity_version=None):
- for ctype in common_creds.CREDENTIAL_TYPES:
- if identity_version is None:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled)
- else:
- creds = common_creds.get_configured_credentials(
- credential_type=ctype, fill_in=filled,
- identity_version=identity_version)
- self._check(creds, credentials_class, filled)
-
- def test_create(self):
- creds = self._get_credentials()
- self.assertEqual(self.attributes, creds._initial)
-
- def test_create_invalid_attr(self):
- self.assertRaises(lib_exc.InvalidCredentials,
- self._get_credentials,
- attributes=dict(invalid='fake'))
-
- def test_get_configured_credentials(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class)
-
- def test_get_configured_credentials_unfilled(self):
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- filled=False)
-
- def test_get_configured_credentials_version(self):
- # version specified and not loaded from config
- self.useFixture(fixtures.LockFixture('auth_version'))
- self._verify_credentials(credentials_class=self.credentials_class,
- identity_version=self.identity_version)
-
- def test_is_valid(self):
- creds = self._get_credentials()
- self.assertTrue(creds.is_valid())
-
-
-class ConfiguredV3CredentialsTests(ConfiguredV2CredentialsTests):
- attributes = {
- 'username': 'fake_username',
- 'password': 'fake_password',
- 'project_name': 'fake_project_name',
- 'user_domain_name': 'fake_domain_name'
- }
-
- credentials_class = auth.KeystoneV3Credentials
- identity_response = fake_identity._fake_v3_response
- tokenclient_class = v3_client.V3TokenClient
- identity_version = 'v3'
-
- def setUp(self):
- super(ConfiguredV3CredentialsTests, self).setUp()
- # Additional config items reset by cfg fixture after each test
- cfg.CONF.set_default('auth_version', 'v3', group='identity')
- # Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- cfg.CONF.set_default(prefix + 'domain_name', 'fake_domain_name',
- group=group)
diff --git a/tempest/tests/common/test_credentials.py b/tempest/tests/common/test_credentials.py
deleted file mode 100644
index 00f2d39..0000000
--- a/tempest/tests/common/test_credentials.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright 2015 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest.common import credentials_factory as credentials
-from tempest import config
-from tempest import exceptions
-from tempest.tests import base
-from tempest.tests import fake_config
-
-
-class TestLegacyCredentialsProvider(base.TestCase):
-
- fixed_params = {'identity_version': 'v2'}
-
- def setUp(self):
- super(TestLegacyCredentialsProvider, self).setUp()
- self.useFixture(fake_config.ConfigFixture())
- self.patchobject(config, 'TempestConfigPrivate',
- fake_config.FakePrivate)
-
- def test_get_creds_roles_legacy_invalid(self):
- test_accounts_class = credentials.LegacyCredentialProvider(
- **self.fixed_params)
- self.assertRaises(exceptions.InvalidConfiguration,
- test_accounts_class.get_creds_by_roles,
- ['fake_role'])
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 492bdca..a56f837 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -38,8 +38,7 @@
# Ensure waiter returns before build_timeout
self.assertTrue((end_time - start_time) < 10)
- @mock.patch('time.sleep')
- def test_wait_for_image_status_timeout(self, mock_sleep):
+ def test_wait_for_image_status_timeout(self):
time_mock = self.patch('time.time')
time_mock.side_effect = utils.generate_timeout_series(1)
@@ -47,15 +46,12 @@
self.assertRaises(exceptions.TimeoutException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
- mock_sleep.assert_called_once_with(1)
- @mock.patch('time.sleep')
- def test_wait_for_image_status_error_on_image_create(self, mock_sleep):
+ def test_wait_for_image_status_error_on_image_create(self):
self.client.show_image.return_value = ({'status': 'ERROR'})
self.assertRaises(exceptions.AddImageException,
waiters.wait_for_image_status,
self.client, 'fake_image_id', 'active')
- mock_sleep.assert_called_once_with(1)
@mock.patch.object(time, 'sleep')
def test_wait_for_volume_status_error_restoring(self, mock_sleep):
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index edd7186..65164a0 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -48,14 +48,9 @@
self.conf.set_default('auth_version', 'v2', group='identity')
for config_option in ['username', 'password', 'project_name']:
# Identity group items
- for prefix in ['', 'alt_', 'admin_']:
- if prefix == 'admin_':
- group = 'auth'
- else:
- group = 'identity'
- self.conf.set_default(prefix + config_option,
- 'fake_' + config_option,
- group=group)
+ self.conf.set_default('admin_' + config_option,
+ 'fake_' + config_option,
+ group='auth')
class FakePrivate(config.TempestConfigPrivate):
diff --git a/tempest/tests/lib/fake_credentials.py b/tempest/tests/lib/fake_credentials.py
index fb81bd6..eac4ada 100644
--- a/tempest/tests/lib/fake_credentials.py
+++ b/tempest/tests/lib/fake_credentials.py
@@ -57,3 +57,18 @@
user_domain_name='fake_domain_name'
)
super(FakeKeystoneV3DomainCredentials, self).__init__(**creds)
+
+
+class FakeKeystoneV3AllCredentials(auth.KeystoneV3Credentials):
+ """Fake credentials for the Keystone Identity V3 API, with no scope"""
+
+ def __init__(self):
+ creds = dict(
+ username='fake_username',
+ password='fake_password',
+ user_domain_name='fake_domain_name',
+ project_name='fake_tenant_name',
+ project_domain_name='fake_domain_name',
+ domain_name='fake_domain_name'
+ )
+ super(FakeKeystoneV3AllCredentials, self).__init__(**creds)
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index 5732065..c903e47 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -133,6 +133,49 @@
}
}
+IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ "catalog": CATALOG_V3
+ }
+}
+
+IDENTITY_V3_RESPONSE_NO_SCOPE = {
+ "token": {
+ "methods": [
+ "token",
+ "password"
+ ],
+ "expires_at": "2020-01-01T00:00:10.000123Z",
+ "user": {
+ "domain": {
+ "id": "fake_domain_id",
+ "name": "domain_name"
+ },
+ "id": "fake_user_id",
+ "name": "username"
+ },
+ "issued_at": "2013-05-29T16:55:21.468960Z",
+ }
+}
+
ALT_IDENTITY_V3 = IDENTITY_V3_RESPONSE
@@ -145,6 +188,28 @@
json.dumps(IDENTITY_V3_RESPONSE))
+def _fake_v3_response_domain_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_DOMAIN_SCOPE))
+
+
+def _fake_v3_response_no_scope(self, uri, method="GET", body=None,
+ headers=None, redirections=5,
+ connection_type=None):
+ fake_headers = {
+ "status": "201",
+ "x-subject-token": TOKEN
+ }
+ return (fake_http.fake_http_response(fake_headers, status=201),
+ json.dumps(IDENTITY_V3_RESPONSE_NO_SCOPE))
+
+
def _fake_v2_response(self, uri, method="GET", body=None, headers=None,
redirections=5, connection_type=None):
return (fake_http.fake_http_response({}, status=200),
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index cc71c92..c253187 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -15,6 +15,7 @@
import copy
import datetime
+import testtools
from oslotest import mockpatch
@@ -425,6 +426,16 @@
self.assertEqual(self.auth_provider.is_expired(auth_data),
should_be_expired)
+ def test_set_scope_all_valid(self):
+ for scope in self.auth_provider.SCOPES:
+ self.auth_provider.scope = scope
+ self.assertEqual(scope, self.auth_provider.scope)
+
+ def test_set_scope_invalid(self):
+ with testtools.ExpectedException(exceptions.InvalidScope,
+ '.* invalid_scope .*'):
+ self.auth_provider.scope = 'invalid_scope'
+
class TestKeystoneV3AuthProvider(TestKeystoneV2AuthProvider):
_endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
@@ -529,6 +540,98 @@
expected = 'http://fake_url/v3'
self._test_base_url_helper(expected, filters, ('token', auth_data))
+ # Base URL test with scope only for V3
+ def test_base_url_scope_project(self):
+ self.auth_provider.scope = 'project'
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = self._get_result_url_from_endpoint(
+ self._endpoints[0]['endpoints'][1])
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_identity(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'identity',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ expected = fake_identity.FAKE_AUTH_URL
+ self._test_base_url_helper(expected, self.filters)
+
+ # Base URL test with scope only for V3
+ def test_base_url_unscoped_other(self):
+ self.auth_provider.scope = 'unscoped'
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_no_scope)
+ self.filters = {
+ 'service': 'compute',
+ 'endpoint_type': 'publicURL',
+ 'region': 'FakeRegion'
+ }
+ self.assertRaises(exceptions.EndpointNotFound,
+ self.auth_provider.base_url,
+ auth_data=self.auth_provider.auth_data,
+ filters=self.filters)
+
+ def test_auth_parameters_with_scope_unset(self):
+ # No scope defaults to 'project'
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_project_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'project'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_with_domain_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'domain'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
+ def test_auth_parameters_unscoped(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'unscoped'
+ auth_params = self.auth_provider._auth_params()
+ self.assertNotIn('scope', auth_params.keys())
+ for attr in all_creds.get_init_attributes():
+ if attr.startswith('project_') or attr.startswith('domain_'):
+ self.assertNotIn(attr, auth_params.keys())
+ else:
+ self.assertIn(attr, auth_params.keys())
+ self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+
class TestKeystoneV3Credentials(base.TestCase):
def testSetAttrUserDomain(self):
@@ -630,3 +733,20 @@
self.assertEqual(
'http://localhost/identity/v2.0/uuid/',
auth.replace_version('http://localhost/identity/v3/uuid/', 'v2.0'))
+
+
+class TestKeystoneV3AuthProvider_DomainScope(BaseAuthTestsSetUp):
+ _endpoints = fake_identity.IDENTITY_V3_RESPONSE['token']['catalog']
+ _auth_provider_class = auth.KeystoneV3AuthProvider
+ credentials = fake_credentials.FakeKeystoneV3Credentials()
+
+ def setUp(self):
+ super(TestKeystoneV3AuthProvider_DomainScope, self).setUp()
+ self.patchobject(v3_client.V3TokenClient, 'raw_request',
+ fake_identity._fake_v3_response_domain_scope)
+
+ def test_get_auth_with_domain_scope(self):
+ self.auth_provider.scope = 'domain'
+ _, auth_data = self.auth_provider.get_auth()
+ self.assertIn('domain', auth_data)
+ self.assertNotIn('project', auth_data)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index fa7129d..e34dec3 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -54,7 +54,6 @@
'q-meta',
'q-metering',
'q-svc',
- 'q-vpn',
's-proxy'])