248 lines
8.4 KiB
Python
Raw Normal View History

2012-05-21 16:32:35 -04:00
# Copyright 2010 Jacob Kaplan-Moss
# Copyright (c) 2011 OpenStack Foundation
2012-05-21 16:32:35 -04:00
# 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 abc
2012-05-21 16:32:35 -04:00
import contextlib
import hashlib
import os
import six
from cinderclient import exceptions
from cinderclient.openstack.common.apiclient import base as common_base
2012-05-21 16:32:35 -04:00
from cinderclient import utils
Resource = common_base.Resource
2012-05-21 16:32:35 -04:00
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,
limit=None, items=None):
2012-05-21 16:32:35 -04:00
resp = None
if items is None:
items = []
2012-05-21 16:32:35 -04:00
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"):
items_new = [obj_class(self, res, loaded=True)
for res in data if res]
if limit:
limit = int(limit)
margin = limit - len(items)
if margin <= len(items_new):
# If the limit is reached, return the items.
items = items + items_new[:margin]
return items
else:
items = items + items_new
else:
items = items + items_new
# It is possible that the length of the list we request is longer
# than osapi_max_limit, so we have to retrieve multiple times to
# get the complete list.
next = None
if 'volumes_links' in body:
volumes_links = body['volumes_links']
if volumes_links:
for volumes_link in volumes_links:
if 'rel' in volumes_link and 'next' == volumes_link['rel']:
next = volumes_link['href']
break
if next:
# As long as the 'next' link is not empty, keep requesting it
# till there is no more items.
items = self._list(next, response_key, obj_class, None,
limit, items)
return items
2012-05-21 16:32:35 -04:00
@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.encode('utf-8') +
url.encode('utf-8')).hexdigest()
2012-05-21 16:32:35 -04:00
cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier))
try:
os.makedirs(cache_dir, 0o755)
2012-05-21 16:32:35 -04:00
except OSError:
# NOTE(kiall): This is typically either permission denied while
2012-05-21 16:32:35 -04:00
# 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
2012-05-21 16:32:35 -04:00
# 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, response_key=None, **kwargs):
2012-05-21 16:32:35 -04:00
self.run_hooks('modify_body_for_update', body, **kwargs)
resp, body = self.api.client.put(url, body=body)
if response_key:
return self.resource_class(self, body[response_key], loaded=True)
2012-05-21 16:32:35 -04:00
return body
class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)):
2012-05-21 16:32:35 -04:00
"""
Like a `Manager`, but with additional `find()`/`findall()` methods.
"""
@abc.abstractmethod
def list(self):
pass
2012-05-21 16:32:35 -04:00
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 = list(kwargs.items())
2012-05-21 16:32:35 -04:00
# Want to search for all tenants here so that when attempting to delete
# that a user like admin doesn't get a failure when trying to delete
# another tenant's volume by name.
for obj in self.list(search_opts={'all_tenants': 1}):
2012-05-21 16:32:35 -04:00
try:
if all(getattr(obj, attr) == value
for (attr, value) in searches):
2012-05-21 16:32:35 -04:00
found.append(obj)
except AttributeError:
continue
return found