Updated Branches: refs/heads/trunk 8e36cb5ec -> 142a10246
LIBCLOUD-512: Added snapshot lifecycle support and fixture tests to support snapshot listing, creation and deletion. Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/caf6a27f Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/caf6a27f Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/caf6a27f Branch: refs/heads/trunk Commit: caf6a27fc0fbd241c60c30a81f14bb2ae0370ab3 Parents: 71aff98 Author: Chris DeRamus <[email protected]> Authored: Tue Feb 4 08:59:44 2014 -0500 Committer: Chris DeRamus <[email protected]> Committed: Tue Feb 4 09:00:20 2014 -0500 ---------------------------------------------------------------------- libcloud/compute/drivers/openstack.py | 81 +++++++++++++++++++- .../fixtures/openstack_v1.1/_os_snapshots.json | 22 ++++++ .../openstack_v1.1/_os_snapshots_create.json | 11 +++ .../_servers_12086_console_output.json | 3 + libcloud/test/compute/test_openstack.py | 35 +++++++++ 5 files changed, 151 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/libcloud/blob/caf6a27f/libcloud/compute/drivers/openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py index 137dc30..bb5183a 100644 --- a/libcloud/compute/drivers/openstack.py +++ b/libcloud/compute/drivers/openstack.py @@ -35,7 +35,8 @@ from libcloud.common.openstack import OpenStackBaseConnection from libcloud.common.openstack import OpenStackDriverMixin from libcloud.common.types import MalformedResponseError, ProviderError from libcloud.compute.base import NodeSize, NodeImage -from libcloud.compute.base import NodeDriver, Node, NodeLocation, StorageVolume +from libcloud.compute.base import (NodeDriver, Node, NodeLocation, + StorageVolume, VolumeSnapshot) from libcloud.compute.base import KeyPair from libcloud.compute.types import NodeState, Provider from libcloud.compute.types import KeyPairDoesNotExistError @@ -1275,6 +1276,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): volumes = obj['volumes'] return [self._to_volume(volume) for volume in volumes] + def _to_snapshots(self, obj): + snapshots = obj['snapshots'] + return [self._to_snapshot(snapshot) for snapshot in snapshots] + def _to_sizes(self, obj): flavors = obj['flavors'] return [self._to_size(flavor) for flavor in flavors] @@ -1572,6 +1577,49 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): method='DELETE') return resp.status == httplib.ACCEPTED + def ex_list_snapshots(self): + return self._to_snapshots( + self.connection.request('/os-snapshots').object) + + def ex_create_snapshot(self, volume, name, description=None, force=False): + """ + Create a snapshot based off of a volume. + + :param node: volume + :type node: :class:`StorageVolume` + + :keyword name: New name for the volume snapshot + :type name: ``str`` + + :keyword description: Description of the snapshot (optional) + :type description: ``str`` + + :keyword force: Whether to force creation (optional) + :type force: ``bool`` + + :rtype: :class:`VolumeSnapshot` + """ + data = {'snapshot': {'display_name': name, + 'display_description': description, + 'volume_id': volume.id, + 'force': force}} + + return self._to_snapshot(self.connection.request('/os-snapshots', + method='POST', + data=data).object) + + def ex_delete_snapshot(self, snapshot): + """ + Delete a VolumeSnapshot + + :param node: snapshot + :type node: :class:`VolumeSnapshot` + + :rtype: ``bool`` + """ + return self.connection.request('/os-snapshots/%s' % snapshot.id, + method='DELETE').success() + def _to_security_group_rules(self, obj): return [self._to_security_group_rule(security_group_rule) for security_group_rule in obj] @@ -1958,6 +2006,37 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver): } ) + def _to_snapshot(self, api_node): + if 'snapshot' in api_node: + api_node = api_node['snapshot'] + + # RackSpace vs. OpenStack + if 'displayName' in api_node: + return VolumeSnapshot( + id=api_node['id'], + driver=self, + size=api_node['size'], + extra={ + 'volume_id': api_node['volumeId'], + 'name': api_node['displayName'], + 'created': api_node['createdAt'], + 'description': api_node['displayDescription'], + 'status': api_node['status'], + }) + + else: + return VolumeSnapshot( + id=api_node['id'], + driver=self, + size=api_node['size'], + extra={ + 'volume_id': api_node['volume_id'], + 'name': api_node['display_name'], + 'created': api_node['created_at'], + 'description': api_node['display_description'], + 'status': api_node['status'], + }) + def _to_size(self, api_flavor, price=None, bandwidth=None): # if provider-specific subclasses can get better values for # price/bandwidth, then can pass them in when they super(). http://git-wip-us.apache.org/repos/asf/libcloud/blob/caf6a27f/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots.json new file mode 100644 index 0000000..032b553 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots.json @@ -0,0 +1,22 @@ +{ + "snapshots": [ + { + "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", + "display_name": "snap-001", + "display_description": "Daily backup", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "status": "available", + "size": 30, + "created_at": "2012-02-29T03:50:07Z" + }, + { + "id": "e479997c-650b-40a4-9dfe-77655818b0d2", + "display_name": "snap-002", + "display_description": "Weekly backup", + "volume_id": "76b8950a-8594-4e5b-8dce-0dfa9c696358", + "status": "available", + "size": 25, + "created_at": "2012-03-19T01:52:47Z" + } + ] +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/caf6a27f/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots_create.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots_create.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots_create.json new file mode 100644 index 0000000..e1059b5 --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_snapshots_create.json @@ -0,0 +1,11 @@ +{ + "snapshot": { + "id": "3fbbcccf-d058-4502-8844-6feeffdf4cb5", + "display_name": "snap-001", + "display_description": "Daily backup", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "status": "available", + "size": 30, + "created_at": "2012-02-29T03:50:07Z" + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/caf6a27f/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12086_console_output.json ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12086_console_output.json b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12086_console_output.json new file mode 100644 index 0000000..27ffe7d --- /dev/null +++ b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12086_console_output.json @@ -0,0 +1,3 @@ +{ + "output": "FAKE CONSOLE OUTPUT\nANOTHER\nLAST LINE" +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/libcloud/blob/caf6a27f/libcloud/test/compute/test_openstack.py ---------------------------------------------------------------------- diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py index 0df6dd3..0edad6a 100644 --- a/libcloud/test/compute/test_openstack.py +++ b/libcloud/test/compute/test_openstack.py @@ -1524,6 +1524,23 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin): ret = self.driver.ex_resume_node(node) self.assertTrue(ret is True) + def test_ex_list_snapshots(self): + snapshots = self.driver.ex_list_snapshots() + self.assertEqual(len(snapshots), 2) + self.assertEqual(snapshots[0].extra['name'], 'snap-001') + + def test_ex_create_snapshot(self): + volume = self.driver.list_volumes()[0] + ret = self.driver.ex_create_snapshot(volume, + 'Test Volume', + 'This is a test') + self.assertEqual(ret.id, '3fbbcccf-d058-4502-8844-6feeffdf4cb5') + + def test_ex_delete_snapshot(self): + snapshot = self.driver.ex_list_snapshots()[0] + ret = self.driver.ex_delete_snapshot(snapshot) + self.assertTrue(ret is True) + class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests): should_list_locations = False @@ -1862,6 +1879,24 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase): return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + def _v1_1_slug_os_snapshots(self, method, url, body, headers): + if method == "GET": + body = self.fixtures.load('_os_snapshots.json') + elif method == "POST": + body = self.fixtures.load('_os_snapshots_create.json') + else: + raise NotImplementedError() + + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + + def _v1_1_slug_os_snapshots_3fbbcccf_d058_4502_8844_6feeffdf4cb5(self, method, url, body, headers): + if method == "DELETE": + body = '' + else: + raise NotImplementedError() + + return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK]) + # This exists because the nova compute url in devstack has v2 in there but the v1.1 fixtures # work fine.
