# Copyright 2012-2013 OpenStack, LLC. # # 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. # """Server action implementations""" import logging import os import time from cliff import command from cliff import lister from cliff import show from novaclient.v1_1 import servers from openstackclient.common import exceptions from openstackclient.common import utils def _format_servers_list_networks(server): """Return a string containing the networks a server is attached to. :param server: a single Server resource :rtype: a string of formatted network addresses """ output = [] for (network, addresses) in server.networks.items(): if not addresses: continue addresses_csv = ', '.join(addresses) group = "%s=%s" % (network, addresses_csv) output.append(group) return '; '.join(output) def _prep_server_detail(compute_client, server): """Prepare the detailed server dict for printing :param compute_client: a compute client instance :param server: a Server resource :rtype: a dict of server details """ info = server._info.copy() # Call .get() to retrieve all of the server information # as findall(name=blah) and REST /details are not the same # and do not return flavor and image information. server = compute_client.servers.get(info['id']) info.update(server._info) # Convert the image blob to a name image_info = info.get('image', {}) image_id = image_info.get('id', '') image = utils.find_resource(compute_client.images, image_id) info['image'] = "%s (%s)" % (image.name, image_id) # Convert the flavor blob to a name flavor_info = info.get('flavor', {}) flavor_id = flavor_info.get('id', '') flavor = utils.find_resource(compute_client.flavors, flavor_id) info['flavor'] = "%s (%s)" % (flavor.name, flavor_id) # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way info['addresses'] = _format_servers_list_networks(server) # Remove values that are long and not too useful info.pop('links', None) return info def _wait_for_status(poll_fn, obj_id, final_ok_states, poll_period=5, status_field="status"): """Block while an action is being performed :param poll_fn: a function to retrieve the state of the object :param obj_id: the id of the object :param final_ok_states: a tuple of the states of the object that end the wait as success, ex ['active'] :param poll_period: the wait time between checks of object status :param status_field: field name containing the status to be checked """ log = logging.getLogger(__name__ + '._wait_for_status') while True: obj = poll_fn(obj_id) status = getattr(obj, status_field) if status: status = status.lower() if status in final_ok_states: log.debug('Wait terminated with success') retval = True break elif status == "error": log.error('Wait terminated with an error') retval = False break time.sleep(poll_period) return retval class CreateServer(show.ShowOne): """Create server command""" api = "compute" log = logging.getLogger(__name__ + '.CreateServer') 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') parser.add_argument( '--image', metavar='<image>', required=True, help='Create server from this image') parser.add_argument( '--flavor', metavar='<flavor>', required=True, help='Create server with this flavor') parser.add_argument( '--security-group', metavar='<security-group-name>', action='append', default=[], help='Security group to assign to this server ' '(repeat for multiple groups)') parser.add_argument( '--key-name', metavar='<key-name>', help='Keypair to inject into this server (optional extension)') parser.add_argument( '--metadata', metavar='<key=value>', action='append', default=[], help='Metadata to store for this server ' '(repeat for multiple values)') parser.add_argument( '--file', metavar='<dest-filename=source-filename>', action='append', default=[], help='File to inject into image before boot ' '(repeat for multiple files)') parser.add_argument( '--user-data', metavar='<user-data>', help='User data file to serve from the metadata server') parser.add_argument( '--availability-zone', metavar='<zone-name>', help='Select an availability zone for the server') parser.add_argument( '--block-device-mapping', metavar='<dev-name=mapping>', action='append', default=[], help='Map block devices; map is ' '<id>:<type>:<size(GB)>:<delete_on_terminate> ' '(optional extension)') parser.add_argument( '--nic', metavar='<nic-config-string>', action='append', default=[], help='Specify NIC configuration (optional extension)') parser.add_argument( '--hint', metavar='<key=value>', action='append', default=[], help='Hints for the scheduler (optional extension)') parser.add_argument( '--config-drive', metavar='<config-drive-volume>|True', default=False, help='Use specified volume as the config drive, ' 'or \'True\' to use an ephemeral drive') parser.add_argument( '--min', metavar='<count>', type=int, default=1, help='Minimum number of servers to launch (default=1)') parser.add_argument( '--max', metavar='<count>', type=int, default=1, help='Maximum number of servers to launch (default=1)') parser.add_argument( '--wait', dest='wait', action='store_true', help='Wait for servers to become active') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute # Lookup parsed_args.image image = utils.find_resource(compute_client.images, parsed_args.image) # Lookup parsed_args.flavor flavor = utils.find_resource(compute_client.flavors, parsed_args.flavor) boot_args = [parsed_args.server_name, image, flavor] meta = dict(v.split('=', 1) for v in parsed_args.metadata) files = {} for f in parsed_args.file: dst, src = f.split('=', 1) try: files[dst] = open(src) except IOError, e: raise exceptions.CommandError("Can't open '%s': %s" % (src, e)) if parsed_args.min > parsed_args.max: raise exceptions.CommandError("min instances should be <= " "max instances") if parsed_args.min < 1: raise exceptions.CommandError("min instances should be > 0") if parsed_args.max < 1: raise exceptions.CommandError("max instances should be > 0") userdata = None if parsed_args.user_data: try: userdata = open(parsed_args.user_data) except IOError, e: raise exceptions.CommandError("Can't open '%s': %s" % (parsed_args.user_data, e)) block_device_mapping = dict(v.split('=', 1) for v in parsed_args.block_device_mapping) nics = [] for nic_str in parsed_args.nic: nic_info = {"net-id": "", "v4-fixed-ip": ""} nic_info.update(dict(kv_str.split("=", 1) for kv_str in nic_str.split(","))) nics.append(nic_info) hints = {} for hint in parsed_args.hint: key, _sep, value = hint.partition('=') # NOTE(vish): multiple copies of the same hint will # result in a list of values if key in hints: if isinstance(hints[key], basestring): hints[key] = [hints[key]] hints[key] += [value] else: hints[key] = value # What does a non-boolean value for config-drive do? # --config-drive argument is either a volume id or # 'True' (or '1') to use an ephemeral volume if str(parsed_args.config_drive).lower() in ("true", "1"): config_drive = True elif str(parsed_args.config_drive).lower() in ("false", "0", "", "none"): config_drive = None else: config_drive = parsed_args.config_drive boot_kwargs = dict( meta=meta, files=files, reservation_id=None, min_count=parsed_args.min, max_count=parsed_args.max, security_groups=parsed_args.security_group, userdata=userdata, key_name=parsed_args.key_name, availability_zone=parsed_args.availability_zone, block_device_mapping=block_device_mapping, nics=nics, scheduler_hints=hints, config_drive=config_drive) self.log.debug('boot_args: %s' % boot_args) self.log.debug('boot_kwargs: %s' % boot_kwargs) server = compute_client.servers.create(*boot_args, **boot_kwargs) if parsed_args.wait: _wait_for_status(compute_client.servers.get, server._info['id'], ['active']) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) class DeleteServer(command.Command): """Delete server command""" api = 'compute' log = logging.getLogger(__name__ + '.DeleteServer') def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to delete') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) compute_client.servers.delete(server.id) return class ListServer(lister.Lister): """List server command""" api = 'compute' log = logging.getLogger(__name__ + '.ListServer') 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 address') parser.add_argument( '--ip6', metavar='<ip-address-regex>', help='regular expression to match IPv6 address') parser.add_argument( '--name', metavar='<name>', help='regular expression to match name') 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 ID') parser.add_argument( '--image', metavar='<image>', help='search by image ID') parser.add_argument( '--host', metavar='<hostname>', help='search by hostname') parser.add_argument( '--instance-name', metavar='<server-name>', help='regular expression to match instance name (admin only)') parser.add_argument( '--all-tenants', action='store_true', default=bool(int(os.environ.get("ALL_TENANTS", 0))), help='display information from all tenants (admin only)') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute 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': parsed_args.flavor, 'image': parsed_args.image, 'host': parsed_args.host, 'all_tenants': parsed_args.all_tenants, } self.log.debug('search options: %s', search_opts) # FIXME(dhellmann): Consider adding other columns columns = ('ID', 'Name', 'Status', 'Networks') data = compute_client.servers.list(search_opts=search_opts) return (columns, (utils.get_item_properties( s, columns, formatters={'Networks': _format_servers_list_networks}, ) for s in data)) class PauseServer(command.Command): """Pause server command""" api = 'compute' log = logging.getLogger(__name__ + '.PauseServer') def get_parser(self, prog_name): parser = super(PauseServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to pause') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) server.pause() return class RebootServer(command.Command): """Reboot server command""" api = 'compute' log = logging.getLogger(__name__ + '.RebootServer') def get_parser(self, prog_name): parser = super(RebootServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to reboot') 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', dest='wait', action='store_true', help='Wait for server to become active to return') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) server.reboot(parsed_args.reboot_type) if parsed_args.wait: _wait_for_status(compute_client.servers.get, server.id, ['active']) return class RebuildServer(show.ShowOne): """Rebuild server command""" api = "compute" log = logging.getLogger(__name__ + '.RebuildServer') 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>', required=True, help='Recreate server from this image') parser.add_argument( '--password', metavar='<password>', default=False, help="Set the provided password on the rebuild instance") parser.add_argument( '--wait', dest='wait', action='store_true', help='Wait for server to become active to return') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute # Lookup parsed_args.image image = utils.find_resource(compute_client.images, parsed_args.image) server = utils.find_resource( compute_client.servers, parsed_args.server) _password = None if parsed_args.rebuild_password is not False: _password = args.rebuild_password kwargs = {} server = server.rebuild(image, _password, **kwargs) # TODO(dtroyer): force silent=True if output filter != table if parsed_args.wait: _wait_for_status(compute_client.servers.get, server._info['id'], ['active']) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) class ResumeServer(command.Command): """Resume server command""" api = 'compute' log = logging.getLogger(__name__ + '.ResumeServer') def get_parser(self, prog_name): parser = super(ResumeServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to resume') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource( compute_client.servers, parsed_args.server) server.resume() return class ShowServer(show.ShowOne): """Show server command""" api = 'compute' log = logging.getLogger(__name__ + '.ShowServer') def get_parser(self, prog_name): parser = super(ShowServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to display') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) details = _prep_server_detail(compute_client, server) return zip(*sorted(details.iteritems())) class SuspendServer(command.Command): """Suspend server command""" api = 'compute' log = logging.getLogger(__name__ + '.SuspendServer') def get_parser(self, prog_name): parser = super(SuspendServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to suspend') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) server.suspend() return class UnpauseServer(command.Command): """Unpause server command""" api = 'compute' log = logging.getLogger(__name__ + '.UnpauseServer') def get_parser(self, prog_name): parser = super(UnpauseServer, self).get_parser(prog_name) parser.add_argument( 'server', metavar='<server>', help='Name or ID of server to unpause') return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) compute_client = self.app.client_manager.compute server = utils.find_resource(compute_client.servers, parsed_args.server) server.unpause() return