diff options
author | Andrew Laski <andrew.laski@rackspace.com> | 2015-02-19 18:24:51 -0500 |
---|---|---|
committer | melanie witt <melwitt@yahoo-inc.com> | 2015-02-20 01:35:57 +0000 |
commit | ac6636a54d72ba76d0adca76e07d1d26d9ea35c3 (patch) | |
tree | cecf9210fb200c52be19e05d03b0138fd06cec66 /novaclient/base.py | |
parent | bfd029c8ef500af705159a5f3d5fa74e39c43096 (diff) | |
download | python-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.py | 97 |
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) |