Basic example of a GCE snapshot driver. POC

Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/e9ebc5fb
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/e9ebc5fb
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/e9ebc5fb

Branch: refs/heads/trunk
Commit: e9ebc5fb6e115c8aa96e157b013ac5fbc5a05afe
Parents: b3ac5d3
Author: Anthony Shaw <anthony.p.s...@gmail.com>
Authored: Tue Nov 10 14:53:35 2015 +1100
Committer: Anthony Shaw <anthony.p.s...@gmail.com>
Committed: Tue Nov 10 14:53:35 2015 +1100

----------------------------------------------------------------------
 libcloud/backup/drivers/ebs.py |   3 +-
 libcloud/backup/drivers/gce.py | 477 ++++++++++++++++++++++++++++++++++++
 libcloud/backup/providers.py   |   4 +
 libcloud/backup/types.py       |   2 +
 4 files changed, 485 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/e9ebc5fb/libcloud/backup/drivers/ebs.py
----------------------------------------------------------------------
diff --git a/libcloud/backup/drivers/ebs.py b/libcloud/backup/drivers/ebs.py
index ea8f80e..4aba536 100644
--- a/libcloud/backup/drivers/ebs.py
+++ b/libcloud/backup/drivers/ebs.py
@@ -290,7 +290,8 @@ class EBSBackupDriver(BackupDriver):
         }
         data = self.connection.request(ROOT, params=params).object
         xpath = 'CreateSnapshotResponse'
-        return self._to_job(findall(element=data, xpath=xpath, 
namespace=NS)[0])
+        return self._to_job(findall(element=data,
+                                    xpath=xpath, namespace=NS)[0])
 
     def resume_target_job(self, target, job):
         """

http://git-wip-us.apache.org/repos/asf/libcloud/blob/e9ebc5fb/libcloud/backup/drivers/gce.py
----------------------------------------------------------------------
diff --git a/libcloud/backup/drivers/gce.py b/libcloud/backup/drivers/gce.py
new file mode 100644
index 0000000..696b2fa
--- /dev/null
+++ b/libcloud/backup/drivers/gce.py
@@ -0,0 +1,477 @@
+
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+
+__all__ = [
+    'GCEBackupDriver'
+]
+
+from libcloud.utils.iso8601 import parse_date
+from libcloud.backup.base import BackupDriver, BackupTargetRecoveryPoint,\
+    BackupTargetJob, BackupTarget
+from libcloud.backup.types import BackupTargetType, BackupTargetJobStatusType
+
+from libcloud.common.google import GoogleResponse, GoogleBaseConnection
+
+API_VERSION = 'v1'
+DEFAULT_TASK_COMPLETION_TIMEOUT = 180
+
+class GCEResponse(GoogleResponse):
+    pass
+
+
+class GCEConnection(GoogleBaseConnection):
+    """
+    Connection class for the GCE driver.
+
+    GCEConnection extends :class:`google.GoogleBaseConnection` for 2 reasons:
+      1. modify request_path for GCE URI.
+      2. Implement gce_params functionality described below.
+
+    If the parameter gce_params is set to a dict prior to calling request(),
+    the URL parameters will be updated to include those key/values FOR A
+    SINGLE REQUEST. If the response contains a nextPageToken,
+    gce_params['pageToken'] will be set to its value. This can be used to
+    implement paging in list:
+
+    >>> params, more_results = {'maxResults': 2}, True
+    >>> while more_results:
+    ...     driver.connection.gce_params=params
+    ...     driver.ex_list_urlmaps()
+    ...     more_results = 'pageToken' in params
+    ...
+    [<GCEUrlMap id="..." name="cli-map">, <GCEUrlMap id="..." name="lc-map">]
+    [<GCEUrlMap id="..." name="web-map">]
+    """
+    host = 'www.googleapis.com'
+    responseCls = GCEResponse
+
+    def __init__(self, user_id, key, secure, auth_type=None,
+                 credential_file=None, project=None, **kwargs):
+        super(GCEConnection, self).__init__(user_id, key, secure=secure,
+                                            auth_type=auth_type,
+                                            credential_file=credential_file,
+                                            **kwargs)
+        self.request_path = '/compute/%s/projects/%s' % (API_VERSION,
+                                                         project)
+        self.gce_params = None
+
+    def pre_connect_hook(self, params, headers):
+        """
+        Update URL parameters with values from self.gce_params.
+
+        @inherits: :class:`GoogleBaseConnection.pre_connect_hook`
+        """
+        params, headers = super(GCEConnection, self).pre_connect_hook(params,
+                                                                      headers)
+        if self.gce_params:
+            params.update(self.gce_params)
+        return params, headers
+
+    def request(self, *args, **kwargs):
+        """
+        Perform request then do GCE-specific processing of URL params.
+
+        @inherits: :class:`GoogleBaseConnection.request`
+        """
+        response = super(GCEConnection, self).request(*args, **kwargs)
+
+        # If gce_params has been set, then update the pageToken with the
+        # nextPageToken so it can be used in the next request.
+        if self.gce_params:
+            if 'nextPageToken' in response.object:
+                self.gce_params['pageToken'] = response.object['nextPageToken']
+            elif 'pageToken' in self.gce_params:
+                del self.gce_params['pageToken']
+            self.gce_params = None
+
+        return response
+
+
+class GCEBackupDriver(BackupDriver):
+    name = 'Google Compute Engine Backup Driver'
+    website = 'http://cloud.google.com/'
+    connectionCls = GCEConnection
+
+    def __init__(self, user_id, key=None, project=None,
+                 auth_type=None, scopes=None, credential_file=None, **kwargs):
+        """
+        :param  user_id: The email address (for service accounts) or Client ID
+                         (for installed apps) to be used for authentication.
+        :type   user_id: ``str``
+
+        :param  key: The RSA Key (for service accounts) or file path containing
+                     key or Client Secret (for installed apps) to be used for
+                     authentication.
+        :type   key: ``str``
+
+        :keyword  project: Your GCE project name. (required)
+        :type     project: ``str``
+
+        :keyword  auth_type: Accepted values are "SA" or "IA" or "GCE"
+                             ("Service Account" or "Installed Application" or
+                             "GCE" if libcloud is being used on a GCE instance
+                             with service account enabled).
+                             If not supplied, auth_type will be guessed based
+                             on value of user_id or if the code is being
+                             executed in a GCE instance.
+        :type     auth_type: ``str``
+
+        :keyword  scopes: List of authorization URLs. Default is empty and
+                          grants read/write to Compute, Storage, DNS.
+        :type     scopes: ``list``
+
+        :keyword  credential_file: Path to file for caching authentication
+                                   information used by GCEConnection.
+        :type     credential_file: ``str``
+        """
+        if not project:
+            raise ValueError('Project name must be specified using '
+                             '"project" keyword.')
+
+        self.auth_type = auth_type
+        self.project = project
+        self.scopes = scopes
+        self.credential_file = credential_file or \
+            '~/.gce_libcloud_auth' + '.' + self.project
+
+        super(GCEBackupDriver, self).__init__(user_id, key, **kwargs)
+
+        # Cache Zone and Region information to reduce API calls and
+        # increase speed
+        self.base_path = '/compute/%s/projects/%s' % (API_VERSION,
+                                                      self.project)
+
+    def get_supported_target_types(self):
+        """
+        Get a list of backup target types this driver supports
+
+        :return: ``list`` of :class:``BackupTargetType``
+        """
+        return [BackupTargetType.VOLUME]
+
+    def list_targets(self):
+        """
+        List all backuptargets
+
+        :rtype: ``list`` of :class:`BackupTarget`
+        """
+        raise NotImplementedError(
+            'list_targets not implemented for this driver')
+
+    def create_target(self, name, address,
+                      type=BackupTargetType.VOLUME, extra=None):
+        """
+        Creates a new backup target
+
+        :param name: Name of the target
+        :type name: ``str``
+
+        :param address: The volume ID.
+        :type address: ``str``
+
+        :param type: Backup target type (Physical, Virtual, ...).
+        :type type: :class:`BackupTargetType`
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type extra: ``dict``
+
+        :rtype: Instance of :class:`BackupTarget`
+        """
+        # Does nothing since any volume can be snapped at anytime.
+        return self.ex_get_target_by_source(address)
+
+    def create_target_from_node(self, node, type=BackupTargetType.VIRTUAL,
+                                extra=None):
+        """
+        Creates a new backup target from an existing node
+
+        :param node: The Node to backup
+        :type  node: ``Node``
+
+        :param type: Backup target type (Physical, Virtual, ...).
+        :type type: :class:`BackupTargetType`
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type extra: ``dict``
+
+        :rtype: Instance of :class:`BackupTarget`
+        """
+        # Get the first persistent disk
+        disks = node.extra['disks']
+        if disks is not None:
+            return self.create_target(
+                name=node.name,
+                address=disks[0]['source'],
+                type=BackupTargetType.VOLUME,
+                extra=None)
+        else:
+            raise RuntimeError("Node does not have any block devices")
+
+    def create_target_from_container(self, container,
+                                     type=BackupTargetType.OBJECT,
+                                     extra=None):
+        """
+        Creates a new backup target from an existing storage container
+
+        :param node: The Container to backup
+        :type  node: ``Container``
+
+        :param type: Backup target type (Physical, Virtual, ...).
+        :type type: :class:`BackupTargetType`
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type extra: ``dict``
+
+        :rtype: Instance of :class:`BackupTarget`
+        """
+        raise NotImplementedError(
+            'create_target_from_container not implemented for this driver')
+
+    def update_target(self, target, name, address, extra):
+        """
+        Update the properties of a backup target
+
+        :param target: Backup target to update
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param name: Name of the target
+        :type name: ``str``
+
+        :param address: Hostname, FQDN, IP, file path etc.
+        :type address: ``str``
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type extra: ``dict``
+
+        :rtype: Instance of :class:`BackupTarget`
+        """
+        # Does nothing since any volume can be snapped at anytime.
+        return self.ex_get_target_by_source(address)
+
+    def delete_target(self, target):
+        """
+        Delete a backup target
+
+        :param target: Backup target to delete
+        :type  target: Instance of :class:`BackupTarget`
+        """
+        raise NotImplementedError(
+            'delete_target not implemented for this driver')
+
+    def list_recovery_points(self, target, start_date=None, end_date=None):
+        """
+        List the recovery points available for a target
+
+        :param target: Backup target to delete
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param start_date: The start date to show jobs between (optional)
+        :type  start_date: :class:`datetime.datetime`
+
+        :param end_date: The end date to show jobs between (optional)
+        :type  end_date: :class:`datetime.datetime``
+
+        :rtype: ``list`` of :class:`BackupTargetRecoveryPoint`
+        """
+        request = '/global/snapshots'
+        response = self.connection.request(request, method='GET').object
+        return self._to_recovery_points(response, target)
+
+    def recover_target(self, target, recovery_point, path=None):
+        """
+        Recover a backup target to a recovery point
+
+        :param target: Backup target to delete
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param recovery_point: Backup target with the backup data
+        :type  recovery_point: Instance of :class:`BackupTarget`
+
+        :param path: The part of the recovery point to recover (optional)
+        :type  path: ``str``
+
+        :rtype: Instance of :class:`BackupTargetJob`
+        """
+        raise NotImplementedError(
+            'recover_target not implemented for this driver')
+
+    def recover_target_out_of_place(self, target, recovery_point,
+                                    recovery_target, path=None):
+        """
+        Recover a backup target to a recovery point out-of-place
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param recovery_point: Backup target with the backup data
+        :type  recovery_point: Instance of :class:`BackupTarget`
+
+        :param recovery_target: Backup target with to recover the data to
+        :type  recovery_target: Instance of :class:`BackupTarget`
+
+        :param path: The part of the recovery point to recover (optional)
+        :type  path: ``str``
+
+        :rtype: Instance of :class:`BackupTargetJob`
+        """
+        raise NotImplementedError(
+            'recover_target_out_of_place not implemented for this driver')
+
+    def get_target_job(self, target, id):
+        """
+        Get a specific backup job by ID
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param id: Backup target with the backup data
+        :type  id: Instance of :class:`BackupTarget`
+
+        :rtype: :class:`BackupTargetJob`
+        """
+        jobs = self.list_target_jobs(target)
+        return list(filter(lambda x: x.id == id, jobs))[0]
+
+    def list_target_jobs(self, target):
+        """
+        List the backup jobs on a target
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :rtype: ``list`` of :class:`BackupTargetJob`
+        """
+        return []
+
+    def create_target_job(self, target, extra=None):
+        """
+        Create a new backup job on a target
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type extra: ``dict``
+
+        :rtype: Instance of :class:`BackupTargetJob`
+        """
+        name = target.name
+        request = '/zones/%s/disks/%s/createSnapshot' % (
+            target.extra['zone'].name, target.name)
+        snapshot_data = {
+            'source': target.extra['source']
+        }
+        self.connection.async_request(request, method='POST',
+                                      data=snapshot_data)
+        return self._to_job(self.ex_get_snapshot(name), target)
+
+    def resume_target_job(self, target, job):
+        """
+        Resume a suspended backup job on a target
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param job: Backup target job to resume
+        :type  job: Instance of :class:`BackupTargetJob`
+
+        :rtype: ``bool``
+        """
+        raise NotImplementedError(
+            'resume_target_job not supported for this driver')
+
+    def suspend_target_job(self, target, job):
+        """
+        Suspend a running backup job on a target
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param job: Backup target job to suspend
+        :type  job: Instance of :class:`BackupTargetJob`
+
+        :rtype: ``bool``
+        """
+        raise NotImplementedError(
+            'suspend_target_job not supported for this driver')
+
+    def cancel_target_job(self, target, job):
+        """
+        Cancel a backup job on a target
+
+        :param target: Backup target with the backup data
+        :type  target: Instance of :class:`BackupTarget`
+
+        :param job: Backup target job to cancel
+        :type  job: Instance of :class:`BackupTargetJob`
+
+        :rtype: ``bool``
+        """
+        raise NotImplementedError(
+            'cancel_target_job not supported for this driver')
+
+    def _to_recovery_points(self, data, target):
+        return [self._to_recovery_point(item, target)
+                for item in data.items]
+
+    def _to_recovery_point(self, item, target):
+        id = item.id
+        date = parse_date(item.creationTimestamp)
+        point = BackupTargetRecoveryPoint(
+            id=id,
+            date=date,
+            target=target,
+            driver=self.connection.driver,
+            extra={
+                'snapshot-id': id,
+            },
+        )
+        return point
+
+    def _to_jobs(self, data, target):
+        return [self._to_job(item, target)
+                for item in data.items]
+
+    def _to_job(self, item, target):
+        id = item.id
+        job = BackupTargetJob(
+            id=id,
+            status=BackupTargetJobStatusType.PENDING,
+            progress=0,
+            target=target,
+            driver=self.connection.driver,
+            extra={
+            },
+        )
+        return job
+
+    def ex_get_snapshot(self, name):
+        request = '/global/snapshots/%s' % (name)
+        response = self.connection.request(request, method='GET').object
+        return response
+
+    def ex_get_target_by_source(self, source):
+        return BackupTarget(
+            id=source,
+            name=source,
+            address=source,
+            type=BackupTargetType.VOLUME,
+            driver=self.connection.driver,
+            extra={
+                "source": source
+            }
+        )

http://git-wip-us.apache.org/repos/asf/libcloud/blob/e9ebc5fb/libcloud/backup/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/backup/providers.py b/libcloud/backup/providers.py
index 21bafda..a38187f 100644
--- a/libcloud/backup/providers.py
+++ b/libcloud/backup/providers.py
@@ -20,6 +20,10 @@ from libcloud.backup.types import Provider
 DRIVERS = {
     Provider.DUMMY:
     ('libcloud.backup.drivers.dummy', 'DummyBackupDriver'),
+    Provider.EBS:
+    ('libcloud.backup.drivers.ebs', 'EBSBackupDriver'),
+    Provider.GCE:
+    ('libcloud.backup.drivers.gce', 'GCEBackupDriver'),
 }
 
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/e9ebc5fb/libcloud/backup/types.py
----------------------------------------------------------------------
diff --git a/libcloud/backup/types.py b/libcloud/backup/types.py
index c145b5c..6ffa6c8 100644
--- a/libcloud/backup/types.py
+++ b/libcloud/backup/types.py
@@ -22,6 +22,8 @@ __all__ = [
 
 class Provider(object):
     DUMMY = 'dummy'
+    EBS = 'ebs'
+    GCE = 'gce'
 
 
 class BackupTargetType(object):

Reply via email to