Merge "1912117: Fix the IP URL for metadata service in fixed network scenario"
diff --git a/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml b/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml
new file mode 100644
index 0000000..ff406fb
--- /dev/null
+++ b/releasenotes/notes/merge-tempest-horizon-plugin-39d555339ab8c7ce.yaml
@@ -0,0 +1,6 @@
+---
+prelude: >
+ The integrated horizon dashboard test is now moved
+ from tempest-horizon plugin into Tempest. You do not need
+ to install tempest-horizon to run the horizon test which
+ can be run using Tempest itself.
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index dd7d5af..e5137f4 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -142,6 +142,26 @@
self.roles_client.delete_role_from_user_on_domain(
self.domain['id'], self.user_body['id'], self.role['id'])
+ @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+ 'Skipped because environment has an immutable user '
+ 'source and solely provides read-only access to users.')
+ @decorators.idempotent_id('e5a81737-d294-424d-8189-8664858aae4c')
+ def test_grant_list_revoke_role_to_user_on_system(self):
+ self.roles_client.create_user_role_on_system(
+ self.user_body['id'], self.role['id'])
+
+ roles = self.roles_client.list_user_roles_on_system(
+ self.user_body['id'])['roles']
+
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
+
+ self.roles_client.check_user_role_existence_on_system(
+ self.user_body['id'], self.role['id'])
+
+ self.roles_client.delete_role_from_user_on_system(
+ self.user_body['id'], self.role['id'])
+
@decorators.idempotent_id('cbf11737-1904-4690-9613-97bcbb3df1c4')
@testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
'Skipped because environment has an immutable user '
@@ -197,6 +217,23 @@
self.roles_client.delete_role_from_group_on_domain(
self.domain['id'], self.group_body['id'], self.role['id'])
+ @decorators.idempotent_id('c888fe4f-8018-48db-b959-542225c1b4b6')
+ def test_grant_list_revoke_role_to_group_on_system(self):
+ self.roles_client.create_group_role_on_system(
+ self.group_body['id'], self.role['id'])
+
+ roles = self.roles_client.list_group_roles_on_system(
+ self.group_body['id'])['roles']
+
+ self.assertEqual(1, len(roles))
+ self.assertEqual(self.role['id'], roles[0]['id'])
+
+ self.roles_client.check_role_from_group_on_system_existence(
+ self.group_body['id'], self.role['id'])
+
+ self.roles_client.delete_role_from_group_on_system(
+ self.group_body['id'], self.role['id'])
+
@decorators.idempotent_id('f5654bcc-08c4-4f71-88fe-05d64e06de94')
def test_list_roles(self):
"""Test listing roles"""
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 9e25901..d1f6f98 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -158,7 +158,7 @@
self.client.stage_image_file(
image['id'],
- six.BytesIO(data_utils.random_bytes(10485760)))
+ six.BytesIO(data_utils.random_bytes()))
# Check image status is 'uploading'
body = self.client.show_image(image['id'])
self.assertEqual(image['id'], body['id'])
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index c6e5dcb..2d486a7 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -245,6 +245,9 @@
if identity_version == 'v3':
conf_attributes.append('domain_name')
+ conf_attributes.append('user_domain_name')
+ conf_attributes.append('project_domain_name')
+ conf_attributes.append('system')
# Read the parts of credentials from config
params = config.service_client_config()
for attr in conf_attributes:
@@ -284,7 +287,8 @@
if identity_version == 'v3':
domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES
if 'domain' in x)
- if not domain_fields.intersection(kwargs.keys()):
+ if (not params.get('system') and
+ not domain_fields.intersection(kwargs.keys())):
domain_name = CONF.auth.default_credentials_domain_name
# NOTE(andreaf) Setting domain_name implicitly sets user and
# project domain names, if they are None
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 914acf7..38881ee 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -59,6 +59,7 @@
# So we should set this True here.
'identity': True,
'object_storage': CONF.service_available.swift,
+ 'dashboard': CONF.service_available.horizon,
}
return service_list
diff --git a/tempest/config.py b/tempest/config.py
index 382b80f..956b593 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -92,7 +92,24 @@
cfg.StrOpt('admin_domain_name',
default='Default',
help="Admin domain name for authentication (Keystone V3). "
- "The same domain applies to user and project"),
+ "The same domain applies to user and project if "
+ "admin_user_domain_name and admin_project_domain_name "
+ "are not specified"),
+ cfg.StrOpt('admin_user_domain_name',
+ help="Domain name that contains the admin user (Keystone V3). "
+ "May be different from admin_project_domain_name and "
+ "admin_domain_name"),
+ cfg.StrOpt('admin_project_domain_name',
+ help="Domain name that contains the project given by "
+ "admin_project_name (Keystone V3). May be different from "
+ "admin_user_domain_name and admin_domain_name"),
+ cfg.StrOpt('admin_system',
+ default=None,
+ help="The system scope on which an admin user has an admin "
+ "role assignment, if any. Valid values are 'all' or None. "
+ "This must be set to 'all' if using the "
+ "[oslo_policy]/enforce_scope=true option for the "
+ "identity service."),
]
identity_group = cfg.OptGroup(name='identity',
@@ -828,6 +845,18 @@
'This value will be increased in case of conflict.')
]
+dashboard_group = cfg.OptGroup(name="dashboard",
+ title="Dashboard options")
+
+DashboardGroup = [
+ cfg.StrOpt('dashboard_url',
+ default='http://localhost/',
+ help="Where the dashboard can be found"),
+ cfg.BoolOpt('disable_ssl_certificate_validation',
+ default=False,
+ help="Set to True if using self-signed SSL certificates."),
+]
+
validation_group = cfg.OptGroup(name='validation',
title='SSH Validation options')
@@ -1173,6 +1202,9 @@
cfg.BoolOpt('nova',
default=True,
help="Whether or not nova is expected to be available"),
+ cfg.BoolOpt('horizon',
+ default=True,
+ help="Whether or not horizon is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@@ -1236,6 +1268,7 @@
(image_feature_group, ImageFeaturesGroup),
(network_group, NetworkGroup),
(network_feature_group, NetworkFeaturesGroup),
+ (dashboard_group, DashboardGroup),
(validation_group, ValidationGroup),
(volume_group, VolumeGroup),
(volume_feature_group, VolumeFeaturesGroup),
@@ -1303,6 +1336,7 @@
self.image_feature_enabled = _CONF['image-feature-enabled']
self.network = _CONF.network
self.network_feature_enabled = _CONF['network-feature-enabled']
+ self.dashboard = _CONF.dashboard
self.validation = _CONF.validation
self.volume = _CONF.volume
self.volume_feature_enabled = _CONF['volume-feature-enabled']
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 7c279ab..9f8c7c5 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -428,7 +428,7 @@
class KeystoneV3AuthProvider(KeystoneAuthProvider):
"""Provides authentication based on the Identity V3 API"""
- SCOPES = set(['project', 'domain', 'unscoped', None])
+ SCOPES = set(['system', 'project', 'domain', 'unscoped', None])
def _auth_client(self, auth_url):
return json_v3id.V3TokenClient(
@@ -441,8 +441,8 @@
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.
+ "domain", or "system". Any other string (e.g. "unscoped") or None will
+ lead to an unscoped token request.
"""
auth_params = dict(
@@ -465,12 +465,16 @@
domain_id=self.credentials.domain_id,
domain_name=self.credentials.domain_name)
+ if self.scope == 'system':
+ auth_params.update(system='all')
+
return auth_params
def _fill_credentials(self, auth_data_body):
- # project or domain, depending on the scope
+ # project, domain, or system depending on the scope
project = auth_data_body.get('project', None)
domain = auth_data_body.get('domain', None)
+ system = auth_data_body.get('system', None)
# user is always there
user = auth_data_body['user']
# Set project fields
@@ -490,6 +494,9 @@
self.credentials.domain_id = domain['id']
if self.credentials.domain_name is None:
self.credentials.domain_name = domain['name']
+ # Set system scope
+ if system is not None:
+ self.credentials.system = 'all'
# Set user fields
if self.credentials.username is None:
self.credentials.username = user['name']
@@ -677,7 +684,8 @@
raise exceptions.InvalidCredentials(msg)
for key in attr:
if key in self.ATTRIBUTES:
- setattr(self, key, attr[key])
+ if attr[key] is not None:
+ setattr(self, key, attr[key])
else:
msg = '%s is not a valid attr for %s' % (key, self.__class__)
raise exceptions.InvalidCredentials(msg)
@@ -779,7 +787,7 @@
ATTRIBUTES = ['domain_id', 'domain_name', 'password', 'username',
'project_domain_id', 'project_domain_name', 'project_id',
'project_name', 'tenant_id', 'tenant_name', 'user_domain_id',
- 'user_domain_name', 'user_id']
+ 'user_domain_name', 'user_id', 'system']
COLLISIONS = [('project_name', 'tenant_name'), ('project_id', 'tenant_id')]
def __setattr__(self, key, value):
diff --git a/tempest/lib/common/cred_client.py b/tempest/lib/common/cred_client.py
index a81f53c..3af09a0 100644
--- a/tempest/lib/common/cred_client.py
+++ b/tempest/lib/common/cred_client.py
@@ -83,12 +83,15 @@
role['id'], project['id'], user['id'])
@abc.abstractmethod
- def get_credentials(self, user, project, password):
+ def get_credentials(
+ self, user, project, password, domain=None, system=None):
"""Produces a Credentials object from the details provided
:param user: a user dict
- :param project: a project dict
+ :param project: a project dict or None if using domain or system scope
:param password: the password as a string
+ :param domain: a domain dict
+ :param system: a system dict
:return: a Credentials object with all the available credential details
"""
pass
@@ -116,7 +119,8 @@
def delete_project(self, project_id):
self.projects_client.delete_tenant(project_id)
- def get_credentials(self, user, project, password):
+ def get_credentials(
+ self, user, project, password, domain=None, system=None):
# User and project already include both ID and name here,
# so there's no need to use the fill_in mode
return auth.get_credentials(
@@ -156,23 +160,37 @@
def delete_project(self, project_id):
self.projects_client.delete_project(project_id)
- def get_credentials(self, user, project, password):
+ def get_credentials(
+ self, user, project, password, domain=None, system=None):
# User, project and domain already include both ID and name here,
# so there's no need to use the fill_in mode.
# NOTE(andreaf) We need to set all fields in the returned credentials.
# Scope is then used to pick only those relevant for the type of
# token needed by each service client.
+ if project:
+ project_name = project['name']
+ project_id = project['id']
+ else:
+ project_name = None
+ project_id = None
+ if domain:
+ domain_name = domain['name']
+ domain_id = domain['id']
+ else:
+ domain_name = self.creds_domain['name']
+ domain_id = self.creds_domain['id']
return auth.get_credentials(
auth_url=None,
fill_in=False,
identity_version='v3',
username=user['name'], user_id=user['id'],
- project_name=project['name'], project_id=project['id'],
+ project_name=project_name, project_id=project_id,
password=password,
project_domain_id=self.creds_domain['id'],
project_domain_name=self.creds_domain['name'],
- domain_id=self.creds_domain['id'],
- domain_name=self.creds_domain['name'])
+ domain_id=domain_id,
+ domain_name=domain_name,
+ system=system)
def assign_user_role_on_domain(self, user, role_name, domain=None):
"""Assign the specified role on a domain
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index 8b82391..d9f4f46 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -142,7 +142,14 @@
else:
# We use a dedicated client manager for identity client in case we
# need a different token scope for them.
- scope = 'domain' if self.identity_admin_domain_scope else 'project'
+ if self.default_admin_creds.system:
+ scope = 'system'
+ elif (self.identity_admin_domain_scope and
+ (self.default_admin_creds.domain_id or
+ self.default_admin_creds.domain_name)):
+ scope = 'domain'
+ else:
+ scope = 'project'
identity_os = clients.ServiceClients(self.default_admin_creds,
self.identity_uri,
scope=scope)
diff --git a/tempest/lib/services/identity/v3/roles_client.py b/tempest/lib/services/identity/v3/roles_client.py
index 0d7593a..e41dc28 100644
--- a/tempest/lib/services/identity/v3/roles_client.py
+++ b/tempest/lib/services/identity/v3/roles_client.py
@@ -89,6 +89,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def create_user_role_on_system(self, user_id, role_id):
+ """Add roles to a user on the system."""
+ resp, body = self.put('system/users/%s/roles/%s' %
+ (user_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def list_user_roles_on_project(self, project_id, user_id):
"""list roles of a user on a project."""
resp, body = self.get('projects/%s/users/%s/roles' %
@@ -105,6 +112,13 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+ def list_user_roles_on_system(self, user_id):
+ """list roles of a user on the system."""
+ resp, body = self.get('system/users/%s/roles' % user_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
def delete_role_from_user_on_project(self, project_id, user_id, role_id):
"""Delete role of a user on a project."""
resp, body = self.delete('projects/%s/users/%s/roles/%s' %
@@ -119,6 +133,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def delete_role_from_user_on_system(self, user_id, role_id):
+ """Delete role of a user on the system."""
+ resp, body = self.delete('system/users/%s/roles/%s' %
+ (user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def check_user_role_existence_on_project(self, project_id,
user_id, role_id):
"""Check role of a user on a project."""
@@ -135,6 +156,12 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
+ def check_user_role_existence_on_system(self, user_id, role_id):
+ """Check role of a user on the system."""
+ resp, body = self.head('system/users/%s/roles/%s' % (user_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
def create_group_role_on_project(self, project_id, group_id, role_id):
"""Add roles to a group on a project."""
resp, body = self.put('projects/%s/groups/%s/roles/%s' %
@@ -149,6 +176,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def create_group_role_on_system(self, group_id, role_id):
+ """Add roles to a group on the system."""
+ resp, body = self.put('system/groups/%s/roles/%s' %
+ (group_id, role_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def list_group_roles_on_project(self, project_id, group_id):
"""list roles of a group on a project."""
resp, body = self.get('projects/%s/groups/%s/roles' %
@@ -165,6 +199,13 @@
body = json.loads(body)
return rest_client.ResponseBody(resp, body)
+ def list_group_roles_on_system(self, group_id):
+ """list roles of a group on the system."""
+ resp, body = self.get('system/groups/%s/roles' % group_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
def delete_role_from_group_on_project(self, project_id, group_id, role_id):
"""Delete role of a group on a project."""
resp, body = self.delete('projects/%s/groups/%s/roles/%s' %
@@ -179,6 +220,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp, body)
+ def delete_role_from_group_on_system(self, group_id, role_id):
+ """Delete role of a group on the system."""
+ resp, body = self.delete('system/groups/%s/roles/%s' %
+ (group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def check_role_from_group_on_project_existence(self, project_id,
group_id, role_id):
"""Check role of a group on a project."""
@@ -195,6 +243,13 @@
self.expected_success(204, resp.status)
return rest_client.ResponseBody(resp)
+ def check_role_from_group_on_system_existence(self, group_id, role_id):
+ """Check role of a group on the system."""
+ resp, body = self.head('system/groups/%s/roles/%s' %
+ (group_id, role_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
def create_role_inference_rule(self, prior_role, implies_role):
"""Create a role inference rule."""
resp, body = self.put('roles/%s/implies/%s' %
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index 6956297..08a8f46 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -51,7 +51,7 @@
def auth(self, user_id=None, username=None, password=None, project_id=None,
project_name=None, user_domain_id=None, user_domain_name=None,
project_domain_id=None, project_domain_name=None, domain_id=None,
- domain_name=None, token=None, app_cred_id=None,
+ domain_name=None, system=None, token=None, app_cred_id=None,
app_cred_secret=None):
"""Obtains a token from the authentication service
@@ -65,6 +65,7 @@
:param domain_name: a domain name to scope to
:param project_id: a project id to scope to
:param project_name: a project name to scope to
+ :param system: whether the token should be scoped to the system
:param token: a token to re-scope.
Accepts different combinations of credentials.
@@ -74,6 +75,7 @@
- user_id, password
- username, password, user_domain_id
- username, password, project_name, user_domain_id, project_domain_id
+ - username, password, user_domain_id, system
Validation is left to the server side.
"""
creds = {
@@ -135,6 +137,8 @@
creds['auth']['scope'] = dict(domain={'id': domain_id})
elif domain_name:
creds['auth']['scope'] = dict(domain={'name': domain_name})
+ elif system:
+ creds['auth']['scope'] = dict(system={system: True})
body = json.dumps(creds, sort_keys=True)
resp, body = self.post(self.auth_url, body=body)
diff --git a/tempest/scenario/test_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
new file mode 100644
index 0000000..b1098fa
--- /dev/null
+++ b/tempest/scenario/test_dashboard_basic_ops.py
@@ -0,0 +1,141 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import html.parser
+import ssl
+from urllib import parse
+from urllib import request
+
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest import test
+
+CONF = config.CONF
+
+
+class HorizonHTMLParser(html.parser.HTMLParser):
+ csrf_token = None
+ region = None
+ login = None
+
+ def _find_name(self, attrs, name):
+ for attrpair in attrs:
+ if attrpair[0] == 'name' and attrpair[1] == name:
+ return True
+ return False
+
+ def _find_value(self, attrs):
+ for attrpair in attrs:
+ if attrpair[0] == 'value':
+ return attrpair[1]
+ return None
+
+ def _find_attr_value(self, attrs, attr_name):
+ for attrpair in attrs:
+ if attrpair[0] == attr_name:
+ return attrpair[1]
+ return None
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'input':
+ if self._find_name(attrs, 'csrfmiddlewaretoken'):
+ self.csrf_token = self._find_value(attrs)
+ if self._find_name(attrs, 'region'):
+ self.region = self._find_value(attrs)
+ if tag == 'form':
+ self.login = self._find_attr_value(attrs, 'action')
+
+
+class TestDashboardBasicOps(test.BaseTestCase):
+
+ """The test suite for dashboard basic operations
+
+ This is a basic scenario test:
+ * checks that the login page is available
+ * logs in as a regular user
+ * checks that the user home page loads without error
+ """
+ opener = None
+
+ credentials = ['primary']
+
+ @classmethod
+ def skip_checks(cls):
+ super(TestDashboardBasicOps, cls).skip_checks()
+ if not CONF.service_available.horizon:
+ raise cls.skipException("Horizon support is required")
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources()
+ super(TestDashboardBasicOps, cls).setup_credentials()
+
+ def check_login_page(self):
+ response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+ self.assertIn("id_username", response.decode("utf-8"))
+
+ def user_login(self, username, password):
+ response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+
+ # Grab the CSRF token and default region
+ parser = HorizonHTMLParser()
+ parser.feed(response.decode("utf-8"))
+
+ # construct login url for dashboard, discovery accommodates non-/ web
+ # root for dashboard
+ login_url = parse.urljoin(CONF.dashboard.dashboard_url, parser.login)
+
+ # Prepare login form request
+ req = request.Request(login_url)
+ req.add_header('Content-type', 'application/x-www-form-urlencoded')
+ req.add_header('Referer', CONF.dashboard.dashboard_url)
+
+ # Pass the default domain name regardless of the auth version in order
+ # to test the scenario of when horizon is running with keystone v3
+ params = {'username': username,
+ 'password': password,
+ 'region': parser.region,
+ 'domain': CONF.auth.default_credentials_domain_name,
+ 'csrfmiddlewaretoken': parser.csrf_token}
+ self._get_opener().open(req, parse.urlencode(params).encode())
+
+ def check_home_page(self):
+ response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
+ self.assertIn('Overview', response.decode("utf-8"))
+
+ def _get_opener(self):
+ if not self.opener:
+ if (CONF.dashboard.disable_ssl_certificate_validation and
+ self._ssl_default_context_supported()):
+ ctx = ssl.create_default_context()
+ ctx.check_hostname = False
+ ctx.verify_mode = ssl.CERT_NONE
+ self.opener = request.build_opener(
+ request.HTTPSHandler(context=ctx),
+ request.HTTPCookieProcessor())
+ else:
+ self.opener = request.build_opener(
+ request.HTTPCookieProcessor())
+ return self.opener
+
+ def _ssl_default_context_supported(self):
+ return (hasattr(ssl, 'create_default_context'))
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('4f8851b1-0e69-482b-b63b-84c6e76f6c80')
+ @utils.services('dashboard')
+ def test_basic_scenario(self):
+ creds = self.os_primary.credentials
+ self.check_login_page()
+ self.user_login(creds.username, creds.password)
+ self.check_home_page()
diff --git a/tempest/tests/common/test_credentials_factory.py b/tempest/tests/common/test_credentials_factory.py
index 0ef3742..374474d 100644
--- a/tempest/tests/common/test_credentials_factory.py
+++ b/tempest/tests/common/test_credentials_factory.py
@@ -173,10 +173,15 @@
@mock.patch.object(cf, 'get_credentials')
def test_get_configured_admin_credentials(self, mock_get_credentials):
cfg.CONF.set_default('auth_version', 'v3', 'identity')
- all_params = [('admin_username', 'username', 'my_name'),
- ('admin_password', 'password', 'secret'),
- ('admin_project_name', 'project_name', 'my_pname'),
- ('admin_domain_name', 'domain_name', 'my_dname')]
+ all_params = [
+ ('admin_username', 'username', 'my_name'),
+ ('admin_user_domain_name', 'user_domain_name', 'my_dname'),
+ ('admin_password', 'password', 'secret'),
+ ('admin_project_name', 'project_name', 'my_pname'),
+ ('admin_project_domain_name', 'project_domain_name', 'my_dname'),
+ ('admin_domain_name', 'domain_name', 'my_dname'),
+ ('admin_system', 'system', None),
+ ]
expected_result = 'my_admin_credentials'
mock_get_credentials.return_value = expected_result
for config_item, _, value in all_params:
@@ -194,10 +199,15 @@
def test_get_configured_admin_credentials_not_fill_valid(
self, mock_get_credentials):
cfg.CONF.set_default('auth_version', 'v2', 'identity')
- all_params = [('admin_username', 'username', 'my_name'),
- ('admin_password', 'password', 'secret'),
- ('admin_project_name', 'project_name', 'my_pname'),
- ('admin_domain_name', 'domain_name', 'my_dname')]
+ all_params = [
+ ('admin_username', 'username', 'my_name'),
+ ('admin_user_domain_name', 'user_domain_name', 'my_dname'),
+ ('admin_password', 'password', 'secret'),
+ ('admin_project_domain_name', 'project_domain_name', 'my_dname'),
+ ('admin_project_name', 'project_name', 'my_pname'),
+ ('admin_domain_name', 'domain_name', 'my_dname'),
+ ('admin_system', 'system', None),
+ ]
expected_result = mock.Mock()
expected_result.is_valid.return_value = True
mock_get_credentials.return_value = expected_result
@@ -278,3 +288,20 @@
mock_auth_get_credentials.assert_called_once_with(
expected_uri, fill_in=False, identity_version='v3',
**expected_params)
+
+ @mock.patch('tempest.lib.auth.get_credentials')
+ def test_get_credentials_v3_system(self, mock_auth_get_credentials):
+ expected_uri = 'V3_URI'
+ expected_result = 'my_creds'
+ mock_auth_get_credentials.return_value = expected_result
+ cfg.CONF.set_default('uri_v3', expected_uri, 'identity')
+ cfg.CONF.set_default('admin_system', 'all', 'auth')
+ params = {'system': 'all'}
+ expected_params = params.copy()
+ expected_params.update(config.service_client_config())
+ result = cf.get_credentials(fill_in=False, identity_version='v3',
+ **params)
+ self.assertEqual(expected_result, result)
+ mock_auth_get_credentials.assert_called_once_with(
+ expected_uri, fill_in=False, identity_version='v3',
+ **expected_params)
diff --git a/tempest/tests/lib/common/test_cred_client.py b/tempest/tests/lib/common/test_cred_client.py
index 860a465..b99311c 100644
--- a/tempest/tests/lib/common/test_cred_client.py
+++ b/tempest/tests/lib/common/test_cred_client.py
@@ -43,6 +43,14 @@
self.projects_client.delete_tenant.assert_called_once_with(
'fake_id')
+ def test_get_credentials(self):
+ ret = self.creds_client.get_credentials(
+ {'name': 'some_user', 'id': 'fake_id'},
+ {'name': 'some_project', 'id': 'fake_id'},
+ 'password123')
+ self.assertEqual(ret.username, 'some_user')
+ self.assertEqual(ret.project_name, 'some_project')
+
class TestCredClientV3(base.TestCase):
def setUp(self):
@@ -53,7 +61,7 @@
self.roles_client = mock.MagicMock()
self.domains_client = mock.MagicMock()
self.domains_client.list_domains.return_value = {
- 'domains': [{'id': 'fake_domain_id'}]
+ 'domains': [{'id': 'fake_domain_id', 'name': 'some_domain'}]
}
self.creds_client = cred_client.V3CredsClient(self.identity_client,
self.projects_client,
@@ -75,3 +83,31 @@
self.creds_client.delete_project('fake_id')
self.projects_client.delete_project.assert_called_once_with(
'fake_id')
+
+ def test_get_credentials(self):
+ ret = self.creds_client.get_credentials(
+ {'name': 'some_user', 'id': 'fake_id'},
+ {'name': 'some_project', 'id': 'fake_id'},
+ 'password123')
+ self.assertEqual(ret.username, 'some_user')
+ self.assertEqual(ret.project_name, 'some_project')
+ self.assertIsNone(ret.system)
+ self.assertEqual(ret.domain_name, 'some_domain')
+ ret = self.creds_client.get_credentials(
+ {'name': 'some_user', 'id': 'fake_id'},
+ None,
+ 'password123',
+ domain={'name': 'another_domain', 'id': 'another_id'})
+ self.assertEqual(ret.username, 'some_user')
+ self.assertIsNone(ret.project_name)
+ self.assertIsNone(ret.system)
+ self.assertEqual(ret.domain_name, 'another_domain')
+ ret = self.creds_client.get_credentials(
+ {'name': 'some_user', 'id': 'fake_id'},
+ None,
+ 'password123',
+ system={'system': 'all'})
+ self.assertEqual(ret.username, 'some_user')
+ self.assertIsNone(ret.project_name)
+ self.assertEqual(ret.system, {'system': 'all'})
+ self.assertEqual(ret.domain_name, 'some_domain')
diff --git a/tempest/tests/lib/services/identity/v3/test_roles_client.py b/tempest/tests/lib/services/identity/v3/test_roles_client.py
index 8d6bb42..e963310 100644
--- a/tempest/tests/lib/services/identity/v3/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_roles_client.py
@@ -225,6 +225,16 @@
role_id="1234",
status=204)
+ def _test_create_user_role_on_system(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_user_role_on_system,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ user_id="123",
+ role_id="1234",
+ status=204)
+
def _test_list_user_roles_on_project(self, bytes_body=False):
self.check_service_client_function(
self.client.list_user_roles_on_project,
@@ -243,6 +253,14 @@
domain_id="b344506af7644f6794d9cb316600b020",
user_id="123")
+ def _test_list_user_roles_on_system(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_user_roles_on_system,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ user_id="123")
+
def _test_create_group_role_on_project(self, bytes_body=False):
self.check_service_client_function(
self.client.create_group_role_on_project,
@@ -265,6 +283,16 @@
role_id="1234",
status=204)
+ def _test_create_group_role_on_system(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_group_role_on_system,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ group_id="123",
+ role_id="1234",
+ status=204)
+
def _test_list_group_roles_on_project(self, bytes_body=False):
self.check_service_client_function(
self.client.list_group_roles_on_project,
@@ -283,6 +311,15 @@
domain_id="b344506af7644f6794d9cb316600b020",
group_id="123")
+ def _test_list_group_roles_on_system(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_group_roles_on_system,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ROLES,
+ bytes_body,
+ domain_id="b344506af7644f6794d9cb316600b020",
+ group_id="123")
+
def _test_create_role_inference_rule(self, bytes_body=False):
self.check_service_client_function(
self.client.create_role_inference_rule,
@@ -405,6 +442,15 @@
role_id="1234",
status=204)
+ def test_delete_role_from_user_on_system(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_user_on_system,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ user_id="123",
+ role_id="1234",
+ status=204)
+
def test_delete_role_from_group_on_project(self):
self.check_service_client_function(
self.client.delete_role_from_group_on_project,
@@ -425,6 +471,15 @@
role_id="1234",
status=204)
+ def test_delete_role_from_group_on_system(self):
+ self.check_service_client_function(
+ self.client.delete_role_from_group_on_system,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ group_id="123",
+ role_id="1234",
+ status=204)
+
def test_check_user_role_existence_on_project(self):
self.check_service_client_function(
self.client.check_user_role_existence_on_project,
@@ -445,6 +500,15 @@
role_id="1234",
status=204)
+ def test_check_user_role_existence_on_system(self):
+ self.check_service_client_function(
+ self.client.check_user_role_existence_on_system,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ user_id="123",
+ role_id="1234",
+ status=204)
+
def test_check_role_from_group_on_project_existence(self):
self.check_service_client_function(
self.client.check_role_from_group_on_project_existence,
@@ -465,6 +529,15 @@
role_id="1234",
status=204)
+ def test_check_role_from_group_on_system_existence(self):
+ self.check_service_client_function(
+ self.client.check_role_from_group_on_system_existence,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ group_id="123",
+ role_id="1234",
+ status=204)
+
def test_create_role_inference_rule_with_str_body(self):
self._test_create_role_inference_rule()
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index c3a792f..3edb122 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -786,6 +786,19 @@
self.assertIn(attr, auth_params.keys())
self.assertEqual(getattr(all_creds, attr), auth_params[attr])
+ def test_auth_parameters_with_system_scope(self):
+ all_creds = fake_credentials.FakeKeystoneV3AllCredentials()
+ self.auth_provider.credentials = all_creds
+ self.auth_provider.scope = 'system'
+ 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):
diff --git a/zuul.d/integrated-gate.yaml b/zuul.d/integrated-gate.yaml
index 4c1ee5a..27bbf64 100644
--- a/zuul.d/integrated-gate.yaml
+++ b/zuul.d/integrated-gate.yaml
@@ -69,6 +69,8 @@
Former names for this job where:
* legacy-tempest-dsvm-py35
* gate-tempest-dsvm-py35
+ required-projects:
+ - openstack/horizon
vars:
tox_envlist: full
devstack_localrc:
@@ -89,6 +91,8 @@
network-feature-enabled:
qos_placement_physnet: public
devstack_services:
+ # Enbale horizon so that we can run horizon test.
+ horizon: true
s-account: false
s-container: false
s-object: false