=== modified file 'libcloud/compute/base.py'
--- libcloud/compute/base.py	2011-10-17 15:20:59 +0000
+++ libcloud/compute/base.py	2011-10-21 21:47:58 +0000
@@ -219,7 +219,7 @@
     4
     """
 
-    def __init__(self, id, name, ram, disk, bandwidth, price, driver, cpu=None, vcpu=None):
+    def __init__(self, id, name, ram, disk, bandwidth, price, driver):
         self.id = str(id)
         self.name = name
         self.ram = ram
@@ -227,12 +227,10 @@
         self.bandwidth = bandwidth
         self.price = price
         self.driver = driver
-        self.cpu = cpu
-        self.vcpu = vcpu
 
     def __repr__(self):
-        return (('<NodeSize: id=%s name=%s ram=%s disk=%s bandwidth=%s '
-                 'price=%s driver=%s ...>')
+        return (('<NodeSize: id=%s, name=%s, ram=%s, disk=%s, bandwidth=%s, '
+                 'price=%s, driver=%s ...>')
                 % (self.id, self.name, self.ram, self.disk, self.bandwidth,
                    self.price, self.driver.name))
 
@@ -271,6 +269,33 @@
                 % (self.id, self.name, self.driver.name))
 
 
+class NodeNetwork(object):
+    """
+    A virtual network.
+
+    NodeNetwork objects are analogous to physical switches connecting 2 or more physical nodes together.
+
+    >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+    >>> driver = DummyNodeDriver(0)
+    >>> network = driver.list_networks()[0]
+    >>> network.name
+    'BlueNetwork'
+
+    Apart from name and id, there is no further standard information;
+    other parameters are stored in a driver specific "extra" variable
+    """
+
+    def __init__(self, id, name, driver, extra=None):
+        self.id = str(id)
+        self.name = name
+        self.driver = driver
+        self.extra = extra or {}
+
+    def __repr__(self):
+        return (('<NodeNetwork: id=%s, name=%s, driver=%s ...>')
+                % (self.id, self.name, self.driver.name))
+
+
 class NodeLocation(object):
     """
     A physical location where nodes can be.
@@ -386,7 +411,8 @@
             'create_node not implemented for this driver')
 
     def destroy_node(self, node):
-        """Destroy a node.
+        """
+        Destroy a node.
 
         Depending upon the provider, this may destroy all data associated with
         the node, including backups.
@@ -436,6 +462,14 @@
         raise NotImplementedError(
             'list_locations not implemented for this driver')
 
+    def list_networks(self, location=None):
+        """
+        List virtual networks on a provider
+        @return: C{list} of L{NodeNetwork} objects
+        """
+        raise NotImplementedError(
+            'list_networks not implemented for this driver')
+
     def deploy_node(self, **kwargs):
         """
         Create a new node, and start deployment.

=== modified file 'libcloud/compute/drivers/opennebula.py'
--- libcloud/compute/drivers/opennebula.py	2011-10-20 18:32:22 +0000
+++ libcloud/compute/drivers/opennebula.py	2011-10-22 18:32:05 +0000
@@ -15,6 +15,9 @@
 # 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
+"""
 
 try:
     import simplejson as json
@@ -24,21 +27,24 @@
 from xml.etree import ElementTree as ET
 from base64 import b64encode
 import hashlib
-import sys
+import httplib
 
 from libcloud.compute.base import NodeState, NodeDriver, Node, NodeLocation
 from libcloud.common.base import ConnectionUserAndKey, Response
 from libcloud.compute.base import NodeImage, NodeSize
 from libcloud.common.types import InvalidCredsError
 from libcloud.compute.providers import Provider
-from libcloud.common.base import Response
 
 API_HOST = ''
 API_PORT = (4567, 443)
 API_SECURE = True
 DEFAULT_API_VERSION = '3.0'
 
+
 class OpenNebulaResponse(Response):
+    """
+    Response class for the OpenNebula driver.
+    """
 
     def success(self):
         i = int(self.status)
@@ -50,14 +56,15 @@
         return ET.XML(self.body)
 
     def parse_error(self):
-        if int(self.status) == 401:
+        if self.status == httplib.UNAUTHORIZED:
             raise InvalidCredsError(self.body)
+
         return self.body
 
 
 class OpenNebulaConnection(ConnectionUserAndKey):
     """
-    Connection class for the OpenNebula driver
+    Connection class for the OpenNebula driver.
     """
 
     host = API_HOST
@@ -67,35 +74,57 @@
 
     def add_default_headers(self, headers):
         pass_sha1 = hashlib.sha1(self.key).hexdigest()
-        headers['Authorization'] = ("Basic %s" % b64encode("%s:%s" %
+        headers['Authorization'] = ('Basic %s' % b64encode('%s:%s' %
                                                 (self.user_id, pass_sha1)))
         return headers
 
 
+class OpenNebulaNodeSize(NodeSize):
+
+    def __init__(self, id, name, ram, disk, bandwidth, price, driver,
+        cpu=None, vcpu=None):
+
+        self.id = str(id)
+        self.name = name
+        self.ram = ram
+        self.disk = disk
+        self.bandwidth = bandwidth
+        self.price = price
+        self.driver = driver
+        self.cpu = cpu
+        self.vcpu = vcpu
+
+    def __repr__(self):
+        return (('<NodeSize: id=%s, name=%s, ram=%s, disk=%s, bandwidth=%s, '
+                 'price=%s, driver=%s, cpu=%s ...>')
+                % (self.id, self.name, self.ram, self.disk, self.bandwidth,
+                   self.price, self.driver.name, self.cpu))
+
+
 class OpenNebulaNodeDriver(NodeDriver):
     """
-    OpenNebula node driver
+    OpenNebula node driver.
     """
 
     connectionCls = OpenNebulaConnection
+    name = 'OpenNebula'
     type = Provider.OPENNEBULA
-    name = 'OpenNebula'
 
     NODE_STATE_MAP = {
-        'pending': NodeState.PENDING,
-        'hold': NodeState.PENDING,
-        'prolog': NodeState.PENDING,
-        'running': NodeState.RUNNING,
-        'migrate': NodeState.PENDING,
-        'epilog': NodeState.TERMINATED,
-        'stopped': NodeState.TERMINATED,
-        'suspended': NodeState.PENDING,
-        'failed': NodeState.TERMINATED,
-        'unknown': NodeState.UNKNOWN,
-        'done': NodeState.TERMINATED,
+        'PENDING': NodeState.PENDING,
+        'HOLD': NodeState.PENDING,
+        'PROLOG': NodeState.PENDING,
+        'RUNNING': NodeState.RUNNING,
+        'MIGRATE': NodeState.PENDING,
+        'EPILOG': NodeState.TERMINATED,
+        'STOPPED': NodeState.TERMINATED,
+        'SUSPENDED': NodeState.PENDING,
+        'FAILED': NodeState.TERMINATED,
+        'UNKNOWN': NodeState.UNKNOWN,
+        'DONE': NodeState.TERMINATED,
     }
 
-    def __new__(cls, key, secret=None, api_version=DEFAULT_API_VERSION,
+    def __new__(cls, key, api_version=DEFAULT_API_VERSION,
                 **kwargs):
         if cls is OpenNebulaNodeDriver:
             if api_version == '1.4':
@@ -104,35 +133,50 @@
                 cls = OpenNebula_3_0_NodeDriver
             else:
                 raise NotImplementedError(
-                    "No OpenNebulaNodeDriver found for API version %s" %
-                    (api_version)
-                )
+                    "No OpenNebulaNodeDriver found for API version %s." %
+                    (api_version))
             return super(OpenNebulaNodeDriver, cls).__new__(cls)
 
-    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 create_node(self, **kwargs):
+        """
+        Create a new OpenNebula node.
+
+        See L{NodeDriver.create_node} for more keyword args.
+        """
+        compute = ET.Element('COMPUTE')
+
+        name = ET.SubElement(compute, 'NAME')
+        name.text = kwargs['name']
+
+        xml = ET.tostring(compute)
+        node = self.connection.request('/compute', method='POST',
+                                       data=xml).object
+
+        return self._to_node(node)
+
+    def destroy_node(self, node):
+        url = '/compute/%s' % (str(node.id))
+        resp = self.connection.request(url, method='DELETE')
+
+        return resp.status == httplib.NO_CONTENT
+
+    def reboot_node(self, node):
+        url = '/compute/%s' % (str(compute_id))
+        resp1 = self.connection.request(url, method='PUT',
+                                        data=self._xml_action(compute_id,
+                                                              'STOPPED'))
+
+        if resp1.status == httplib.BAD_REQUEST:
+            return False
+
+        resp2 = self.connection.request(url, method='PUT',
+                                        data=self._xml_action(compute_id,
+                                        'RESUME'))
+
+        if resp2.status == httplib.BAD_REQUEST:
+            return False
+
+        return True
 
     def list_nodes(self):
         return self._to_nodes(self.connection.request('/compute').object)
@@ -140,103 +184,149 @@
     def list_images(self, location=None):
         return self._to_images(self.connection.request('/storage').object)
 
+    def list_sizes(self, location=None):
+        return [
+            OpenNebulaNodeSize(id=1,
+                name='small',
+                ram=None,
+                disk=None,
+                bandwidth=None,
+                price=None,
+                driver=self),
+            OpenNebulaNodeSize(id=2,
+                name='medium',
+                ram=None,
+                disk=None,
+                bandwidth=None,
+                price=None,
+                driver=self),
+            OpenNebulaNodeSize(id=3,
+                name='large',
+                ram=None,
+                disk=None,
+                bandwidth=None,
+                price=None,
+                driver=self),
+        ]
+
     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):
-        """Create a new OpenNebula node
-
-        See L{NodeDriver.create_node} for more keyword args.
-        """
-        compute = ET.Element('COMPUTE')
-
-        name = ET.SubElement(compute, 'NAME')
-        name.text = kwargs['name']
-
-        xml = ET.tostring(compute)
-        node = self.connection.request('/compute', method='POST',
-                                       data=xml).object
-
-        return self._to_node(node)
+    def list_networks(self, location=None):
+        return self._to_networks(self.connection.request('/network').object)
 
     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" % (
+        for element in object.findall('DISK'):
+            image_id = element.attrib['href'].partition('/storage/')[2]
+            imageElement = self.connection.request(('/storage/%s' % (
                                              image_id))).object
-            images.append(self._to_image(image))
+            images.append(self._to_image(imageElement))
 
         return images
 
-    def _to_image(self, image):
-        return NodeImage(id=image.findtext("ID"),
-                         name=image.findtext("NAME"),
-                         driver=self.connection.driver,
-                         extra={"size": image.findtext("SIZE"),
-                                "url": image.findtext("URL")})
+    def _to_image(self, element):
+        return NodeImage(id=element.findtext('ID'),
+                      name=element.findtext('NAME'),
+                      driver=self.connection.driver,
+                      extra={'size': element.findtext('SIZE'),
+                             'url': element.findtext('URL')})
+
+    def _to_networks(self, object):
+        networks = []
+        for element in object.findall('NETWORK'):
+            network_id = element.attrib['href'].partition('/network/')[2]
+            networkElement = self.connection.request(('/network/%s' % (
+                                             network_id))).object
+            networks.append(self._to_network(networkElement))
+
+        return networks
+
+    def _to_network(self, element):
+        return NodeNetwork(id=element.findtext('ID'),
+                      name=element.findtext('NAME'),
+                      driver=self.connection.driver,
+                      extra={'address': element.findtext('ADRESS'),
+                             'size': element.findtext('SIZE')})
 
     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" % (
+        for element in object.findall('COMPUTE'):
+            compute_id = element.attrib['href'].partition('/compute/')[2]
+            computeElement = self.connection.request(('/compute/%s' % (
                                                compute_id))).object
-            computes.append(self._to_node(compute))
+            computes.append(self._to_node(computeElement))
 
         return computes
 
-    def _extract_networks(self, compute):
-        networks = []
-
-        for element in compute.findall("NIC"):
-            ip = element.element.attrib.get('ip', None)
-
-            if ip is not None:
-                networks.append(ip)
-
-        return networks
-
     def _to_node(self, compute):
         try:
-            state = self.NODE_STATE_MAP[compute.findtext("STATE")]
+            state = self.NODE_STATE_MAP[compute.findtext('STATE')]
         except KeyError:
             state = NodeState.UNKNOWN
 
         networks = self._extract_networks(compute)
+        images = self._extract_images(compute)
+        nodeSizes = filter(lambda nodeSize:
+                          nodeSize.name == compute.findtext('INSTANCE_TYPE'),
+                          self.list_sizes())
+        if nodeSizes:
+            nodeSize = nodeSizes[0]
+        else:
+            nodeSize = None
 
-        return Node(id=compute.findtext("ID"),
-                    name=compute.findtext("NAME"),
+        return Node(id=compute.findtext('ID'),
+                    name=compute.findtext('NAME'),
                     state=state,
-                    public_ip=networks,
+                    public_ip=[],
                     private_ip=[],
-                    driver=self.connection.driver)
+                    driver=self.connection.driver,
+                    size=nodeSize,
+                    image=images,
+                    extra={'networks': networks})
+
+    def _extract_networks(self, compute):
+        networks = []
+
+        networkList = compute.find('NETWORK')
+        for element in networkList.findall('NIC'):
+            networks.append(
+                NodeNetwork(id=element.attrib['network'],
+                    name=None,
+                    driver=self.connection.driver,
+                    extra={'ip': element.attrib.get('ip', None)}))
+
+        return networks
+
+    def _extract_images(self, compute):
+        images = []
+
+        diskList = compute.find('DISKS')
+        for element in diskList.findall('DISK'):
+            networks.append(
+                NodeImage(id=element.attrib['image'],
+                    name=None,
+                    driver=self.connection.driver,
+                    extra={'dev': element.attrib.get('dev', None)}))
+
+        for element in diskList.findall('SWAP'):
+            networks.append(
+                NodeImage(id=None,
+                    name=None,
+                    driver=self.connection.driver,
+                    extra={'dev': element.attrib.get('dev', None),
+                           'size': element.attrib.get('size', None)}))
+
+        for element in diskList.findall('FS'):
+            networks.append(
+                NodeImage(id=None,
+                    name=None,
+                    driver=self.connection.driver,
+                    extra={'dev': element.attrib.get('dev', None),
+                           'format': element.attrib.get('format', None),
+                           'size': element.attrib.get('size', None)}))
+
+        return images
 
     def _xml_action(self, compute_id, action):
         compute = ET.Element('COMPUTE')
@@ -252,38 +342,70 @@
 
 
 class OpenNebula_1_4_NodeDriver(OpenNebulaNodeDriver):
+    """
+    OpenNebula v1.4 node driver.
+    """
+
     pass
 
 
 class OpenNebula_3_0_NodeDriver(OpenNebulaNodeDriver):
+    """
+    OpenNebula v3.0 node driver.
+    """
+
+    def create_node(self, **kwargs):
+        """
+        Create a new OpenNebula node.
+
+        See L{NodeDriver.create_node} for more keyword args.
+        """
+        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
+
+        disk = ET.SubElement(compute, 'DISK')
+        storage = ET.SubElement(disk, 'STORAGE', {'href': '/storage/%s' %
+                                                  (str(kwargs['image'].id))})
+
+        xml = ET.tostring(compute)
+        node = self.connection.request('/compute', method='POST',
+                                       data=xml).object
+
+        return self._to_node(node)
+
     def list_sizes(self, location=None):
         return [
-          NodeSize(id=1,
-                   name="small",
+          OpenNebulaNodeSize(id=1,
+                   name='small',
                    ram=1024,
                    cpu=1,
                    disk=None,
                    bandwidth=None,
                    price=None,
                    driver=self),
-          NodeSize(id=2,
-                   name="medium",
+          OpenNebulaNodeSize(id=2,
+                   name='medium',
                    ram=4096,
                    cpu=4,
                    disk=None,
                    bandwidth=None,
                    price=None,
                    driver=self),
-          NodeSize(id=3,
-                   name="large",
+          OpenNebulaNodeSize(id=3,
+                   name='large',
                    ram=8192,
                    cpu=8,
                    disk=None,
                    bandwidth=None,
                    price=None,
                    driver=self),
-          NodeSize(id=4,
-                   name="custom",
+          OpenNebulaNodeSize(id=4,
+                   name='custom',
                    ram=0,
                    cpu=0,
                    disk=None,
@@ -292,52 +414,60 @@
                    driver=self),
         ]
 
-    def create_node(self, **kwargs):
-        """Create a new OpenNebula node
-
-        See L{NodeDriver.create_node} for more keyword args.
-        """
-        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
-
-        disk = ET.SubElement(compute, 'DISK')
-        storage = ET.SubElement(disk, 'STORAGE', {'href': '/storage/%s' %
-                                                  (str(kwargs['image'].id))})
-
-        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("STORAGE"):
-            image_id = element.attrib["href"].partition("/storage/")[2]
-            image = self.connection.request(("/storage/%s" %
+        for element in object.findall('STORAGE'):
+            image_id = element.attrib['href'].partition('/storage/')[2]
+            imageElement = self.connection.request(('/storage/%s' %
                                              (image_id))).object
-            images.append(self._to_image(image))
+            images.append(self._to_image(imageElement))
 
         return images
 
-    def _to_image(self, image):
-        return NodeImage(id=image.findtext("ID"),
-                         name=image.findtext("NAME"),
-                         driver=self.connection.driver,
-                         extra={"description": image.findtext("DESCRIPTION"),
-                                "TYPE": image.findtext("TYPE"),
-                                "size": image.findtext("SIZE"),
-                                "fstype": image.findtext("FSTYPE", None)})
+    def _to_image(self, element):
+        return NodeImage(id=element.findtext('ID'),
+                      name=element.findtext('NAME'),
+                      driver=self.connection.driver,
+                      extra={'description': element.findtext('DESCRIPTION'),
+                             'type': element.findtext('TYPE'),
+                             'size': element.findtext('SIZE'),
+                             'fstype': element.findtext('FSTYPE', None),
+                      })
 
     def _extract_networks(self, compute):
         networks = []
-        for element in compute.findall("NIC"):
-            for ip in element.findall("IP"):
-                networks.append(ip)
+
+        for element in compute.findall('NIC'):
+            network = element.find('NETWORK')
+            network_id = network.attrib['href'].partition('/network/')[2]
+
+            ips = []
+            for ip in element.findall('IP'):
+                ips.append(ip)
+
+            networks.append(
+                NodeNetwork(id=network_id,
+                         name=network.attrib['name'],
+                         driver=self.connection.driver,
+                         extra={'ip': ips,
+                                'mac': element.findtext('MAC'),
+                         }))
 
         return networks
+
+    def _extract_images(self, compute):
+        images = []
+
+        for element in compute.findall('DISK'):
+            storage = element.find('STORAGE')
+            storage_id = storage.attrib['href'].partition('/storage/')[2]
+
+            images.append(
+                NodeImage(id=storage_id,
+                    name=storage.attrib['name'],
+                    driver=self.connection.driver,
+                    extra={'type': element.findtext('TYPE'),
+                           'target': element.findtext('TARGET'),
+                    }))
+
+        return images
