OpenStack Compute (Nova) Client
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4459 lines
140 KiB

# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack Foundation
# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# 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.
from __future__ import print_function
import argparse
import copy
import datetime
import getpass
import locale
import logging
import os
import sys
import time
from oslo_utils import encodeutils
from oslo_utils import strutils
from oslo_utils import timeutils
import six
from novaclient import client
from novaclient import exceptions
from novaclient.i18n import _
from novaclient.openstack.common import cliutils
from novaclient.openstack.common import uuidutils
from novaclient import utils
from novaclient.v2 import availability_zones
from novaclient.v2 import quotas
from novaclient.v2 import servers
logger = logging.getLogger(__name__)
CLIENT_BDM2_KEYS = {
'id': 'uuid',
'source': 'source_type',
'dest': 'destination_type',
'bus': 'disk_bus',
'device': 'device_name',
'size': 'volume_size',
'format': 'guest_format',
'bootindex': 'boot_index',
'type': 'device_type',
'shutdown': 'delete_on_termination',
}
def _key_value_pairing(text):
try:
(k, v) = text.split('=', 1)
return (k, v)
except ValueError:
msg = "%r is not in the format of key=value" % text
raise argparse.ArgumentTypeError(msg)
def _match_image(cs, wanted_properties):
image_list = cs.images.list()
images_matched = []
match = set(wanted_properties)
for img in image_list:
try:
if match == match.intersection(set(img.metadata.items())):
images_matched.append(img)
except AttributeError:
pass
return images_matched
def _parse_block_device_mapping_v2(args, image):
bdm = []
if args.boot_volume:
bdm_dict = {'uuid': args.boot_volume, 'source_type': 'volume',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)
if args.snapshot:
bdm_dict = {'uuid': args.snapshot, 'source_type': 'snapshot',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}
bdm.append(bdm_dict)
for device_spec in args.block_device:
spec_dict = dict(v.split('=') for v in device_spec.split(','))
bdm_dict = {}
for key, value in six.iteritems(spec_dict):
bdm_dict[CLIENT_BDM2_KEYS[key]] = value
# Convert the delete_on_termination to a boolean or set it to true by
# default for local block devices when not specified.
if 'delete_on_termination' in bdm_dict:
action = bdm_dict['delete_on_termination']
bdm_dict['delete_on_termination'] = (action == 'remove')
elif bdm_dict.get('destination_type') == 'local':
bdm_dict['delete_on_termination'] = True
bdm.append(bdm_dict)
for ephemeral_spec in args.ephemeral:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True}
eph_dict = dict(v.split('=') for v in ephemeral_spec.split(','))
if 'size' in eph_dict:
bdm_dict['volume_size'] = eph_dict['size']
if 'format' in eph_dict:
bdm_dict['guest_format'] = eph_dict['format']
bdm.append(bdm_dict)
if args.swap:
bdm_dict = {'source_type': 'blank', 'destination_type': 'local',
'boot_index': -1, 'delete_on_termination': True,
'guest_format': 'swap', 'volume_size': args.swap}
bdm.append(bdm_dict)
return bdm
def _boot(cs, args):
"""Boot a new server."""
if args.image:
image = _find_image(cs, args.image)
else:
image = None
if not image and args.image_with:
images = _match_image(cs, args.image_with)
if images:
# TODO(harlowja): log a warning that we
# are selecting the first of many?
image = images[0]
if not args.flavor:
raise exceptions.CommandError(_("you need to specify a Flavor ID "))
min_count = 1
max_count = 1
# Don't let user mix num_instances and max_count/min_count.
if (args.num_instances is not None and
args.min_count is None and
args.max_count is None):
if args.num_instances < 1:
raise exceptions.CommandError(_("num_instances should be >= 1"))
max_count = args.num_instances
elif (args.num_instances is not None and
(args.min_count is not None or args.max_count is not None)):
raise exceptions.CommandError(_("Don't mix num-instances and "
"max/min-count"))
if args.min_count is not None:
if args.min_count < 1:
raise exceptions.CommandError(_("min_count should be >= 1"))
min_count = args.min_count
max_count = min_count
if args.max_count is not None:
if args.max_count < 1:
raise exceptions.CommandError(_("max_count should be >= 1"))
max_count = args.max_count
if (args.min_count is not None and
args.max_count is not None and
args.min_count > args.max_count):
raise exceptions.CommandError(_("min_count should be <= max_count"))
flavor = _find_flavor(cs, args.flavor)
meta = dict(v.split('=', 1) for v in args.meta)
files = {}
for f in args.files:
try:
dst, src = f.split('=', 1)
files[dst] = open(src)
except IOError as e:
raise exceptions.CommandError(_("Can't open '%(src)s': %(exc)s") %
{'src': src, 'exc': e})
except ValueError:
raise exceptions.CommandError(_("Invalid file argument '%s'. "
"File arguments must be of the "
"form '--file "
"<dst-path=src-path>'") % f)
# use the os-keypair extension
key_name = None
if args.key_name is not None:
key_name = args.key_name
if args.user_data:
try:
userdata = open(args.user_data)
except IOError as e:
raise exceptions.CommandError(_("Can't open '%(user_data)s': "
"%(exc)s") %
{'user_data': args.user_data,
'exc': e})
else:
userdata = None
if args.availability_zone:
availability_zone = args.availability_zone
else:
availability_zone = None
if args.security_groups:
security_groups = args.security_groups.split(',')
else:
security_groups = None
block_device_mapping = {}
for bdm in args.block_device_mapping:
device_name, mapping = bdm.split('=', 1)
block_device_mapping[device_name] = mapping
block_device_mapping_v2 = _parse_block_device_mapping_v2(args, image)
n_boot_args = len(list(filter(
bool, (image, args.boot_volume, args.snapshot))))
have_bdm = block_device_mapping_v2 or block_device_mapping
# Fail if more than one boot devices are present
# or if there is no device to boot from.
if n_boot_args > 1 or n_boot_args == 0 and not have_bdm:
raise exceptions.CommandError(
_("you need to specify at least one source ID (Image, Snapshot, "
"or Volume), a block device mapping or provide a set of "
"properties to match against an image"))
if block_device_mapping and block_device_mapping_v2:
raise exceptions.CommandError(
_("you can't mix old block devices (--block-device-mapping) "
"with the new ones (--block-device, --boot-volume, --snapshot, "
"--ephemeral, --swap)"))
nics = []
for nic_str in args.nics:
err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of "
"the form --nic <net-id=net-uuid,v4-fixed-ip=ip-addr,"
"v6-fixed-ip=ip-addr,port-id=port-uuid>, with at minimum "
"net-id or port-id (but not both) specified.") % nic_str)
nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "",
"port-id": ""}
for kv_str in nic_str.split(","):
try:
k, v = kv_str.split("=", 1)
except ValueError:
raise exceptions.CommandError(err_msg)
if k in nic_info:
nic_info[k] = v
else:
raise exceptions.CommandError(err_msg)
if bool(nic_info['net-id']) == bool(nic_info['port-id']):
raise exceptions.CommandError(err_msg)
nics.append(nic_info)
hints = {}
if args.scheduler_hints:
for hint in args.scheduler_hints:
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
boot_args = [args.name, image, flavor]
if str(args.config_drive).lower() in ("true", "1"):
config_drive = True
elif str(args.config_drive).lower() in ("false", "0", "", "none"):
config_drive = None
else:
config_drive = args.config_drive
boot_kwargs = dict(
meta=meta,
files=files,
key_name=key_name,
min_count=min_count,
max_count=max_count,
userdata=userdata,
availability_zone=availability_zone,
security_groups=security_groups,
block_device_mapping=block_device_mapping,
block_device_mapping_v2=block_device_mapping_v2,
nics=nics,
scheduler_hints=hints,
config_drive=config_drive)
return boot_args, boot_kwargs
@cliutils.arg(
'--flavor',
default=None,
metavar='<flavor>',
help=_("Name or ID of flavor (see 'nova flavor-list')."))
@cliutils.arg(
'--image',
default=None,
metavar='<image>',
help=_("Name or ID of image (see 'nova image-list'). "))
@cliutils.arg(
'--image-with',
default=[],
type=_key_value_pairing,
action='append',
metavar='<key=value>',
help=_("Image metadata property (see 'nova image-show'). "))
@cliutils.arg(
'--boot-volume',
default=None,
metavar="<volume_id>",
help=_("Volume ID to boot from."))
@cliutils.arg(
'--snapshot',
default=None,
metavar="<snapshot_id>",
help=_("Snapshot ID to boot from (will create a volume)."))
@cliutils.arg(
'--num-instances',
default=None,
type=int,
metavar='<number>',
help=argparse.SUPPRESS)
@cliutils.arg(
'--min-count',
default=None,
type=int,
metavar='<number>',
help=_("Boot at least <number> servers (limited by quota)."))
@cliutils.arg(
'--max-count',
default=None,
type=int,
metavar='<number>',
help=_("Boot up to <number> servers (limited by quota)."))
@cliutils.arg(
'--meta',
metavar="<key=value>",
action='append',
default=[],
help=_("Record arbitrary key/value metadata to /meta_data.json "
"on the metadata server. Can be specified multiple times."))
@cliutils.arg(
'--file',
metavar="<dst-path=src-path>",
action='append',
dest='files',
default=[],
help=_("Store arbitrary files from <src-path> locally to <dst-path> "
"on the new server. Limited by the injected_files quota value."))
@cliutils.arg(
'--key-name',
default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'),
metavar='<key-name>',
help=_("Key name of keypair that should be created earlier with \
the command keypair-add"))
@cliutils.arg(
'--key_name',
help=argparse.SUPPRESS)
@cliutils.arg('name', metavar='<name>', help=_('Name for the new server'))
@cliutils.arg(
'--user-data',
default=None,
metavar='<user-data>',
help=_("user data file to pass to be exposed by the metadata server."))
@cliutils.arg(
'--user_data',
help=argparse.SUPPRESS)
@cliutils.arg(
'--availability-zone',
default=None,
metavar='<availability-zone>',
help=_("The availability zone for server placement."))
@cliutils.arg(
'--availability_zone',
help=argparse.SUPPRESS)
@cliutils.arg(
'--security-groups',
default=None,
metavar='<security-groups>',
help=_("Comma separated list of security group names."))
@cliutils.arg(
'--security_groups',
help=argparse.SUPPRESS)
@cliutils.arg(
'--block-device-mapping',
metavar="<dev-name=mapping>",
action='append',
default=[],
help=_("Block device mapping in the format "
"<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>."))
@cliutils.arg(
'--block_device_mapping',
action='append',
help=argparse.SUPPRESS)
@cliutils.arg(
'--block-device',
metavar="key1=value1[,key2=value2...]",
action='append',
default=[],
help=_("Block device mapping with the keys: "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
"source=source type (image, snapshot, volume or blank), "
"dest=destination type of the block device (volume or local), "
"bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, "
"hypervisor driver chooses a suitable default, "
"honoured only if device type is supplied) "
"type=device type (e.g. disk, cdrom, ...; defaults to 'disk') "
"device=name of the device (e.g. vda, xda, ...; "
"if omitted, hypervisor driver chooses suitable device "
"depending on selected bus), "
"size=size of the block device in MB(for swap) and in "
"GB(for other formats) "
"(if omitted, hypervisor driver calculates size), "
"format=device will be formatted (e.g. swap, ntfs, ...; optional), "
"bootindex=integer used for ordering the boot disks "
"(for image backed instances it is equal to 0, "
"for others need to be specified) and "
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove)."))
@cliutils.arg(
'--swap',
metavar="<swap_size>",
default=None,
help=_("Create and attach a local swap block device of <swap_size> MB."))
@cliutils.arg(
'--ephemeral',
metavar="size=<size>[,format=<format>]",
action='append',
default=[],
help=_("Create and attach a local ephemeral block device of <size> GB "
"and format it to <format>."))
@cliutils.arg(
'--hint',
action='append',
dest='scheduler_hints',
default=[],
metavar='<key=value>',
help=_("Send arbitrary key/value pairs to the scheduler for custom "
"use."))
@cliutils.arg(
'--nic',
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
"port-id=port-uuid>",
action='append',
dest='nics',
default=[],
help=_("Create a NIC on the server. "
"Specify option multiple times to create multiple NICs. "
"net-id: attach NIC to network with this UUID "
"(either port-id or net-id must be provided), "
"v4-fixed-ip: IPv4 fixed address for NIC (optional), "
"v6-fixed-ip: IPv6 fixed address for NIC (optional), "
"port-id: attach NIC to port with this UUID "
"(either port-id or net-id must be provided)."))
@cliutils.arg(
'--config-drive',
metavar="<value>",
dest='config_drive',
default=False,
help=_("Enable config drive"))
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Report the new server boot progress until it completes.'))
def do_boot(cs, args):
"""Boot a new server."""
boot_args, boot_kwargs = _boot(cs, args)
extra_boot_kwargs = utils.get_resource_manager_extra_kwargs(do_boot, args)
boot_kwargs.update(extra_boot_kwargs)
server = cs.servers.create(*boot_args, **boot_kwargs)
_print_server(cs, args, server)
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'building', ['active'])
def do_cloudpipe_list(cs, _args):
"""Print a list of all cloudpipe instances."""
cloudpipes = cs.cloudpipe.list()
columns = ['Project Id', "Public IP", "Public Port", "Internal IP"]
utils.print_list(cloudpipes, columns)
@cliutils.arg(
'project',
metavar='<project_id>',
help=_('UUID of the project to create the cloudpipe for.'))
def do_cloudpipe_create(cs, args):
"""Create a cloudpipe instance for the given project."""
cs.cloudpipe.create(args.project)
@cliutils.arg('address', metavar='<ip address>', help=_('New IP Address.'))
@cliutils.arg('port', metavar='<port>', help='New Port.')
def do_cloudpipe_configure(cs, args):
"""Update the VPN IP/port of a cloudpipe instance."""
cs.cloudpipe.update(args.address, args.port)
def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
poll_period=5, show_progress=True,
status_field="status", silent=False):
"""Block while an action is being performed, periodically printing
progress.
"""
def print_progress(progress):
if show_progress:
msg = (_('\rServer %(action)s... %(progress)s%% complete')
% dict(action=action, progress=progress))
else:
msg = _('\rServer %(action)s...') % dict(action=action)
sys.stdout.write(msg)
sys.stdout.flush()
if not silent:
print()
while True:
obj = poll_fn(obj_id)
status = getattr(obj, status_field)
if status:
status = status.lower()
progress = getattr(obj, 'progress', None) or 0
if status in final_ok_states:
if not silent:
print_progress(100)
print(_("\nFinished"))
break
elif status == "error":
if not silent:
print(_("\nError %s server") % action)
raise exceptions.InstanceInErrorState(obj.fault['message'])
if not silent:
print_progress(progress)
time.sleep(poll_period)
def _translate_keys(collection, convert):
for item in collection:
keys = item.__dict__.keys()
for from_key, to_key in convert:
if from_key in keys and to_key not in keys:
setattr(item, to_key, item._info[from_key])
def _translate_extended_states(collection):
power_states = [
'NOSTATE', # 0x00
'Running', # 0x01
'', # 0x02
'Paused', # 0x03
'Shutdown', # 0x04
'', # 0x05
'Crashed', # 0x06
'Suspended' # 0x07
]
for item in collection:
try:
setattr(item, 'power_state',
power_states[getattr(item, 'power_state')])
except AttributeError:
setattr(item, 'power_state', "N/A")
try:
getattr(item, 'task_state')
except AttributeError:
setattr(item, 'task_state', "N/A")
def _translate_flavor_keys(collection):
_translate_keys(collection, [('ram', 'memory_mb')])
def _print_flavor_extra_specs(flavor):
try:
return flavor.get_keys()
except exceptions.NotFound:
return "N/A"
def _print_flavor_list(flavors, show_extra_specs=False):
_translate_flavor_keys(flavors)
headers = [
'ID',
'Name',
'Memory_MB',
'Disk',
'Ephemeral',
'Swap',
'VCPUs',
'RXTX_Factor',
'Is_Public',
]
if show_extra_specs:
formatters = {'extra_specs': _print_flavor_extra_specs}
headers.append('extra_specs')
else:
formatters = {}
utils.print_list(flavors, headers, formatters)
@cliutils.arg(
'--extra-specs',
dest='extra_specs',
action='store_true',
default=False,
help=_('Get extra-specs of each flavor.'))
@cliutils.arg(
'--all',
dest='all',
action='store_true',
default=False,
help=_('Display all flavors (Admin only).'))
def do_flavor_list(cs, args):
"""Print a list of available 'flavors' (sizes of servers)."""
if args.all:
flavors = cs.flavors.list(is_public=None)
else:
flavors = cs.flavors.list()
_print_flavor_list(flavors, args.extra_specs)
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Name or ID of the flavor to delete"))
def do_flavor_delete(cs, args):
"""Delete a specific flavor"""
flavorid = _find_flavor(cs, args.flavor)
cs.flavors.delete(flavorid)
_print_flavor_list([flavorid])
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Name or ID of flavor"))
def do_flavor_show(cs, args):
"""Show details about the given flavor."""
flavor = _find_flavor(cs, args.flavor)
_print_flavor(flavor)
@cliutils.arg(
'name',
metavar='<name>',
help=_("Name of the new flavor"))
@cliutils.arg(
'id',
metavar='<id>',
help=_("Unique ID (integer or UUID) for the new flavor."
" If specifying 'auto', a UUID will be generated as id"))
@cliutils.arg(
'ram',
metavar='<ram>',
help=_("Memory size in MB"))
@cliutils.arg(
'disk',
metavar='<disk>',
help=_("Disk size in GB"))
@cliutils.arg(
'--ephemeral',
metavar='<ephemeral>',
help=_("Ephemeral space size in GB (default 0)"),
default=0)
@cliutils.arg(
'vcpus',
metavar='<vcpus>',
help=_("Number of vcpus"))
@cliutils.arg(
'--swap',
metavar='<swap>',
help=_("Swap space size in MB (default 0)"),
default=0)
@cliutils.arg(
'--rxtx-factor',
metavar='<factor>',
help=_("RX/TX factor (default 1)"),
default=1.0)
@cliutils.arg(
'--is-public',
metavar='<is-public>',
help=_("Make flavor accessible to the public (default true)"),
type=lambda v: strutils.bool_from_string(v, True),
default=True)
def do_flavor_create(cs, args):
"""Create a new flavor"""
f = cs.flavors.create(args.name, args.ram, args.vcpus, args.disk, args.id,
args.ephemeral, args.swap, args.rxtx_factor,
args.is_public)
_print_flavor_list([f])
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Name or ID of flavor"))
@cliutils.arg(
'action',
metavar='<action>',
choices=['set', 'unset'],
help=_("Actions: 'set' or 'unset'"))
@cliutils.arg(
'metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help=_('Extra_specs to set/unset (only key is necessary on unset)'))
def do_flavor_key(cs, args):
"""Set or unset extra_spec for a flavor."""
flavor = _find_flavor(cs, args.flavor)
keypair = _extract_metadata(args)
if args.action == 'set':
flavor.set_keys(keypair)
elif args.action == 'unset':
flavor.unset_keys(keypair.keys())
@cliutils.arg(
'--flavor',
metavar='<flavor>',
help=_("Filter results by flavor name or ID."))
@cliutils.arg(
'--tenant', metavar='<tenant_id>',
help=_('Filter results by tenant ID.'))
def do_flavor_access_list(cs, args):
"""Print access information about the given flavor."""
if args.flavor and args.tenant:
raise exceptions.CommandError(_("Unable to filter results by "
"both --flavor and --tenant."))
elif args.flavor:
flavor = _find_flavor(cs, args.flavor)
if flavor.is_public:
raise exceptions.CommandError(_("Failed to get access list "
"for public flavor type."))
kwargs = {'flavor': flavor}
elif args.tenant:
kwargs = {'tenant': args.tenant}
else:
raise exceptions.CommandError(_("Unable to get all access lists. "
"Specify --flavor or --tenant"))
try:
access_list = cs.flavor_access.list(**kwargs)
except NotImplementedError as e:
raise exceptions.CommandError("%s" % str(e))
columns = ['Flavor_ID', 'Tenant_ID']
utils.print_list(access_list, columns)
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Flavor name or ID to add access for the given tenant."))
@cliutils.arg(
'tenant', metavar='<tenant_id>',
help=_('Tenant ID to add flavor access for.'))
def do_flavor_access_add(cs, args):
"""Add flavor access for the given tenant."""
flavor = _find_flavor(cs, args.flavor)
access_list = cs.flavor_access.add_tenant_access(flavor, args.tenant)
columns = ['Flavor_ID', 'Tenant_ID']
utils.print_list(access_list, columns)
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Flavor name or ID to remove access for the given tenant."))
@cliutils.arg(
'tenant', metavar='<tenant_id>',
help=_('Tenant ID to remove flavor access for.'))
def do_flavor_access_remove(cs, args):
"""Remove flavor access for the given tenant."""
flavor = _find_flavor(cs, args.flavor)
access_list = cs.flavor_access.remove_tenant_access(flavor, args.tenant)
columns = ['Flavor_ID', 'Tenant_ID']
utils.print_list(access_list, columns)
@cliutils.arg(
'project_id', metavar='<project_id>',
help=_('The ID of the project.'))
def do_scrub(cs, args):
"""Delete networks and security groups associated with a project."""
networks_list = cs.networks.list()
networks_list = [network for network in networks_list
if getattr(network, 'project_id', '') == args.project_id]
search_opts = {'all_tenants': 1}
groups = cs.security_groups.list(search_opts)
groups = [group for group in groups
if group.tenant_id == args.project_id]
for network in networks_list:
cs.networks.disassociate(network)
for group in groups:
cs.security_groups.delete(group)
@cliutils.arg(
'--fields',
default=None,
metavar='<fields>',
help='Comma-separated list of fields to display. '
'Use the show command to see which fields are available.')
def do_network_list(cs, args):
"""Print a list of available networks."""
network_list = cs.networks.list()
columns = ['ID', 'Label', 'Cidr']
formatters = {}
field_titles = []
if args.fields:
for field in args.fields.split(','):
field_title, formatter = utils._make_field_formatter(field, {})
field_titles.append(field_title)
formatters[field_title] = formatter
columns = columns + field_titles
utils.print_list(network_list, columns)
@cliutils.arg(
'network',
metavar='<network>',
help=_("uuid or label of network"))
def do_network_show(cs, args):
"""Show details about the given network."""
network = utils.find_resource(cs.networks, args.network)
utils.print_dict(network._info)
@cliutils.arg(
'network',
metavar='<network>',
help=_("uuid or label of network"))
def do_network_delete(cs, args):
"""Delete network by label or id."""
network = utils.find_resource(cs.networks, args.network)
network.delete()
@cliutils.arg(
'--host-only',
dest='host_only',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0)
@cliutils.arg(
'--project-only',
dest='project_only',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0)
@cliutils.arg(
'network',
metavar='<network>',
help="uuid of network")
def do_network_disassociate(cs, args):
"""Disassociate host and/or project from the given network."""
if args.host_only:
cs.networks.disassociate(args.network, True, False)
elif args.project_only:
cs.networks.disassociate(args.network, False, True)
else:
cs.networks.disassociate(args.network, True, True)
@cliutils.arg(
'network',
metavar='<network>',
help="uuid of network")
@cliutils.arg(
'host',
metavar='<host>',
help="Name of host")
def do_network_associate_host(cs, args):
"""Associate host with network."""
cs.networks.associate_host(args.network, args.host)
@cliutils.arg(
'network',
metavar='<network>',
help="uuid of network")
def do_network_associate_project(cs, args):
"""Associate project with network."""
cs.networks.associate_project(args.network)
def _filter_network_create_options(args):
valid_args = ['label', 'cidr', 'vlan_start', 'vpn_start', 'cidr_v6',
'gateway', 'gateway_v6', 'bridge', 'bridge_interface',
'multi_host', 'dns1', 'dns2', 'uuid', 'fixed_cidr',
'project_id', 'priority', 'vlan', 'mtu', 'dhcp_server',
'allowed_start', 'allowed_end']
kwargs = {}
for k, v in args.__dict__.items():
if k in valid_args and v is not None:
kwargs[k] = v
return kwargs
@cliutils.arg(
'label',
metavar='<network_label>',
help=_("Label for network"))
@cliutils.arg(
'--fixed-range-v4',
dest='cidr',
metavar='<x.x.x.x/yy>',
help=_("IPv4 subnet (ex: 10.0.0.0/8)"))
@cliutils.arg(
'--fixed-range-v6',
dest="cidr_v6",
help=_('IPv6 subnet (ex: fe80::/64'))
@cliutils.arg(
'--vlan',
dest='vlan',
type=int,
metavar='<vlan id>',
help=_("The vlan ID to be assigned to the project."))
@cliutils.arg(
'--vlan-start',
dest='vlan_start',
type=int,
metavar='<vlan start>',
help=_('First vlan ID to be assigned to the project. Subsequent vlan '
'IDs will be assigned incrementally.'))
@cliutils.arg(
'--vpn',
dest='vpn_start',
type=int,
metavar='<vpn start>',
help=_("vpn start"))
@cliutils.arg(
'--gateway',
dest="gateway",
help=_('gateway'))
@cliutils.arg(
'--gateway-v6',
dest="gateway_v6",
help=_('IPv6 gateway'))
@cliutils.arg(
'--bridge',
dest="bridge",
metavar='<bridge>',
help=_('VIFs on this network are connected to this bridge.'))
@cliutils.arg(
'--bridge-interface',
dest="bridge_interface",
metavar='<bridge interface>',
help=_('The bridge is connected to this interface.'))
@cliutils.arg(
'--multi-host',
dest="multi_host",
metavar="<'T'|'F'>",
help=_('Multi host'))
@cliutils.arg(
'--dns1',
dest="dns1",
metavar="<DNS Address>", help='First DNS')
@cliutils.arg(
'--dns2',
dest="dns2",
metavar="<DNS Address>",
help=_('Second DNS'))
@cliutils.arg(
'--uuid',
dest="uuid",
metavar="<network uuid>",
help=_('Network UUID'))
@cliutils.arg(
'--fixed-cidr',
dest="fixed_cidr",
metavar='<x.x.x.x/yy>',
help=_('IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)'))
@cliutils.arg(
'--project-id',
dest="project_id",
metavar="<project id>",
help=_('Project ID'))
@cliutils.arg(
'--priority',
dest="priority",
metavar="<number>",
help=_('Network interface priority'))
@cliutils.arg(
'--mtu',
dest="mtu",
type=int,
help=_('MTU for network'))
@cliutils.arg(
'--enable-dhcp',
dest="enable_dhcp",
metavar="<'T'|'F'>",
help=_('Enable dhcp'))
@cliutils.arg(
'--dhcp-server',
dest="dhcp_server",
help=_('Dhcp-server (defaults to gateway address)'))
@cliutils.arg(
'--share-address',
dest="share_address",
metavar="<'T'|'F'>",
help=_('Share address'))
@cliutils.arg(
'--allowed-start',
dest="allowed_start",
help=_('Start of allowed addresses for instances'))
@cliutils.arg(
'--allowed-end',
dest="allowed_end",
help=_('End of allowed addresses for instances'))
def do_network_create(cs, args):
"""Create a network."""
if not (args.cidr or args.cidr_v6):
raise exceptions.CommandError(
_("Must specify either fixed_range_v4 or fixed_range_v6"))
kwargs = _filter_network_create_options(args)
if args.multi_host is not None:
kwargs['multi_host'] = bool(args.multi_host == 'T' or
strutils.bool_from_string(args.multi_host))
if args.enable_dhcp is not None:
kwargs['enable_dhcp'] = bool(
args.enable_dhcp == 'T' or
strutils.bool_from_string(args.enable_dhcp))
if args.share_address is not None:
kwargs['share_address'] = bool(
args.share_address == 'T' or
strutils.bool_from_string(args.share_address))
cs.networks.create(**kwargs)
@cliutils.arg(
'--limit',
dest="limit",
metavar="<limit>",
help=_('Number of images to return per request.'))
def do_image_list(cs, _args):
"""Print a list of available images to boot from."""
limit = _args.limit
image_list = cs.images.list(limit=limit)
def parse_server_name(image):
try:
return image.server['id']
except (AttributeError, KeyError):
return ''
fmts = {'Server': parse_server_name}
utils.print_list(image_list, ['ID', 'Name', 'Status', 'Server'],
fmts, sortby_index=1)
@cliutils.arg(
'image',
metavar='<image>',
help=_("Name or ID of image"))
@cliutils.arg(
'action',
metavar='<action>',
choices=['set', 'delete'],
help=_("Actions: 'set' or 'delete'"))
@cliutils.arg(
'metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help=_('Metadata to add/update or delete (only key is necessary on '
'delete)'))
def do_image_meta(cs, args):
"""Set or Delete metadata on an image."""
image = _find_image(cs, args.image)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.images.set_meta(image, metadata)
elif args.action == 'delete':
cs.images.delete_meta(image, metadata.keys())
def _extract_metadata(args):
metadata = {}
for metadatum in args.metadata[0]:
# Can only pass the key in on 'delete'
# So this doesn't have to have '='
if metadatum.find('=') > -1:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
return metadata
def _print_image(image):
info = image._info.copy()
# ignore links, we don't need to present those
info.pop('links')
# try to replace a server entity to just an id
server = info.pop('server', None)
try:
info['server'] = server['id']
except (KeyError, TypeError):
pass
# break up metadata and display each on its own row
metadata = info.pop('metadata', {})
try:
for key, value in metadata.items():
_key = 'metadata %s' % key
info[_key] = value
except AttributeError:
pass
utils.print_dict(info)
def _print_flavor(flavor):
info = flavor._info.copy()
# ignore links, we don't need to present those
info.pop('links')
info.update({"extra_specs": _print_flavor_extra_specs(flavor)})
utils.print_dict(info)
@cliutils.arg(
'image',
metavar='<image>',
help=_("Name or ID of image"))
def do_image_show(cs, args):
"""Show details about the given image."""
image = _find_image(cs, args.image)
_print_image(image)
@cliutils.arg(
'image', metavar='<image>', nargs='+',
help=_('Name or ID of image(s).'))
def do_image_delete(cs, args):
"""Delete specified image(s)."""
for image in args.image:
try:
_find_image(cs, image).delete()
except Exception as e:
print(_("Delete for image %(image)s failed: %(e)s") %
{'image': image, 'e': e})
@cliutils.arg(
'--reservation-id',
dest='reservation_id',
metavar='<reservation-id>',
default=None,
help=_('Only return servers that match reservation-id.'))
@cliutils.arg(
'--reservation_id',
help=argparse.SUPPRESS)
@cliutils.arg(
'--ip',
dest='ip',
metavar='<ip-regexp>',
default=None,
help=_('Search with regular expression match by IP address.'))
@cliutils.arg(
'--ip6',
dest='ip6',
metavar='<ip6-regexp>',
default=None,
help=_('Search with regular expression match by IPv6 address.'))
@cliutils.arg(
'--name',
dest='name',
metavar='<name-regexp>',
default=None,
help=_('Search with regular expression match by name'))
@cliutils.arg(
'--instance-name',
dest='instance_name',
metavar='<name-regexp>',
default=None,
help=_('Search with regular expression match by server name.'))
@cliutils.arg(
'--instance_name',
help=argparse.SUPPRESS)
@cliutils.arg(
'--status',
dest='status',
metavar='<status>',
default=None,
help=_('Search by server status'))
@cliutils.arg(
'--flavor',
dest='flavor',
metavar='<flavor>',
default=None,
help=_('Search by flavor name or ID'))
@cliutils.arg(
'--image',
dest='image',
metavar='<image>',
default=None,
help=_('Search by image name or ID'))
@cliutils.arg(
'--host',
dest='host',
metavar='<hostname>',
default=None,
help=_('Search servers by hostname to which they are assigned (Admin '
'only).'))
@cliutils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=int(strutils.bool_from_string(
os.environ.get("ALL_TENANTS", 'false'), True)),
help=_('Display information from all tenants (Admin only).'))
@cliutils.arg(
'--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@cliutils.arg(
'--tenant',
# nova db searches by project_id
dest='tenant',
metavar='<tenant>',
nargs='?',
help=_('Display information from single tenant (Admin only). '
'The --all-tenants option must also be provided.'))
@cliutils.arg(
'--user',
dest='user',
metavar='<user>',
nargs='?',
help=_('Display information from single user (Admin only).'))
@cliutils.arg(
'--deleted',
dest='deleted',
action="store_true",
default=False,
help='Only display deleted servers (Admin only).')
@cliutils.arg(
'--fields',
default=None,
metavar='<fields>',
help=_('Comma-separated list of fields to display. '
'Use the show command to see which fields are available.'))
@cliutils.arg(
'--minimal',
dest='minimal',
action="store_true",
default=False,
help=_('Get only uuid and name.'))
@cliutils.arg(
'--sort',
dest='sort',
metavar='<key>[:<direction>]',
help=('Comma-separated list of sort keys and directions in the form'
' of <key>[:<asc|desc>]. The direction defaults to descending if'
' not specified.'))
def do_list(cs, args):
"""List active servers."""
imageid = None
flavorid = None
if args.image:
imageid = _find_image(cs, args.image).id
if args.flavor:
flavorid = _find_flavor(cs, args.flavor).id
# search by tenant or user only works with all_tenants
if args.tenant or args.user:
args.all_tenants = 1
search_opts = {
'all_tenants': args.all_tenants,
'reservation_id': args.reservation_id,
'ip': args.ip,
'ip6': args.ip6,
'name': args.name,
'image': imageid,
'flavor': flavorid,
'status': args.status,
'tenant_id': args.tenant,
'user_id': args.user,
'host': args.host,
'deleted': args.deleted,
'instance_name': args.instance_name}
filters = {'flavor': lambda f: f['id'],
'security_groups': utils._format_security_groups}
formatters = {}
field_titles = []
if args.fields:
for field in args.fields.split(','):
field_title, formatter = utils._make_field_formatter(field,
filters)
field_titles.append(field_title)
formatters[field_title] = formatter
id_col = 'ID'
detailed = not args.minimal
sort_keys = []
sort_dirs = []
if args.sort:
for sort in args.sort.split(','):
sort_key, _sep, sort_dir = sort.partition(':')
if not sort_dir:
sort_dir = 'desc'
elif sort_dir not in ('asc', 'desc'):
raise exceptions.CommandError(_(
'Unknown sort direction: %s') % sort_dir)
sort_keys.append(sort_key)
sort_dirs.append(sort_dir)
servers = cs.servers.list(detailed=detailed,
search_opts=search_opts,
sort_keys=sort_keys,
sort_dirs=sort_dirs)
convert = [('OS-EXT-SRV-ATTR:host', 'host'),
('OS-EXT-STS:task_state', 'task_state'),
('OS-EXT-SRV-ATTR:instance_name', 'instance_name'),
('OS-EXT-STS:power_state', 'power_state'),
('hostId', 'host_id')]
_translate_keys(servers, convert)
_translate_extended_states(servers)
if args.minimal:
columns = [
id_col,
'Name']
elif field_titles:
columns = [id_col] + field_titles
else:
columns = [
id_col,
'Name',
'Status',
'Task State',
'Power State',
'Networks'
]
# If getting the data for all tenants, print
# Tenant ID as well
if search_opts['all_tenants']:
columns.insert(2, 'Tenant ID')
formatters['Networks'] = utils._format_servers_list_networks
sortby_index = 1
if args.sort:
sortby_index = None
utils.print_list(servers, columns,
formatters, sortby_index=sortby_index)
@cliutils.arg(
'--hard',
dest='reboot_type',
action='store_const',
const=servers.REBOOT_HARD,
default=servers.REBOOT_SOFT,
help=_('Perform a hard reboot (instead of a soft one).'))
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Poll until reboot is complete.'))
def do_reboot(cs, args):
"""Reboot a server."""
server = _find_server(cs, args.server)
server.reboot(args.reboot_type)
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'rebooting', ['active'],
show_progress=False)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg('image', metavar='<image>', help=_("Name or ID of new image."))
@cliutils.arg(
'--rebuild-password',
dest='rebuild_password',
metavar='<rebuild-password>',
default=False,
help=_("Set the provided admin password on the rebuilt server."))
@cliutils.arg(
'--rebuild_password',
help=argparse.SUPPRESS)
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Report the server rebuild progress until it completes.'))
@cliutils.arg(
'--minimal',
dest='minimal',
action="store_true",
default=False,
help=_('Skips flavor/image lookups when showing servers'))
@cliutils.arg(
'--preserve-ephemeral',
action="store_true",
default=False,
help='Preserve the default ephemeral storage partition on rebuild.')
@cliutils.arg(
'--name',
metavar='<name>',
default=None,
help=_('Name for the new server'))
@cliutils.arg(
'--meta',
metavar="<key=value>",
action='append',
default=[],
help=_("Record arbitrary key/value metadata to /meta_data.json "
"on the metadata server. Can be specified multiple times."))
@cliutils.arg(
'--file',
metavar="<dst-path=src-path>",
action='append',
dest='files',
default=[],
help=_("Store arbitrary files from <src-path> locally to <dst-path> "
"on the new server. You may store up to 5 files."))
def do_rebuild(cs, args):
"""Shutdown, re-image, and re-boot a server."""
server = _find_server(cs, args.server)
image = _find_image(cs, args.image)
if args.rebuild_password is not False:
_password = args.rebuild_password
else:
_password = None
kwargs = utils.get_resource_manager_extra_kwargs(do_rebuild, args)
kwargs['preserve_ephemeral'] = args.preserve_ephemeral
kwargs['name'] = args.name
meta = dict(v.split('=', 1) for v in args.meta)
kwargs['meta'] = meta
files = {}
for f in args.files:
try:
dst, src = f.split('=', 1)
with open(src, 'r') as s:
files[dst] = s.read()
except IOError as e:
raise exceptions.CommandError(_("Can't open '%(src)s': %(exc)s") %
{'src': src, 'exc': e})
except ValueError:
raise exceptions.CommandError(_("Invalid file argument '%s'. "
"File arguments must be of the "
"form '--file "
"<dst-path=src-path>'") % f)
kwargs['files'] = files
server = server.rebuild(image, _password, **kwargs)
_print_server(cs, args, server)
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'rebuilding', ['active'])
@cliutils.arg(
'server', metavar='<server>',
help=_('Name (old name) or ID of server.'))
@cliutils.arg('name', metavar='<name>', help=_('New name for the server.'))
def do_rename(cs, args):
"""Rename a server."""
_find_server(cs, args.server).update(name=args.name)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(
'flavor',
metavar='<flavor>',
help=_("Name or ID of new flavor."))
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Report the server resize progress until it completes.'))
def do_resize(cs, args):
"""Resize a server."""
server = _find_server(cs, args.server)
flavor = _find_flavor(cs, args.flavor)
kwargs = utils.get_resource_manager_extra_kwargs(do_resize, args)
server.resize(flavor, **kwargs)
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'resizing',
['active', 'verify_resize'])
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_resize_confirm(cs, args):
"""Confirm a previous resize."""
_find_server(cs, args.server).confirm_resize()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_resize_revert(cs, args):
"""Revert a previous resize (and return to the previous VM)."""
_find_server(cs, args.server).revert_resize()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Report the server migration progress until it completes.'))
def do_migrate(cs, args):
"""Migrate a server. The new host will be selected by the scheduler."""
server = _find_server(cs, args.server)
server.migrate()
if args.poll:
_poll_for_status(cs.servers.get, server.id, 'migrating',
['active', 'verify_resize'])
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_pause(cs, args):
"""Pause a server."""
_find_server(cs, args.server).pause()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_unpause(cs, args):
"""Unpause a server."""
_find_server(cs, args.server).unpause()
@cliutils.arg(
'server',
metavar='<server>', nargs='+',
help=_('Name or ID of server(s).'))
def do_stop(cs, args):
"""Stop the server(s)."""
utils.do_action_on_many(
lambda s: _find_server(cs, s).stop(),
args.server,
_("Request to stop server %s has been accepted."),
_("Unable to stop the specified server(s)."))
@cliutils.arg(
'server',
metavar='<server>', nargs='+',
help=_('Name or ID of server(s).'))
def do_start(cs, args):
"""Start the server(s)."""
utils.do_action_on_many(
lambda s: _find_server(cs, s).start(),
args.server,
_("Request to start server %s has been accepted."),
_("Unable to start the specified server(s)."))
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_lock(cs, args):
"""Lock a server. A normal (non-admin) user will not be able to execute
actions on a locked server.
"""
_find_server(cs, args.server).lock()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_unlock(cs, args):
"""Unlock a server."""
_find_server(cs, args.server).unlock()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_suspend(cs, args):
"""Suspend a server."""
_find_server(cs, args.server).suspend()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_resume(cs, args):
"""Resume a server."""
_find_server(cs, args.server).resume()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(
'--password',
metavar='<password>',
dest='password',
help=_('The admin password to be set in the rescue environment.'))
@cliutils.arg(
'--image',
metavar='<image>',
dest='image',
help=_('The image to rescue with.'))
def do_rescue(cs, args):
"""Reboots a server into rescue mode, which starts the machine
from either the initial image or a specified image, attaching the current
boot disk as secondary.
"""
kwargs = {}
if args.image:
kwargs['image'] = _find_image(cs, args.image)
if args.password:
kwargs['password'] = args.password
utils.print_dict(_find_server(cs, args.server).rescue(**kwargs)[1])
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_unrescue(cs, args):
"""Restart the server from normal boot disk again."""
_find_server(cs, args.server).unrescue()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_shelve(cs, args):
"""Shelve a server."""
_find_server(cs, args.server).shelve()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_shelve_offload(cs, args):
"""Remove a shelved server from the compute node."""
_find_server(cs, args.server).shelve_offload()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_unshelve(cs, args):
"""Unshelve a server."""
_find_server(cs, args.server).unshelve()
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_diagnostics(cs, args):
"""Retrieve server diagnostics."""
server = _find_server(cs, args.server)
utils.print_dict(cs.servers.diagnostics(server)[1], wrap=80)
@cliutils.arg(
'server', metavar='<server>',
help=_('Name or ID of a server for which the network cache should '
'be refreshed from neutron (Admin only).'))
def do_refresh_network(cs, args):
"""Refresh server network information."""
server = _find_server(cs, args.server)
cs.server_external_events.create([{'server_uuid': server.id,
'name': 'network-changed'}])
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_root_password(cs, args):
"""
Change the admin password for a server.
"""
server = _find_server(cs, args.server)
p1 = getpass.getpass('New password: ')
p2 = getpass.getpass('Again: ')
if p1 != p2:
raise exceptions.CommandError(_("Passwords do not match."))
server.change_password(p1)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg('name', metavar='<name>', help=_('Name of snapshot.'))
@cliutils.arg(
'--show',
dest='show',
action="store_true",
default=False,
help=_('Print image info.'))
@cliutils.arg(
'--poll',
dest='poll',
action="store_true",
default=False,
help=_('Report the snapshot progress and poll until image creation is '
'complete.'))
def do_image_create(cs, args):
"""Create a new image by taking a snapshot of a running server."""
server = _find_server(cs, args.server)
image_uuid = cs.servers.create_image(server, args.name)
if args.poll:
_poll_for_status(cs.images.get, image_uuid, 'snapshotting',
['active'])
# NOTE(sirp): A race-condition exists between when the image finishes
# uploading and when the servers's `task_state` is cleared. To account
# for this, we need to poll a second time to ensure the `task_state` is
# cleared before returning, ensuring that a snapshot taken immediately
# after this function returns will succeed.
#
# A better long-term solution will be to separate 'snapshotting' and
# 'image-uploading' in Nova and clear the task-state once the VM
# snapshot is complete but before the upload begins.
task_state_field = "OS-EXT-STS:task_state"
if hasattr(server, task_state_field):
_poll_for_status(cs.servers.get, server.id, 'image_snapshot',
[None], status_field=task_state_field,
show_progress=False, silent=True)
if args.show:
_print_image(cs.images.get(image_uuid))
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg('name', metavar='<name>', help=_('Name of the backup image.'))
@cliutils.arg(
'backup_type', metavar='<backup-type>',
help=_('The backup type, like "daily" or "weekly".'))
@cliutils.arg(
'rotation', metavar='<rotation>',
help=_('Int parameter representing how many backups to keep '
'around.'))
def do_backup(cs, args):
"""Backup a server by creating a 'backup' type snapshot."""
_find_server(cs, args.server).backup(args.name,
args.backup_type,
args.rotation)
@cliutils.arg(
'server',
metavar='<server>',
help=_("Name or ID of server"))
@cliutils.arg(
'action',
metavar='<action>',
choices=['set', 'delete'],
help=_("Actions: 'set' or 'delete'"))
@cliutils.arg(
'metadata',
metavar='<key=value>',
nargs='+',
action='append',
default=[],
help=_('Metadata to set or delete (only key is necessary on delete)'))
def do_meta(cs, args):
"""Set or Delete metadata on a server."""
server = _find_server(cs, args.server)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.servers.set_meta(server, metadata)
elif args.action == 'delete':
cs.servers.delete_meta(server, sorted(metadata.keys(), reverse=True))
def _print_server(cs, args, server=None):
# By default when searching via name we will do a
# findall(name=blah) and due a REST /details which is not the same
# as a .get() and doesn't get the information about flavors and
# images. This fix it as we redo the call with the id which does a
# .get() to get all informations.
if not server:
server = _find_server(cs, args.server)
minimal = getattr(args, "minimal", False)
networks = server.networks
info = server._info.copy()
for network_label, address_list in networks.items():
info['%s network' % network_label] = ', '.join(address_list)
flavor = info.get('flavor', {})
flavor_id = flavor.get('id', '')
if minimal:
info['flavor'] = flavor_id
else:
info['flavor'] = '%s (%s)' % (_find_flavor(cs, flavor_id).name,
flavor_id)
if 'security_groups' in info:
# when we have multiple nics the info will include the
# security groups N times where N == number of nics. Be nice
# and only display it once.
info['security_groups'] = ', '.join(
sorted(set(group['name'] for group in info['security_groups'])))
image = info.get('image', {})
if image:
image_id = image.get('id', '')
if minimal:
info['image'] = image_id
else:
try:
info['image'] = '%s (%s)' % (_find_image(cs, image_id).name,
image_id)
except Exception:
info['image'] = '%s (%s)' % (_("Image not found"), image_id)
else: # Booted from volume
info['image'] = _("Attempt to boot from volume - no image supplied")
info.pop('links', None)
info.pop('addresses', None)
utils.print_dict(info)
@cliutils.arg(
'--minimal',
dest='minimal',
action="store_true",
default=False,
help=_('Skips flavor/image lookups when showing servers'))
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
def do_show(cs, args):
"""Show details about the given server."""
_print_server(cs, args)
@cliutils.arg(
'server', metavar='<server>', nargs='+',
help=_('Name or ID of server(s).'))
def do_delete(cs, args):
"""Immediately shut down and delete specified server(s)."""
find_args = {'all_tenants': '1'}
utils.do_action_on_many(
lambda s: _find_server(cs, s, **find_args).delete(),
args.server,
_("Request to delete server %s has been accepted."),
_("Unable to delete the specified server(s)."))
def _find_server(cs, server, **find_args):
"""Get a server by name or ID."""
return utils.find_resource(cs.servers, server, **find_args)
def _find_image(cs, image):
"""Get an image by name or ID."""
return utils.find_resource(cs.images, image)
def _find_flavor(cs, flavor):
"""Get a flavor by name, ID, or RAM size."""
try:
# isinstance() is being used to check if flavor is an instance of
# integer. It will help us to check if the user has entered flavor
# name or flavorid. If flavor name has been entered it is being
# converted to lowercase using lower(). Incase it is an ID the user
# has passed it will not go through the "flavor = flavor.lower()"
# code.The reason for checking if it is a flavor name or flavorid is
# that int has no lower() so it will give an error.
if isinstance(flavor, six.integer_types):
pass
else:
flavor = flavor.lower()
return utils.find_resource(cs.flavors, flavor, is_public=None)
except exceptions.NotFound:
return cs.flavors.find(ram=flavor)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(
'network_id',
metavar='<network-id>',
help='Network ID.')
def do_add_fixed_ip(cs, args):
"""Add new IP address on a network to server."""
server = _find_server(cs, args.server)
server.add_fixed_ip(args.network_id)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg('address', metavar='<address>', help=_('IP Address.'))
def do_remove_fixed_ip(cs, args):
"""Remove an IP address from a server."""
server = _find_server(cs, args.server)
server.remove_fixed_ip(args.address)
def _find_volume(cs, volume):
"""Get a volume by name or ID."""
return utils.find_resource(cs.volumes, volume)
def _find_volume_snapshot(cs, snapshot):
"""Get a volume snapshot by name or ID."""
return utils.find_resource(cs.volume_snapshots, snapshot)
def _print_volume(volume):
utils.print_dict(volume._info)
def _print_volume_snapshot(snapshot):
utils.print_dict(snapshot._info)
def _translate_volume_keys(collection):
_translate_keys(collection,
[('displayName', 'display_name'),
('volumeType', 'volume_type')])
def _translate_volume_snapshot_keys(collection):
_translate_keys(collection,
[('displayName', 'display_name'),
('volumeId', 'volume_id')])
def _translate_availability_zone_keys(collection):
_translate_keys(collection,
[('zoneName', 'name'), ('zoneState', 'status')])
@cliutils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=int(strutils.bool_from_string(
os.environ.get("ALL_TENANTS", 'false'), True)),
help=_('Display information from all tenants (Admin only).'))
@cliutils.arg(
'--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
def do_volume_list(cs, args):
"""List all the volumes."""
search_opts = {'all_tenants': args.all_tenants}
volumes = cs.volumes.list(search_opts=search_opts)
_translate_volume_keys(volumes)
# Create a list of servers to which the volume is attached
for vol in volumes:
servers = [s.get('server_id') for s in vol.attachments]
setattr(vol, 'attached_to', ','.join(map(str, servers)))
utils.print_list(volumes, ['ID', 'Status', 'Display Name',
'Size', 'Volume Type', 'Attached to'])
@cliutils.arg(
'volume',
metavar='<volume>',
help=_('Name or ID of the volume.'))
def do_volume_show(cs, args):
"""Show details about a volume."""
volume = _find_volume(cs, args.volume)
_print_volume(volume)
@cliutils.arg(
'size',
metavar='<size>',
type=int,
help=_('Size of volume in GB'))
@cliutils.arg(
'--snapshot-id',
metavar='<snapshot-id>',
default=None,
help=_('Optional snapshot id to create the volume from. (Default=None)'))
@cliutils.arg(
'--snapshot_id',
help=argparse.SUPPRESS)
@cliutils.arg(
'--image-id',
metavar='<image-id>',
help=_('Optional image id to create the volume from. (Default=None)'),
default=None)
@cliutils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help=_('Optional volume name. (Default=None)'))
@cliutils.arg(
'--display_name',
help=argparse.SUPPRESS)
@cliutils.arg(
'--display-description',
metavar='<display-description>',
default=None,
help=_('Optional volume description. (Default=None)'))
@cliutils.arg(
'--display_description',
help=argparse.SUPPRESS)
@cliutils.arg(
'--volume-type',
metavar='<volume-type>',
default=None,
help=_('Optional volume type. (Default=None)'))
@cliutils.arg(
'--volume_type',
help=argparse.SUPPRESS)
@cliutils.arg(
'--availability-zone', metavar='<availability-zone>',
help=_('Optional Availability Zone for volume. (Default=None)'),
default=None)
def do_volume_create(cs, args):
"""Add a new volume."""
volume = cs.volumes.create(args.size,
args.snapshot_id,
args.display_name,
args.display_description,
args.volume_type,
args.availability_zone,
imageRef=args.image_id)
_print_volume(volume)
@cliutils.arg(
'volume',
metavar='<volume>', nargs='+',
help=_('Name or ID of the volume(s) to delete.'))
def do_volume_delete(cs, args):
"""Remove volume(s)."""
for volume in args.volume:
try:
_find_volume(cs, volume).delete()
except Exception as e:
print(_("Delete for volume %(volume)s failed: %(e)s") %
{'volume': volume, 'e': e})
@cliutils.arg(
'server',
metavar='<server>',
help=_('Name or ID of server.'))
@cliutils.arg(
'volume',
metavar='<volume>',
help=_('ID of the volume to attach.'))
@cliutils.arg(
'device', metavar='<device>', default=None, nargs='?',
help=_('Name of the device e.g. /dev/vdb. '
'Use "auto" for autoassign (if supported)'))
def do_volume_attach(cs, args):
"""Attach a volume to a server."""
if args.device == 'auto':
args.device = None
volume = cs.volumes.create_server_volume(_find_server(cs, args.server).id,
args.volume,
args.device)
_print_volume(volume)
@cliutils.arg(
'server',
metavar='<server>',
help=_('Name or ID of server.'))
@cliutils.arg(
'attachment_id',
metavar='<attachment>',
help=_('Attachment ID of the volume.'))
@cliutils.arg(
'new_volume',
metavar='<volume>',
help=_('ID of the volume to attach.'))
def do_volume_update(cs, args):
"""Update volume attachment."""
cs.volumes.update_server_volume(_find_server(cs, args.server).id,
args.attachment_id,
args.new_volume)
@cliutils.arg(
'server',
metavar='<server>',
help=_('Name or ID of server.'))
@cliutils.arg(
'attachment_id',
metavar='<volume>',
help=_('ID of the volume to detach.'))
def do_volume_detach(cs, args):
"""Detach a volume from a server."""
cs.volumes.delete_server_volume(_find_server(cs, args.server).id,
args.attachment_id)
def do_volume_snapshot_list(cs, _args):
"""List all the snapshots."""
snapshots = cs.volume_snapshots.list()
_translate_volume_snapshot_keys(snapshots)
utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name',
'Size'])
@cliutils.arg(
'snapshot',
metavar='<snapshot>',
help=_('Name or ID of the snapshot.'))
def do_volume_snapshot_show(cs, args):
"""Show details about a snapshot."""
snapshot = _find_volume_snapshot(cs, args.snapshot)
_print_volume_snapshot(snapshot)
@cliutils.arg(
'volume_id',
metavar='<volume-id>',
help=_('ID of the volume to snapshot'))
@cliutils.arg(
'--force',
metavar='<True|False>',
help=_('Optional flag to indicate whether to snapshot a volume even if '
'its attached to a server. (Default=False)'),
default=False)
@cliutils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help=_('Optional snapshot name. (Default=None)'))
@cliutils.arg(
'--display_name',
help=argparse.SUPPRESS)
@cliutils.arg(
'--display-description',
metavar='<display-description>',
default=None,
help=_('Optional snapshot description. (Default=None)'))
@cliutils.arg(
'--display_description',
help=argparse.SUPPRESS)
def do_volume_snapshot_create(cs, args):
"""Add a new snapshot."""
snapshot = cs.volume_snapshots.create(args.volume_id,
args.force,
args.display_name,
args.display_description)
_print_volume_snapshot(snapshot)
@cliutils.arg(
'snapshot',
metavar='<snapshot>',
help=_('Name or ID of the snapshot to delete.'))
def do_volume_snapshot_delete(cs, args):
"""Remove a snapshot."""
snapshot = _find_volume_snapshot(cs, args.snapshot)
snapshot.delete()
def _print_volume_type_list(vtypes):
utils.print_list(vtypes, ['ID', 'Name'])
def do_volume_type_list(cs, args):
"""Print a list of available 'volume types'."""
vtypes = cs.volume_types.list()
_print_volume_type_list(vtypes)
@cliutils.arg(
'name',
metavar='<name>',
help=_("Name of the new volume type"))
def do_volume_type_create(cs, args):
"""Create a new volume type."""
vtype = cs.volume_types.create(args.name)
_print_volume_type_list([vtype])
@cliutils.arg(
'id',
metavar='<id>',
help=_("Unique ID of the volume type to delete"))
def do_volume_type_delete(cs, args):
"""Delete a specific volume type."""
cs.volume_types.delete(args.id)
@cliutils.arg('server', metavar='<server>', help=_('Name or ID of server.'))
@cliutils.arg(