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
This commit is contained in:
Brant Knudson 2016-03-06 10:47:52 -06:00
parent 46272c9be7
commit 2a24e359c6
5 changed files with 206 additions and 10 deletions

View File

@ -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.

View File

@ -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

View File

@ -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()

View File

@ -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)