# 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 logging import os import iso8601 from novaclient import api_versions from novaclient.v2 import servers from openstack import exceptions as sdk_exceptions from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common as network_common LOG = logging.getLogger(__name__) IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)' 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, str): 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'] msg = _("ERROR: No %(type)s IP version %(family)s address found") raise exceptions.CommandError( msg % {"type": address_type, "family": ip_address_family} ) def _prefix_checked_value(prefix): def func(value): if ',' in value or '=' in value: msg = _("Invalid argument %s, " "characters ',' and '=' are not allowed") % value raise argparse.ArgumentTypeError(msg) return prefix + value return func def _prep_server_detail(compute_client, image_client, server, refresh=True): """Prepare the detailed server dict for printing :param compute_client: a compute client instance :param image_client: an image client instance :param server: a Server resource :param refresh: Flag indicating if ``server`` is already the latest version or if it needs to be refreshed, for example when showing the latest details of a server after creating it. :rtype: a dict of server details """ info = server.to_dict() if refresh: server = utils.find_resource(compute_client.servers, info['id']) info.update(server.to_dict()) # Convert the image blob to a name image_info = info.get('image', {}) if image_info: image_id = image_info.get('id', '') try: image = image_client.get_image(image_id) info['image'] = "%s (%s)" % (image.name, image_id) except Exception: info['image'] = image_id else: # NOTE(melwitt): An server booted from a volume will have no image # associated with it. We fill in the image with "N/A (booted from # volume)" to help users who want to be able to grep for # boot-from-volume servers when using the CLI. info['image'] = IMAGE_STRING_FOR_BFV # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) # Microversion 2.47 puts the embedded flavor into the server response # body but omits the id, so if not present we just expose the flavor # dict in the server output. if 'id' in flavor_info: 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 else: info['flavor'] = utils.format_dict(flavor_info) if 'os-extended-volumes:volumes_attached' in info: info.update( { 'volumes_attached': format_columns.ListDictColumn( info.pop('os-extended-volumes:volumes_attached')) } ) if 'security_groups' in info: info.update( { 'security_groups': format_columns.ListDictColumn( info.pop('security_groups')) } ) # 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' if not info['metadata']: info.update( {'properties': utils.format_dict(info.pop('metadata'))} ) else: info.update( {'properties': format_columns.DictColumn(info.pop('metadata'))} ) # Migrate tenant_id to project_id naming if 'tenant_id' in info: info['project_id'] = info.pop('tenant_id') # Map power state num to meaningful string if 'OS-EXT-STS:power_state' in info: info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( info['OS-EXT-STS:power_state']) # Remove values that are long and not too useful info.pop('links', None) return info class AddFixedIP(command.Command): _description = _("Add fixed IP address to server") def get_parser(self, prog_name): parser = super(AddFixedIP, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to receive the fixed IP address (name or ID)"), ) parser.add_argument( "network", metavar="<network>", help=_( "Network to allocate the fixed IP address from (name or ID)" ), ) parser.add_argument( "--fixed-ip-address", metavar="<ip-address>", help=_("Requested fixed IP address"), ) parser.add_argument( '--tag', metavar='<tag>', help=_( 'Tag for the attached interface. ' '(supported by --os-compute-api-version 2.52 or above)' ) ) 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) network = compute_client.api.network_find(parsed_args.network) kwargs = { 'port_id': None, 'net_id': network['id'], 'fixed_ip': parsed_args.fixed_ip_address, } if parsed_args.tag: if compute_client.api_version < api_versions.APIVersion('2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) kwargs['tag'] = parsed_args.tag server.interface_attach(**kwargs) class AddFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Add floating IP address to server") def update_parser_common(self, parser): parser.add_argument( "server", metavar="<server>", help=_("Server to receive the floating IP address (name or ID)"), ) parser.add_argument( "ip_address", metavar="<ip-address>", help=_("Floating IP address to assign to the first available " "server port (IP only)"), ) parser.add_argument( "--fixed-ip-address", metavar="<ip-address>", help=_( "Fixed IP address to associate with this floating IP address. " "The first server port containing the fixed IP address will " "be used" ), ) return parser def take_action_network(self, client, parsed_args): compute_client = self.app.client_manager.compute attrs = {} obj = client.find_ip( parsed_args.ip_address, ignore_missing=False, ) server = utils.find_resource( compute_client.servers, parsed_args.server, ) ports = list(client.ports(device_id=server.id)) if not ports: msg = _('No attached ports found to associate floating IP with') raise exceptions.CommandError(msg) # If the fixed IP address was specified, we need to find the # corresponding port. if parsed_args.fixed_ip_address: fip_address = parsed_args.fixed_ip_address attrs['fixed_ip_address'] = fip_address for port in ports: for ip in port.fixed_ips: if ip['ip_address'] == fip_address: attrs['port_id'] = port.id break else: continue break if 'port_id' not in attrs: msg = _('No port found for fixed IP address %s') raise exceptions.CommandError(msg % fip_address) client.update_ip(obj, **attrs) else: # It's possible that one or more ports are not connected to a # router and thus could fail association with a floating IP. # Try each port until one succeeds. If none succeed, re-raise the # last exception. error = None for port in ports: attrs['port_id'] = port.id try: client.update_ip(obj, **attrs) except sdk_exceptions.NotFoundException as exp: # 404 ExternalGatewayForFloatingIPNotFound from neutron LOG.info('Skipped port %s because it is not attached to ' 'an external gateway', port.id) error = exp continue else: error = None break if error: raise error def take_action_compute(self, client, parsed_args): client.api.floating_ip_add( parsed_args.server, parsed_args.ip_address, fixed_address=parsed_args.fixed_ip_address, ) class AddPort(command.Command): _description = _("Add port to server") def get_parser(self, prog_name): parser = super(AddPort, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to add the port to (name or ID)"), ) parser.add_argument( "port", metavar="<port>", help=_("Port to add to the server (name or ID)"), ) parser.add_argument( '--tag', metavar='<tag>', help=_( "Tag for the attached interface. " "(Supported by API versions '2.49' - '2.latest')" ) ) 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 self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network port_id = network_client.find_port( parsed_args.port, ignore_missing=False).id else: port_id = parsed_args.port kwargs = { 'port_id': port_id, 'net_id': None, 'fixed_ip': None, } if parsed_args.tag: if compute_client.api_version < api_versions.APIVersion("2.49"): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) kwargs['tag'] = parsed_args.tag server.interface_attach(**kwargs) class AddNetwork(command.Command): _description = _("Add network to server") def get_parser(self, prog_name): parser = super(AddNetwork, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to add the network to (name or ID)"), ) parser.add_argument( "network", metavar="<network>", help=_("Network to add to the server (name or ID)"), ) parser.add_argument( '--tag', metavar='<tag>', help=_( 'Tag for the attached interface. ' '(supported by --os-compute-api-version 2.49 or above)' ), ) 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 self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network net_id = network_client.find_network( parsed_args.network, ignore_missing=False).id else: net_id = parsed_args.network kwargs = { 'port_id': None, 'net_id': net_id, 'fixed_ip': None, } if parsed_args.tag: if compute_client.api_version < api_versions.APIVersion('2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) kwargs['tag'] = parsed_args.tag server.interface_attach(**kwargs) class AddServerSecurityGroup(command.Command): _description = _("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 = compute_client.api.security_group_find( parsed_args.group, ) server.add_security_group(security_group['id']) class AddServerVolume(command.Command): _description = _( "Add volume to server. " "Specify ``--os-compute-api-version 2.20`` or higher to add a volume " "to a server with status ``SHELVED`` or ``SHELVED_OFFLOADED``.") 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'), ) parser.add_argument( '--tag', metavar='<tag>', help=_( "Tag for the attached volume. " "(Supported by API versions '2.49' - '2.latest')" ), ) termination_group = parser.add_mutually_exclusive_group() termination_group.add_argument( '--enable-delete-on-termination', action='store_true', help=_( "Specify if the attached volume should be deleted when the " "server is destroyed. " "(Supported by API versions '2.79' - '2.latest')" ), ) termination_group.add_argument( '--disable-delete-on-termination', action='store_true', help=_( "Specify if the attached volume should not be deleted when " "the server is destroyed. " "(Supported by API versions '2.79' - '2.latest')" ), ) 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, ) kwargs = { "device": parsed_args.device } if parsed_args.tag: if compute_client.api_version < api_versions.APIVersion('2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) kwargs['tag'] = parsed_args.tag if parsed_args.enable_delete_on_termination: if compute_client.api_version < api_versions.APIVersion('2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --enable-delete-on-termination option.' ) raise exceptions.CommandError(msg) kwargs['delete_on_termination'] = True if parsed_args.disable_delete_on_termination: if compute_client.api_version < api_versions.APIVersion('2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --disable-delete-on-termination option.' ) raise exceptions.CommandError(msg) kwargs['delete_on_termination'] = False compute_client.volumes.create_server_volume( server.id, volume.id, **kwargs ) class CreateServer(command.ShowOne): _description = _("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 boot disk from this image (name or ID)'), ) disk_group.add_argument( '--image-property', metavar='<key=value>', action=parseractions.KeyValueAction, help=_("Image property to be matched"), ) disk_group.add_argument( '--volume', metavar='<volume>', help=_( 'Create server using this volume as the boot disk (name or ID)' '\n' 'This option automatically creates a block device mapping ' 'with a boot index of 0. On many hypervisors (libvirt/kvm ' 'for example) this will be device vda. Do not create a ' 'duplicate mapping using --block-device-mapping for this ' 'volume.' ), ) parser.add_argument( '--password', metavar='<password>', help=_("Set the password to this server"), ) 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>', action='append', default=[], help=_( 'Security group to assign to this server (name or ID) ' '(repeat option to set multiple groups)' ), ) parser.add_argument( '--key-name', metavar='<key-name>', help=_('Keypair to inject into this server'), ) parser.add_argument( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, help=_( 'Set a property on this server ' '(repeat option to set multiple values)' ), ) parser.add_argument( '--file', metavar='<dest-filename=source-filename>', action='append', default=[], help=_( 'File to inject into image before boot ' '(repeat option to set multiple files)' ), ) parser.add_argument( '--user-data', metavar='<user-data>', help=_('User data file to serve from the metadata server'), ) parser.add_argument( '--description', metavar='<description>', help=_( 'Set description for the server ' '(supported by --os-compute-api-version 2.19 or above)' ), ) parser.add_argument( '--availability-zone', metavar='<zone-name>', help=_('Select an availability zone for the server'), ) parser.add_argument( '--host', metavar='<host>', help=_( 'Requested host to create servers. ' '(admin only) ' '(supported by --os-compute-api-version 2.74 or above)' ), ) parser.add_argument( '--hypervisor-hostname', metavar='<hypervisor-hostname>', help=_( 'Requested hypervisor hostname to create servers. ' '(admin only) ' '(supported by --os-compute-api-version 2.74 or above)' ), ) parser.add_argument( '--boot-from-volume', metavar='<volume-size>', type=int, help=_( 'When used in conjunction with the ``--image`` or ' '``--image-property`` option, this option automatically ' 'creates a block device mapping with a boot index of 0 ' 'and tells the compute service to create a volume of the ' 'given size (in GB) from the specified image and use it ' 'as the root disk of the server. The root volume will not ' 'be deleted when the server is deleted. This option is ' 'mutually exclusive with the ``--volume`` option.' ) ) parser.add_argument( '--block-device-mapping', metavar='<dev-name=mapping>', action=parseractions.KeyValueAction, default={}, # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. help=_( 'Create a block device on the server.\n' 'Block device mapping in the format\n' '<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n' '<dev-name>: block device name, like: vdb, xvdc ' '(required)\n' '<id>: Name or ID of the volume, volume snapshot or image ' '(required)\n' '<type>: volume, snapshot or image; default: volume ' '(optional)\n' '<size(GB)>: volume size if create from image or snapshot ' '(optional)\n' '<delete-on-terminate>: true or false; default: false ' '(optional)\n' ), ) parser.add_argument( '--nic', metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr," "port-id=port-uuid,auto,none>", action='append', 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), " "none: (v2.37+) no network is attached, " "auto: (v2.37+) the compute service will automatically " "allocate a network. Specifying a --nic of auto or none " "cannot be used with any other --nic value." ), ) parser.add_argument( '--network', metavar="<network>", action='append', dest='nic', type=_prefix_checked_value('net-id='), help=_( "Create a NIC on the server and connect it to network. " "Specify option multiple times to create multiple NICs. " "This is a wrapper for the '--nic net-id=<network>' " "parameter that provides simple syntax for the standard " "use case of connecting a new server to a given network. " "For more advanced use cases, refer to the '--nic' " "parameter." ), ) parser.add_argument( '--port', metavar="<port>", action='append', dest='nic', type=_prefix_checked_value('port-id='), help=_( "Create a NIC on the server and connect it to port. " "Specify option multiple times to create multiple NICs. " "This is a wrapper for the '--nic port-id=<port>' " "parameter that provides simple syntax for the standard " "use case of connecting a new server to a given port. For " "more advanced use cases, refer to the '--nic' parameter." ), ) parser.add_argument( '--hint', metavar='<key=value>', action=parseractions.KeyValueAppendAction, default={}, help=_('Hints for the scheduler (optional extension)'), ) config_drive_group = parser.add_mutually_exclusive_group() config_drive_group.add_argument( '--use-config-drive', action='store_true', dest='config_drive', help=_("Enable config drive."), ) config_drive_group.add_argument( '--no-config-drive', action='store_false', dest='config_drive', help=_("Disable config drive."), ) # TODO(stephenfin): Drop support in the next major version bump after # Victoria config_drive_group.add_argument( '--config-drive', metavar='<config-drive-volume>|True', default=False, help=_( "**Deprecated** Use specified volume as the config drive, " "or 'True' to use an ephemeral drive. Replaced by " "'--use-config-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'), ) parser.add_argument( '--tag', metavar='<tag>', action='append', default=[], dest='tags', help=_( 'Tags for the server. ' 'Specify multiple times to add multiple tags. ' '(supported by --os-compute-api-version 2.52 or above)' ), ) return parser def take_action(self, parsed_args): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume image_client = self.app.client_manager.image # Lookup parsed_args.image image = None if parsed_args.image: image = image_client.find_image( parsed_args.image, ignore_missing=False) if not image and parsed_args.image_property: def emit_duplicated_warning(img, image_property): img_uuid_list = [str(image.id) for image in img] LOG.warning( 'Multiple matching images: %(img_uuid_list)s\n' 'Using image: %(chosen_one)s', { 'img_uuid_list': img_uuid_list, 'chosen_one': img_uuid_list[0], }) def _match_image(image_api, wanted_properties): image_list = image_api.images() images_matched = [] for img in image_list: img_dict = {} # exclude any unhashable entries img_dict_items = list(img.items()) if img.properties: img_dict_items.extend(list(img.properties.items())) for key, value in img_dict_items: try: set([key, value]) except TypeError: if key != 'properties': LOG.debug( 'Skipped the \'%s\' attribute. ' 'That cannot be compared. ' '(image: %s, value: %s)', key, img.id, value, ) pass else: img_dict[key] = value if all( k in img_dict and img_dict[k] == v for k, v in wanted_properties.items() ): images_matched.append(img) return images_matched images = _match_image(image_client, parsed_args.image_property) if len(images) > 1: emit_duplicated_warning(images, parsed_args.image_property) if images: image = images[0] else: msg = _( 'No images match the property expected by ' '--image-property' ) raise exceptions.CommandError(msg) # Lookup parsed_args.volume volume = None if parsed_args.volume: # --volume and --boot-from-volume are mutually exclusive. if parsed_args.boot_from_volume: msg = _('--volume is not allowed with --boot-from-volume') raise exceptions.CommandError(msg) 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) files = {} for f in parsed_args.file: dst, src = f.split('=', 1) try: files[dst] = io.open(src, 'rb') except IOError as e: msg = _("Can't open '%(source)s': %(exception)s") raise exceptions.CommandError( msg % {'source': src, 'exception': 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 '%(data)s': %(exception)s") raise exceptions.CommandError( msg % {'data': parsed_args.user_data, 'exception': e} ) if parsed_args.description: if compute_client.api_version < api_versions.APIVersion("2.19"): msg = _("Description is not supported for " "--os-compute-api-version less than 2.19") raise exceptions.CommandError(msg) block_device_mapping_v2 = [] if volume: block_device_mapping_v2 = [{ 'uuid': volume, 'boot_index': '0', 'source_type': 'volume', 'destination_type': 'volume' }] elif parsed_args.boot_from_volume: # Tell nova to create a root volume from the image provided. block_device_mapping_v2 = [{ 'uuid': image.id, 'boot_index': '0', 'source_type': 'image', 'destination_type': 'volume', 'volume_size': parsed_args.boot_from_volume }] # If booting from volume we do not pass an image to compute. image = None boot_args = [parsed_args.server_name, image, flavor] # Handle block device by device name order, like: vdb -> vdc -> vdd for dev_name in sorted(parsed_args.block_device_mapping): dev_map = parsed_args.block_device_mapping[dev_name] dev_map = dev_map.split(':') if dev_map[0]: mapping = {'device_name': dev_name} # 1. decide source and destination type if (len(dev_map) > 1 and dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' mapping['destination_type'] = 'volume' # 2. check target exist, update target uuid according by # source type if mapping['source_type'] == 'volume': volume_id = utils.find_resource( volume_client.volumes, dev_map[0]).id mapping['uuid'] = volume_id elif mapping['source_type'] == 'snapshot': snapshot_id = utils.find_resource( volume_client.volume_snapshots, dev_map[0]).id mapping['uuid'] = snapshot_id elif mapping['source_type'] == 'image': # NOTE(mriedem): In case --image is specified with the same # image, that becomes the root disk for the server. If the # block device is specified with a root device name, e.g. # vda, then the compute API will likely fail complaining # that there is a conflict. So if using the same image ID, # which doesn't really make sense but it's allowed, the # device name would need to be a non-root device, e.g. vdb. # Otherwise if the block device image is different from the # one specified by --image, then the compute service will # create a volume from the image and attach it to the # server as a non-root volume. image_id = image_client.find_image(dev_map[0], ignore_missing=False).id mapping['uuid'] = image_id # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: msg = _( 'Volume, volume snapshot or image (name or ID) must ' 'be specified if --block-device-mapping is specified' ) raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) nics = [] auto_or_none = False if parsed_args.nic is None: parsed_args.nic = [] for nic_str in parsed_args.nic: # Handle the special auto/none cases if nic_str in ('auto', 'none'): auto_or_none = True nics.append(nic_str) else: nic_info = { 'net-id': '', 'v4-fixed-ip': '', 'v6-fixed-ip': '', 'port-id': '', } for kv_str in nic_str.split(","): k, sep, v = kv_str.partition("=") if k in nic_info and v: nic_info[k] = v else: 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>." ) raise exceptions.CommandError(msg % k) if bool(nic_info["net-id"]) == bool(nic_info["port-id"]): msg = _( 'Either network or port 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"] = compute_client.api.network_find( 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) if nics: if auto_or_none: if len(nics) > 1: msg = _( 'Specifying a --nic of auto or none cannot ' 'be used with any other --nic, --network ' 'or --port value.' ) raise exceptions.CommandError(msg) nics = nics[0] else: # Compute API version >= 2.37 requires a value, so default to # 'auto' to maintain legacy behavior if a nic wasn't specified. if compute_client.api_version >= api_versions.APIVersion('2.37'): nics = 'auto' else: # Default to empty list if nothing was specified, let nova # side to decide the default behavior. nics = [] # Check security group exist and convert ID to name security_group_names = [] if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network for each_sg in parsed_args.security_group: sg = network_client.find_security_group(each_sg, ignore_missing=False) # Use security group ID to avoid multiple security group have # same name in neutron networking backend security_group_names.append(sg.id) else: # Handle nova-network case for each_sg in parsed_args.security_group: sg = compute_client.api.security_group_find(each_sg) security_group_names.append(sg['name']) hints = {} for key, values in parsed_args.hint.items(): # only items with multiple values will result in a list if len(values) == 1: hints[key] = values[0] else: hints[key] = values if isinstance(parsed_args.config_drive, bool): # NOTE(stephenfin): The API doesn't accept False as a value :'( config_drive = parsed_args.config_drive or None else: # TODO(stephenfin): Remove when we drop support for # '--config-drive' 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=security_group_names, userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, admin_pass=parsed_args.password, block_device_mapping_v2=block_device_mapping_v2, nics=nics, scheduler_hints=hints, config_drive=config_drive) if parsed_args.description: boot_kwargs['description'] = parsed_args.description if parsed_args.tags: if compute_client.api_version < api_versions.APIVersion('2.52'): msg = _( '--os-compute-api-version 2.52 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) boot_kwargs['tags'] = parsed_args.tags if parsed_args.host: if compute_client.api_version < api_versions.APIVersion("2.74"): msg = _( '--os-compute-api-version 2.74 or greater is required to ' 'support the --host option' ) raise exceptions.CommandError(msg) boot_kwargs['host'] = parsed_args.host if parsed_args.hypervisor_hostname: if compute_client.api_version < api_versions.APIVersion("2.74"): msg = _( '--os-compute-api-version 2.74 or greater is required to ' 'support the --hypervisor-hostname option' ) raise exceptions.CommandError(msg) boot_kwargs['hypervisor_hostname'] = ( parsed_args.hypervisor_hostname) LOG.debug('boot_args: %s', boot_args) 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, ): self.app.stdout.write('\n') else: LOG.error('Error creating server: %s', parsed_args.server_name) self.app.stdout.write(_('Error creating server\n')) raise SystemExit details = _prep_server_detail(compute_client, image_client, server) return zip(*sorted(details.items())) 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. This command requires ``--os-compute-api-version`` 2.17 or greater. """ 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 DeleteServer(command.Command): _description = _("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): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() 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 not utils.wait_for_delete(compute_client.servers, server_obj.id, callback=_show_progress): LOG.error(_('Error deleting server: %s'), server_obj.id) self.app.stdout.write(_('Error deleting server\n')) raise SystemExit class ListServer(command.Lister): _description = _("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. Note ' 'that this option only applies for non-admin users ' 'when using ``--os-compute-api-version`` 2.5 or greater.'), ) 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'), ) name_lookup_group = parser.add_mutually_exclusive_group() name_lookup_group.add_argument( '-n', '--no-name-lookup', action='store_true', default=False, help=_('Skip flavor and image name lookup.' 'Mutually exclusive with "--name-lookup-one-by-one"' ' option.'), ) name_lookup_group.add_argument( '--name-lookup-one-by-one', action='store_true', default=False, help=_('When looking up flavor and image names, look them up' 'one by one as needed instead of all together (default). ' 'Mutually exclusive with "--no-name-lookup|-n" option.'), ) parser.add_argument( '--marker', metavar='<server>', default=None, help=_('The last server of the previous page. Display ' 'list of servers after marker. Display all servers if not ' 'specified. When used with ``--deleted``, the marker must ' 'be an ID, otherwise a name or ID can be used.'), ) parser.add_argument( '--limit', metavar='<num-servers>', 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."), ) parser.add_argument( '--deleted', action="store_true", default=False, help=_('Only display deleted servers (Admin only).') ) parser.add_argument( '--changes-before', metavar='<changes-before>', default=None, help=_("List only servers changed before a certain point of time. " "The provided time should be an ISO 8061 formatted time " "(e.g., 2016-03-05T06:27:59Z). " "(Supported by API versions '2.66' - '2.latest')") ) parser.add_argument( '--changes-since', metavar='<changes-since>', default=None, help=_("List only servers changed after a certain point of time." " The provided time should be an ISO 8061 formatted time" " (e.g., 2016-03-04T06:27:59Z).") ) lock_group = parser.add_mutually_exclusive_group() lock_group.add_argument( '--locked', action='store_true', default=False, help=_('Only display locked servers. ' 'Requires ``--os-compute-api-version`` 2.73 or greater.'), ) lock_group.add_argument( '--unlocked', action='store_true', default=False, help=_('Only display unlocked servers. ' 'Requires ``--os-compute-api-version`` 2.73 or greater.'), ) parser.add_argument( '--tags', metavar='<tag>', action='append', default=[], dest='tags', help=_( 'Only list servers with the specified tag. ' 'Specify multiple times to filter on multiple tags. ' '(supported by --os-compute-api-version 2.26 or above)' ), ) parser.add_argument( '--not-tags', metavar='<tag>', action='append', default=[], dest='not_tags', help=_( 'Only list servers without the specified tag. ' 'Specify multiple times to filter on multiple tags. ' '(supported by --os-compute-api-version 2.26 or above)' ), ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image 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 = image_client.find_image(parsed_args.image, ignore_missing=False).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, 'deleted': parsed_args.deleted, 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } if parsed_args.tags: if compute_client.api_version < api_versions.APIVersion('2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) search_opts['tags'] = parsed_args.tags if parsed_args.not_tags: if compute_client.api_version < api_versions.APIVersion('2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --not-tag option' ) raise exceptions.CommandError(msg) search_opts['not-tags'] = parsed_args.not_tags support_locked = (compute_client.api_version >= api_versions.APIVersion('2.73')) if not support_locked and (parsed_args.locked or parsed_args.unlocked): msg = _('--os-compute-api-version 2.73 or greater is required to ' 'use the (un)locked filter option.') raise exceptions.CommandError(msg) elif support_locked: # Only from 2.73. if parsed_args.locked: search_opts['locked'] = True if parsed_args.unlocked: search_opts['locked'] = False LOG.debug('search options: %s', search_opts) if search_opts['changes-before']: if compute_client.api_version < api_versions.APIVersion('2.66'): msg = _('--os-compute-api-version 2.66 or later is required') raise exceptions.CommandError(msg) try: iso8601.parse_date(search_opts['changes-before']) except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-before value: %s') % search_opts['changes-before'] ) if search_opts['changes-since']: try: iso8601.parse_date(search_opts['changes-since']) except (TypeError, iso8601.ParseError): raise exceptions.CommandError( _('Invalid changes-since value: %s') % search_opts['changes-since'] ) if parsed_args.long: columns = ( 'ID', 'Name', 'Status', 'OS-EXT-STS:task_state', 'OS-EXT-STS:power_state', 'Networks', 'Image Name', 'Image ID', 'Flavor Name', 'Flavor ID', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', 'Metadata', ) column_headers = ( 'ID', 'Name', 'Status', 'Task State', 'Power State', 'Networks', 'Image Name', 'Image ID', 'Flavor Name', 'Flavor ID', '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: if parsed_args.no_name_lookup: columns = ( 'ID', 'Name', 'Status', 'Networks', 'Image ID', 'Flavor ID', ) else: columns = ( 'ID', 'Name', 'Status', 'Networks', 'Image Name', 'Flavor Name', ) column_headers = ( 'ID', 'Name', 'Status', 'Networks', 'Image', 'Flavor', ) mixed_case_fields = [] marker_id = None if parsed_args.marker: # Check if both "--marker" and "--deleted" are used. # In that scenario a lookup is not needed as the marker # needs to be an ID, because find_resource does not # handle deleted resources if parsed_args.deleted: marker_id = parsed_args.marker else: 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) images = {} flavors = {} if data and not parsed_args.no_name_lookup: # Create a dict that maps image_id to image object. # Needed so that we can display the "Image Name" column. # "Image Name" is not crucial, so we swallow any exceptions. # The 'image' attribute can be an empty string if the server was # booted from a volume. if parsed_args.name_lookup_one_by_one or image_id: for i_id in set(filter(lambda x: x is not None, (s.image.get('id') for s in data if s.image))): try: images[i_id] = image_client.get_image(i_id) except Exception: pass else: try: images_list = image_client.images() for i in images_list: images[i.id] = i except Exception: pass # Create a dict that maps flavor_id to flavor object. # Needed so that we can display the "Flavor Name" column. # "Flavor Name" is not crucial, so we swallow any exceptions. if parsed_args.name_lookup_one_by_one or flavor_id: for f_id in set(filter(lambda x: x is not None, (s.flavor.get('id') for s in data))): try: flavors[f_id] = compute_client.flavors.get(f_id) except Exception: pass else: try: flavors_list = compute_client.flavors.list(is_public=None) for i in flavors_list: flavors[i.id] = i except Exception: pass # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. for s in data: if compute_client.api_version >= api_versions.APIVersion('2.69'): # NOTE(tssurya): From 2.69, we will have the keys 'flavor' # and 'image' missing in the server response during # infrastructure failure situations. # For those servers with partial constructs we just skip the # processing of the image and flavor informations. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue if 'id' in s.image: image = images.get(s.image['id']) if image: s.image_name = image.name s.image_id = s.image['id'] else: # NOTE(melwitt): An server booted from a volume will have no # image associated with it. We fill in the Image Name and ID # with "N/A (booted from volume)" to help users who want to be # able to grep for boot-from-volume servers when using the CLI. s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV if 'id' in s.flavor: flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: # TODO(mriedem): Fix this for microversion >= 2.47 where the # flavor is embedded in the server response without the id. # We likely need to drop the Flavor ID column in that case if # --long is specified. s.flavor_name = '' s.flavor_id = '' table = (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)) return table class LockServer(command.Command): _description = _("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)'), ) parser.add_argument( '--reason', metavar='<reason>', default=None, help=_("Reason for locking the server(s). Requires " "``--os-compute-api-version`` 2.73 or greater.") ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute support_reason = compute_client.api_version >= api_versions.APIVersion( '2.73') if not support_reason and parsed_args.reason: msg = _('--os-compute-api-version 2.73 or greater is required to ' 'use the --reason option.') raise exceptions.CommandError(msg) for server in parsed_args.server: serv = utils.find_resource(compute_client.servers, server) (serv.lock(reason=parsed_args.reason) if support_reason else serv.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): _description = _("""Migrate server to different host. A migrate operation is implemented as a resize operation using the same flavor as the old server. This means that, like resize, migrate works by creating a new server using the same flavor and copying the contents of the original disk into a new one. As with resize, the migrate operation is a two-step process for the user: the first step is to perform the migrate, and the second step is to either confirm (verify) success and release the old server, or to declare a revert to release the new server and restart the old one.""") 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-migration', dest='live_migration', action='store_true', help=_('Live migrate the server. Use the ``--host`` option to ' 'specify a target host for the migration which will be ' 'validated by the scheduler.'), ) # The --live and --host options are mutually exclusive ways of asking # for a target host during a live migration. host_group = parser.add_mutually_exclusive_group() # TODO(mriedem): Remove --live in the next major version bump after # the Train release. host_group.add_argument( '--live', metavar='<hostname>', help=_('**Deprecated** This option is problematic in that it ' 'requires a host and prior to compute API version 2.30, ' 'specifying a host during live migration will bypass ' 'validation by the scheduler which could result in ' 'failures to actually migrate the server to the specified ' 'host or over-subscribe the host. Use the ' '``--live-migration`` option instead. If both this option ' 'and ``--live-migration`` are used, ``--live-migration`` ' 'takes priority.'), ) host_group.add_argument( '--host', metavar='<hostname>', help=_('Migrate the server to the specified host. Requires ' '``--os-compute-api-version`` 2.30 or greater when used ' 'with the ``--live-migration`` option, otherwise requires ' '``--os-compute-api-version`` 2.56 or greater.'), ) migration_group = parser.add_mutually_exclusive_group() migration_group.add_argument( '--shared-migration', dest='block_migration', action='store_false', default=False, help=_('Perform a shared live migration (default)'), ) migration_group.add_argument( '--block-migration', dest='block_migration', action='store_true', 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 migrate to complete'), ) return parser def _log_warning_for_live(self, parsed_args): if parsed_args.live: # NOTE(mriedem): The --live option requires a host and if # --os-compute-api-version is less than 2.30 it will forcefully # bypass the scheduler which is dangerous. self.log.warning(_( 'The --live option has been deprecated. Please use the ' '--live-migration option instead.')) def take_action(self, parsed_args): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server, ) # Check for live migration. if parsed_args.live or parsed_args.live_migration: # Always log a warning if --live is used. self._log_warning_for_live(parsed_args) kwargs = { 'block_migration': parsed_args.block_migration } # Prefer --live-migration over --live if both are specified. if parsed_args.live_migration: # Technically we could pass a non-None host with # --os-compute-api-version < 2.30 but that is the same thing # as the --live option bypassing the scheduler which we don't # want to support, so if the user is using --live-migration # and --host, we want to enforce that they are using version # 2.30 or greater. if (parsed_args.host and compute_client.api_version < api_versions.APIVersion('2.30')): raise exceptions.CommandError( '--os-compute-api-version 2.30 or greater is required ' 'when using --host') # The host parameter is required in the API even if None. kwargs['host'] = parsed_args.host else: kwargs['host'] = parsed_args.live if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit server.live_migrate(**kwargs) else: if parsed_args.block_migration or parsed_args.disk_overcommit: raise exceptions.CommandError( "--live-migration must be specified if " "--block-migration or --disk-overcommit is " "specified") if parsed_args.host: if (compute_client.api_version < api_versions.APIVersion('2.56')): msg = _( '--os-compute-api-version 2.56 or greater is ' 'required to use --host without --live-migration.' ) raise exceptions.CommandError(msg) kwargs = {'host': parsed_args.host} if parsed_args.host else {} server.migrate(**kwargs) if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, server.id, success_status=['active', 'verify_resize'], callback=_show_progress, ): self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error migrating server: %s'), server.id) self.app.stdout.write(_('Error migrating server\n')) raise SystemExit class ListMigration(command.Command): _description = _("""List server migrations""") def get_parser(self, prog_name): parser = super(ListMigration, self).get_parser(prog_name) parser.add_argument( '--server', metavar='<server>', help=_( 'Filter migrations by server (name or ID)' ) ) parser.add_argument( '--host', metavar='<host>', help=_( 'Filter migrations by source or destination host' ), ) parser.add_argument( '--status', metavar='<status>', help=_('Filter migrations by status') ) parser.add_argument( '--type', metavar='<type>', choices=[ 'evacuation', 'live-migration', 'cold-migration', 'resize', ], help=_('Filter migrations by type'), ) parser.add_argument( '--marker', metavar='<marker>', help=_( "The last migration of the previous page; displays list " "of migrations after 'marker'. Note that the marker is " "the migration UUID. " "(supported with --os-compute-api-version 2.59 or above)" ), ) parser.add_argument( '--limit', metavar='<limit>', type=int, help=_( "Maximum number of migrations to display. Note that there " "is a configurable max limit on the server, and the limit " "that is used will be the minimum of what is requested " "here and what is configured in the server. " "(supported with --os-compute-api-version 2.59 or above)" ), ) parser.add_argument( '--changes-since', dest='changes_since', metavar='<changes-since>', help=_( "List only migrations changed later or equal to a certain " "point of time. The provided time should be an ISO 8061 " "formatted time, e.g. ``2016-03-04T06:27:59Z``. " "(supported with --os-compute-api-version 2.59 or above)" ), ) parser.add_argument( '--changes-before', dest='changes_before', metavar='<changes-before>', help=_( "List only migrations changed earlier or equal to a " "certain point of time. The provided time should be an ISO " "8061 formatted time, e.g. ``2016-03-04T06:27:59Z``. " "(supported with --os-compute-api-version 2.66 or above)" ), ) parser.add_argument( '--project', metavar='<project>', dest='project_id', help=_( "Filter migrations by project (ID) " "(supported with --os-compute-api-version 2.80 or above)" ), ) parser.add_argument( '--user', metavar='<user>', dest='user_id', help=_( "Filter migrations by user (ID) " "(supported with --os-compute-api-version 2.80 or above)" ), ) return parser def print_migrations(self, parsed_args, compute_client, migrations): columns = [ 'Source Node', 'Dest Node', 'Source Compute', 'Dest Compute', 'Dest Host', 'Status', 'Server UUID', 'Old Flavor', 'New Flavor', 'Created At', 'Updated At', ] # Insert migrations UUID after ID if compute_client.api_version >= api_versions.APIVersion("2.59"): columns.insert(0, "UUID") if compute_client.api_version >= api_versions.APIVersion("2.23"): columns.insert(0, "Id") columns.insert(len(columns) - 2, "Type") if compute_client.api_version >= api_versions.APIVersion("2.80"): if parsed_args.project_id: columns.insert(len(columns) - 2, "Project") if parsed_args.user_id: columns.insert(len(columns) - 2, "User") return ( columns, (utils.get_item_properties(mig, columns) for mig in migrations), ) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute search_opts = { 'host': parsed_args.host, 'server': parsed_args.server, 'status': parsed_args.status, } if parsed_args.type: migration_type = parsed_args.type # we're using an alias because the default value is confusing if migration_type == 'cold-migration': migration_type = 'migration' search_opts['type'] = migration_type if parsed_args.marker: if compute_client.api_version < api_versions.APIVersion('2.59'): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --marker option' ) raise exceptions.CommandError(msg) search_opts['marker'] = parsed_args.marker if parsed_args.limit: if compute_client.api_version < api_versions.APIVersion('2.59'): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --limit option' ) raise exceptions.CommandError(msg) search_opts['limit'] = parsed_args.limit if parsed_args.changes_since: if compute_client.api_version < api_versions.APIVersion('2.59'): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --changes-since option' ) raise exceptions.CommandError(msg) search_opts['changes_since'] = parsed_args.changes_since if parsed_args.changes_before: if compute_client.api_version < api_versions.APIVersion('2.66'): msg = _( '--os-compute-api-version 2.66 or greater is required to ' 'support the --changes-before option' ) raise exceptions.CommandError(msg) search_opts['changes_before'] = parsed_args.changes_before if parsed_args.project_id: if compute_client.api_version < api_versions.APIVersion('2.80'): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --project option' ) raise exceptions.CommandError(msg) search_opts['project_id'] = parsed_args.project_id if parsed_args.user_id: if compute_client.api_version < api_versions.APIVersion('2.80'): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --user option' ) raise exceptions.CommandError(msg) search_opts['user_id'] = parsed_args.user_id migrations = compute_client.migrations.list(**search_opts) return self.print_migrations(parsed_args, compute_client, migrations) class AbortMigration(command.Command): """Cancel an ongoing live migration. This command requires ``--os-compute-api-version`` 2.24 or greater. """ def get_parser(self, prog_name): parser = super(AbortMigration, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help=_('Server (name or ID)'), ) parser.add_argument( 'migration', metavar='<migration>', help=_("Migration (ID)"), ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute if compute_client.api_version < api_versions.APIVersion('2.24'): msg = _( '--os-compute-api-version 2.24 or greater is required to ' 'support the server migration abort command' ) raise exceptions.CommandError(msg) server = utils.find_resource( compute_client.servers, parsed_args.server, ) compute_client.server_migrations.live_migration_abort( server.id, parsed_args.migration) class ForceCompleteMigration(command.Command): """Force an ongoing live migration to complete. This command requires ``--os-compute-api-version`` 2.22 or greater. """ def get_parser(self, prog_name): parser = super(ForceCompleteMigration, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help=_('Server (name or ID)'), ) parser.add_argument( 'migration', metavar='<migration>', help=_('Migration (ID)') ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute if compute_client.api_version < api_versions.APIVersion('2.22'): msg = _( '--os-compute-api-version 2.22 or greater is required to ' 'support the server migration force complete command' ) raise exceptions.CommandError(msg) server = utils.find_resource( compute_client.servers, parsed_args.server, ) compute_client.server_migrations.live_migrate_force_complete( server.id, parsed_args.migration) class PauseServer(command.Command): _description = _("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): _description = _("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): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() 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, ): self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebooting server: %s'), server.id) self.app.stdout.write(_('Error rebooting server\n')) raise SystemExit class RebuildServer(command.ShowOne): _description = _("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( '--property', metavar='<key=value>', action=parseractions.KeyValueAction, help=_('Set a property on the rebuilt instance ' '(repeat option to set multiple values)'), ) parser.add_argument( '--description', metavar='<description>', help=_('New description for the server (supported by ' '--os-compute-api-version 2.19 or above'), ) parser.add_argument( '--wait', action='store_true', help=_('Wait for rebuild to complete'), ) key_group = parser.add_mutually_exclusive_group() key_group.add_argument( '--key-name', metavar='<key-name>', help=_("Set the key name of key pair on the rebuilt instance." " Cannot be specified with the '--key-unset' option." " (Supported by API versions '2.54' - '2.latest')"), ) key_group.add_argument( '--key-unset', action='store_true', default=False, help=_("Unset the key name of key pair on the rebuilt instance." " Cannot be specified with the '--key-name' option." " (Supported by API versions '2.54' - '2.latest')"), ) return parser def take_action(self, parsed_args): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() 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.image is not set, default to the currently used one. if parsed_args.image: image = image_client.find_image( parsed_args.image, ignore_missing=False) else: image_id = server.to_dict().get('image', {}).get('id') image = image_client.get_image(image_id) kwargs = {} if parsed_args.property: kwargs['meta'] = parsed_args.property if parsed_args.description: if server.api_version < api_versions.APIVersion("2.19"): msg = _("Description is not supported for " "--os-compute-api-version less than 2.19") raise exceptions.CommandError(msg) kwargs['description'] = parsed_args.description if parsed_args.key_name or parsed_args.key_unset: if compute_client.api_version < api_versions.APIVersion('2.54'): msg = _('--os-compute-api-version 2.54 or later is required') raise exceptions.CommandError(msg) if parsed_args.key_unset: kwargs['key_name'] = None if parsed_args.key_name: kwargs['key_name'] = parsed_args.key_name server = server.rebuild(image, parsed_args.password, **kwargs) if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, server.id, callback=_show_progress, ): self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error rebuilding server: %s'), server.id) self.app.stdout.write(_('Error rebuilding server\n')) raise SystemExit details = _prep_server_detail(compute_client, image_client, server, refresh=False) return zip(*sorted(details.items())) class RemoveFixedIP(command.Command): _description = _("Remove fixed IP address from server") def get_parser(self, prog_name): parser = super(RemoveFixedIP, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to remove the fixed IP address from (name or ID)"), ) parser.add_argument( "ip_address", metavar="<ip-address>", help=_("Fixed IP address to remove from the server (IP only)"), ) 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.remove_fixed_ip(parsed_args.ip_address) class RemoveFloatingIP(network_common.NetworkAndComputeCommand): _description = _("Remove floating IP address from server") def update_parser_common(self, parser): parser.add_argument( "server", metavar="<server>", help=_( "Server to remove the floating IP address from (name or ID)" ), ) parser.add_argument( "ip_address", metavar="<ip-address>", help=_("Floating IP address to remove from server (IP only)"), ) return parser def take_action_network(self, client, parsed_args): attrs = {} obj = client.find_ip( parsed_args.ip_address, ignore_missing=False, ) attrs['port_id'] = None client.update_ip(obj, **attrs) def take_action_compute(self, client, parsed_args): client.api.floating_ip_remove( parsed_args.server, parsed_args.ip_address, ) class RemovePort(command.Command): _description = _("Remove port from server") def get_parser(self, prog_name): parser = super(RemovePort, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to remove the port from (name or ID)"), ) parser.add_argument( "port", metavar="<port>", help=_("Port to remove from the server (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) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network port_id = network_client.find_port( parsed_args.port, ignore_missing=False).id else: port_id = parsed_args.port server.interface_detach(port_id) class RemoveNetwork(command.Command): _description = _("Remove all ports of a network from server") def get_parser(self, prog_name): parser = super(RemoveNetwork, self).get_parser(prog_name) parser.add_argument( "server", metavar="<server>", help=_("Server to remove the port from (name or ID)"), ) parser.add_argument( "network", metavar="<network>", help=_("Network to remove from the server (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) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network net_id = network_client.find_network( parsed_args.network, ignore_missing=False).id else: net_id = parsed_args.network for inf in server.interface_list(): if inf.net_id == net_id: server.interface_detach(inf.port_id) class RemoveServerSecurityGroup(command.Command): _description = _("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 = compute_client.api.security_group_find( parsed_args.group, ) server.remove_security_group(security_group['id']) class RemoveServerVolume(command.Command): _description = _( "Remove volume from server. " "Specify ``--os-compute-api-version 2.20`` or higher to remove a " "volume from a server with status ``SHELVED`` or " "``SHELVED_OFFLOADED``.") 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.Command): _description = _("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)'), ) parser.add_argument( '--image', metavar='<image>', help=_('Image (name or ID) to use for the rescue mode.' ' Defaults to the currently used one.'), ) parser.add_argument( '--password', metavar='<password>', help=_("Set the password on the rescued instance"), ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute image_client = self.app.client_manager.image image = None if parsed_args.image: image = image_client.find_image(parsed_args.image) utils.find_resource( compute_client.servers, parsed_args.server, ).rescue(image=image, password=parsed_args.password) class ResizeServer(command.Command): _description = _("""Scale server to a new flavor. A resize operation is implemented by creating a new server and copying the contents of the original disk into a new one. It is a two-step process for the user: the first step is to perform the resize, and the second step is to either confirm (verify) success and release the old server or to declare a revert to release the new server and restart the old one.""") 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): def _show_progress(progress): if progress: self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() 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, ): self.app.stdout.write(_('Complete\n')) else: LOG.error(_('Error resizing server: %s'), server.id) self.app.stdout.write(_('Error resizing server\n')) raise SystemExit elif parsed_args.confirm: self.log.warning(_( "The --confirm option has been deprecated. Please use the " "'openstack server resize confirm' command instead.")) compute_client.servers.confirm_resize(server) elif parsed_args.revert: self.log.warning(_( "The --revert option has been deprecated. Please use the " "'openstack server resize revert' command instead.")) compute_client.servers.revert_resize(server) class ResizeConfirm(command.Command): _description = _("""Confirm server resize. Confirm (verify) success of resize operation and release the old server.""") def get_parser(self, prog_name): parser = super(ResizeConfirm, 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 server = utils.find_resource( compute_client.servers, parsed_args.server, ) server.confirm_resize() class MigrateConfirm(ResizeConfirm): _description = _("""Confirm server migrate. Confirm (verify) success of migrate operation and release the old server.""") class ResizeRevert(command.Command): _description = _("""Revert server resize. Revert the resize operation. Release the new server and restart the old one.""") def get_parser(self, prog_name): parser = super(ResizeRevert, 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 server = utils.find_resource( compute_client.servers, parsed_args.server, ) server.revert_resize() class MigrateRevert(ResizeRevert): _description = _("""Revert server migrate. Revert the migrate operation. Release the new server and restart the old one.""") class RestoreServer(command.Command): _description = _("Restore server(s)") def get_parser(self, prog_name): parser = super(RestoreServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', nargs='+', help=_('Server(s) to restore (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 ).restore() class ResumeServer(command.Command): _description = _("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): _description = _("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)'), ) parser.add_argument( '--state', metavar='<state>', choices=['active', 'error'], help=_('New server state (valid value: active, error)'), ) parser.add_argument( '--description', metavar='<description>', help=_('New server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) parser.add_argument( '--tag', metavar='<tag>', action='append', default=[], dest='tags', help=_( 'Tag for the server. ' 'Specify multiple times to add multiple tags. ' '(supported by --os-compute-api-version 2.26 or above)' ), ) 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.state: server.reset_state(state=parsed_args.state) 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) if parsed_args.description: if server.api_version < api_versions.APIVersion("2.19"): msg = _("Description is not supported for " "--os-compute-api-version less than 2.19") raise exceptions.CommandError(msg) server.update(description=parsed_args.description) if parsed_args.tags: if server.api_version < api_versions.APIVersion('2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) for tag in parsed_args.tags: server.add_tag(tag=tag) class ShelveServer(command.Command): _description = _("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): _description = _( "Show server details. Specify ``--os-compute-api-version 2.47`` " "or higher to see the embedded flavor information for the server.") 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: self.app.stderr.write(_( "Error retrieving diagnostics data\n" )) return ({}, {}) else: data = _prep_server_detail(compute_client, self.app.client_manager.image, server, refresh=False) return zip(*sorted(data.items())) class SshServer(command.Command): _description = _("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.auth_ref.username if parsed_args.verbose: cmd += " -v" cmd += " %s@%s" ip_address = _get_ip_address(server.addresses, parsed_args.address_type, ip_address_family) LOG.debug("ssh command: %s", (cmd % (login, ip_address))) os.system(cmd % (login, ip_address)) class StartServer(command.Command): _description = _("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): _description = _("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): _description = _("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): _description = _("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): _description = _("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): _description = _("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): _description = _("Unset server properties and tags") 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 option to remove multiple values)'), ) parser.add_argument( '--description', dest='description', action='store_true', help=_('Unset server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) parser.add_argument( '--tag', metavar='<tag>', action='append', default=[], dest='tags', help=_( 'Tag to remove from the server. ' 'Specify multiple times to remove multiple tags. ' '(supported by --os-compute-api-version 2.26 or later' ), ) 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, ) if parsed_args.description: if compute_client.api_version < api_versions.APIVersion("2.19"): msg = _("Description is not supported for " "--os-compute-api-version less than 2.19") raise exceptions.CommandError(msg) compute_client.servers.update( server, description="", ) if parsed_args.tags: if compute_client.api_version < api_versions.APIVersion('2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) for tag in parsed_args.tags: compute_client.servers.delete_tag(server, tag=tag) class UnshelveServer(command.Command): _description = _("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)'), ) parser.add_argument( '--availability-zone', default=None, help=_('Name of the availability zone in which to unshelve a ' 'SHELVED_OFFLOADED server (supported by ' '--os-compute-api-version 2.77 or above)'), ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute support_az = compute_client.api_version >= api_versions.APIVersion( '2.77') if not support_az and parsed_args.availability_zone: msg = _("--os-compute-api-version 2.77 or greater is required " "to support the '--availability-zone' option.") raise exceptions.CommandError(msg) for server in parsed_args.server: if support_az: utils.find_resource( compute_client.servers, server ).unshelve(availability_zone=parsed_args.availability_zone) else: utils.find_resource( compute_client.servers, server, ).unshelve()