Repository: libcloud Updated Branches: refs/heads/trunk f7a9ba0ca -> f86f3277f
Support deletion and deprecation of GCE's images. Add a new class GCENodeImage to represent GCE's images, and support the `delete` and `deprecated` functions. Added a new extra field to the image object to specify the deprecation status. Closes #260 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/fb4099ea Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/fb4099ea Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/fb4099ea Branch: refs/heads/trunk Commit: fb4099eadfd0f3991160be1733a0935e37463741 Parents: f7a9ba0 Author: Franck Cuny <[email protected]> Authored: Tue Mar 11 08:26:58 2014 -0700 Committer: Tomaz Muraus <[email protected]> Committed: Fri Mar 14 10:50:55 2014 +0100 ---------------------------------------------------------------------- libcloud/compute/drivers/gce.py | 126 ++++++++++++++++--- ...es_debian_6_squeeze_v20130926_deprecate.json | 14 +++ ...images_debian_7_wheezy_v20130617_delete.json | 15 +++ ..._operation_global_images_debian7_delete.json | 15 +++ libcloud/test/compute/test_gce.py | 26 ++++ 5 files changed, 179 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/fb4099ea/libcloud/compute/drivers/gce.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/gce.py b/libcloud/compute/drivers/gce.py index 6b12de6..7aa48f2 100644 --- a/libcloud/compute/drivers/gce.py +++ b/libcloud/compute/drivers/gce.py @@ -229,6 +229,37 @@ class GCEForwardingRule(UuidMixin): self.id, self.name, self.address) +class GCENodeImage(NodeImage): + """A GCE Node Image class.""" + def __init__(self, id, name, driver, extra=None): + super(GCENodeImage, self).__init__(id, name, driver, extra=extra) + + def delete(self): + """ + Delete this image + + :return: True if successful + :rtype: ``bool`` + """ + return self.driver.ex_delete_image(image=self) + + def deprecate(self, replacement, state): + """ + Deprecate this image + + :param replacement: Image to use as a replacement + :type replacement: ``str`` or :class: `GCENodeImage` + + :param state: Deprecation state of this image. Possible values include + \'DELETED\', \'DEPRECATED\' or \'OBSOLETE\'. + :type state: ``str`` + + :return: True if successful + :rtype: ``bool`` + """ + return self.driver.ex_deprecate_image(self, replacement, state) + + class GCENetwork(UuidMixin): """A GCE Network object class.""" def __init__(self, id, name, cidr, driver, extra=None): @@ -661,10 +692,9 @@ class GCENodeDriver(NodeDriver): :keyword ex_project: Optional alternate project name. :type ex_project: ``str`` or ``None`` - :return: List of NodeImage objects - :rtype: ``list`` of :class:`NodeImage` + :return: List of GCENodeImage objects + :rtype: ``list`` of :class:`GCENodeImage` """ - list_images = [] request = '/global/images' if ex_project is None: response = self.connection.request(request, method='GET').object @@ -1109,7 +1139,7 @@ class GCENodeDriver(NodeDriver): :param image: The image to use to create the node (or, if attaching a persistent disk, the image used to create the disk) - :type image: ``str`` or :class:`NodeImage` + :type image: ``str`` or :class:`GCENodeImage` :keyword location: The location (zone) to create the node in. :type location: ``str`` or :class:`NodeLocation` or @@ -1189,7 +1219,7 @@ class GCENodeDriver(NodeDriver): :type size: ``str`` or :class:`GCENodeSize` :param image: The image to use to create the nodes. - :type image: ``str`` or :class:`NodeImage` + :type image: ``str`` or :class:`GCENodeImage` :param number: The number of nodes to create. :type number: ``int`` @@ -1372,7 +1402,7 @@ class GCENodeDriver(NodeDriver): :type snapshot: :class:`GCESnapshot` or ``str`` or ``None`` :keyword image: Image to create disk from. - :type image: :class:`NodeImage` or ``str`` or ``None`` + :type image: :class:`GCENodeImage` or ``str`` or ``None`` :keyword use_existing: If True and a disk with the given name already exists, return an object for that disk instead @@ -1740,7 +1770,7 @@ class GCENodeDriver(NodeDriver): :type size: ``str`` or :class:`GCENodeSize` :param image: The image to use to create the node. - :type image: ``str`` or :class:`NodeImage` + :type image: ``str`` or :class:`GCENodeImage` :param script: File path to start-up script :type script: ``str`` @@ -1854,6 +1884,66 @@ class GCENodeDriver(NodeDriver): self.connection.async_request(request, method='DELETE') return True + def ex_delete_image(self, image): + """ + Delete a specific image resource. + + :param image: Image object to delete + :type image: ``str`` or :class:`GCENodeImage` + + :return: True if successfull + :rtype: ``bool`` + """ + if not hasattr(image, 'name'): + image = self.ex_get_image(image) + + request = '/global/images/%s' % (image.name) + self.connection.async_request(request, method='DELETE') + return True + + def ex_deprecate_image(self, image, replacement, state=None): + """ + Deprecate a specific image resource. + + :param image: Image object to deprecate + :type image: ``str`` or :class: `GCENodeImage` + + :param replacement: Image object to use as a replacement + :type replacement: ``str`` or :class: `GCENodeImage` + + :param state: State of the image + :type state: ``str`` + + :return: True if successfull + :rtype: ``bool`` + """ + if not hasattr(image, 'name'): + image = self.ex_get_image(image) + + if not hasattr(replacement, 'name'): + replacement = self.ex_get_image(replacement) + + if state is None: + state = 'DEPRECATED' + + possible_states = ['DELETED', 'DEPRECATED', 'OBSOLETE'] + + if state not in possible_states: + raise ValueError('state must be one of %s' + % ','.join(possible_states)) + + image_data = { + 'state': state, + 'replacement': replacement.extra['selfLink'], + } + + request = '/global/images/%s/deprecate' % (image.name) + + self.connection.request( + request, method='POST', data=image_data).object + + return True + def ex_destroy_healthcheck(self, healthcheck): """ Destroy a healthcheck. @@ -2154,15 +2244,15 @@ class GCENodeDriver(NodeDriver): def ex_get_image(self, partial_name): """ - Return an NodeImage object based on the name or link provided. + Return an GCENodeImage object based on the name or link provided. :param partial_name: The name, partial name, or full path of a GCE image. :type partial_name: ``str`` - :return: NodeImage object based on provided information or None if an - image with that name is not found. - :rtype: :class:`NodeImage` or ``None`` + :return: GCENodeImage object based on provided information or None if + an image with that name is not found. + :rtype: :class:`GCENodeImage` or ``None`` """ if partial_name.startswith('https://'): response = self.connection.request(partial_name, method='GET') @@ -2469,7 +2559,7 @@ class GCENodeDriver(NodeDriver): :return: The latest image object that matches the partial name or None if no matching image is found. - :rtype: :class:`NodeImage` or ``None`` + :rtype: :class:`GCENodeImage` or ``None`` """ project_images = self.list_images(project) partial_match = [] @@ -2538,7 +2628,7 @@ class GCENodeDriver(NodeDriver): :param image: The image to use to create the node (or, if using a persistent disk, the image the disk was created from). - :type image: :class:`NodeImage` + :type image: :class:`GCENodeImage` :param location: The location (zone) to create the node in. :type location: :class:`NodeLocation` or :class:`GCEZone` @@ -2755,7 +2845,7 @@ class GCENodeDriver(NodeDriver): :type snapshot: :class:`GCESnapshot` or ``str`` or ``None`` :keyword image: Image to create disk from. - :type image: :class:`NodeImage` or ``str`` or ``None`` + :type image: :class:`GCENodeImage` or ``str`` or ``None`` :return: Tuple containing the request string, the data dictionary and the URL parameters @@ -2918,15 +3008,17 @@ class GCENodeDriver(NodeDriver): :type image: ``dict`` :return: Image object - :rtype: :class:`NodeImage` + :rtype: :class:`GCENodeImage` """ extra = {} extra['preferredKernel'] = image.get('preferredKernel', None) extra['description'] = image.get('description', None) extra['creationTimestamp'] = image.get('creationTimestamp') extra['selfLink'] = image.get('selfLink') - return NodeImage(id=image['id'], name=image['name'], driver=self, - extra=extra) + extra['deprecated'] = image.get('deprecated', None) + + return GCENodeImage(id=image['id'], name=image['name'], driver=self, + extra=extra) def _to_node_location(self, location): """ http://git-wip-us.apache.org/repos/asf/libcloud/blob/fb4099ea/libcloud/test/compute/fixtures/gce/global_images_debian_6_squeeze_v20130926_deprecate.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_images_debian_6_squeeze_v20130926_deprecate.json b/libcloud/test/compute/fixtures/gce/global_images_debian_6_squeeze_v20130926_deprecate.json new file mode 100644 index 0000000..fdc6654 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_images_debian_6_squeeze_v20130926_deprecate.json @@ -0,0 +1,14 @@ +{ + "status": "PENDING", + "kind": "compute#operation", + "name": "operation-1394594316110-4f4604ad0e708-2e4622ab", + "startTime": "2014-03-11T20:18:36.194-07:00", + "insertTime": "2014-03-11T20:18:36.110-07:00", + "targetId": "10034929421075729520", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/images/debian_6_squeeze_v20130926", + "operationType": "setDeprecation", + "progress": 0, + "id": "11223768474922166090", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-1394594316110-4f4604ad0e708-2e4622ab", + "user": "[email protected]" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/fb4099ea/libcloud/test/compute/fixtures/gce/global_images_debian_7_wheezy_v20130617_delete.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/global_images_debian_7_wheezy_v20130617_delete.json b/libcloud/test/compute/fixtures/gce/global_images_debian_7_wheezy_v20130617_delete.json new file mode 100644 index 0000000..7b70942 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/global_images_debian_7_wheezy_v20130617_delete.json @@ -0,0 +1,15 @@ +{ + "kind": "compute#operation", + "id": "10762099380229198553", + "name": "operation-global_images_debian7_delete", + "operationType": "delete", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/images/debian-7-wheezy-v20130617", + "targetId": "14881612020726561163", + "status": "PENDING", + "user": "[email protected]", + "progress": 0, + "insertTime": "2014-03-11T14:37:48.075-07:00", + "startTime": "2014-03-11T14:37:48.158-07:00", + "endTime": "2014-03-11T14:37:48.634-07:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_images_debian7_delete" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/fb4099ea/libcloud/test/compute/fixtures/gce/operations_operation_global_images_debian7_delete.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/gce/operations_operation_global_images_debian7_delete.json b/libcloud/test/compute/fixtures/gce/operations_operation_global_images_debian7_delete.json new file mode 100644 index 0000000..0c085a9 --- /dev/null +++ b/libcloud/test/compute/fixtures/gce/operations_operation_global_images_debian7_delete.json @@ -0,0 +1,15 @@ +{ + "kind": "compute#operation", + "id": "10762099380229198553", + "name": "operation-global_images_debian7_delete", + "operationType": "delete", + "targetLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/images/debian-7-wheezy-v20130617", + "targetId": "14881612020726561163", + "status": "DONE", + "user": "[email protected]", + "progress": 100, + "insertTime": "2014-03-11T14:37:48.075-07:00", + "startTime": "2014-03-11T14:37:48.158-07:00", + "endTime": "2014-03-11T14:37:48.634-07:00", + "selfLink": "https://www.googleapis.com/compute/v1/projects/project_name/global/operations/operation-global_images_debian7_delete" +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/fb4099ea/libcloud/test/compute/test_gce.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_gce.py b/libcloud/test/compute/test_gce.py index 31f35bf..5a4af54 100644 --- a/libcloud/test/compute/test_gce.py +++ b/libcloud/test/compute/test_gce.py @@ -416,6 +416,16 @@ class GCENodeDriverTest(LibcloudTestCase, TestCaseMixin): destroyed = hc.destroy() self.assertTrue(destroyed) + def test_ex_delete_image(self): + image = self.driver.ex_get_image('debian-7') + deleted = self.driver.ex_delete_image(image) + self.assertTrue(deleted) + + def test_ex_deprecate_image(self): + image = self.driver.ex_get_image('debian-6') + deprecated = image.deprecate('debian-7', 'DEPRECATED') + self.assertTrue(deprecated) + def test_ex_destroy_firewall(self): firewall = self.driver.ex_get_firewall('lcfirewall') destroyed = firewall.destroy() @@ -672,6 +682,16 @@ class GCEMockHttp(MockHttpTestCase): body = self.fixtures.load('global_images.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_images_debian_7_wheezy_v20130617( + self, method, url, body, headers): + body = self.fixtures.load('global_images_debian_7_wheezy_v20130617_delete.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + + def _global_images_debian_6_squeeze_v20130926_deprecate( + self, method, url, body, headers): + body = self.fixtures.load('global_images_debian_6_squeeze_v20130926_deprecate.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_networks(self, method, url, body, headers): if method == 'POST': body = self.fixtures.load('global_networks_post.json') @@ -719,6 +739,12 @@ class GCEMockHttp(MockHttpTestCase): 'operations_operation_global_httpHealthChecks_lchealthcheck_delete.json') return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_images_debian7_delete( + self, method, url, body, headers): + body = self.fixtures.load( + 'operations_operation_global_images_debian7_delete.json') + return (httplib.OK, body, self.json_hdr, httplib.responses[httplib.OK]) + def _global_operations_operation_global_httpHealthChecks_lchealthcheck_put( self, method, url, body, headers): body = self.fixtures.load(
