From 2a24e359c6af26e4da6c2b4b2dc40a958acc59ad Mon Sep 17 00:00:00 2001 From: Brant Knudson Date: Sun, 6 Mar 2016 10:47:52 -0600 Subject: [PATCH] Correct create_project driver versioning create_project works differently in the V9 resource driver than it did in V8. The V9 driver allows passing a null domain_id whereas the V8 driver would fail. Since the methods are different, create_project should not be defined in ResourceDriverBase but in ResourceDriverV8 and ResourceDriverV9. Also, its behavior should be documented and tested. Also, the existing documentation was incorrect. It said that Conflict would be raised if there was already a project with the same name but it's only if the same name in the same domain. Change-Id: Ib276310056f8826b97ef2b5f94b91840c46bb761 --- keystone/resource/core.py | 88 ++++++++++++++++--- .../backend/legacy_drivers/resource/V8/sql.py | 26 ++++++ .../tests/unit/resource/backends/__init__.py | 0 .../tests/unit/resource/backends/test_sql.py | 24 +++++ keystone/tests/unit/resource/test_backends.py | 78 ++++++++++++++++ 5 files changed, 206 insertions(+), 10 deletions(-) create mode 100644 keystone/tests/unit/resource/backends/__init__.py create mode 100644 keystone/tests/unit/resource/backends/test_sql.py diff --git a/keystone/resource/core.py b/keystone/resource/core.py index ee9e28c462..ce90111d6e 100644 --- a/keystone/resource/core.py +++ b/keystone/resource/core.py @@ -929,16 +929,6 @@ class ResourceDriverBase(object): return CONF.resource.list_limit or CONF.list_limit # project crud - @abc.abstractmethod - def create_project(self, project_id, project): - """Creates a new project. - - :raises keystone.exception.Conflict: if project_id or project name - already exists - - """ - raise exception.NotImplemented() # pragma: no cover - @abc.abstractmethod def list_projects(self, hints): """List projects in the system. @@ -1092,6 +1082,45 @@ class ResourceDriverV8(ResourceDriverBase): """ + @abc.abstractmethod + def create_project(self, tenant_id, tenant): + """Creates a new project. + + :param tenant_id: This parameter can be ignored. + :param dict tenant: The new project + + Project schema:: + + type: object + properties: + id: + type: string + name: + type: string + domain_id: + type: string + description: + type: string + enabled: + type: boolean + parent_id: + type: string + is_domain: + type: boolean + required: [id, name, domain_id] + additionalProperties: true + + If project doesn't match the schema the behavior is undefined. + + The driver can impose requirements such as the maximum length of a + field. If these requirements are not met the behavior is undefined. + + :raises keystone.exception.Conflict: if the project id already exists + or the name already exists for the domain_id. + + """ + raise exception.NotImplemented() # pragma: no cover + @abc.abstractmethod def get_project_by_name(self, tenant_name, domain_id): """Get a tenant by name. @@ -1204,6 +1233,45 @@ class ResourceDriverV9(ResourceDriverBase): """ + @abc.abstractmethod + def create_project(self, project_id, project): + """Creates a new project. + + :param project_id: This parameter can be ignored. + :param dict project: The new project + + Project schema:: + + type: object + properties: + id: + type: string + name: + type: string + domain_id: + type: [string, null] + description: + type: string + enabled: + type: boolean + parent_id: + type: string + is_domain: + type: boolean + required: [id, name, domain_id] + additionalProperties: true + + If the project doesn't match the schema the behavior is undefined. + + The driver can impose requirements such as the maximum length of a + field. If these requirements are not met the behavior is undefined. + + :raises keystone.exception.Conflict: if the project id already exists + or the name already exists for the domain_id. + + """ + raise exception.NotImplemented() # pragma: no cover + @abc.abstractmethod def get_project_by_name(self, project_name, domain_id): """Get a project by name. diff --git a/keystone/tests/unit/backend/legacy_drivers/resource/V8/sql.py b/keystone/tests/unit/backend/legacy_drivers/resource/V8/sql.py index ba0c855d50..16acbdc338 100644 --- a/keystone/tests/unit/backend/legacy_drivers/resource/V8/sql.py +++ b/keystone/tests/unit/backend/legacy_drivers/resource/V8/sql.py @@ -10,6 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. +import unittest + +from keystone.resource.V8_backends import sql +from keystone.tests import unit +from keystone.tests.unit.ksfixtures import database +from keystone.tests.unit.resource import test_backends from keystone.tests.unit import test_backend_sql @@ -43,3 +49,23 @@ class SqlIdentityV8(test_backend_sql.SqlIdentity): def test_hidden_project_domain_root_is_really_hidden(self): self.skipTest('Operation not supported in v8 and earlier drivers') + + +class TestSqlResourceDriverV8(unit.BaseTestCase, + test_backends.ResourceDriverTests): + def setUp(self): + super(TestSqlResourceDriverV8, self).setUp() + + version_specifiers = { + 'keystone.resource': { + 'versionless_backend': 'backends', + 'versioned_backend': 'V8_backends' + } + } + self.useFixture(database.Database(version_specifiers)) + + self.driver = sql.Resource() + + @unittest.skip('Null domain not allowed.') + def test_create_project_null_domain(self): + pass diff --git a/keystone/tests/unit/resource/backends/__init__.py b/keystone/tests/unit/resource/backends/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/keystone/tests/unit/resource/backends/test_sql.py b/keystone/tests/unit/resource/backends/test_sql.py new file mode 100644 index 0000000000..79ad3df2ea --- /dev/null +++ b/keystone/tests/unit/resource/backends/test_sql.py @@ -0,0 +1,24 @@ +# 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 keystone.resource.backends import sql +from keystone.tests import unit +from keystone.tests.unit.ksfixtures import database +from keystone.tests.unit.resource import test_backends + + +class TestSqlResourceDriver(unit.BaseTestCase, + test_backends.ResourceDriverTests): + def setUp(self): + super(TestSqlResourceDriver, self).setUp() + self.useFixture(database.Database()) + self.driver = sql.Resource() diff --git a/keystone/tests/unit/resource/test_backends.py b/keystone/tests/unit/resource/test_backends.py index 0d7b27d821..eed4c6bad2 100644 --- a/keystone/tests/unit/resource/test_backends.py +++ b/keystone/tests/unit/resource/test_backends.py @@ -1589,3 +1589,81 @@ class ResourceTests(object): project_ref = self.resource_api.get_project(project['id']) self.assertDictEqual(updated_project_ref, project_ref) + + +class ResourceDriverTests(object): + """Tests for the resource driver. + + Subclasses must set self.driver to the driver instance. + + """ + + def test_create_project(self): + project_id = uuid.uuid4().hex + project = { + 'name': uuid.uuid4().hex, + 'id': project_id, + 'domain_id': uuid.uuid4().hex, + } + self.driver.create_project(project_id, project) + + def test_create_project_all_defined_properties(self): + project_id = uuid.uuid4().hex + project = { + 'name': uuid.uuid4().hex, + 'id': project_id, + 'domain_id': uuid.uuid4().hex, + 'description': uuid.uuid4().hex, + 'enabled': True, + 'parent_id': uuid.uuid4().hex, + 'is_domain': True, + } + self.driver.create_project(project_id, project) + + def test_create_project_null_domain(self): + project_id = uuid.uuid4().hex + project = { + 'name': uuid.uuid4().hex, + 'id': project_id, + 'domain_id': None, + } + self.driver.create_project(project_id, project) + + def test_create_project_same_name_same_domain_conflict(self): + name = uuid.uuid4().hex + domain_id = uuid.uuid4().hex + + project_id = uuid.uuid4().hex + project = { + 'name': name, + 'id': project_id, + 'domain_id': domain_id, + } + self.driver.create_project(project_id, project) + + project_id = uuid.uuid4().hex + project = { + 'name': name, + 'id': project_id, + 'domain_id': domain_id, + } + self.assertRaises(exception.Conflict, self.driver.create_project, + project_id, project) + + def test_create_project_same_id_conflict(self): + project_id = uuid.uuid4().hex + + project = { + 'name': uuid.uuid4().hex, + 'id': project_id, + 'domain_id': uuid.uuid4().hex, + } + self.driver.create_project(project_id, project) + + project = { + 'name': uuid.uuid4().hex, + 'id': project_id, + 'domain_id': uuid.uuid4().hex, + } + self.assertRaises(exception.Conflict, self.driver.create_project, + project_id, project)