Update compute client bits

* add server create, delete, pause, reboot, rebuild
  resume, suspend, unpause commands

Change-Id: I728ec199e4562bd621c3a73106c90d8b790b459a
This commit is contained in:
Dean Troyer 2012-08-20 18:02:30 -05:00
parent 8010e773ac
commit 90a1c65f3a
3 changed files with 576 additions and 40 deletions
openstackclient/compute
setup.py

@ -17,19 +17,28 @@
import logging import logging
from novaclient import client as nova_client from openstackclient.common import exceptions as exc
from openstackclient.common import utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
API_NAME = 'compute' API_NAME = 'compute'
API_VERSIONS = {
'1.1': 'novaclient.v1_1.client.Client',
'2': 'novaclient.v1_1.client.Client',
}
def make_client(instance): def make_client(instance):
"""Returns a compute service client. """Returns a compute service client.
""" """
LOG.debug('instantiating compute client') compute_client = utils.get_client_class(
client = nova_client.Client( API_NAME,
version=instance._api_version[API_NAME], instance._api_version[API_NAME],
API_VERSIONS,
)
LOG.debug('instantiating compute client: %s' % compute_client)
client = compute_client(
username=instance._username, username=instance._username,
api_key=instance._password, api_key=instance._password,
project_id=instance._tenant_name, project_id=instance._tenant_name,

@ -21,11 +21,15 @@ Server action implementations
import logging import logging
import os import os
import sys
import time
from cliff import command from cliff import command
from cliff import lister from cliff import lister
from cliff import show from cliff import show
from novaclient.v1_1 import servers
from openstackclient.common import exceptions
from openstackclient.common import utils from openstackclient.common import utils
@ -33,6 +37,7 @@ def _format_servers_list_networks(server):
"""Return a string containing the networks a server is attached to. """Return a string containing the networks a server is attached to.
:param server: a single Server resource :param server: a single Server resource
:rtype: a string of formatted network addresses
""" """
output = [] output = []
for (network, addresses) in server.networks.items(): for (network, addresses) in server.networks.items():
@ -44,6 +49,318 @@ def _format_servers_list_networks(server):
return '; '.join(output) 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): class ListServer(lister.Lister):
"""List server command""" """List server command"""
@ -54,40 +371,48 @@ class ListServer(lister.Lister):
parser = super(ListServer, self).get_parser(prog_name) parser = super(ListServer, self).get_parser(prog_name)
parser.add_argument( parser.add_argument(
'--reservation-id', '--reservation-id',
metavar='<reservation-id>',
help='only return instances that match the reservation', help='only return instances that match the reservation',
) )
parser.add_argument( parser.add_argument(
'--ip', '--ip',
metavar='<ip-address-regex>',
help='regular expression to match IP address', help='regular expression to match IP address',
) )
parser.add_argument( parser.add_argument(
'--ip6', '--ip6',
metavar='<ip-address-regex>',
help='regular expression to match IPv6 address', help='regular expression to match IPv6 address',
) )
parser.add_argument( parser.add_argument(
'--name', '--name',
metavar='<name>',
help='regular expression to match name', help='regular expression to match name',
) )
parser.add_argument( parser.add_argument(
'--instance-name', '--instance-name',
metavar='<server-name>',
help='regular expression to match instance name', help='regular expression to match instance name',
) )
parser.add_argument( parser.add_argument(
'--status', '--status',
metavar='<status>',
help='search by server status', help='search by server status',
# FIXME(dhellmann): Add choices? # FIXME(dhellmann): Add choices?
) )
parser.add_argument( parser.add_argument(
'--flavor', '--flavor',
metavar='<flavor>',
help='search by flavor ID', help='search by flavor ID',
) )
parser.add_argument( parser.add_argument(
'--image', '--image',
metavar='<image>',
help='search by image ID', help='search by image ID',
) )
parser.add_argument( parser.add_argument(
'--host', '--host',
metavar='HOSTNAME', metavar='<hostname>',
help='search by hostname', help='search by hostname',
) )
parser.add_argument( parser.add_argument(
@ -100,23 +425,23 @@ class ListServer(lister.Lister):
def take_action(self, parsed_args): def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args) self.log.debug('take_action(%s)' % parsed_args)
nova_client = self.app.client_manager.compute compute_client = self.app.client_manager.compute
search_opts = { search_opts = {
'all_tenants': parsed_args.all_tenants,
'reservation_id': parsed_args.reservation_id, 'reservation_id': parsed_args.reservation_id,
'ip': parsed_args.ip, 'ip': parsed_args.ip,
'ip6': parsed_args.ip6, 'ip6': parsed_args.ip6,
'name': parsed_args.name, 'name': parsed_args.name,
'image': parsed_args.image,
'flavor': parsed_args.flavor,
'status': parsed_args.status,
'host': parsed_args.host,
'instance_name': parsed_args.instance_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) self.log.debug('search options: %s', search_opts)
# FIXME(dhellmann): Consider adding other columns # FIXME(dhellmann): Consider adding other columns
columns = ('ID', 'Name', 'Status', 'Networks') columns = ('ID', 'Name', 'Status', 'Networks')
data = nova_client.servers.list(search_opts=search_opts) data = compute_client.servers.list(search_opts=search_opts)
return (columns, return (columns,
(utils.get_item_properties( (utils.get_item_properties(
s, columns, s, columns,
@ -125,6 +450,165 @@ class ListServer(lister.Lister):
) )
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): class ShowServer(show.ShowOne):
"""Show server command""" """Show server command"""
@ -136,32 +620,62 @@ class ShowServer(show.ShowOne):
parser.add_argument( parser.add_argument(
'server', 'server',
metavar='<server>', metavar='<server>',
help='Name or ID of server to display') help='Name or ID of server to display'),
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args) self.log.debug('take_action(%s)' % parsed_args)
nova_client = self.app.client_manager.compute compute_client = self.app.client_manager.compute
server = utils.find_resource(nova_client.servers, parsed_args.server) server = utils.find_resource(compute_client.servers,
parsed_args.server)
info = {} details = _prep_server_detail(compute_client, server)
info.update(server._info) return zip(*sorted(details.iteritems()))
# Convert the flavor blob to a name
flavor_info = info.get('flavor', {})
flavor_id = flavor_info.get('id', '')
flavor = utils.find_resource(nova_client.flavors, flavor_id)
info['flavor'] = flavor.name
# Convert the image blob to a name class SuspendServer(command.Command):
image_info = info.get('image', {}) """Suspend server command"""
image_id = image_info.get('id', '')
image = utils.find_resource(nova_client.images, image_id)
info['image'] = image.name
# Format addresses in a useful way api = 'compute'
info['addresses'] = _format_servers_list_networks(server) log = logging.getLogger(__name__ + '.SuspendServer')
# Remove a couple of values that are long and not too useful def get_parser(self, prog_name):
info.pop('links', None) parser = super(SuspendServer, self).get_parser(prog_name)
return zip(*sorted(info.iteritems())) 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

@ -55,6 +55,15 @@ setuptools.setup(
entry_points={ entry_points={
'console_scripts': ['openstack=openstackclient.shell:main'], 'console_scripts': ['openstack=openstackclient.shell:main'],
'openstack.cli': [ 'openstack.cli': [
'create_endpoint=' +
'openstackclient.identity.v2_0.endpoint:CreateEndpoint',
'delete_endpoint=' +
'openstackclient.identity.v2_0.endpoint:DeleteEndpoint',
'list_endpoint=' +
'openstackclient.identity.v2_0.endpoint:ListEndpoint',
'show_endpoint=' +
'openstackclient.identity.v2_0.endpoint:ShowEndpoint',
'add_role=' + 'add_role=' +
'openstackclient.identity.v2_0.role:AddRole', 'openstackclient.identity.v2_0.role:AddRole',
'create_role=' + 'create_role=' +
@ -65,22 +74,25 @@ setuptools.setup(
'remove_role=' + 'remove_role=' +
'openstackclient.identity.v2_0.role:RemoveRole', 'openstackclient.identity.v2_0.role:RemoveRole',
'show_role=openstackclient.identity.v2_0.role:ShowRole', 'show_role=openstackclient.identity.v2_0.role:ShowRole',
'create_server=openstackclient.compute.v2.server:CreateServer',
'delete_server=openstackclient.compute.v2.server:DeleteServer',
'list_server=openstackclient.compute.v2.server:ListServer', 'list_server=openstackclient.compute.v2.server:ListServer',
'pause_server=openstackclient.compute.v2.server:PauseServer',
'reboot_server=openstackclient.compute.v2.server:RebootServer',
'rebuild_server=openstackclient.compute.v2.server:RebuildServer',
'resume_server=openstackclient.compute.v2.server:ResumeServer',
'show_server=openstackclient.compute.v2.server:ShowServer', 'show_server=openstackclient.compute.v2.server:ShowServer',
'create_endpoint=' + 'suspend_server=openstackclient.compute.v2.server:SuspendServer',
'openstackclient.identity.v2_0.endpoint:CreateEndpoint', 'unpause_server=openstackclient.compute.v2.server:UnpauseServer',
'delete_endpoint=' +
'openstackclient.identity.v2_0.endpoint:DeleteEndpoint',
'list_endpoint=' +
'openstackclient.identity.v2_0.endpoint:ListEndpoint',
'show_endpoint=' +
'openstackclient.identity.v2_0.endpoint:ShowEndpoint',
'create_service=' + 'create_service=' +
'openstackclient.identity.v2_0.service:CreateService', 'openstackclient.identity.v2_0.service:CreateService',
'delete_service=' + 'delete_service=' +
'openstackclient.identity.v2_0.service:DeleteService', 'openstackclient.identity.v2_0.service:DeleteService',
'list_service=openstackclient.identity.v2_0.service:ListService', 'list_service=openstackclient.identity.v2_0.service:ListService',
'show_service=openstackclient.identity.v2_0.service:ShowService', 'show_service=openstackclient.identity.v2_0.service:ShowService',
'create_tenant=' + 'create_tenant=' +
'openstackclient.identity.v2_0.tenant:CreateTenant', 'openstackclient.identity.v2_0.tenant:CreateTenant',
'delete_tenant=' + 'delete_tenant=' +
@ -88,6 +100,7 @@ setuptools.setup(
'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant', 'list_tenant=openstackclient.identity.v2_0.tenant:ListTenant',
'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant', 'set_tenant=openstackclient.identity.v2_0.tenant:SetTenant',
'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant', 'show_tenant=openstackclient.identity.v2_0.tenant:ShowTenant',
'create_user=' + 'create_user=' +
'openstackclient.identity.v2_0.user:CreateUser', 'openstackclient.identity.v2_0.user:CreateUser',
'delete_user=' + 'delete_user=' +