summaryrefslogtreecommitdiff
path: root/novaclient/base.py
diff options
context:
space:
mode:
authorAndrew Laski <andrew.laski@rackspace.com>2015-02-19 18:24:51 -0500
committermelanie witt <melwitt@yahoo-inc.com>2015-02-20 01:35:57 +0000
commitac6636a54d72ba76d0adca76e07d1d26d9ea35c3 (patch)
treececf9210fb200c52be19e05d03b0138fd06cec66 /novaclient/base.py
parentbfd029c8ef500af705159a5f3d5fa74e39c43096 (diff)
downloadpython-novaclient-ac6636a54d72ba76d0adca76e07d1d26d9ea35c3.tar.gz
Revert "Overhaul bash-completion to support non-UUID based IDs"
This reverts commit 4c8cefb98a425382204df2f38f24e6b5b71520dd. The cache completion was causing a bug where 'nova volume-attach' would then try to query Nova for volume information using a URL that was not valid. This caused it to appear that the attach had failed. Additionally the idea of making extra API queries for a bash completion cache should be thought through more. Conflicts: novaclient/client.py novaclient/shell.py novaclient/v2/client.py novaclient/v3/client.py Closes-Bug: #1423695 Change-Id: I6676a11f9b5318a1cda2d03a5d4550bda549d5c2
Diffstat (limited to 'novaclient/base.py')
-rw-r--r--novaclient/base.py97
1 files changed, 75 insertions, 22 deletions
diff --git a/novaclient/base.py b/novaclient/base.py
index 731d220e..8cee35ae 100644
--- a/novaclient/base.py
+++ b/novaclient/base.py
@@ -20,12 +20,17 @@ Base utilities to build API operation managers and objects on top of.
"""
import abc
+import contextlib
+import hashlib
import inspect
+import os
+import threading
import six
from novaclient import exceptions
from novaclient.openstack.common.apiclient import base
+from novaclient.openstack.common import cliutils
Resource = base.Resource
@@ -47,18 +52,11 @@ class Manager(base.HookableMixin):
etc.) and provide CRUD operations for them.
"""
resource_class = None
+ cache_lock = threading.RLock()
def __init__(self, api):
self.api = api
- def _write_object_to_completion_cache(self, obj):
- if hasattr(self.api, 'write_object_to_completion_cache'):
- self.api.write_object_to_completion_cache(obj)
-
- def _clear_completion_cache_for_class(self, obj_class):
- if hasattr(self.api, 'clear_completion_cache_for_class'):
- self.api.clear_completion_cache_for_class(obj_class)
-
def _list(self, url, response_key, obj_class=None, body=None):
if body:
_resp, body = self.api.client.post(url, body=body)
@@ -77,22 +75,77 @@ class Manager(base.HookableMixin):
except KeyError:
pass
- self._clear_completion_cache_for_class(obj_class)
+ with self.completion_cache('human_id', obj_class, mode="w"):
+ with self.completion_cache('uuid', obj_class, mode="w"):
+ return [obj_class(self, res, loaded=True)
+ for res in data if res]
+
+ @contextlib.contextmanager
+ def completion_cache(self, cache_type, obj_class, mode):
+ """
+ The completion cache store items that can be used for bash
+ autocompletion, like UUIDs or human-friendly IDs.
+
+ A resource listing will clear and repopulate the cache.
- objs = []
- for res in data:
- if res:
- obj = obj_class(self, res, loaded=True)
- self._write_object_to_completion_cache(obj)
- objs.append(obj)
+ A resource create will append to the cache.
- return objs
+ Delete is not handled because listings are assumed to be performed
+ often enough to keep the cache reasonably up-to-date.
+ """
+ # NOTE(wryan): This lock protects read and write access to the
+ # completion caches
+ with self.cache_lock:
+ base_dir = cliutils.env('NOVACLIENT_UUID_CACHE_DIR',
+ default="~/.novaclient")
+
+ # NOTE(sirp): Keep separate UUID caches for each username +
+ # endpoint pair
+ username = cliutils.env('OS_USERNAME', 'NOVA_USERNAME')
+ url = cliutils.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))
+
+ try:
+ os.makedirs(cache_dir, 0o755)
+ except OSError:
+ # NOTE(kiall): This is typically 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)
+
+ cache_attr = "_%s_cache" % cache_type
+
+ try:
+ setattr(self, cache_attr, open(path, mode))
+ except IOError:
+ # NOTE(kiall): This is typically 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)
+
+ def write_to_completion_cache(self, cache_type, val):
+ cache = getattr(self, "_%s_cache" % cache_type, None)
+ if cache:
+ cache.write("%s\n" % val)
def _get(self, url, response_key):
_resp, body = self.api.client.get(url)
- obj = self.resource_class(self, body[response_key], loaded=True)
- self._write_object_to_completion_cache(obj)
- return obj
+ return self.resource_class(self, body[response_key], loaded=True)
def _create(self, url, body, response_key, return_raw=False, **kwargs):
self.run_hooks('modify_body_for_create', body, **kwargs)
@@ -100,9 +153,9 @@ class Manager(base.HookableMixin):
if return_raw:
return body[response_key]
- obj = self.resource_class(self, body[response_key])
- self._write_object_to_completion_cache(obj)
- return obj
+ with self.completion_cache('human_id', self.resource_class, mode="a"):
+ with self.completion_cache('uuid', self.resource_class, mode="a"):
+ return self.resource_class(self, body[response_key])
def _delete(self, url):
_resp, _body = self.api.client.delete(url)