Repository: libcloud Updated Branches: refs/heads/trunk b37df1281 -> 193c42f21
Add the following new methods to the Linode driver: ex_list_volumes, ex_create_volume, ex_destroy_volume. Closes #430 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/d9efecdb Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d9efecdb Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d9efecdb Branch: refs/heads/trunk Commit: d9efecdb227c9d6167d35b71c5b361d3bb356963 Parents: 26ddc9a Author: Wojciech Wirkijowski <[email protected]> Authored: Sun Nov 9 12:46:54 2014 +0000 Committer: Tomaz Muraus <[email protected]> Committed: Sun Feb 22 00:24:58 2015 +0100 ---------------------------------------------------------------------- CHANGES.rst | 5 + libcloud/common/linode.py | 3 + libcloud/compute/drivers/linode.py | 141 ++++++++++++++++++- .../fixtures/linode/_linode_disk_list.json | 28 ++++ libcloud/test/compute/test_linode.py | 44 +++++- 5 files changed, 213 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/d9efecdb/CHANGES.rst ---------------------------------------------------------------------- diff --git a/CHANGES.rst b/CHANGES.rst index 976823f..b8baa23 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,6 +29,11 @@ Compute (LIBCLOUD-668, GITHUB-462) [Allard Hoeve] +- Add the following new methods to the Linode driver: ``ex_list_volumes``, + ``ex_create_volume``, ``ex_destroy_volume``. + (LIBCLOUD-649, GITHUB-430) + [Wojciech Wirkijowski] + Changes with Apache Libcloud 0.17.0 ----------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/d9efecdb/libcloud/common/linode.py ---------------------------------------------------------------------- diff --git a/libcloud/common/linode.py b/libcloud/common/linode.py index aba75f0..c991695 100644 --- a/libcloud/common/linode.py +++ b/libcloud/common/linode.py @@ -42,6 +42,9 @@ LINODE_PLAN_IDS = {1024: '1', 65536: '10', 98304: '12'} +# Available filesystems for disk creation +LINODE_DISK_FILESYSTEMS = ['ext3', 'ext4', 'swap', 'raw'] + class LinodeException(Exception): """Error originating from the Linode API http://git-wip-us.apache.org/repos/asf/libcloud/blob/d9efecdb/libcloud/compute/drivers/linode.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/linode.py b/libcloud/compute/drivers/linode.py index 4960f29..c3a9642 100644 --- a/libcloud/compute/drivers/linode.py +++ b/libcloud/compute/drivers/linode.py @@ -42,11 +42,12 @@ from copy import copy from libcloud.utils.py3 import PY3 from libcloud.common.linode import (API_ROOT, LinodeException, - LinodeConnection, LINODE_PLAN_IDS) + LinodeConnection, LINODE_PLAN_IDS, + LINODE_DISK_FILESYSTEMS) from libcloud.compute.types import Provider, NodeState from libcloud.compute.base import NodeDriver, NodeSize, Node, NodeLocation from libcloud.compute.base import NodeAuthPassword, NodeAuthSSHKey -from libcloud.compute.base import NodeImage +from libcloud.compute.base import NodeImage, StorageVolume class LinodeNodeDriver(NodeDriver): @@ -64,6 +65,8 @@ class LinodeNodeDriver(NodeDriver): list_sizes avail.linodeplans list_images avail.distributions list_locations avail.datacenters + list_volumes linode.disk.list + destroy_volume linode.disk.delete For more information on the Linode API, be sure to read the reference: @@ -74,6 +77,7 @@ class LinodeNodeDriver(NodeDriver): website = 'http://www.linode.com/' connectionCls = LinodeConnection _linode_plan_ids = LINODE_PLAN_IDS + _linode_disk_filesystems = LINODE_DISK_FILESYSTEMS features = {'create_node': ['ssh_key', 'password']} def __init__(self, key): @@ -464,7 +468,7 @@ class LinodeNodeDriver(NodeDriver): used. :keyword dc: the datacenter to create Linodes in unless specified - :type dc: :class:`NodeLocation` + :type dc: :class:`NodeLocation` :rtype: ``bool`` """ @@ -480,6 +484,137 @@ class LinodeNodeDriver(NodeDriver): self.datacenter = None raise LinodeException(0xFD, "Invalid datacenter (use one of %s)" % dcs) + def destroy_volume(self, volume): + """ + Destroys disk volume for the Linode. Linode id is to be provided as + extra["LinodeId"] whithin :class:`StorageVolume`. It can be retrieved + by :meth:`libcloud.compute.drivers.linode.LinodeNodeDriver\ + .ex_list_volumes`. + + :param volume: Volume to be destroyed + :type volume: :class:`StorageVolume` + + :rtype: ``bool`` + """ + if not isinstance(volume, StorageVolume): + raise LinodeException(0xFD, "Invalid volume instance") + + if volume.extra["LINODEID"] is None: + raise LinodeException(0xFD, "Missing LinodeID") + + params = { + "api_action": "linode.disk.delete", + "LinodeID": volume.extra["LINODEID"], + "DiskID": volume.id, + } + self.connection.request(API_ROOT, params=params) + + return True + + def ex_create_volume(self, size, name, node, fs_type): + """ + Create disk for the Linode. + + :keyword size: Size of volume in megabytes (required) + :type size: ``int`` + + :keyword name: Name of the volume to be created + :type name: ``str`` + + :keyword node: Node to attach volume to. + :type node: :class:`Node` + + :keyword fs_type: The formatted type of this disk. Valid types are: + ext3, ext4, swap, raw + :type fs_type: ``str`` + + + :return: StorageVolume representing the newly-created volume + :rtype: :class:`StorageVolume` + """ + # check node + if not isinstance(node, Node): + raise LinodeException(0xFD, "Invalid node instance") + + # check space available + total_space = node.extra['TOTALHD'] + existing_volumes = self.ex_list_volumes(node) + used_space = 0 + for volume in existing_volumes: + used_space = used_space + volume.size + + available_space = total_space - used_space + if available_space < size: + raise LinodeException(0xFD, "Volume size too big. Available space\ + %d" % available_space) + + # check filesystem type + if fs_type not in self._linode_disk_filesystems: + raise LinodeException(0xFD, "Not valid filesystem type") + + params = { + "api_action": "linode.disk.create", + "LinodeID": node.id, + "Label": name, + "Type": fs_type, + "Size": size + } + data = self.connection.request(API_ROOT, params=params).objects[0] + volume = data["DiskID"] + # Make a volume out of it and hand it back + params = { + "api_action": "linode.disk.list", + "LinodeID": node.id, + "DiskID": volume + } + data = self.connection.request(API_ROOT, params=params).objects[0] + return self._to_volumes(data)[0] + + def ex_list_volumes(self, node, disk_id=None): + """ + List existing disk volumes for for given Linode. + + :keyword node: Node to list disk volumes for. (required) + :type node: :class:`Node` + + :keyword disk_id: Id for specific disk volume. (optional) + :type disk_id: ``int`` + + :rtype: ``list`` of :class:`StorageVolume` + """ + if not isinstance(node, Node): + raise LinodeException(0xFD, "Invalid node instance") + + params = { + "api_action": "linode.disk.list", + "LinodeID": node.id + } + # Add param if disk_id was specified + if disk_id is not None: + params["DiskID"] = disk_id + + data = self.connection.request(API_ROOT, params=params).objects[0] + return self._to_volumes(data) + + def _to_volumes(self, objs): + """ + Covert returned JSON volumes into StorageVolume instances + + :keyword objs: ``list`` of JSON dictionaries representing the + StorageVolumes + :type objs: ``list`` + + :return: ``list`` of :class:`StorageVolume`s + """ + volumes = {} + for o in objs: + vid = o["DISKID"] + volumes[vid] = vol = StorageVolume(id=vid, name=o["LABEL"], + size=int(o["SIZE"]), + driver=self.connection.driver) + vol.extra = copy(o) + return list(volumes.values()) + def _to_nodes(self, objs): """Convert returned JSON Linodes into Node instances http://git-wip-us.apache.org/repos/asf/libcloud/blob/d9efecdb/libcloud/test/compute/fixtures/linode/_linode_disk_list.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/linode/_linode_disk_list.json b/libcloud/test/compute/fixtures/linode/_linode_disk_list.json new file mode 100644 index 0000000..8ec05a9 --- /dev/null +++ b/libcloud/test/compute/fixtures/linode/_linode_disk_list.json @@ -0,0 +1,28 @@ +{ +"ERRORARRAY":[], +"ACTION":"linode.disk.list", +"DATA":[ + { + "UPDATE_DT":"2009-06-30 13:19:00.0", + "DISKID":55319, + "LABEL":"test label", + "TYPE":"ext3", + "LINODEID":8098, + "ISREADONLY":0, + "STATUS":1, + "CREATE_DT":"2008-04-04 10:08:06.0", + "SIZE":4096 + }, + { + "UPDATE_DT":"2009-07-18 12:53:043.0", + "DISKID":55320, + "LABEL":"256M Swap Image", + "TYPE":"swap", + "LINODEID":8098, + "ISREADONLY":0, + "STATUS":1, + "CREATE_DT":"2008-04-04 10:08:06.0", + "SIZE":256 + } + ] +} http://git-wip-us.apache.org/repos/asf/libcloud/blob/d9efecdb/libcloud/test/compute/test_linode.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_linode.py b/libcloud/test/compute/test_linode.py index 090e4d8..9a35f07 100644 --- a/libcloud/test/compute/test_linode.py +++ b/libcloud/test/compute/test_linode.py @@ -22,7 +22,8 @@ import unittest from libcloud.utils.py3 import httplib from libcloud.compute.drivers.linode import LinodeNodeDriver -from libcloud.compute.base import Node, NodeAuthPassword, NodeAuthSSHKey +from libcloud.compute.base import Node, NodeAuthPassword +from libcloud.compute.base import NodeAuthSSHKey, StorageVolume from libcloud.test import MockHttp from libcloud.test.compute import TestCaseMixin @@ -95,6 +96,31 @@ class LinodeTest(unittest.TestCase, TestCaseMixin): auth=NodeAuthPassword("foobar")) self.assertTrue(isinstance(node, Node)) + def test_destroy_volume(self): + # Will exception on failure + node = self.driver.list_nodes()[0] + volume = StorageVolume(id=55648, name="test", size=1024, + driver=self.driver, extra={"LINODEID": node.id}) + self.driver.destroy_volume(volume) + + def test_ex_create_volume(self): + # should return a StorageVolume object + node = self.driver.list_nodes()[0] + volume = self.driver.ex_create_volume(size=4096, + name="Another test image", + node=node, + fs_type="ext4") + self.assertTrue(isinstance(volume, StorageVolume)) + + def test_ex_list_volumes(self): + # should return list of StorageVolume objects + node = self.driver.list_nodes()[0] + volumes = self.driver.ex_list_volumes(node=node) + + self.assertTrue(isinstance(volumes, list)) + self.assertTrue(isinstance(volumes[0], StorageVolume)) + self.assertEqual(len(volumes), 2) + class LinodeMockHttp(MockHttp): fixtures = ComputeFileFixtures('linode') @@ -115,10 +141,22 @@ class LinodeMockHttp(MockHttp): body = '{"ERRORARRAY":[],"ACTION":"linode.create","DATA":{"LinodeID":8098}}' return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _linode_disk_create(self, method, url, body, headers): + body = '{"ERRORARRAY":[],"ACTION":"linode.disk.create","DATA":{"JobID":1298,"DiskID":55647}}' + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _linode_disk_delete(self, method, url, body, headers): + body = '{"ERRORARRAY":[],"ACTION":"linode.disk.delete","DATA":{"JobID":1298,"DiskID":55648}}' + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _linode_disk_createfromdistribution(self, method, url, body, headers): body = '{"ERRORARRAY":[],"ACTION":"linode.disk.createFromDistribution","DATA":{"JobID":1298,"DiskID":55647}}' return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _linode_disk_list(self, method, url, body, headers): + body = self.fixtures.load('_linode_disk_list.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + def _linode_delete(self, method, url, body, headers): body = '{"ERRORARRAY":[],"ACTION":"linode.delete","DATA":{"LinodeID":8098}}' return (httplib.OK, body, {}, httplib.responses[httplib.OK]) @@ -135,10 +173,6 @@ class LinodeMockHttp(MockHttp): body = self.fixtures.load('_avail_kernels.json') return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - def _linode_disk_create(self, method, url, body, headers): - body = '{"ERRORARRAY":[],"ACTION":"linode.disk.create","DATA":{"JobID":1299,"DiskID":55648}}' - return (httplib.OK, body, {}, httplib.responses[httplib.OK]) - def _linode_boot(self, method, url, body, headers): body = '{"ERRORARRAY":[],"ACTION":"linode.boot","DATA":{"JobID":1300}}' return (httplib.OK, body, {}, httplib.responses[httplib.OK])
