From c32056287704e4ce6e616bbbc37b7be98c44b218 Mon Sep 17 00:00:00 2001 From: Ryan Wilson <wryan@vmware.com> Date: Tue, 15 Oct 2013 06:09:43 -0700 Subject: [PATCH] Adds locking to completion caches Previously, completion caches were not thread-safe and multiple threads could not make calls to python-novaclient simultaneously. With locking, the completion caches are now thread-safe. Closes-Bug: #1213958 Change-Id: I201c2ed0f1eb09b1c2a74de96119cbae9ed6f4b0 --- novaclient/base.py | 72 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/novaclient/base.py b/novaclient/base.py index b506286a9..45bea18d8 100644 --- a/novaclient/base.py +++ b/novaclient/base.py @@ -25,6 +25,7 @@ import contextlib import hashlib import inspect import os +import threading import six @@ -50,6 +51,7 @@ class Manager(utils.HookableMixin): etc.) and provide CRUD operations for them. """ resource_class = None + cache_lock = threading.RLock() def __init__(self, api): self.api = api @@ -90,46 +92,50 @@ class Manager(utils.HookableMixin): Delete is not handled because listings are assumed to be performed often enough to keep the cache reasonably up-to-date. """ - base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR', - default="~/.novaclient") + # NOTE(wryan): This lock protects read and write access to the + # completion caches + with self.cache_lock: + base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR', + default="~/.novaclient") - # NOTE(sirp): Keep separate UUID caches for each username + endpoint - # pair - username = utils.env('OS_USERNAME', 'NOVA_USERNAME') - url = utils.env('OS_URL', 'NOVA_URL') - uniqifier = hashlib.md5(username.encode('utf-8') + - url.encode('utf-8')).hexdigest() + # NOTE(sirp): Keep separate UUID caches for each username + + # endpoint pair + username = utils.env('OS_USERNAME', 'NOVA_USERNAME') + url = utils.env('OS_URL', 'NOVA_URL') + uniqifier = hashlib.md5(username.encode('utf-8') + + url.encode('utf-8')).hexdigest() - cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) + cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) - try: - os.makedirs(cache_dir, 0o755) - except OSError: - # NOTE(kiall): This is typicaly either permission denied while - # attempting to create the directory, or the directory - # already exists. Either way, don't fail. - pass + try: + os.makedirs(cache_dir, 0o755) + except OSError: + # NOTE(kiall): This is typicaly either permission denied while + # attempting to create the directory, or the + # directory already exists. Either way, don't + # fail. + pass - resource = obj_class.__name__.lower() - filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) - path = os.path.join(cache_dir, filename) + resource = obj_class.__name__.lower() + filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) + path = os.path.join(cache_dir, filename) - cache_attr = "_%s_cache" % cache_type + cache_attr = "_%s_cache" % cache_type - try: - setattr(self, cache_attr, open(path, mode)) - except IOError: - # NOTE(kiall): This is typicaly a permission denied while - # attempting to write the cache file. - pass + try: + setattr(self, cache_attr, open(path, mode)) + except IOError: + # NOTE(kiall): This is typicaly a permission denied while + # attempting to write the cache file. + pass - try: - yield - finally: - cache = getattr(self, cache_attr, None) - if cache: - cache.close() - delattr(self, cache_attr) + try: + yield + finally: + cache = getattr(self, cache_attr, None) + if cache: + cache.close() + delattr(self, cache_attr) def write_to_completion_cache(self, cache_type, val): cache = getattr(self, "_%s_cache" % cache_type, None)