Remove Identity and Assignment controller interdependancies

The DomainV3 controller explicitly instantiated the UserV3, GroupV3
and ProjectV3 controllers to call some delete methods. This makes it
very difficult to isolate the controllers from each other and
incorrectly implemented some logic that should exist in the
credential_api.

The code for deleting credentials by project and by user is now
implemented on the credential_api. This allows the controllers
to directly ask the credential_api to perform the deletes (and
in the case of the SQL backend, perform a more efficient delete
process).

The DomainV3 controller properly uses the identity_api to cleanup
users and groups when deletion occurs. Likewise, DomainV3 controller
now properly calls the assignment_api to perform project deletions.

These changes have been done to aid in clarifying explicit uses of
the per-domain-identity backend logic.

bp: assignment-controller-first-class
related-bug: #1218094
Change-Id: Ie8ed8bce556fe2f41cc7db61f04eefaf14f836f1
This commit is contained in:
Morgan Fainberg 2013-12-13 21:52:01 -08:00
parent a7d398c5de
commit dcefec5e0f
4 changed files with 101 additions and 44 deletions

View File

@ -89,3 +89,19 @@ class Credential(sql.Base, credential.Driver):
ref = self._get_credential(session, credential_id)
session.delete(ref)
session.flush()
def delete_credentials_for_project(self, project_id):
session = self.get_session()
with session.begin():
query = session.query(CredentialModel)
query = query.filter_by(project_id=project_id)
query.delete()
def delete_credentials_for_user(self, user_id):
session = self.get_session()
with session.begin():
query = session.query(CredentialModel)
query = query.filter_by(user_id=user_id)
query.delete()

View File

@ -95,3 +95,28 @@ class Driver(object):
"""
raise exception.NotImplemented()
@abc.abstractmethod
def delete_credentials_for_project(self, project_id):
"""Deletes all existing credentials for an existing project."""
for cred in self.list_credentials():
if cred['project_id'] == project_id:
try:
self.credential_api.delete_credential(cred['id'])
except exception.CredentialNotFound:
# NOTE(morganfainberg): If the credential doesn't exist
# it doesn't matter, it is meant to be deleted. Continue
# on and delete the rest.
pass
@abc.abstractmethod
def delete_credentials_for_user(self, user_id):
for cred in self.list_credentials():
if cred['user_id'] == user_id:
try:
self.credential_api.delete_credential(cred['id'])
except exception.CredentialNotFound:
# NOTE(morganfainberg): If the credential doesn't exist
# it doesn't matter, it is meant to be deleted. Continue
# on and delete the rest.
pass

View File

@ -508,7 +508,7 @@ class Role(controller.V2Controller):
self._delete_tokens_for_user(user_id)
@dependency.requires('assignment_api', 'identity_api')
@dependency.requires('assignment_api', 'credential_api', 'identity_api')
class DomainV3(controller.V3Controller):
collection_name = 'domains'
member_name = 'domain'
@ -606,23 +606,49 @@ class DomainV3(controller.V3Controller):
proj_refs = self.assignment_api.list_projects()
proj_ids = [r['id'] for r in proj_refs if r['domain_id'] == domain_id]
# First delete the projects themselves
project_cntl = ProjectV3()
for project in proj_ids:
project_cntl._delete_project(context, project)
# Get the list of groups owned by this domain and delete them
group_refs = self.identity_api.list_groups()
group_ids = ([r['id'] for r in group_refs
if r['domain_id'] == domain_id])
group_cntl = GroupV3()
for group in group_ids:
group_cntl._delete_group(context, group)
# First delete the projects themselves
for project_id in proj_ids:
# NOTE(morganfainberg): Ensure we cleanup the credentials for the
# project and any outstanding tokens.
self.credential_api.delete_credentials_for_project(project_id)
try:
self._delete_tokens_for_project(project_id)
self.assignment_api.delete_project(project_id)
except exception.ProjectNotFound:
# NOTE(morganfainberg): We should still perform the cleanups
# if the project can't be found for sanity-sake.
pass
for group_id in group_ids:
# NOTE(morganfainberg): Cleanup any existing groups.
try:
self.identity_api.delete_group(group_id,
domain_scope=r['domain_id'])
except exception.GroupNotFound:
# NOTE(morganfainberg): In the case that a race has occurred
# and the group no longer exists, continue on and delete the
# rest of the groups.
pass
# And finally, delete the users themselves
user_cntl = UserV3()
for user in user_ids:
user_cntl._delete_user(context, user)
for user_id in user_ids:
# Delete any credentials that reference this user
self.credential_api.delete_credentials_for_user(user_id)
# Make sure any tokens are marked as deleted
try:
self._delete_tokens_for_user(user_id)
self.identity_api.delete_user(user_id,
domain_scope=r['domain_id'])
except exception.UserNotFound:
# NOTE(morganfainberg): In the case that a race has occurred
# and the user no longer exists, continue on and delete the
# rest of the users.
pass
@controller.protected()
def delete_domain(self, context, domain_id):
@ -690,23 +716,11 @@ class ProjectV3(controller.V3Controller):
ref = self.assignment_api.update_project(project_id, project)
return ProjectV3.wrap_member(context, ref)
def _delete_project(self, context, project_id):
# Delete any credentials that reference this project
for cred in self.credential_api.list_credentials():
if cred['project_id'] == project_id:
self.credential_api.delete_credential(cred['id'])
# Delete all tokens belonging to the users for that project
self._delete_tokens_for_project(project_id)
# Finally delete the project itself - the backend is
# responsible for deleting any role assignments related
# to this project
return self.assignment_api.delete_project(project_id)
@controller.protected()
def delete_project(self, context, project_id):
return self._delete_project(context, project_id)
self.credential_api.delete_credentials_for_project(project_id)
self._delete_tokens_for_project(project_id)
return self.assignment_api.delete_project(project_id)
@dependency.requires('identity_api', 'credential_api')
@ -792,24 +806,17 @@ class UserV3(controller.V3Controller):
domain_scope=self._get_domain_id_for_request(context))
self._delete_tokens_for_user(user_id)
def _delete_user(self, context, user_id):
@controller.protected()
def delete_user(self, context, user_id):
# Delete any credentials that reference this user
for cred in self.credential_api.list_credentials():
if cred['user_id'] == user_id:
self.credential_api.delete_credential(cred['id'])
self.credential_api.delete_credentials_for_user(user_id)
# Make sure any tokens are marked as deleted
domain_id = self._get_domain_id_for_request(context)
self._delete_tokens_for_user(user_id)
# Finally delete the user itself - the backend is
# responsible for deleting any role assignments related
# to this user
return self.identity_api.delete_user(
user_id, domain_scope=domain_id)
@controller.protected()
def delete_user(self, context, user_id):
return self._delete_user(context, user_id)
return self.identity_api.delete_user(user_id, domain_scope=domain_id)
@controller.protected()
def change_password(self, context, user_id, user):
@ -882,13 +889,13 @@ class GroupV3(controller.V3Controller):
domain_scope=self._get_domain_id_for_request(context))
return GroupV3.wrap_member(context, ref)
def _delete_group(self, context, group_id):
@controller.protected()
def delete_group(self, context, group_id):
# As well as deleting the group, we need to invalidate
# any tokens for the users who are members of the group.
# We get the list of users before we attempt the group
# deletion, so that we can remove these tokens after we know
# the group deletion succeeded.
domain_id = self._get_domain_id_for_request(context)
user_refs = self.identity_api.list_users_in_group(
group_id, domain_scope=domain_id)
@ -896,10 +903,6 @@ class GroupV3(controller.V3Controller):
for user in user_refs:
self._delete_tokens_for_user(user['id'])
@controller.protected()
def delete_group(self, context, group_id):
return self._delete_group(context, group_id)
@dependency.requires('assignment_api', 'identity_api')
class RoleV3(controller.V3Controller):

View File

@ -18,6 +18,7 @@ import hashlib
import json
import uuid
from keystone import exception
from keystone.tests import test_v3
@ -36,6 +37,18 @@ class CredentialTestCase(test_v3.RestfulTestCase):
self.credential_id,
self.credential)
def test_credential_api_delete_credentials_for_project(self):
self.credential_api.delete_credentials_for_project(self.project_id)
self.assertRaises(exception.CredentialNotFound,
self.credential_api.get_credential,
credential_id=self.credential_id)
def test_credential_api_delete_credentials_for_user(self):
self.credential_api.delete_credentials_for_user(self.user_id)
self.assertRaises(exception.CredentialNotFound,
self.credential_api.get_credential,
credential_id=self.credential_id)
def test_list_credentials(self):
"""Call ``GET /credentials``."""
r = self.get('/credentials')