summaryrefslogtreecommitdiff
path: root/novaclient/v1_1/shell.py
diff options
context:
space:
mode:
Diffstat (limited to 'novaclient/v1_1/shell.py')
-rw-r--r--novaclient/v1_1/shell.py555
1 files changed, 555 insertions, 0 deletions
diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py
new file mode 100644
index 00000000..dfa26a14
--- /dev/null
+++ b/novaclient/v1_1/shell.py
@@ -0,0 +1,555 @@
+# Copyright 2010 Jacob Kaplan-Moss
+
+# 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.
+
+import getpass
+import os
+import uuid
+
+from novaclient import exceptions
+from novaclient import utils
+from novaclient.v1_1 import client
+from novaclient.v1_1 import servers
+
+
+CLIENT_CLASS = client.Client
+
+
+AUTO_KEY = object()
+
+
+def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
+ """Boot a new server."""
+ if min_count is None:
+ min_count = 1
+ if max_count is None:
+ max_count = min_count
+ if min_count > max_count:
+ raise exceptions.CommandError("min_instances should be <= max_instances")
+ if not min_count or not max_count:
+ raise exceptions.CommandError("min_instances nor max_instances should be 0")
+
+ 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)
+ try:
+ 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
+ else:
+ 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:
+ try:
+ 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,
+ reservation_id, min_count, max_count)
+
+@utils.arg('--flavor',
+ default=None,
+ metavar='<flavor>',
+ help="Flavor ID (see 'nova flavors'). "\
+ "Defaults to 256MB RAM instance.")
+@utils.arg('--image',
+ default=None,
+ metavar='<image>',
+ help="Image ID (see 'nova images'). "\
+ "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, reservation_id, \
+ min_count, max_count = _boot(cs, args)
+
+ server = cs.servers.create(args.name, image, flavor,
+ meta=metadata,
+ files=files,
+ min_count=min_count,
+ max_count=max_count)
+ utils.print_dict(server._info)
+
+
+@utils.arg('--flavor',
+ default=None,
+ metavar='<flavor>',
+ help="Flavor ID (see 'nova flavors'). "\
+ "Defaults to 256MB RAM instance.")
+@utils.arg('--image',
+ default=None,
+ metavar='<image>',
+ help="Image ID (see 'nova images'). "\
+ "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('--reservation_id',
+ default=None,
+ metavar='<reservation_id>',
+ help="Reservation ID (a UUID). "\
+ "If unspecified will be generated by the server.")
+@utils.arg('--min_instances',
+ default=None,
+ type=int,
+ metavar='<number>',
+ help="The minimum number of instances to build. "\
+ "Defaults to 1.")
+@utils.arg('--max_instances',
+ default=None,
+ type=int,
+ metavar='<number>',
+ help="The maximum number of instances to build. "\
+ "Defaults to 'min_instances' setting.")
+@utils.arg('name', metavar='<name>', help='Name for the new server')
+def do_zone_boot(cs, args):
+ """Boot a new server, potentially across Zones."""
+ reservation_id = args.reservation_id
+ min_count = args.min_instances
+ max_count = args.max_instances
+ name, image, flavor, metadata, \
+ files, reservation_id, min_count, max_count = \
+ _boot(cs, args,
+ reservation_id=reservation_id,
+ min_count=min_count,
+ max_count=max_count)
+
+ reservation_id = cs.zones.boot(args.name, image, flavor,
+ meta=metadata,
+ files=files,
+ reservation_id=reservation_id,
+ min_count=min_count,
+ max_count=max_count)
+ print "Reservation ID=", reservation_id
+
+
+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])
+
+
+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}
+
+ if recurse_zones:
+ id_col = 'UUID'
+ else:
+ 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)
+
+
+@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_migrate(cs, args):
+ """Migrate a server."""
+ _find_server(cs, args.server).migrate()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_pause(cs, args):
+ """Pause a server."""
+ _find_server(cs, args.server).pause()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_unpause(cs, args):
+ """Unpause a server."""
+ _find_server(cs, args.server).unpause()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_suspend(cs, args):
+ """Suspend a server."""
+ _find_server(cs, args.server).suspend()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_resume(cs, args):
+ """Resume a server."""
+ _find_server(cs, args.server).resume()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_rescue(cs, args):
+ """Rescue a server."""
+ _find_server(cs, args.server).rescue()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_unrescue(cs, args):
+ """Unrescue a server."""
+ _find_server(cs, args.server).unrescue()
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_diagnostics(cs, args):
+ """Retrieve server diagnostics."""
+ utils.print_dict(cs.servers.diagnostics(args.server)[1])
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+def do_actions(cs, args):
+ """Retrieve server actions."""
+ utils.print_list(
+ cs.servers.actions(args.server),
+ ["Created_At", "Action", "Error"])
+
+
+
+@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.')
+def do_image_create(cs, args):
+ """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)
+
+ networks = s.networks
+
+ info = s._info.copy()
+ for network_label, address_list in networks.items():
+ info['%s network' % network_label] = ', '.join(address_list)
+
+ 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
+
+ info.pop('links', None)
+ info.pop('addresses', None)
+
+ 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)
+
+def _find_resource(manager, name_or_id):
+ """Helper for the _find_* methods."""
+ try:
+ 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))
+
+# --zone_username is required since --username is already used.
+@utils.arg('zone', metavar='<zone_id>', help='ID of the zone', default=None)
+@utils.arg('--api_url', dest='api_url', default=None, help='New URL.')
+@utils.arg('--zone_username', dest='zone_username', default=None,
+ help='New zone username.')
+@utils.arg('--password', dest='password', default=None, help='New password.')
+@utils.arg('--weight_offset', dest='weight_offset', default=None,
+ help='Child Zone weight offset.')
+@utils.arg('--weight_scale', dest='weight_scale', default=None,
+ help='Child Zone weight scale.')
+def do_zone(cs, args):
+ """Show or edit a child zone. No zone arg for this zone."""
+ zone = cs.zones.get(args.zone)
+
+ # If we have some flags, update the zone
+ zone_delta = {}
+ if args.api_url:
+ zone_delta['api_url'] = args.api_url
+ if args.zone_username:
+ zone_delta['username'] = args.zone_username
+ if args.password:
+ zone_delta['password'] = args.password
+ if args.weight_offset:
+ zone_delta['weight_offset'] = args.weight_offset
+ if args.weight_scale:
+ zone_delta['weight_scale'] = args.weight_scale
+ if zone_delta:
+ zone.update(**zone_delta)
+ else:
+ utils.print_dict(zone._info)
+
+def do_zone_info(cs, args):
+ """Get this zones name and capabilities."""
+ zone = cs.zones.info()
+ utils.print_dict(zone._info)
+
+@utils.arg('api_url', metavar='<api_url>', help="URL for the Zone's API")
+@utils.arg('zone_username', metavar='<zone_username>',
+ help='Authentication username.')
+@utils.arg('password', metavar='<password>', help='Authentication password.')
+@utils.arg('weight_offset', metavar='<weight_offset>',
+ help='Child Zone weight offset (typically 0.0).')
+@utils.arg('weight_scale', metavar='<weight_scale>',
+ help='Child Zone weight scale (typically 1.0).')
+def do_zone_add(cs, args):
+ """Add a new child zone."""
+ zone = cs.zones.create(args.api_url, args.zone_username,
+ args.password, args.weight_offset,
+ args.weight_scale)
+ utils.print_dict(zone._info)
+
+@utils.arg('zone', metavar='<zone>', help='Name or ID of the zone')
+def do_zone_delete(cs, args):
+ """Delete a zone."""
+ cs.zones.delete(args.zone)
+
+def do_zone_list(cs, args):
+ """List the children of a zone."""
+ utils.print_list(cs.zones.list(), ['ID', 'Name', 'Is Active', \
+ 'API URL', 'Weight Offset', 'Weight Scale'])
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+@utils.arg('network_id', metavar='<network_id>', help='Network ID.')
+def do_add_fixed_ip(cs, args):
+ """Add new IP address to network."""
+ server = _find_server(cs, args.server)
+ server.add_fixed_ip(args.network_id)
+
+@utils.arg('server', metavar='<server>', help='Name or ID of server.')
+@utils.arg('address', metavar='<address>', help='IP Address.')
+def do_remove_fixed_ip(cs, args):
+ """Remove an IP address from a server."""
+ server = _find_server(cs, args.server)
+ server.remove_fixed_ip(args.address)