Merge "Add is_domain field in Project Table"
This commit is contained in:
commit
59ea878c8c
|
@ -252,6 +252,12 @@ class V2Controller(wsgi.Application):
|
|||
ref.pop('parent_id', None)
|
||||
return ref
|
||||
|
||||
@staticmethod
|
||||
def filter_is_domain(ref):
|
||||
"""Remove is_domain field since v2 calls are not domain-aware."""
|
||||
ref.pop('is_domain', None)
|
||||
return ref
|
||||
|
||||
@staticmethod
|
||||
def normalize_username_in_response(ref):
|
||||
"""Adds username to outgoing user refs to match the v2 spec.
|
||||
|
@ -340,6 +346,7 @@ class V2Controller(wsgi.Application):
|
|||
"""Run through the various filter methods."""
|
||||
V2Controller.filter_domain_id(ref)
|
||||
V2Controller.filter_project_parent_id(ref)
|
||||
V2Controller.filter_is_domain(ref)
|
||||
return ref
|
||||
|
||||
if isinstance(ref, dict):
|
||||
|
|
|
@ -130,11 +130,12 @@ class Project(Model):
|
|||
Optional Keys:
|
||||
description
|
||||
enabled (bool, default True)
|
||||
is_domain (bool, default False)
|
||||
|
||||
"""
|
||||
|
||||
required_keys = ('id', 'name', 'domain_id')
|
||||
optional_keys = ('description', 'enabled')
|
||||
optional_keys = ('description', 'enabled', 'is_domain')
|
||||
|
||||
|
||||
class Role(Model):
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# 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 sqlalchemy as sql
|
||||
|
||||
|
||||
_PROJECT_TABLE_NAME = 'project'
|
||||
_IS_DOMAIN_COLUMN_NAME = 'is_domain'
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = sql.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
project_table = sql.Table(_PROJECT_TABLE_NAME, meta, autoload=True)
|
||||
is_domain = sql.Column(_IS_DOMAIN_COLUMN_NAME, sql.Boolean, nullable=False,
|
||||
server_default='0', default=False)
|
||||
project_table.create_column(is_domain)
|
|
@ -60,6 +60,14 @@ class Resource(resource.Driver):
|
|||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def _set_default_is_domain_project(self, ref):
|
||||
if isinstance(ref, dict):
|
||||
return dict(ref, is_domain=False)
|
||||
elif isinstance(ref, list):
|
||||
return [self._set_default_is_domain_project(x) for x in ref]
|
||||
else:
|
||||
raise ValueError(_('Expected dict or list: %s') % type(ref))
|
||||
|
||||
def _validate_parent_project_is_none(self, ref):
|
||||
"""If a parent_id different from None was given,
|
||||
raises InvalidProjectException.
|
||||
|
@ -69,8 +77,15 @@ class Resource(resource.Driver):
|
|||
if parent_id is not None:
|
||||
raise exception.InvalidParentProject(parent_id)
|
||||
|
||||
def _validate_is_domain_field_is_false(self, ref):
|
||||
is_domain = ref.pop('is_domain', None)
|
||||
if is_domain:
|
||||
raise exception.ValidationError(_('LDAP does not support projects '
|
||||
'with is_domain flag enabled'))
|
||||
|
||||
def _set_default_attributes(self, project_ref):
|
||||
project_ref = self._set_default_domain(project_ref)
|
||||
project_ref = self._set_default_is_domain_project(project_ref)
|
||||
return self._set_default_parent_project(project_ref)
|
||||
|
||||
def get_project(self, tenant_id):
|
||||
|
@ -117,6 +132,7 @@ class Resource(resource.Driver):
|
|||
def create_project(self, tenant_id, tenant):
|
||||
self.project.check_allow_create()
|
||||
self._validate_parent_project_is_none(tenant)
|
||||
self._validate_is_domain_field_is_false(tenant)
|
||||
tenant['name'] = clean.project_name(tenant['name'])
|
||||
data = tenant.copy()
|
||||
if 'id' not in data or data['id'] is None:
|
||||
|
@ -129,6 +145,7 @@ class Resource(resource.Driver):
|
|||
def update_project(self, tenant_id, tenant):
|
||||
self.project.check_allow_update()
|
||||
tenant = self._validate_default_domain(tenant)
|
||||
self._validate_is_domain_field_is_false(tenant)
|
||||
if 'name' in tenant:
|
||||
tenant['name'] = clean.project_name(tenant['name'])
|
||||
return self._set_default_attributes(
|
||||
|
|
|
@ -245,7 +245,7 @@ class Domain(sql.ModelBase, sql.DictBase):
|
|||
class Project(sql.ModelBase, sql.DictBase):
|
||||
__tablename__ = 'project'
|
||||
attributes = ['id', 'name', 'domain_id', 'description', 'enabled',
|
||||
'parent_id']
|
||||
'parent_id', 'is_domain']
|
||||
id = sql.Column(sql.String(64), primary_key=True)
|
||||
name = sql.Column(sql.String(64), nullable=False)
|
||||
domain_id = sql.Column(sql.String(64), sql.ForeignKey('domain.id'),
|
||||
|
@ -254,6 +254,7 @@ class Project(sql.ModelBase, sql.DictBase):
|
|||
enabled = sql.Column(sql.Boolean)
|
||||
extra = sql.Column(sql.JsonBlob())
|
||||
parent_id = sql.Column(sql.String(64), sql.ForeignKey('project.id'))
|
||||
is_domain = sql.Column(sql.Boolean, default=False, nullable=False)
|
||||
# Unique constraint across two columns to create the separation
|
||||
# rather than just only 'name' being unique
|
||||
__table_args__ = (sql.UniqueConstraint('domain_id', 'name'), {})
|
||||
|
|
|
@ -47,24 +47,34 @@ class Tenant(controller.V2Controller):
|
|||
self.assert_admin(context)
|
||||
tenant_refs = self.resource_api.list_projects_in_domain(
|
||||
CONF.identity.default_domain_id)
|
||||
for tenant_ref in tenant_refs:
|
||||
tenant_ref = self.v3_to_v2_project(tenant_ref)
|
||||
tenant_refs = [self.v3_to_v2_project(tenant_ref)
|
||||
for tenant_ref in tenant_refs
|
||||
if not tenant_ref.get('is_domain')]
|
||||
params = {
|
||||
'limit': context['query_string'].get('limit'),
|
||||
'marker': context['query_string'].get('marker'),
|
||||
}
|
||||
return self.format_project_list(tenant_refs, **params)
|
||||
|
||||
def _assert_not_is_domain_project(self, project_id, project_ref=None):
|
||||
# Projects acting as a domain should not be visible via v2
|
||||
if not project_ref:
|
||||
project_ref = self.resource_api.get_project(project_id)
|
||||
if project_ref.get('is_domain'):
|
||||
raise exception.ProjectNotFound(project_id)
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project(self, context, tenant_id):
|
||||
# TODO(termie): this stuff should probably be moved to middleware
|
||||
self.assert_admin(context)
|
||||
ref = self.resource_api.get_project(tenant_id)
|
||||
self._assert_not_is_domain_project(tenant_id, ref)
|
||||
return {'tenant': self.v3_to_v2_project(ref)}
|
||||
|
||||
@controller.v2_deprecated
|
||||
def get_project_by_name(self, context, tenant_name):
|
||||
self.assert_admin(context)
|
||||
# Projects acting as a domain should not be visible via v2
|
||||
ref = self.resource_api.get_project_by_name(
|
||||
tenant_name, CONF.identity.default_domain_id)
|
||||
return {'tenant': self.v3_to_v2_project(ref)}
|
||||
|
@ -88,11 +98,12 @@ class Tenant(controller.V2Controller):
|
|||
@controller.v2_deprecated
|
||||
def update_project(self, context, tenant_id, tenant):
|
||||
self.assert_admin(context)
|
||||
# Remove domain_id if specified - a v2 api caller should not
|
||||
# be specifying that
|
||||
self._assert_not_is_domain_project(tenant_id)
|
||||
# Remove domain_id and is_domain if specified - a v2 api caller
|
||||
# should not be specifying that
|
||||
clean_tenant = tenant.copy()
|
||||
clean_tenant.pop('domain_id', None)
|
||||
|
||||
clean_tenant.pop('is_domain', None)
|
||||
tenant_ref = self.resource_api.update_project(
|
||||
tenant_id, clean_tenant)
|
||||
return {'tenant': self.v3_to_v2_project(tenant_ref)}
|
||||
|
@ -100,6 +111,7 @@ class Tenant(controller.V2Controller):
|
|||
@controller.v2_deprecated
|
||||
def delete_project(self, context, tenant_id):
|
||||
self.assert_admin(context)
|
||||
self._assert_not_is_domain_project(tenant_id)
|
||||
self.resource_api.delete_project(tenant_id)
|
||||
|
||||
|
||||
|
@ -201,6 +213,12 @@ class ProjectV3(controller.V3Controller):
|
|||
def create_project(self, context, project):
|
||||
ref = self._assign_unique_id(self._normalize_dict(project))
|
||||
ref = self._normalize_domain_id(context, ref)
|
||||
|
||||
if ref.get('is_domain'):
|
||||
msg = _('The creation of projects acting as domains is not '
|
||||
'allowed yet.')
|
||||
raise exception.NotImplemented(msg)
|
||||
|
||||
initiator = notifications._get_request_audit_info(context)
|
||||
try:
|
||||
ref = self.resource_api.create_project(ref['id'], ref,
|
||||
|
|
|
@ -88,6 +88,7 @@ class Manager(manager.Manager):
|
|||
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
|
||||
tenant.setdefault('description', '')
|
||||
tenant.setdefault('parent_id', None)
|
||||
tenant.setdefault('is_domain', False)
|
||||
|
||||
self.get_domain(tenant.get('domain_id'))
|
||||
if tenant.get('parent_id') is not None:
|
||||
|
@ -198,6 +199,11 @@ class Manager(manager.Manager):
|
|||
raise exception.ForbiddenAction(
|
||||
action=_('Update of `parent_id` is not allowed.'))
|
||||
|
||||
if ('is_domain' in tenant and
|
||||
tenant['is_domain'] != original_tenant['is_domain']):
|
||||
raise exception.ValidationError(
|
||||
message=_('Update of `is_domain` is not allowed.'))
|
||||
|
||||
if 'enabled' in tenant:
|
||||
tenant['enabled'] = clean.project_enabled(tenant['enabled'])
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ _project_properties = {
|
|||
# implementation.
|
||||
'domain_id': parameter_types.id_string,
|
||||
'enabled': parameter_types.boolean,
|
||||
'is_domain': parameter_types.boolean,
|
||||
'parent_id': validation.nullable(parameter_types.id_string),
|
||||
'name': {
|
||||
'type': 'string',
|
||||
|
|
|
@ -25,6 +25,7 @@ TENANTS = [
|
|||
'description': 'description',
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'baz',
|
||||
'name': 'BAZ',
|
||||
|
@ -32,6 +33,7 @@ TENANTS = [
|
|||
'description': 'description',
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'mtu',
|
||||
'name': 'MTU',
|
||||
|
@ -39,6 +41,7 @@ TENANTS = [
|
|||
'enabled': True,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}, {
|
||||
'id': 'service',
|
||||
'name': 'service',
|
||||
|
@ -46,6 +49,7 @@ TENANTS = [
|
|||
'enabled': True,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': None,
|
||||
'is_domain': False,
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -2089,7 +2089,7 @@ class IdentityTests(object):
|
|||
# Create a project
|
||||
project = {'id': uuid.uuid4().hex, 'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'name': uuid.uuid4().hex, 'description': uuid.uuid4().hex,
|
||||
'enabled': True, 'parent_id': None}
|
||||
'enabled': True, 'parent_id': None, 'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Build driver hints with the project's name and inexistent description
|
||||
|
@ -2157,7 +2157,8 @@ class IdentityTests(object):
|
|||
'domain_id': domain_id,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project_id, project)
|
||||
|
||||
projects = [project]
|
||||
|
@ -2167,13 +2168,38 @@ class IdentityTests(object):
|
|||
'domain_id': domain_id,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project_id}
|
||||
'parent_id': project_id,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(new_project['id'], new_project)
|
||||
projects.append(new_project)
|
||||
project_id = new_project['id']
|
||||
|
||||
return projects
|
||||
|
||||
def test_create_project_without_is_domain_flag(self):
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
|
||||
ref = self.resource_api.create_project(project['id'], project)
|
||||
# The is_domain flag should be False by default
|
||||
self.assertFalse(ref['is_domain'])
|
||||
|
||||
def test_create_is_domain_project(self):
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None,
|
||||
'is_domain': True}
|
||||
|
||||
ref = self.resource_api.create_project(project['id'], project)
|
||||
self.assertTrue(ref['is_domain'])
|
||||
|
||||
def test_check_leaf_projects(self):
|
||||
projects_hierarchy = self._create_projects_hierarchy()
|
||||
root_project = projects_hierarchy[0]
|
||||
|
@ -2201,7 +2227,8 @@ class IdentityTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project2['id']}
|
||||
'parent_id': project2['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project4['id'], project4)
|
||||
|
||||
subtree = self.resource_api.list_projects_in_subtree(project1['id'])
|
||||
|
@ -2270,7 +2297,8 @@ class IdentityTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': project2['id']}
|
||||
'parent_id': project2['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project4['id'], project4)
|
||||
|
||||
parents1 = self.resource_api.list_project_parents(project3['id'])
|
||||
|
@ -2873,7 +2901,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': 'fake'}
|
||||
'parent_id': 'fake',
|
||||
'is_domain': False}
|
||||
self.assertRaises(exception.ProjectNotFound,
|
||||
self.resource_api.create_project,
|
||||
project['id'],
|
||||
|
@ -2886,7 +2915,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
|
||||
domain = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
|
@ -2897,7 +2927,8 @@ class IdentityTests(object):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.create_project,
|
||||
|
@ -2948,13 +2979,15 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': False,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project1['id'], project1)
|
||||
|
||||
project2 = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': project1['id']}
|
||||
'parent_id': project1['id'],
|
||||
'is_domain': False}
|
||||
|
||||
# It's not possible to create a project under a disabled one in the
|
||||
# hierarchy
|
||||
|
@ -3020,7 +3053,8 @@ class IdentityTests(object):
|
|||
'id': project_id,
|
||||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'parent_id': leaf_project['id']}
|
||||
'parent_id': leaf_project['id'],
|
||||
'is_domain': False}
|
||||
self.assertRaises(exception.ForbiddenAction,
|
||||
self.resource_api.create_project,
|
||||
project_id,
|
||||
|
@ -3032,7 +3066,8 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Add a description attribute.
|
||||
|
@ -3048,7 +3083,8 @@ class IdentityTests(object):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Add a description attribute.
|
||||
|
@ -3726,16 +3762,16 @@ class IdentityTests(object):
|
|||
domain2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex}
|
||||
self.resource_api.create_domain(domain2['id'], domain2)
|
||||
project1 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project1 = self.resource_api.create_project(project1['id'], project1)
|
||||
project2 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project2 = self.resource_api.create_project(project2['id'], project2)
|
||||
project3 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain1['id']}
|
||||
'domain_id': domain1['id'], 'is_domain': False}
|
||||
project3 = self.resource_api.create_project(project3['id'], project3)
|
||||
project4 = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': domain2['id']}
|
||||
'domain_id': domain2['id'], 'is_domain': False}
|
||||
project4 = self.resource_api.create_project(project4['id'], project4)
|
||||
group_list = []
|
||||
role_list = []
|
||||
|
@ -5538,14 +5574,16 @@ class InheritanceTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
leaf_project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(leaf_project['id'], leaf_project)
|
||||
|
||||
user = {'name': uuid.uuid4().hex, 'password': uuid.uuid4().hex,
|
||||
|
@ -5659,14 +5697,16 @@ class InheritanceTests(object):
|
|||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(root_project['id'], root_project)
|
||||
leaf_project = {'id': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': DEFAULT_DOMAIN_ID,
|
||||
'enabled': True,
|
||||
'name': uuid.uuid4().hex,
|
||||
'parent_id': root_project['id']}
|
||||
'parent_id': root_project['id'],
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(leaf_project['id'], leaf_project)
|
||||
|
||||
user = {'name': uuid.uuid4().hex, 'password': uuid.uuid4().hex,
|
||||
|
|
|
@ -1532,7 +1532,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'domain_id': CONF.identity.default_domain_id,
|
||||
'description': uuid.uuid4().hex,
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
||||
|
@ -1610,7 +1611,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project1['id'], project1)
|
||||
|
||||
# Creating project2 under project1. LDAP will not allow
|
||||
|
@ -1620,7 +1622,8 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': project1['id']}
|
||||
'parent_id': project1['id'],
|
||||
'is_domain': False}
|
||||
|
||||
self.assertRaises(exception.InvalidParentProject,
|
||||
self.resource_api.create_project,
|
||||
|
@ -1634,6 +1637,37 @@ class LDAPIdentity(BaseLDAPIdentity, tests.TestCase):
|
|||
# Returning projects to be used across the tests
|
||||
return [project1, project2]
|
||||
|
||||
def test_create_is_domain_project(self):
|
||||
domain = self._get_domain_fixture()
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': True}
|
||||
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.create_project,
|
||||
project['id'], project)
|
||||
|
||||
def test_update_is_domain_field(self):
|
||||
domain = self._get_domain_fixture()
|
||||
project = {'id': uuid.uuid4().hex,
|
||||
'name': uuid.uuid4().hex,
|
||||
'description': '',
|
||||
'domain_id': domain['id'],
|
||||
'enabled': True,
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
|
||||
# Try to update the is_domain field to True
|
||||
project['is_domain'] = True
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.resource_api.update_project,
|
||||
project['id'], project)
|
||||
|
||||
def test_check_leaf_projects(self):
|
||||
projects = self._assert_create_hierarchy_not_allowed()
|
||||
for project in projects:
|
||||
|
@ -1966,7 +2000,8 @@ class LDAPIdentityEnabledEmulation(LDAPIdentity):
|
|||
'name': uuid.uuid4().hex,
|
||||
'domain_id': CONF.identity.default_domain_id,
|
||||
'description': uuid.uuid4().hex,
|
||||
'parent_id': None}
|
||||
'parent_id': None,
|
||||
'is_domain': False}
|
||||
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
@ -2603,7 +2638,8 @@ class MultiLDAPandSQLIdentity(BaseLDAPIdentity, tests.SQLDriverOverrides,
|
|||
'domain_id': domain['id'],
|
||||
'description': uuid.uuid4().hex,
|
||||
'parent_id': None,
|
||||
'enabled': True}
|
||||
'enabled': True,
|
||||
'is_domain': False}
|
||||
self.resource_api.create_domain(domain['id'], domain)
|
||||
self.resource_api.create_project(project['id'], project)
|
||||
project_ref = self.resource_api.get_project(project['id'])
|
||||
|
|
|
@ -156,7 +156,8 @@ class SqlModels(SqlTests):
|
|||
('domain_id', sql.String, 64),
|
||||
('enabled', sql.Boolean, None),
|
||||
('extra', sql.JsonBlob, None),
|
||||
('parent_id', sql.String, 64))
|
||||
('parent_id', sql.String, 64),
|
||||
('is_domain', sql.Boolean, False))
|
||||
self.assertExpectedSchema('project', cols)
|
||||
|
||||
def test_role_assignment_model(self):
|
||||
|
|
|
@ -629,6 +629,13 @@ class SqlUpgradeTests(SqlMigrateBase):
|
|||
self.assertFalse(self._does_index_exist('assignment',
|
||||
'assignment_role_id_fkey'))
|
||||
|
||||
def test_project_is_domain_upgrade(self):
|
||||
self.upgrade(74)
|
||||
self.assertTableColumns('project',
|
||||
['id', 'name', 'extra', 'description',
|
||||
'enabled', 'domain_id', 'parent_id',
|
||||
'is_domain'])
|
||||
|
||||
def populate_user_table(self, with_pass_enab=False,
|
||||
with_pass_enab_domain=False):
|
||||
# Populate the appropriate fields in the user
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
import uuid
|
||||
|
||||
from keystone.assignment import controllers as assignment_controllers
|
||||
from keystone import exception
|
||||
from keystone.resource import controllers as resource_controllers
|
||||
from keystone.tests import unit as tests
|
||||
from keystone.tests.unit import default_fixtures
|
||||
|
@ -93,4 +94,50 @@ class TenantTestCase(tests.TestCase):
|
|||
tenant_copy = tenant.copy()
|
||||
tenant_copy.pop('domain_id')
|
||||
tenant_copy.pop('parent_id')
|
||||
tenant_copy.pop('is_domain')
|
||||
self.assertIn(tenant_copy, refs['tenants'])
|
||||
|
||||
def _create_is_domain_project(self):
|
||||
project = {'id': uuid.uuid4().hex, 'name': uuid.uuid4().hex,
|
||||
'domain_id': 'default', 'is_domain': True}
|
||||
project_ref = self.resource_api.create_project(project['id'], project)
|
||||
return self.tenant_controller.v3_to_v2_project(project_ref)
|
||||
|
||||
def test_update_is_domain_project_not_found(self):
|
||||
"""Test that update is_domain project is not allowed in v2."""
|
||||
project = self._create_is_domain_project()
|
||||
|
||||
project['name'] = uuid.uuid4().hex
|
||||
self.assertRaises(
|
||||
exception.ProjectNotFound,
|
||||
self.tenant_controller.update_project,
|
||||
_ADMIN_CONTEXT,
|
||||
project['id'],
|
||||
project
|
||||
)
|
||||
|
||||
def test_delete_is_domain_project_not_found(self):
|
||||
"""Test that delete is_domain project is not allowed in v2."""
|
||||
project = self._create_is_domain_project()
|
||||
|
||||
self.assertRaises(
|
||||
exception.ProjectNotFound,
|
||||
self.tenant_controller.delete_project,
|
||||
_ADMIN_CONTEXT,
|
||||
project['id']
|
||||
)
|
||||
|
||||
def test_list_is_domain_project_not_found(self):
|
||||
"""Test v2 get_all_projects having projects that act as a domain.
|
||||
|
||||
In v2 no project with the is_domain flag enabled should be
|
||||
returned.
|
||||
"""
|
||||
project1 = self._create_is_domain_project()
|
||||
project2 = self._create_is_domain_project()
|
||||
|
||||
refs = self.tenant_controller.get_all_projects(_ADMIN_CONTEXT)
|
||||
projects = refs.get('tenants')
|
||||
|
||||
self.assertNotIn(project1, projects)
|
||||
self.assertNotIn(project2, projects)
|
||||
|
|
|
@ -299,10 +299,11 @@ class RestfulTestCase(tests.SQLDriverOverrides, rest.RestfulTestCase,
|
|||
ref = self.new_ref()
|
||||
return ref
|
||||
|
||||
def new_project_ref(self, domain_id, parent_id=None):
|
||||
def new_project_ref(self, domain_id=None, parent_id=None, is_domain=False):
|
||||
ref = self.new_ref()
|
||||
ref['domain_id'] = domain_id
|
||||
ref['parent_id'] = parent_id
|
||||
ref['is_domain'] = is_domain
|
||||
return ref
|
||||
|
||||
def new_user_ref(self, domain_id, project_id=None):
|
||||
|
|
|
@ -527,6 +527,18 @@ class AssignmentTestCase(test_v3.RestfulTestCase,
|
|||
ref = self.new_project_ref(domain_id=uuid.uuid4().hex)
|
||||
self.post('/projects', body={'project': ref}, expected_status=400)
|
||||
|
||||
def test_create_project_is_domain_not_allowed(self):
|
||||
"""Call ``POST /projects``.
|
||||
|
||||
Setting is_domain=True is not supported yet and should raise
|
||||
NotImplemented.
|
||||
|
||||
"""
|
||||
ref = self.new_project_ref(domain_id=self.domain_id, is_domain=True)
|
||||
self.post('/projects',
|
||||
body={'project': ref},
|
||||
expected_status=501)
|
||||
|
||||
def _create_projects_hierarchy(self, hierarchy_size=1):
|
||||
"""Creates a single-branched project hierarchy with the specified size.
|
||||
|
||||
|
@ -942,6 +954,22 @@ class AssignmentTestCase(test_v3.RestfulTestCase,
|
|||
body={'project': leaf_project},
|
||||
expected_status=403)
|
||||
|
||||
def test_update_project_is_domain_not_allowed(self):
|
||||
"""Call ``PATCH /projects/{project_id}`` with is_domain.
|
||||
|
||||
The is_domain flag is immutable.
|
||||
"""
|
||||
project = self.new_project_ref(domain_id=self.domain['id'])
|
||||
resp = self.post('/projects',
|
||||
body={'project': project})
|
||||
self.assertFalse(resp.result['project']['is_domain'])
|
||||
|
||||
project['is_domain'] = True
|
||||
self.patch('/projects/%(project_id)s' % {
|
||||
'project_id': resp.result['project']['id']},
|
||||
body={'project': project},
|
||||
expected_status=400)
|
||||
|
||||
def test_disable_leaf_project(self):
|
||||
"""Call ``PATCH /projects/{project_id}``."""
|
||||
projects = self._create_projects_hierarchy()
|
||||
|
|
Loading…
Reference in New Issue