FAM-690 Snapshots support for azure resource manager driver
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/6078d854 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/6078d854 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/6078d854 Branch: refs/heads/trunk Commit: 6078d854685c8e2114bdef840425828505e19ba8 Parents: 4bd4af9 Author: mermoldy <s.ba...@scalr.com> Authored: Mon Feb 27 18:55:30 2017 +0200 Committer: Anthony Shaw <anthonys...@apache.org> Committed: Fri Aug 11 14:59:31 2017 +1000 ---------------------------------------------------------------------- libcloud/compute/drivers/azure_arm.py | 229 +++++++++++++++++++++++++---- 1 file changed, 199 insertions(+), 30 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/6078d854/libcloud/compute/drivers/azure_arm.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/azure_arm.py b/libcloud/compute/drivers/azure_arm.py index 3f20b7e..57117f3 100644 --- a/libcloud/compute/drivers/azure_arm.py +++ b/libcloud/compute/drivers/azure_arm.py @@ -27,14 +27,16 @@ import time from libcloud.common.azure_arm import AzureResourceManagementConnection from libcloud.compute.providers import Provider from libcloud.compute.base import Node, NodeDriver, NodeLocation, NodeSize -from libcloud.compute.base import NodeImage, StorageVolume +from libcloud.compute.base import NodeImage, StorageVolume, VolumeSnapshot from libcloud.compute.base import NodeAuthPassword, NodeAuthSSHKey -from libcloud.compute.types import NodeState, StorageVolumeState +from libcloud.compute.types import (NodeState, StorageVolumeState, + VolumeSnapshotState) from libcloud.common.types import LibcloudError from libcloud.storage.types import ObjectDoesNotExistError from libcloud.common.exceptions import BaseHTTPError from libcloud.storage.drivers.azure_blobs import AzureBlobsStorageDriver from libcloud.utils.py3 import basestring +from libcloud.utils.iso8601 import parse_date RESOURCE_API_VERSION = '2016-04-30-preview' @@ -769,14 +771,14 @@ class AzureNodeDriver(NodeDriver): :type name: ``str`` :param location: Which data center to create a volume in. - :type location: :class:`.NodeLocation` + :type location: :class:`NodeLocation` :param snapshot: Snapshot from which to create the new volume. (optional) - :type snapshot: :class:`.VolumeSnapshot` + :type snapshot: :class:`VolumeSnapshot` :param ex_resource_group: The name of resource group in which to - create the node. + create the volume. :type ex_resource_group: ``str`` :param ex_tags: Optional tags to associate with this resource. @@ -785,7 +787,6 @@ class AzureNodeDriver(NodeDriver): :return: The newly created volume. :rtype: :class:`StorageVolume` """ - if location is None: raise ValueError("Must provide `location` value") @@ -811,6 +812,13 @@ class AzureNodeDriver(NodeDriver): 'diskSizeGB': size } } + if snapshot is not None: + data['properties']['creationData'] = { + 'createOption': 'Copy', + 'sourceUri': { + 'id': snapshot.id + } + } response = self.connection.request( action, @@ -892,7 +900,6 @@ class AzureNodeDriver(NodeDriver): disks.append({ 'lun': ex_lun, - 'name': volume.name, 'createOption': 'attach', 'managedDisk': { 'id': volume.id @@ -957,29 +964,117 @@ class AzureNodeDriver(NodeDriver): """ Delete a volume. """ - self.connection.request( - volume.id, - method='DELETE', - params={ - 'api-version': RESOURCE_API_VERSION - }, - ) + self.ex_delete_resource(volume) return True - def create_volume_snapshot(self, volume, name=None): + def create_volume_snapshot(self, volume, name=None, location=None, + ex_resource_group=None, ex_tags=None): """ Create snapshot from volume. - :param volume: Instance of ``StorageVolume`` + :param volume: Instance of ``StorageVolume``. :type volume: :class`StorageVolume` - :param name: Name of snapshot (optional) + :param name: Name of snapshot (optional). :type name: ``str`` + :param location: Which data center to create a volume in. + :type location: :class:`NodeLocation` + + :param ex_resource_group: The name of resource group in which to + create the snapshot. + :type ex_resource_group: ``str`` + + :param ex_tags: Optional tags to associate with this resource. + :type ex_tags: ``dict`` + :rtype: :class:`VolumeSnapshot` """ - super(AzureNodeDriver).create_volume_snapshot(volume, name=name) - # TODO + if name is None: + raise ValueError("Must provide `name` value") + if location is None: + raise ValueError("Must provide `location` value") + if ex_resource_group is None: + raise ValueError("Must provide `ex_resource_group` value") + + snapshot_id = ( + u'/subscriptions/{subscription_id}' + u'/resourceGroups/{resource_group}' + u'/providers/Microsoft.Compute' + u'/snapshots/{snapshot_name}' + ).format( + subscription_id=self.subscription_id, + resource_group=ex_resource_group, + snapshot_name=name, + ) + tags = ex_tags if ex_tags is not None else {} + + data = { + 'location': location.id, + 'tags': tags, + 'properties': { + 'creationData': { + 'createOption': 'Copy', + 'sourceUri': volume.id + }, + } + } + response = self.connection.request( + snapshot_id, + method='PUT', + data=data, + params={ + 'api-version': RESOURCE_API_VERSION + }, + ) + + return self._to_snapshot( + response.object, + name=name, + ex_resource_group=ex_resource_group + ) + + def list_volume_snapshots(self, volume): + return [snapshot for snapshot in self.list_snapshots() + if snapshot.extra['source_id'] == volume.id] + + def list_snapshots(self, ex_resource_group=None): + """ + Lists all the snapshots under a resource group or subscription. + + :param ex_resource_group: The identifier of your subscription + where the managed snapshots are located (optional). + :type ex_resource_group: ``str`` + + :rtype: list of :class:`VolumeSnapshot` + """ + if ex_resource_group: + action = u'/subscriptions/{subscription_id}/resourceGroups' \ + u'/{resource_group}/providers/Microsoft.Compute/snapshots' + else: + action = u'/subscriptions/{subscription_id}' \ + u'/providers/Microsoft.Compute/snapshots' + + action = action.format( + subscription_id=self.subscription_id, + resource_group=ex_resource_group + ) + + response = self.connection.request( + action, + method='GET', + params={ + 'api-version': RESOURCE_API_VERSION + } + ) + return [self._to_snapshot(snap) for snap in response.object['value']] + + def destroy_volume_snapshot(self, snapshot): + """ + Delete a snapshot. + """ + self.ex_delete_resource(snapshot) + return True def _to_volume(self, volume_obj, name=None, ex_resource_group=None): """ @@ -989,25 +1084,21 @@ class AzureNodeDriver(NodeDriver): :type volume_obj: ``dict`` :param name: An optional name for the volume. If not provided - then either tag with a key "name" will be used. + then either tag with a key "name" will be used. (optional) :type name: ``str`` :param ex_resource_group: An optional resource group for the volume. If not provided then either tag with a key "resource_group" - will be used. + will be used. (optional) :type ex_resource_group: ``str`` :rtype: :class:`StorageVolume` """ volume_id = volume_obj.get('id') volume_name = volume_obj.get('name') - properties = volume_obj['properties'] + extra = dict(volume_obj) + properties = extra['properties'] size = int(properties['diskSizeGB']) - extra = { - 'tags': volume_obj.get('tags', {}), - 'location_id': volume_obj['location'], - 'properties': properties, - } provisioning_state = properties.get('provisioningState', '').lower() disk_state = properties.get('diskState', '').lower() @@ -1023,9 +1114,9 @@ class AzureNodeDriver(NodeDriver): else: state = StorageVolumeState.UNKNOWN - if volume_id is None and \ - ex_resource_group is not None and \ - name is not None: + if volume_id is None \ + and ex_resource_group is not None \ + and name is not None: volume_id = ( u'/subscriptions/{subscription_id}' u'/resourceGroups/{resource_group}' @@ -1048,6 +1139,84 @@ class AzureNodeDriver(NodeDriver): extra=extra ) + def _to_snapshot(self, snapshot_obj, name=None, ex_resource_group=None): + """ + Parse the JSON element and return a VolumeSnapshot object. + + :param snapshot_obj: A snapshot object from an azure response. + :type snapshot_obj: ``dict`` + + :param name: An optional name for the volume. + :type name: ``str`` + + :param ex_resource_group: An optional resource group for the volume. + If not provided then either tag with a key "resource_group" + will be used. + :type ex_resource_group: ``str`` + + :rtype: :class:`VolumeSnapshot` + """ + snapshot_id = snapshot_obj.get('id') + name = snapshot_obj.get('name', name) + properties = snapshot_obj['properties'] + size = properties.get('diskSizeGB') + if size is not None: + size = int(size) + extra = dict(snapshot_obj) + extra['source_id'] = properties['creationData']['sourceUri'] + if '/providers/Microsoft.Compute/disks/' in extra['source_id']: + extra['volume_id'] = extra['source_id'] + + provisioning_state = properties.get('provisioningState', '').lower() + if provisioning_state in ('creating', 'updating'): + state = VolumeSnapshotState.CREATING + elif provisioning_state == 'succeeded': + state = VolumeSnapshotState.AVAILABLE + elif provisioning_state == 'failed': + state = VolumeSnapshotState.ERROR + else: + state = VolumeSnapshotState.UNKNOWN + + try: + created_at = parse_date(snapshot_obj['properties']['timeCreated']) + except ValueError: + created_at = None + + if snapshot_id is None \ + and ex_resource_group is not None \ + and name is not None: + snapshot_id = ( + u'/subscriptions/{subscription_id}' + u'/resourceGroups/{resource_group}' + u'/providers/Microsoft.Compute/snapshots/{snapshot_name}' + ).format( + subscription_id=self.subscription_id, + resource_group=ex_resource_group.upper(), + snapshot_name=name + ) + + return VolumeSnapshot( + snapshot_id, + name=name, + size=size, + driver=self, + state=state, + extra=extra, + created=created_at + ) + + def ex_delete_resource(self, resource): + """ + Delete a resource. + """ + self.connection.request( + resource.id, + method='DELETE', + params={ + 'api-version': RESOURCE_API_VERSION + }, + ) + def ex_get_ratecard(self, offer_durable_id, currency='USD', locale='en-US', region='US'): """