libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hbe...@apache.org
Subject svn commit: r1209752 - in /libcloud/trunk: libcloud/compute/drivers/ test/compute/ test/compute/fixtures/opennebula/ test/compute/fixtures/opennebula_1_4/ test/compute/fixtures/opennebula_2_0/
Date Fri, 02 Dec 2011 22:41:36 GMT
Author: hbetts
Date: Fri Dec  2 22:41:34 2011
New Revision: 1209752

URL: http://svn.apache.org/viewvc?rev=1209752&view=rev
Log:
Updated to reflect a patch attributed to LIBCLOUD-121 brining full compute support, PEP 8 compliance, and additional driver tests, for OpenNebula.org versions up to 3.2.

Added:
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/computes.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/networks.xml
    libcloud/trunk/test/compute/fixtures/opennebula_1_4/storage.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_collection.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_collection.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_15.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_5.xml
    libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_collection.xml
Removed:
    libcloud/trunk/test/compute/fixtures/opennebula/
Modified:
    libcloud/trunk/libcloud/compute/drivers/brightbox.py
    libcloud/trunk/libcloud/compute/drivers/opennebula.py
    libcloud/trunk/test/compute/__init__.py
    libcloud/trunk/test/compute/test_opennebula.py

Modified: libcloud/trunk/libcloud/compute/drivers/brightbox.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/brightbox.py?rev=1209752&r1=1209751&r2=1209752&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/brightbox.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/brightbox.py Fri Dec  2 22:41:34 2011
@@ -141,7 +141,7 @@ class BrightboxNodeDriver(NodeDriver):
             ram = data['ram'],
             disk = data['disk_size'],
             bandwidth = 0,
-            price = '',
+            price = 0,
             driver = self
         )
 

Modified: libcloud/trunk/libcloud/compute/drivers/opennebula.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/opennebula.py?rev=1209752&r1=1209751&r2=1209752&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/opennebula.py (original)
+++ libcloud/trunk/libcloud/compute/drivers/opennebula.py Fri Dec  2 22:41:34 2011
@@ -15,10 +15,13 @@
 # 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
+OpenNebula.org driver.
 """
 
+__docformat__ = 'epytext'
+
 try:
     import simplejson as json
 except ImportError:
@@ -35,22 +38,53 @@ from libcloud.compute.base import NodeIm
 from libcloud.common.types import InvalidCredsError
 from libcloud.compute.providers import Provider
 
+__all__ = [
+    'ACTION',
+    'OpenNebulaResponse',
+    'OpenNebulaConnection',
+    'OpenNebulaNodeSize',
+    'OpenNebulaNetwork',
+    'OpenNebulaNodeDriver',
+    'OpenNebula_1_4_NodeDriver',
+    'OpenNebula_2_0_NodeDriver']
+
 API_HOST = ''
 API_PORT = (4567, 443)
 API_SECURE = True
-DEFAULT_API_VERSION = '3.0'
+DEFAULT_API_VERSION = '3.2'
+
+
+class ACTION(object):
+    STOP = 'STOPPED'
+    PAUSE = 'SUSPENDED'
+    RESUME = 'RESUME'
 
 
 class OpenNebulaResponse(XmlResponse):
     """
-    Response class for the OpenNebula driver.
+    XmlResponse class for the OpenNebula.org driver.
     """
 
     def success(self):
+        """
+        Check if response has the appropriate HTTP response code to be a
+        success.
+
+        @rtype:  C{bool}
+        @return: True is success, else False.
+        """
         i = int(self.status)
         return i >= 200 and i <= 299
 
     def parse_error(self):
+        """
+        Check if response contains any errors.
+
+        @raise: L{InvalidCredsError}
+
+        @rtype:  C{ElementTree}
+        @return: Contents of HTTP response body.
+        """
         if int(self.status) == httplib.UNAUTHORIZED:
             raise InvalidCredsError(self.body)
         return self.body
@@ -58,7 +92,7 @@ class OpenNebulaResponse(XmlResponse):
 
 class OpenNebulaConnection(ConnectionUserAndKey):
     """
-    Connection class for the OpenNebula driver.
+    Connection class for the OpenNebula.org driver.
     """
 
     host = API_HOST
@@ -67,6 +101,18 @@ class OpenNebulaConnection(ConnectionUse
     responseCls = OpenNebulaResponse
 
     def add_default_headers(self, headers):
+        """
+        Add headers required by the OpenNebula.org OCCI interface.
+
+        Includes adding Basic HTTP Authorization headers for authenticating
+        against the OpenNebula.org OCCI interface.
+
+        @type  headers: C{dict}
+        @param headers: Dictionary containing HTTP headers.
+
+        @rtype:  C{dict}
+        @return: Dictionary containing updated headers.
+        """
         pass_sha1 = hashlib.sha1(self.key).hexdigest()
         headers['Authorization'] = ('Basic %s' % b64encode('%s:%s' %
                                                 (self.user_id, pass_sha1)))
@@ -74,48 +120,109 @@ class OpenNebulaConnection(ConnectionUse
 
 
 class OpenNebulaNodeSize(NodeSize):
+    """
+    NodeSize class for the OpenNebula.org driver.
+    """
 
     def __init__(self, id, name, ram, disk, bandwidth, price, driver,
                  cpu=None, vcpu=None):
-        self.cpu = cpu
-        self.vcpu = vcpu
         super(OpenNebulaNodeSize, self).__init__(id=id, name=name, ram=ram,
                                                  disk=disk,
                                                  bandwidth=bandwidth,
                                                  price=price, 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 ...>')
+        return (('<OpenNebulaNodeSize: id=%s, name=%s, ram=%s, disk=%s, '
+                 'bandwidth=%s, price=%s, driver=%s, cpu=%s, vcpu=%s ...>')
                 % (self.id, self.name, self.ram, self.disk, self.bandwidth,
-                   self.price, self.driver.name, self.cpu))
+                   self.price, self.driver.name, self.cpu, self.vcpu))
 
 
 class OpenNebulaNetwork(object):
     """
-    A virtual network.
+    Provide a common interface for handling networks of all types.
 
-    NodeNetwork objects are analogous to physical switches connecting 2
-    or more physical nodes together.
-
-    Apart from name and id, there is no further standard information;
-    other parameters are stored in a driver specific "extra" variable
+    Network objects are analogous to physical switches connecting two or
+    more physical nodes together. The Network object provides the interface in
+    libcloud through which we can manipulate networks in different cloud
+    providers in the same way. Network objects don't actually do much directly
+    themselves, instead the network driver handles the connection to the
+    network.
+
+    You don't normally create a network object yourself; instead you use
+    a driver and then have that create the network for you.
+
+    >>> from libcloud.compute.drivers.dummy import DummyNodeDriver
+    >>> driver = DummyNetworkDriver()
+    >>> network = driver.create_network()
+    >>> network = driver.list_networks()[0]
+    >>> network.name
+    'dummy-1'
     """
 
-    def __init__(self, id, name, driver, extra=None):
+    def __init__(self, id, name, address, size, driver, extra=None):
         self.id = str(id)
         self.name = name
+        self.address = address
+        self.size = size
         self.driver = driver
+        self.uuid = self.get_uuid()
         self.extra = extra or {}
 
+    def get_uuid(self):
+        """
+        Unique hash for this network.
+
+        The hash is a function of an SHA1 hash of the network's ID and
+        its driver which means that it should be unique between all
+        networks. In some subclasses (e.g. GoGrid) there is no ID
+        available so the public IP address is used. This means that,
+        unlike a properly done system UUID, the same UUID may mean a
+        different system install at a different time
+
+        >>> from libcloud.network.drivers.dummy import DummyNetworkDriver
+        >>> driver = DummyNetworkDriver()
+        >>> network = driver.create_network()
+        >>> network.get_uuid()
+        'd3748461511d8b9b0e0bfa0d4d3383a619a2bb9f'
+
+        Note, for example, that this example will always produce the
+        same UUID!
+
+        @rtype:  C{string}
+        @return: Unique identifier for this instance.
+        """
+        return hashlib.sha1("%s:%d" % (self.id, self.driver.type)).hexdigest()
+
+    def destroy(self):
+        """
+        Destroy this network.
+
+        This calls the networks's driver and destroys the network.
+
+        >>> from libcloud.network.drivers.dummy import DummyNetworkDriver
+        >>> driver = DummyNetworkDriver()
+        >>> network = driver.create_network()
+        >>> network.destroy()
+        True
+
+        @return: True is the network was destroyed, else False.
+        @rtype:  C{bool}
+        """
+        return self.driver.destroy_network(self)
+
     def __repr__(self):
-        return (('<OpenNebulaNetwork: id=%s, name=%s, driver=%s ...>')
-                % (self.id, self.name, self.driver.name))
+        return (('<OpenNebulaNetwork: uuid=%s, name=%s, address=%s, size=%s, '
+                 'provider=%s ...>')
+                % (self.uuid, self.name, self.address, self.size,
+                   self.driver.name))
 
 
 class OpenNebulaNodeDriver(NodeDriver):
     """
-    OpenNebula node driver.
+    OpenNebula.org node driver.
     """
 
     connectionCls = OpenNebulaConnection
@@ -133,39 +240,112 @@ class OpenNebulaNodeDriver(NodeDriver):
         'SUSPENDED': NodeState.PENDING,
         'FAILED': NodeState.TERMINATED,
         'UNKNOWN': NodeState.UNKNOWN,
-        'DONE': NodeState.TERMINATED
-    }
+        'DONE': NodeState.TERMINATED}
 
     def __new__(cls, key, secret=None, api_version=DEFAULT_API_VERSION,
                 **kwargs):
         if cls is OpenNebulaNodeDriver:
-            if api_version == '1.4':
+            if api_version in ['1.4']:
                 cls = OpenNebula_1_4_NodeDriver
-            elif api_version == '3.0':
-                cls = OpenNebula_3_0_NodeDriver
+            elif api_version in ['2.0', '2.2', '3.0', '3.2']:
+                cls = OpenNebula_2_0_NodeDriver
             else:
                 raise NotImplementedError(
                     "No OpenNebulaNodeDriver found for API version %s" %
                     (api_version))
             return super(OpenNebulaNodeDriver, cls).__new__(cls)
 
+    def create_node(self, **kwargs):
+        """
+        Create a new OpenNebula node.
+
+        See L{NodeDriver.create_node} for more keyword args.
+        @type    networks: L{OpenNebulaNetwork} or C{list}
+                           of L{OpenNebulaNetwork}s
+        @keyword networks: List of virtual networks to which this node should
+                           connect. (optional)
+
+        @rtype:  L{Node}
+        @return: Instance of a newly created node.
+        """
+        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': '%s' %
+                                                  (str(kwargs['image'].id))})
+
+        if 'networks' in kwargs:
+            if not isinstance(kwargs['networks'], list):
+                kwargs['networks'] = [kwargs['networks']]
+
+            networkGroup = ET.SubElement(compute, 'NETWORK')
+            for network in kwargs['networks']:
+                if network.address:
+                    nic = ET.SubElement(networkGroup, 'NIC',
+                        {'network': '%s' % (str(network.id)),
+                        'ip': network.address})
+                else:
+                    nic = ET.SubElement(networkGroup, 'NIC',
+                        {'network': '%s' % (str(network.id))})
+
+        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.OK
+
+    def reboot_node(self, node):
+        if not self.ex_node_action(node, ACTION.STOP):
+            return False
+
+        if not self.ex_node_action(node, ACTION.RESUME):
+            return False
+
+        return True
+
+    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_sizes(self, location=None):
+        """
+        Return list of sizes on a provider.
+
+        See L{NodeDriver.list_sizes} for more args.
+
+        @rtype:  C{list} of L{OpenNebulaNodeSize}
+        @return: List of compute node sizes supported by the cloud provider.
+        """
         return [
-            OpenNebulaNodeSize(id=1,
+            NodeSize(id=1,
                 name='small',
                 ram=None,
                 disk=None,
                 bandwidth=None,
                 price=None,
                 driver=self),
-            OpenNebulaNodeSize(id=2,
+            NodeSize(id=2,
                 name='medium',
                 ram=None,
                 disk=None,
                 bandwidth=None,
                 price=None,
                 driver=self),
-            OpenNebulaNodeSize(id=3,
+            NodeSize(id=3,
                 name='large',
                 ram=None,
                 disk=None,
@@ -174,65 +354,73 @@ class OpenNebulaNodeDriver(NodeDriver):
                 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)
+        return [NodeLocation(0, '', '', self)]
 
-        url = '/compute/%s' % compute_id
-        resp1 = self.connection.request(url, method='PUT',
-                                        data=self._xml_action(compute_id,
-                                                              'STOPPED'))
+    def ex_list_networks(self, location=None):
+        """
+        List virtual networks on a provider.
 
-        if resp1.status == 400:
-            return False
+        @type  location: L{NodeLocation}
+        @param location: Location from which to request a list of virtual
+                         networks. (optional)
+
+        @rtype:  C{list} of L{OpenNebulaNetwork}
+        @return: List of virtual networks available to be connected to a
+                 compute node.
+        """
+        return self._to_networks(self.connection.request('/network').object)
 
-        resp2 = self.connection.request(url, method='PUT',
-                                        data=self._xml_action(compute_id,
-                                        'RESUME'))
+    def ex_node_action(self, node, action):
+        """
+        Build action representation and instruct node to commit action.
 
-        if resp2.status == 400:
-            return False
+        Build action representation from the compute node ID, and the
+        action which should be carried out on that compute node. Then
+        instruct the node to carry out that action.
+
+        @type  node: L{Node}
+        @param node: Compute node instance.
+        @type  action: C{str}
+        @param action: Action to be carried out on the compute node.
+
+        @rtype:  C{bool}
+        @return: False if an HTTP Bad Request is received, else, True is
+                 returned.
+        """
+        compute_node_id = str(node.id)
 
-        return True
+        compute = ET.Element('COMPUTE')
 
-    def destroy_node(self, node):
-        url = '/compute/%s' % (str(node.id))
-        resp = self.connection.request(url, method='DELETE')
+        compute_id = ET.SubElement(compute, 'ID')
+        compute_id.text = str(compute_node_id)
 
-        return resp.status == 204
+        state = ET.SubElement(compute, 'STATE')
+        state.text = action
 
-    def create_node(self, **kwargs):
-        """Create a new OpenNebula node
+        xml = ET.tostring(compute)
 
-        See L{NodeDriver.create_node} for more keyword args.
-        """
-        compute = ET.Element('COMPUTE')
+        url = '/compute/%s' % compute_node_id
+        resp = self.connection.request(url, method='PUT',
+                                        data=xml)
 
-        name = ET.SubElement(compute, 'NAME')
-        name.text = kwargs['name']
+        if resp.status == httplib.BAD_REQUEST:
+            return False
+        else:
+            return True
 
-        xml = ET.tostring(compute)
-        node = self.connection.request('/compute', method='POST',
-                                       data=xml).object
+    def _to_images(self, object):
+        """
+        Request a list of images and convert that list to a list of NodeImage
+        objects.
 
-        return self._to_node(node)
+        Request a list of images from the OpenNebula web interface, and
+        issue a request to convert each XML object representation of an image
+        to a NodeImage object.
 
-    def ex_list_networks(self, location=None):
-        """
-        List virtual networks on a provider
-        @return: C{list} of L{OpenNebulaNetwork} objects
+        @rtype:  C{list} of L{NodeImage}
+        @return: List of images.
         """
-        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]
@@ -243,6 +431,16 @@ class OpenNebulaNodeDriver(NodeDriver):
         return images
 
     def _to_image(self, image):
+        """
+        Take XML object containing an image description and convert to
+        NodeImage object.
+
+        @type  image: L{ElementTree}
+        @param image: XML representation of an image.
+
+        @rtype:  L{NodeImage}
+        @return: The newly extracted L{NodeImage}.
+        """
         return NodeImage(id=image.findtext('ID'),
                          name=image.findtext('NAME'),
                          driver=self.connection.driver,
@@ -250,6 +448,17 @@ class OpenNebulaNodeDriver(NodeDriver):
                                 'url': image.findtext('URL')})
 
     def _to_networks(self, object):
+        """
+        Request a list of networks and convert that list to a list of
+        OpenNebulaNetwork objects.
+
+        Request a list of networks from the OpenNebula web interface, and
+        issue a request to convert each XML object representation of a network
+        to an OpenNebulaNetwork object.
+
+        @rtype:  C{list} of L{OpenNebulaNetwork}
+        @return: List of virtual networks.
+        """
         networks = []
         for element in object.findall('NETWORK'):
             network_id = element.attrib['href'].partition('/network/')[2]
@@ -260,13 +469,34 @@ class OpenNebulaNodeDriver(NodeDriver):
         return networks
 
     def _to_network(self, element):
+        """
+        Take XML object containing a network description and convert to
+        OpenNebulaNetwork object.
+
+        Take XML representation containing a network description and
+        convert to OpenNebulaNetwork object.
+
+        @rtype:  L{OpenNebulaNetwork}
+        @return: The newly extracted L{OpenNebulaNetwork}.
+        """
         return OpenNebulaNetwork(id=element.findtext('ID'),
                       name=element.findtext('NAME'),
-                      driver=self.connection.driver,
-                      extra={'address': element.findtext('ADDRESS'),
-                             'size': element.findtext('SIZE')})
+                      address=element.findtext('ADDRESS'),
+                      size=element.findtext('SIZE'),
+                      driver=self.connection.driver)
 
     def _to_nodes(self, object):
+        """
+        Request a list of compute nodes and convert that list to a list of
+        Node objects.
+
+        Request a list of compute nodes from the OpenNebula web interface, and
+        issue a request to convert each XML object representation of a node
+        to a Node object.
+
+        @rtype:  C{list} of L{Node}
+        @return: A list of compute nodes.
+        """
         computes = []
         for element in object.findall('COMPUTE'):
             compute_id = element.attrib['href'].partition('/compute/')[2]
@@ -276,20 +506,20 @@ class OpenNebulaNodeDriver(NodeDriver):
 
         return computes
 
-    def _extract_networks(self, compute):
-        networks = []
+    def _to_node(self, compute):
+        """
+        Take XML object containing a compute node description and convert to
+        Node object.
 
-        network_list = compute.find('NETWORK')
-        for element in network_list.findall('NIC'):
-            networks.append(
-                OpenNebulaNetwork(id=element.attrib.get('network', None),
-                    name=None,
-                    driver=self.connection.driver,
-                    extra={'ip': element.attrib.get('ip', None)}))
+        Take XML representation containing a compute node description and
+        convert to Node object.
 
-        return networks
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
 
-    def _to_node(self, compute):
+        @rtype:  L{Node}
+        @return: The newly extracted L{Node}.
+        """
         try:
             state = self.NODE_STATE_MAP[compute.findtext('STATE').upper()]
         except KeyError:
@@ -302,30 +532,96 @@ class OpenNebulaNodeDriver(NodeDriver):
                     state=state,
                     public_ips=networks,
                     private_ips=[],
-                    driver=self.connection.driver)
+                    driver=self.connection.driver,
+                    image=self._extract_images(compute))
 
-    def _xml_action(self, compute_id, action):
-        compute = ET.Element('COMPUTE')
+    def _extract_networks(self, compute):
+        """
+        Extract networks from a compute node XML representation.
 
-        compute_id = ET.SubElement(compute, 'ID')
-        compute_id.text = str(compute_id)
+        Extract network descriptions from a compute node XML representation,
+        converting each network to an OpenNebulaNetwork object.
 
-        state = ET.SubElement(compute, 'STATE')
-        state.text = action
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
 
-        xml = ET.tostring(compute)
-        return xml
+        @rtype:  C{list} of L{OpenNebulaNetwork}s.
+        @return: List of virtual networks attached to the compute node.
+        """
+        networks = list()
+
+        network_list = compute.find('NETWORK')
+        for element in network_list.findall('NIC'):
+            networks.append(
+                OpenNebulaNetwork(id=element.attrib.get('network', None),
+                    name=None,
+                    address=element.attrib.get('ip', None),
+                    size=1,
+                    driver=self.connection.driver))
+
+        return networks
+
+    def _extract_images(self, compute):
+        """
+        Extract image disks from a compute node XML representation.
+
+        Extract image disk descriptions from a compute node XML representation,
+        converting the disks to an NodeImage object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  L{NodeImage}.
+        @return: First disk attached to a compute node.
+        """
+        disks = list()
+
+        disk_list = compute.find('STORAGE')
+        if disk_list is not None:
+            for element in disk_list.findall('DISK'):
+                disks.append(
+                    NodeImage(id=element.attrib.get('image', None),
+                        name=None,
+                        driver=self.connection.driver,
+                        extra={'dev': element.attrib.get('dev', None)}))
+
+        # @TODO: Return all disks when the Node type accepts multiple
+        # attached disks per node.
+        if len(disks) > 0:
+            return disks[0]
+        else:
+            return None
 
 
 class OpenNebula_1_4_NodeDriver(OpenNebulaNodeDriver):
+    """
+    OpenNebula.org node driver for OpenNebula.org v1.4.
+    """
+
     pass
 
 
-class OpenNebula_3_0_NodeDriver(OpenNebulaNodeDriver):
+class OpenNebula_2_0_NodeDriver(OpenNebulaNodeDriver):
+    """
+    OpenNebula.org node driver for OpenNebula.org v2.0 through OpenNebula.org
+    v3.2.
+    """
+
     def create_node(self, **kwargs):
-        """Create a new OpenNebula node
+        """
+        Create a new OpenNebula node.
 
         See L{NodeDriver.create_node} for more keyword args.
+        @type    networks: L{OpenNebulaNetwork} or C{list}
+                           of L{OpenNebulaNetwork}s
+        @keyword networks: List of virtual networks to which this node should
+                           connect. (optional)
+        @type    context: C{dict}
+        @keyword context: Custom (key, value) pairs to be injected into
+                          compute node XML description. (optional)
+
+        @rtype:  L{Node}
+        @return: Instance of a newly created node.
         """
         compute = ET.Element('COMPUTE')
 
@@ -339,13 +635,46 @@ class OpenNebula_3_0_NodeDriver(OpenNebu
         storage = ET.SubElement(disk, 'STORAGE', {'href': '/storage/%s' %
                                                   (str(kwargs['image'].id))})
 
+        if 'networks' in kwargs:
+            if not isinstance(kwargs['networks'], list):
+                kwargs['networks'] = [kwargs['networks']]
+
+            for network in kwargs['networks']:
+                nic = ET.SubElement(compute, 'NIC')
+                network_line = ET.SubElement(nic, 'NETWORK',
+                            {'href': '/network/%s' % (str(network.id))})
+                if network.address:
+                    ip_line = ET.SubElement(nic, 'IP')
+                    ip_line.text = network.address
+
+        if 'context' in kwargs:
+            if isinstance(kwargs['context'], dict):
+                contextGroup = ET.SubElement(compute, 'CONTEXT')
+                for key, value in kwargs['context'].items():
+                    context = ET.SubElement(contextGroup, key.upper())
+                    context.text = value
+
         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 list_sizes(self, location=None):
+        """
+        Return list of sizes on a provider.
+
+        See L{NodeDriver.list_sizes} for more args.
+
+        @rtype:  C{list} of L{OpenNebulaNodeSize}
+        @return: List of compute node sizes supported by the cloud provider.
+        """
         return [
           OpenNebulaNodeSize(id=1,
                    name='small',
@@ -381,14 +710,18 @@ class OpenNebula_3_0_NodeDriver(OpenNebu
                    driver=self),
         ]
 
-    def ex_list_networks(self, location=None):
-        """
-        List virtual networks on a provider
-        @return: C{list} of L{OpenNebulaNetwork} objects
+    def _to_images(self, object):
         """
-        return self._to_networks(self.connection.request('/network').object)
+        Request a list of images and convert that list to a list of NodeImage
+        objects.
 
-    def _to_images(self, object):
+        Request a list of images from the OpenNebula web interface, and
+        issue a request to convert each XML object representation of an image
+        to a NodeImage object.
+
+        @rtype:  C{list} of L{NodeImage}
+        @return: List of images.
+        """
         images = []
         for element in object.findall('STORAGE'):
             image_id = element.attrib["href"].partition("/storage/")[2]
@@ -399,31 +732,157 @@ class OpenNebula_3_0_NodeDriver(OpenNebu
         return images
 
     def _to_image(self, image):
+        """
+        Take XML object containing an image description and convert to
+        NodeImage object.
+
+        @type  image: L{ElementTree}
+        @param image: XML representation of an image.
+
+        @rtype:  L{NodeImage}
+        @return: The newly extracted L{NodeImage}.
+        """
         return NodeImage(id=image.findtext('ID'),
                          name=image.findtext('NAME'),
                          driver=self.connection.driver,
                          extra={'description': image.findtext('DESCRIPTION'),
-                                'TYPE': image.findtext('TYPE'),
+                                'type': image.findtext('TYPE'),
                                 'size': image.findtext('SIZE'),
                                 'fstype': image.findtext('FSTYPE', None)})
 
+    def _to_node(self, compute):
+        """
+        Take XML object containing a compute node description and convert to
+        Node object.
+
+        Take XML representation containing a compute node description and
+        convert to Node object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  L{Node}
+        @return: The newly extracted L{Node}.
+        """
+        try:
+            state = self.NODE_STATE_MAP[compute.findtext('STATE').upper()]
+        except KeyError:
+            state = NodeState.UNKNOWN
+
+        networks = self._extract_networks(compute)
+
+        return Node(id=compute.findtext('ID'),
+                    name=compute.findtext('NAME'),
+                    state=state,
+                    public_ips=networks,
+                    private_ips=[],
+                    driver=self.connection.driver,
+                    image=self._extract_images(compute),
+                    size=self._extract_size(compute),
+                    extra={'context': self._extract_context(compute)})
+
     def _extract_networks(self, compute):
+        """
+        Extract networks from a compute node XML representation.
+
+        Extract network descriptions from a compute node XML representation,
+        converting each network to an OpenNebulaNetwork object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  C{list} of L{OpenNebulaNetwork}
+        @return: List of virtual networks attached to the compute node.
+        """
         networks = []
 
         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(
                 OpenNebulaNetwork(id=network_id,
-                         name=network.attrib['name'],
+                         name=network.attrib.get('name', None),
+                         address=element.findtext('IP'),
+                         size=1,
                          driver=self.connection.driver,
-                         extra={'ip': ips,
-                                'mac': element.findtext('MAC'),
-                         }))
+                         extra={'mac': element.findtext('MAC')}))
 
         return networks
+
+    def _extract_images(self, compute):
+        """
+        Extract image disks from a compute node XML representation.
+
+        Extract image disk descriptions from a compute node XML representation,
+        converting the disks to an NodeImage object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  L{NodeImage}
+        @return: First disk 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]
+
+            disks.append(
+                NodeImage(id=disk_id,
+                    name=disk.attrib.get('name', None),
+                    driver=self.connection.driver,
+                    extra={'type': element.findtext('TYPE'),
+                           'target': element.findtext('TARGET')}))
+
+        # @TODO: Return all disks when the Node type accepts multiple
+        # attached disks per node.
+        if len(disks) > 0:
+            return disks[0]
+        else:
+            return None
+
+    def _extract_size(self, compute):
+        """
+        Extract size, or node type, from a compute node XML representation.
+
+        Extract node size, or node type, description from a compute node XML
+        representation, converting the node size to a NodeSize object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  L{OpenNebulaNodeSize}
+        @return: Node type of compute node.
+        """
+        instance_type = compute.find('INSTANCE_TYPE')
+
+        try:
+            return (node_size for node_size in self.list_sizes()
+                    if node_size.name == instance_type.text).next()
+        except StopIteration:
+            return None
+
+    def _extract_context(self, compute):
+        """
+        Extract size, or node type, from a compute node XML representation.
+
+        Extract node size, or node type, description from a compute node XML
+        representation, converting the node size to a NodeSize object.
+
+        @type  compute: L{ElementTree}
+        @param compute: XML representation of a compute node.
+
+        @rtype:  C{dict}
+        @return: Dictionary containing (key, value) pairs related to
+                 compute node context.
+        """
+        contexts = dict()
+        context = compute.find('CONTEXT')
+
+        if context is not None:
+            for context_element in list(context):
+                contexts[context_element.tag.lower()] = context_element.text
+
+        return contexts

Modified: libcloud/trunk/test/compute/__init__.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/__init__.py?rev=1209752&r1=1209751&r2=1209752&view=diff
==============================================================================
--- libcloud/trunk/test/compute/__init__.py (original)
+++ libcloud/trunk/test/compute/__init__.py Fri Dec  2 22:41:34 2011
@@ -16,6 +16,7 @@
 from libcloud.compute.base import Node, NodeImage, NodeLocation
 from libcloud.pricing import get_pricing
 
+
 class TestCaseMixin(object):
     should_list_locations = True
     should_have_pricing = False
@@ -35,6 +36,9 @@ class TestCaseMixin(object):
         self.assertTrue(size.disk is None or isinstance(size.disk, int))
         self.assertTrue(size.bandwidth is None or
                             isinstance(size.bandwidth, int))
+        # Check that price values are ints, floats, or None.
+        self.assertTrue(size.price is None or isinstance(size.price, float)
+                        or isinstance(size.price, int))
 
     def test_list_images_response(self):
         images = self.driver.list_images()
@@ -78,7 +82,8 @@ class TestCaseMixin(object):
 
         driver_type = 'compute'
         try:
-            get_pricing(driver_type=driver_type, driver_name=self.driver.api_name)
+            get_pricing(driver_type=driver_type,
+                        driver_name=self.driver.api_name)
         except KeyError:
             self.fail("No {driver_type!r} pricing info for {driver}.".format(
                 driver=self.driver.__class__.__name__,

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE>
+    <ID>15</ID>
+    <NAME>Compute 15</NAME>
+    <STATE>RUNNING</STATE>
+    <STORAGE>
+        <DISK image="15" dev="sda1"/>
+        <FS size="512" format="ext3" dev="sda3"/>
+        <SWAP size="1024" dev="sda2"/>
+    </STORAGE>  
+    <NETWORK>
+        <NIC network="5" ip="192.168.0.2"/>
+        <NIC network="15" ip="192.168.1.2"/>
+    </NETWORK>
+</COMPUTE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/compute_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE>
+    <ID>5</ID>
+    <NAME>Compute 5</NAME>
+    <STATE>RUNNING</STATE>
+    <STORAGE>
+        <DISK image="5" dev="sda1"/>
+        <FS size="512" format="ext3" dev="sda3"/>
+        <SWAP size="1024" dev="sda2"/>
+    </STORAGE>  
+    <NETWORK>
+        <NIC network="5" ip="192.168.0.1"/>
+        <NIC network="15" ip="192.168.1.1"/>
+    </NETWORK>
+</COMPUTE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/computes.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/computes.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/computes.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/computes.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTES>  
+    <COMPUTE href="http://www.opennebula.org/compute/5"/>  
+    <COMPUTE href="http://www.opennebula.org/compute/15"/>
+</COMPUTES>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DISK>
+    <ID>15</ID>
+    <NAME>Ubuntu 9.04 LAMP</NAME>
+    <SIZE>2048</SIZE>
+    <URL>file:///images/ubuntu/jaunty.img</URL>
+</DISK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/disk_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<DISK>
+    <ID>5</ID>
+    <NAME>Ubuntu 9.04 LAMP</NAME>
+    <SIZE>2048</SIZE>
+    <URL>file:///images/ubuntu/jaunty.img</URL>
+</DISK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORK>
+    <ID>15</ID>
+    <NAME>Network 15</NAME>
+    <ADDRESS>192.168.1.0</ADDRESS>
+    <SIZE>256</SIZE>
+</NETWORK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/network_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORK>
+    <ID>5</ID>
+    <NAME>Network 5</NAME>
+    <ADDRESS>192.168.0.0</ADDRESS>
+    <SIZE>256</SIZE>
+</NETWORK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/networks.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/networks.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/networks.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/networks.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORKS>  
+    <NETWORK href="http://www.opennebula.org/network/5"/>  
+    <NETWORK href="http://www.opennebula.org/network/15"/>
+</NETWORKS>

Added: libcloud/trunk/test/compute/fixtures/opennebula_1_4/storage.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_1_4/storage.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_1_4/storage.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_1_4/storage.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<STORAGE>
+    <DISK href="http://www.opennebula.org/storage/5"/>
+    <DISK href="http://www.opennebula.org/storage/15"/>
+</STORAGE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE href='http://www.opennebula.org/compute/5'>
+    <ID>15</ID>
+    <NAME>Compute 15</NAME>
+    <INSTANCE_TYPE>small</INSTANCE_TYPE>
+    <STATE>RUNNING</STATE>
+    <DISK>
+        <STORAGE href='http://www.opennebula.org/storage/15' name='Ubuntu 9.04 LAMP'/>
+        <TYPE>DISK</TYPE>
+        <TARGET>hda</TARGET>
+    </DISK>
+    <NIC>
+        <NETWORK href='http://www.opennebula.org/network/5' name='Network 5'/>
+        <IP>192.168.0.2</IP>
+        <MAC>02:00:c0:a8:00:02</MAC>
+    </NIC>
+    <NIC>
+        <NETWORK href='http://www.opennebula.org/network/15' name='Network 15'/>
+        <IP>192.168.1.2</IP>
+        <MAC>02:00:c0:a8:01:02</MAC>
+    </NIC>
+    <CONTEXT>
+        <HOSTNAME>compute-15</HOSTNAME>
+    </CONTEXT>
+</COMPUTE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE href='http://www.opennebula.org/compute/5'>
+    <ID>5</ID>
+    <NAME>Compute 5</NAME>
+    <INSTANCE_TYPE>small</INSTANCE_TYPE>
+    <STATE>RUNNING</STATE>
+    <DISK>
+        <STORAGE href='http://www.opennebula.org/storage/5' name='Ubuntu 9.04 LAMP'/>
+        <TYPE>DISK</TYPE>
+        <TARGET>hda</TARGET>
+    </DISK>
+    <NIC>
+        <NETWORK href='http://www.opennebula.org/network/5' name='Network 5'/>
+        <IP>192.168.0.1</IP>
+        <MAC>02:00:c0:a8:00:01</MAC>
+    </NIC>
+    <NIC>
+        <NETWORK href='http://www.opennebula.org/network/15' name='Network 15'/>
+        <IP>192.168.1.1</IP>
+        <MAC>02:00:c0:a8:01:01</MAC>
+    </NIC>
+    <CONTEXT>
+        <HOSTNAME>compute-5</HOSTNAME>
+    </CONTEXT>
+</COMPUTE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_collection.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_collection.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_collection.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/compute_collection.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<COMPUTE_COLLECTION>  
+    <COMPUTE href="http://www.opennebula.org/compute/5" name="Compute 5"/>  
+    <COMPUTE href="http://www.opennebula.org/compute/15" name="Compute 15"/>
+</COMPUTE_COLLECTION>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORK>
+    <ID>15</ID>
+    <NAME>Network 15</NAME>
+    <ADDRESS>192.168.1.0</ADDRESS>
+    <SIZE>256</SIZE>
+</NETWORK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORK>
+    <ID>5</ID>
+    <NAME>Network 5</NAME>
+    <ADDRESS>192.168.0.0</ADDRESS>
+    <SIZE>256</SIZE>
+</NETWORK>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_collection.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_collection.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_collection.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/network_collection.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<NETWORK_COLLECTION>  
+    <NETWORK href="http://www.opennebula.org/network/5" name="Network 5"/>  
+    <NETWORK href="http://www.opennebula.org/network/15" name="Network 15"/>
+</NETWORK_COLLECTION>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_15.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_15.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_15.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_15.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<STORAGE>
+    <ID>15</ID>
+    <NAME>Ubuntu 9.04 LAMP</NAME>
+    <TYPE>OS</TYPE>
+    <DESCRIPTION>Ubuntu 9.04 LAMP Description</DESCRIPTION>
+    <SIZE>2048</SIZE>
+</STORAGE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_5.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_5.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_5.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_5.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<STORAGE>
+    <ID>5</ID>
+    <NAME>Ubuntu 9.04 LAMP</NAME>
+    <TYPE>OS</TYPE>
+    <DESCRIPTION>Ubuntu 9.04 LAMP Description</DESCRIPTION>
+    <SIZE>2048</SIZE>
+</STORAGE>

Added: libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_collection.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_collection.xml?rev=1209752&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_collection.xml (added)
+++ libcloud/trunk/test/compute/fixtures/opennebula_2_0/storage_collection.xml Fri Dec  2 22:41:34 2011
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<STORAGE_COLLECTION>
+    <STORAGE href="http://www.opennebula.org/storage/5" name="Ubuntu 9.04 LAMP"/>
+    <STORAGE href="http://www.opennebula.org/storage/15" name="Ubuntu 9.04 LAMP"/>
+</STORAGE_COLLECTION>

Modified: libcloud/trunk/test/compute/test_opennebula.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/test_opennebula.py?rev=1209752&r1=1209751&r2=1209752&view=diff
==============================================================================
--- libcloud/trunk/test/compute/test_opennebula.py (original)
+++ libcloud/trunk/test/compute/test_opennebula.py Fri Dec  2 22:41:34 2011
@@ -15,108 +15,635 @@
 # 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
+
+"""
+OpenNebula.org test suite.
+"""
+
+__docformat__ = 'epytext'
+
 import unittest
 import httplib
+import sys
 
-from libcloud.compute.drivers.opennebula import OpenNebulaNodeDriver
-from libcloud.compute.base import Node, NodeImage, NodeSize
+from libcloud.compute.base import Node, NodeImage, NodeSize, NodeState
+from libcloud.compute.drivers.opennebula import *
 
-from test import MockHttp
-from test.compute import TestCaseMixin
 from test.file_fixtures import ComputeFileFixtures
+from test.compute import TestCaseMixin
+from test import MockHttp
 
 from test.secrets import OPENNEBULA_PARAMS
 
 
 class OpenNebula_1_4_Tests(unittest.TestCase, TestCaseMixin):
+    """
+    OpenNebula.org test suite for OpenNebula v1.4.
+    """
 
     def setUp(self):
-        OpenNebulaNodeDriver.connectionCls.conn_classes = (None,
-                                                           OpenNebulaMockHttp)
+        """
+        Setup test environment.
+        """
+        OpenNebulaNodeDriver.connectionCls.conn_classes = (
+            OpenNebula_1_4_MockHttp, OpenNebula_1_4_MockHttp)
         self.driver = OpenNebulaNodeDriver(*OPENNEBULA_PARAMS + ('1.4',))
 
     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)
+        """
+        Test create_node functionality.
+        """
+        image = NodeImage(id=5, name='Ubuntu 9.04 LAMP', driver=self.driver)
+        size = NodeSize(id=1, name='small', ram=None, disk=None,
+                        bandwidth=None, price=None, driver=self.driver)
+        networks = list()
+        networks.append(OpenNebulaNetwork(id=5, name='Network 5',
+                        address='192.168.0.0', size=256, driver=self.driver))
+        networks.append(OpenNebulaNetwork(id=15, name='Network 15',
+                        address='192.168.1.0', size=256, driver=self.driver))
+
+        node = self.driver.create_node(name='Compute 5', image=image,
+                                       size=size, networks=networks)
+
         self.assertEqual(node.id, '5')
-        self.assertEqual(node.name, 'MyCompute')
+        self.assertEqual(node.name, 'Compute 5')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].name, None)
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].address, '192.168.0.1')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[1].name, None)
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].address, '192.168.1.1')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.private_ips, [])
+        self.assertEqual(node.image.id, '5')
+        self.assertEqual(node.image.extra['dev'], 'sda1')
+
+    def test_destroy_node(self):
+        """
+        Test destroy_node functionality.
+        """
+        node = Node(5, None, None, None, None, self.driver)
+        ret = self.driver.destroy_node(node)
+        self.assertTrue(ret)
+
+    def test_reboot_node(self):
+        """
+        Test reboot_node functionality.
+        """
+        node = Node(5, None, None, None, None, self.driver)
+        ret = self.driver.reboot_node(node)
+        self.assertTrue(ret)
 
     def test_list_nodes(self):
+        """
+        Test list_nodes functionality.
+        """
         nodes = self.driver.list_nodes()
+
         self.assertEqual(len(nodes), 2)
         node = nodes[0]
         self.assertEqual(node.id, '5')
-        self.assertEqual(node.name, 'MyCompute')
+        self.assertEqual(node.name, 'Compute 5')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].name, None)
+        self.assertEqual(node.public_ips[0].address, '192.168.0.1')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].name, None)
+        self.assertEqual(node.public_ips[1].address, '192.168.1.1')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.private_ips, [])
+        self.assertEqual(node.image.id, '5')
+        self.assertEqual(node.image.extra['dev'], 'sda1')
+        node = nodes[1]
+        self.assertEqual(node.id, '15')
+        self.assertEqual(node.name, 'Compute 15')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].name, None)
+        self.assertEqual(node.public_ips[0].address, '192.168.0.2')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].name, None)
+        self.assertEqual(node.public_ips[1].address, '192.168.1.2')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.private_ips, [])
+        self.assertEqual(node.image.id, '15')
+        self.assertEqual(node.image.extra['dev'], 'sda1')
 
-    def test_reboot_node(self):
+    def test_list_images(self):
+        """
+        Test list_images functionality.
+        """
+        images = self.driver.list_images()
+
+        self.assertEqual(len(images), 2)
+        image = images[0]
+        self.assertEqual(image.id, '5')
+        self.assertEqual(image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(image.extra['size'], '2048')
+        self.assertEqual(image.extra['url'],
+                         'file:///images/ubuntu/jaunty.img')
+        image = images[1]
+        self.assertEqual(image.id, '15')
+        self.assertEqual(image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(image.extra['size'], '2048')
+        self.assertEqual(image.extra['url'],
+                         'file:///images/ubuntu/jaunty.img')
+
+    def test_list_sizes(self):
+        """
+        Test list_sizes functionality.
+        """
+        sizes = self.driver.list_sizes()
+
+        self.assertEqual(len(sizes), 3)
+        size = sizes[0]
+        self.assertEqual(size.id, '1')
+        self.assertEqual(size.name, 'small')
+        self.assertEqual(size.ram, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+        size = sizes[1]
+        self.assertEqual(size.id, '2')
+        self.assertEqual(size.name, 'medium')
+        self.assertEqual(size.ram, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+        size = sizes[2]
+        self.assertEqual(size.id, '3')
+        self.assertEqual(size.name, 'large')
+        self.assertEqual(size.ram, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+
+    def test_list_locations(self):
+        """
+        Test list_locations functionality.
+        """
+        locations = self.driver.list_locations()
+
+        self.assertEqual(len(locations), 1)
+        location = locations[0]
+        self.assertEqual(location.id, '0')
+        self.assertEqual(location.name, '')
+        self.assertEqual(location.country, '')
+
+    def test_ex_list_networks(self):
+        """
+        Test ex_list_networks functionality.
+        """
+        networks = self.driver.ex_list_networks()
+
+        self.assertEqual(len(networks), 2)
+        network = networks[0]
+        self.assertEqual(network.id, '5')
+        self.assertEqual(network.name, 'Network 5')
+        self.assertEqual(network.address, '192.168.0.0')
+        self.assertEqual(network.size, '256')
+        network = networks[1]
+        self.assertEqual(network.id, '15')
+        self.assertEqual(network.name, 'Network 15')
+        self.assertEqual(network.address, '192.168.1.0')
+        self.assertEqual(network.size, '256')
+
+    def test_node_action(self):
+        """
+        Test ex_node_action functionality.
+        """
         node = Node(5, None, None, None, None, self.driver)
-        ret = self.driver.reboot_node(node)
+        ret = self.driver.ex_node_action(node, ACTION.STOP)
         self.assertTrue(ret)
 
+
+class OpenNebula_2_0_Tests(unittest.TestCase, TestCaseMixin):
+    """
+    OpenNebula.org test suite for OpenNebula v2.0 through v3.2.
+    """
+
+    def setUp(self):
+        """
+        Setup test environment.
+        """
+        OpenNebulaNodeDriver.connectionCls.conn_classes = (
+            OpenNebula_2_0_MockHttp, OpenNebula_2_0_MockHttp)
+        self.driver = OpenNebulaNodeDriver(*OPENNEBULA_PARAMS + ('2.0',))
+
+    def test_create_node(self):
+        """
+        Test create_node functionality.
+        """
+        image = NodeImage(id=5, name='Ubuntu 9.04 LAMP', driver=self.driver)
+        size = OpenNebulaNodeSize(id=1, name='small', ram=1024, cpu=1,
+                                  disk=None, bandwidth=None, price=None,
+                                  driver=self.driver)
+        networks = list()
+        networks.append(OpenNebulaNetwork(id=5, name='Network 5',
+                        address='192.168.0.0', size=256, driver=self.driver))
+        networks.append(OpenNebulaNetwork(id=15, name='Network 15',
+                        address='192.168.1.0', size=256, driver=self.driver))
+        context = {'hostname': 'compute-5'}
+
+        node = self.driver.create_node(name='Compute 5', image=image,
+                                       size=size, networks=networks,
+                                       context=context)
+
+        self.assertEqual(node.id, '5')
+        self.assertEqual(node.name, 'Compute 5')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].name, 'Network 5')
+        self.assertEqual(node.public_ips[0].address, '192.168.0.1')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[0].extra['mac'], '02:00:c0:a8:00:01')
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].name, 'Network 15')
+        self.assertEqual(node.public_ips[1].address, '192.168.1.1')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.public_ips[1].extra['mac'], '02:00:c0:a8:01:01')
+        self.assertEqual(node.private_ips, [])
+        self.assertTrue(len([size for size in self.driver.list_sizes() \
+                        if size.id == node.size.id]) == 1)
+        self.assertEqual(node.image.id, '5')
+        self.assertEqual(node.image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(node.image.extra['type'], 'DISK')
+        self.assertEqual(node.image.extra['target'], 'hda')
+        context = node.extra['context']
+        self.assertEqual(context['hostname'], 'compute-5')
+
     def test_destroy_node(self):
+        """
+        Test destroy_node functionality.
+        """
         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_reboot_node(self):
+        """
+        Test reboot_node functionality.
+        """
+        node = Node(5, None, None, None, None, self.driver)
+        ret = self.driver.reboot_node(node)
+        self.assertTrue(ret)
+
+    def test_list_nodes(self):
+        """
+        Test list_nodes functionality.
+        """
+        nodes = self.driver.list_nodes()
+
+        self.assertEqual(len(nodes), 2)
+        node = nodes[0]
+        self.assertEqual(node.id, '5')
+        self.assertEqual(node.name, 'Compute 5')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].name, 'Network 5')
+        self.assertEqual(node.public_ips[0].address, '192.168.0.1')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[0].extra['mac'], '02:00:c0:a8:00:01')
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].name, 'Network 15')
+        self.assertEqual(node.public_ips[1].address, '192.168.1.1')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.public_ips[1].extra['mac'], '02:00:c0:a8:01:01')
+        self.assertEqual(node.private_ips, [])
+        self.assertTrue(len([size for size in self.driver.list_sizes() \
+                        if size.id == node.size.id]) == 1)
+        self.assertEqual(node.image.id, '5')
+        self.assertEqual(node.image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(node.image.extra['type'], 'DISK')
+        self.assertEqual(node.image.extra['target'], 'hda')
+        context = node.extra['context']
+        self.assertEqual(context['hostname'], 'compute-5')
+        node = nodes[1]
+        self.assertEqual(node.id, '15')
+        self.assertEqual(node.name, 'Compute 15')
+        self.assertEqual(node.state,
+                         OpenNebulaNodeDriver.NODE_STATE_MAP['RUNNING'])
+        self.assertEqual(node.public_ips[0].id, '5')
+        self.assertEqual(node.public_ips[0].name, 'Network 5')
+        self.assertEqual(node.public_ips[0].address, '192.168.0.2')
+        self.assertEqual(node.public_ips[0].size, 1)
+        self.assertEqual(node.public_ips[0].extra['mac'], '02:00:c0:a8:00:02')
+        self.assertEqual(node.public_ips[1].id, '15')
+        self.assertEqual(node.public_ips[1].name, 'Network 15')
+        self.assertEqual(node.public_ips[1].address, '192.168.1.2')
+        self.assertEqual(node.public_ips[1].size, 1)
+        self.assertEqual(node.public_ips[1].extra['mac'], '02:00:c0:a8:01:02')
+        self.assertEqual(node.private_ips, [])
+        self.assertTrue(len([size for size in self.driver.list_sizes() \
+                        if size.id == node.size.id]) == 1)
+        self.assertEqual(node.image.id, '15')
+        self.assertEqual(node.image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(node.image.extra['type'], 'DISK')
+        self.assertEqual(node.image.extra['target'], 'hda')
+        context = node.extra['context']
+        self.assertEqual(context['hostname'], 'compute-15')
 
     def test_list_images(self):
+        """
+        Test list_images functionality.
+        """
         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 OpenNebula_3_0_Tests(unittest.TestCase):
-
-    def setUp(self):
-        OpenNebulaNodeDriver.connectionCls.conn_classes = (None,
-                                                           OpenNebulaMockHttp)
-        self.driver = OpenNebulaNodeDriver(*OPENNEBULA_PARAMS + ('3.0',))
+        self.assertEqual(image.id, '5')
+        self.assertEqual(image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(image.extra['description'],
+                         'Ubuntu 9.04 LAMP Description')
+        self.assertEqual(image.extra['type'], 'OS')
+        self.assertEqual(image.extra['size'], '2048')
+        image = images[1]
+        self.assertEqual(image.id, '15')
+        self.assertEqual(image.name, 'Ubuntu 9.04 LAMP')
+        self.assertEqual(image.extra['description'],
+                         'Ubuntu 9.04 LAMP Description')
+        self.assertEqual(image.extra['type'], 'OS')
+        self.assertEqual(image.extra['size'], '2048')
 
     def test_list_sizes(self):
+        """
+        Test list_sizes functionality.
+        """
         sizes = self.driver.list_sizes()
-        names = [s.name for s in sizes]
-        self.assertEqual(len(sizes), 4)
-        self.assertTrue('small' in names)
-        self.assertTrue('medium' in names)
-        self.assertTrue('large' in names)
-        self.assertTrue('custom' in names)
-        self.assertEqual([s for s in sizes if s.id == '3'][0].cpu, 8)
 
+        self.assertEqual(len(sizes), 4)
+        size = sizes[0]
+        self.assertEqual(size.id, '1')
+        self.assertEqual(size.name, 'small')
+        self.assertEqual(size.ram, 1024)
+        self.assertTrue(size.cpu is None or isinstance(size.cpu, int))
+        self.assertTrue(size.vcpu is None or isinstance(size.vcpu, int))
+        self.assertEqual(size.cpu, 1)
+        self.assertEqual(size.vcpu, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+        size = sizes[1]
+        self.assertEqual(size.id, '2')
+        self.assertEqual(size.name, 'medium')
+        self.assertEqual(size.ram, 4096)
+        self.assertTrue(size.cpu is None or isinstance(size.cpu, int))
+        self.assertTrue(size.vcpu is None or isinstance(size.vcpu, int))
+        self.assertEqual(size.cpu, 4)
+        self.assertEqual(size.vcpu, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+        size = sizes[2]
+        self.assertEqual(size.id, '3')
+        self.assertEqual(size.name, 'large')
+        self.assertEqual(size.ram, 8192)
+        self.assertTrue(size.cpu is None or isinstance(size.cpu, int))
+        self.assertTrue(size.vcpu is None or isinstance(size.vcpu, int))
+        self.assertEqual(size.cpu, 8)
+        self.assertEqual(size.vcpu, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+        size = sizes[3]
+        self.assertEqual(size.id, '4')
+        self.assertEqual(size.name, 'custom')
+        self.assertEqual(size.ram, 0)
+        self.assertEqual(size.cpu, 0)
+        self.assertEqual(size.vcpu, None)
+        self.assertEqual(size.disk, None)
+        self.assertEqual(size.bandwidth, None)
+        self.assertEqual(size.price, None)
+
+    def test_list_locations(self):
+        """
+        Test list_locations functionality.
+        """
+        locations = self.driver.list_locations()
+
+        self.assertEqual(len(locations), 1)
+        location = locations[0]
+        self.assertEqual(location.id, '0')
+        self.assertEqual(location.name, '')
+        self.assertEqual(location.country, '')
+
+    def test_ex_list_networks(self):
+        """
+        Test ex_list_networks functionality.
+        """
+        networks = self.driver.ex_list_networks()
+
+        self.assertEqual(len(networks), 2)
+        network = networks[0]
+        self.assertEqual(network.id, '5')
+        self.assertEqual(network.name, 'Network 5')
+        self.assertEqual(network.address, '192.168.0.0')
+        self.assertEqual(network.size, '256')
+        network = networks[1]
+        self.assertEqual(network.id, '15')
+        self.assertEqual(network.name, 'Network 15')
+        self.assertEqual(network.address, '192.168.1.0')
+        self.assertEqual(network.size, '256')
+
+
+class OpenNebula_1_4_MockHttp(MockHttp):
+    """
+    Mock HTTP server for testing v1.4 of the OpenNebula.org compute driver.
+    """
 
-class OpenNebulaMockHttp(MockHttp):
-
-    fixtures = ComputeFileFixtures('opennebula')
+    fixtures = ComputeFileFixtures('opennebula_1_4')
 
     def _compute(self, method, url, body, headers):
+        """
+        Compute pool resources.
+        """
         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')
+            body = self.fixtures.load('compute_5.xml')
             return (httplib.CREATED, body, {},
                     httplib.responses[httplib.CREATED])
 
     def _storage(self, method, url, body, headers):
+        """
+        Storage pool resources.
+        """
         if method == 'GET':
             body = self.fixtures.load('storage.xml')
             return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+        if method == 'POST':
+            body = self.fixtures.load('disk_5.xml')
+            return (httplib.CREATED, body, {},
+                    httplib.responses[httplib.CREATED])
+
+    def _network(self, method, url, body, headers):
+        """
+        Network pool resources.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('networks.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'POST':
+            body = self.fixtures.load('network_5.xml')
+            return (httplib.CREATED, body, {},
+                    httplib.responses[httplib.CREATED])
+
     def _compute_5(self, method, url, body, headers):
+        """
+        Compute entry resource.
+        """
         if method == 'GET':
-            body = self.fixtures.load('compute.xml')
+            body = self.fixtures.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.OK, body, {},
+                    httplib.responses[httplib.OK])
+
+    def _compute_15(self, method, url, body, headers):
+        """
+        Compute entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.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.OK, body, {},
+                    httplib.responses[httplib.OK])
+
+    def _storage_5(self, method, url, body, headers):
+        """
+        Storage entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('disk_5.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'DELETE':
+            body = ""
+            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.load('disk_15.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'DELETE':
+            body = ""
+            return (httplib.OK, body, {},
+                    httplib.responses[httplib.OK])
+
+    def _network_5(self, method, url, body, headers):
+        """
+        Network entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('network_5.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'DELETE':
+            body = ""
+            return (httplib.OK, body, {},
+                    httplib.responses[httplib.OK])
+
+    def _network_15(self, method, url, body, headers):
+        """
+        Network entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('network_15.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'DELETE':
+            body = ""
+            return (httplib.OK, body, {},
+                    httplib.responses[httplib.OK])
+
+
+class OpenNebula_2_0_MockHttp(MockHttp):
+    """
+    Mock HTTP server for testing v2.0 through v3.2 of the OpenNebula.org
+    compute driver.
+    """
+
+    fixtures = ComputeFileFixtures('opennebula_2_0')
+
+    def _compute(self, method, url, body, headers):
+        """
+        Compute pool resources.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('compute_collection.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'POST':
+            body = self.fixtures.load('compute_5.xml')
+            return (httplib.CREATED, body, {},
+                    httplib.responses[httplib.CREATED])
+
+    def _storage(self, method, url, body, headers):
+        """
+        Storage pool resources.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('storage_collection.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'POST':
+            body = self.fixtures.load('storage_5.xml')
+            return (httplib.CREATED, body, {},
+                    httplib.responses[httplib.CREATED])
+
+    def _network(self, method, url, body, headers):
+        """
+        Network pool resources.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('network_collection.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'POST':
+            body = self.fixtures.load('network_5.xml')
+            return (httplib.CREATED, body, {},
+                    httplib.responses[httplib.CREATED])
+
+    def _compute_5(self, method, url, body, headers):
+        """
+        Compute entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('compute_5.xml')
             return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
         if method == 'PUT':
@@ -130,19 +657,74 @@ class OpenNebulaMockHttp(MockHttp):
                     httplib.responses[httplib.NO_CONTENT])
 
     def _compute_15(self, method, url, body, headers):
+        """
+        Compute entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.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_5(self, method, url, body, headers):
+        """
+        Storage entry resource.
+        """
         if method == 'GET':
-            body = self.fixtures.load('compute.xml')
+            body = self.fixtures.load('storage_5.xml')
             return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-    def _storage_1(self, method, url, body, headers):
+        if method == 'DELETE':
+            body = ""
+            return (httplib.NO_CONTENT, body, {},
+                    httplib.responses[httplib.NO_CONTENT])
+
+    def _storage_15(self, method, url, body, headers):
+        """
+        Storage entry resource.
+        """
         if method == 'GET':
-            body = self.fixtures.load('disk.xml')
+            body = self.fixtures.load('storage_15.xml')
             return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-    def _storage_8(self, method, url, body, headers):
+        if method == 'DELETE':
+            body = ""
+            return (httplib.NO_CONTENT, body, {},
+                    httplib.responses[httplib.NO_CONTENT])
+
+    def _network_5(self, method, url, body, headers):
+        """
+        Network entry resource.
+        """
         if method == 'GET':
-            body = self.fixtures.load('disk.xml')
+            body = self.fixtures.load('network_5.xml')
             return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+        if method == 'DELETE':
+            body = ""
+            return (httplib.NO_CONTENT, body, {},
+                    httplib.responses[httplib.NO_CONTENT])
+
+    def _network_15(self, method, url, body, headers):
+        """
+        Network entry resource.
+        """
+        if method == 'GET':
+            body = self.fixtures.load('network_15.xml')
+            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+        if method == 'DELETE':
+            body = ""
+            return (httplib.NO_CONTENT, body, {},
+                    httplib.responses[httplib.NO_CONTENT])
+
 if __name__ == '__main__':
     sys.exit(unittest.main())



Mime
View raw message