Repository: libcloud Updated Branches: refs/heads/trunk d69adbd88 -> 08d7d95db
Added support for generic image management at Rackspace and EC2. Closes #277 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/d2604aff Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d2604aff Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d2604aff Branch: refs/heads/trunk Commit: d2604aff4db0a4c6683e04550a94e9ee4c0d4274 Parents: d69adbd Author: Matthew Lehman <[email protected]> Authored: Mon Apr 14 11:56:12 2014 -0400 Committer: Tomaz Muraus <[email protected]> Committed: Mon Apr 28 14:00:59 2014 +0200 ---------------------------------------------------------------------- CHANGES.rst | 5 + .../_supported_methods_image_management.rst | 119 +++++++++++++++++++ libcloud/compute/base.py | 99 +++++++++++++-- libcloud/compute/drivers/ec2.py | 56 +++++++-- libcloud/compute/drivers/openstack.py | 38 +++++- libcloud/test/compute/test_ec2.py | 37 ++++-- libcloud/test/compute/test_openstack.py | 20 ++-- 7 files changed, 322 insertions(+), 52 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 1ba169f..89bf70e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -96,6 +96,11 @@ Compute (GITHUB-278) [zerthimon] +- Add new standard API for image management and initial implementation for the + EC2 and Rackspace driver. + (GITHUB-277) + [Matt Lehman] + Load Balancer ~~~~~~~~~~~~~ http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/docs/compute/_supported_methods_image_management.rst ---------------------------------------------------------------------- diff --git a/docs/compute/_supported_methods_image_management.rst b/docs/compute/_supported_methods_image_management.rst new file mode 100644 index 0000000..1e2d2c0 --- /dev/null +++ b/docs/compute/_supported_methods_image_management.rst @@ -0,0 +1,119 @@ +===================================== ============ ============= ============== ============= ============= +Provider create image delete image get image list images copy image +===================================== ============ ============= ============== ============= ============= +`Abiquo`_ no no no yes no +`Bluebox Blocks`_ no no no yes no +`Brightbox`_ no no no yes no +`CloudFrames`_ no no no yes no +`CloudSigma (API v2.0)`_ no no no yes no +`CloudStack`_ no no no yes no +`Digital Ocean`_ no no no yes no +`Dreamhost`_ no no no yes no +`Amazon EC2`_ yes yes yes yes yes +`Amazon EC2 (ap-northeast-1)`_ yes yes yes yes yes +`Amazon EC2 (ap-southeast-1)`_ yes yes yes yes yes +`Amazon EC2 (ap-southeast-2)`_ yes yes yes yes yes +`Amazon EC2 (eu-west-1)`_ yes yes yes yes yes +`Amazon EC2 (eu-west-1)`_ yes yes yes yes yes +`Amazon EC2 (sa-east-1)`_ yes yes yes yes yes +`Amazon EC2`_ yes yes yes yes yes +`Amazon EC2 (us-west-1)`_ yes yes yes yes yes +`Amazon EC2 (us-west-2)`_ yes yes yes yes yes +`Enomaly Elastic Computing Platform`_ no no no yes no +`ElasticHosts`_ no no no yes no +`ElasticHosts (syd-y)`_ no no no yes no +`ElasticHosts (tor-p)`_ no no no yes no +`ElasticHosts (cn-1)`_ no no no yes no +`ElasticHosts (lon-p)`_ no no no yes no +`ElasticHosts (lon-b)`_ no no no yes no +`ElasticHosts (sat-p)`_ no no no yes no +`ElasticHosts (lax-p)`_ no no no yes no +`ElasticHosts (sjc-c)`_ no no no yes no +`Eucalyptus`_ yes yes yes yes yes +`Exoscale`_ no no no no no +`Gandi`_ no no no yes no +`Google Compute Engine`_ no no no yes no +`GoGrid`_ no no no yes no +`HostVirtual`_ no no no yes no +`IBM SmartCloud Enterprise`_ no no no yes no +`Ikoula`_ no no no no no +`Joyent`_ no no no yes no +`KTUCloud`_ no no no yes no +`Libvirt`_ no no no no no +`Linode`_ no no no yes no +`NephoScale`_ no no no yes no +`Nimbus`_ yes yes yes yes yes +`Ninefold`_ no no no no no +`OpenNebula (v3.8)`_ no no no yes no +`OpenStack`_ yes yes yes yes no +`Opsource`_ no no no yes no +`Rackspace Cloud (Next Gen)`_ yes yes yes yes no +`Rackspace Cloud (First Gen)`_ yes yes yes yes no +`RimuHosting`_ no no no yes no +`ServerLove`_ no no no no no +`skalicloud`_ no no no no no +`SoftLayer`_ no no no yes no +`vCloud`_ no no no yes no +`VCL`_ no no no yes no +`vCloud`_ no no no yes no +`Voxel VoxCLOUD`_ no no no yes no +`vps.net`_ no no no yes no +===================================== ============ ============= ============== ============= ============= + +.. _`Abiquo`: http://www.abiquo.com/ +.. _`Bluebox Blocks`: http://bluebox.net +.. _`Brightbox`: http://www.brightbox.co.uk/ +.. _`CloudFrames`: http://www.cloudframes.net/ +.. _`CloudSigma (API v2.0)`: http://www.cloudsigma.com/ +.. _`CloudStack`: http://cloudstack.org/ +.. _`Digital Ocean`: https://www.digitalocean.com +.. _`Dreamhost`: http://dreamhost.com/ +.. _`Amazon EC2`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (ap-northeast-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (ap-southeast-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (ap-southeast-2)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (eu-west-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (eu-west-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (sa-east-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (us-west-1)`: http://aws.amazon.com/ec2/ +.. _`Amazon EC2 (us-west-2)`: http://aws.amazon.com/ec2/ +.. _`Enomaly Elastic Computing Platform`: http://www.enomaly.com/ +.. _`ElasticHosts`: http://www.elastichosts.com/ +.. _`ElasticHosts (syd-y)`: http://www.elastichosts.com/ +.. _`ElasticHosts (tor-p)`: http://www.elastichosts.com/ +.. _`ElasticHosts (cn-1)`: http://www.elastichosts.com/ +.. _`ElasticHosts (lon-p)`: http://www.elastichosts.com/ +.. _`ElasticHosts (lon-b)`: http://www.elastichosts.com/ +.. _`ElasticHosts (sat-p)`: http://www.elastichosts.com/ +.. _`ElasticHosts (lax-p)`: http://www.elastichosts.com/ +.. _`ElasticHosts (sjc-c)`: http://www.elastichosts.com/ +.. _`Eucalyptus`: http://www.eucalyptus.com/ +.. _`Exoscale`: https://www.exoscale.ch/ +.. _`Gandi`: http://www.gandi.net/ +.. _`Google Compute Engine`: https://cloud.google.com/ +.. _`GoGrid`: http://www.gogrid.com/ +.. _`HostVirtual`: http://www.vr.org +.. _`IBM SmartCloud Enterprise`: http://ibm.com/services/us/en/cloud-enterprise/ +.. _`Ikoula`: http://express.ikoula.co.uk/cloudstack +.. _`Joyent`: http://www.joyentcloud.com +.. _`KTUCloud`: https://ucloudbiz.olleh.com/ +.. _`Libvirt`: http://libvirt.org/ +.. _`Linode`: http://www.linode.com/ +.. _`NephoScale`: http://www.nephoscale.com +.. _`Nimbus`: http://www.nimbusproject.org/ +.. _`Ninefold`: http://ninefold.com/ +.. _`OpenNebula (v3.8)`: http://opennebula.org/ +.. _`OpenStack`: http://openstack.org/ +.. _`Opsource`: http://www.opsource.net/ +.. _`Rackspace Cloud (Next Gen)`: http://www.rackspace.com +.. _`Rackspace Cloud (First Gen)`: http://www.rackspace.com +.. _`RimuHosting`: http://rimuhosting.com/ +.. _`ServerLove`: http://www.serverlove.com/ +.. _`skalicloud`: http://www.skalicloud.com/ +.. _`SoftLayer`: http://www.softlayer.com/ +.. _`vCloud`: http://www.vmware.com/products/vcloud/ +.. _`VCL`: http://incubator.apache.org/vcl/ +.. _`vCloud`: http://www.vmware.com/products/vcloud/ +.. _`Voxel VoxCLOUD`: http://www.voxel.net/ +.. _`vps.net`: http://vps.net/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/libcloud/compute/base.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/base.py b/libcloud/compute/base.py index 6d24f3f..6c34186 100644 --- a/libcloud/compute/base.py +++ b/libcloud/compute/base.py @@ -645,19 +645,6 @@ class NodeDriver(BaseDriver): raise NotImplementedError( 'list_nodes not implemented for this driver') - def list_images(self, location=None): - """ - List images on a provider - - :param location: The location at which to list images - :type location: :class:`.NodeLocation` - - :return: list of node image objects - :rtype: ``list`` of :class:`.NodeImage` - """ - raise NotImplementedError( - 'list_images not implemented for this driver') - def list_sizes(self, location=None): """ List sizes on a provider @@ -1066,6 +1053,92 @@ class NodeDriver(BaseDriver): 'destroy_volume_snapshot not implemented for this driver') ## + # Image management methods + ## + + def list_images(self, location=None): + """ + List images on a provider. + + :param location: The location at which to list images. + :type location: :class:`.NodeLocation` + + :return: list of node image objects. + :rtype: ``list`` of :class:`.NodeImage` + """ + raise NotImplementedError( + 'list_images not implemented for this driver') + + def create_image(self, node, name, description=None): + """ + Creates an image from a node object. + + :param node: Node to run the task on. + :type node: :class:`.Node` + + :param name: name for new image. + :type name: ``str`` + + :param description: description for new image. + :type name: ``description`` + + :rtype: :class:`.NodeImage`: + :return: NodeImage instance on success. + + """ + raise NotImplementedError( + 'create_image not implemented for this driver') + + def delete_image(self, node_image): + """ + Deletes a node image from a provider. + + :param node_image: Node image object. + :type node_image: :class:`.NodeImage` + + :return: ``True`` if delete_image was successful, ``False`` otherwise. + :rtype: ``bool`` + """ + + raise NotImplementedError( + 'delete_image not implemented for this driver') + + def get_image(self, image_id): + """ + Returns a single node image from a provider. + + :param image_id: Node to run the task on. + :type image_id: ``str`` + + :rtype :class:`.NodeImage`: + :return: NodeImage instance on success. + """ + raise NotImplementedError( + 'get_image not implemented for this driver') + + def copy_image(self, source_region, node_image, name, description=None): + """ + Copies an image from a source region to the current region. + + :param source_region: Region to copy the node from. + :type source_region: ``str`` + + :param node_image: NodeImage to copy. + :type node_image: :class`.NodeImage`: + + :param name: name for new image. + :type name: ``str`` + + :param description: description for new image. + :type name: ``str`` + + :rtype: :class:`.NodeImage`: + :return: NodeImage instance on success. + """ + raise NotImplementedError( + 'copy_image not implemented for this driver') + + ## # SSH key pair management methods ## http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/libcloud/compute/drivers/ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/ec2.py b/libcloud/compute/drivers/ec2.py index 692282e..0a57b0b 100644 --- a/libcloud/compute/drivers/ec2.py +++ b/libcloud/compute/drivers/ec2.py @@ -1329,6 +1329,7 @@ class BaseEC2NodeDriver(NodeDriver): ex_executableby=None): """ List all images + @inherits: :class:`NodeDriver.list_images` Ex_image_ids parameter is used to filter the list of images that should be returned. Only the images @@ -1376,6 +1377,22 @@ class BaseEC2NodeDriver(NodeDriver): ) return images + def get_image(self, image_id): + """ + Get an image based on a image_id + + :param image_id: Image identifier + :type image_id: ``str`` + + :return: A NodeImage object + :rtype: :class:`NodeImage` + + """ + images = self.list_images(ex_image_ids=[image_id]) + image = images[0] + + return image + def list_locations(self): locations = [] for index, availability_zone in \ @@ -1716,11 +1733,13 @@ class BaseEC2NodeDriver(NodeDriver): namespace=NAMESPACE) return element == 'true' - def ex_copy_image(self, source_region, image, name=None, description=None): + def copy_image(self, image, source_region, name=None, description=None): """ Copy an Amazon Machine Image from the specified source region to the current region. + @inherits: :class:`NodeDriver.copy_image` + :param source_region: The region where the image resides :type source_region: ``str`` @@ -1751,11 +1770,13 @@ class BaseEC2NodeDriver(NodeDriver): return image - def ex_create_image_from_node(self, node, name, block_device_mapping, - reboot=False, description=None): + def create_image(self, node, name, description=None, reboot=False, + block_device_mapping=None): """ Create an Amazon Machine Image based off of an EBS-backed instance. + @inherits: :class:`NodeDriver.create_image` + :param node: Instance of ``Node`` :type node: :class: `Node` @@ -1768,8 +1789,9 @@ class BaseEC2NodeDriver(NodeDriver): :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. + creation. Amazon calls this NoReboot and + sets it to false by default to ensure a + clean image. :type reboot: ``bool`` :param description: An optional description for the new image @@ -1794,19 +1816,29 @@ class BaseEC2NodeDriver(NodeDriver): if description is not None: params['Description'] = description - params.update(self._get_block_device_mapping_params( - block_device_mapping)) + if block_device_mapping is not None: + 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', - 'ImageId': image.id - } + def delete_image(self, image): + """ + Deletes an image at Amazon given a NodeImage object + + @inherits: :class:`NodeDriver.delete_image` + + :param image: Instance of ``NodeImage`` + :type image: :class: `NodeImage` + + :rtype: ``bool`` + """ + params = {'Action': 'DeregisterImage', + 'ImageId': image.id} + response = self.connection.request(self.path, params=params).object return self._get_boolean(response) http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 3988030..2aca45c 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -287,6 +287,8 @@ class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin): def list_images(self, location=None, ex_only_active=True): """ + Lists all active images + @inherits: :class:`NodeDriver.list_images` :param ex_only_active: True if list only active @@ -296,6 +298,22 @@ class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin): return self._to_images( self.connection.request('/images/detail').object, ex_only_active) + def get_image(self, image_id): + """ + Get an image based on a image_id + + @inherits: :class:`NodeDriver.get_image` + + :param image_id: Image identifier + :type image_id: ``str`` + + :return: A NodeImage object + :rtype: :class:`NodeImage` + + """ + return self._to_image(self.connection.request( + '/images/%s' % (image_id,)).object['image']) + def list_sizes(self, location=None): return self._to_sizes( self.connection.request('/flavors/detail').object) @@ -912,9 +930,11 @@ class OpenStack_1_0_NodeDriver(OpenStackNodeDriver): return {"rate": rate, "absolute": absolute} - def ex_save_image(self, node, name): + def create_image(self, node, name, description=None, reboot=True): """Create an image for node. + @inherits: :class:`NodeDriver.create_image` + :param node: node to use as a base for image :type node: :class:`Node` @@ -935,9 +955,11 @@ class OpenStack_1_0_NodeDriver(OpenStackNodeDriver): self.connection.request("/images", method="POST", data=ET.tostring(image_elm)).object) - def ex_delete_image(self, image): + def delete_image(self, image): """Delete an image for node. + @inherits: :class:`NodeDriver.delete_image` + :param image: the image to be deleted :type image: :class:`NodeImage` @@ -1444,7 +1466,7 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): resp = self._node_action(node, 'revertResize') return resp.status == httplib.ACCEPTED - def ex_save_image(self, node, name, metadata=None): + def create_image(self, node, name, metadata=None): """ Creates a new image. @@ -1465,7 +1487,7 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): resp = self._node_action(node, 'createImage', name=name, **optional_params) image_id = self._extract_image_id_from_url(resp.headers['location']) - return self.ex_get_image(image_id=image_id) + return self.get_image(image_id=image_id) def ex_set_server_name(self, node, name): """ @@ -1927,10 +1949,12 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): return self._to_size(self.connection.request( '/flavors/%s' % (size_id,)) .object['flavor']) - def ex_get_image(self, image_id): + def get_image(self, image_id): """ Get a NodeImage + @inherits: :class:`NodeDriver.get_image` + :param image_id: ID of the image which should be used :type image_id: ``str`` @@ -1939,10 +1963,12 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): return self._to_image(self.connection.request( '/images/%s' % (image_id,)).object['image']) - def ex_delete_image(self, image): + def delete_image(self, image): """ Delete a NodeImage + @inherits: :class:`NodeDriver.delete_image` + :param image: image witch should be used :type image: :class:`NodeImage` http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/libcloud/test/compute/test_ec2.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_ec2.py b/libcloud/test/compute/test_ec2.py index 9832408..64010f4 100644 --- a/libcloud/test/compute/test_ec2.py +++ b/libcloud/test/compute/test_ec2.py @@ -447,14 +447,21 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): self.assertEqual(len(images), 2) - def test_ex_copy_image(self): + def test_get_image(self): + image = self.driver.get_image('ami-57ba933a') + self.assertEqual(image.id, 'ami-57ba933a') + self.assertEqual(image.name, 'Test Image') + self.assertEqual(image.extra['architecture'], 'x86_64') + self.assertEqual(len(image.extra['block_device_mapping']), 2) + + def test_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') + resp = self.driver.copy_image(image, 'us-east-1', + name='Faux Image', + description='Test Image Copy') self.assertEqual(resp.id, 'ami-4db38224') - def test_ex_create_image_from_node(self): + def test_create_image(self): node = self.driver.list_nodes()[0] mapping = [{'VirtualName': None, @@ -463,17 +470,25 @@ class EC2Tests(LibcloudTestCase, TestCaseMixin): 'DeleteOnTermination': 'true'}, 'DeviceName': '/dev/sda1'}] - resp = self.driver.ex_create_image_from_node(node, - 'New Image', - mapping, - description='New EBS Image') + resp = self.driver.create_image(node, + 'New Image', + description='New EBS Image', + block_device_mapping=mapping) self.assertEqual(resp.id, 'ami-e9b38280') - def ex_destroy_image(self): + def test_create_image_no_mapping(self): + node = self.driver.list_nodes()[0] + + resp = self.driver.create_image(node, + 'New Image', + description='New EBS Image') + self.assertEqual(resp.id, 'ami-e9b38280') + + def delete_image(self): images = self.driver.list_images() image = images[0] - resp = self.driver.ex_destroy_image(image) + resp = self.driver.delete_image(image) self.assertTrue(resp) def ex_register_image(self): http://git-wip-us.apache.org/repos/asf/libcloud/blob/d2604aff/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index ca379b2..ef5dfc7 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -537,17 +537,17 @@ class OpenStack_1_0_Tests(unittest.TestCase, TestCaseMixin): self.assertTrue("rate" in limits) self.assertTrue("absolute" in limits) - def test_ex_save_image(self): + def test_create_image(self): node = Node(id=444222, name=None, state=None, public_ips=None, private_ips=None, driver=self.driver) - image = self.driver.ex_save_image(node, "imgtest") + image = self.driver.create_image(node, "imgtest") self.assertEqual(image.name, "imgtest") self.assertEqual(image.id, "12345") - def test_ex_delete_image(self): + def test_delete_image(self): image = NodeImage(id=333111, name='Ubuntu 8.10 (intrepid)', driver=self.driver) - ret = self.driver.ex_delete_image(image) + ret = self.driver.delete_image(image) self.assertTrue(ret) def test_ex_list_ip_addresses(self): @@ -1158,8 +1158,8 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): e = sys.exc_info()[1] self.fail('An error was raised: ' + repr(e)) - def test_ex_save_image(self): - image = self.driver.ex_save_image(self.node, 'new_image') + def test_create_image(self): + image = self.driver.create_image(self.node, 'new_image') self.assertEqual(image.name, 'new_image') self.assertEqual(image.id, '4949f9ee-2421-4c81-8b49-13119446008b') @@ -1214,19 +1214,19 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): self.assertEqual(size.id, size_id) self.assertEqual(size.name, '15.5GB slice') - def test_ex_get_image(self): + def test_get_image(self): image_id = '13' - image = self.driver.ex_get_image(image_id) + image = self.driver.get_image(image_id) self.assertEqual(image.id, image_id) self.assertEqual(image.name, 'Windows 2008 SP2 x86 (B24)') self.assertEqual(image.extra['serverId'], None) self.assertEqual(image.extra['minDisk'], "5") self.assertEqual(image.extra['minRam'], "256") - def test_ex_delete_image(self): + def test_delete_image(self): image = NodeImage( id='26365521-8c62-11f9-2c33-283d153ecc3a', name='My Backup', driver=self.driver) - result = self.driver.ex_delete_image(image) + result = self.driver.delete_image(image) self.assertTrue(result) def test_extract_image_id_from_url(self):
