python-openstackclient/openstackclient/compute/v2/server.py

681 lines
22 KiB
Python

# Copyright 2012 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
"""
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(
'--meta-data',
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 be serverd by the metadata server',
)
parser.add_argument(
'--availability-zone',
metavar='<zone-name>',
help='Keypair to inject into this 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 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)
# 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.meta_data)
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(
'--instance-name',
metavar='<server-name>',
help='regular expression to match instance name',
)
parser.add_argument(
'--status',
metavar='<status>',
help='search by server status',
# FIXME(dhellmann): Add choices?
)
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(
'--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(
'--rebuild-password',
metavar='<rebuild_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