Commit 2f68a898 authored by Stavros Sachtouris's avatar Stavros Sachtouris
Browse files

Merge branch 'hotfix-0.12.2'

parents 2320de66 f557e944
CHANGELOG for hotfix version 0.12.2
- Always use "details" call in neworks/subnets list
- Restore server console, remove --vnc option from server info
- Fix server create --networks|--no-networks
- Use --status in * wait commands
- Fix typos in documentation
CHANGELOG for hotfix version 0.12.1
- Various minor typos
......
......@@ -211,6 +211,7 @@ server (Compute/Cyclades)
start Start an existing virtual server
shutdown Shutdown an active virtual server
delete Delete a virtual server
console Create a VMC console and show connection information
wait Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]
Showcase: Create a server
......
......@@ -225,6 +225,7 @@ server
* start Start an existing virtual server
* shutdown Shutdown an active virtual server
* delete Delete a virtual server
* console Create a VMC console and show connection information
* wait Wait for server to finish [BUILD, STOPPED, REBOOT, ACTIVE]
flavor
......
......@@ -32,7 +32,8 @@
# or implied, of GRNET S.A.
from kamaki.cli.config import Config
from kamaki.cli.errors import CLISyntaxError, raiseCLIError
from kamaki.cli.errors import (
CLISyntaxError, raiseCLIError, CLIInvalidArgument)
from kamaki.cli.utils import split_input, to_bytes
from datetime import datetime as dtm
......@@ -375,6 +376,30 @@ class KeyValueArgument(Argument):
raiseCLIError(e, 'KeyValueArgument Syntax Error')
class StatusArgument(ValueArgument):
"""Initialize with valid_states=['list', 'of', 'states']
First state is the default"""
def __init__(self, *args, **kwargs):
self.valid_states = kwargs.pop('valid_states', ['BUILD', ])
super(StatusArgument, self).__init__(*args, **kwargs)
@property
def value(self):
return getattr(self, '_value', None)
@value.setter
def value(self, new_status):
if new_status:
new_status = new_status.upper()
if new_status not in self.valid_states:
raise CLIInvalidArgument(
'Invalid argument %s' % new_status, details=[
'Usage: '
'%s=[%s]' % (self.lvalue, '|'.join(self.valid_states))])
self._value = new_status
class ProgressBarArgument(FlagArgument):
"""Manage a progress bar"""
......
......@@ -44,7 +44,7 @@ from kamaki.cli.errors import (
from kamaki.clients.cyclades import CycladesClient
from kamaki.cli.argument import (
FlagArgument, ValueArgument, KeyValueArgument, RepeatableArgument,
ProgressBarArgument, DateArgument, IntArgument)
ProgressBarArgument, DateArgument, IntArgument, StatusArgument)
from kamaki.cli.commands import _command_init, errors, addLogSettings
from kamaki.cli.commands import (
_optional_output_cmd, _optional_json, _name_filter, _id_filter)
......@@ -69,6 +69,8 @@ howto_personality = [
' [mode=]MODE: permission in octal (e.g., 0777)',
'e.g., -p /tmp/my.file,owner=root,mode=0777']
server_states = ('BUILD', 'ACTIVE', 'STOPPED', 'REBOOT')
class _service_wait(object):
......@@ -264,33 +266,31 @@ class server_info(_init_cyclades, _optional_json):
"""Detailed information on a Virtual Machine"""
arguments = dict(
addr=FlagArgument(
nics=FlagArgument(
'Show only the network interfaces of this virtual server',
'--nics'),
vnc=FlagArgument(
'Show VNC connection information (valid for a short period)',
'--vnc-credentials'),
stats=FlagArgument('Get URLs for server statistics', '--stats')
network_id=ValueArgument(
'Show the connection details to that network', '--network-id'),
stats=FlagArgument('Get URLs for server statistics', '--stats'),
diagnostics=FlagArgument('Diagnostic information', '--diagnostics')
)
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, server_id):
vm = self.client.get_server_details(server_id)
if self['addr']:
self._print(vm.get('attachments', []))
elif self['vnc']:
self.error(
'(!) For security reasons, the following credentials are '
'invalidated\nafter a short time period, depending on the '
'server settings\n')
if self['nics']:
self._print(
self.client.get_server_console(server_id), self.print_dict)
self.client.get_server_nics(server_id), self.print_dict)
elif self['network_id']:
self._print(
self.client.get_server_network_nics(
server_id, self['network_id']), self.print_dict)
elif self['stats']:
self._print(
self.client.get_server_stats(server_id), self.print_dict)
else:
vm = self.client.get_server_details(server_id)
uuids = self._uuids2usernames([vm['user_id'], vm['tenant_id']])
vm['user_id'] += ' (%s)' % uuids[vm['user_id']]
vm['tenant_id'] += ' (%s)' % uuids[vm['tenant_id']]
......@@ -298,7 +298,7 @@ class server_info(_init_cyclades, _optional_json):
def main(self, server_id):
super(self.__class__, self)._run()
choose_one = ('addr', 'vnc', 'stats')
choose_one = ('nics', 'stats', 'diagnostics')
count = len([a for a in choose_one if self[a]])
if count > 1:
raise CLIInvalidArgument('Invalid argument compination', details=[
......@@ -423,8 +423,8 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
arguments = dict(
server_name=ValueArgument('The name of the new server', '--name'),
flavor_id=IntArgument('The ID of the hardware flavor', '--flavor-id'),
image_id=ValueArgument('The ID of the hardware image', '--image-id'),
flavor_id=IntArgument('The ID of the flavor', '--flavor-id'),
image_id=ValueArgument('The ID of the image', '--image-id'),
personality=PersonalityArgument(
(80 * ' ').join(howto_personality), ('-p', '--personality')),
wait=FlagArgument('Wait server to build', ('-w', '--wait')),
......@@ -445,8 +445,8 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
'Do not create any network NICs on the server. . '
'Mutually exclusive to --network . '
'If neither --network or --no-network are used, the default '
'network policy is applied. This policy is configured on the '
'cloud and kamaki is oblivious to it',
'network policy is applied. These policies are set on the cloud, '
'so kamaki is oblivious to them',
'--no-network')
)
required = ('server_name', 'flavor_id', 'image_id')
......@@ -454,7 +454,7 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
@errors.cyclades.cluster_size
def _create_cluster(self, prefix, flavor_id, image_id, size):
networks = self['network_configuration'] or (
None if self['no_network'] else [])
[] if self['no_network'] else None)
servers = [dict(
name='%s%s' % (prefix, i if size > 1 else ''),
flavor_id=flavor_id,
......@@ -506,12 +506,12 @@ class server_create(_init_cyclades, _optional_json, _server_wait):
def main(self):
super(self.__class__, self)._run()
if self['no_network'] and self['network']:
if self['no_network'] and self['network_configuration']:
raise CLIInvalidArgument(
'Invalid argument compination', importance=2, details=[
'Arguments %s and %s are mutually exclusive' % (
self.arguments['no_network'].lvalue,
self.arguments['network'].lvalue)])
self.arguments['network_configuration'].lvalue)])
self._run(
name=self['server_name'],
flavor_id=self['flavor_id'],
......@@ -728,7 +728,7 @@ class server_shutdown(_init_cyclades, _optional_output_cmd, _server_wait):
@command(server_cmds)
class server_addr(_init_cyclades):
class server_nics(_init_cyclades):
"""DEPRECATED, use: [kamaki] server info SERVER_ID --nics"""
def main(self, *args):
......@@ -739,12 +739,19 @@ class server_addr(_init_cyclades):
@command(server_cmds)
class server_console(_init_cyclades, _optional_json):
"""DEPRECATED, use: [kamaki] server info SERVER_ID --vnc-credentials"""
"""Create a VMC console and show connection information"""
def main(self, *args):
raiseCLIError('DEPRECATED since v0.12', importance=3, details=[
'Replaced by',
' [kamaki] server info <SERVER_ID> --vnc-credentials'])
@errors.generic.all
@errors.cyclades.connection
@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)
def main(self, server_id):
super(self.__class__, self)._run()
self._run(server_id=server_id)
@command(server_cmds)
......@@ -769,11 +776,16 @@ class server_stats(_init_cyclades, _optional_json):
@command(server_cmds)
class server_wait(_init_cyclades, _server_wait):
"""Wait for server to finish (BUILD, STOPPED, REBOOT, ACTIVE)"""
"""Wait for server to change its status (default: BUILD)"""
arguments = dict(
timeout=IntArgument(
'Wait limit in seconds (default: 60)', '--timeout', default=60)
'Wait limit in seconds (default: 60)', '--timeout', default=60),
server_status=StatusArgument(
'Status to wait for (%s, default: %s)' % (
', '.join(server_states), server_states[0]),
'--status',
valid_states=server_states)
)
@errors.generic.all
......@@ -789,9 +801,9 @@ class server_wait(_init_cyclades, _server_wait):
'status is already %s' % (
server_id, current_status, r['status']))
def main(self, server_id, current_status='BUILD'):
def main(self, server_id):
super(self.__class__, self)._run()
self._run(server_id=server_id, current_status=current_status)
self._run(server_id=server_id, current_status=self['server_status'])
@command(flavor_cmds)
......
......@@ -40,7 +40,8 @@ from kamaki.cli.errors import (
CLIBaseUrlError, CLIInvalidArgument, raiseCLIError)
from kamaki.clients.cyclades import CycladesNetworkClient, ClientError
from kamaki.cli.argument import (
FlagArgument, ValueArgument, RepeatableArgument, IntArgument)
FlagArgument, ValueArgument, RepeatableArgument, IntArgument,
StatusArgument)
from kamaki.cli.commands import _command_init, errors, addLogSettings
from kamaki.cli.commands import (
_optional_output_cmd, _optional_json, _name_filter, _id_filter)
......@@ -59,6 +60,8 @@ about_authentication = '\nUser Authentication:\
\n to set authentication token: \
[kamaki] config set cloud.<CLOUD>.token <TOKEN>'
port_states = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')
class _port_wait(_service_wait):
......@@ -119,15 +122,19 @@ class network_list(_init_network, _optional_json, _name_filter, _id_filter):
@errors.generic.all
@errors.cyclades.connection
def _run(self):
detail = bool(self['detail'] or self['user_id'])
nets = self.client.list_networks(detail=detail)
nets = self.client.list_networks(detail=True)
nets = self._filter_by_user_id(nets)
nets = self._filter_by_name(nets)
nets = self._filter_by_id(nets)
if detail and not self['detail']:
if not self['detail']:
nets = [dict(
id=n['id'], name=n['name'], links=n['links']) for n in nets]
kwargs = dict()
_0_id=n['id'],
_1_name=n['name'],
_2_public='( %s )' % 'public' if (
n.get('public', None)) else 'private') for n in nets]
kwargs = dict(title=('_0_id', '_1_name', '_2_public'))
else:
kwargs = dict()
if self['more']:
kwargs['out'] = StringIO()
kwargs['title'] = ()
......@@ -256,8 +263,12 @@ class subnet_list(_init_network, _optional_json, _name_filter, _id_filter):
nets = self._filter_by_id(nets)
if not self['detail']:
nets = [dict(
id=n['id'], name=n['name'], links=n['links']) for n in nets]
kwargs = dict()
_0_id=n['id'],
_1_name=n['name'],
_2_net='( of network %s )' % n['network_id']) for n in nets]
kwargs = dict(title=('_0_id', '_1_name', '_2_net'))
else:
kwargs = dict()
if self['more']:
kwargs['out'] = StringIO()
kwargs['title'] = ()
......@@ -476,25 +487,6 @@ class port_modify(_init_network, _optional_json):
self._run(port_id=port_id)
class PortStatusArgument(ValueArgument):
valid = ('BUILD', 'ACTIVE', 'DOWN', 'ERROR')
@property
def value(self):
return getattr(self, '_value', None)
@value.setter
def value(self, new_status):
if new_status:
new_status = new_status.upper()
if new_status in self.valid:
raise CLIInvalidArgument(
'Invalid argument %s' % new_status, details=[
'Status valid values: %s'] % ', '.join(self.valid))
self._value = new_status
class _port_create(_init_network, _optional_json, _port_wait):
def connect(self, network_id, device_id):
......@@ -549,32 +541,34 @@ class port_create(_port_create):
@command(port_cmds)
class port_wait(_init_network, _port_wait):
"""Wait for port to finish [ACTIVE, DOWN, BUILD, ERROR]"""
"""Wait for port to finish (default: BUILD)"""
arguments = dict(
current_status=PortStatusArgument(
'Wait while in this status', '--status'),
port_status=StatusArgument(
'Wait while in this status (%s, default: %s)' % (
', '.join(port_states), port_states[0]),
'--status',
valid_states=port_states),
timeout=IntArgument(
'Wait limit in seconds (default: 60)', '--timeout', default=60)
)
@errors.generic.all
@errors.cyclades.connection
def _run(self, port_id, current_status):
def _run(self, port_id, port_status):
port = self.client.get_port_details(port_id)
if port['status'].lower() == current_status.lower():
self._wait(port_id, current_status, timeout=self['timeout'])
if port['status'].lower() == port_status.lower():
self._wait(port_id, port_status, timeout=self['timeout'])
else:
self.error(
'Port %s: Cannot wait for status %s, '
'status is already %s' % (
port_id, current_status, port['status']))
port_id, port_status, port['status']))
def main(self, port_id):
super(self.__class__, self)._run()
current_status = self['current_status'] or self.arguments[
'current_status'].valid[0]
self._run(port_id=port_id, current_status=current_status)
port_status = self['port_status'] or port_states[0]
self._run(port_id=port_id, port_status=port_status)
@command(ip_cmds)
......@@ -662,29 +656,29 @@ class ip_attach(_port_create):
@errors.generic.all
@errors.cyclades.connection
@errors.cyclades.server_id
def _run(self, ip_address, server_id):
def _run(self, ip_or_ip_id, server_id):
netid = None
for ip in self.client.list_floatingips():
if ip['floating_ip_address'] == ip_address:
if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
netid = ip['floating_network_id']
iparg = ValueArgument(parsed_name='--ip')
iparg.value = ip_address
iparg.value = ip['floating_ip_address']
self.arguments['ip_address'] = iparg
break
if netid:
self.error('Creating a port to attach IP %s to server %s' % (
ip_address, server_id))
ip_or_ip_id, server_id))
self.connect(netid, server_id)
else:
raiseCLIError(
'IP address %s does not match any reserved IPs' % ip_address,
'%s does not match any reserved IPs or IP ids' % ip_or_ip_id,
details=[
'To reserve an IP:', ' [kamaki] ip create',
'To see all reserved IPs:', ' [kamaki] ip list'])
def main(self, ip_address):
def main(self, ip_or_ip_id):
super(self.__class__, self)._run()
self._run(ip_address=ip_address, server_id=self['server_id'])
self._run(ip_or_ip_id=ip_or_ip_id, server_id=self['server_id'])
@command(ip_cmds)
......@@ -697,11 +691,11 @@ class ip_detach(_init_network, _port_wait, _optional_json):
@errors.generic.all
@errors.cyclades.connection
def _run(self, ip_address):
def _run(self, ip_or_ip_id):
for ip in self.client.list_floatingips():
if ip['floating_ip_address'] == ip_address:
if ip_or_ip_id in (ip['floating_ip_address'], ip['id']):
if not ip['port_id']:
raiseCLIError('IP %s is not attached' % ip_address)
raiseCLIError('IP %s is not attached' % ip_or_ip_id)
self.error('Deleting port %s:' % ip['port_id'])
self.client.delete_port(ip['port_id'])
if self['wait']:
......@@ -714,11 +708,11 @@ class ip_detach(_init_network, _port_wait, _optional_json):
raise
self.error('Port %s is deleted' % ip['port_id'])
return
raiseCLIError('IP %s not found' % ip_address)
raiseCLIError('IP or IP id %s not found' % ip_or_ip_id)
def main(self, ip_address):
def main(self, ip_or_ip_id):
super(self.__class__, self)._run()
self._run(ip_address)
self._run(ip_or_ip_id)
# Warn users for some importand changes
......
......@@ -51,7 +51,7 @@ class History(object):
return True
def get(self, match_terms=None, limit=0):
limit = int(limit) or 0
limit = int(limit or 0)
with codecs.open(self.filepath, mode='r', encoding='utf-8') as f:
result = [u'%s. \t%s' % (
i + 1, line) for i, line in enumerate(f.readlines())
......
......@@ -133,8 +133,7 @@ class ComputeClient(ComputeRestClient):
{"uuid": <network_uuid>},
{"uuid": <network_uuid>, "fixed_ip": address},
{"port": <port_id>}, ...]
ATTENTION: Empty list is different to None. None means ' do not
mention it', empty list means 'automatically get an ip'
ATTENTION: Empty list is different to None.
:returns: a dict with the new virtual server details
......@@ -150,7 +149,7 @@ class ComputeClient(ComputeRestClient):
req['server']['personality'] = personality
if networks is not None:
req['server']['networks'] = networks or []
req['server']['networks'] = networks
r = self.servers_post(
json_data=req,
......@@ -282,6 +281,16 @@ class ComputeClient(ComputeRestClient):
r = self.servers_metadata_delete(server_id, key)
return r.headers
def get_server_nics(self, server_id, changes_since=None):
r = self.servers_ips_get(server_id, changes_since=changes_since)
return r.json
def get_server_network_nics(
self, server_id, network_id, changes_since=None):
r = self.servers_ips_get(
server_id, network_id=network_id, changes_since=changes_since)
return r.json['network']
def list_flavors(self, detail=False, response_headers=dict(
previous=None, next=None)):
r = self.flavors_get(detail=bool(detail))
......@@ -375,6 +384,8 @@ class ComputeClient(ComputeRestClient):
r = self.images_metadata_delete(image_id, key)
return r.headers
# Extensions
def get_floating_ip_pools(self, tenant_id):
"""
:param tenant_id: (str)
......
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