Updated Branches: refs/heads/trunk edaff3f8d -> c3c90ac53
Issue LIBCLOUD-494: Add extension methods to support additional EC2 image calls. The first is ex_copy_image which is used to copy Amazon Machine Images between regions and the second is ex_create_image which can be used to create an AMI from an EBS backed instance. Closes #222. Signed-off-by: Tomaz Muraus <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/b81a63a8 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/b81a63a8 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/b81a63a8 Branch: refs/heads/trunk Commit: b81a63a88f11bd0fe1105be3868c915c69faba45 Parents: edaff3f Author: Chris DeRamus <[email protected]> Authored: Sun Jan 12 14:55:16 2014 -0500 Committer: Tomaz Muraus <[email protected]> Committed: Sun Jan 12 23:53:23 2014 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/ec2.py | 132 +++++++++++++++++-- .../test/compute/fixtures/ec2/copy_image.xml | 4 + .../test/compute/fixtures/ec2/create_image.xml | 4 + libcloud/test/compute/test_ec2.py | 30 +++++ 4 files changed, 158 insertions(+), 12 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/b81a63a8/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index a226f88..176d450 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -1339,18 +1339,8 @@ class BaseEC2NodeDriver(NodeDriver): params['ClientToken'] = kwargs['ex_clienttoken'] if 'ex_blockdevicemappings' in kwargs: - if not isinstance(kwargs['ex_blockdevicemappings'], (list, tuple)): - raise AttributeError( - 'ex_blockdevicemappings not list or tuple') - - for idx, mapping in enumerate(kwargs['ex_blockdevicemappings']): - idx += 1 # we want 1-based indexes - if not isinstance(mapping, dict): - raise AttributeError( - 'mapping %s in ex_blockdevicemappings ' - 'not a dict' % mapping) - for k, v in mapping.items(): - params['BlockDeviceMapping.%d.%s' % (idx, k)] = str(v) + params.update(self._get_block_device_mapping_params( + kwargs['ex_blockdevicemappings'])) if 'ex_iamprofile' in kwargs: if not isinstance(kwargs['ex_iamprofile'], basestring): @@ -1566,6 +1556,91 @@ class BaseEC2NodeDriver(NodeDriver): namespace=NAMESPACE) return element == 'true' + def ex_copy_image(self, source_region, image, name=None, description=None): + """ + Copy an Amazon Machine Image from the specified source region + to the current region. + + :param source_region: The region where the image resides + :type source_region: ``str`` + + :param image: Instance of class NodeImage + :type image: :class:`NodeImage` + + :param name: The name of the new image + :type name: ``str`` + + :param description: The description of the new image + :type description: ``str`` + + :return: Instance of class ``NodeImage`` + :rtype: :class:`NodeImage` + """ + params = {'Action': 'CopyImage', + 'SourceRegion': source_region, + 'SourceImageId': image.id} + + if name is not None: + params['Name'] = name + + if description is not None: + params['Description'] = description + + image = self._to_image( + self.connection.request(self.path, params=params).object) + + return image + + def ex_create_image_from_node(self, node, name, block_device_mapping, + reboot=False, description=None): + """ + Create an Amazon Machine Image based off of an EBS-backed instance. + + :param node: Instance of ``Node`` + :type node: :class: `Node` + + :param name: The name for the new image + :type name: ``str`` + + :param block_device_mapping: A dictionary of the disk layout + An example of this dict is included + below. + :type block_device_mapping: ``list`` of ``dict`` + + :param reboot: Whether or not to shutdown the instance before + creation. By default Amazon sets this to false + to ensure a clean image. + :type reboot: ``bool`` + + :param description: An optional description for the new image + :type description: ``str`` + + @note An example block device mapping dictionary is included: + mapping = [{'VirtualName': None, + 'Ebs': {'VolumeSize': 10, + 'VolumeType': 'standard', + 'DeleteOnTermination': 'true'}, + 'DeviceName': '/dev/sda1'}] + + :return: Instance of class ``NodeImage`` + :rtype: :class:`NodeImage` + """ + params = {'Action': 'CreateImage', + 'InstanceId': node.id, + 'Name': name, + 'Reboot': reboot} + + if description is not None: + params['Description'] = description + + params.update(self._get_block_device_mapping_params( + block_device_mapping)) + + image = self._to_image( + self.connection.request(self.path, params=params).object) + + return image + def ex_destroy_image(self, image): params = { 'Action': 'DeregisterImage', @@ -3447,6 +3522,39 @@ class BaseEC2NodeDriver(NodeDriver): return tags + def _get_block_device_mapping_params(self, block_device_mapping): + """ + Return a list of dictionaries with query parameters for + a valid block device mapping. + + :param mapping: List of dictionaries with the drive layout + :type mapping: ``list`` or ``dict`` + + :return: Dictionary representation of the drive mapping + :rtype: ``dict`` + """ + + if not isinstance(block_device_mapping, (list, tuple)): + raise AttributeError( + 'block_device_mapping not list or tuple') + + params = {} + + for idx, mapping in enumerate(block_device_mapping): + idx += 1 # We want 1-based indexes + if not isinstance(mapping, dict): + raise AttributeError( + 'mapping %s in block_device_mapping ' + 'not a dict' % mapping) + for k, v in mapping.items(): + if not isinstance(v, dict): + params['BlockDeviceMapping.%d.%s' % (idx, k)] = str(v) + else: + for key, value in v.items(): + params['BlockDeviceMapping.%d.%s.%s' + % (idx, k, key)] = str(value) + 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/b81a63a8/libcloud/test/compute/fixtures/ec2/copy_image.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/copy_image.xml b/libcloud/test/compute/fixtures/ec2/copy_image.xml new file mode 100644 index 0000000..f2e76b0 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/copy_image.xml @@ -0,0 +1,4 @@ +<CopyImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>7b7d87d5-c045-4c2c-a2c4-b538debe14b2</requestId> + <imageId>ami-4db38224</imageId> +</CopyImageResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/b81a63a8/libcloud/test/compute/fixtures/ec2/create_image.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/create_image.xml b/libcloud/test/compute/fixtures/ec2/create_image.xml new file mode 100644 index 0000000..1213f0a --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/create_image.xml @@ -0,0 +1,4 @@ +<CreateImageResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>3629ec66-c1f8-4b66-aac5-a8ad1cdf6c15</requestId> + <imageId>ami-e9b38280</imageId> +</CreateImageResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/b81a63a8/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index d86af68..e0af93d 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -442,6 +442,28 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): self.assertEqual(len(images), 2) + def test_ex_copy_image(self): + image = self.driver.list_images()[0] + resp = self.driver.ex_copy_image('us-east-1', image, + name='Faux Image', + description='Test Image Copy') + self.assertEqual(resp.id, 'ami-4db38224') + + def test_ex_create_image_from_node(self): + node = self.driver.list_nodes()[0] + + mapping = [{'VirtualName': None, + 'Ebs': {'VolumeSize': 10, + 'VolumeType': 'standard', + 'DeleteOnTermination': 'true'}, + 'DeviceName': '/dev/sda1'}] + + resp = self.driver.ex_create_image_from_node(node, + 'New Image', + mapping, + description='New EBS Image') + self.assertEqual(resp.id, 'ami-e9b38280') + def ex_destroy_image(self): images = self.driver.list_images() image = images[0] @@ -1222,6 +1244,14 @@ class EC2MockHttp(MockHttpTestCase): body = self.fixtures.load('delete_snapshot.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _CopyImage(self, method, url, body, headers): + body = self.fixtures.load('copy_image.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _CreateImage(self, method, url, body, headers): + body = self.fixtures.load('create_image.xml') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _DeregisterImage(self, method, url, body, headers): body = self.fixtures.load('deregister_image.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK])
