python-troveclient/reddwarfclient/common.py

396 lines
13 KiB
Python

# Copyright 2011 OpenStack LLC
#
# 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.
import copy
import json
import optparse
import os
import pickle
import sys
from reddwarfclient import client
from reddwarfclient.xml import ReddwarfXmlClient
from reddwarfclient import exceptions
def methods_of(obj):
"""Get all callable methods of an object that don't start with underscore
returns a list of tuples of the form (method_name, method)"""
result = {}
for i in dir(obj):
if callable(getattr(obj, i)) and not i.startswith('_'):
result[i] = getattr(obj, i)
return result
def check_for_exceptions(resp, body):
if resp.status in (400, 422, 500):
raise exceptions.from_response(resp, body)
def print_actions(cmd, actions):
"""Print help for the command with list of options and description"""
print ("Available actions for '%s' cmd:") % cmd
for k, v in actions.iteritems():
print "\t%-20s%s" % (k, v.__doc__)
sys.exit(2)
def print_commands(commands):
"""Print the list of available commands and description"""
print "Available commands"
for k, v in commands.iteritems():
print "\t%-20s%s" % (k, v.__doc__)
sys.exit(2)
def limit_url(url, limit=None, marker=None):
if not limit and not marker:
return url
query = []
if marker:
query.append("marker=%s" % marker)
if limit:
query.append("limit=%s" % limit)
query = '?' + '&'.join(query)
return url + query
class CliOptions(object):
"""A token object containing the user, apikey and token which
is pickleable."""
APITOKEN = os.path.expanduser("~/.apitoken")
DEFAULT_VALUES = {
'username': None,
'apikey': None,
'tenant_id': None,
'auth_url': None,
'auth_type': 'keystone',
'service_type': 'database',
'service_name': 'reddwarf',
'region': 'RegionOne',
'service_url': None,
'insecure': False,
'verbose': False,
'debug': False,
'token': None,
'xml': None,
}
def __init__(self, **kwargs):
for key, value in self.DEFAULT_VALUES.items():
setattr(self, key, value)
@classmethod
def default(cls):
kwargs = copy.deepcopy(cls.DEFAULT_VALUES)
return cls(**kwargs)
@classmethod
def load_from_file(cls):
try:
with open(cls.APITOKEN, 'rb') as token:
return pickle.load(token)
except IOError:
pass # File probably not found.
except:
print("ERROR: Token file found at %s was corrupt." % cls.APITOKEN)
return cls.default()
@classmethod
def save_from_instance_fields(cls, instance):
apitoken = cls.default()
for key, default_value in cls.DEFAULT_VALUES.items():
final_value = getattr(instance, key, default_value)
setattr(apitoken, key, final_value)
with open(cls.APITOKEN, 'wb') as token:
pickle.dump(apitoken, token, protocol=2)
@classmethod
def create_optparser(cls, load_file):
oparser = optparse.OptionParser(
usage="%prog [options] <cmd> <action> <args>",
version='1.0', conflict_handler='resolve')
if load_file:
file = cls.load_from_file()
else:
file = cls.default()
def add_option(*args, **kwargs):
if len(args) == 1:
name = args[0]
else:
name = args[1]
kwargs['default'] = getattr(file, name, cls.DEFAULT_VALUES[name])
oparser.add_option("--%s" % name, **kwargs)
add_option("verbose", action="store_true",
help="Show equivalent curl statement along "
"with actual HTTP communication.")
add_option("debug", action="store_true",
help="Show the stack trace on errors.")
add_option("auth_url", help="Auth API endpoint URL with port and "
"version. Default: http://localhost:5000/v2.0")
add_option("username", help="Login username")
add_option("apikey", help="Api key")
add_option("tenant_id",
help="Tenant Id associated with the account")
add_option("auth_type",
help="Auth type to support different auth environments, \
Supported values are 'keystone', 'rax'.")
add_option("service_type",
help="Service type is a name associated for the catalog")
add_option("service_name",
help="Service name as provided in the service catalog")
add_option("service_url",
help="Service endpoint to use if the catalog doesn't have one.")
add_option("region", help="Region the service is located in")
add_option("insecure", action="store_true",
help="Run in insecure mode for https endpoints.")
add_option("token", help="Token from a prior login.")
add_option("xml", action="store_true", help="Changes format to XML.")
oparser.add_option("--secure", action="store_false", dest="insecure",
help="Run in insecure mode for https endpoints.")
oparser.add_option("--json", action="store_false", dest="xml",
help="Changes format to JSON.")
oparser.add_option("--terse", action="store_false", dest="verbose",
help="Toggles verbose mode off.")
oparser.add_option("--hide-debug", action="store_false", dest="debug",
help="Toggles debug mode off.")
return oparser
class ArgumentRequired(Exception):
def __init__(self, param):
self.param = param
def __str__(self):
return 'Argument "--%s" required.' % self.param
class CommandsBase(object):
params = []
def __init__(self, parser):
self._parse_options(parser)
def _get_client(self):
"""Creates the all important client object."""
try:
if self.xml:
client_cls = ReddwarfXmlClient
else:
client_cls = client.ReddwarfHTTPClient
if self.verbose:
client.log_to_streamhandler(sys.stdout)
client.RDC_PP = True
return client.Dbaas(self.username, self.apikey, self.tenant_id,
auth_url=self.auth_url,
auth_strategy=self.auth_type,
service_type=self.service_type,
service_name=self.service_name,
region_name=self.region,
service_url=self.service_url,
insecure=self.insecure,
client_cls=client_cls)
except:
if self.debug:
raise
print sys.exc_info()[1]
def _safe_exec(self, func, *args, **kwargs):
if not self.debug:
try:
return func(*args, **kwargs)
except:
print(sys.exc_info()[1])
return None
else:
return func(*args, **kwargs)
@classmethod
def _prepare_parser(cls, parser):
for param in cls.params:
parser.add_option("--%s" % param)
def _parse_options(self, parser):
opts, args = parser.parse_args()
for param in opts.__dict__:
value = getattr(opts, param)
setattr(self, param, value)
def _require(self, *params):
for param in params:
if not hasattr(self, param):
raise ArgumentRequired(param)
if not getattr(self, param):
raise ArgumentRequired(param)
def _make_list(self, *params):
# Convert the listed params to lists.
for param in params:
raw = getattr(self, param)
if isinstance(raw, list):
return
raw = [item.strip() for item in raw.split(',')]
setattr(self, param, raw)
def _pretty_print(self, func, *args, **kwargs):
if self.verbose:
self._safe_exec(func, *args, **kwargs)
return # Skip this, since the verbose stuff will show up anyway.
def wrapped_func():
result = func(*args, **kwargs)
if result:
print(json.dumps(result._info, sort_keys=True, indent=4))
else:
print("OK")
self._safe_exec(wrapped_func)
def _dumps(self, item):
return json.dumps(item, sort_keys=True, indent=4)
def _pretty_list(self, func, *args, **kwargs):
result = self._safe_exec(func, *args, **kwargs)
if self.verbose:
return
if result and len(result) > 0:
for item in result:
print(self._dumps(item._info))
else:
print("OK")
def _pretty_paged(self, func, *args, **kwargs):
try:
limit = self.limit
if limit:
limit = int(limit, 10)
result = func(*args, limit=limit, marker=self.marker, **kwargs)
if self.verbose:
return # Verbose already shows the output, so skip this.
if result and len(result) > 0:
for item in result:
print self._dumps(item._info)
if result.links:
print("Links:")
for link in result.links:
print self._dumps((link))
else:
print("OK")
except:
if self.debug:
raise
print sys.exc_info()[1]
class Auth(CommandsBase):
"""Authenticate with your username and api key"""
params = [
'apikey',
'auth_strategy',
'auth_type',
'auth_url',
'options',
'region',
'service_name',
'service_type',
'service_url',
'tenant_id',
'username',
]
def __init__(self, parser):
super(Auth, self).__init__(parser)
self.dbaas = None
def login(self):
"""Login to retrieve an auth token to use for other api calls"""
self._require('username', 'apikey', 'tenant_id', 'auth_url')
try:
self.dbaas = self._get_client()
self.dbaas.authenticate()
self.token = self.dbaas.client.auth_token
self.service_url = self.dbaas.client.service_url
CliOptions.save_from_instance_fields(self)
print("Token aquired! Saving to %s..." % CliOptions.APITOKEN)
print(" service_url = %s" % self.service_url)
print(" token = %s" % self.token)
except:
if self.debug:
raise
print sys.exc_info()[1]
class AuthedCommandsBase(CommandsBase):
"""Commands that work only with an authicated client."""
def __init__(self, parser):
"""Makes sure a token is available somehow and logs in."""
super(AuthedCommandsBase, self).__init__(parser)
try:
self._require('token')
except ArgumentRequired:
if self.debug:
raise
print('No token argument supplied. Use the "auth login" command '
'to log in and get a token.\n')
sys.exit(1)
try:
self._require('service_url')
except ArgumentRequired:
if self.debug:
raise
print('No service_url given.\n')
sys.exit(1)
self.dbaas = self._get_client()
# Actually set the token to avoid a re-auth.
self.dbaas.client.auth_token = self.token
self.dbaas.client.authenticate_with_token(self.token, self.service_url)
class Paginated(object):
""" Pretends to be a list if you iterate over it, but also keeps a
next property you can use to get the next page of data. """
def __init__(self, items=[], next_marker=None, links=[]):
self.items = items
self.next = next_marker
self.links = links
def __len__(self):
return len(self.items)
def __iter__(self):
return self.items.__iter__()
def __getitem__(self, key):
return self.items[key]
def __setitem__(self, key, value):
self.items[key] = value
def __delitem__(self, key):
del self.items[key]
def __reversed__(self):
return reversed(self.items)
def __contains__(self, needle):
return needle in self.items