6b3583ab06
The triggering crash dump feature is supported by nova [1] and novaclient [2] now, it's time to introduce this feature into OSC correspondingly. [1]The change id is: I6ed777ff637254b4b79417008f9055dd19fc7405 [2]The change id is: If03b1864bbe7074c720b946fc2700bd5d07debc3 Change-Id: I5a411f283fdf0fc3c00380d069848a332c799cdd Closes-Bug: #1538372 Co-Authored-By: Tang Chen <chen.tang@easystack.cn>
1743 lines
55 KiB
Python
1743 lines
55 KiB
Python
# Copyright 2012-2013 OpenStack Foundation
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
"""Compute v2 Server action implementations"""
|
|
|
|
import argparse
|
|
import getpass
|
|
import io
|
|
import os
|
|
import six
|
|
import sys
|
|
|
|
from openstackclient.common import command
|
|
|
|
try:
|
|
from novaclient.v2 import servers
|
|
except ImportError:
|
|
from novaclient.v1_1 import servers
|
|
|
|
from openstackclient.common import exceptions
|
|
from openstackclient.common import parseractions
|
|
from openstackclient.common import utils
|
|
from openstackclient.i18n import _ # noqa
|
|
from openstackclient.identity import common as identity_common
|
|
|
|
|
|
def _format_servers_list_networks(networks):
|
|
"""Return a formatted string of a server's networks
|
|
|
|
:param networks: a Server.networks field
|
|
:rtype: a string of formatted network addresses
|
|
"""
|
|
output = []
|
|
for (network, addresses) in networks.items():
|
|
if not addresses:
|
|
continue
|
|
addresses_csv = ', '.join(addresses)
|
|
group = "%s=%s" % (network, addresses_csv)
|
|
output.append(group)
|
|
return '; '.join(output)
|
|
|
|
|
|
def _format_servers_list_power_state(state):
|
|
"""Return a formatted string of a server's power state
|
|
|
|
:param state: the power state number of a server
|
|
:rtype: a string mapped to the power state number
|
|
"""
|
|
power_states = [
|
|
'NOSTATE', # 0x00
|
|
'Running', # 0x01
|
|
'', # 0x02
|
|
'Paused', # 0x03
|
|
'Shutdown', # 0x04
|
|
'', # 0x05
|
|
'Crashed', # 0x06
|
|
'Suspended' # 0x07
|
|
]
|
|
|
|
try:
|
|
return power_states[state]
|
|
except Exception:
|
|
return 'N/A'
|
|
|
|
|
|
def _get_ip_address(addresses, address_type, ip_address_family):
|
|
# Old style addresses
|
|
if address_type in addresses:
|
|
for addy in addresses[address_type]:
|
|
if int(addy['version']) in ip_address_family:
|
|
return addy['addr']
|
|
|
|
# New style addresses
|
|
new_address_type = address_type
|
|
if address_type == 'public':
|
|
new_address_type = 'floating'
|
|
if address_type == 'private':
|
|
new_address_type = 'fixed'
|
|
for network in addresses:
|
|
for addy in addresses[network]:
|
|
# Case where it is list of strings
|
|
if isinstance(addy, six.string_types):
|
|
if new_address_type == 'fixed':
|
|
return addresses[network][0]
|
|
else:
|
|
return addresses[network][-1]
|
|
# Case where it is a dict
|
|
if 'OS-EXT-IPS:type' not in addy:
|
|
continue
|
|
if addy['OS-EXT-IPS:type'] == new_address_type:
|
|
if int(addy['version']) in ip_address_family:
|
|
return addy['addr']
|
|
raise exceptions.CommandError(
|
|
"ERROR: No %s IP version %s address found" %
|
|
(address_type, ip_address_family)
|
|
)
|
|
|
|
|
|
def _prep_server_detail(compute_client, server):
|
|
"""Prepare the detailed server dict for printing
|
|
|
|
:param compute_client: a compute client instance
|
|
:param server: a Server resource
|
|
:rtype: a dict of server details
|
|
"""
|
|
info = server._info.copy()
|
|
|
|
# Call .get() to retrieve all of the server information
|
|
# as findall(name=blah) and REST /details are not the same
|
|
# and do not return flavor and image information.
|
|
server = compute_client.servers.get(info['id'])
|
|
info.update(server._info)
|
|
|
|
# Convert the image blob to a name
|
|
image_info = info.get('image', {})
|
|
if image_info:
|
|
image_id = image_info.get('id', '')
|
|
try:
|
|
image = utils.find_resource(compute_client.images, image_id)
|
|
info['image'] = "%s (%s)" % (image.name, image_id)
|
|
except Exception:
|
|
info['image'] = image_id
|
|
|
|
# Convert the flavor blob to a name
|
|
flavor_info = info.get('flavor', {})
|
|
flavor_id = flavor_info.get('id', '')
|
|
try:
|
|
flavor = utils.find_resource(compute_client.flavors, flavor_id)
|
|
info['flavor'] = "%s (%s)" % (flavor.name, flavor_id)
|
|
except Exception:
|
|
info['flavor'] = flavor_id
|
|
|
|
# NOTE(dtroyer): novaclient splits these into separate entries...
|
|
# Format addresses in a useful way
|
|
info['addresses'] = _format_servers_list_networks(server.networks)
|
|
|
|
# Map 'metadata' field to 'properties'
|
|
info.update(
|
|
{'properties': utils.format_dict(info.pop('metadata'))}
|
|
)
|
|
|
|
# Migrate tenant_id to project_id naming
|
|
if 'tenant_id' in info:
|
|
info['project_id'] = info.pop('tenant_id')
|
|
|
|
# Remove values that are long and not too useful
|
|
info.pop('links', None)
|
|
|
|
return info
|
|
|
|
|
|
def _show_progress(progress):
|
|
if progress:
|
|
sys.stdout.write('\rProgress: %s' % progress)
|
|
sys.stdout.flush()
|
|
|
|
|
|
class AddServerSecurityGroup(command.Command):
|
|
"""Add security group to server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(AddServerSecurityGroup, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'group',
|
|
metavar='<group>',
|
|
help=_('Security group to add (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
security_group = utils.find_resource(
|
|
compute_client.security_groups,
|
|
parsed_args.group,
|
|
)
|
|
|
|
server.add_security_group(security_group.name)
|
|
|
|
|
|
class AddServerVolume(command.Command):
|
|
"""Add volume to server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(AddServerVolume, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'volume',
|
|
metavar='<volume>',
|
|
help=_('Volume to add (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--device',
|
|
metavar='<device>',
|
|
help=_('Server internal device name for volume'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
volume = utils.find_resource(
|
|
volume_client.volumes,
|
|
parsed_args.volume,
|
|
)
|
|
|
|
compute_client.volumes.create_server_volume(
|
|
server.id,
|
|
volume.id,
|
|
parsed_args.device,
|
|
)
|
|
|
|
|
|
class CreateServer(command.ShowOne):
|
|
"""Create a new server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(CreateServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server_name',
|
|
metavar='<server-name>',
|
|
help=_('New server name'),
|
|
)
|
|
disk_group = parser.add_mutually_exclusive_group(
|
|
required=True,
|
|
)
|
|
disk_group.add_argument(
|
|
'--image',
|
|
metavar='<image>',
|
|
help=_('Create server from this image (name or ID)'),
|
|
)
|
|
disk_group.add_argument(
|
|
'--volume',
|
|
metavar='<volume>',
|
|
help=_('Create server from this volume (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--flavor',
|
|
metavar='<flavor>',
|
|
required=True,
|
|
help=_('Create server with this flavor (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--security-group',
|
|
metavar='<security-group-name>',
|
|
action='append',
|
|
default=[],
|
|
help=_('Security group to assign to this server (name or ID) '
|
|
'(repeat for multiple groups)'),
|
|
)
|
|
parser.add_argument(
|
|
'--key-name',
|
|
metavar='<key-name>',
|
|
help=_('Keypair to inject into this server (optional extension)'),
|
|
)
|
|
parser.add_argument(
|
|
'--property',
|
|
metavar='<key=value>',
|
|
action=parseractions.KeyValueAction,
|
|
help=_('Set a property on this server '
|
|
'(repeat for multiple values)'),
|
|
)
|
|
parser.add_argument(
|
|
'--file',
|
|
metavar='<dest-filename=source-filename>',
|
|
action='append',
|
|
default=[],
|
|
help=_('File to inject into image before boot '
|
|
'(repeat for multiple files)'),
|
|
)
|
|
parser.add_argument(
|
|
'--user-data',
|
|
metavar='<user-data>',
|
|
help=_('User data file to serve from the metadata server'),
|
|
)
|
|
parser.add_argument(
|
|
'--availability-zone',
|
|
metavar='<zone-name>',
|
|
help=_('Select an availability zone for the server'),
|
|
)
|
|
parser.add_argument(
|
|
'--block-device-mapping',
|
|
metavar='<dev-name=mapping>',
|
|
action='append',
|
|
default=[],
|
|
help=_('Map block devices; map is '
|
|
'<id>:<type>:<size(GB)>:<delete_on_terminate> '
|
|
'(optional extension)'),
|
|
)
|
|
parser.add_argument(
|
|
'--nic',
|
|
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
|
|
"port-id=port-uuid>",
|
|
action='append',
|
|
default=[],
|
|
help=_("Create a NIC on the server. "
|
|
"Specify option multiple times to create multiple NICs. "
|
|
"Either net-id or port-id must be provided, but not both. "
|
|
"net-id: attach NIC to network with this UUID, "
|
|
"port-id: attach NIC to port with this UUID, "
|
|
"v4-fixed-ip: IPv4 fixed address for NIC (optional), "
|
|
"v6-fixed-ip: IPv6 fixed address for NIC (optional)."),
|
|
)
|
|
parser.add_argument(
|
|
'--hint',
|
|
metavar='<key=value>',
|
|
action='append',
|
|
default=[],
|
|
help=_('Hints for the scheduler (optional extension)'),
|
|
)
|
|
parser.add_argument(
|
|
'--config-drive',
|
|
metavar='<config-drive-volume>|True',
|
|
default=False,
|
|
help=_('Use specified volume as the config drive, '
|
|
'or \'True\' to use an ephemeral drive'),
|
|
)
|
|
parser.add_argument(
|
|
'--min',
|
|
metavar='<count>',
|
|
type=int,
|
|
default=1,
|
|
help=_('Minimum number of servers to launch (default=1)'),
|
|
)
|
|
parser.add_argument(
|
|
'--max',
|
|
metavar='<count>',
|
|
type=int,
|
|
default=1,
|
|
help=_('Maximum number of servers to launch (default=1)'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for build to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
# Lookup parsed_args.image
|
|
image = None
|
|
if parsed_args.image:
|
|
image = utils.find_resource(
|
|
compute_client.images,
|
|
parsed_args.image,
|
|
)
|
|
|
|
# Lookup parsed_args.volume
|
|
volume = None
|
|
if parsed_args.volume:
|
|
volume = utils.find_resource(
|
|
volume_client.volumes,
|
|
parsed_args.volume,
|
|
).id
|
|
|
|
# Lookup parsed_args.flavor
|
|
flavor = utils.find_resource(compute_client.flavors,
|
|
parsed_args.flavor)
|
|
|
|
boot_args = [parsed_args.server_name, image, flavor]
|
|
|
|
files = {}
|
|
for f in parsed_args.file:
|
|
dst, src = f.split('=', 1)
|
|
try:
|
|
files[dst] = io.open(src, 'rb')
|
|
except IOError as e:
|
|
raise exceptions.CommandError("Can't open '%s': %s" % (src, e))
|
|
|
|
if parsed_args.min > parsed_args.max:
|
|
msg = _("min instances should be <= max instances")
|
|
raise exceptions.CommandError(msg)
|
|
if parsed_args.min < 1:
|
|
msg = _("min instances should be > 0")
|
|
raise exceptions.CommandError(msg)
|
|
if parsed_args.max < 1:
|
|
msg = _("max instances should be > 0")
|
|
raise exceptions.CommandError(msg)
|
|
|
|
userdata = None
|
|
if parsed_args.user_data:
|
|
try:
|
|
userdata = io.open(parsed_args.user_data)
|
|
except IOError as e:
|
|
msg = "Can't open '%s': %s"
|
|
raise exceptions.CommandError(msg % (parsed_args.user_data, e))
|
|
|
|
block_device_mapping = {}
|
|
if volume:
|
|
# When booting from volume, for now assume no other mappings
|
|
# This device value is likely KVM-specific
|
|
block_device_mapping = {'vda': volume}
|
|
else:
|
|
for dev_map in parsed_args.block_device_mapping:
|
|
dev_key, dev_vol = dev_map.split('=', 1)
|
|
block_volume = None
|
|
if dev_vol:
|
|
vol = dev_vol.split(':', 1)[0]
|
|
if vol:
|
|
vol_id = utils.find_resource(
|
|
volume_client.volumes,
|
|
vol,
|
|
).id
|
|
block_volume = dev_vol.replace(vol, vol_id)
|
|
else:
|
|
msg = _("Volume name or ID must be specified if "
|
|
"--block-device-mapping is specified")
|
|
raise exceptions.CommandError(msg)
|
|
block_device_mapping.update({dev_key: block_volume})
|
|
|
|
nics = []
|
|
for nic_str in parsed_args.nic:
|
|
nic_info = {"net-id": "", "v4-fixed-ip": "",
|
|
"v6-fixed-ip": "", "port-id": ""}
|
|
nic_info.update(dict(kv_str.split("=", 1)
|
|
for kv_str in nic_str.split(",")))
|
|
if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
|
|
msg = _("either net-id or port-id should be specified "
|
|
"but not both")
|
|
raise exceptions.CommandError(msg)
|
|
if self.app.client_manager.is_network_endpoint_enabled():
|
|
network_client = self.app.client_manager.network
|
|
if nic_info["net-id"]:
|
|
net = network_client.find_network(
|
|
nic_info["net-id"], ignore_missing=False)
|
|
nic_info["net-id"] = net.id
|
|
if nic_info["port-id"]:
|
|
port = network_client.find_port(
|
|
nic_info["port-id"], ignore_missing=False)
|
|
nic_info["port-id"] = port.id
|
|
else:
|
|
if nic_info["net-id"]:
|
|
nic_info["net-id"] = utils.find_resource(
|
|
compute_client.networks,
|
|
nic_info["net-id"]
|
|
).id
|
|
if nic_info["port-id"]:
|
|
msg = _("can't create server with port specified "
|
|
"since network endpoint not enabled")
|
|
raise exceptions.CommandError(msg)
|
|
nics.append(nic_info)
|
|
|
|
hints = {}
|
|
for hint in parsed_args.hint:
|
|
key, _sep, value = hint.partition('=')
|
|
# NOTE(vish): multiple copies of the same hint will
|
|
# result in a list of values
|
|
if key in hints:
|
|
if isinstance(hints[key], six.string_types):
|
|
hints[key] = [hints[key]]
|
|
hints[key] += [value]
|
|
else:
|
|
hints[key] = value
|
|
|
|
# What does a non-boolean value for config-drive do?
|
|
# --config-drive argument is either a volume id or
|
|
# 'True' (or '1') to use an ephemeral volume
|
|
if str(parsed_args.config_drive).lower() in ("true", "1"):
|
|
config_drive = True
|
|
elif str(parsed_args.config_drive).lower() in ("false", "0",
|
|
"", "none"):
|
|
config_drive = None
|
|
else:
|
|
config_drive = parsed_args.config_drive
|
|
|
|
boot_kwargs = dict(
|
|
meta=parsed_args.property,
|
|
files=files,
|
|
reservation_id=None,
|
|
min_count=parsed_args.min,
|
|
max_count=parsed_args.max,
|
|
security_groups=parsed_args.security_group,
|
|
userdata=userdata,
|
|
key_name=parsed_args.key_name,
|
|
availability_zone=parsed_args.availability_zone,
|
|
block_device_mapping=block_device_mapping,
|
|
nics=nics,
|
|
scheduler_hints=hints,
|
|
config_drive=config_drive)
|
|
|
|
self.log.debug('boot_args: %s', boot_args)
|
|
self.log.debug('boot_kwargs: %s', boot_kwargs)
|
|
|
|
# Wrap the call to catch exceptions in order to close files
|
|
try:
|
|
server = compute_client.servers.create(*boot_args, **boot_kwargs)
|
|
finally:
|
|
# Clean up open files - make sure they are not strings
|
|
for f in files:
|
|
if hasattr(f, 'close'):
|
|
f.close()
|
|
if hasattr(userdata, 'close'):
|
|
userdata.close()
|
|
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
compute_client.servers.get,
|
|
server.id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write('\n')
|
|
else:
|
|
self.log.error(_('Error creating server: %s'),
|
|
parsed_args.server_name)
|
|
sys.stdout.write(_('\nError creating server'))
|
|
raise SystemExit
|
|
|
|
details = _prep_server_detail(compute_client, server)
|
|
return zip(*sorted(six.iteritems(details)))
|
|
|
|
|
|
class CreateServerDump(command.Command):
|
|
"""Create a dump file in server(s)
|
|
|
|
Trigger crash dump in server(s) with features like kdump in Linux.
|
|
It will create a dump file in the server(s) dumping the server(s)'
|
|
memory, and also crash the server(s). OSC sees the dump file
|
|
(server dump) as a kind of resource.
|
|
"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(CreateServerDump, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to create dump file (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).trigger_crash_dump()
|
|
|
|
|
|
class CreateServerImage(command.ShowOne):
|
|
"""Create a new disk image from a running server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(CreateServerImage, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--name',
|
|
metavar='<image-name>',
|
|
help=_('Name of new image (default is server name)'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for image create to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
image_client = self.app.client_manager.image
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
if parsed_args.name:
|
|
name = parsed_args.name
|
|
else:
|
|
name = server.name
|
|
|
|
image_id = compute_client.servers.create_image(
|
|
server,
|
|
name,
|
|
)
|
|
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
image_client.images.get,
|
|
image_id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write('\n')
|
|
else:
|
|
self.log.error(_('Error creating server snapshot: %s'),
|
|
parsed_args.image_name)
|
|
sys.stdout.write(_('\nError creating server snapshot'))
|
|
raise SystemExit
|
|
|
|
image = utils.find_resource(
|
|
image_client.images,
|
|
image_id,
|
|
)
|
|
|
|
return zip(*sorted(six.iteritems(image._info)))
|
|
|
|
|
|
class DeleteServer(command.Command):
|
|
"""Delete server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(DeleteServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs="+",
|
|
help=_('Server(s) to delete (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for delete to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
server_obj = utils.find_resource(
|
|
compute_client.servers, server)
|
|
compute_client.servers.delete(server_obj.id)
|
|
if parsed_args.wait:
|
|
if utils.wait_for_delete(
|
|
compute_client.servers,
|
|
server_obj.id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write('\n')
|
|
else:
|
|
self.log.error(_('Error deleting server: %s'),
|
|
server_obj.id)
|
|
sys.stdout.write(_('\nError deleting server'))
|
|
raise SystemExit
|
|
|
|
|
|
class ListServer(command.Lister):
|
|
"""List servers"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(ListServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'--reservation-id',
|
|
metavar='<reservation-id>',
|
|
help=_('Only return instances that match the reservation'),
|
|
)
|
|
parser.add_argument(
|
|
'--ip',
|
|
metavar='<ip-address-regex>',
|
|
help=_('Regular expression to match IP addresses'),
|
|
)
|
|
parser.add_argument(
|
|
'--ip6',
|
|
metavar='<ip-address-regex>',
|
|
help=_('Regular expression to match IPv6 addresses'),
|
|
)
|
|
parser.add_argument(
|
|
'--name',
|
|
metavar='<name-regex>',
|
|
help=_('Regular expression to match names'),
|
|
)
|
|
parser.add_argument(
|
|
'--instance-name',
|
|
metavar='<server-name>',
|
|
help=_('Regular expression to match instance name (admin only)'),
|
|
)
|
|
parser.add_argument(
|
|
'--status',
|
|
metavar='<status>',
|
|
# FIXME(dhellmann): Add choices?
|
|
help=_('Search by server status'),
|
|
)
|
|
parser.add_argument(
|
|
'--flavor',
|
|
metavar='<flavor>',
|
|
help=_('Search by flavor (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--image',
|
|
metavar='<image>',
|
|
help=_('Search by image (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--host',
|
|
metavar='<hostname>',
|
|
help=_('Search by hostname'),
|
|
)
|
|
parser.add_argument(
|
|
'--all-projects',
|
|
action='store_true',
|
|
default=bool(int(os.environ.get("ALL_PROJECTS", 0))),
|
|
help=_('Include all projects (admin only)'),
|
|
)
|
|
parser.add_argument(
|
|
'--project',
|
|
metavar='<project>',
|
|
help="Search by project (admin only) (name or ID)")
|
|
identity_common.add_project_domain_option_to_parser(parser)
|
|
parser.add_argument(
|
|
'--user',
|
|
metavar='<user>',
|
|
help=_('Search by user (admin only) (name or ID)'),
|
|
)
|
|
identity_common.add_user_domain_option_to_parser(parser)
|
|
parser.add_argument(
|
|
'--long',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('List additional fields in output'),
|
|
)
|
|
parser.add_argument(
|
|
'--marker',
|
|
metavar='<marker>',
|
|
default=None,
|
|
help=('The last server (name or ID) of the previous page. Display'
|
|
' list of servers after marker. Display all servers if not'
|
|
' specified.')
|
|
)
|
|
parser.add_argument(
|
|
'--limit',
|
|
metavar='<limit>',
|
|
type=int,
|
|
default=None,
|
|
help=("Maximum number of servers to display. If limit equals -1,"
|
|
" all servers will be displayed. If limit is greater than"
|
|
" 'osapi_max_limit' option of Nova API,"
|
|
" 'osapi_max_limit' will be used instead."),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
identity_client = self.app.client_manager.identity
|
|
|
|
project_id = None
|
|
if parsed_args.project:
|
|
project_id = identity_common.find_project(
|
|
identity_client,
|
|
parsed_args.project,
|
|
parsed_args.project_domain,
|
|
).id
|
|
parsed_args.all_projects = True
|
|
|
|
user_id = None
|
|
if parsed_args.user:
|
|
user_id = identity_common.find_user(
|
|
identity_client,
|
|
parsed_args.user,
|
|
parsed_args.user_domain,
|
|
).id
|
|
|
|
# Nova only supports list servers searching by flavor ID. So if a
|
|
# flavor name is given, map it to ID.
|
|
flavor_id = None
|
|
if parsed_args.flavor:
|
|
flavor_id = utils.find_resource(compute_client.flavors,
|
|
parsed_args.flavor).id
|
|
|
|
# Nova only supports list servers searching by image ID. So if a
|
|
# image name is given, map it to ID.
|
|
image_id = None
|
|
if parsed_args.image:
|
|
image_id = utils.find_resource(compute_client.images,
|
|
parsed_args.image).id
|
|
|
|
search_opts = {
|
|
'reservation_id': parsed_args.reservation_id,
|
|
'ip': parsed_args.ip,
|
|
'ip6': parsed_args.ip6,
|
|
'name': parsed_args.name,
|
|
'instance_name': parsed_args.instance_name,
|
|
'status': parsed_args.status,
|
|
'flavor': flavor_id,
|
|
'image': image_id,
|
|
'host': parsed_args.host,
|
|
'tenant_id': project_id,
|
|
'all_tenants': parsed_args.all_projects,
|
|
'user_id': user_id,
|
|
}
|
|
self.log.debug('search options: %s', search_opts)
|
|
|
|
if parsed_args.long:
|
|
columns = (
|
|
'ID',
|
|
'Name',
|
|
'Status',
|
|
'OS-EXT-STS:task_state',
|
|
'OS-EXT-STS:power_state',
|
|
'Networks',
|
|
'OS-EXT-AZ:availability_zone',
|
|
'OS-EXT-SRV-ATTR:host',
|
|
'Metadata',
|
|
)
|
|
column_headers = (
|
|
'ID',
|
|
'Name',
|
|
'Status',
|
|
'Task State',
|
|
'Power State',
|
|
'Networks',
|
|
'Availability Zone',
|
|
'Host',
|
|
'Properties',
|
|
)
|
|
mixed_case_fields = [
|
|
'OS-EXT-STS:task_state',
|
|
'OS-EXT-STS:power_state',
|
|
'OS-EXT-AZ:availability_zone',
|
|
'OS-EXT-SRV-ATTR:host',
|
|
]
|
|
else:
|
|
columns = (
|
|
'ID',
|
|
'Name',
|
|
'Status',
|
|
'Networks',
|
|
)
|
|
column_headers = (
|
|
'ID',
|
|
'Name',
|
|
'Status',
|
|
'Networks',
|
|
)
|
|
mixed_case_fields = []
|
|
|
|
marker_id = None
|
|
if parsed_args.marker:
|
|
marker_id = utils.find_resource(compute_client.servers,
|
|
parsed_args.marker).id
|
|
|
|
data = compute_client.servers.list(search_opts=search_opts,
|
|
marker=marker_id,
|
|
limit=parsed_args.limit)
|
|
return (column_headers,
|
|
(utils.get_item_properties(
|
|
s, columns,
|
|
mixed_case_fields=mixed_case_fields,
|
|
formatters={
|
|
'OS-EXT-STS:power_state':
|
|
_format_servers_list_power_state,
|
|
'Networks': _format_servers_list_networks,
|
|
'Metadata': utils.format_dict,
|
|
},
|
|
) for s in data))
|
|
|
|
|
|
class LockServer(command.Command):
|
|
|
|
"""Lock server(s). A non-admin user will not be able to execute actions"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(LockServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to lock (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).lock()
|
|
|
|
|
|
# FIXME(dtroyer): Here is what I want, how with argparse/cliff?
|
|
# server migrate [--wait] \
|
|
# [--live <hostname>
|
|
# [--shared-migration | --block-migration]
|
|
# [--disk-overcommit | --no-disk-overcommit]]
|
|
# <server>
|
|
#
|
|
# live_parser = parser.add_argument_group(title='Live migration options')
|
|
# then adding the groups doesn't seem to work
|
|
|
|
class MigrateServer(command.Command):
|
|
"""Migrate server to different host"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(MigrateServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--live',
|
|
metavar='<hostname>',
|
|
help=_('Target hostname'),
|
|
)
|
|
migration_group = parser.add_mutually_exclusive_group()
|
|
migration_group.add_argument(
|
|
'--shared-migration',
|
|
dest='shared_migration',
|
|
action='store_true',
|
|
default=True,
|
|
help=_('Perform a shared live migration (default)'),
|
|
)
|
|
migration_group.add_argument(
|
|
'--block-migration',
|
|
dest='shared_migration',
|
|
action='store_false',
|
|
help=_('Perform a block live migration'),
|
|
)
|
|
disk_group = parser.add_mutually_exclusive_group()
|
|
disk_group.add_argument(
|
|
'--disk-overcommit',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('Allow disk over-commit on the destination host'),
|
|
)
|
|
disk_group.add_argument(
|
|
'--no-disk-overcommit',
|
|
dest='disk_overcommit',
|
|
action='store_false',
|
|
default=False,
|
|
help=_('Do not over-commit disk on the'
|
|
' destination host (default)'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for resize to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
if parsed_args.live:
|
|
server.live_migrate(
|
|
parsed_args.live,
|
|
parsed_args.shared_migration,
|
|
parsed_args.disk_overcommit,
|
|
)
|
|
else:
|
|
server.migrate()
|
|
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
compute_client.servers.get,
|
|
server.id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write(_('Complete\n'))
|
|
else:
|
|
sys.stdout.write(_('\nError migrating server'))
|
|
raise SystemExit
|
|
|
|
|
|
class PauseServer(command.Command):
|
|
"""Pause server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(PauseServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to pause (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server
|
|
).pause()
|
|
|
|
|
|
class RebootServer(command.Command):
|
|
"""Perform a hard or soft server reboot"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(RebootServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument(
|
|
'--hard',
|
|
dest='reboot_type',
|
|
action='store_const',
|
|
const=servers.REBOOT_HARD,
|
|
default=servers.REBOOT_SOFT,
|
|
help=_('Perform a hard reboot'),
|
|
)
|
|
group.add_argument(
|
|
'--soft',
|
|
dest='reboot_type',
|
|
action='store_const',
|
|
const=servers.REBOOT_SOFT,
|
|
default=servers.REBOOT_SOFT,
|
|
help=_('Perform a soft reboot'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for reboot to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(
|
|
compute_client.servers, parsed_args.server)
|
|
server.reboot(parsed_args.reboot_type)
|
|
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
compute_client.servers.get,
|
|
server.id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write(_('\nReboot complete\n'))
|
|
else:
|
|
sys.stdout.write(_('\nError rebooting server\n'))
|
|
raise SystemExit
|
|
|
|
|
|
class RebuildServer(command.ShowOne):
|
|
"""Rebuild server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(RebuildServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--image',
|
|
metavar='<image>',
|
|
help=_('Recreate server from the specified image (name or ID).'
|
|
' Defaults to the currently used one.'),
|
|
)
|
|
parser.add_argument(
|
|
'--password',
|
|
metavar='<password>',
|
|
help="Set the password on the rebuilt instance",
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for rebuild to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers, parsed_args.server)
|
|
|
|
# If parsed_args.image is not set, default to the currently used one.
|
|
image_id = parsed_args.image or server._info.get('image', {}).get('id')
|
|
image = utils.find_resource(compute_client.images, image_id)
|
|
|
|
server = server.rebuild(image, parsed_args.password)
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
compute_client.servers.get,
|
|
server.id,
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write(_('\nComplete\n'))
|
|
else:
|
|
sys.stdout.write(_('\nError rebuilding server'))
|
|
raise SystemExit
|
|
|
|
details = _prep_server_detail(compute_client, server)
|
|
return zip(*sorted(six.iteritems(details)))
|
|
|
|
|
|
class RemoveServerSecurityGroup(command.Command):
|
|
"""Remove security group from server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(RemoveServerSecurityGroup, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Name or ID of server to use'),
|
|
)
|
|
parser.add_argument(
|
|
'group',
|
|
metavar='<group>',
|
|
help=_('Name or ID of security group to remove from server'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
security_group = utils.find_resource(
|
|
compute_client.security_groups,
|
|
parsed_args.group,
|
|
)
|
|
|
|
server.remove_security_group(security_group)
|
|
|
|
|
|
class RemoveServerVolume(command.Command):
|
|
"""Remove volume from server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(RemoveServerVolume, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'volume',
|
|
metavar='<volume>',
|
|
help=_('Volume to remove (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
volume_client = self.app.client_manager.volume
|
|
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
volume = utils.find_resource(
|
|
volume_client.volumes,
|
|
parsed_args.volume,
|
|
)
|
|
|
|
compute_client.volumes.delete_server_volume(
|
|
server.id,
|
|
volume.id,
|
|
)
|
|
|
|
|
|
class RescueServer(command.ShowOne):
|
|
"""Put server in rescue mode"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(RescueServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
_, body = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
).rescue()
|
|
return zip(*sorted(six.iteritems(body)))
|
|
|
|
|
|
class ResizeServer(command.Command):
|
|
"""Scale server to a new flavor"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(ResizeServer, self).get_parser(prog_name)
|
|
phase_group = parser.add_mutually_exclusive_group()
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
phase_group.add_argument(
|
|
'--flavor',
|
|
metavar='<flavor>',
|
|
help=_('Resize server to specified flavor'),
|
|
)
|
|
phase_group.add_argument(
|
|
'--confirm',
|
|
action="store_true",
|
|
help=_('Confirm server resize is complete'),
|
|
)
|
|
phase_group.add_argument(
|
|
'--revert',
|
|
action="store_true",
|
|
help=_('Restore server state before resize'),
|
|
)
|
|
parser.add_argument(
|
|
'--wait',
|
|
action='store_true',
|
|
help=_('Wait for resize to complete'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
if parsed_args.flavor:
|
|
flavor = utils.find_resource(
|
|
compute_client.flavors,
|
|
parsed_args.flavor,
|
|
)
|
|
compute_client.servers.resize(server, flavor)
|
|
if parsed_args.wait:
|
|
if utils.wait_for_status(
|
|
compute_client.servers.get,
|
|
server.id,
|
|
success_status=['active', 'verify_resize'],
|
|
callback=_show_progress,
|
|
):
|
|
sys.stdout.write(_('Complete\n'))
|
|
else:
|
|
sys.stdout.write(_('\nError resizing server'))
|
|
raise SystemExit
|
|
elif parsed_args.confirm:
|
|
compute_client.servers.confirm_resize(server)
|
|
elif parsed_args.revert:
|
|
compute_client.servers.revert_resize(server)
|
|
|
|
|
|
class ResumeServer(command.Command):
|
|
"""Resume server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(ResumeServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to resume (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).resume()
|
|
|
|
|
|
class SetServer(command.Command):
|
|
"""Set server properties"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(SetServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--name',
|
|
metavar='<new-name>',
|
|
help=_('New server name'),
|
|
)
|
|
parser.add_argument(
|
|
'--root-password',
|
|
action="store_true",
|
|
help=_('Set new root password (interactive only)'),
|
|
)
|
|
parser.add_argument(
|
|
"--property",
|
|
metavar="<key=value>",
|
|
action=parseractions.KeyValueAction,
|
|
help=_('Property to add/change for this server '
|
|
'(repeat option to set multiple properties)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
|
|
if parsed_args.name:
|
|
server.update(name=parsed_args.name)
|
|
|
|
if parsed_args.property:
|
|
compute_client.servers.set_meta(
|
|
server,
|
|
parsed_args.property,
|
|
)
|
|
|
|
if parsed_args.root_password:
|
|
p1 = getpass.getpass(_('New password: '))
|
|
p2 = getpass.getpass(_('Retype new password: '))
|
|
if p1 == p2:
|
|
server.change_password(p1)
|
|
else:
|
|
msg = _("Passwords do not match, password unchanged")
|
|
raise exceptions.CommandError(msg)
|
|
|
|
|
|
class ShelveServer(command.Command):
|
|
"""Shelve server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(ShelveServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to shelve (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).shelve()
|
|
|
|
|
|
class ShowServer(command.ShowOne):
|
|
"""Show server details"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(ShowServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--diagnostics',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('Display server diagnostics information'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(compute_client.servers,
|
|
parsed_args.server)
|
|
|
|
if parsed_args.diagnostics:
|
|
(resp, data) = server.diagnostics()
|
|
if not resp.status_code == 200:
|
|
sys.stderr.write(_("Error retrieving diagnostics data"))
|
|
return ({}, {})
|
|
else:
|
|
data = _prep_server_detail(compute_client, server)
|
|
|
|
return zip(*sorted(six.iteritems(data)))
|
|
|
|
|
|
class SshServer(command.Command):
|
|
"""Ssh to server"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(SshServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--login',
|
|
metavar='<login-name>',
|
|
help=_('Login name (ssh -l option)'),
|
|
)
|
|
parser.add_argument(
|
|
'-l',
|
|
dest='login',
|
|
metavar='<login-name>',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
parser.add_argument(
|
|
'--port',
|
|
metavar='<port>',
|
|
type=int,
|
|
help=_('Destination port (ssh -p option)'),
|
|
)
|
|
parser.add_argument(
|
|
'-p',
|
|
metavar='<port>',
|
|
dest='port',
|
|
type=int,
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
parser.add_argument(
|
|
'--identity',
|
|
metavar='<keyfile>',
|
|
help=_('Private key file (ssh -i option)'),
|
|
)
|
|
parser.add_argument(
|
|
'-i',
|
|
metavar='<filename>',
|
|
dest='identity',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
parser.add_argument(
|
|
'--option',
|
|
metavar='<config-options>',
|
|
help=_('Options in ssh_config(5) format (ssh -o option)'),
|
|
)
|
|
parser.add_argument(
|
|
'-o',
|
|
metavar='<option>',
|
|
dest='option',
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
ip_group = parser.add_mutually_exclusive_group()
|
|
ip_group.add_argument(
|
|
'-4',
|
|
dest='ipv4',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('Use only IPv4 addresses'),
|
|
)
|
|
ip_group.add_argument(
|
|
'-6',
|
|
dest='ipv6',
|
|
action='store_true',
|
|
default=False,
|
|
help=_('Use only IPv6 addresses'),
|
|
)
|
|
type_group = parser.add_mutually_exclusive_group()
|
|
type_group.add_argument(
|
|
'--public',
|
|
dest='address_type',
|
|
action='store_const',
|
|
const='public',
|
|
default='public',
|
|
help=_('Use public IP address'),
|
|
)
|
|
type_group.add_argument(
|
|
'--private',
|
|
dest='address_type',
|
|
action='store_const',
|
|
const='private',
|
|
default='public',
|
|
help=_('Use private IP address'),
|
|
)
|
|
type_group.add_argument(
|
|
'--address-type',
|
|
metavar='<address-type>',
|
|
dest='address_type',
|
|
default='public',
|
|
help=_('Use other IP address (public, private, etc)'),
|
|
)
|
|
parser.add_argument(
|
|
'-v',
|
|
dest='verbose',
|
|
action='store_true',
|
|
default=False,
|
|
help=argparse.SUPPRESS,
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
|
|
# Build the command
|
|
cmd = "ssh"
|
|
|
|
ip_address_family = [4, 6]
|
|
if parsed_args.ipv4:
|
|
ip_address_family = [4]
|
|
cmd += " -4"
|
|
if parsed_args.ipv6:
|
|
ip_address_family = [6]
|
|
cmd += " -6"
|
|
|
|
if parsed_args.port:
|
|
cmd += " -p %d" % parsed_args.port
|
|
if parsed_args.identity:
|
|
cmd += " -i %s" % parsed_args.identity
|
|
if parsed_args.option:
|
|
cmd += " -o %s" % parsed_args.option
|
|
if parsed_args.login:
|
|
login = parsed_args.login
|
|
else:
|
|
login = self.app.client_manager._username
|
|
if parsed_args.verbose:
|
|
cmd += " -v"
|
|
|
|
cmd += " %s@%s"
|
|
ip_address = _get_ip_address(server.addresses,
|
|
parsed_args.address_type,
|
|
ip_address_family)
|
|
self.log.debug("ssh command: %s", (cmd % (login, ip_address)))
|
|
os.system(cmd % (login, ip_address))
|
|
|
|
|
|
class StartServer(command.Command):
|
|
"""Start server(s)."""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(StartServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs="+",
|
|
help=_('Server(s) to start (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).start()
|
|
|
|
|
|
class StopServer(command.Command):
|
|
"""Stop server(s)."""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(StopServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs="+",
|
|
help=_('Server(s) to stop (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).stop()
|
|
|
|
|
|
class SuspendServer(command.Command):
|
|
"""Suspend server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(SuspendServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to suspend (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).suspend()
|
|
|
|
|
|
class UnlockServer(command.Command):
|
|
"""Unlock server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(UnlockServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to unlock (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).unlock()
|
|
|
|
|
|
class UnpauseServer(command.Command):
|
|
"""Unpause server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(UnpauseServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to unpause (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).unpause()
|
|
|
|
|
|
class UnrescueServer(command.Command):
|
|
"""Restore server from rescue mode"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(UnrescueServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
|
|
compute_client = self.app.client_manager.compute
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
).unrescue()
|
|
|
|
|
|
class UnsetServer(command.Command):
|
|
"""Unset server properties"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(UnsetServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
help=_('Server (name or ID)'),
|
|
)
|
|
parser.add_argument(
|
|
'--property',
|
|
metavar='<key>',
|
|
action='append',
|
|
default=[],
|
|
help=_('Property key to remove from server '
|
|
'(repeat to unset multiple values)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
server = utils.find_resource(
|
|
compute_client.servers,
|
|
parsed_args.server,
|
|
)
|
|
|
|
if parsed_args.property:
|
|
compute_client.servers.delete_meta(
|
|
server,
|
|
parsed_args.property,
|
|
)
|
|
|
|
|
|
class UnshelveServer(command.Command):
|
|
"""Unshelve server(s)"""
|
|
|
|
def get_parser(self, prog_name):
|
|
parser = super(UnshelveServer, self).get_parser(prog_name)
|
|
parser.add_argument(
|
|
'server',
|
|
metavar='<server>',
|
|
nargs='+',
|
|
help=_('Server(s) to unshelve (name or ID)'),
|
|
)
|
|
return parser
|
|
|
|
def take_action(self, parsed_args):
|
|
compute_client = self.app.client_manager.compute
|
|
for server in parsed_args.server:
|
|
utils.find_resource(
|
|
compute_client.servers,
|
|
server,
|
|
).unshelve()
|