Merge pull request #27 from TimSimpsonR/better_cli-2

Added a ton of CLI options, plus fixed a CI bug.
This commit is contained in:
Tim Simpson
2012-08-14 10:47:07 -07:00
7 changed files with 410 additions and 239 deletions

View File

@@ -24,6 +24,8 @@ def get_authenticator_cls(cls_or_name):
return KeyStoneV2Authenticator
elif cls_or_name == "rax":
return RaxAuthenticator
elif cls_or_name == "auth1.1":
return Auth1_1
elif cls_or_name == "fake":
return FakeAuth
@@ -40,6 +42,8 @@ class Authenticator(object):
"""
URL_REQUIRED=True
def __init__(self, client, type, url, username, password, tenant,
region=None, service_type=None, service_name=None,
service_url=None):
@@ -54,7 +58,7 @@ class Authenticator(object):
self.service_name = service_name
self.service_url = service_url
def _authenticate(self, url, body):
def _authenticate(self, url, body, root_key='access'):
"""Authenticate and extract the service catalog."""
# Make sure we follow redirects when trying to reach Keystone
tmp_follow_all_redirects = self.client.follow_all_redirects
@@ -70,7 +74,8 @@ class Authenticator(object):
return ServiceCatalog(body, region=self.region,
service_type=self.service_type,
service_name=self.service_name,
service_url=self.service_url)
service_url=self.service_url,
root_key=root_key)
except exceptions.AmbiguousEndpoints:
print "Found more than one valid endpoint. Use a more "\
"restrictive filter"
@@ -93,6 +98,8 @@ class Authenticator(object):
class KeyStoneV2Authenticator(Authenticator):
def authenticate(self):
if self.url is None:
raise exceptions.AuthUrlNotGiven()
return self._v2_auth(self.url)
def _v2_auth(self, url):
@@ -110,9 +117,40 @@ class KeyStoneV2Authenticator(Authenticator):
return self._authenticate(url, body)
class Auth1_1(Authenticator):
def authenticate(self):
"""Authenticate against a v2.0 auth service."""
if self.url is None:
raise exceptions.AuthUrlNotGiven()
auth_url = self.url
body = {"credentials": {"username": self.username,
"key": self.password}}
return self._authenticate(auth_url, body, root_key='auth')
try:
print(resp_body)
self.auth_token = resp_body['auth']['token']['id']
except KeyError:
raise nova_exceptions.AuthorizationFailure()
catalog = resp_body['auth']['serviceCatalog']
if 'cloudDatabases' not in catalog:
raise nova_exceptions.EndpointNotFound()
endpoints = catalog['cloudDatabases']
for endpoint in endpoints:
if self.region_name is None or \
endpoint['region'] == self.region_name:
self.management_url = endpoint['publicURL']
return
raise nova_exceptions.EndpointNotFound()
class RaxAuthenticator(Authenticator):
def authenticate(self):
if self.url is None:
raise exceptions.AuthUrlNotGiven()
return self._rax_auth(self.url)
def _rax_auth(self, url):
@@ -155,7 +193,7 @@ class ServiceCatalog(object):
"""
def __init__(self, resource_dict, region=None, service_type=None,
service_name=None, service_url=None):
service_name=None, service_url=None, root_key='access'):
self.catalog = resource_dict
self.region = region
self.service_type = service_type
@@ -163,6 +201,7 @@ class ServiceCatalog(object):
self.service_url = service_url
self.management_url = None
self.public_url = None
self.root_key = root_key
self._load()
def _load(self):
@@ -178,7 +217,7 @@ class ServiceCatalog(object):
self.management_url = self.service_url
def get_token(self):
return self.catalog['access']['token']['id']
return self.catalog[self.root_key]['token']['id']
def get_management_url(self):
return self.management_url
@@ -202,11 +241,11 @@ class ServiceCatalog(object):
raise exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
if not 'serviceCatalog' in self.catalog['access']:
if not 'serviceCatalog' in self.catalog[self.root_key]:
raise exceptions.EndpointNotFound()
# Full catalog ...
catalog = self.catalog['access']['serviceCatalog']
catalog = self.catalog[self.root_key]['serviceCatalog']
for service in catalog:
if service.get("type") != self.service_type:

View File

@@ -18,6 +18,7 @@
Reddwarf Command line tool
"""
#TODO(tim.simpson): optparse is deprecated. Replace with argparse.
import optparse
import os
import sys
@@ -36,7 +37,7 @@ if os.path.exists(os.path.join(possible_topdir, 'reddwarfclient',
from reddwarfclient import common
class InstanceCommands(common.CommandsBase):
class InstanceCommands(common.AuthedCommandsBase):
"""Commands to perform various instances operations and actions"""
params = [
@@ -98,17 +99,17 @@ class InstanceCommands(common.CommandsBase):
self._pretty_print(self.dbaas.instances.reset_password, self.id)
class FlavorsCommands(common.CommandsBase):
class FlavorsCommands(common.AuthedCommandsBase):
"""Commands for listing Flavors"""
params = []
def list(self):
"""List the available flavors"""
self._pretty_print(self.dbaas.flavors.list)
self._pretty_list(self.dbaas.flavors.list)
class DatabaseCommands(common.CommandsBase):
class DatabaseCommands(common.AuthedCommandsBase):
"""Database CRUD operations on an instance"""
params = [
@@ -135,7 +136,7 @@ class DatabaseCommands(common.CommandsBase):
self._pretty_paged(self.dbaas.databases.list, self.id)
class UserCommands(common.CommandsBase):
class UserCommands(common.AuthedCommandsBase):
"""User CRUD operations on an instance"""
params = [
'id',
@@ -164,7 +165,7 @@ class UserCommands(common.CommandsBase):
self._pretty_paged(self.dbaas.users.list, self.id)
class RootCommands(common.CommandsBase):
class RootCommands(common.AuthedCommandsBase):
"""Root user related operations on an instance"""
params = [
@@ -186,7 +187,7 @@ class RootCommands(common.CommandsBase):
self._pretty_print(self.dbaas.root.is_root_enabled, self.id)
class VersionCommands(common.CommandsBase):
class VersionCommands(common.AuthedCommandsBase):
"""List available versions"""
params = [
@@ -199,31 +200,6 @@ class VersionCommands(common.CommandsBase):
self._pretty_print(self.dbaas.versions.index, self.url)
def config_options(oparser):
oparser.add_option("--auth_url", default="http://localhost:5000/v2.0",
help="Auth API endpoint URL with port and version. \
Default: http://localhost:5000/v2.0")
oparser.add_option("--username", help="Login username")
oparser.add_option("--apikey", help="Api key")
oparser.add_option("--tenant_id",
help="Tenant Id associated with the account")
oparser.add_option("--auth_type", default="keystone",
help="Auth type to support different auth environments, \
Supported values are 'keystone', 'rax'.")
oparser.add_option("--service_type", default="reddwarf",
help="Service type is a name associated for the catalog")
oparser.add_option("--service_name", default="Reddwarf",
help="Service name as provided in the service catalog")
oparser.add_option("--service_url", default="",
help="Service endpoint to use if the catalog doesn't \
have one")
oparser.add_option("--region", default="RegionOne",
help="Region the service is located in")
oparser.add_option("-i", "--insecure", action="store_true",
dest="insecure", default=False,
help="Run in insecure mode for https endpoints.")
COMMANDS = {'auth': common.Auth,
'instance': InstanceCommands,
'flavor': FlavorsCommands,
@@ -235,10 +211,7 @@ COMMANDS = {'auth': common.Auth,
def main():
# Parse arguments
oparser = optparse.OptionParser(usage="%prog [options] <cmd> <action> <args>",
version='1.0',
conflict_handler='resolve')
config_options(oparser)
oparser = common.CliOptions.create_optparser()
for k, v in COMMANDS.items():
v._prepare_parser(oparser)
(options, args) = oparser.parse_args()
@@ -246,11 +219,21 @@ def main():
if not args:
common.print_commands(COMMANDS)
if options.verbose:
os.environ['RDC_PP'] = "True"
os.environ['REDDWARFCLIENT_DEBUG'] = "True"
# Pop the command and check if it's in the known commands
cmd = args.pop(0)
if cmd in COMMANDS:
fn = COMMANDS.get(cmd)
command_object = fn(oparser)
command_object = None
try:
command_object = fn(oparser)
except Exception as ex:
if options.debug:
raise
print(ex)
# Get a list of supported actions for the command
actions = common.methods_of(command_object)
@@ -261,10 +244,15 @@ def main():
# Check for a valid action and perform that action
action = args.pop(0)
if action in actions:
try:
if not options.debug:
try:
getattr(command_object, action)()
except Exception as ex:
if options.debug:
raise
print ex
else:
getattr(command_object, action)()
except Exception as ex:
print ex
else:
common.print_actions(cmd, actions)
else:

View File

@@ -18,6 +18,7 @@ import logging
import os
import time
import urlparse
import sys
try:
import json
@@ -37,12 +38,17 @@ _logger = logging.getLogger(__name__)
RDC_PP = os.environ.get("RDC_PP", "False") == "True"
if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']:
ch = logging.StreamHandler()
def log_to_streamhandler(stream=None):
stream = stream or sys.stderr
ch = logging.StreamHandler(stream)
_logger.setLevel(logging.DEBUG)
_logger.addHandler(ch)
if 'REDDWARFCLIENT_DEBUG' in os.environ and os.environ['REDDWARFCLIENT_DEBUG']:
log_to_streamhandler()
class ReddwarfHTTPClient(httplib2.Http):
USER_AGENT = 'python-reddwarfclient'
@@ -60,7 +66,10 @@ class ReddwarfHTTPClient(httplib2.Http):
self.username = user
self.password = password
self.tenant = tenant
self.auth_url = auth_url.rstrip('/')
if auth_url:
self.auth_url = auth_url.rstrip('/')
else:
self.auth_url = None
self.region_name = region_name
self.endpoint_type = endpoint_type
self.service_url = service_url
@@ -165,7 +174,13 @@ class ReddwarfHTTPClient(httplib2.Http):
self.http_log(args, kwargs, resp, body)
if body:
body = self.morph_response_body(body)
try:
body = self.morph_response_body(body)
except exceptions.ResponseFormatError:
# Acceptable only if the response status is an error code.
# Otherwise its the API or client misbehaving.
self.raise_error_from_status(resp, None)
raise # Not accepted!
else:
body = None
@@ -174,6 +189,10 @@ class ReddwarfHTTPClient(httplib2.Http):
return resp, body
def raise_error_from_status(self, resp, body):
if resp.status in (400, 401, 403, 404, 408, 409, 413, 500, 501):
raise exceptions.from_response(resp, body)
def morph_request(self, kwargs):
kwargs['headers']['Accept'] = 'application/json'
kwargs['headers']['Content-Type'] = 'application/json'
@@ -181,7 +200,10 @@ class ReddwarfHTTPClient(httplib2.Http):
kwargs['body'] = json.dumps(kwargs['body'])
def morph_response_body(self, body_string):
return json.loads(body_string)
try:
return json.loads(body_string)
except ValueError:
raise exceptions.ResponseFormatError()
def _time_request(self, url, method, **kwargs):
start_time = time.time()
@@ -236,12 +258,23 @@ class ReddwarfHTTPClient(httplib2.Http):
"""
catalog = self.authenticator.authenticate()
self.auth_token = catalog.get_token()
if not self.service_url:
if self.service_url:
possible_service_url = None
else:
if self.endpoint_type == "publicURL":
self.service_url = catalog.get_public_url()
possible_service_url = catalog.get_public_url()
elif self.endpoint_type == "adminURL":
self.service_url = catalog.get_management_url()
possible_service_url = catalog.get_management_url()
self.authenticate_with_token(catalog.get_token(), possible_service_url)
def authenticate_with_token(self, token, service_url=None):
self.auth_token = token
if not self.service_url:
if not service_url:
raise exceptions.ServiceUrlNotGiven()
else:
self.service_url = service_url
class Dbaas(object):

View File

@@ -12,42 +12,18 @@
# 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.client import Dbaas
from reddwarfclient import client
from reddwarfclient.xml import ReddwarfXmlClient
from reddwarfclient import exceptions
APITOKEN = os.path.expanduser("~/.apitoken")
def get_client():
"""Load an existing apitoken if available"""
try:
with open(APITOKEN, 'rb') as token:
apitoken = pickle.load(token)
dbaas = Dbaas(apitoken._user, apitoken._apikey,
tenant=apitoken._tenant, auth_url=apitoken._auth_url,
auth_strategy=apitoken._auth_strategy,
service_type=apitoken._service_type,
service_name=apitoken._service_name,
service_url=apitoken._service_url,
insecure=apitoken._insecure,
region_name=apitoken._region_name)
dbaas.client.auth_token = apitoken._token
return dbaas
except IOError:
print "ERROR: You need to login first and get an auth token\n"
sys.exit(1)
except:
print "ERROR: There was an error using your existing auth token, " \
"please login again.\n"
sys.exit(1)
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)"""
@@ -92,24 +68,110 @@ def limit_url(url, limit=None, marker=None):
return url + query
class APIToken(object):
class CliOptions(object):
"""A token object containing the user, apikey and token which
is pickleable."""
def __init__(self, user, apikey, tenant, token, auth_url, auth_strategy,
service_type, service_name, service_url, region_name,
insecure):
self._user = user
self._apikey = apikey
self._tenant = tenant
self._token = token
self._auth_url = auth_url
self._auth_strategy = auth_strategy
self._service_type = service_type
self._service_name = service_name
self._service_url = service_url
self._region_name = region_name
self._insecure = insecure
APITOKEN = os.path.expanduser("~/.apitoken")
DEFAULT_VALUES = {
'username':None,
'apikey':None,
'tenant_id':None,
'auth_url':None,
'auth_type':'keystone',
'service_type':'reddwarf',
'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):
oparser = optparse.OptionParser(
usage="%prog [options] <cmd> <action> <args>",
version='1.0', conflict_handler='resolve')
file = cls.load_from_file()
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):
@@ -123,15 +185,49 @@ class ArgumentRequired(Exception):
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 __init__(self, parser):
self.dbaas = get_client()
self._parse_options(parser)
def _parse_options(self, parser):
opts, args = parser.parse_args()
for param in opts.__dict__:
@@ -140,9 +236,8 @@ class CommandsBase(object):
def _require(self, *params):
for param in params:
if any([param not in self.params,
not hasattr(self, param)]):
raise ArgumentRequired(param)
if not hasattr(self, param):
raise ArgumentRequired(param)
if not getattr(self, param):
raise ArgumentRequired(param)
@@ -156,11 +251,23 @@ class CommandsBase(object):
setattr(self, param, raw)
def _pretty_print(self, func, *args, **kwargs):
try:
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)
print json.dumps(result._info, sort_keys=True, indent=4)
except:
print sys.exc_info()[1]
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
for item in result:
print self._dumps(item._info)
def _pretty_paged(self, func, *args, **kwargs):
try:
@@ -168,12 +275,16 @@ class CommandsBase(object):
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.
for item in result:
print json.dumps(item._info, sort_keys=True, indent=4)
print self._dumps(item._info)
if result.links:
for link in result.links:
print json.dumps(link, sort_keys=True, indent=4)
print self._dumps((link))
except:
if self.debug:
raise
print sys.exc_info()[1]
@@ -195,37 +306,52 @@ class Auth(CommandsBase):
]
def __init__(self, parser):
self.parser = parser
super(Auth, self).__init__(parser)
self.dbaas = None
self._parse_options(parser)
def login(self):
"""Login to retrieve an auth token to use for other api calls"""
self._require('username', 'apikey', 'tenant_id')
self._require('username', 'apikey', 'tenant_id', 'auth_url')
try:
self.dbaas = 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)
self.dbaas = self.get_client()
self.dbaas.authenticate()
apitoken = APIToken(self.username, self.apikey,
self.tenant_id, self.dbaas.client.auth_token,
self.auth_url, self.auth_type,
self.service_type, self.service_name,
self.service_url, self.region,
self.insecure)
with open(APITOKEN, 'wb') as token:
pickle.dump(apitoken, token, protocol=2)
print apitoken._token
self.token = self.dbaas.client.auth_token
self.service_url = self.dbaas.client.service_url
CliOptions.save_from_instance_fields(self)
print(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. """

View File

@@ -41,6 +41,20 @@ class EndpointNotFound(Exception):
pass
class AuthUrlNotGiven(EndpointNotFound):
"""The auth url was not given."""
pass
class ServiceUrlNotGiven(EndpointNotFound):
"""The service url was not given."""
pass
class ResponseFormatError(Exception):
"""Could not parse the response format."""
pass
class AmbiguousEndpoints(Exception):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
@@ -159,4 +173,5 @@ def from_response(response, body):
details = error.get('details', None)
return cls(code=response.status, message=message, details=details)
else:
request_id = response.get('x-compute-request-id')
return cls(code=response.status, request_id=request_id)

View File

@@ -44,143 +44,112 @@ def _pretty_print(info):
print json.dumps(info, sort_keys=True, indent=4)
class HostCommands(object):
class HostCommands(common.AuthedCommandsBase):
"""Commands to list info on hosts"""
def __init__(self):
pass
params = [
'name',
]
def get(self, name):
def update_all(self):
"""Update all instances on a host"""
self._require('name')
self.dbaas.hosts.update_all(self.name)
def get(self):
"""List details for the specified host"""
dbaas = common.get_client()
try:
_pretty_print(dbaas.hosts.get(name)._info)
except:
print sys.exc_info()[1]
self._require('name')
self._pretty_print(self.dbaas.hosts.get, self.name)
def list(self):
"""List all compute hosts"""
dbaas = common.get_client()
try:
for host in dbaas.hosts.index():
_pretty_print(host._info)
except:
print sys.exc_info()[1]
self._pretty_list(self.dbaas.hosts.index)
class RootCommands(object):
class RootCommands(common.AuthedCommandsBase):
"""List details about the root info for an instance."""
def __init__(self):
pass
params = [
'id',
]
def history(self, id):
def history(self):
"""List root history for the instance."""
dbaas = common.get_client()
try:
result = dbaas.management.root_enabled_history(id)
_pretty_print(result._info)
except:
print sys.exc_info()[1]
self._require('id')
self._pretty_print(self.dbaas.management.root_enabled_history, self.id)
class AccountCommands(object):
class AccountCommands(common.AuthedCommandsBase):
"""Commands to list account info"""
def __init__(self):
pass
params = [
'id',
]
def list(self):
"""List all accounts with non-deleted instances"""
dbaas = common.get_client()
try:
_pretty_print(dbaas.accounts.index()._info)
except:
print sys.exc_info()[1]
self._pretty_print(self.dbaas.accounts.index)
def get(self, acct):
def get(self):
"""List details for the account provided"""
dbaas = common.get_client()
try:
_pretty_print(dbaas.accounts.show(acct)._info)
except:
print sys.exc_info()[1]
self._require('id')
self._pretty_print(self.dbaas.accounts.show, self.id)
class InstanceCommands(object):
class InstanceCommands(common.AuthedCommandsBase):
"""List details about an instance."""
def __init__(self):
pass
params = [
'deleted',
'id',
'limit',
'marker',
]
def get(self, id):
def get(self):
"""List details for the instance."""
dbaas = common.get_client()
try:
result = dbaas.management.show(id)
_pretty_print(result._info)
except:
print sys.exc_info()[1]
self._require('id')
self._pretty_print(self.dbaas.management.show, self.id)
def list(self, deleted=None, limit=None, marker=None):
def list(self):
"""List all instances for account"""
dbaas = common.get_client()
if limit:
limit = int(limit, 10)
try:
instances = dbaas.management.index(deleted, limit, marker)
for instance in instances:
_pretty_print(instance._info)
if instances.links:
for link in instances.links:
_pretty_print(link)
except:
print sys.exc_info()[1]
deleted = None
if self.deleted is not None:
if self.deleted.lower() in ['true']:
deleted = True
elif self.deleted.lower() in ['false']:
deleted = False
self._pretty_paged(self.dbaas.management.index, deleted=deleted)
def diagnostic(self, id):
def diagnostic(self):
"""List diagnostic details about an instance."""
self._require('id')
dbaas = common.get_client()
try:
result = dbaas.diagnostics.get(id)
_pretty_print(result._info)
except:
print sys.exc_info()[1]
self._pretty_print(self.dbaas.diagnostics.get, self.id)
def stop(self, id):
def stop(self):
"""Stop MySQL on the given instance."""
dbaas = common.get_client()
try:
result = dbaas.management.stop(id)
except:
print sys.exc_info()[1]
self._require('id')
self._pretty_print(self.dbaas.management.stop, self.id)
def reboot(self, id):
"""Reboot the instance."""
dbaas = common.get_client()
try:
result = dbaas.management.reboot(id)
except:
print sys.exec_info()[1]
self._require('id')
self._pretty_print(self.dbaas.management.reboot, self.id)
class StorageCommands(object):
class StorageCommands(common.AuthedCommandsBase):
"""Commands to list devices info"""
def __init__(self):
pass
params = []
def list(self):
"""List details for the storage device"""
dbaas = common.get_client()
try:
for storage in dbaas.storage.index():
_pretty_print(storage._info)
except:
print sys.exc_info()[1]
self._pretty_list(self.dbaas.storage.index)
def config_options():
global oparser
def config_options(oparser):
oparser.add_option("-u", "--url", default="http://localhost:5000/v1.1",
help="Auth API endpoint URL with port and version. \
Default: http://localhost:5000/v1.1")
@@ -196,10 +165,9 @@ COMMANDS = {'account': AccountCommands,
def main():
# Parse arguments
global oparser
oparser = optparse.OptionParser("%prog [options] <cmd> <action> <args>",
version='1.0')
config_options()
oparser = common.CliOptions.create_optparser()
for k, v in COMMANDS.items():
v._prepare_parser(oparser)
(options, args) = oparser.parse_args()
if not args:
@@ -209,7 +177,13 @@ def main():
cmd = args.pop(0)
if cmd in COMMANDS:
fn = COMMANDS.get(cmd)
command_object = fn()
command_object = None
try:
command_object = fn(oparser)
except Exception as ex:
if options.debug:
raise
print(ex)
# Get a list of supported actions for the command
actions = common.methods_of(command_object)
@@ -220,20 +194,12 @@ def main():
# Check for a valid action and perform that action
action = args.pop(0)
if action in actions:
fn = actions.get(action)
try:
fn(*args)
sys.exit(0)
except TypeError as err:
print "Possible wrong number of arguments supplied."
print "%s %s: %s" % (cmd, action, fn.__doc__)
print "\t\t", [fn.func_code.co_varnames[i] for i in
range(fn.func_code.co_argcount)]
print "ERROR: %s" % err
except Exception:
print "Command failed, please check the log for more info."
raise
getattr(command_object, action)()
except Exception as ex:
if options.debug:
raise
print ex
else:
common.print_actions(cmd, actions)
else:

View File

@@ -2,6 +2,7 @@ from lxml import etree
import json
from numbers import Number
from reddwarfclient import exceptions
from reddwarfclient.client import ReddwarfHTTPClient
@@ -196,7 +197,10 @@ class ReddwarfXmlClient(ReddwarfHTTPClient):
# The root XML element always becomes a dictionary with a single
# field, which has the same key as the elements name.
result = {}
root_element = etree.XML(body_string)
try:
root_element = etree.XML(body_string)
except etree.XMLSyntaxError:
raise exceptions.ResponseFormatError()
root_name = normalize_tag(root_element)
root_value, links = root_element_to_json(root_name, root_element)
result = { root_name:root_value }