From 5c38baf65a39800b48a2d7b696b3090ea775176a Mon Sep 17 00:00:00 2001 From: Gaurav Gupta Date: Wed, 19 Oct 2011 10:54:27 -0700 Subject: [PATCH] Added support for listing/creating/deleting snapshots of nova volumes. Also implemented the supporting CLI commands. Requires the OS API extension, 'os-snapshots' --- README.rst | 10 ++++-- novaclient/v1_1/client.py | 1 + novaclient/v1_1/shell.py | 67 ++++++++++++++++++++++++++++++++++-- novaclient/v1_1/volumes.py | 69 +++++++++++++++++++++++++++++++++++++- 4 files changed, 142 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index af07bfca6..3d0b06c7b 100644 --- a/README.rst +++ b/README.rst @@ -56,7 +56,7 @@ endpoint:: export NOVA_URL=http://example.com:5000/v2.0/ Since Keystone can return multiple regions in the Service Catalog, you -can specify the one you want with ``--region_name`` (or +can specify the one you want with ``--region_name`` (or ``export NOVA_REGION_NAME``). It defaults to the first in the list returned. You'll find complete documentation on the shell by running @@ -124,6 +124,12 @@ You'll find complete documentation on the shell by running secgroup-list List security groups for the curent tenant. secgroup-list-rules List rules for a security group. show Show details about the given server. + snapshot-create Add a new snapshot. + snapshot-delete Remove a snapshot. + snapshot-list List all the snapshots. + snapshot-show Show details about a snapshot. + suspend Suspend a server. + unpause Unpause a server. unrescue Unrescue a server. volume-attach Attach a volume to a server. volume-create Add a new volume. @@ -190,7 +196,7 @@ Quick-start using keystone:: [...] >>> nt.keypairs.list() [...] - + # if you want to use the keystone api to modify users/tenants: >>> from novaclient import client >>> conn = client.HTTPClient(USER, PASS, TENANT, KEYSTONE_URL) diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index a46d6bbb8..0a0653338 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -38,6 +38,7 @@ class Client(object): # extensions self.volumes = volumes.VolumeManager(self) + self.snapshots = volumes.SnapshotManager(self) self.keypairs = keypairs.KeypairManager(self) self.zones = zones.ZoneManager(self) self.quotas = quotas.QuotaSetManager(self) diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py index 750cb1468..68bf1292a 100644 --- a/novaclient/v1_1/shell.py +++ b/novaclient/v1_1/shell.py @@ -253,7 +253,7 @@ def do_zone_boot(cs, args): 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')] @@ -760,11 +760,18 @@ def _find_volume(cs, volume): """Get a volume by ID.""" return utils.find_resource(cs.volumes, volume) +def _find_snapshot(cs, snapshot): + """Get a snapshot by ID.""" + return utils.find_resource(cs.snapshots, snapshot) def _print_volume(cs, volume): utils.print_dict(volume._info) +def _print_snapshot(cs, snapshot): + utils.print_dict(snapshot._info) + + def _translate_volume_keys(collection): convert = [('displayName', 'display_name')] for item in collection: @@ -774,6 +781,15 @@ def _translate_volume_keys(collection): setattr(item, to_key, item._info[from_key]) +def _translate_snapshot_keys(collection): + convert = [('displayName', 'display_name'), ('volumeId', 'volume_id')] + 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_volume_list(cs, args): """List all the volumes.""" volumes = cs.volumes.list() @@ -851,6 +867,53 @@ def do_volume_detach(cs, args): cs.volumes.delete_server_volume(_find_server(cs, args.server).id, args.attachment_id) +def do_snapshot_list(cs, args): + """List all the snapshots.""" + snapshots = cs.snapshots.list() + _translate_snapshot_keys(snapshots) + utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name', + 'Size']) + + +@utils.arg('snapshot', metavar='', help='ID of the snapshot.') +def do_snapshot_show(cs, args): + """Show details about a snapshot.""" + snapshot = _find_snapshot(cs, args.snapshot) + _print_snapshot(cs, snapshot) + + +@utils.arg('volume_id', + metavar='', + type=int, + help='ID of the volume to snapshot') +@utils.arg('--force', + metavar='', + help='Optional flag to indicate whether to snapshot a volume even if its ' + 'attached to an instance. (Default=False)', + default=False) +@utils.arg('--display_name', metavar='', + help='Optional snapshot name. (Default=None)', + default=None) +@utils.arg('--display_description', metavar='', + help='Optional snapshot description. (Default=None)', + default=None) +def do_snapshot_create(cs, args): + """Add a new snapshot.""" + cs.snapshots.create(args.volume_id, + args.force, + args.display_name, + args.display_description) + + +@utils.arg('snapshot_id', + metavar='', + help='ID of the snapshot to delete.') +def do_snapshot_delete(cs, args): + """Remove a snapshot.""" + snapshot = _find_snapshot(cs, args.snapshot_id) + snapshot.delete() + + def _print_floating_ip_list(floating_ips): utils.print_list(floating_ips, ['Ip', 'Instance Id', 'Fixed Ip']) @@ -1057,7 +1120,7 @@ def do_keypair_add(cs, args): pub_key = f.read() except IOError, e: raise exceptions.CommandError("Can't open or read '%s': %s" % (pub_key, e)) - + keypair = cs.keypairs.create(name, pub_key) if not pub_key: diff --git a/novaclient/v1_1/volumes.py b/novaclient/v1_1/volumes.py index b61a8f7d0..bed82e03f 100644 --- a/novaclient/v1_1/volumes.py +++ b/novaclient/v1_1/volumes.py @@ -33,6 +33,19 @@ class Volume(base.Resource): """ return self.manager.delete(self) +class Snapshot(base.Resource): + """ + A Snapshot is a point-in-time snapshot of an openstack volume. + """ + def __repr__(self): + return "" % self.id + + def delete(self): + """ + Delete this snapshot. + """ + return self.manager.delete(self) + class VolumeManager(base.ManagerWithFind): """ @@ -46,7 +59,7 @@ class VolumeManager(base.ManagerWithFind): Create a volume. :param size: Size of volume in GB - :param snapshot_id: ID of the snapshot + :param snapshot_id: ID of the snapshot :param display_name: Name of the volume :param display_description: Description of the volume :rtype: :class:`Volume` @@ -130,3 +143,57 @@ class VolumeManager(base.ManagerWithFind): """ return self._delete("/servers/%s/os-volume_attachments/%s" % (server_id, attachment_id,)) + + +class SnapshotManager(base.ManagerWithFind): + """ + Manage :class:`Snapshot` resources. + """ + resource_class = Snapshot + + def create(self, volume_id, force=False, + display_name=None, display_description=None): + + """ + Create a snapshot of the given volume. + + :param volume_id: The ID of the volume to snapshot. + :param force: If force is True, create a snapshot even if the volume is + attached to an instance. Default is False. + :param display_name: Name of the snapshot + :param display_description: Description of the snapshot + :rtype: :class:`Snapshot` + """ + body = {'snapshot': {'volume_id': volume_id, + 'force': force, + 'display_name': display_name, + 'display_description': display_description}} + return self._create('/os-snapshots', body, 'snapshot') + + def get(self, snapshot_id): + """ + Get a snapshot. + + :param snapshot_id: The ID of the snapshot to get. + :rtype: :class:`Snapshot` + """ + return self._get("/os-snapshots/%s" % snapshot_id, "snapshot") + + def list(self, detailed=True): + """ + Get a list of all snapshots. + + :rtype: list of :class:`Snapshot` + """ + if detailed is True: + return self._list("/os-snapshots/detail", "snapshots") + else: + return self._list("/os-snapshots", "snapshots") + + def delete(self, snapshot): + """ + Delete a snapshot. + + :param snapshot: The :class:`Snapshot` to delete. + """ + self._delete("/os-snapshots/%s" % base.getid(snapshot))