summaryrefslogtreecommitdiffstats
path: root/tests/test_backend_memcache.py
diff options
context:
space:
mode:
authorMorgan Fainberg <m@metacloud.com>2013-06-03 16:50:23 -0700
committerMorgan Fainberg <m@metacloud.com>2013-06-10 00:23:21 -0700
commitc639c53176473aa9196b1e5a2b65991477b02cb3 (patch)
tree816cef6369e21fe50699d0551e904720c5255b55 /tests/test_backend_memcache.py
parente5983f323950297d389dce8712a4616142bb37e6 (diff)
downloadkeystone-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.py84
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)