Commit 8aa5d91e authored by Stavros Sachtouris's avatar Stavros Sachtouris

Enrich CLI errors in "cmds.cyclades"

Refs #21
parent faef81f7
......@@ -41,7 +41,8 @@ from kamaki.cli import command
from kamaki.cli.cmdtree import CommandTree
from kamaki.cli.utils import remove_from_items, filter_dicts_by_dict
from kamaki.cli.errors import raiseCLIError, CLISyntaxError, CLIInvalidArgument
from kamaki.clients.cyclades import CycladesComputeClient
from kamaki.clients.cyclades import (
CycladesComputeClient, ClientError, CycladesNetworkClient)
from kamaki.cli.argument import (
FlagArgument, ValueArgument, KeyValueArgument, RepeatableArgument,
DateArgument, IntArgument, StatusArgument)
......@@ -82,6 +83,10 @@ class _CycladesInit(CommandInit):
def _run(self):
self.client = self.get_client(CycladesComputeClient, 'cyclades')
@errors.Cyclades.flavor_id
def _flavor_exists(self, flavor_id):
self.client.get_flavor_details(flavor_id=flavor_id)
@fall_back
def _restruct_server_info(self, vm):
if not vm:
......@@ -124,7 +129,8 @@ class server_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
arguments = dict(
detail=FlagArgument('show detailed output', ('-l', '--details')),
since=DateArgument(
'show only items since date (\' d/m/Y H:M:S \')',
'show only items modified since date (\'H:M:S YYYY-mm-dd\') '
'Can look back up to a limit (POLL_TIME) defined on service-side',
'--since'),
limit=IntArgument(
'limit number of listed virtual servers', ('-n', '--number')),
......@@ -196,7 +202,8 @@ class server_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
self['status'] or self['user_id'] or self['user_name'])
detail = self['detail'] or (
withimage or withflavor or withmeta or withcommons)
servers = self.client.list_servers(detail, self['since'])
ch_since = self.arguments['since'].isoformat if self['since'] else None
servers = self.client.list_servers(detail, ch_since)
servers = self._filter_by_name(servers)
servers = self._filter_by_id(servers)
......@@ -209,7 +216,6 @@ class server_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
servers = self._filter_by_metadata(servers)
if detail and self['detail']:
# servers = [self._restruct_server_info(vm) for vm in servers]
pass
else:
for srv in servers:
......@@ -420,8 +426,7 @@ class server_create(_CycladesInit, OptionalOutput, _ServerWait):
required = ('server_name', 'flavor_id', 'image_id')
@errors.Cyclades.cluster_size
def _create_cluster(
self, prefix, flavor_id, image_id, size):
def _create_cluster(self, prefix, flavor_id, image_id, size):
networks = self['network_configuration'] or (
[] if self['no_network'] else None)
servers = [dict(
......@@ -455,20 +460,70 @@ class server_create(_CycladesInit, OptionalOutput, _ServerWait):
finally:
raise e
def _get_network_client(self):
network = getattr(self, '_network_client', None)
if not network:
net_URL = self.astakos.get_endpoint_url(
CycladesNetworkClient.service_type)
network = CycladesNetworkClient(net_URL, self.client.token)
self._network_client = network
return network
@errors.Image.id
def _image_exists(self, image_id):
self.client.get_image_details(image_id)
@errors.Cyclades.network_id
def _network_exists(self, network_id):
network = self._get_network_client()
network.get_network_details(network_id)
def _ip_ready(self, ip, network_id, cerror):
network = self._get_network_client()
ips = [fip for fip in network.list_floatingips() if (
fip['floating_ip_address'] == ip)]
if not ips:
msg = 'IP %s not available for current user' % ip
raiseCLIError(cerror, details=[msg] + errors.Cyclades.about_ips)
ipnet, ipvm = ips[0]['floating_network_id'], ips[0]['instance_id']
if getattr(cerror, 'status', 0) in (409, ):
msg = ''
if ipnet != network_id:
msg = 'IP %s belong to network %s, not %s' % (
ip, ipnet, network_id)
elif ipvm:
msg = 'IP %s is already used by device %s' % (ip, ipvm)
if msg:
raiseCLIError(cerror, details=[
msg,
'To get details on IP',
' kamaki ip info %s' % ip] + errors.Cyclades.about_ips)
@errors.Generic.all
@errors.Cyclades.connection
@errors.Image.id
@errors.Cyclades.flavor_id
def _run(self, name, flavor_id, image_id):
for r in self._create_cluster(
name, flavor_id, image_id, size=self['cluster_size'] or 1):
if not r:
self.error('Create %s: server response was %s' % (name, r))
continue
self.print_(r, self.print_dict)
if self['wait']:
self.wait(r['id'], r['status'] or 'BUILD')
self.writeln(' ')
def _run(self):
try:
for r in self._create_cluster(
self['server_name'], self['flavor_id'], self['image_id'],
size=self['cluster_size'] or 1):
if not r:
self.error('Create %s: server response was %s' % (
self['server_name'], r))
continue
self.print_(r, self.print_dict)
if self['wait']:
self.wait(r['id'], r['status'] or 'BUILD')
self.writeln(' ')
except ClientError as ce:
if ce.status in (404, 400):
self._flavor_exists(flavor_id=self['flavor_id'])
self._image_exists(image_id=self['image_id'])
if ce.status in (404, 400, 409):
for net in self['network_configuration']:
self._network_exists(network_id=net['uuid'])
if 'fixed_ip' in net:
self._ip_ready(net['fixed_ip'], net['uuid'], ce)
raise
def main(self):
super(self.__class__, self)._run()
......@@ -478,10 +533,7 @@ class server_create(_CycladesInit, OptionalOutput, _ServerWait):
'Arguments %s and %s are mutually exclusive' % (
self.arguments['no_network'].lvalue,
self.arguments['network_configuration'].lvalue)])
self._run(
name=self['server_name'],
flavor_id=self['flavor_id'],
image_id=self['image_id'])
self._run()
class FirewallProfileArgument(ValueArgument):
......@@ -546,19 +598,20 @@ class server_modify(_CycladesInit):
'Multiple public connections on server %s' % (
server_id), details=port_strings + [
'To select one:',
' %s <port id>' % pick_port.lvalue,
' %s PORT_ID' % pick_port.lvalue,
'To set all:',
' %s *' % pick_port.lvalue, ])
if not ports:
pp = pick_port.value
raiseCLIError(
'No *public* networks attached on server %s%s' % (
'No public networks attached on server %s%s' % (
server_id, ' through port %s' % pp if pp else ''),
details=[
'To see all networks:',
' kamaki network list',
'To see all networks:', ' kamaki network list',
'To see all connections:',
' kamaki server info %s --nics' % server_id,
'To connect to a network:',
' kamaki network connect <net id> --device-id %s' % (
' kamaki network connect NETWORK_ID --device-id %s' % (
server_id)])
for port in ports:
self.error('Set port %s firewall to %s' % (
......@@ -568,6 +621,15 @@ class server_modify(_CycladesInit):
profile=self['firewall_profile'],
port_id=port['id'])
def _server_is_stopped(self, server_id, cerror):
vm = self.client.get_server_details(server_id)
if vm['status'].lower() not in ('stopped'):
raiseCLIError(cerror, details=[
'To resize a virtual server, it must be STOPPED',
'Server %s status is %s' % (server_id, vm['status']),
'To stop the server',
' kamaki server shutdown %s -w' % server_id])
@errors.Generic.all
@errors.Cyclades.connection
@errors.Cyclades.server_id
......@@ -575,7 +637,14 @@ class server_modify(_CycladesInit):
if self['server_name'] is not None:
self.client.update_server_name((server_id), self['server_name'])
if self['flavor_id']:
self.client.resize_server(server_id, self['flavor_id'])
try:
self.client.resize_server(server_id, self['flavor_id'])
except ClientError as ce:
if ce.status in (404, ):
self._flavor_exists(flavor_id=self['flavor_id'])
if ce.status in (400, ):
self._server_is_stopped(server_id, ce)
raise
if self['firewall_profile']:
self._set_firewall_profile(server_id)
if self['metadata_to_set']:
......@@ -623,8 +692,9 @@ class server_delete(_CycladesInit, _ServerWait):
arguments = dict(
wait=FlagArgument('Wait server to be destroyed', ('-w', '--wait')),
cluster=FlagArgument(
'(DANGEROUS) Delete all virtual servers prefixed with the cluster '
'prefix. In that case, the prefix replaces the server id',
'(DANGEROUS) Delete all VMs with names starting with the cluster '
'prefix. Do not use it if unsure. Syntax:'
' kamaki server delete --cluster CLUSTER_PREFIX',
'--cluster')
)
......@@ -633,23 +703,29 @@ class server_delete(_CycladesInit, _ServerWait):
return [s['id'] for s in self.client.list_servers() if (
s['name'].startswith(server_var))]
@errors.Cyclades.server_id
def _check_server_id(self, server_id):
return server_id
return [server_var, ]
@errors.Cyclades.server_id
def _delete_server(self, server_id):
if self['wait']:
details = self.client.get_server_details(server_id)
status = details['status']
return [_check_server_id(self, server_id=server_var), ]
self.client.delete_server(server_id)
if self['wait']:
self.wait(server_id, status)
@errors.Generic.all
@errors.Cyclades.connection
def _run(self, server_var):
deleted_vms = []
for server_id in self._server_ids(server_var):
if self['wait']:
details = self.client.get_server_details(server_id)
status = details['status']
self.client.delete_server(server_id)
if self['wait']:
self.wait(server_id, status)
self._delete_server(server_id=server_id)
deleted_vms.append(server_id)
if self['cluster']:
dlen = len(deleted_vms)
self.error('%s virtual server%s deleted' % (
dlen, '' if dlen == 1 else 's'))
def main(self, server_id_or_cluster_prefix):
super(self.__class__, self)._run()
......@@ -761,8 +837,7 @@ class server_console(_CycladesInit, OptionalOutput):
@errors.Cyclades.server_id
def _run(self, server_id):
self.error('The following credentials will be invalidated shortly')
self.print_(
self.client.get_server_console(server_id), self.print_dict)
self.print_(self.client.get_server_console(server_id), self.print_dict)
def main(self, server_id):
super(self.__class__, self)._run()
......@@ -851,10 +926,7 @@ class flavor_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
for key in set(flv).difference(['id', 'name']):
flv.pop(key)
kwargs = dict(out=StringIO(), title=()) if self['more'] else {}
self.print_(
flavors,
with_redundancy=self['detail'], with_enumeration=self['enum'],
**kwargs)
self.print_(flavors, with_enumeration=self['enum'], **kwargs)
if self['more']:
pager(kwargs['out'].getvalue())
......@@ -865,16 +937,13 @@ class flavor_list(_CycladesInit, OptionalOutput, NameFilter, IDFilter):
@command(flavor_cmds)
class flavor_info(_CycladesInit, OptionalOutput):
"""Detailed information on a hardware flavor
To get a list of available flavors and flavor ids, try /flavor list
"""
"""Detailed information on a hardware flavor"""
@errors.Generic.all
@errors.Cyclades.connection
@errors.Cyclades.flavor_id
def _run(self, flavor_id):
self.print_(
self.client.get_flavor_details(int(flavor_id)), self.print_dict)
self.print_(self.client.get_flavor_details(flavor_id), self.print_dict)
def main(self, flavor_id):
super(self.__class__, self)._run()
......
......@@ -77,13 +77,12 @@ class Generic(object):
ce_msg = ('%s' % ce).lower()
if ce.status == 401:
raise CLIError('Authorization failed', details=[
'Make sure a valid token is provided:',
' # to check if token is valid',
' $ kamaki user authenticate',
' # to set token:',
' $ kamaki config set cloud.default.token <token>',
' # to get current token:',
' $ kamaki config get cloud.default.token',
'To check if token is valid',
' kamaki user authenticate',
'To set token:',
' kamaki config set cloud.default.token <token>',
'To get current token:',
' kamaki config get cloud.default.token',
'%s' % ce,
] + CLOUDNAME)
elif ce.status in range(-12, 200) + [302, 401, 500]:
......@@ -96,10 +95,10 @@ class Generic(object):
raise CLIError('Invalid service URL %s' % url, details=[
'%s' % ce,
'Check if authentication URL is correct',
' # check current URL',
' $ kamaki config get cloud.default.url',
' # set new authentication URL',
' $ kamaki config set cloud.default.url'] + CLOUDNAME)
'To check current URL',
' kamaki config get cloud.default.url',
'To set new authentication URL',
' kamaki config set cloud.default.url'] + CLOUDNAME)
raise
return _raise
......@@ -202,22 +201,16 @@ class History(object):
class Cyclades(object):
about_flavor_id = [
'How to pick a valid flavor id:',
' # get a list of flavor ids',
' $ kamaki flavor list',
' # details of flavor',
' $ kamaki flavor info <flavor id>',
'',
]
'To get a list of flavors', ' kamaki flavor list',
'More details on a flavor', ' kamaki flavor info FLAVOR_ID', ]
about_network_id = [
'How to pick a valid network id:',
' # get a list of network ids',
' $ kamaki network list',
' # details of network',
' $ kamaki network info <network id>',
'',
]
'To get a list of networks', ' kamaki network list',
'More details on a network', ' kamaki network info NETWORK_ID', ]
about_ips = [
'To list available IPs', ' kamaki ip list',
'To reserve a new IP', ' kamaki ip create', ]
net_types = ('CUSTOM', 'MAC_FILTERED', 'IP_LESS_ROUTED', 'PHYSICAL_VLAN')
......@@ -231,11 +224,11 @@ class Cyclades(object):
try:
return func(self, *args, **kwargs)
except ClientError as ce:
if ce.status == 400 and 'changes-since' in ('%s' % ce):
raise CLIError(
'Incorrect date format for --since',
details=['Accepted date format: d/m/y'])
raise
if ce.status in (304, ):
log.debug('%s %s' % (ce.status, ce))
self.error('No servers have been modified since')
else:
raise
return _raise
@classmethod
......@@ -263,21 +256,21 @@ class Cyclades(object):
def _raise(self, *args, **kwargs):
network_id = kwargs.get('network_id', None)
try:
network_id = int(network_id)
return func(self, *args, **kwargs)
except ValueError as ve:
raise CLIError(
'Invalid network id %s ' % network_id,
details=[
'network id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce:
if network_id and ce.status == 404 and (
'network' in ('%s' % ce).lower()
):
if network_id and ce.status in (404, 400):
msg = ''
if ce.status in (400, ):
try:
network_id = int(network_id)
except ValueError:
msg = 'Network ID should be a positive integer'
log.debug(msg)
raise CLIError(
'No network with id %s found' % network_id,
details=this.about_network_id + ['%s' % ce])
importance=2,
details=[msg, ] + this.about_network_id + [
'%s %s' % (getattr(ce, 'status', ''), ce)])
raise
return _raise
......@@ -296,97 +289,42 @@ class Cyclades(object):
def _raise(self, *args, **kwargs):
flavor_id = kwargs.get('flavor_id', None)
try:
flavor_id = int(flavor_id)
return func(self, *args, **kwargs)
except ValueError as ve:
raise CLIError(
'Invalid flavor id %s ' % flavor_id,
details=[
'Flavor id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce:
if flavor_id and ce.status == 404 and (
'flavor' in ('%s' % ce).lower()
):
if ce.status in (404, 400):
details = this.about_flavor_id
if ce.status in (400, ):
try:
flavor_id = int(flavor_id)
except ValueError:
details.insert(
0, 'Flavor ID should be a positive integer')
raise CLIError(
'No flavor with id %s found' % flavor_id,
details=this.about_flavor_id + ['%s' % ce, ])
'No flavor with ID %s' % flavor_id,
importance=2, details=details + [
'%s %s' % (getattr(ce, 'status', ''), ce)])
raise
return _raise
@classmethod
def server_id(this, func):
def _raise(self, *args, **kwargs):
server_id = kwargs.get('server_id', None)
try:
server_id = int(server_id)
return func(self, *args, **kwargs)
except ValueError as ve:
raise CLIError(
'Invalid virtual server id %s' % server_id,
details=[
'Server id must be a positive integer', '%s' % ve],
importance=1)
except ClientError as ce:
err_msg = ('%s' % ce).lower()
if (
ce.status == 404 and 'server' in err_msg
) or (
ce.status == 400 and 'not found' in err_msg
):
raise CLIError(
'virtual server with id %s not found' % server_id,
details=[
'# to get ids of all servers',
'$ kamaki server list',
'# to get server details',
'$ kamaki server info <server id>',
'%s' % ce])
raise
return _raise
@classmethod
def firewall(this, func):
def _raise(self, *args, **kwargs):
profile = kwargs.get('profile', None)
try:
return func(self, *args, **kwargs)
except ClientError as ce:
if ce.status == 400 and profile and (
'firewall' in ('%s' % ce).lower()
):
if ce.status in (404, 400):
server_id = kwargs.get('server_id', None)
details = [
'to get a list of all servers', ' kamaki server list']
if ce.status in (404, ):
try:
server_id = int(server_id)
except ValueError:
details.insert(0, 'Server ID must be an integer')
raise CLIError(
'%s is an invalid firewall profile term' % profile,
details=[
'Try one of the following:',
'* DISABLED: Shutdown firewall',
'* ENABLED: Firewall in normal mode',
'* PROTECTED: Firewall in secure mode',
'%s' % ce])
raise
return _raise
@classmethod
def nic_id(this, func):
def _raise(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except ClientError as ce:
nic_id = kwargs.get('nic_id', None)
if nic_id and ce.status == 404 and (
'network interface' in ('%s' % ce).lower()
):
server_id = kwargs.get('server_id', '<no server>')
err_msg = 'No nic %s on virtual server with id %s' % (
nic_id,
server_id)
raise CLIError(err_msg, details=[
'* check v. server with id %s: /server info %s' % (
server_id,
server_id),
'* list nics for v. server with id %s:' % server_id,
' /server addr %s' % server_id,
'%s' % ce])
'No servers with ID %s' % server_id,
importance=2, details=details + [
'%s %s' % (getattr(ce, 'status', ''), ce)])
raise
return _raise
......@@ -398,24 +336,18 @@ class Cyclades(object):
func(self, *args, **kwargs)
except ClientError as ce:
if key and ce.status == 404 and (
'metadata' in ('%s' % ce).lower()
):
raise CLIError(
'No virtual server metadata with key %s' % key,
details=['%s' % ce, ])
'metadata' in ('%s' % ce).lower()):
raise CLIError(
'No virtual server metadata with key %s' % key,
details=['%s %s' % (getattr(ce, 'status', ''), ce), ])
raise
return _raise
class Image(object):
about_image_id = [
'How to pick a suitable image:',
' # get a list of image ids',
' $ kamaki image list',
' # details of an image',
' $ kamaki image info <image id>',
'',
]
'To list all images', ' kamaki image list',
'To get image details', ' kamaki image info IMAGE_ID', ]
@classmethod
def connection(this, func):
......@@ -428,10 +360,10 @@ class Image(object):
try:
func(self, *args, **kwargs)
except ClientError as ce:
if image_id or (ce.status in (404, 400) and (
'image not found' in ('%s' % ce).lower())):
if image_id and ce.status in (404, 400):
raise CLIError(
'No image with id %s found' % image_id,
importance=2,
details=this.about_image_id + ['%s' % ce])
raise
return _raise
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment