libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject [1/4] git commit: Issue LIBCLOUD-353: Implement storageVolume methods for openstack driver.
Date Thu, 11 Jul 2013 15:37:31 GMT
Updated Branches:
  refs/heads/0.13.x 36f8a94d2 -> b3a7467bf
  refs/heads/trunk c0cc8f986 -> b44a0f658


Issue LIBCLOUD-353: Implement storageVolume methods for openstack driver.

Signed-off-by: Tomaz Muraus <tomaz@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/91159b3e
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/91159b3e
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/91159b3e

Branch: refs/heads/trunk
Commit: 91159b3e094eb4b8f50c89d475f897cda217b2c3
Parents: c0cc8f9
Author: Bernard Kerckenaere <bernieke@bernieke.com>
Authored: Fri Jun 28 15:14:59 2013 +0200
Committer: Tomaz Muraus <tomaz@apache.org>
Committed: Thu Jul 11 15:57:55 2013 +0200

----------------------------------------------------------------------
 libcloud/compute/drivers/openstack.py           | 87 +++++++++++++++++++-
 libcloud/test/compute/__init__.py               | 12 ++-
 .../fixtures/openstack_v1.1/_os_volumes.json    | 39 +++++++++
 ...es_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json | 22 +++++
 .../openstack_v1.1/_os_volumes_create.json      | 17 ++++
 .../_servers_12065_os_volume_attachments.json   |  8 ++
 libcloud/test/compute/test_openstack.py         | 80 +++++++++++++++++-
 7 files changed, 261 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/compute/drivers/openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/openstack.py b/libcloud/compute/drivers/openstack.py
index 8c2401a..4904443 100644
--- a/libcloud/compute/drivers/openstack.py
+++ b/libcloud/compute/drivers/openstack.py
@@ -34,10 +34,10 @@ from xml.etree import ElementTree as ET
 
 from libcloud.common.openstack import OpenStackBaseConnection
 from libcloud.common.openstack import OpenStackDriverMixin
-from libcloud.common.types import MalformedResponseError
+from libcloud.common.types import MalformedResponseError, ProviderError
 from libcloud.compute.types import NodeState, Provider
 from libcloud.compute.base import NodeSize, NodeImage
-from libcloud.compute.base import NodeDriver, Node, NodeLocation
+from libcloud.compute.base import NodeDriver, Node, NodeLocation, StorageVolume
 from libcloud.pricing import get_size_price
 from libcloud.common.base import Response
 from libcloud.utils.xml import findall
@@ -59,6 +59,10 @@ ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"
 DEFAULT_API_VERSION = '1.1'
 
 
+class OpenStackException(ProviderError):
+    pass
+
+
 class OpenStackResponse(Response):
     node_driver = None
 
@@ -206,6 +210,67 @@ class OpenStackNodeDriver(NodeDriver, OpenStackDriverMixin):
         return self._to_nodes(
             self.connection.request('/servers/detail').object)
 
+    def create_volume(self, size, name, location=None, snapshot=None):
+        if snapshot:
+            raise NotImplementedError(
+                "create_volume does not yet support create from snapshot")
+        return self.connection.request('/os-volumes',
+                                       method='POST',
+                                       data={
+                                           'volume': {
+                                               'display_name': name,
+                                               'display_description': name,
+                                               'size': size,
+                                               'volume_type': None,
+                                               'metadata': {
+                                                   'contents': name,
+                                               },
+                                               'availability_zone': location,
+                                           }
+                                       }).success()
+
+    def destroy_volume(self, volume):
+        return self.connection.request('/os-volumes/%s' % volume.id,
+                                       method='DELETE').success()
+
+    def attach_volume(self, node, volume, device="auto"):
+        # when "auto" or None is provided for device, openstack will let
+        # the guest OS pick the next available device (fi. /dev/vdb)
+        return self.connection.request(
+            '/servers/%s/os-volume_attachments' % node.id,
+            method='POST',
+            data={
+                'volumeAttachment': {
+                    'volumeId': volume.id,
+                    'device': device,
+                }
+            }).success()
+
+    def detach_volume(self, volume, ex_node=None):
+        # when ex_node is not provided, volume is detached from all nodes
+        failed_nodes = []
+        for attachment in volume.extra['attachments']:
+            if not ex_node or ex_node.id == attachment['serverId']:
+                if not self.connection.request(
+                    '/servers/%s/os-volume_attachments/%s' %
+                    (attachment['serverId'], attachment['id']),
+                    method='DELETE').success():
+                    failed_nodes.append(attachment['serverId'])
+        if failed_nodes:
+            raise OpenStackException(
+                'detach_volume failed for nodes with id: %s' %
+                ', '.join(failed_nodes), 500, self
+            )
+        return True
+
+    def list_volumes(self):
+        return self._to_volumes(
+            self.connection.request('/os-volumes').object)
+
+    def ex_get_volume(self, volumeId):
+        return self._to_volume(
+            self.connection.request('/os-volumes/%s' % volumeId).object)
+
     def list_images(self, location=None, ex_only_active=True):
         """
         @inherits: L{NodeDriver.list_images}
@@ -1153,6 +1218,10 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
         servers = obj['servers']
         return [self._to_node(server) for server in servers]
 
+    def _to_volumes(self, obj):
+        volumes = obj['volumes']
+        return [self._to_volume(volume) for volume in volumes]
+
     def _to_sizes(self, obj):
         flavors = obj['flavors']
         return [self._to_size(flavor) for flavor in flavors]
@@ -1658,6 +1727,20 @@ class OpenStack_1_1_NodeDriver(OpenStackNodeDriver):
             ),
         )
 
+    def _to_volume(self, api_node):
+        if 'volume' in api_node:
+            api_node = api_node['volume']
+        return StorageVolume(
+            id=api_node['id'],
+            name=api_node['displayName'],
+            size=api_node['size'],
+            driver=self,
+            extra={
+                'description': api_node['displayDescription'],
+                'attachments': [att for att in api_node['attachments'] if att],
+            }
+        )
+
     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/91159b3e/libcloud/test/compute/__init__.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/__init__.py b/libcloud/test/compute/__init__.py
index ae754cf..1ac4a11 100644
--- a/libcloud/test/compute/__init__.py
+++ b/libcloud/test/compute/__init__.py
@@ -13,13 +13,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from libcloud.compute.base import Node, NodeImage, NodeLocation
+from libcloud.compute.base import Node, NodeImage, NodeLocation, StorageVolume
 from libcloud.pricing import get_pricing
 
 
 class TestCaseMixin(object):
     should_list_locations = True
     should_have_pricing = False
+    should_list_volumes = False
 
     def test_list_nodes_response(self):
         nodes = self.driver.list_nodes()
@@ -46,6 +47,15 @@ class TestCaseMixin(object):
         for image in images:
             self.assertTrue(isinstance(image, NodeImage))
 
+    def test_list_volumes_response(self):
+        if not self.should_list_volumes:
+            return None
+
+        volumes = self.driver.list_volumes()
+        self.assertTrue(isinstance(volumes, list))
+        for volume in volumes:
+            self.assertTrue(isinstance(volume, StorageVolume))
+
     def test_list_locations_response(self):
         if not self.should_list_locations:
             return None

http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes.json
new file mode 100644
index 0000000..d92e3e8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes.json
@@ -0,0 +1,39 @@
+{
+    "volumes": [
+        {
+            "attachments": [
+                {
+                    "device": "/dev/vdb",
+                    "id": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d",
+                    "serverId": "12065",
+                    "volumeId": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d"
+                }
+            ],
+            "availabilityZone": "nova",
+            "createdAt": "2013-06-24T11:20:13.000000",
+            "displayDescription": "",
+            "displayName": "test_volume_2",
+            "id": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d",
+            "metadata": {},
+            "size": 2,
+            "snapshotId": null,
+            "status": "available",
+            "volumeType": "None"
+        },
+        {
+            "attachments": [
+                {}
+            ],
+            "availabilityZone": "nova",
+            "createdAt": "2013-06-21T12:39:02.000000",
+            "displayDescription": "some description",
+            "displayName": "test_volume",
+            "id": "cfcec3bc-b736-4db5-9535-4c24112691b5",
+            "metadata": {},
+            "size": 50,
+            "snapshotId": null,
+            "status": "available",
+            "volumeType": "None"
+        }
+    ]
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json
b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json
new file mode 100644
index 0000000..b2c580f
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json
@@ -0,0 +1,22 @@
+{
+    "volume": {
+        "attachments": [
+            {
+                "device": "/dev/vdb",
+                "id": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d",
+                "serverId": "12065",
+                "volumeId": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d"
+            }
+        ],
+        "availabilityZone": "nova",
+        "createdAt": "2013-06-24T11:20:13.000000",
+        "displayDescription": "",
+        "displayName": "test_volume_2",
+        "id": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d",
+        "metadata": {},
+        "size": 2,
+        "snapshotId": null,
+        "status": "in-use",
+        "volumeType": "None"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_create.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_create.json b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_create.json
new file mode 100644
index 0000000..d1a0cdf
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_os_volumes_create.json
@@ -0,0 +1,17 @@
+{
+    "volume": {
+        "attachments": [
+            {}
+        ],
+        "availabilityZone": "nova",
+        "createdAt": "2013-06-28T12:22:39.616660",
+        "displayDescription": null,
+        "displayName": "test",
+        "id": "43b7db44-0497-40fa-b817-c906f13bbea3",
+        "metadata": {},
+        "size": 1,
+        "snapshotId": null,
+        "status": "creating",
+        "volumeType": "None"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12065_os_volume_attachments.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12065_os_volume_attachments.json
b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12065_os_volume_attachments.json
new file mode 100644
index 0000000..c381384
--- /dev/null
+++ b/libcloud/test/compute/fixtures/openstack_v1.1/_servers_12065_os_volume_attachments.json
@@ -0,0 +1,8 @@
+{
+    "volumeAttachment": {
+        "device": "/dev/vdb",
+        "id": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d",
+        "serverId": "12065",
+        "volumeId": "cd76a3a1-c4ce-40f6-9b9f-07a61508938d"
+    }
+}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/91159b3e/libcloud/test/compute/test_openstack.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_openstack.py b/libcloud/test/compute/test_openstack.py
index 09ce48d..730dced 100644
--- a/libcloud/test/compute/test_openstack.py
+++ b/libcloud/test/compute/test_openstack.py
@@ -39,7 +39,7 @@ from libcloud.compute.drivers.openstack import (
     OpenStack_1_0_NodeDriver, OpenStack_1_0_Response,
     OpenStack_1_1_NodeDriver, OpenStackSecurityGroup, OpenStackSecurityGroupRule
 )
-from libcloud.compute.base import Node, NodeImage, NodeSize
+from libcloud.compute.base import Node, NodeImage, NodeSize, StorageVolume
 from libcloud.pricing import set_pricing, clear_pricing_data
 
 from libcloud.test import MockResponse, MockHttpTestCase, XML_HEADERS
@@ -226,6 +226,7 @@ class OpenStackAuthConnectionTests(unittest.TestCase):
 
 class OpenStack_1_0_Tests(unittest.TestCase, TestCaseMixin):
     should_list_locations = False
+    should_list_volumes = False
 
     driver_klass = OpenStack_1_0_NodeDriver
     driver_args = OPENSTACK_PARAMS
@@ -529,6 +530,7 @@ class OpenStack_1_0_Tests(unittest.TestCase, TestCaseMixin):
 
 class OpenStack_1_0_FactoryMethodTests(OpenStack_1_0_Tests):
     should_list_locations = False
+    should_list_volumes = False
 
     driver_klass = OpenStack_1_0_NodeDriver
     driver_type = get_driver(Provider.OPENSTACK)
@@ -698,6 +700,7 @@ class OpenStackMockHttp(MockHttpTestCase):
 
 class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
     should_list_locations = False
+    should_list_volumes = True
 
     driver_klass = OpenStack_1_1_NodeDriver
     driver_type = OpenStack_1_1_NodeDriver
@@ -823,6 +826,26 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
         self.assertEqual(node.extra['updated'], '2011-10-11T00:50:04Z')
         self.assertEqual(node.extra['created'], '2011-10-11T00:51:39Z')
 
+    def test_list_volumes(self):
+        volumes = self.driver.list_volumes()
+        self.assertEqual(len(volumes), 2)
+        volume = volumes[0]
+
+        self.assertEqual('cd76a3a1-c4ce-40f6-9b9f-07a61508938d', volume.id)
+        self.assertEqual('test_volume_2', volume.name)
+        self.assertEqual(2, volume.size)
+
+        self.assertEqual(volume.extra['description'], '')
+        self.assertEqual(volume.extra['attachments'][0]['id'], 'cd76a3a1-c4ce-40f6-9b9f-07a61508938d')
+
+        volume = volumes[1]
+        self.assertEqual('cfcec3bc-b736-4db5-9535-4c24112691b5', volume.id)
+        self.assertEqual('test_volume', volume.name)
+        self.assertEqual(50, volume.size)
+
+        self.assertEqual(volume.extra['description'], 'some description')
+        self.assertEqual(volume.extra['attachments'], [])
+
     def test_list_sizes(self):
         sizes = self.driver.list_sizes()
         self.assertEqual(len(sizes), 8, 'Wrong sizes count')
@@ -891,6 +914,24 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
     def test_reboot_node(self):
         self.assertTrue(self.node.reboot())
 
+    def test_create_volume(self):
+        self.assertEqual(self.driver.create_volume(1, 'test'), True)
+
+    def test_destroy_volume(self):
+        volume = self.driver.ex_get_volume('cd76a3a1-c4ce-40f6-9b9f-07a61508938d')
+        self.assertEqual(self.driver.destroy_volume(volume), True)
+
+    def test_attach_volume(self):
+        node = self.driver.list_nodes()[0]
+        volume = self.driver.ex_get_volume('cd76a3a1-c4ce-40f6-9b9f-07a61508938d')
+        self.assertEqual(self.driver.attach_volume(node, volume, '/dev/sdb'), True)
+
+    def test_detach_volume(self):
+        node = self.driver.list_nodes()[0]
+        volume = self.driver.ex_get_volume('cd76a3a1-c4ce-40f6-9b9f-07a61508938d')
+        self.assertEqual(self.driver.attach_volume(node, volume, '/dev/sdb'), True)
+        self.assertEqual(self.driver.detach_volume(volume), True)
+
     def test_ex_set_password(self):
         try:
             self.driver.ex_set_password(self.node, 'New1&53jPass')
@@ -1102,6 +1143,7 @@ class OpenStack_1_1_Tests(unittest.TestCase, TestCaseMixin):
 
 class OpenStack_1_1_FactoryMethodTests(OpenStack_1_1_Tests):
     should_list_locations = False
+    should_list_volumes = True
 
     driver_klass = OpenStack_1_1_NodeDriver
     driver_type = get_driver(Provider.OPENSTACK)
@@ -1291,6 +1333,42 @@ class OpenStack_1_1_MockHttp(MockHttpTestCase):
         else:
             raise NotImplementedError()
 
+    def _v1_1_slug_os_volumes(self, method, url, body, headers):
+        if method == "GET":
+            body = self.fixtures.load('_os_volumes.json')
+        elif method == "POST":
+            body = self.fixtures.load('_os_volumes_create.json')
+        else:
+            raise NotImplementedError()
+
+        return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
+
+    def _v1_1_slug_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d(self, method, url, body,
headers):
+        if method == "GET":
+            body = self.fixtures.load('_os_volumes_cd76a3a1_c4ce_40f6_9b9f_07a61508938d.json')
+        elif method == "DELETE":
+            body = ''
+        else:
+            raise NotImplementedError()
+
+        return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
+
+    def _v1_1_slug_servers_12065_os_volume_attachments(self, method, url, body, headers):
+        if method == "POST":
+            body = self.fixtures.load('_servers_12065_os_volume_attachments.json')
+        else:
+            raise NotImplementedError()
+
+        return (httplib.OK, body, self.json_content_headers, httplib.responses[httplib.OK])
+
+    def _v1_1_slug_servers_12065_os_volume_attachments_cd76a3a1_c4ce_40f6_9b9f_07a61508938d(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.


Mime
View raw message