diff options
author | Morgan Fainberg <m@metacloud.com> | 2013-06-03 16:50:23 -0700 |
---|---|---|
committer | Morgan Fainberg <m@metacloud.com> | 2013-06-10 00:23:21 -0700 |
commit | c639c53176473aa9196b1e5a2b65991477b02cb3 (patch) | |
tree | 816cef6369e21fe50699d0551e904720c5255b55 /tests/test_backend_memcache.py | |
parent | e5983f323950297d389dce8712a4616142bb37e6 (diff) | |
download | keystone-c639c53176473aa9196b1e5a2b65991477b02cb3.tar.gz keystone-c639c53176473aa9196b1e5a2b65991477b02cb3.tar.xz keystone-c639c53176473aa9196b1e5a2b65991477b02cb3.zip |
Fix token purging for memcache for user token index.
When issuing a new token, purge all expired tokens from the user's
token index list.
New Options:
* max_compare_and_set_retry:
The number of retries that will be attempted when performing
an update of the user_record or the revocation-list record.
This is relevant due to the use of CAS (compare and set)
function of the memcache client. This allows for multiple
keystone processes/wsgi/etc to run without worry of race
conditions clobbering the lists.
DocImpact - New Options.
Change-Id: I9441105b1e46982b0354bccbf8297daaaa1904b2
Fixes: bug #1171985
Diffstat (limited to 'tests/test_backend_memcache.py')
-rw-r--r-- | tests/test_backend_memcache.py | 84 |
1 files changed, 82 insertions, 2 deletions
diff --git a/tests/test_backend_memcache.py b/tests/test_backend_memcache.py index f5999002..5391d7f9 100644 --- a/tests/test_backend_memcache.py +++ b/tests/test_backend_memcache.py @@ -14,14 +14,18 @@ # License for the specific language governing permissions and limitations # under the License. +import copy +import datetime import uuid import memcache from keystone.common import utils from keystone import exception +from keystone.openstack.common import jsonutils from keystone.openstack.common import timeutils from keystone import test +from keystone import token from keystone.token.backends import memcache as token_memcache import test_backend @@ -33,6 +37,7 @@ class MemcacheClient(object): def __init__(self, *args, **kwargs): """Ignores the passed in args.""" self.cache = {} + self.reject_cas = False def add(self, key, value): if self.get(key): @@ -50,20 +55,50 @@ class MemcacheClient(object): if not isinstance(key, str): raise memcache.Client.MemcachedStringEncodingError() + def gets(self, key): + #Call self.get() since we don't really do 'cas' here. + return self.get(key) + def get(self, key): """Retrieves the value for a key or None.""" self.check_key(key) obj = self.cache.get(key) now = utils.unixtime(timeutils.utcnow()) if obj and (obj[1] == 0 or obj[1] > now): - return obj[0] + # NOTE(morganfainberg): This behaves more like memcache + # actually does and prevents modification of the passed in + # reference from affecting the cached back-end data. This makes + # tests a little easier to write. + # + # The back-end store should only change with an explicit + # set/delete/append/etc + data_copy = copy.deepcopy(obj[0]) + return data_copy def set(self, key, value, time=0): """Sets the value for a key.""" self.check_key(key) - self.cache[key] = (value, time) + # NOTE(morganfainberg): This behaves more like memcache + # actually does and prevents modification of the passed in + # reference from affecting the cached back-end data. This makes + # tests a little easier to write. + # + # The back-end store should only change with an explicit + # set/delete/append/etc + data_copy = copy.deepcopy(value) + self.cache[key] = (data_copy, time) return True + def cas(self, key, value, time=0, min_compress_len=0): + # Call self.set() since we don't really do 'cas' here. + if self.reject_cas: + return False + return self.set(key, value, time=time) + + def reset_cas(self): + #This is a stub for the memcache client reset_cas function. + pass + def delete(self, key): self.check_key(key) try: @@ -101,3 +136,48 @@ class MemcacheToken(test.TestCase, test_backend.TokenTests): def test_flush_expired_token(self): with self.assertRaises(exception.NotImplemented): self.token_api.flush_expired_tokens() + + def test_cleanup_user_index_on_create(self): + valid_token_id = uuid.uuid4().hex + second_valid_token_id = uuid.uuid4().hex + expired_token_id = uuid.uuid4().hex + user_id = unicode(uuid.uuid4().hex) + + expire_delta = datetime.timedelta(seconds=86400) + + valid_data = {'id': valid_token_id, 'a': 'b', + 'user': {'id': user_id}} + second_valid_data = {'id': second_valid_token_id, 'a': 'b', + 'user': {'id': user_id}} + expired_data = {'id': expired_token_id, 'a': 'b', + 'user': {'id': user_id}} + self.token_api.create_token(valid_token_id, valid_data) + self.token_api.create_token(expired_token_id, expired_data) + # NOTE(morganfainberg): Directly access the data cache since we need to + # get expired tokens as well as valid tokens. token_api.list_tokens() + # will not return any expired tokens in the list. + user_key = self.token_api._prefix_user_id(user_id) + user_record = self.token_api.client.get(user_key) + user_token_list = jsonutils.loads('[%s]' % user_record) + self.assertEquals(len(user_token_list), 2) + expired_token_ptk = self.token_api._prefix_token_id( + token.unique_id(expired_token_id)) + expired_token = self.token_api.client.get(expired_token_ptk) + expired_token['expires'] = (timeutils.utcnow() - expire_delta) + self.token_api.client.set(expired_token_ptk, expired_token) + + self.token_api.create_token(second_valid_token_id, second_valid_data) + user_record = self.token_api.client.get(user_key) + user_token_list = jsonutils.loads('[%s]' % user_record) + self.assertEquals(len(user_token_list), 2) + + def test_cas_failure(self): + self.token_api.client.reject_cas = True + token_id = uuid.uuid4().hex + user_id = unicode(uuid.uuid4().hex) + user_key = self.token_api._prefix_user_id(user_id) + token_data = jsonutils.dumps(token_id) + self.assertRaises( + exception.UnexpectedError, + self.token_api._update_user_list_with_cas, + user_key, token_data) |