libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anthonys...@apache.org
Subject [1/3] libcloud git commit: Added Import Snapshot and Describe Import Snapshot to EC2 compute driver
Date Mon, 10 Apr 2017 22:16:41 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk 9f4d0a263 -> a5731abb0


Added Import Snapshot and Describe Import Snapshot to EC2 compute driver


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

Branch: refs/heads/trunk
Commit: 0e73f7758caa6ca11b979c286c314e8b7c91f821
Parents: c62d7c9
Author: Nirzari Iyer <niyer@localhost.localdomain>
Authored: Tue Feb 14 14:36:41 2017 -0500
Committer: nirzari <niyer@redhat.com>
Committed: Sun Apr 9 22:46:39 2017 -0400

----------------------------------------------------------------------
 libcloud/compute/drivers/ec2.py                 | 228 +++++++++++++++++++
 .../ec2/describe_import_snapshot_tasks.xml      |  18 ++
 .../describe_import_snapshot_tasks_active.xml   |  17 ++
 .../compute/fixtures/ec2/import_snapshot.xml    |  16 ++
 libcloud/test/compute/test_ec2.py               |  41 ++++
 5 files changed, 320 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e73f775/libcloud/compute/drivers/ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py
index 466b802..461248c 100644
--- a/libcloud/compute/drivers/ec2.py
+++ b/libcloud/compute/drivers/ec2.py
@@ -22,6 +22,7 @@ import sys
 import base64
 import copy
 import warnings
+import time
 
 try:
     from lxml import etree as ET
@@ -67,6 +68,7 @@ __all__ = [
     'EC2NodeLocation',
     'EC2ReservedNode',
     'EC2SecurityGroup',
+    'EC2ImportSnapshotTask',
     'EC2PlacementGroup',
     'EC2Network',
     'EC2NetworkSubnet',
@@ -2872,6 +2874,22 @@ class EC2SecurityGroup(object):
                 % (self.id, self.name))
 
 
+class EC2ImportSnapshotTask(object):
+    """
+    Represents information about a describe_import_snapshot_task.
+
+    Note: This class is EC2 specific.
+    """
+
+    def __init__(self, status, snapshotId):
+        self.status = status
+        self.snapshotId = snapshotId
+
+    def __repr__(self):
+        return (('<EC2SecurityGroup: status=%s, snapshotId=%s')
+                % (self.status, self.snapshotId))
+
+
 class EC2PlacementGroup(object):
     """
     Represents information about a Placement Grous
@@ -3906,6 +3924,138 @@ class BaseEC2NodeDriver(NodeDriver):
         response = self.connection.request(self.path, params=params).object
         return self._get_boolean(response)
 
+    def ex_import_snapshot(self, client_data=None,
+                           client_token=None, description=None,
+                           disk_container=None, dry_run=None, role_name=None):
+        """
+        Imports a disk into an EBS snapshot. More information can be found
+        at https://goo.gl/sbXkYA.
+
+        :param  client_data: Describes the client specific data (optional)
+        :type   client_data: ``dict``
+
+        :param  client_token: The token to enable idempotency for VM
+                import requests.(optional)
+        :type   client_token: ``str``
+
+        :param  description: The description string for the
+                             import snapshot task.(optional)
+        :type   description: ``str``
+
+        :param  disk_container:The disk container object for the
+                              import snapshot request.
+        :type   disk_container:``dict``
+
+        :param  dry_run: Checks whether you have the permission for
+                        the action, without actually making the request,
+                        and provides an error response.(optional)
+        :type   dry_run: ``bool``
+
+        :param  role_name: The name of the role to use when not using the
+                          default role, 'vmimport'.(optional)
+        :type   role_name: ``str``
+
+        :rtype: :class: ``VolumeSnapshot``
+        """
+
+        params = {'Action': 'ImportSnapshot'}
+
+        if client_data is not None:
+            params.update(self._get_client_date_params(client_data))
+
+        if client_token is not None:
+            params['ClientToken'] = client_token
+
+        if description is not None:
+            params['Description'] = description
+
+        if disk_container is not None:
+            params.update(self._get_disk_container_params(disk_container))
+
+        if dry_run is not None:
+            params['DryRun'] = dry_run
+
+        if role_name is not None:
+            params['RoleName'] = role_name
+
+        importSnapshot = self.connection.request(self.path,
+                                                 params=params).object
+
+        importTaskId = findtext(element=importSnapshot,
+                                xpath='importTaskId',
+                                namespace=NAMESPACE)
+
+        volumeSnapshot = self._wait_for_import_snapshot_completion(
+            import_task_id=importTaskId, timeout=1800, interval=15)
+
+        return volumeSnapshot
+
+    def _wait_for_import_snapshot_completion(self,
+                                             import_task_id,
+                                             timeout=1800,
+                                             interval=15):
+        """
+        It waits for import snapshot to be completed
+
+        :param import_task_id: Import task Id for the
+                               current Import Snapshot Task
+        :type import_task_id: ``str``
+
+        :param timeout: Timeout value for snapshot generation
+        :type timeout: ``float``
+
+        :param interval: Time interval for repetative describe
+                         import snapshot tasks requests
+        :type interval: ``float``
+
+        :rtype: :class:``VolumeSnapshot``
+        """
+        start_time = time.time()
+        snapshotId = None
+        while snapshotId is None:
+            if (time.time() - start_time >= timeout):
+                raise Exception('Timeout while waiting '
+                                'for import task Id %s'
+                                % import_task_id)
+            res = self.ex_describe_import_snapshot_tasks(import_task_id)
+            snapshotId = res.snapshotId
+
+            if snapshotId is None:
+                time.sleep(interval)
+
+        volumeSnapshot = VolumeSnapshot(snapshotId, driver=self)
+        return volumeSnapshot
+
+    def ex_describe_import_snapshot_tasks(self, import_task_id, dry_run=None):
+        """
+        Describes your import snapshot tasks. More information can be found
+        at https://goo.gl/CI0MdS.
+
+        :param import_task_id: Import task Id for the current
+                               Import Snapshot Task
+        :type import_task_id: ``str``
+
+        :param  dry_run: Checks whether you have the permission for
+                        the action, without actually making the request,
+                        and provides an error response.(optional)
+        :type   dry_run: ``bool``
+
+        :rtype: :class:``DescribeImportSnapshotTasks Object``
+
+        """
+        params = {'Action': 'DescribeImportSnapshotTasks'}
+
+        if dry_run is not None:
+            params['DryRun'] = dry_run
+
+        # This can be extended for multiple import snapshot tasks
+        params['ImportTaskId.1'] = import_task_id
+
+        res = self._to_import_snapshot_task(
+            self.connection.request(self.path, params=params).object
+        )
+        return res
+
     def ex_list_placement_groups(self, names=None):
         """
         A list of placement groups.
@@ -6040,6 +6190,19 @@ class BaseEC2NodeDriver(NodeDriver):
                               state=state,
                               name=name)
 
+    def _to_import_snapshot_task(self, element):
+        status = findtext(element=element, xpath='importSnapshotTaskSet/item/'
+                          'snapshotTaskDetail/status', namespace=NAMESPACE)
+
+        if status != 'completed':
+            snapshotId = None
+        else:
+            xpath = 'importSnapshotTaskSet/item/snapshotTaskDetail/snapshotId'
+            snapshotId = findtext(element=element, xpath=xpath,
+                                  namespace=NAMESPACE)
+
+        return EC2ImportSnapshotTask(status, snapshotId=snapshotId)
+
     def _to_key_pairs(self, elems):
         key_pairs = [self._to_key_pair(elem=elem) for elem in elems]
         return key_pairs
@@ -6696,6 +6859,71 @@ class BaseEC2NodeDriver(NodeDriver):
                                % (idx, k, key)] = str(value)
         return params
 
+    def _get_disk_container_params(self, disk_container):
+        """
+        Return a list of dictionaries with query parameters for
+        a valid disk container.
+
+        :param      disk_container: List of dictionaries with
+                                    disk_container details
+        :type       disk_container: ``list`` or ``dict``
+
+        :return:    Dictionary representation of the disk_container
+        :rtype:     ``dict``
+        """
+
+        if not isinstance(disk_container, (list, tuple)):
+            raise AttributeError('disk_container not list or tuple')
+
+        params = {}
+
+        for idx, content in enumerate(disk_container):
+            idx += 1  # We want 1-based indexes
+            if not isinstance(content, dict):
+                raise AttributeError(
+                    'content %s in disk_container not a dict' % content)
+
+            for k, v in content.items():
+                if not isinstance(v, dict):
+                    params['DiskContainer.%s' % (k)] = str(v)
+
+                else:
+                    for key, value in v.items():
+                        params['DiskContainer.%s.%s'
+                               % (k, key)] = str(value)
+
+        return params
+
+    def _get_client_data_params(self, client_data):
+        """
+        Return a dictionary with query parameters for
+        a valid client data.
+
+        :param      client_data: List of dictionaries with the disk
+                                 upload details
+        :type       client_data: ``dict``
+
+        :return:    Dictionary representation of the client data
+        :rtype:     ``dict``
+        """
+
+        if not isinstance(client_data, (list, tuple)):
+            raise AttributeError('client_data not list or tuple')
+
+        params = {}
+
+        for idx, content in enumerate(client_data):
+            idx += 1  # We want 1-based indexes
+            if not isinstance(content, dict):
+                raise AttributeError(
+                    'content %s in client_data'
+                    'not a dict' % content)
+
+            for k, v in content.items():
+                params['ClientData.%s' % (k)] = str(v)
+
+        return params
+
     def _get_common_security_group_params(self, group_id, protocol,
                                           from_port, to_port, cidr_ips,
                                           group_pairs):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e73f775/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks.xml b/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks.xml
new file mode 100644
index 0000000..86ae679
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks.xml
@@ -0,0 +1,18 @@
+<DescribeImportSnapshotTasksResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+    <requestId>a8c44ac4-8e63-46e9-b8b2-8c72ce3d9b18</requestId>
+    <importSnapshotTaskSet>
+        <item>
+            <importTaskId>import-snap-fh7y6i6w</importTaskId>
+            <snapshotTaskDetail>
+                <snapshotId>snap-0ea83e8a87e138f39</snapshotId>
+                <format>RAW</format>
+                <diskImageSize>1.073741824E10</diskImageSize>
+                <userBucket>
+                    <s3Bucket>dummy-bucket</s3Bucket>
+                    <s3Key>dummy-key</s3Key>
+                </userBucket>
+                <status>completed</status>
+            </snapshotTaskDetail>
+        </item>
+    </importSnapshotTaskSet>
+</DescribeImportSnapshotTasksResponse>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e73f775/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks_active.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks_active.xml
b/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks_active.xml
new file mode 100644
index 0000000..edb00cd
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/describe_import_snapshot_tasks_active.xml
@@ -0,0 +1,17 @@
+<DescribeImportSnapshotTasksResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+    <requestId>a8c44ac4-8e63-46e9-b8b2-8c72ce3d9b18</requestId>
+    <importSnapshotTaskSet>
+        <item>
+            <importTaskId>import-snap-fh7y6i6w</importTaskId>
+            <snapshotTaskDetail>
+                <format>RAW</format>
+                <diskImageSize>1.073741824E10</diskImageSize>
+                <userBucket>
+                    <s3Bucket>dummy-bucket</s3Bucket>
+                    <s3Key>dummy-key</s3Key>
+                </userBucket>
+                <status>active</status>
+            </snapshotTaskDetail>
+        </item>
+    </importSnapshotTaskSet>
+</DescribeImportSnapshotTasksResponse>

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e73f775/libcloud/test/compute/fixtures/ec2/import_snapshot.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ec2/import_snapshot.xml b/libcloud/test/compute/fixtures/ec2/import_snapshot.xml
new file mode 100644
index 0000000..e2d51de
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ec2/import_snapshot.xml
@@ -0,0 +1,16 @@
+<ImportSnapshotResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
+    <requestId>0d490bf6-19cf-4456-9c71-31d097faf46d</requestId>
+    <importTaskId>import-snap-fgsddbhv</importTaskId>
+        <snapshotTaskDetail>
+        <format>RAW</format>
+        <progress>3</progress>
+        <diskImageSize>0.0</diskImageSize>
+        <statusMessage>pending</statusMessage>
+        <userBucket>
+            <s3Bucket>dummy-bucket</s3Bucket>
+            <s3Key>dummy-key</s3Key>
+        </userBucket>
+        <status>active</status>
+    </snapshotTaskDetail>
+</ImportSnapshotResponse>
+

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0e73f775/libcloud/test/compute/test_ec2.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py
index 7702d82..6b942e3 100644
--- a/libcloud/test/compute/test_ec2.py
+++ b/libcloud/test/compute/test_ec2.py
@@ -562,6 +562,35 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin):
                                               ena_support=True)
         self.assertEqual(image.id, 'ami-57c2fb3e')
 
+    def test_ex_import_snapshot(self):
+        disk_container = [{'Description': 'Dummy import snapshot task',
+                           'Format': 'raw',
+                           'UserBucket': {'S3Bucket': 'dummy-bucket', 'S3Key': 'dummy-key'}}]
+
+        snap = self.driver.ex_import_snapshot(disk_container=disk_container)
+        self.assertEqual(snap.id, 'snap-0ea83e8a87e138f39')
+
+    def test_wait_for_import_snapshot_completion(self):
+        snap = self.driver._wait_for_import_snapshot_completion(
+            import_task_id='import-snap-fhdysyq6')
+        self.assertEqual(snap.id, 'snap-0ea83e8a87e138f39')
+
+    def test_timeout_wait_for_import_snapshot_completion(self):
+        import_task_id = 'import-snap-fhdysyq6'
+        EC2MockHttp.type = 'timeout'
+        with self.assertRaises(Exception) as context:
+            self.driver._wait_for_import_snapshot_completion(
+                import_task_id=import_task_id, timeout=0.01, interval=0.001)
+        self.assertEqual('Timeout while waiting for import task Id %s'
+                         % import_task_id, str(context.exception))
+
+    def test_ex_describe_import_snapshot_tasks(self):
+        snap = self.driver.ex_describe_import_snapshot_tasks(
+            import_task_id='import-snap-fh7y6i6w<')
+
+        self.assertEqual(snap.snapshotId, 'snap-0ea83e8a87e138f39')
+        self.assertEqual(snap.status, 'completed')
+
     def test_ex_list_availability_zones(self):
         availability_zones = self.driver.ex_list_availability_zones()
         availability_zone = availability_zones[0]
@@ -1264,6 +1293,18 @@ class EC2MockHttp(MockHttpTestCase):
         body = self.fixtures.load('register_image.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _ImportSnapshot(self, method, url, body, headers):
+        body = self.fixtures.load('import_snapshot.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _DescribeImportSnapshotTasks(self, method, url, body, headers):
+        body = self.fixtures.load('describe_import_snapshot_tasks.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _timeout_DescribeImportSnapshotTasks(self, method, url, body, headers):
+        body = self.fixtures.load('describe_import_snapshot_tasks_active.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _ex_imageids_DescribeImages(self, method, url, body, headers):
         body = self.fixtures.load('describe_images_ex_imageids.xml')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])


Mime
View raw message