Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Improve the performance of tokens deletion for user
Provide new delete the tokens api 'delete_tokens' to support
delete all the tokens for user in one session in the sql backend. For
the kvs and memcache, I also provide the corresponding implementation.

Fix bug 1178063

Change-Id: I986a583e5900ea04e26cbdb7c49638a33818bca7
  • Loading branch information
gengjh committed May 30, 2013
1 parent 6d33805 commit d6cfe4f
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 22 deletions.
19 changes: 5 additions & 14 deletions keystone/common/controller.py
Expand Up @@ -153,23 +153,14 @@ class V2Controller(wsgi.Application):
"""Base controller class for Identity API v2."""

def _delete_tokens_for_trust(self, context, user_id, trust_id):
try:
token_list = self.token_api.list_tokens(context, user_id,
trust_id=trust_id)
for token in token_list:
self.token_api.delete_token(context, token)
except exception.NotFound:
pass
self.token_api.delete_tokens(context, user_id,
trust_id=trust_id)

def _delete_tokens_for_user(self, context, user_id, project_id=None):
#First delete tokens that could get other tokens.
for token_id in self.token_api.list_tokens(context,
user_id,
tenant_id=project_id):
try:
self.token_api.delete_token(context, token_id)
except exception.NotFound:
pass
self.token_api.delete_tokens(context,
user_id,
tenant_id=project_id)

#delete tokens generated from trusts
for trust in self.trust_api.list_trusts_for_trustee(context, user_id):
Expand Down
41 changes: 35 additions & 6 deletions keystone/token/backends/sql.py
Expand Up @@ -77,6 +77,40 @@ def delete_token(self, token_id):
token_ref.valid = False
session.flush()

def delete_tokens(self, user_id, tenant_id=None, trust_id=None):
"""Deletes all tokens in one session
The user_id will be ignored if the trust_id is specified. user_id
will always be specified.
If using a trust, the token's user_id is set to the trustee's user ID
or the trustor's user ID, so will use trust_id to query the tokens.
"""
session = self.get_session()
with session.begin():
now = timeutils.utcnow()
query = session.query(TokenModel)
query = query.filter_by(valid=True)
query = query.filter(TokenModel.expires > now)
if trust_id:
query = query.filter(TokenModel.trust_id == trust_id)
else:
query = query.filter(TokenModel.user_id == user_id)

for token_ref in query.all():
if tenant_id:
token_ref_dict = token_ref.to_dict()
if not self._tenant_matches(tenant_id, token_ref_dict):
continue
token_ref.valid = False

session.flush()

def _tenant_matches(self, tenant_id, token_ref_dict):
return ((tenant_id is None) or
(token_ref_dict.get('tenant') and
token_ref_dict['tenant'].get('id') == tenant_id))

def _list_tokens_for_trust(self, trust_id):
session = self.get_session()
tokens = []
Expand All @@ -92,11 +126,6 @@ def _list_tokens_for_trust(self, trust_id):
return tokens

def _list_tokens_for_user(self, user_id, tenant_id=None):
def tenant_matches(tenant_id, token_ref_dict):
return ((tenant_id is None) or
(token_ref_dict.get('tenant') and
token_ref_dict['tenant'].get('id') == tenant_id))

session = self.get_session()
tokens = []
now = timeutils.utcnow()
Expand All @@ -107,7 +136,7 @@ def tenant_matches(tenant_id, token_ref_dict):
token_references = query.filter_by(valid=True)
for token_ref in token_references:
token_ref_dict = token_ref.to_dict()
if tenant_matches(tenant_id, token_ref_dict):
if self._tenant_matches(tenant_id, token_ref_dict):
tokens.append(token_ref['id'])
return tokens

Expand Down
26 changes: 26 additions & 0 deletions keystone/token/core.py
Expand Up @@ -166,6 +166,32 @@ def delete_token(self, token_id):
"""
raise exception.NotImplemented()

def delete_tokens(self, user_id, tenant_id=None, trust_id=None):
"""Deletes tokens by user.
If the tenant_id is not None, only delete the tokens by user id under
the specified tenant.
If the trust_id is not None, it will be used to query tokens and the
user_id will be ignored.
:param user_id: identity of user
:type token_id: string
:param tenant_id: identity of the tenant
:type tenant_id: string
:param trust_id: identified of the trust
:type trust_id: string
:returns: None.
:raises: keystone.exception.TokenNotFound
"""
token_list = self.list_tokens(user_id,
tenant_id=tenant_id,
trust_id=trust_id)
for token in token_list:
try:
self.delete_token(token)
except exception.NotFound:
pass

def list_tokens(self, user_id, tenant_id=None, trust_id=None):
"""Returns a list of current token_id's for a user
Expand Down
45 changes: 43 additions & 2 deletions tests/test_backend.py
Expand Up @@ -2052,10 +2052,11 @@ def test_token_crud(self):
self.assertRaises(exception.TokenNotFound,
self.token_api.delete_token, token_id)

def create_token_sample_data(self, tenant_id=None, trust_id=None):
def create_token_sample_data(self, tenant_id=None, trust_id=None,
user_id="testuserid"):
token_id = self._create_token_id()
data = {'id': token_id, 'a': 'b',
'user': {'id': 'testuserid'}}
'user': {'id': user_id}}
if tenant_id is not None:
data['tenant'] = {'id': tenant_id, 'name': tenant_id}
if tenant_id is NULL_OBJECT:
Expand All @@ -2065,6 +2066,46 @@ def create_token_sample_data(self, tenant_id=None, trust_id=None):
new_token = self.token_api.create_token(token_id, data)
return new_token['id']

def test_delete_tokens(self):
tokens = self.token_api.list_tokens('testuserid')
self.assertEquals(len(tokens), 0)
token_id1 = self.create_token_sample_data('testtenantid')
token_id2 = self.create_token_sample_data('testtenantid')
token_id3 = self.create_token_sample_data(tenant_id='testtenantid',
user_id="testuserid1")
tokens = self.token_api.list_tokens('testuserid')
self.assertEquals(len(tokens), 2)
self.assertIn(token_id2, tokens)
self.assertIn(token_id1, tokens)
self.token_api.delete_tokens(user_id='testuserid',
tenant_id='testtenantid')
tokens = self.token_api.list_tokens('testuserid')
self.assertEquals(len(tokens), 0)
self.assertRaises(exception.TokenNotFound,
self.token_api.get_token, token_id1)
self.assertRaises(exception.TokenNotFound,
self.token_api.get_token, token_id2)

self.token_api.get_token(token_id3)

def test_delete_tokens_trust(self):
tokens = self.token_api.list_tokens(user_id='testuserid')
self.assertEquals(len(tokens), 0)
token_id1 = self.create_token_sample_data(tenant_id='testtenantid',
trust_id='testtrustid')
token_id2 = self.create_token_sample_data(tenant_id='testtenantid',
user_id="testuserid1",
trust_id="testtrustid1")
tokens = self.token_api.list_tokens('testuserid')
self.assertEquals(len(tokens), 1)
self.assertIn(token_id1, tokens)
self.token_api.delete_tokens(user_id='testuserid',
tenant_id='testtenantid',
trust_id='testtrustid')
self.assertRaises(exception.TokenNotFound,
self.token_api.get_token, token_id1)
self.token_api.get_token(token_id2)

def test_token_list(self):
tokens = self.token_api.list_tokens('testuserid')
self.assertEquals(len(tokens), 0)
Expand Down

0 comments on commit d6cfe4f

Please sign in to comment.