diff --git a/keystone/identity/controllers.py b/keystone/identity/controllers.py index 32e6a2e62c..808bd1e7ea 100644 --- a/keystone/identity/controllers.py +++ b/keystone/identity/controllers.py @@ -80,6 +80,8 @@ class User(controller.V2Controller): self.resource_api.get_project(default_project_id) user['default_project_id'] = default_project_id + self.resource_api.ensure_default_domain_exists() + # The manager layer will generate the unique ID for users user_ref = self._normalize_domain_id(context, user.copy()) initiator = notifications._get_request_audit_info(context) diff --git a/keystone/resource/controllers.py b/keystone/resource/controllers.py index f3c4e543da..5cabe06483 100644 --- a/keystone/resource/controllers.py +++ b/keystone/resource/controllers.py @@ -43,8 +43,13 @@ class Tenant(controller.V2Controller): if 'name' in context['query_string']: return self._get_project_by_name(context['query_string']['name']) - tenant_refs = self.resource_api.list_projects_in_domain( - CONF.identity.default_domain_id) + try: + tenant_refs = self.resource_api.list_projects_in_domain( + CONF.identity.default_domain_id) + except exception.DomainNotFound: + # If the default domain doesn't exist then there are no V2 + # projects. + tenant_refs = [] tenant_refs = [self.v3_to_v2_project(tenant_ref) for tenant_ref in tenant_refs if not tenant_ref.get('is_domain')] @@ -91,6 +96,9 @@ class Tenant(controller.V2Controller): raise exception.ValidationError(message=msg) self.assert_admin(context) + + self.resource_api.ensure_default_domain_exists() + tenant_ref['id'] = tenant_ref.get('id', uuid.uuid4().hex) initiator = notifications._get_request_audit_info(context) tenant = self.resource_api.create_project( diff --git a/keystone/resource/core.py b/keystone/resource/core.py index 8ede00e64e..6d0f44198a 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -909,6 +909,32 @@ class Manager(manager.Manager): # from persistence if persistence is enabled. pass + def ensure_default_domain_exists(self): + """Creates the default domain if it doesn't exist. + + This is only used for the v2 API and can go away when V2 does. + + """ + try: + default_domain_attrs = { + 'name': 'Default', + 'id': CONF.identity.default_domain_id, + 'description': 'Domain created automatically to support V2.0 ' + 'operations.', + } + self.create_domain(CONF.identity.default_domain_id, + default_domain_attrs) + LOG.warning(_LW( + 'The default domain was created automatically to contain V2 ' + 'resources. This is deprecated in the M release and will not ' + 'be supported in the O release. Create the default domain ' + 'manually or use the keystone-manage bootstrap command.')) + except exception.Conflict: + LOG.debug('The default domain already exists.') + except Exception: + LOG.error(_LE('Failed to create the default domain.')) + raise + # The ResourceDriverBase class is the set of driver methods from earlier # drivers that we still support, that have not been removed or modified. This diff --git a/keystone/tests/unit/identity/test_controllers.py b/keystone/tests/unit/identity/test_controllers.py new file mode 100644 index 0000000000..ed2fe3ffb4 --- /dev/null +++ b/keystone/tests/unit/identity/test_controllers.py @@ -0,0 +1,65 @@ +# Copyright 2016 IBM Corp. +# +# 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 uuid + +from oslo_config import cfg + +from keystone import exception +from keystone.identity import controllers +from keystone.tests import unit +from keystone.tests.unit.ksfixtures import database + + +CONF = cfg.CONF + +_ADMIN_CONTEXT = {'is_admin': True, 'query_string': {}} + + +class UserTestCaseNoDefaultDomain(unit.TestCase): + + def setUp(self): + super(UserTestCaseNoDefaultDomain, self).setUp() + self.useFixture(database.Database()) + self.load_backends() + self.user_controller = controllers.User() + + def test_setup(self): + # Other tests in this class assume there's no default domain, so make + # sure the setUp worked as expected. + self.assertRaises( + exception.DomainNotFound, + self.resource_api.get_domain, CONF.identity.default_domain_id) + + def test_get_users(self): + # When list_users is done and there's no default domain, the result is + # an empty list. + res = self.user_controller.get_users(_ADMIN_CONTEXT) + self.assertEqual([], res['users']) + + def test_get_user_by_name(self): + # When get_user_by_name is done and there's no default domain, the + # result is 404 Not Found + user_name = uuid.uuid4().hex + self.assertRaises( + exception.UserNotFound, + self.user_controller.get_user_by_name, _ADMIN_CONTEXT, user_name) + + def test_create_user(self): + # When a user is created using the v2 controller and there's no default + # domain, it doesn't fail with can't find domain (a default domain is + # created) + user = {'name': uuid.uuid4().hex} + self.user_controller.create_user(_ADMIN_CONTEXT, user) + # If the above doesn't fail then this is successful. diff --git a/keystone/tests/unit/resource/__init__.py b/keystone/tests/unit/resource/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/keystone/tests/unit/resource/test_controllers.py b/keystone/tests/unit/resource/test_controllers.py new file mode 100644 index 0000000000..b8f247c858 --- /dev/null +++ b/keystone/tests/unit/resource/test_controllers.py @@ -0,0 +1,57 @@ +# Copyright 2016 IBM Corp. +# +# 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 uuid + +from oslo_config import cfg + +from keystone import exception +from keystone.resource import controllers +from keystone.tests import unit +from keystone.tests.unit.ksfixtures import database + + +CONF = cfg.CONF + +_ADMIN_CONTEXT = {'is_admin': True, 'query_string': {}} + + +class TenantTestCaseNoDefaultDomain(unit.TestCase): + + def setUp(self): + super(TenantTestCaseNoDefaultDomain, self).setUp() + self.useFixture(database.Database()) + self.load_backends() + self.tenant_controller = controllers.Tenant() + + def test_setup(self): + # Other tests in this class assume there's no default domain, so make + # sure the setUp worked as expected. + self.assertRaises( + exception.DomainNotFound, + self.resource_api.get_domain, CONF.identity.default_domain_id) + + def test_get_all_projects(self): + # When get_all_projects is done and there's no default domain, the + # result is an empty list. + res = self.tenant_controller.get_all_projects(_ADMIN_CONTEXT) + self.assertEqual([], res['tenants']) + + def test_create_project(self): + # When a project is created using the v2 controller and there's no + # default domain, it doesn't fail with can't find domain (a default + # domain is created) + tenant = {'name': uuid.uuid4().hex} + self.tenant_controller.create_project(_ADMIN_CONTEXT, tenant) + # If the above doesn't fail then this is successful. diff --git a/keystone/tests/unit/resource/test_core.py b/keystone/tests/unit/resource/test_core.py new file mode 100644 index 0000000000..f640dc6dd2 --- /dev/null +++ b/keystone/tests/unit/resource/test_core.py @@ -0,0 +1,91 @@ +# 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 uuid + +from oslo_config import cfg +from oslotest import mockpatch + +from keystone import exception +from keystone.tests import unit +from keystone.tests.unit.ksfixtures import database + + +CONF = cfg.CONF + + +class TestResourceManagerNoFixtures(unit.SQLDriverOverrides, unit.TestCase): + + def setUp(self): + super(TestResourceManagerNoFixtures, self).setUp() + self.useFixture(database.Database(self.sql_driver_version_overrides)) + self.load_backends() + + def test_ensure_default_domain_exists(self): + # When there's no default domain, ensure_default_domain_exists creates + # it. + + # First make sure there's no default domain. + self.assertRaises( + exception.DomainNotFound, + self.resource_api.get_domain, CONF.identity.default_domain_id) + + self.resource_api.ensure_default_domain_exists() + default_domain = self.resource_api.get_domain( + CONF.identity.default_domain_id) + + expected_domain = { + 'id': CONF.identity.default_domain_id, + 'name': 'Default', + 'enabled': True, + 'description': 'Domain created automatically to support V2.0 ' + 'operations.', + } + self.assertEqual(expected_domain, default_domain) + + def test_ensure_default_domain_exists_already_exists(self): + # When there's already a default domain, ensure_default_domain_exists + # doesn't do anything. + + name = uuid.uuid4().hex + description = uuid.uuid4().hex + domain_attrs = { + 'id': CONF.identity.default_domain_id, + 'name': name, + 'description': description, + } + self.resource_api.create_domain(CONF.identity.default_domain_id, + domain_attrs) + + self.resource_api.ensure_default_domain_exists() + + default_domain = self.resource_api.get_domain( + CONF.identity.default_domain_id) + + expected_domain = { + 'id': CONF.identity.default_domain_id, + 'name': name, + 'enabled': True, + 'description': description, + } + + self.assertEqual(expected_domain, default_domain) + + def test_ensure_default_domain_exists_fails(self): + # When there's an unexpected exception creating domain it's passed on. + + self.useFixture(mockpatch.PatchObject( + self.resource_api, 'create_domain', + side_effect=exception.UnexpectedError)) + + self.assertRaises(exception.UnexpectedError, + self.resource_api.ensure_default_domain_exists)