You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by or...@apache.org on 2010/05/04 23:33:07 UTC

svn commit: r941052 - in /incubator/libcloud/trunk: libcloud/ libcloud/drivers/ test/ test/fixtures/opennebula/

Author: oremj
Date: Tue May  4 21:33:06 2010
New Revision: 941052

URL: http://svn.apache.org/viewvc?rev=941052&view=rev
Log:
LIBCLOUD-17: Add OpenNebula driver.

Author:    Daniel Molina Aranda <da...@pdi.ucm.es>
Signed-off-by: Jeremy Orem <je...@gmail.com>

Added:
    incubator/libcloud/trunk/libcloud/drivers/opennebula.py
    incubator/libcloud/trunk/test/fixtures/opennebula/
    incubator/libcloud/trunk/test/fixtures/opennebula/compute.xml
    incubator/libcloud/trunk/test/fixtures/opennebula/computes.xml
    incubator/libcloud/trunk/test/fixtures/opennebula/disk.xml
    incubator/libcloud/trunk/test/fixtures/opennebula/storage.xml
    incubator/libcloud/trunk/test/test_opennebula.py
Modified:
    incubator/libcloud/trunk/libcloud/providers.py
    incubator/libcloud/trunk/libcloud/types.py
    incubator/libcloud/trunk/test/secrets.py-dist

Added: incubator/libcloud/trunk/libcloud/drivers/opennebula.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/drivers/opennebula.py?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/drivers/opennebula.py (added)
+++ incubator/libcloud/trunk/libcloud/drivers/opennebula.py Tue May  4 21:33:06 2010
@@ -0,0 +1,206 @@
+# Copyright 2002-2009, Distributed Systems Architecture Group, Universidad
+# Complutense de Madrid (dsa-research.org)
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# libcloud.org licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+OpenNebula driver
+"""
+
+from base64 import b64encode
+import hashlib
+from xml.etree import ElementTree as ET
+
+from libcloud.providers import Provider
+from libcloud.types import NodeState, InvalidCredsException
+from libcloud.base import Response, ConnectionUserAndKey
+from libcloud.base import NodeDriver, Node, NodeLocation
+from libcloud.base import NodeImage, NodeSize
+
+
+API_HOST = ''
+API_PORT = (4567, 443)
+API_SECURE = True
+
+
+class OpenNebulaResponse(Response):
+
+    def success(self):
+        i = int(self.status)
+        return i >= 200 and i <= 299
+
+    def parse_body(self):
+        if not self.body:
+            return None
+        return ET.XML(self.body)
+
+    def parse_error(self):
+        if int(self.status) == 401:
+            raise InvalidCredsException(self.body)
+        return self.body
+
+
+class OpenNebulaConnection(ConnectionUserAndKey):
+
+    host = API_HOST
+    port = API_PORT
+    secure = API_SECURE
+    responseCls = OpenNebulaResponse
+
+    def add_default_headers(self, headers):
+        pass_sha1 = hashlib.sha1(self.key).hexdigest()
+        headers['Authorization'] = ("Basic %s" % b64encode("%s:%s" % (self.user_id, pass_sha1)))
+        return headers
+
+
+class OpenNebulaNodeDriver(NodeDriver):
+
+    connectionCls = OpenNebulaConnection
+    type = Provider.OPENNEBULA
+    name = 'OpenNebula'
+
+    NODE_STATE_MAP = {
+        'PENDING': NodeState.PENDING,
+        'ACTIVE': NodeState.RUNNING,
+        'DONE': NodeState.TERMINATED,
+        'STOPPED': NodeState.TERMINATED
+    }
+
+    def list_sizes(self, location=None):
+        return [
+          NodeSize(id=1,
+                   name="small",
+                   ram=None,
+                   disk=None,
+                   bandwidth=None,
+                   price=None,
+                   driver=self),
+          NodeSize(id=2,
+                   name="medium",
+                   ram=None,
+                   disk=None,
+                   bandwidth=None,
+                   price=None,
+                   driver=self),
+          NodeSize(id=3,
+                   name="large",
+                   ram=None,
+                   disk=None,
+                   bandwidth=None,
+                   price=None,
+                   driver=self),
+        ]
+
+    def list_nodes(self):
+        return self._to_nodes(self.connection.request('/compute').object)
+
+    def list_images(self, location=None):
+        return self._to_images(self.connection.request('/storage').object)
+
+    def list_locations(self):
+        return [NodeLocation(0,  'OpenNebula', 'ONE', self)]
+
+    def reboot_node(self, node):
+        compute_id = str(node.id)
+
+        url = '/compute/%s' % compute_id
+        resp1 = self.connection.request(url,method='PUT',data=self._xml_action(compute_id,'STOPPED'))
+
+        if resp1.status == 400:
+            return False
+
+        resp2 = self.connection.request(url,method='PUT',data=self._xml_action(compute_id,'RESUME'))
+
+        if resp2.status == 400:
+            return False
+
+        return True
+
+    def destroy_node(self, node):
+        url = '/compute/%s' % (str(node.id))
+        resp = self.connection.request(url,method='DELETE')
+
+        return resp.status == 204
+
+    def create_node(self, **kwargs):
+        compute = ET.Element('COMPUTE')
+
+        name = ET.SubElement(compute, 'NAME')
+        name.text = kwargs['name']
+
+        instance_type = ET.SubElement(compute, 'INSTANCE_TYPE')
+        instance_type.text = kwargs['size'].name
+
+        storage = ET.SubElement(compute, 'STORAGE')
+        disk = ET.SubElement(storage, 'DISK', {'image': str(kwargs['image'].id),
+                                               'dev': 'sda1'})
+
+        xml = ET.tostring(compute)
+
+        node = self.connection.request('/compute',method='POST',data=xml).object
+
+        return self._to_node(node)
+
+    def _to_images(self, object):
+        images = []
+        for element in object.findall("DISK"):
+            image_id = element.attrib["href"].partition("/storage/")[2]
+            image = self.connection.request(("/storage/%s" % (image_id))).object
+            images.append(self._to_image(image))
+
+        return images
+
+    def _to_image(self, image):
+        return NodeImage(id=image.findtext("ID"),
+                         name=image.findtext("NAME"),
+                         driver=self.connection.driver)
+
+    def _to_nodes(self, object):
+        computes = []
+        for element in object.findall("COMPUTE"):
+            compute_id = element.attrib["href"].partition("/compute/")[2]
+            compute = self.connection.request(("/compute/%s" % (compute_id))).object
+            computes.append(self._to_node(compute))
+
+        return computes
+
+    def _to_node(self, compute):
+        try:
+            state = self.NODE_STATE_MAP[compute.findtext("STATE")]
+        except KeyError:
+            state = NodeState.UNKNOWN
+
+        networks = []
+        for element in compute.findall("NIC"):
+            networks.append(element.attrib["ip"])
+
+        return Node(id=compute.findtext("ID"),
+                    name=compute.findtext("NAME"),
+                    state=state,
+                    public_ip=networks,
+                    private_ip=[],
+                    driver=self.connection.driver)
+
+    def _xml_action(self, compute_id, action):
+        compute = ET.Element('COMPUTE')
+
+        compute_id = ET.SubElement(compute, 'ID')
+        compute_id.text = str(compute_id)
+
+        state = ET.SubElement(compute, 'STATE')
+        state.text = action
+
+        xml = ET.tostring(compute)
+        return xml

Modified: incubator/libcloud/trunk/libcloud/providers.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/providers.py?rev=941052&r1=941051&r2=941052&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/providers.py (original)
+++ incubator/libcloud/trunk/libcloud/providers.py Tue May  4 21:33:06 2010
@@ -49,6 +49,8 @@ DRIVERS = {
         ('libcloud.drivers.ec2', 'EucNodeDriver'),
     Provider.IBM:
         ('libcloud.drivers.ibm', 'IBMNodeDriver'),
+    Provider.OPENNEBULA:
+        ('libcloud.drivers.opennebula', 'OpenNebulaNodeDriver'),
 }
 
 def get_driver(provider):

Modified: incubator/libcloud/trunk/libcloud/types.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/types.py?rev=941052&r1=941051&r2=941052&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/types.py (original)
+++ incubator/libcloud/trunk/libcloud/types.py Tue May  4 21:33:06 2010
@@ -32,6 +32,7 @@ class Provider(object):
     @cvar VCLOUD: vmware vCloud
     @cvar RIMUHOSTING: RimuHosting.com
     @cvar ECP: Enomaly
+    @cvar OPENNEBULA: OpenNebula.org
     """
     DUMMY = 0
     EC2 = 1  # deprecated name
@@ -51,6 +52,7 @@ class Provider(object):
     EUCALYPTUS = 13
     ECP = 14
     IBM = 15
+    OPENNEBULA = 16
 
 class NodeState(object):
     """

Added: incubator/libcloud/trunk/test/fixtures/opennebula/compute.xml
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/opennebula/compute.xml?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/opennebula/compute.xml (added)
+++ incubator/libcloud/trunk/test/fixtures/opennebula/compute.xml Tue May  4 21:33:06 2010
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE>
+    <ID>5</ID>
+    <NAME>MyCompute</NAME>
+    <STATE>ACTIVE</STATE>
+    <STORAGE>
+        <DISK type="disk" href="devel.cloud.opennebula.org/storage/1" dev="sda1"/>
+        <DISK type="fs" size="512" format="ext3" dev="sda3"/>
+        <DISK type="swap" size="1024" dev="sda2"/>           
+    </STORAGE>  
+    <NETWORK>
+        <NIC href="devel.cloud.opennebula.org/network/3" ip="192.168.1.4"/>
+    </NETWORK>
+    <INSTANCE_TYPE>small</INSTANCE_TYPE>
+</COMPUTE>
\ No newline at end of file

Added: incubator/libcloud/trunk/test/fixtures/opennebula/computes.xml
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/opennebula/computes.xml?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/opennebula/computes.xml (added)
+++ incubator/libcloud/trunk/test/fixtures/opennebula/computes.xml Tue May  4 21:33:06 2010
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTES>  
+    <COMPUTE href="devel.cloud.opennebula.org/compute/5"/>  
+    <COMPUTE href="devel.cloud.opennebula.org/compute/15"/>
+</COMPUTES>
\ No newline at end of file

Added: incubator/libcloud/trunk/test/fixtures/opennebula/disk.xml
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/opennebula/disk.xml?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/opennebula/disk.xml (added)
+++ incubator/libcloud/trunk/test/fixtures/opennebula/disk.xml Tue May  4 21:33:06 2010
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DISK>
+    <ID>1</ID>
+    <NAME>UbuntuServer9.04-Contextualized</NAME>
+    <SIZE>5120</SIZE>
+    <URL>file:///Users/oneuser/ubuntu-server-9.04/ubuntu-server-9.04.img</URL>
+</DISK>
\ No newline at end of file

Added: incubator/libcloud/trunk/test/fixtures/opennebula/storage.xml
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/fixtures/opennebula/storage.xml?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/fixtures/opennebula/storage.xml (added)
+++ incubator/libcloud/trunk/test/fixtures/opennebula/storage.xml Tue May  4 21:33:06 2010
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<STORAGE>
+    <DISK href="devel.cloud.opennebula.org/storage/1"/>
+    <DISK href="devel.cloud.opennebula.org/storage/8"/>
+</STORAGE>
\ No newline at end of file

Modified: incubator/libcloud/trunk/test/secrets.py-dist
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/secrets.py-dist?rev=941052&r1=941051&r2=941052&view=diff
==============================================================================
--- incubator/libcloud/trunk/test/secrets.py-dist (original)
+++ incubator/libcloud/trunk/test/secrets.py-dist Tue May  4 21:33:06 2010
@@ -50,3 +50,6 @@ ECP_PASSWORD=''
 
 IBM_USER=''
 IBM_SECRET=''
+
+OPENNEBULA_USER=''
+OPENNEBULA_KEY=''

Added: incubator/libcloud/trunk/test/test_opennebula.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/test/test_opennebula.py?rev=941052&view=auto
==============================================================================
--- incubator/libcloud/trunk/test/test_opennebula.py (added)
+++ incubator/libcloud/trunk/test/test_opennebula.py Tue May  4 21:33:06 2010
@@ -0,0 +1,122 @@
+# Copyright 2002-2009, Distributed Systems Architecture Group, Universidad
+# Complutense de Madrid (dsa-research.org)
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# libcloud.org licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import sys
+import unittest
+
+from libcloud.drivers.opennebula import OpenNebulaNodeDriver
+from libcloud.base import Node, NodeImage, NodeSize
+
+from test import MockHttp, TestCaseMixin
+from test.file_fixtures import FileFixtures
+
+import httplib
+
+from secrets import OPENNEBULA_USER, OPENNEBULA_KEY
+
+class OpenNebulaTests(unittest.TestCase, TestCaseMixin):
+
+    def setUp(self):
+        OpenNebulaNodeDriver.connectionCls.conn_classes = (None, OpenNebulaMockHttp)
+        self.driver = OpenNebulaNodeDriver(OPENNEBULA_USER, OPENNEBULA_KEY)
+
+    def test_create_node(self):
+        image = NodeImage(id=1, name='UbuntuServer9.04-Contextualized', driver=self.driver)
+        size = NodeSize(1, 'small', None, None, None, None, driver=self.driver)
+        node = self.driver.create_node(name='MyCompute', image=image, size=size)
+        self.assertEqual(node.id, '5')
+        self.assertEqual(node.name, 'MyCompute')
+
+    def test_list_nodes(self):
+        nodes = self.driver.list_nodes()
+        self.assertEqual(len(nodes), 2)
+        node = nodes[0]
+        self.assertEqual(node.id, '5')
+        self.assertEqual(node.name, 'MyCompute')
+
+    def test_reboot_node(self):
+        node = Node(5, None, None, None, None, self.driver)
+        ret = self.driver.reboot_node(node)
+        self.assertTrue(ret)
+
+    def test_destroy_node(self):
+        node = Node(5, None, None, None, None, self.driver)
+        ret = self.driver.destroy_node(node)
+        self.assertTrue(ret)
+
+    def test_list_sizes(self):
+        sizes = self.driver.list_sizes()
+        self.assertEqual(len(sizes), 3)
+        self.assertTrue('small' in [ s.name for s in sizes])
+        self.assertTrue('medium' in [ s.name for s in sizes])
+        self.assertTrue('large' in [ s.name for s in sizes])
+
+    def test_list_images(self):
+        images = self.driver.list_images()
+        self.assertEqual(len(images), 2)
+        image = images[0]
+        self.assertEqual(image.id, '1')
+        self.assertEqual(image.name, 'UbuntuServer9.04-Contextualized')
+
+class OpenNebulaMockHttp(MockHttp):
+
+    fixtures = FileFixtures('opennebula')
+    
+    def _compute(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('computes.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+            
+        if method == 'POST':
+            body = self.fixtures.load('compute.xml')
+            return (httplib.CREATED, body, {}, httplib.responses[httplib.CREATED])
+
+    def _storage(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('storage.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _compute_5(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('compute.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_15(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('compute.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _storage_1(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('disk.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+            
+    def _storage_8(self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('disk.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())
\ No newline at end of file