Skip to content

Commit

Permalink
Issue LIBCLOUD-494: Add extension methods to support additional EC2 i…
Browse files Browse the repository at this point in the history
…mage

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 <tomaz@apache.org>
  • Loading branch information
Chris DeRamus authored and Kami committed Jan 12, 2014
1 parent edaff3f commit b81a63a
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 12 deletions.
132 changes: 120 additions & 12 deletions libcloud/compute/drivers/ec2.py
Expand Up @@ -1339,18 +1339,8 @@ def create_node(self, **kwargs):
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):
Expand Down Expand Up @@ -1566,6 +1556,91 @@ def delete_key_pair(self, key_pair):
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',
Expand Down Expand Up @@ -3447,6 +3522,39 @@ def _get_resource_tags(self, element):

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):
Expand Down
4 changes: 4 additions & 0 deletions 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>
4 changes: 4 additions & 0 deletions 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>
30 changes: 30 additions & 0 deletions libcloud/test/compute/test_ec2.py
Expand Up @@ -442,6 +442,28 @@ def test_list_images_with_executable_by(self):

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]
Expand Down Expand Up @@ -1222,6 +1244,14 @@ def _DeleteSnapshot(self, method, url, body, headers):
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])
Expand Down

0 comments on commit b81a63a

Please sign in to comment.