2011-02-25 02:00:13 -08:00
|
|
|
# Copyright 2010 Jacob Kaplan-Moss
|
2011-02-24 13:54:10 -04:00
|
|
|
|
|
|
|
# Copyright 2011 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.
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
import getpass
|
|
|
|
import os
|
2011-06-09 10:38:01 -05:00
|
|
|
import uuid
|
2011-01-25 14:01:22 -06:00
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
from novaclient import exceptions
|
|
|
|
from novaclient import utils
|
|
|
|
from novaclient.v1_1 import client
|
2011-08-03 16:36:03 -04:00
|
|
|
from novaclient.v1_1 import servers
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
CLIENT_CLASS = client.Client
|
|
|
|
|
2011-01-25 14:01:22 -06:00
|
|
|
|
|
|
|
AUTO_KEY = object()
|
|
|
|
|
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
def _translate_flavor_keys(collection):
|
|
|
|
convert = [('ram', 'memory_mb'), ('disk', 'local_gb')]
|
|
|
|
for item in collection:
|
|
|
|
keys = item.__dict__.keys()
|
|
|
|
for from_key, to_key in convert:
|
|
|
|
if from_key in keys and to_key not in keys:
|
|
|
|
setattr(item, to_key, item._info[from_key])
|
|
|
|
|
|
|
|
@utils.arg('--flavor',
|
|
|
|
default=None,
|
|
|
|
metavar='<flavor>',
|
2011-08-04 12:40:25 -04:00
|
|
|
help="Flavor ID (see 'nova flavors'). "\
|
2011-08-03 17:41:33 -04:00
|
|
|
"Defaults to 256MB RAM instance.")
|
|
|
|
@utils.arg('--image',
|
|
|
|
default=None,
|
|
|
|
metavar='<image>',
|
2011-08-04 12:40:25 -04:00
|
|
|
help="Image ID (see 'nova images'). "\
|
2011-08-03 17:41:33 -04:00
|
|
|
"Defaults to Ubuntu 10.04 LTS.")
|
|
|
|
@utils.arg('--meta',
|
|
|
|
metavar="<key=value>",
|
|
|
|
action='append',
|
|
|
|
default=[],
|
|
|
|
help="Record arbitrary key/value metadata. "\
|
|
|
|
"May be give multiple times.")
|
|
|
|
@utils.arg('--file',
|
|
|
|
metavar="<dst-path=src-path>",
|
|
|
|
action='append',
|
|
|
|
dest='files',
|
|
|
|
default=[],
|
|
|
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
|
|
|
"on the new server. You may store up to 5 files.")
|
|
|
|
@utils.arg('--key',
|
|
|
|
metavar='<path>',
|
|
|
|
nargs='?',
|
|
|
|
const=AUTO_KEY,
|
|
|
|
help="Key the server with an SSH keypair. "\
|
|
|
|
"Looks in ~/.ssh for a key, "\
|
|
|
|
"or takes an explicit <path> to one.")
|
|
|
|
@utils.arg('name', metavar='<name>', help='Name for the new server')
|
|
|
|
def do_boot(cs, args):
|
|
|
|
"""Boot a new server."""
|
|
|
|
name, image, flavor, metadata, files = _boot(cs, args)
|
|
|
|
server = cs.servers.create(args.name, image, flavor,
|
|
|
|
meta=metadata, files=files)
|
2011-08-04 18:39:21 +00:00
|
|
|
info = server._info
|
|
|
|
|
|
|
|
flavor = info.get('flavor', {})
|
|
|
|
flavor_id = flavor.get('id', '')
|
|
|
|
info['flavor'] = _find_flavor(cs, flavor_id).name
|
|
|
|
|
|
|
|
image = info.get('image', {})
|
|
|
|
image_id = image.get('id', '')
|
|
|
|
info['image'] = _find_image(cs, image_id).name
|
|
|
|
|
2011-08-04 15:53:30 -04:00
|
|
|
info.pop('links', None)
|
2011-08-04 18:39:21 +00:00
|
|
|
info.pop('addresses', None)
|
2011-08-04 15:53:30 -04:00
|
|
|
|
2011-08-04 18:39:21 +00:00
|
|
|
utils.print_dict(info)
|
2011-08-03 17:41:33 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _boot(cs, args):
|
|
|
|
"""Boot a new server."""
|
|
|
|
|
|
|
|
flavor = args.flavor or cs.flavors.find(ram=256)
|
|
|
|
image = args.image or cs.images.find(name="Ubuntu 10.04 LTS "\
|
|
|
|
"(lucid)")
|
|
|
|
|
|
|
|
metadata = dict(v.split('=') for v in args.meta)
|
|
|
|
|
|
|
|
files = {}
|
|
|
|
for f in args.files:
|
|
|
|
dst, src = f.split('=', 1)
|
2011-01-25 14:01:22 -06:00
|
|
|
try:
|
2011-08-03 17:41:33 -04:00
|
|
|
files[dst] = open(src)
|
|
|
|
except IOError, e:
|
|
|
|
raise exceptions.CommandError("Can't open '%s': %s" % (src, e))
|
|
|
|
|
|
|
|
if args.key is AUTO_KEY:
|
|
|
|
possible_keys = [os.path.join(os.path.expanduser('~'), '.ssh', k)
|
|
|
|
for k in ('id_dsa.pub', 'id_rsa.pub')]
|
|
|
|
for k in possible_keys:
|
|
|
|
if os.path.exists(k):
|
|
|
|
keyfile = k
|
|
|
|
break
|
2011-01-25 14:01:22 -06:00
|
|
|
else:
|
2011-08-03 17:41:33 -04:00
|
|
|
raise exceptions.CommandError("Couldn't find a key file: tried "
|
|
|
|
"~/.ssh/id_dsa.pub or ~/.ssh/id_rsa.pub")
|
|
|
|
elif args.key:
|
|
|
|
keyfile = args.key
|
|
|
|
else:
|
|
|
|
keyfile = None
|
|
|
|
|
|
|
|
if keyfile:
|
2011-01-25 14:01:22 -06:00
|
|
|
try:
|
2011-08-03 17:41:33 -04:00
|
|
|
files['/root/.ssh/authorized_keys2'] = open(keyfile)
|
|
|
|
except IOError, e:
|
|
|
|
raise exceptions.CommandError("Can't open '%s': %s" % (keyfile, e))
|
|
|
|
|
|
|
|
return (args.name, image, flavor, metadata, files)
|
|
|
|
|
|
|
|
|
|
|
|
def do_flavor_list(cs, args):
|
|
|
|
"""Print a list of available 'flavors' (sizes of servers)."""
|
|
|
|
flavors = cs.flavors.list()
|
|
|
|
_translate_flavor_keys(flavors)
|
|
|
|
utils.print_list(flavors, [
|
|
|
|
'ID',
|
|
|
|
'Name',
|
|
|
|
'Memory_MB',
|
|
|
|
'Swap',
|
|
|
|
'Local_GB',
|
|
|
|
'VCPUs',
|
|
|
|
'RXTX_Quota',
|
|
|
|
'RXTX_Cap'])
|
|
|
|
|
|
|
|
def do_image_list(cs, args):
|
|
|
|
"""Print a list of available images to boot from."""
|
|
|
|
utils.print_list(cs.images.list(), ['ID', 'Name', 'Status'])
|
|
|
|
|
|
|
|
@utils.arg('image', metavar='<image>', help='Name or ID of image.')
|
|
|
|
def do_image_delete(cs, args):
|
|
|
|
"""
|
|
|
|
Delete an image.
|
|
|
|
|
|
|
|
It should go without saying, but you can only delete images you
|
|
|
|
created.
|
|
|
|
"""
|
|
|
|
image = _find_image(cs, args.image)
|
|
|
|
image.delete()
|
|
|
|
|
|
|
|
@utils.arg('--fixed_ip',
|
|
|
|
dest='fixed_ip',
|
|
|
|
metavar='<fixed_ip>',
|
|
|
|
default=None,
|
|
|
|
help='Only match against fixed IP.')
|
|
|
|
@utils.arg('--reservation_id',
|
|
|
|
dest='reservation_id',
|
|
|
|
metavar='<reservation_id>',
|
|
|
|
default=None,
|
|
|
|
help='Only return instances that match reservation_id.')
|
|
|
|
@utils.arg('--recurse_zones',
|
|
|
|
dest='recurse_zones',
|
|
|
|
metavar='<0|1>',
|
|
|
|
nargs='?',
|
|
|
|
type=int,
|
|
|
|
const=1,
|
|
|
|
default=0,
|
|
|
|
help='Recurse through all zones if set.')
|
|
|
|
@utils.arg('--ip',
|
|
|
|
dest='ip',
|
|
|
|
metavar='<ip_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by IP address')
|
|
|
|
@utils.arg('--ip6',
|
|
|
|
dest='ip6',
|
|
|
|
metavar='<ip6_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by IPv6 address')
|
|
|
|
@utils.arg('--server_name',
|
|
|
|
dest='server_name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by server name')
|
|
|
|
@utils.arg('--name',
|
|
|
|
dest='display_name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by display name')
|
|
|
|
@utils.arg('--instance_name',
|
|
|
|
dest='name',
|
|
|
|
metavar='<name_regexp>',
|
|
|
|
default=None,
|
|
|
|
help='Search with regular expression match by instance name')
|
|
|
|
def do_list(cs, args):
|
|
|
|
"""List active servers."""
|
|
|
|
recurse_zones = args.recurse_zones
|
|
|
|
search_opts = {
|
|
|
|
'reservation_id': args.reservation_id,
|
|
|
|
'fixed_ip': args.fixed_ip,
|
|
|
|
'recurse_zones': recurse_zones,
|
|
|
|
'ip': args.ip,
|
|
|
|
'ip6': args.ip6,
|
|
|
|
'name': args.name,
|
|
|
|
'server_name': args.server_name,
|
|
|
|
'display_name': args.display_name}
|
2011-08-04 18:39:21 +00:00
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
if recurse_zones:
|
2011-08-04 18:39:21 +00:00
|
|
|
id_col = 'UUID'
|
2011-08-03 17:41:33 -04:00
|
|
|
else:
|
2011-08-04 18:39:21 +00:00
|
|
|
id_col = 'ID'
|
|
|
|
|
|
|
|
columns = [id_col, 'Name', 'Status', 'Networks']
|
|
|
|
formatters = {'Networks': _format_servers_list_networks}
|
|
|
|
utils.print_list(cs.servers.list(search_opts=search_opts), columns, formatters)
|
|
|
|
|
|
|
|
|
|
|
|
def _format_servers_list_networks(server):
|
|
|
|
output = []
|
|
|
|
for (network, addresses) in server.networks.items():
|
|
|
|
if len(addresses) == 0:
|
|
|
|
continue
|
|
|
|
addresses_csv = ', '.join(addresses)
|
|
|
|
group = "%s=%s" % (network, addresses_csv)
|
|
|
|
output.append(group)
|
|
|
|
|
|
|
|
return '; '.join(output)
|
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
|
|
|
|
@utils.arg('--hard',
|
|
|
|
dest='reboot_type',
|
|
|
|
action='store_const',
|
|
|
|
const=servers.REBOOT_HARD,
|
|
|
|
default=servers.REBOOT_SOFT,
|
|
|
|
help='Perform a hard reboot (instead of a soft one).')
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_reboot(cs, args):
|
|
|
|
"""Reboot a server."""
|
|
|
|
_find_server(cs, args.server).reboot(args.reboot_type)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@utils.arg('image', metavar='<image>', help="Name or ID of new image.")
|
|
|
|
def do_rebuild(cs, args):
|
|
|
|
"""Shutdown, re-image, and re-boot a server."""
|
|
|
|
server = _find_server(cs, args.server)
|
|
|
|
image = _find_image(cs, args.image)
|
|
|
|
server.rebuild(image)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name (old name) or ID of server.')
|
|
|
|
@utils.arg('name', metavar='<name>', help='New name for the server.')
|
|
|
|
def do_rename(cs, args):
|
|
|
|
"""Rename a server."""
|
|
|
|
_find_server(cs, args.server).update(name=args.name)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@utils.arg('flavor', metavar='<flavor>', help="Name or ID of new flavor.")
|
|
|
|
def do_resize(cs, args):
|
|
|
|
"""Resize a server."""
|
|
|
|
server = _find_server(cs, args.server)
|
|
|
|
flavor = _find_flavor(cs, args.flavor)
|
|
|
|
server.resize(flavor)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_resize_confirm(cs, args):
|
|
|
|
"""Confirm a previous resize."""
|
|
|
|
_find_server(cs, args.server).confirm_resize()
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_resize_revert(cs, args):
|
|
|
|
"""Revert a previous resize (and return to the previous VM)."""
|
|
|
|
_find_server(cs, args.server).revert_resize()
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_root_password(cs, args):
|
|
|
|
"""
|
|
|
|
Change the root password for a server.
|
|
|
|
"""
|
|
|
|
server = _find_server(cs, args.server)
|
|
|
|
p1 = getpass.getpass('New password: ')
|
|
|
|
p2 = getpass.getpass('Again: ')
|
|
|
|
if p1 != p2:
|
|
|
|
raise exceptions.CommandError("Passwords do not match.")
|
|
|
|
server.change_password(p1)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
@utils.arg('name', metavar='<name>', help='Name of snapshot.')
|
2011-08-04 12:35:41 -04:00
|
|
|
def do_image_create(cs, args):
|
2011-08-03 17:41:33 -04:00
|
|
|
"""Create a new image by taking a snapshot of a running server."""
|
|
|
|
server = _find_server(cs, args.server)
|
|
|
|
cs.servers.create_image(server, args.name)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_show(cs, args):
|
|
|
|
"""Show details about the given server."""
|
|
|
|
s = _find_server(cs, args.server)
|
|
|
|
|
2011-08-04 18:39:21 +00:00
|
|
|
networks = s.networks
|
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
info = s._info.copy()
|
2011-08-04 18:39:21 +00:00
|
|
|
for network_label, address_list in networks.items():
|
|
|
|
info['%s network' % network_label] = ', '.join(address_list)
|
2011-08-03 17:41:33 -04:00
|
|
|
|
|
|
|
flavor = info.get('flavor', {})
|
|
|
|
flavor_id = flavor.get('id', '')
|
|
|
|
info['flavor'] = _find_flavor(cs, flavor_id).name
|
|
|
|
|
|
|
|
image = info.get('image', {})
|
|
|
|
image_id = image.get('id', '')
|
|
|
|
info['image'] = _find_image(cs, image_id).name
|
|
|
|
|
2011-08-04 15:53:30 -04:00
|
|
|
info.pop('links', None)
|
2011-08-04 18:39:21 +00:00
|
|
|
info.pop('addresses', None)
|
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
utils.print_dict(info)
|
|
|
|
|
|
|
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
|
|
|
def do_delete(cs, args):
|
|
|
|
"""Immediately shut down and delete a server."""
|
|
|
|
_find_server(cs, args.server).delete()
|
|
|
|
|
|
|
|
def _find_server(cs, server):
|
|
|
|
"""Get a server by name or ID."""
|
|
|
|
return _find_resource(cs.servers, server)
|
|
|
|
|
|
|
|
def _find_image(cs, image):
|
|
|
|
"""Get an image by name or ID."""
|
|
|
|
return _find_resource(cs.images, image)
|
|
|
|
|
|
|
|
def _find_flavor(cs, flavor):
|
|
|
|
"""Get a flavor by name, ID, or RAM size."""
|
|
|
|
try:
|
|
|
|
return _find_resource(cs.flavors, flavor)
|
|
|
|
except exceptions.NotFound:
|
|
|
|
return cs.flavors.find(ram=flavor)
|
2011-01-25 14:01:22 -06:00
|
|
|
|
2011-08-03 17:41:33 -04:00
|
|
|
def _find_resource(manager, name_or_id):
|
|
|
|
"""Helper for the _find_* methods."""
|
2011-01-25 14:01:22 -06:00
|
|
|
try:
|
2011-08-03 17:41:33 -04:00
|
|
|
if isinstance(name_or_id, int) or name_or_id.isdigit():
|
|
|
|
return manager.get(int(name_or_id))
|
|
|
|
|
|
|
|
try:
|
|
|
|
uuid.UUID(name_or_id)
|
|
|
|
return manager.get(name_or_id)
|
|
|
|
except ValueError:
|
|
|
|
return manager.find(name=name_or_id)
|
|
|
|
except exceptions.NotFound:
|
|
|
|
raise exceptions.CommandError("No %s with a name or ID of '%s' exists." %
|
|
|
|
(manager.resource_class.__name__.lower(), name_or_id))
|
2011-02-24 13:54:10 -04:00
|
|
|
|