You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/07/08 13:05:41 UTC
[4/6] git commit: Issue LIBCLOUD-354: Add support for volume-related
functions to OpenNebula compute driver
Issue LIBCLOUD-354: Add support for volume-related functions to OpenNebula compute driver
Signed-off-by: Tomaz Muraus <to...@apache.org>
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/7a7b2f45
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/7a7b2f45
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/7a7b2f45
Branch: refs/heads/0.13.x
Commit: 7a7b2f4593b8ad02c540cbed37c2473907e7d8eb
Parents: c79d000
Author: Emanuele Rocca <em...@linux.it>
Authored: Mon Jul 1 20:36:19 2013 +0200
Committer: Tomaz Muraus <to...@apache.org>
Committed: Mon Jul 8 12:59:55 2013 +0200
----------------------------------------------------------------------
libcloud/compute/drivers/opennebula.py | 148 +++++++++++++++++--
.../fixtures/opennebula_3_6/compute_15.xml | 17 +++
.../fixtures/opennebula_3_6/compute_5.xml | 22 +++
.../compute/fixtures/opennebula_3_6/disk_10.xml | 7 +
.../compute/fixtures/opennebula_3_6/disk_15.xml | 7 +
.../fixtures/opennebula_3_6/storage_5.xml | 13 ++
libcloud/test/compute/test_opennebula.py | 139 +++++++++++++++++
7 files changed, 342 insertions(+), 11 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/compute/drivers/opennebula.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/opennebula.py b/libcloud/compute/drivers/opennebula.py
index 7700198..da7af07 100644
--- a/libcloud/compute/drivers/opennebula.py
+++ b/libcloud/compute/drivers/opennebula.py
@@ -32,7 +32,7 @@ from libcloud.utils.py3 import b
from libcloud.compute.base import NodeState, NodeDriver, Node, NodeLocation
from libcloud.common.base import ConnectionUserAndKey, XmlResponse
-from libcloud.compute.base import NodeImage, NodeSize
+from libcloud.compute.base import NodeImage, NodeSize, StorageVolume
from libcloud.common.types import InvalidCredsError
from libcloud.compute.providers import Provider
@@ -301,6 +301,8 @@ class OpenNebulaNodeDriver(NodeDriver):
cls = OpenNebula_3_0_NodeDriver
elif api_version in ['3.2']:
cls = OpenNebula_3_2_NodeDriver
+ elif api_version in ['3.6']:
+ cls = OpenNebula_3_6_NodeDriver
elif api_version in ['3.8']:
cls = OpenNebula_3_8_NodeDriver
if 'plain_auth' not in kwargs:
@@ -868,28 +870,35 @@ class OpenNebula_2_0_NodeDriver(OpenNebulaNodeDriver):
@type compute: L{ElementTree}
@param compute: XML representation of a compute node.
- @rtype: L{NodeImage}
- @return: First disk attached to a compute node.
+ @rtype: C{list} of L{NodeImage}
+ @return: Disks attached to a compute node.
"""
disks = list()
for element in compute.findall('DISK'):
disk = element.find('STORAGE')
- disk_id = disk.attrib['href'].partition('/storage/')[2]
+ image_id = disk.attrib['href'].partition('/storage/')[2]
+
+ if 'id' in element.attrib:
+ disk_id = element.attrib['id']
+ else:
+ disk_id = None
disks.append(
- NodeImage(id=disk_id,
+ NodeImage(id=image_id,
name=disk.attrib.get('name', None),
driver=self.connection.driver,
extra={'type': element.findtext('TYPE'),
+ 'disk_id': disk_id,
'target': element.findtext('TARGET')}))
- # @TODO: Return all disks when the Node type accepts multiple
- # attached disks per node.
- if len(disks) > 0:
+ # Return all disks when the Node type accepts multiple attached disks
+ # per node.
+ if len(disks) > 1:
+ return disks
+
+ if len(disks) == 1:
return disks[0]
- else:
- return None
def _extract_size(self, compute):
"""
@@ -1071,7 +1080,124 @@ class OpenNebula_3_2_NodeDriver(OpenNebula_3_0_NodeDriver):
return values
-class OpenNebula_3_8_NodeDriver(OpenNebula_3_2_NodeDriver):
+class OpenNebula_3_6_NodeDriver(OpenNebula_3_2_NodeDriver):
+ """
+ OpenNebula.org node driver for OpenNebula.org v3.6.
+ """
+
+ def create_volume(self, size, name, location=None, snapshot=None):
+ storage = ET.Element('STORAGE')
+
+ vol_name = ET.SubElement(storage, 'NAME')
+ vol_name.text = name
+
+ vol_type = ET.SubElement(storage, 'TYPE')
+ vol_type.text = 'DATABLOCK'
+
+ description = ET.SubElement(storage, 'DESCRIPTION')
+ description.text = 'Attached storage'
+
+ public = ET.SubElement(storage, 'PUBLIC')
+ public.text = 'NO'
+
+ persistent = ET.SubElement(storage, 'PERSISTENT')
+ persistent.text = 'YES'
+
+ fstype = ET.SubElement(storage, 'FSTYPE')
+ fstype.text = 'ext3'
+
+ vol_size = ET.SubElement(storage, 'SIZE')
+ vol_size.text = str(size)
+
+ xml = ET.tostring(storage)
+ volume = self.connection.request('/storage',
+ { 'occixml': xml }, method='POST').object
+
+ return self._to_volume(volume)
+
+ def destroy_volume(self, volume):
+ url = '/storage/%s' % (str(volume.id))
+ resp = self.connection.request(url, method='DELETE')
+
+ return resp.status == httplib.NO_CONTENT
+
+ def attach_volume(self, node, volume, device):
+ action = ET.Element('ACTION')
+
+ perform = ET.SubElement(action, 'PERFORM')
+ perform.text = 'ATTACHDISK'
+
+ params = ET.SubElement(action, 'PARAMS')
+
+ ET.SubElement(params,
+ 'STORAGE',
+ {'href': '/storage/%s' % (str(volume.id))})
+
+ target = ET.SubElement(params, 'TARGET')
+ target.text = device
+
+ xml = ET.tostring(action)
+
+ url = '/compute/%s/action' % node.id
+
+ resp = self.connection.request(url, method='POST', data=xml)
+ return resp.status == httplib.ACCEPTED
+
+ def _do_detach_volume(self, node_id, disk_id):
+ action = ET.Element('ACTION')
+
+ perform = ET.SubElement(action, 'PERFORM')
+ perform.text = 'DETACHDISK'
+
+ params = ET.SubElement(action, 'PARAMS')
+
+ ET.SubElement(params,
+ 'DISK',
+ {'id': disk_id})
+
+ xml = ET.tostring(action)
+
+ url = '/compute/%s/action' % node_id
+
+ resp = self.connection.request(url, method='POST', data=xml)
+ return resp.status == httplib.ACCEPTED
+
+ def detach_volume(self, volume):
+ # We need to find the node using this volume
+ for node in self.list_nodes():
+ if type(node.image) is not list:
+ # This node has only one associated image. It is not the one we
+ # are after.
+ continue
+
+ for disk in node.image:
+ if disk.id == volume.id:
+ # Node found. We can now detach the volume
+ disk_id = disk.extra['disk_id']
+ return self._do_detach_volume(node.id, disk_id)
+
+ return False
+
+ def list_volumes(self):
+ return self._to_volumes(self.connection.request('/storage').object)
+
+ def _to_volume(self, storage):
+ return StorageVolume(id=storage.findtext('ID'),
+ name=storage.findtext('NAME'),
+ size=int(storage.findtext('SIZE')),
+ driver=self.connection.driver)
+
+ def _to_volumes(self, object):
+ volumes = []
+ for storage in object.findall('STORAGE'):
+ storage_id = storage.attrib['href'].partition('/storage/')[2]
+
+ volumes.append(self._to_volume(
+ self.connection.request('/storage/%s' % storage_id).object))
+
+ return volumes
+
+class OpenNebula_3_8_NodeDriver(OpenNebula_3_6_NodeDriver):
"""
OpenNebula.org node driver for OpenNebula.org v3.8.
"""
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/fixtures/opennebula_3_6/compute_15.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/opennebula_3_6/compute_15.xml b/libcloud/test/compute/fixtures/opennebula_3_6/compute_15.xml
new file mode 100644
index 0000000..ce928ec
--- /dev/null
+++ b/libcloud/test/compute/fixtures/opennebula_3_6/compute_15.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE href='http://www.opennebula.org/compute/15'>
+ <ID>15</ID>
+ <NAME>Compute 15 Test</NAME>
+ <INSTANCE_TYPE>small</INSTANCE_TYPE>
+ <STATE>ACTIVE</STATE>
+ <DISK id="0">
+ <STORAGE href="http://www.opennebula.org/storage/10" name="Debian"/>
+ <TYPE>FILE</TYPE>
+ <TARGET>hda</TARGET>
+ </DISK>
+ <NIC>
+ <NETWORK href="http://www.opennebula.org/network/5" name="Small network"/>
+ <IP>192.168.122.2</IP>
+ <MAC>02:00:c0:a8:7a:02</MAC>
+ </NIC>
+</COMPUTE>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/fixtures/opennebula_3_6/compute_5.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/opennebula_3_6/compute_5.xml b/libcloud/test/compute/fixtures/opennebula_3_6/compute_5.xml
new file mode 100644
index 0000000..6767122
--- /dev/null
+++ b/libcloud/test/compute/fixtures/opennebula_3_6/compute_5.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE href='http://www.opennebula.org/compute/5'>
+ <ID>5</ID>
+ <NAME>Compute 5 Test</NAME>
+ <INSTANCE_TYPE>small</INSTANCE_TYPE>
+ <STATE>ACTIVE</STATE>
+ <DISK id="0">
+ <STORAGE href="http://www.opennebula.org/storage/5" name="Conpaas2"/>
+ <TYPE>FILE</TYPE>
+ <TARGET>hda</TARGET>
+ </DISK>
+ <DISK id="2">
+ <STORAGE href="http://www.opennebula.org/storage/15" name="test-volume"/>
+ <TYPE>FILE</TYPE>
+ <TARGET>sda</TARGET>
+ </DISK>
+ <NIC>
+ <NETWORK href="http://www.opennebula.org/network/5" name="Small network"/>
+ <IP>192.168.122.2</IP>
+ <MAC>02:00:c0:a8:7a:02</MAC>
+ </NIC>
+</COMPUTE>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/fixtures/opennebula_3_6/disk_10.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/opennebula_3_6/disk_10.xml b/libcloud/test/compute/fixtures/opennebula_3_6/disk_10.xml
new file mode 100644
index 0000000..1da6fa2
--- /dev/null
+++ b/libcloud/test/compute/fixtures/opennebula_3_6/disk_10.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DISK>
+ <ID>10</ID>
+ <NAME>Debian 7.1 LAMP</NAME>
+ <SIZE>2048</SIZE>
+ <URL>file:///images/debian/wheezy.img</URL>
+</DISK>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/fixtures/opennebula_3_6/disk_15.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/opennebula_3_6/disk_15.xml b/libcloud/test/compute/fixtures/opennebula_3_6/disk_15.xml
new file mode 100644
index 0000000..811369b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/opennebula_3_6/disk_15.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DISK>
+ <ID>15</ID>
+ <NAME>Debian Sid</NAME>
+ <SIZE>1024</SIZE>
+ <URL>file:///images/debian/sid.img</URL>
+</DISK>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/fixtures/opennebula_3_6/storage_5.xml
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/opennebula_3_6/storage_5.xml b/libcloud/test/compute/fixtures/opennebula_3_6/storage_5.xml
new file mode 100644
index 0000000..27aaf73
--- /dev/null
+++ b/libcloud/test/compute/fixtures/opennebula_3_6/storage_5.xml
@@ -0,0 +1,13 @@
+<STORAGE href="http://www.opennebula.org/storage/5">
+ <ID>5</ID>
+ <NAME>test-volume</NAME>
+ <USER href="http://www.opennebula.org/user/0" name="oneadmin"/>
+ <GROUP>oneadmin</GROUP>
+ <STATE>READY</STATE>
+ <TYPE>DATABLOCK</TYPE>
+ <DESCRIPTION>Attached storage</DESCRIPTION>
+ <SIZE>1000</SIZE>
+ <FSTYPE>ext3</FSTYPE>
+ <PUBLIC>NO</PUBLIC>
+ <PERSISTENT>YES</PERSISTENT>
+</STORAGE>
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7a7b2f45/libcloud/test/compute/test_opennebula.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_opennebula.py b/libcloud/test/compute/test_opennebula.py
index 9cee04d..d978a5d 100644
--- a/libcloud/test/compute/test_opennebula.py
+++ b/libcloud/test/compute/test_opennebula.py
@@ -631,6 +631,74 @@ class OpenNebula_3_2_Tests(unittest.TestCase, OpenNebulaCaseMixin):
self.assertEqual(size.bandwidth, None)
self.assertEqual(size.price, None)
+class OpenNebula_3_6_Tests(unittest.TestCase, OpenNebulaCaseMixin):
+ """
+ OpenNebula.org test suite for OpenNebula v3.6.
+ """
+
+ def setUp(self):
+ """
+ Setup test environment.
+ """
+ OpenNebulaNodeDriver.connectionCls.conn_classes = (
+ OpenNebula_3_6_MockHttp, OpenNebula_3_6_MockHttp)
+ self.driver = OpenNebulaNodeDriver(*OPENNEBULA_PARAMS + ('3.6',))
+
+ def test_create_volume(self):
+ new_volume = self.driver.create_volume(1000, 'test-volume')
+
+ self.assertEquals(new_volume.id, '5')
+ self.assertEquals(new_volume.size, 1000)
+ self.assertEquals(new_volume.name, 'test-volume')
+
+ def test_destroy_volume(self):
+ images = self.driver.list_images()
+
+ self.assertEqual(len(images), 2)
+ image = images[0]
+
+ ret = self.driver.destroy_volume(image)
+ self.assertTrue(ret)
+
+ def test_attach_volume(self):
+ nodes = self.driver.list_nodes()
+ node = nodes[0]
+
+ images = self.driver.list_images()
+ image = images[0]
+
+ ret = self.driver.attach_volume(node, image, 'sda')
+ self.assertTrue(ret)
+
+ def test_detach_volume(self):
+ images = self.driver.list_images()
+ image = images[1]
+
+ ret = self.driver.detach_volume(image)
+ self.assertTrue(ret)
+
+ nodes = self.driver.list_nodes()
+ # node with only a single associated image
+ node = nodes[1]
+
+ ret = self.driver.detach_volume(node.image)
+ self.assertFalse(ret)
+
+ def test_list_volumes(self):
+ volumes = self.driver.list_volumes()
+
+ self.assertEqual(len(volumes), 2)
+
+ volume = volumes[0]
+ self.assertEqual(volume.id, '5')
+ self.assertEqual(volume.size, 2048)
+ self.assertEqual(volume.name, 'Ubuntu 9.04 LAMP')
+
+ volume = volumes[1]
+ self.assertEqual(volume.id, '15')
+ self.assertEqual(volume.size, 1024)
+ self.assertEqual(volume.name, 'Debian Sid')
+
class OpenNebula_3_8_Tests(unittest.TestCase, OpenNebulaCaseMixin):
"""
OpenNebula.org test suite for OpenNebula v3.8.
@@ -1069,6 +1137,77 @@ class OpenNebula_3_2_MockHttp(OpenNebula_3_0_MockHttp):
body = self.fixtures_3_2.load('instance_type_collection.xml')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+class OpenNebula_3_6_MockHttp(OpenNebula_3_2_MockHttp):
+ """
+ Mock HTTP server for testing v3.6 of the OpenNebula.org compute driver.
+ """
+
+ fixtures_3_6 = ComputeFileFixtures('opennebula_3_6')
+
+ def _storage(self, method, url, body, headers):
+ if method == 'GET':
+ body = self.fixtures.load('storage_collection.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ if method == 'POST':
+ body = self.fixtures_3_6.load('storage_5.xml')
+ return (httplib.CREATED, body, {},
+ httplib.responses[httplib.CREATED])
+
+ def _compute_5(self, method, url, body, headers):
+ if method == 'GET':
+ body = self.fixtures_3_6.load('compute_5.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ if method == 'PUT':
+ body = ""
+ return (httplib.ACCEPTED, body, {},
+ httplib.responses[httplib.ACCEPTED])
+
+ if method == 'DELETE':
+ body = ""
+ return (httplib.NO_CONTENT, body, {},
+ httplib.responses[httplib.NO_CONTENT])
+
+ def _compute_5_action(self, method, url, body, headers):
+ body = self.fixtures_3_6.load('compute_5.xml')
+ if method == 'POST':
+ return (httplib.ACCEPTED, body, {},
+ httplib.responses[httplib.ACCEPTED])
+
+ if method == 'GET':
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _compute_15(self, method, url, body, headers):
+ if method == 'GET':
+ body = self.fixtures_3_6.load('compute_15.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ if method == 'PUT':
+ body = ""
+ return (httplib.ACCEPTED, body, {},
+ httplib.responses[httplib.ACCEPTED])
+
+ if method == 'DELETE':
+ body = ""
+ return (httplib.NO_CONTENT, body, {},
+ httplib.responses[httplib.NO_CONTENT])
+
+ def _storage_10(self, method, url, body, headers):
+ """
+ Storage entry resource.
+ """
+ if method == 'GET':
+ body = self.fixtures_3_6.load('disk_10.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _storage_15(self, method, url, body, headers):
+ """
+ Storage entry resource.
+ """
+ if method == 'GET':
+ body = self.fixtures_3_6.load('disk_15.xml')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
class OpenNebula_3_8_MockHttp(OpenNebula_3_2_MockHttp):
"""