summaryrefslogtreecommitdiff
path: root/cinderclient/base.py
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-05-21 16:32:35 -0400
committerMonty Taylor <mordred@inaugust.com>2012-05-21 16:32:35 -0400
commit471704df644eced17026c280b0aab9e549718e14 (patch)
treec2d8d0ec74fa45e0b61ca4b2153fb5b0e7bf490d /cinderclient/base.py
downloadpython-cinderclient-0.0.tar.gz
Initial split from python-novaclient.0.0
Diffstat (limited to 'cinderclient/base.py')
-rw-r--r--cinderclient/base.py293
1 files changed, 293 insertions, 0 deletions
diff --git a/cinderclient/base.py b/cinderclient/base.py
new file mode 100644
index 0000000..02d3549
--- /dev/null
+++ b/cinderclient/base.py
@@ -0,0 +1,293 @@
+# Copyright 2010 Jacob Kaplan-Moss
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Base utilities to build API operation managers and objects on top of.
+"""
+
+import contextlib
+import hashlib
+import os
+from cinderclient import exceptions
+from cinderclient import utils
+
+
+# Python 2.4 compat
+try:
+ all
+except NameError:
+ def all(iterable):
+ return True not in (not x for x in iterable)
+
+
+def getid(obj):
+ """
+ Abstracts the common pattern of allowing both an object or an object's ID
+ as a parameter when dealing with relationships.
+ """
+ try:
+ return obj.id
+ except AttributeError:
+ return obj
+
+
+class Manager(utils.HookableMixin):
+ """
+ Managers interact with a particular type of API (servers, flavors, images,
+ etc.) and provide CRUD operations for them.
+ """
+ resource_class = None
+
+ def __init__(self, api):
+ self.api = api
+
+ def _list(self, url, response_key, obj_class=None, body=None):
+ resp = None
+ if body:
+ resp, body = self.api.client.post(url, body=body)
+ else:
+ resp, body = self.api.client.get(url)
+
+ if obj_class is None:
+ obj_class = self.resource_class
+
+ data = body[response_key]
+ # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
+ # unlike other services which just return the list...
+ if isinstance(data, dict):
+ try:
+ data = data['values']
+ except KeyError:
+ pass
+
+ 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.
+
+ A resource create will append to the cache.
+
+ 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('CINDERCLIENT_UUID_CACHE_DIR',
+ default="~/.cinderclient")
+
+ # NOTE(sirp): Keep separate UUID caches for each username + endpoint
+ # pair
+ username = utils.env('OS_USERNAME', 'CINDER_USERNAME')
+ url = utils.env('OS_URL', 'CINDER_URL')
+ uniqifier = hashlib.md5(username + url).hexdigest()
+
+ cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier))
+
+ try:
+ os.makedirs(cache_dir, 0755)
+ 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)
+
+ 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:
+ 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=None):
+ resp, body = self.api.client.get(url)
+ if response_key:
+ return self.resource_class(self, body[response_key], loaded=True)
+ else:
+ return self.resource_class(self, body, loaded=True)
+
+ def _create(self, url, body, response_key, return_raw=False, **kwargs):
+ self.run_hooks('modify_body_for_create', body, **kwargs)
+ resp, body = self.api.client.post(url, body=body)
+ if return_raw:
+ return body[response_key]
+
+ 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)
+
+ def _update(self, url, body, **kwargs):
+ self.run_hooks('modify_body_for_update', body, **kwargs)
+ resp, body = self.api.client.put(url, body=body)
+ return body
+
+
+class ManagerWithFind(Manager):
+ """
+ Like a `Manager`, but with additional `find()`/`findall()` methods.
+ """
+ def find(self, **kwargs):
+ """
+ Find a single item with attributes matching ``**kwargs``.
+
+ This isn't very efficient: it loads the entire list then filters on
+ the Python side.
+ """
+ matches = self.findall(**kwargs)
+ num_matches = len(matches)
+ if num_matches == 0:
+ msg = "No %s matching %s." % (self.resource_class.__name__, kwargs)
+ raise exceptions.NotFound(404, msg)
+ elif num_matches > 1:
+ raise exceptions.NoUniqueMatch
+ else:
+ return matches[0]
+
+ def findall(self, **kwargs):
+ """
+ Find all items with attributes matching ``**kwargs``.
+
+ This isn't very efficient: it loads the entire list then filters on
+ the Python side.
+ """
+ found = []
+ searches = kwargs.items()
+
+ for obj in self.list():
+ try:
+ if all(getattr(obj, attr) == value
+ for (attr, value) in searches):
+ found.append(obj)
+ except AttributeError:
+ continue
+
+ return found
+
+ def list(self):
+ raise NotImplementedError
+
+
+class Resource(object):
+ """
+ A resource represents a particular instance of an object (server, flavor,
+ etc). This is pretty much just a bag for attributes.
+
+ :param manager: Manager object
+ :param info: dictionary representing resource attributes
+ :param loaded: prevent lazy-loading if set to True
+ """
+ HUMAN_ID = False
+
+ def __init__(self, manager, info, loaded=False):
+ self.manager = manager
+ self._info = info
+ self._add_details(info)
+ self._loaded = loaded
+
+ # NOTE(sirp): ensure `id` is already present because if it isn't we'll
+ # enter an infinite loop of __getattr__ -> get -> __init__ ->
+ # __getattr__ -> ...
+ if 'id' in self.__dict__ and len(str(self.id)) == 36:
+ self.manager.write_to_completion_cache('uuid', self.id)
+
+ human_id = self.human_id
+ if human_id:
+ self.manager.write_to_completion_cache('human_id', human_id)
+
+ @property
+ def human_id(self):
+ """Subclasses may override this provide a pretty ID which can be used
+ for bash completion.
+ """
+ if 'name' in self.__dict__ and self.HUMAN_ID:
+ return utils.slugify(self.name)
+ return None
+
+ def _add_details(self, info):
+ for (k, v) in info.iteritems():
+ try:
+ setattr(self, k, v)
+ except AttributeError:
+ # In this case we already defined the attribute on the class
+ pass
+
+ def __getattr__(self, k):
+ if k not in self.__dict__:
+ #NOTE(bcwaldon): disallow lazy-loading if already loaded once
+ if not self.is_loaded():
+ self.get()
+ return self.__getattr__(k)
+
+ raise AttributeError(k)
+ else:
+ return self.__dict__[k]
+
+ def __repr__(self):
+ reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and
+ k != 'manager')
+ info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
+ return "<%s %s>" % (self.__class__.__name__, info)
+
+ def get(self):
+ # set_loaded() first ... so if we have to bail, we know we tried.
+ self.set_loaded(True)
+ if not hasattr(self.manager, 'get'):
+ return
+
+ new = self.manager.get(self.id)
+ if new:
+ self._add_details(new._info)
+
+ def __eq__(self, other):
+ if not isinstance(other, self.__class__):
+ return False
+ if hasattr(self, 'id') and hasattr(other, 'id'):
+ return self.id == other.id
+ return self._info == other._info
+
+ def is_loaded(self):
+ return self._loaded
+
+ def set_loaded(self, val):
+ self._loaded = val