From d6cfe4f2e2c7c1b3eb23bf9f864779940a645555 Mon Sep 17 00:00:00 2001 From: gengjh Date: Sat, 11 May 2013 17:20:10 +0800 Subject: [PATCH] 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 --- keystone/common/controller.py | 19 ++++---------- keystone/token/backends/sql.py | 41 ++++++++++++++++++++++++++----- keystone/token/core.py | 26 ++++++++++++++++++++ tests/test_backend.py | 45 ++++++++++++++++++++++++++++++++-- 4 files changed, 109 insertions(+), 22 deletions(-) diff --git a/keystone/common/controller.py b/keystone/common/controller.py index 3452f85e41..4a7a9a673d 100644 --- a/keystone/common/controller.py +++ b/keystone/common/controller.py @@ -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): diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py index df1bc95807..57dbf410ff 100644 --- a/keystone/token/backends/sql.py +++ b/keystone/token/backends/sql.py @@ -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 = [] @@ -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() @@ -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 diff --git a/keystone/token/core.py b/keystone/token/core.py index 5a47d0278b..183958f9a5 100644 --- a/keystone/token/core.py +++ b/keystone/token/core.py @@ -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 diff --git a/tests/test_backend.py b/tests/test_backend.py index 717535c712..8f87e4e195 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -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: @@ -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)