Updated Branches: refs/heads/trunk 58c1b6fc4 -> 369a16433
Issue LIBCLOUD-481: Store additional attributes (iops, tags, block_device_mapping) in the "extra" dictionary of the NodeImage object in the EC2 driver. Also fix ex_image_ids filtering in the list_images method. 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/c9e933b7 Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/c9e933b7 Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/c9e933b7 Branch: refs/heads/trunk Commit: c9e933b7b32fc54bacece0c2ebee3913d591249e Parents: 58c1b6f Author: Chris DeRamus <[email protected]> Authored: Sun Dec 29 09:37:34 2013 -0500 Committer: Tomaz Muraus <[email protected]> Committed: Mon Dec 30 00:11:53 2013 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/ec2.py | 191 +++++++++++++++---- .../compute/fixtures/ec2/describe_images.xml | 76 ++++++-- .../ec2/describe_images_ex_imageids.xml | 36 ++++ libcloud/test/compute/test_ec2.py | 37 +++- 4 files changed, 278 insertions(+), 62 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/c9e933b7/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 2011826..533a05c 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -819,49 +819,145 @@ class BaseEC2NodeDriver(NodeDriver): ) return n + def _to_device_mappings(self, object): + return [self._to_device_mapping(el) for el in object.findall( + fixxpath(xpath='blockDeviceMapping/item', namespace=NAMESPACE)) + ] + + def _to_device_mapping(self, element): + """ + Parse the XML element and return a dictionary of device properties. + Additional information can be found at http://goo.gl/GjWYBf. + + @note: EBS volumes do not have a virtual name. Only ephemeral + disks use this property. + :rtype: ``dict`` + """ + mapping = {} + + mapping['device_name'] = findattr(element=element, + xpath='deviceName', + namespace=NAMESPACE) + + mapping['virtual_name'] = findattr(element=element, + xpath='virtualName', + namespace=NAMESPACE) + + # If virtual name does not exist then this is an EBS volume. + # Build the EBS dictionary leveraging the _get_extra_dict method. + if mapping['virtual_name'] is None: + # Build our attributes map + attributes_map = { + 'snapshot_id': { + 'xpath': 'ebs/snapshotId', + 'transform_func': str + }, + 'volume_size': { + 'xpath': 'ebs/volumeSize', + 'transform_func': int + }, + 'delete': { + 'xpath': 'ebs/deleteOnTermination', + 'transform_func': str + }, + 'volume_type': { + 'xpath': 'ebs/volumeType', + 'transform_func': str + }, + 'iops': { + 'xpath': 'ebs/iops', + 'transform_func': int + } + } + + mapping['ebs'] = self._get_extra_dict(element, attributes_map) + + return mapping + def _to_images(self, object): return [self._to_image(el) for el in object.findall( fixxpath(xpath='imagesSet/item', namespace=NAMESPACE)) ] def _to_image(self, element): - n = NodeImage( - id=findtext(element=element, xpath='imageId', namespace=NAMESPACE), - name=findtext(element=element, xpath='imageLocation', - namespace=NAMESPACE), - driver=self.connection.driver, - extra={ - 'state': findattr(element=element, xpath="imageState", - namespace=NAMESPACE), - 'ownerid': findattr(element=element, xpath="imageOwnerId", - namespace=NAMESPACE), - 'owneralias': findattr(element=element, - xpath="imageOwnerAlias", - namespace=NAMESPACE), - 'ispublic': findattr(element=element, - xpath="isPublic", - namespace=NAMESPACE), - 'architecture': findattr(element=element, - xpath="architecture", - namespace=NAMESPACE), - 'imagetype': findattr(element=element, - xpath="imageType", - namespace=NAMESPACE), - 'platform': findattr(element=element, - xpath="platform", - namespace=NAMESPACE), - 'rootdevicetype': findattr(element=element, - xpath="rootDeviceType", - namespace=NAMESPACE), - 'virtualizationtype': findattr( - element=element, xpath="virtualizationType", - namespace=NAMESPACE), - 'hypervisor': findattr(element=element, - xpath="hypervisor", - namespace=NAMESPACE) + + id = findtext(element=element, xpath='imageId', namespace=NAMESPACE) + name = findtext(element=element, xpath='name', namespace=NAMESPACE) + + # Build block device mapping + block_device_mapping = self._to_device_mappings(element) + + # Get our tags + tags = self._get_resource_tags(element) + + # Build our extra attributes map + extra_attributes_map = { + 'state': { + 'xpath': 'imageState', + 'transform_func': str + }, + 'owner_id': { + 'xpath': 'imageOwnerId', + 'transform_func': str + }, + 'owner_alias': { + 'xpath': 'imageOwnerAlias', + 'transform_func': str + }, + 'is_public': { + 'xpath': 'isPublic', + 'transform_func': str + }, + 'architecture': { + 'xpath': 'architecture', + 'transform_func': str + }, + 'image_type': { + 'xpath': 'imageType', + 'transform_func': str + }, + 'image_location': { + 'xpath': 'imageLocation', + 'transform_func': str + }, + 'platform': { + 'xpath': 'platform', + 'transform_func': str + }, + 'description': { + 'xpath': 'description', + 'transform_func': str + }, + 'root_device_type': { + 'xpath': 'rootDeviceType', + 'transform_func': str + }, + 'virtualization_type': { + 'xpath': 'virtualizationType', + 'transform_func': str + }, + 'hypervisor': { + 'xpath': 'hypervisor', + 'transform_func': str + }, + 'kernel_id': { + 'xpath': 'kernelId', + 'transform_func': str + }, + 'ramdisk_id': { + 'xpath': 'ramdisk_id', + 'transform_func': str } - ) - return n + } + + # Get our extra dictionary + extra = self._get_extra_dict(element, extra_attributes_map) + + # Add our tags and block device mapping + extra['tags'] = tags + extra['block_device_mapping'] = block_device_mapping + + return NodeImage(id=id, name=name, driver=self, extra=extra) def _to_volume(self, element, name=None): """ @@ -1146,7 +1242,8 @@ class BaseEC2NodeDriver(NodeDriver): sizes.append(NodeSize(driver=self, **attributes)) return sizes - def list_images(self, location=None, ex_image_ids=None, ex_owner=None): + def list_images(self, location=None, ex_image_ids=None, ex_owner=None, + ex_executableby=None): """ List all images @@ -1159,11 +1256,22 @@ class BaseEC2NodeDriver(NodeDriver): with the corresponding owner will be returned. Valid values: amazon|aws-marketplace|self|all|aws id + Ex_executableby parameter describes images for which + the specified user has explicit launch permissions. + The user can be an AWS account ID, self to return + images for which the sender of the request has + explicit launch permissions, or all to return + images with public launch permissions. + Valid values: all|self|aws id + :param ex_image_ids: List of ``NodeImage.id`` :type ex_image_ids: ``list`` of ``str`` :param ex_owner: Owner name - :type ex_image_ids: ``str`` + :type ex_owner: ``str`` + + :param ex_executableby: Executable by + :type ex_executableby: ``str`` :rtype: ``list`` of :class:`NodeImage` """ @@ -1172,8 +1280,13 @@ class BaseEC2NodeDriver(NodeDriver): if ex_owner: params.update({'Owner.1': ex_owner}) + if ex_executableby: + params.update({'ExecutableBy.1': ex_executableby}) + if ex_image_ids: - params.update(self._pathlist('ImageId', ex_image_ids)) + for index, image_id in enumerate(ex_image_ids): + index += 1 + params.update({'ImageId.%s' % (index): image_id}) images = self._to_images( self.connection.request(self.path, params=params).object http://git-wip-us.apache.org/repos/asf/libcloud/blob/c9e933b7/libcloud/test/compute/fixtures/ec2/describe_images.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/describe_images.xml b/libcloud/test/compute/fixtures/ec2/describe_images.xml index e99cbe7..03594e9 100644 --- a/libcloud/test/compute/fixtures/ec2/describe_images.xml +++ b/libcloud/test/compute/fixtures/ec2/describe_images.xml @@ -1,16 +1,62 @@ <DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> - <imagesSet> - <item> - <imageId>ami-be3adfd7</imageId> - <imageLocation>ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml</imageLocation> - <imageState>available</imageState> - <imageOwnerId>206029621532</imageOwnerId> - <isPublic>false</isPublic> - <architecture>i386</architecture> - <imageType>machine</imageType> - <kernelId>aki-4438dd2d</kernelId> - <ramdiskId>ari-4538dd2c</ramdiskId> - </item> - </imagesSet> -</DescribeImagesResponse> - + <requestId>73fac9c5-f6d2-4b45-846f-47adf1e82d6c</requestId> + <imagesSet> + <item> + <imageId>ami-57ba933a</imageId> + <imageLocation>123456788908/Test Image</imageLocation> + <imageState>available</imageState> + <imageOwnerId>123456788908</imageOwnerId> + <isPublic>false</isPublic> + <architecture>x86_64</architecture> + <imageType>machine</imageType> + <kernelId>aki-88aa75e1</kernelId> + <name>Test Image</name> + <description>Testing Stuff</description> + <rootDeviceType>ebs</rootDeviceType> + <rootDeviceName>/dev/sda1</rootDeviceName> + <blockDeviceMapping> + <item> + <deviceName>/dev/sda1</deviceName> + <ebs> + <snapshotId>snap-88123ed9</snapshotId> + <volumeSize>10</volumeSize> + <deleteOnTermination>true</deleteOnTermination> + <volumeType>standard</volumeType> + </ebs> + </item> + <item> + <deviceName>/dev/sda2</deviceName> + <virtualName>ephemeral0</virtualName> + </item> + </blockDeviceMapping> + <virtualizationType>paravirtual</virtualizationType> + <hypervisor>xen</hypervisor> + </item> + <item> + <imageId>ami-85b2a8ae</imageId> + <imageLocation>123456788908/Test Image 2</imageLocation> + <imageState>available</imageState> + <imageOwnerId>123456788908</imageOwnerId> + <isPublic>false</isPublic> + <architecture>x86_64</architecture> + <imageType>machine</imageType> + <kernelId>aki-88aa75e1</kernelId> + <name>Test Image 2</name> + <rootDeviceType>ebs</rootDeviceType> + <rootDeviceName>/dev/sda1</rootDeviceName> + <blockDeviceMapping> + <item> + <deviceName>/dev/sda1</deviceName> + <ebs> + <snapshotId>snap-c0bfbbdb</snapshotId> + <volumeSize>20</volumeSize> + <deleteOnTermination>false</deleteOnTermination> + <volumeType>standard</volumeType> + </ebs> + </item> + </blockDeviceMapping> + <virtualizationType>paravirtual</virtualizationType> + <hypervisor>xen</hypervisor> + </item> + </imagesSet> +</DescribeImagesResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/c9e933b7/libcloud/test/compute/fixtures/ec2/describe_images_ex_imageids.xml ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/ec2/describe_images_ex_imageids.xml b/libcloud/test/compute/fixtures/ec2/describe_images_ex_imageids.xml new file mode 100644 index 0000000..47f2be0 --- /dev/null +++ b/libcloud/test/compute/fixtures/ec2/describe_images_ex_imageids.xml @@ -0,0 +1,36 @@ +<DescribeImagesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/"> + <requestId>73fac9c5-f6d2-4b45-846f-47adf1e82d6c</requestId> + <imagesSet> + <item> + <imageId>ami-57ba933a</imageId> + <imageLocation>123456788908/Test Image</imageLocation> + <imageState>available</imageState> + <imageOwnerId>123456788908</imageOwnerId> + <isPublic>false</isPublic> + <architecture>x86_64</architecture> + <imageType>machine</imageType> + <kernelId>aki-88aa75e1</kernelId> + <name>Test Image</name> + <description>Testing Stuff</description> + <rootDeviceType>ebs</rootDeviceType> + <rootDeviceName>/dev/sda1</rootDeviceName> + <blockDeviceMapping> + <item> + <deviceName>/dev/sda1</deviceName> + <ebs> + <snapshotId>snap-88123ed9</snapshotId> + <volumeSize>10</volumeSize> + <deleteOnTermination>true</deleteOnTermination> + <volumeType>standard</volumeType> + </ebs> + </item> + <item> + <deviceName>/dev/sda2</deviceName> + <virtualName>ephemeral0</virtualName> + </item> + </blockDeviceMapping> + <virtualizationType>paravirtual</virtualizationType> + <hypervisor>xen</hypervisor> + </item> + </imagesSet> +</DescribeImagesResponse> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/c9e933b7/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index 81ac6bd..b68f944 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -411,19 +411,36 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): def test_list_images(self): images = self.driver.list_images() - image = images[0] - name = 'ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml' - self.assertEqual(len(images), 1) - self.assertEqual(image.name, name) - self.assertEqual(image.id, 'ami-be3adfd7') + self.assertEqual(len(images), 2) + location = '123456788908/Test Image' + self.assertEqual(images[0].id, 'ami-57ba933a') + self.assertEqual(images[0].name, 'Test Image') + self.assertEqual(images[0].extra['image_location'], location) + self.assertEqual(images[0].extra['architecture'], 'x86_64') + self.assertEqual(len(images[0].extra['block_device_mapping']), 2) + ephemeral = images[0].extra['block_device_mapping'][1]['virtual_name'] + self.assertEqual(ephemeral, 'ephemeral0') + + location = '123456788908/Test Image 2' + self.assertEqual(images[1].id, 'ami-85b2a8ae') + self.assertEqual(images[1].name, 'Test Image 2') + self.assertEqual(images[1].extra['image_location'], location) + self.assertEqual(images[1].extra['architecture'], 'x86_64') + size = images[1].extra['block_device_mapping'][0]['ebs']['volume_size'] + self.assertEqual(size, 20) def test_list_images_with_image_ids(self): - images = self.driver.list_images(ex_image_ids=['ami-be3adfd7']) + EC2MockHttp.type = 'ex_imageids' + images = self.driver.list_images(ex_image_ids=['ami-57ba933a']) - name = 'ec2-public-images/fedora-8-i386-base-v1.04.manifest.xml' self.assertEqual(len(images), 1) - self.assertEqual(images[0].name, name) + self.assertEqual(images[0].name, 'Test Image') + + def test_list_images_with_executable_by(self): + images = self.driver.list_images(ex_executableby='self') + + self.assertEqual(len(images), 2) def ex_destroy_image(self): images = self.driver.list_images() @@ -968,6 +985,10 @@ class EC2MockHttp(MockHttpTestCase): body = self.fixtures.load('describe_images.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]) + def _RunInstances(self, method, url, body, headers): body = self.fixtures.load('run_instances.xml') return (httplib.OK, body, {}, httplib.responses[httplib.OK])
