You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2011/06/30 14:26:19 UTC

svn commit: r1141506 - in /libcloud/trunk: ./ libcloud/common/ libcloud/compute/ libcloud/compute/drivers/ libcloud/loadbalancer/ libcloud/loadbalancer/drivers/ test/common/ test/compute/ test/compute/fixtures/cloudstack/ test/loadbalancer/ test/loadba...

Author: tomaz
Date: Thu Jun 30 12:26:18 2011
New Revision: 1141506

URL: http://svn.apache.org/viewvc?rev=1141506&view=rev
Log:
Add Ninefold.com compute and load-balancer driver.

The patch has been contributed by Benno Rice <benno at jeamland dot net> and
it's part of LIBCLOUD-98.

Added:
    libcloud/trunk/libcloud/common/cloudstack.py
    libcloud/trunk/libcloud/compute/drivers/cloudstack.py
    libcloud/trunk/libcloud/compute/drivers/ninefold.py
    libcloud/trunk/libcloud/loadbalancer/drivers/cloudstack.py
    libcloud/trunk/libcloud/loadbalancer/drivers/ninefold.py
    libcloud/trunk/test/common/
    libcloud/trunk/test/common/__init__.py
    libcloud/trunk/test/common/test_cloudstack.py
    libcloud/trunk/test/compute/fixtures/cloudstack/
    libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail.json
    libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail2.json
    libcloud/trunk/test/compute/fixtures/cloudstack/destroyVirtualMachine_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail2.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listPublicIpAddresses_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listServiceOfferings_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listTemplates_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listVirtualMachines_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listZones_default.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail.json
    libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail2.json
    libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17164.json
    libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17165.json
    libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17166.json
    libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17177.json
    libcloud/trunk/test/compute/fixtures/cloudstack/rebootVirtualMachine_default.json
    libcloud/trunk/test/compute/test_cloudstack.py
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/assignToLoadBalancerRule_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/associateIpAddress_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/createLoadBalancerRule_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/deleteLoadBalancerRule_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/disassociateIpAddress_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRuleInstances_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRules_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listZones_default.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17340.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17341.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17342.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17344.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17346.json
    libcloud/trunk/test/loadbalancer/fixtures/cloudstack/removeFromLoadBalancerRule_default.json
    libcloud/trunk/test/loadbalancer/test_cloudstack.py
Modified:
    libcloud/trunk/libcloud/compute/providers.py
    libcloud/trunk/libcloud/compute/types.py
    libcloud/trunk/libcloud/loadbalancer/base.py
    libcloud/trunk/libcloud/loadbalancer/providers.py
    libcloud/trunk/libcloud/loadbalancer/types.py
    libcloud/trunk/setup.py

Added: libcloud/trunk/libcloud/common/cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/libcloud/common/cloudstack.py (added)
+++ libcloud/trunk/libcloud/common/cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,124 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import hashlib
+import hmac
+import time
+import urllib
+
+try:
+    import json
+except:
+    import simplejson as json
+
+from libcloud.common.base import ConnectionUserAndKey, Response
+from libcloud.common.types import MalformedResponseError
+
+class CloudStackResponse(Response):
+    def parse_body(self):
+        try:
+            body = json.loads(self.body)
+        except:
+            raise MalformedResponseError(
+                "Failed to parse JSON",
+                body=self.body,
+                driver=self.connection.driver)
+        return body
+
+    parse_error = parse_body
+
+class CloudStackConnection(ConnectionUserAndKey):
+    responseCls = CloudStackResponse
+
+    ASYNC_PENDING = 0
+    ASYNC_SUCCESS = 1
+    ASYNC_FAILURE = 2
+
+    def _make_signature(self, params):
+        signature = [(k.lower(), v) for k, v in params.items()]
+        signature.sort(key=lambda x: x[0])
+        signature = urllib.urlencode(signature)
+        signature = signature.lower().replace('+', '%20')
+        signature = hmac.new(self.key, msg=signature, digestmod=hashlib.sha1)
+        return base64.b64encode(signature.digest())
+
+    def add_default_params(self, params):
+        params['apiKey'] = self.user_id
+        params['response'] = 'json'
+
+        return params
+
+    def pre_connect_hook(self, params, headers):
+        params['signature'] = self._make_signature(params)
+
+        return params, headers
+
+    def _sync_request(self, command, **kwargs):
+        """This method handles synchronous calls which are generally fast
+           information retrieval requests and thus return 'quickly'."""
+
+        kwargs['command'] = command
+        result = self.request(self.driver.path, params=kwargs)
+        command = command.lower() + 'response'
+        if command not in result.object:
+            raise MalformedResponseError(
+                "Unknown response format",
+                body=result.body,
+                driver=self.driver)
+        result = result.object[command]
+        return result
+
+    def _async_request(self, command, **kwargs):
+        """This method handles asynchronous calls which are generally
+           requests for the system to do something and can thus take time.
+
+           In these cases the initial call will either fail fast and return
+           an error, or it can return a job ID.  We then poll for the status
+           of the job ID which can either be pending, successful or failed."""
+
+        result = self._sync_request(command, **kwargs)
+        job_id = result['jobid']
+        success = True
+
+        while True:
+            result = self._sync_request('queryAsyncJobResult', jobid=job_id)
+            status = result.get('jobstatus', self.ASYNC_PENDING)
+            if status != self.ASYNC_PENDING:
+                break
+            time.sleep(self.driver.async_poll_frequency)
+
+        if result['jobstatus'] == self.ASYNC_FAILURE:
+            raise Exception(result)
+
+        return result['jobresult']
+
+class CloudStackDriverMixIn(object):
+    host = None
+    path = None
+    async_poll_frequency = 1
+
+    connectionCls = CloudStackConnection
+
+    def __init__(self, key, secret=None, secure=True, host=None, port=None):
+        host = host or self.host
+        super(CloudStackDriverMixIn, self).__init__(key, secret, secure, host,
+                                                    port)
+
+    def _sync_request(self, command, **kwargs):
+        return self.connection._sync_request(command, **kwargs)
+
+    def _async_request(self, command, **kwargs):
+        return self.connection._async_request(command, **kwargs)

Added: libcloud/trunk/libcloud/compute/drivers/cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/cloudstack.py (added)
+++ libcloud/trunk/libcloud/compute/drivers/cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,267 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from libcloud.common.cloudstack import CloudStackConnection, \
+                                       CloudStackDriverMixIn
+from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation, \
+                                  NodeSize
+from libcloud.compute.types import DeploymentError, NodeState
+
+class CloudStackNode(Node):
+    "Subclass of Node so we can expose our extension methods."
+
+    def ex_allocate_public_ip(self):
+        "Allocate a public IP and bind it to this node."
+        return self.driver.ex_allocate_public_ip(self)
+
+    def ex_release_public_ip(self, address):
+        "Release a public IP that this node holds."
+        return self.driver.ex_release_public_ip(self, address)
+
+    def ex_add_ip_forwarding_rule(self, address, protocol, start_port,
+                                  end_port=None):
+        "Add a NAT/firewall forwarding rule for a port or ports."
+        return self.driver.ex_add_ip_forwarding_rule(self, address, protocol,
+                                                     start_port, end_port)
+
+    def ex_delete_ip_forwarding_rule(self, rule):
+        "Delete a NAT/firewall rule."
+        return self.driver.ex_delete_ip_forwarding_rule(self, rule)
+
+class CloudStackAddress(object):
+    "A public IP address."
+
+    def __init__(self, node, id, address):
+        self.node = node
+        self.id = id
+        self.address = address
+
+    def release(self):
+        self.node.ex_release_public_ip(self)
+
+    def __str__(self):
+        return self.address
+
+    def __eq__(self, other):
+        return self.__class__ is other.__class__ and self.id == other.id
+
+class CloudStackForwardingRule(object):
+    "A NAT/firewall forwarding rule."
+
+    def __init__(self, node, id, address, protocol, start_port, end_port=None):
+        self.node = node
+        self.id = id
+        self.address = address
+        self.protocol = protocol
+        self.start_port = start_port
+        self.end_port = end_port
+
+    def delete(self):
+        self.node.ex_delete_ip_forwarding_rule(self)
+
+    def __eq__(self, other):
+        return self.__class__ is other.__class__ and self.id == other.id
+
+class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
+    """Driver for the CloudStack API.
+
+    @cvar host: The host where the API can be reached.
+    @cvar path: The path where the API can be reached.
+    @cvar async_poll_frequency: How often (in seconds) to poll for async
+                                job completion.
+    @type async_poll_frequency: C{int}"""
+
+    api_name = 'cloudstack'
+
+    NODE_STATE_MAP = {
+        'Running': NodeState.RUNNING,
+        'Starting': NodeState.REBOOTING,
+        'Stopped': NodeState.TERMINATED,
+        'Stopping': NodeState.TERMINATED
+    }
+
+    def list_images(self, location=None):
+        args = {
+            'templatefilter': 'executable'
+        }
+        if location is not None:
+            args['zoneid'] = location.id
+        imgs = self._sync_request('listTemplates', **args)
+        images = []
+        for img in imgs['template']:
+            images.append(NodeImage(img['id'], img['name'], self, {
+                'hypervisor': img['hypervisor'],
+                'format': img['format'],
+                'os': img['ostypename'],
+            }))
+        return images
+
+    def list_locations(self):
+        locs = self._sync_request('listZones')
+        locations = []
+        for loc in locs['zone']:
+            locations.append(NodeLocation(loc['id'], loc['name'], 'AU', self))
+        return locations
+
+    def list_nodes(self):
+        vms = self._sync_request('listVirtualMachines')
+        addrs = self._sync_request('listPublicIpAddresses')
+
+        public_ips = {}
+        for addr in addrs['publicipaddress']:
+            if 'virtualmachineid' not in addr:
+                continue
+            vm_id = addr['virtualmachineid']
+            if vm_id not in public_ips:
+                public_ips[vm_id] = {}
+            public_ips[vm_id][addr['ipaddress']] = addr['id']
+
+        nodes = []
+
+        for vm in vms.get('virtualmachine', []):
+            node = CloudStackNode(
+                id=vm['id'],
+                name=vm.get('displayname', None),
+                state=self.NODE_STATE_MAP[vm['state']],
+                public_ip=public_ips.get(vm['id'], {}).keys(),
+                private_ip=[x['ipaddress'] for x in vm['nic']],
+                driver=self,
+                extra={
+                    'zoneid': vm['zoneid'],
+                }
+            )
+
+            addrs = public_ips.get(vm['id'], {}).items()
+            addrs = [CloudStackAddress(node, v, k) for k, v in addrs]
+            node.extra['ip_addresses'] = addrs
+
+            rules = []
+            for addr in addrs:
+                result = self._sync_request('listIpForwardingRules')
+                for r in result.get('ipforwardingrule', []):
+                    rule = CloudStackForwardingRule(node, r['id'], addr,
+                                                    r['protocol'].upper(),
+                                                    r['startport'],
+                                                    r['endport'])
+                    rules.append(rule)
+            node.extra['ip_forwarding_rules'] = rules
+
+            nodes.append(node)
+
+        return nodes
+
+    def list_sizes(self, location=None):
+        szs = self._sync_request('listServiceOfferings')
+        sizes = []
+        for sz in szs['serviceoffering']:
+            sizes.append(NodeSize(sz['id'], sz['name'], sz['memory'], 0, 0,
+                                  0, self))
+        return sizes
+
+    def create_node(self, name, size, image, location=None, **kwargs):
+        if location is None:
+            location = self.list_locations()[0]
+
+        networks = self._sync_request('listNetworks')
+        network_id = networks['network'][0]['id']
+
+        result = self._async_request('deployVirtualMachine',
+            name=name,
+            displayname=name,
+            serviceofferingid=size.id,
+            templateid=image.id,
+            zoneid=location.id,
+            networkids=network_id,
+        )
+
+        node = result['virtualmachine']
+
+        return Node(
+            id=node['id'],
+            name=node['displayname'],
+            state=self.NODE_STATE_MAP[node['state']],
+            public_ip=[],
+            private_ip=[x['ipaddress'] for x in node['nic']],
+            driver=self,
+            extra={
+                'zoneid': location.id,
+                'ip_addresses': [],
+                'forwarding_rules': [],
+            }
+        )
+
+    def destroy_node(self, node):
+        self._async_request('destroyVirtualMachine', id=node.id)
+        return True
+
+    def reboot_node(self, node):
+        self._async_request('rebootVirtualMachine', id=node.id)
+        return True
+
+    def ex_allocate_public_ip(self, node):
+        "Allocate a public IP and bind it to a node."
+
+        zoneid = node.extra['zoneid']
+        addr = self._async_request('associateIpAddress', zoneid=zoneid)
+        addr = addr['ipaddress']
+        result = self._sync_request('enableStaticNat', virtualmachineid=node.id,
+                                   ipaddressid=addr['id'])
+        if result.get('success', '').lower() != 'true':
+            return None
+
+        node.public_ip.append(addr['ipaddress'])
+        addr = CloudStackAddress(node, addr['id'], addr['ipaddress'])
+        node.extra['ip_addresses'].append(addr)
+        return addr
+
+    def ex_release_public_ip(self, node, address):
+        "Release a public IP."
+
+        node.extra['ip_addresses'].remove(address)
+        node.public_ip.remove(address.address)
+
+        self._async_request('disableStaticNat', ipaddressid=address.id)
+        self._async_request('disassociateIpAddress', id=address.id)
+        return True
+
+    def ex_add_ip_forwarding_rule(self, node, address, protocol,
+                                  start_port, end_port=None):
+        "Add a NAT/firewall forwarding rule."
+
+        protocol = protocol.upper()
+        if protocol not in ('TCP', 'UDP'):
+            return None
+
+        args = {
+            'ipaddressid': address.id,
+            'protocol': protocol,
+            'startport': int(start_port)
+        }
+        if end_port is not None:
+            args['endport'] = int(end_port)
+
+        result = self._async_request('createIpForwardingRule', **args)
+        result = result['ipforwardingrule']
+        rule = CloudStackForwardingRule(node, result['id'], address,
+                                        protocol, start_port, end_port)
+        node.extra['ip_forwarding_rules'].append(rule)
+        return rule
+
+    def ex_delete_ip_forwarding_rule(self, node, rule):
+        "Remove a NAT/firewall forwading rule."
+
+        node.extra['ip_forwarding_rules'].remove(rule)
+        self._async_request('deleteIpForwardingRule', id=rule.id)
+        return True

Added: libcloud/trunk/libcloud/compute/drivers/ninefold.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/drivers/ninefold.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/libcloud/compute/drivers/ninefold.py (added)
+++ libcloud/trunk/libcloud/compute/drivers/ninefold.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from libcloud.compute.providers import Provider
+
+from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver
+
+class NinefoldNodeDriver(CloudStackNodeDriver):
+    "Driver for Ninefold's Compute platform."
+
+    host = 'api.ninefold.com'
+    path = '/compute/v1.0/'
+
+    type = Provider.NINEFOLD
+    name = 'Ninefold'

Modified: libcloud/trunk/libcloud/compute/providers.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/providers.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/providers.py (original)
+++ libcloud/trunk/libcloud/compute/providers.py Thu Jun 30 12:26:18 2011
@@ -89,6 +89,8 @@ DRIVERS = {
         ('libcloud.compute.drivers.opsource', 'OpsourceNodeDriver'),
     Provider.OPENSTACK:
         ('libcloud.compute.drivers.rackspace', 'OpenStackNodeDriver'),
+    Provider.NINEFOLD:
+        ('libcloud.compute.drivers.ninefold', 'NinefoldNodeDriver'),
 }
 
 def get_driver(provider):

Modified: libcloud/trunk/libcloud/compute/types.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/compute/types.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/libcloud/compute/types.py (original)
+++ libcloud/trunk/libcloud/compute/types.py Thu Jun 30 12:26:18 2011
@@ -54,6 +54,7 @@ class Provider(object):
     @cvar NIMBUS: Nimbus
     @cvar BLUEBOX: Bluebox
     @cvar OPSOURCE: Opsource Cloud
+    @cvar NINEFOLD: Ninefold
     """
     DUMMY = 0
     EC2 = 1  # deprecated name
@@ -91,6 +92,7 @@ class Provider(object):
     OPENSTACK = 31
     SKALICLOUD = 32
     SERVERLOVE = 33
+    NINEFOLD = 34
 
 class NodeState(object):
     """

Modified: libcloud/trunk/libcloud/loadbalancer/base.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/base.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/base.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/base.py Thu Jun 30 12:26:18 2011
@@ -83,7 +83,7 @@ class Driver(object):
     _ALGORITHM_TO_VALUE_MAP = {}
     _VALUE_TO_ALGORITHM_MAP = {}
 
-    def __init__(self, key, secret=None, secure=True):
+    def __init__(self, key, secret=None, secure=True, host=None, port=None):
         self.key = key
         self.secret = secret
         args = [self.key]
@@ -93,6 +93,12 @@ class Driver(object):
 
         args.append(secure)
 
+        if host != None:
+            args.append(host)
+
+        if port != None:
+            args.append(port)
+
         self.connection = self.connectionCls(*args)
         self.connection.driver = self
         self.connection.connect()

Added: libcloud/trunk/libcloud/loadbalancer/drivers/cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/drivers/cloudstack.py (added)
+++ libcloud/trunk/libcloud/loadbalancer/drivers/cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,123 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from libcloud.common.cloudstack import CloudStackConnection, \
+                                       CloudStackDriverMixIn
+from libcloud.loadbalancer.base import LoadBalancer, Member, Driver, Algorithm
+from libcloud.loadbalancer.base import DEFAULT_ALGORITHM
+from libcloud.loadbalancer.types import State, LibcloudLBImmutableError
+from libcloud.utils import reverse_dict
+
+class CloudStackLBDriver(CloudStackDriverMixIn, Driver):
+    """Driver for CloudStack load balancers."""
+
+    api_name = 'cloudstack_lb'
+
+    _VALUE_TO_ALGORITHM_MAP = {
+        'roundrobin': Algorithm.ROUND_ROBIN,
+        'leastconn': Algorithm.LEAST_CONNECTIONS
+    }
+    _ALGORITHM_TO_VALUE_MAP = reverse_dict(_VALUE_TO_ALGORITHM_MAP)
+
+    LB_STATE_MAP = {
+        'Active': State.RUNNING,
+    }
+
+    def list_protocols(self):
+        """We don't actually have any protocol awareness beyond TCP."""
+        return [ 'tcp' ]
+
+    def list_balancers(self):
+        balancers = self._sync_request('listLoadBalancerRules')
+        balancers = balancers.get('loadbalancerrule', [])
+        return [self._to_balancer(balancer) for balancer in balancers]
+
+    def get_balancer(self, balancer_id):
+        balancer = self._sync_request('listLoadBalancerRules', id=balancer_id)
+        balancer = balancer.get('loadbalancerrule', [])
+        if not balancer:
+            raise Exception("no such load balancer: " + str(balancer_id))
+        return self._to_balancer(balancer[0])
+
+    def create_balancer(self, name, members, protocol='http', port=80,
+                        algorithm=DEFAULT_ALGORITHM, location=None,
+                        private_port=None):
+        if location is None:
+            locations = self._sync_request('listZones')
+            location = locations['zone'][0]['id']
+        else:
+            location = location.id
+        if private_port is None:
+            private_port = port
+
+        result = self._async_request('associateIpAddress', zoneid=location)
+        public_ip = result['ipaddress']
+
+        result = self._sync_request('createLoadBalancerRule',
+            algorithm=self._ALGORITHM_TO_VALUE_MAP[algorithm],
+            name=name,
+            privateport=private_port,
+            publicport=port,
+            publicipid=public_ip['id'],
+        )
+
+        balancer = self._to_balancer(result['loadbalancer'])
+
+        for member in members:
+            balancer.attach_member(member)
+
+        return balancer
+
+    def destroy_balancer(self, balancer):
+        self._async_request('deleteLoadBalancerRule', id=balancer.id)
+        self._async_request('disassociateIpAddress',
+                            id=balancer.ex_public_ip_id)
+
+    def balancer_attach_member(self, balancer, member):
+        member.port = balancer.ex_private_port
+        self._async_request('assignToLoadBalancerRule', id=balancer.id,
+                            virtualmachineids=member.id)
+        return True
+
+    def balancer_detach_member(self, balancer, member):
+        self._async_request('removeFromLoadBalancerRule', id=balancer.id,
+                            virtualmachineids=member.id)
+        return True
+
+    def balancer_list_members(self, balancer):
+        members = self._sync_request('listLoadBalancerRuleInstances',
+                                     id=balancer.id)
+        members = members['loadbalancerruleinstance']
+        return [self._to_member(m, balancer.ex_private_port) for m in members]
+
+    def _to_balancer(self, obj):
+        balancer = LoadBalancer(
+            id=obj['id'],
+            name=obj['name'],
+            state=self.LB_STATE_MAP.get(obj['state'], State.UNKNOWN),
+            ip=obj['publicip'],
+            port=obj['publicport'],
+            driver=self.connection.driver
+        )
+        balancer.ex_private_port = obj['privateport']
+        balancer.ex_public_ip_id = obj['publicipid']
+        return balancer
+
+    def _to_member(self, obj, port):
+        return Member(
+            id=obj['id'],
+            ip=obj['nic'][0]['ipaddress'],
+            port=port
+        )

Added: libcloud/trunk/libcloud/loadbalancer/drivers/ninefold.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/drivers/ninefold.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/drivers/ninefold.py (added)
+++ libcloud/trunk/libcloud/loadbalancer/drivers/ninefold.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from libcloud.loadbalancer.providers import Provider
+
+from libcloud.loadbalancer.drivers.cloudstack import CloudStackLBDriver
+
+class NinefoldLBDriver(CloudStackLBDriver):
+    "Driver for load balancers on Ninefold's Compute platform."
+
+    host = 'api.ninefold.com'
+    path = '/compute/v1.0/'
+
+    type = Provider.NINEFOLD
+    name = 'Ninefold LB'

Modified: libcloud/trunk/libcloud/loadbalancer/providers.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/providers.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/providers.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/providers.py Thu Jun 30 12:26:18 2011
@@ -27,6 +27,8 @@ DRIVERS = {
             ('libcloud.loadbalancer.drivers.rackspace', 'RackspaceLBDriver'),
         Provider.GOGRID:
             ('libcloud.loadbalancer.drivers.gogrid', 'GoGridLBDriver'),
+        Provider.NINEFOLD:
+            ('libcloud.loadbalancer.drivers.ninefold', 'NinefoldLBDriver'),
 }
 
 def get_driver(provider):

Modified: libcloud/trunk/libcloud/loadbalancer/types.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/loadbalancer/types.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/libcloud/loadbalancer/types.py (original)
+++ libcloud/trunk/libcloud/loadbalancer/types.py Thu Jun 30 12:26:18 2011
@@ -29,6 +29,7 @@ class LibcloudLBImmutableError(LibcloudL
 class Provider(object):
     RACKSPACE_US = 0
     GOGRID = 1
+    NINEFOLD = 2
 
 class State(object):
     """

Modified: libcloud/trunk/setup.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/setup.py?rev=1141506&r1=1141505&r2=1141506&view=diff
==============================================================================
--- libcloud/trunk/setup.py (original)
+++ libcloud/trunk/setup.py Thu Jun 30 12:26:18 2011
@@ -29,7 +29,8 @@ libcloud.utils.SHOW_DEPRECATION_WARNING 
 
 HTML_VIEWSOURCE_BASE = 'https://svn.apache.org/viewvc/libcloud/trunk'
 PROJECT_BASE_DIR = 'http://libcloud.apache.org'
-TEST_PATHS = [ 'test', 'test/compute', 'test/storage' , 'test/loadbalancer']
+TEST_PATHS = ['test', 'test/common', 'test/compute', 'test/storage',
+              'test/loadbalancer']
 DOC_TEST_MODULES = [ 'libcloud.compute.drivers.dummy',
                      'libcloud.storage.drivers.dummy' ]
 

Added: libcloud/trunk/test/common/__init__.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/common/__init__.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/common/__init__.py (added)
+++ libcloud/trunk/test/common/__init__.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,14 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.

Added: libcloud/trunk/test/common/test_cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/common/test_cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/common/test_cloudstack.py (added)
+++ libcloud/trunk/test/common/test_cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,181 @@
+import httplib
+import sys
+import unittest
+import urlparse
+
+try:
+    import json
+except:
+    import simplejson as json
+
+from libcloud.common.cloudstack import CloudStackConnection, CloudStackResponse
+from libcloud.common.types import MalformedResponseError
+
+from test import MockHttpTestCase
+
+async_delay = 0
+
+class CloudStackMockDriver(object):
+    host = 'nonexistant.'
+    path = '/path'
+    async_poll_frequency = 0
+
+    name = 'fake'
+
+    async_delay = 0
+
+class CloudStackCommonTest(unittest.TestCase):
+    def setUp(self):
+        CloudStackConnection.conn_classes = (None, CloudStackMockHttp)
+        self.connection = CloudStackConnection('apikey', 'secret',
+                                               host=CloudStackMockDriver.host)
+        self.driver = self.connection.driver = CloudStackMockDriver()
+
+    def test_sync_request_bad_response(self):
+        self.driver.path = '/bad/response'
+        try:
+            self.connection._sync_request('fake')
+        except Exception, e:
+            self.assertTrue(isinstance(e, MalformedResponseError))
+            return
+        self.assertTrue(False)
+
+    def test_sync_request(self):
+        self.driver.path = '/sync'
+        self.connection._sync_request('fake')
+
+    def test_async_request_successful(self):
+        self.driver.path = '/async/success'
+        result = self.connection._async_request('fake')
+        self.assertEqual(result, {'fake': 'result'})
+
+    def test_async_request_unsuccessful(self):
+        self.driver.path = '/async/fail'
+        try:
+            self.connection._async_request('fake')
+        except:
+            return
+        self.assertFalse(True)
+
+    def test_async_request_delayed(self):
+        global async_delay
+        self.driver.path = '/async/delayed'
+        async_delay = 2
+        self.connection._async_request('fake')
+        self.assertEqual(async_delay, 0)
+
+    def test_signature_algorithm(self):
+        cases = [
+            (
+                {
+                    'command': 'listVirtualMachines'
+                }, 'z/a9Y7J52u48VpqIgiwaGUMCso0='
+            ), (
+                {
+                    'command': 'deployVirtualMachine',
+                    'name': 'fred',
+                    'displayname': 'George',
+                    'serviceofferingid': 5,
+                    'templateid': 17,
+                    'zoneid': 23,
+                    'networkids': 42
+                 }, 'gHTo7mYmadZ+zluKHzlEKb1i/QU='
+            ), (
+                {
+                    'command': 'deployVirtualMachine',
+                    'name': 'fred',
+                    'displayname': 'George+Ringo',
+                    'serviceofferingid': 5,
+                    'templateid': 17,
+                    'zoneid': 23,
+                    'networkids': 42
+                 }, 'tAgfrreI1ZvWlWLClD3gu4+aKv4='
+            )
+        ]
+
+        connection = CloudStackConnection('fnord', 'abracadabra')
+        for case in cases:
+            params = connection.add_default_params(case[0])
+            self.assertEqual(connection._make_signature(params), case[1])
+
+class CloudStackMockHttp(MockHttpTestCase):
+    def _response(self, status, result, response):
+        return (status, json.dumps(result), result, response)
+
+    def _check_request(self, url):
+        url = urlparse.urlparse(url)
+        query = dict(urlparse.parse_qsl(url.query))
+
+        self.assertTrue('apiKey' in query)
+        self.assertTrue('command' in query)
+        self.assertTrue('response' in query)
+        self.assertTrue('signature' in query)
+
+        self.assertTrue(query['response'] == 'json')
+
+        return query
+
+    def _bad_response(self, method, url, body, headers):
+        self._check_request(url)
+        result = {'success': True}
+        return self._response(httplib.OK, result, httplib.responses[httplib.OK])
+
+    def _sync(self, method, url, body, headers):
+        query = self._check_request(url)
+        result = {query['command'].lower() + 'response': {}}
+        return self._response(httplib.OK, result, httplib.responses[httplib.OK])
+
+    def _async_success(self, method, url, body, headers):
+        query = self._check_request(url)
+        if query['command'].lower() == 'queryasyncjobresult':
+            self.assertEqual(query['jobid'], '42')
+            result = {
+                query['command'].lower() + 'response': {
+                    'jobstatus': 1,
+                    'jobresult': {'fake': 'result'}
+                }
+            }
+        else:
+            result = {query['command'].lower() + 'response': {'jobid': '42'}}
+        return self._response(httplib.OK, result, httplib.responses[httplib.OK])
+
+    def _async_fail(self, method, url, body, headers):
+        query = self._check_request(url)
+        if query['command'].lower() == 'queryasyncjobresult':
+            self.assertEqual(query['jobid'], '42')
+            result = {
+                query['command'].lower() + 'response': {
+                    'jobstatus': 2,
+                    'jobresult': {'fake': 'failresult'}
+                }
+            }
+        else:
+            result = {query['command'].lower() + 'response': {'jobid': '42'}}
+        return self._response(httplib.OK, result, httplib.responses[httplib.OK])
+
+    def _async_delayed(self, method, url, body, headers):
+        global async_delay
+
+        query = self._check_request(url)
+        if query['command'].lower() == 'queryasyncjobresult':
+            self.assertEqual(query['jobid'], '42')
+            if async_delay == 0:
+                result = {
+                    query['command'].lower() + 'response': {
+                        'jobstatus': 1,
+                        'jobresult': {'fake': 'result'}
+                    }
+                }
+            else:
+                result = {
+                    query['command'].lower() + 'response': {
+                        'jobstatus': 0,
+                    }
+                }
+                async_delay -= 1
+        else:
+            result = {query['command'].lower() + 'response': {'jobid': '42'}}
+        return self._response(httplib.OK, result, httplib.responses[httplib.OK])
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

Added: libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "deployvirtualmachineresponse" : {"jobid":17164,"id":2602} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "deployvirtualmachineresponse" : {"errorcode" : 431, "errortext" : "Unable to find service offering: 104"}  }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail2.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail2.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail2.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/deployVirtualMachine_deployfail2.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "deployvirtualmachineresponse" : {"jobid":17177,"id":2602} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/destroyVirtualMachine_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/destroyVirtualMachine_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/destroyVirtualMachine_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/destroyVirtualMachine_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "destroyvirtualmachineresponse" : {"jobid":17166} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listnetworksresponse" : { "network" : [  {"id":860,"name":"Virtual Network","displaytext":"A dedicated virtualized network for your account.  The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.","broadcastdomaintype":"Vlan","traffictype":"Guest","zoneid":1,"networkofferingid":6,"networkofferingname":"DefaultVirtualizedNetworkOffering","networkofferingdisplaytext":"Virtual Vlan","networkofferingavailability":"Required","isshared":false,"issystem":false,"state":"Implemented","related":860,"broadcasturi":"vlan://1459","dns1":"1.1.1.1","dns2":"1.1.1.2","type":"Virtual","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","isdefault":true,"service":[{"name":"Gateway"},{"name":"Firewall","capability":[{"name":"MultipleIps","value":"true"},{"name":"TrafficStatistics","value":"per public ip"},{"name":"StaticNat","value":"true"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedSourceNa
 tTypes","value":"per account"}]},{"name":"UserData"},{"name":"Dns"},{"name":"Dhcp"},{"name":"Lb","capability":[{"name":"TrafficStatistics","value":"per public ip"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn"}]}],"networkdomain":"cs363local","securitygroupenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listnetworksresponse" : { "network" : [  {"id":860,"name":"Virtual Network","displaytext":"A dedicated virtualized network for your account.  The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.","broadcastdomaintype":"Vlan","traffictype":"Guest","zoneid":1,"networkofferingid":6,"networkofferingname":"DefaultVirtualizedNetworkOffering","networkofferingdisplaytext":"Virtual Vlan","networkofferingavailability":"Required","isshared":false,"issystem":false,"state":"Implemented","related":860,"broadcasturi":"vlan://1459","dns1":"1.1.1.1","dns2":"1.1.1.2","type":"Virtual","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","isdefault":true,"service":[{"name":"Gateway"},{"name":"Firewall","capability":[{"name":"MultipleIps","value":"true"},{"name":"TrafficStatistics","value":"per public ip"},{"name":"StaticNat","value":"true"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedSourceNa
 tTypes","value":"per account"}]},{"name":"UserData"},{"name":"Dns"},{"name":"Dhcp"},{"name":"Lb","capability":[{"name":"TrafficStatistics","value":"per public ip"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn"}]}],"networkdomain":"cs363local","securitygroupenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail2.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail2.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail2.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listNetworks_deployfail2.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listnetworksresponse" : { "network" : [  {"id":860,"name":"Virtual Network","displaytext":"A dedicated virtualized network for your account.  The broadcast domain is contained within a VLAN and all public network access is routed out by a virtual router.","broadcastdomaintype":"Vlan","traffictype":"Guest","zoneid":1,"networkofferingid":6,"networkofferingname":"DefaultVirtualizedNetworkOffering","networkofferingdisplaytext":"Virtual Vlan","networkofferingavailability":"Required","isshared":false,"issystem":false,"state":"Implemented","related":860,"broadcasturi":"vlan://1459","dns1":"1.1.1.1","dns2":"1.1.1.2","type":"Virtual","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","isdefault":true,"service":[{"name":"Gateway"},{"name":"Firewall","capability":[{"name":"MultipleIps","value":"true"},{"name":"TrafficStatistics","value":"per public ip"},{"name":"StaticNat","value":"true"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedSourceNa
 tTypes","value":"per account"}]},{"name":"UserData"},{"name":"Dns"},{"name":"Dhcp"},{"name":"Lb","capability":[{"name":"TrafficStatistics","value":"per public ip"},{"name":"SupportedProtocols","value":"tcp,udp"},{"name":"SupportedLbAlgorithms","value":"roundrobin,leastconn"}]}],"networkdomain":"cs363local","securitygroupenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listPublicIpAddresses_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listPublicIpAddresses_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listPublicIpAddresses_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listPublicIpAddresses_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listpublicipaddressesresponse" : { "publicipaddress" : [  {"id":34000,"ipaddress":"1.1.1.49","allocated":"2011-06-23T05:20:39+0000","zoneid":1,"zonename":"Sydney","issourcenat":false,"account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","forvirtualnetwork":true,"isstaticnat":false,"associatednetworkid":860,"networkid":200,"state":"Allocated"}, {"id":33999,"ipaddress":"1.1.1.48","allocated":"2011-06-23T05:20:34+0000","zoneid":1,"zonename":"Sydney","issourcenat":false,"account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","forvirtualnetwork":true,"isstaticnat":false,"associatednetworkid":860,"networkid":200,"state":"Allocated"}, {"id":33998,"ipaddress":"1.1.1.47","allocated":"2011-06-23T05:20:30+0000","zoneid":1,"zonename":"Sydney","issourcenat":false,"account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","forvirtualnetwork":true,"isstaticnat":false,"associatednetworkid":860,"networkid":200,"state":"Allocated"}, {"id":3397
 0,"ipaddress":"1.1.1.19","allocated":"2011-06-20T04:08:34+0000","zoneid":1,"zonename":"Sydney","issourcenat":true,"account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","forvirtualnetwork":true,"isstaticnat":false,"associatednetworkid":860,"networkid":200,"state":"Allocated"} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listServiceOfferings_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listServiceOfferings_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listServiceOfferings_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listServiceOfferings_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listserviceofferingsresponse" : { "serviceoffering" : [  {"id":105,"name":"Compute Micro PRD","displaytext":"1CPU, 384MB, 80GB HDD","cpunumber":1,"cpuspeed":1200,"memory":384,"created":"2011-06-01T03:38:05+0000","storagetype":"shared","offerha":false,"domainid":14,"domain":"AA000062"}, {"id":70,"name":"Compute XLarge PRD","displaytext":"8CPU, 13.6GB RAM, 160GB Storage","cpunumber":8,"cpuspeed":1200,"memory":13928,"created":"2011-02-08T07:06:19+0000","storagetype":"shared","offerha":true,"domainid":14,"domain":"AA000062"}, {"id":69,"name":"Compute Large PRD","displaytext":"4CPU, 6.8GB RAM, 160GB Storage","cpunumber":4,"cpuspeed":1200,"memory":6964,"created":"2011-02-08T07:05:47+0000","storagetype":"shared","offerha":true,"domainid":14,"domain":"AA000062"}, {"id":68,"name":"Compute Medium PRD","displaytext":"2CPU, 3.4GB RAM, 160GB Storage","cpunumber":2,"cpuspeed":1200,"memory":3484,"created":"2011-02-08T07:05:03+0000","storagetype":"shared","offerha":true,"domainid":14,"do
 main":"AA000062"}, {"id":67,"name":"Compute Small PRD","displaytext":"1CPU, 1.7GB RAM, 160GB Storage","cpunumber":1,"cpuspeed":1200,"memory":1744,"created":"2011-02-08T07:03:44+0000","storagetype":"shared","offerha":true,"domainid":14,"domain":"AA000062"} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listTemplates_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listTemplates_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listTemplates_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listTemplates_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listtemplatesresponse" : { "template" : [  {"id":576,"name":"ESX[beta] Ubuntu 10.04.2 CHEF Small \\ Micro Optimised","displaytext":"ESX[beta] Ubuntu 10.04.2 CHEF Small \\ Micro Optimised","ispublic":true,"created":"2011-06-01T01:25:12+0000","isready":true,"passwordenabled":false,"format":"OVA","isfeatured":true,"crossZones":false,"ostypeid":126,"ostypename":"Ubuntu 10.04 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":702743552,"templatetype":"USER","hypervisor":"VMware","domain":"ROOT","domainid":1,"isextractable":false}, {"id":443,"name":"XEN Basic Windows Svr 2008 R2 x64 R2.1","displaytext":"XEN Basic Windows Svr 2008 R2 x64 R2.1","ispublic":true,"created":"2011-03-25T01:29:46+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":54,"ostypename":"Windows Server 2008 R2 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":171798691840,"templatetype":"USER","hypervisor":"XenServer","doma
 in":"ROOT","domainid":1,"isextractable":false}, {"id":474,"name":"XEN Basic Windows Svr 2003 SP2 STD","displaytext":"XEN Basic Windows Svr 2003 SP2 STD","ispublic":true,"created":"2011-04-07T10:38:45+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":89,"ostypename":"Windows Server 2003 Standard Edition(32-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":171798691840,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":444,"name":"ESX[beta] Windows 2003 x32 R2.0","displaytext":"ESX[beta] Windows 2003 x32 R2.0","ispublic":true,"created":"2011-03-25T01:34:00+0000","isready":true,"passwordenabled":false,"format":"OVA","isfeatured":true,"crossZones":false,"ostypeid":89,"ostypename":"Windows Server 2003 Standard Edition(32-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":876909056,"templatetype":"USER","hypervisor":"VMware","domain":"ROOT","domai
 nid":1,"isextractable":false}, {"id":447,"name":"ESX[beta] Windows 2008 x32 R2.0","displaytext":"ESX[beta] Windows 2008 x32 R2.0","ispublic":true,"created":"2011-03-25T01:45:23+0000","isready":true,"passwordenabled":false,"format":"OVA","isfeatured":true,"crossZones":false,"ostypeid":52,"ostypename":"Windows Server 2008 (32-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":3391547904,"templatetype":"USER","hypervisor":"VMware","domain":"ROOT","domainid":1,"isextractable":false}, {"id":462,"name":"ESX[beta] Centos 5.5 x64 R2.0","displaytext":"ESX[beta] Centos 5.5 x64 R2.0","ispublic":true,"created":"2011-03-28T05:06:36+0000","isready":true,"passwordenabled":false,"format":"OVA","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":2263178240,"templatetype":"USER","hypervisor":"VMware","domain":"ROOT","domainid":1,"isextractable":false}, {"id":425,"name":"XEN Windows 2008 x32 R2
 .0","displaytext":"XEN Windows 2008 x32 R2.0","ispublic":true,"created":"2011-03-22T03:22:21+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":52,"ostypename":"Windows Server 2008 (32-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":171798691840,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":461,"name":"ESX[beta] Basic Windows 2008 R2 x64","displaytext":"ESX[beta] Basic Windows 2008 R2 x64","ispublic":true,"created":"2011-03-26T22:48:48+0000","isready":true,"passwordenabled":false,"format":"OVA","isfeatured":true,"crossZones":false,"ostypeid":54,"ostypename":"Windows Server 2008 R2 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":3230146048,"templatetype":"USER","hypervisor":"VMware","domain":"ROOT","domainid":1,"isextractable":false}, {"id":575,"name":"Xen Ubuntu 10.04.2 CHEF Small \\ Micro Optimised","displaytext":"Xen Ubuntu 10
 .04.2 CHEF Small \\ Micro Optimised","ispublic":true,"created":"2011-06-01T01:06:21+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":85899345920,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":481,"name":"XEN Centos 5.4 x64 R2.0","displaytext":"XEN Centos 5.4 x64 R2.0","ispublic":true,"created":"2011-04-14T01:43:49+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":171966464000,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":421,"name":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","displaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","ispublic":true,"created":"
 2011-03-22T02:54:06+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":167772160000,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":423,"name":"XEN Basic Centos 5.5 x64 PV r2.2","displaytext":"XEN Basic Centos 5.5 x64 PV r2.2","ispublic":true,"created":"2011-03-22T02:59:31+0000","isready":true,"passwordenabled":false,"format":"VHD","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":167772160000,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false}, {"id":422,"name":"XEN OpenSUSE x64 11.4 R2.0","displaytext":"XEN OpenSUSE x64 11.4 R2.0","ispublic":true,"created":"2011-03-22T02:58:25+0000","isready":true,"passwordenabled":false,"format":"
 VHD","isfeatured":true,"crossZones":false,"ostypeid":12,"ostypename":"CentOS 5.3 (64-bit)","account":"admin","zoneid":1,"zonename":"Sydney","size":171966464000,"templatetype":"USER","hypervisor":"XenServer","domain":"ROOT","domainid":1,"isextractable":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listVirtualMachines_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listVirtualMachines_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listVirtualMachines_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listVirtualMachines_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listvirtualmachinesresponse" : { "virtualmachine" : [  {"id":2600,"name":"test","displayname":"test","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-23T05:06:42+0000","state":"Running","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"cpuused":"1.78%","networkkbsread":2,"networkkbswrite":2,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3891,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.2.1","ipaddress":"1.1.1.116","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"}, {"id":2601,"name":"test","displayname":"test","account":"fakeaccount","domainid":801,"domain":"AA000062-libclo
 ud-dev","created":"2011-06-23T05:09:44+0000","state":"Starting","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"jobid":17147,"jobstatus":0,"nic":[{"id":3892,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.2.1","ipaddress":"1.1.1.203","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listZones_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listZones_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listZones_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listZones_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listzonesresponse" : { "zone" : [  {"id":1,"name":"Sydney","networktype":"Advanced","securitygroupsenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listzonesresponse" : { "zone" : [  {"id":1,"name":"Sydney","networktype":"Advanced","securitygroupsenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail2.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail2.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail2.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/listZones_deployfail2.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listzonesresponse" : { "zone" : [  {"id":1,"name":"Sydney","networktype":"Advanced","securitygroupsenabled":false} ] } }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17164.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17164.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17164.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17164.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17164,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"virtualmachine":{"id":2602,"name":"fred","displayname":"fred","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-23T05:48:31+0000","state":"Running","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3893,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.1.1","ipaddress":"1.1.1.2","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"}}} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17165.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17165.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17165.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17165.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17165,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"virtualmachine":{"id":2602,"name":"fred","displayname":"fred","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-23T05:48:31+0000","state":"Running","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"cpuused":"0.14%","networkkbsread":2,"networkkbswrite":1,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3893,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.1.1","ipaddress":"1.1.1.2","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"}}} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17166.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17166.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17166.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17166.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17166,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"virtualmachine":{"id":2602,"name":"fred","displayname":"fred","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-23T05:48:31+0000","state":"Destroyed","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"cpuused":"0.13%","networkkbsread":2,"networkkbswrite":1,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3893,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.1.1","ipaddress":"1.1.1.2","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"}}} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17177.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17177.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17177.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/queryAsyncJobResult_17177.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17177,"jobstatus":2} }

Added: libcloud/trunk/test/compute/fixtures/cloudstack/rebootVirtualMachine_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/fixtures/cloudstack/rebootVirtualMachine_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/fixtures/cloudstack/rebootVirtualMachine_default.json (added)
+++ libcloud/trunk/test/compute/fixtures/cloudstack/rebootVirtualMachine_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "rebootvirtualmachineresponse" : {"jobid":17165} }

Added: libcloud/trunk/test/compute/test_cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/compute/test_cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/compute/test_cloudstack.py (added)
+++ libcloud/trunk/test/compute/test_cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,88 @@
+import httplib
+import sys
+import unittest
+import urlparse
+
+try:
+    import json
+except:
+    import simplejson as json
+
+from libcloud.compute.drivers.cloudstack import CloudStackNodeDriver
+from libcloud.compute.types import DeploymentError
+
+from test import MockHttpTestCase
+from test.compute import TestCaseMixin
+from test.file_fixtures import ComputeFileFixtures
+
+class CloudStackNodeDriverTest(unittest.TestCase, TestCaseMixin):
+    def setUp(self):
+        CloudStackNodeDriver.connectionCls.conn_classes = \
+            (None, CloudStackMockHttp)
+        self.driver = CloudStackNodeDriver('apikey', 'secret')
+        self.driver.path = '/test/path'
+        self.driver.type = -1
+        CloudStackMockHttp.fixture_tag = 'default'
+
+    def test_create_node_immediate_failure(self):
+        size = self.driver.list_sizes()[0]
+        image = self.driver.list_images()[0]
+        CloudStackMockHttp.fixture_tag = 'deployfail'
+        try:
+            node = self.driver.create_node(name='node-name',
+                                           image=image,
+                                           size=size)
+        except:
+            return
+        self.assertTrue(False)
+
+    def test_create_node_delayed_failure(self):
+        size = self.driver.list_sizes()[0]
+        image = self.driver.list_images()[0]
+        CloudStackMockHttp.fixture_tag = 'deployfail2'
+        try:
+            node = self.driver.create_node(name='node-name',
+                                           image=image,
+                                           size=size)
+        except:
+            return
+        self.assertTrue(False)
+
+class CloudStackMockHttp(MockHttpTestCase):
+    fixtures = ComputeFileFixtures('cloudstack')
+    fixture_tag = 'default'
+
+    def _load_fixture(self, fixture):
+        body = self.fixtures.load(fixture)
+        return body, json.loads(body)
+
+    def _test_path(self, method, url, body, headers):
+        url = urlparse.urlparse(url)
+        query = dict(urlparse.parse_qsl(url.query))
+
+        self.assertTrue('apiKey' in query)
+        self.assertTrue('command' in query)
+        self.assertTrue('response' in query)
+        self.assertTrue('signature' in query)
+
+        self.assertTrue(query['response'] == 'json')
+
+        del query['apiKey']
+        del query['response']
+        del query['signature']
+        command = query.pop('command')
+
+        if hasattr(self, '_cmd_' + command):
+            return getattr(self, '_cmd_' + command)(**query)
+        else:
+            fixture = command + '_' + self.fixture_tag + '.json'
+            body, obj = self._load_fixture(fixture)
+            return (httplib.OK, body, obj, httplib.responses[httplib.OK])
+
+    def _cmd_queryAsyncJobResult(self, jobid):
+        fixture = 'queryAsyncJobResult' + '_' + str(jobid) + '.json'
+        body, obj = self._load_fixture(fixture)
+        return (httplib.OK, body, obj, httplib.responses[httplib.OK])
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/assignToLoadBalancerRule_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/assignToLoadBalancerRule_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/assignToLoadBalancerRule_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/assignToLoadBalancerRule_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "assigntoloadbalancerruleresponse" : {"jobid":17341} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/associateIpAddress_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/associateIpAddress_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/associateIpAddress_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/associateIpAddress_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "associateipaddressresponse" : {"jobid":17346,"id":34000} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/createLoadBalancerRule_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/createLoadBalancerRule_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/createLoadBalancerRule_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/createLoadBalancerRule_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "createloadbalancerruleresponse" :  { "loadbalancer" : {"id":2253,"name":"fake","publicipid":34000,"publicip":"1.1.1.49","publicport":"80","privateport":"80","algorithm":"roundrobin","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","state":"Add"} }  }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/deleteLoadBalancerRule_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/deleteLoadBalancerRule_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/deleteLoadBalancerRule_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/deleteLoadBalancerRule_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "deleteloadbalancerruleresponse" : {"jobid":17342} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/disassociateIpAddress_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/disassociateIpAddress_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/disassociateIpAddress_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/disassociateIpAddress_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "disassociateipaddressresponse" : {"jobid":17344} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRuleInstances_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRuleInstances_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRuleInstances_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRuleInstances_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listloadbalancerruleinstancesresponse" : { "loadbalancerruleinstance" : [  {"id":2614,"name":"test_1308874974","displayname":"test_1308874974","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-24T00:22:56+0000","state":"Running","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"cpuused":"0.14%","networkkbsread":2185,"networkkbswrite":109,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3914,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.1.1","ipaddress":"1.1.3.122","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"}, {"id":2615,"name":"test_1308875456","displayname":"test_1308875
 456","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","created":"2011-06-24T00:30:57+0000","state":"Running","haenable":false,"zoneid":1,"zonename":"Sydney","templateid":421,"templatename":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","templatedisplaytext":"XEN Basic Ubuntu 10.04 Server x64 PV r2.0","passwordenabled":false,"serviceofferingid":105,"serviceofferingname":"Compute Micro PRD","cpunumber":1,"cpuspeed":1200,"memory":384,"cpuused":"0.14%","networkkbsread":1118,"networkkbswrite":75,"guestosid":12,"rootdeviceid":0,"rootdevicetype":"IscsiLUN","securitygroup":[],"nic":[{"id":3915,"networkid":860,"netmask":"255.255.240.0","gateway":"1.1.1.1","ipaddress":"1.1.2.62","traffictype":"Guest","type":"Virtual","isdefault":true}],"hypervisor":"XenServer"} ] } }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRules_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRules_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRules_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listLoadBalancerRules_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listloadbalancerrulesresponse" : { "loadbalancerrule" : [  {"id":2247,"name":"test","publicipid":34000,"publicip":"1.1.1.49","publicport":"80","privateport":"80","algorithm":"roundrobin","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","state":"Active"},{"id":2249,"name":"testmore","publicipid":34001,"publicip":"1.1.2.49","publicport":"80","privateport":"80","algorithm":"leastconn","account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","state":"Active"} ] } }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listZones_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listZones_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listZones_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/listZones_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "listzonesresponse" : { "zone" : [  {"id":1,"name":"Sydney","networktype":"Advanced","securitygroupsenabled":false} ] } }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17340.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17340.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17340.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17340.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17340,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success":true}} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17341.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17341.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17341.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17341.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17341,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success":true}} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17342.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17342.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17342.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17342.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17342,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success":true}} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17344.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17344.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17344.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17344.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17344,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"success":true}} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17346.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17346.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17346.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/queryAsyncJobResult_17346.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "queryasyncjobresultresponse" : {"jobid":17346,"jobstatus":1,"jobprocstatus":0,"jobresultcode":0,"jobresulttype":"object","jobresult":{"ipaddress":{"id":34000,"ipaddress":"1.1.1.49","allocated":"2011-06-24T05:52:55+0000","zoneid":1,"zonename":"Sydney","issourcenat":false,"account":"fakeaccount","domainid":801,"domain":"AA000062-libcloud-dev","forvirtualnetwork":true,"isstaticnat":false,"associatednetworkid":860,"networkid":200,"state":"Allocating"}}} }

Added: libcloud/trunk/test/loadbalancer/fixtures/cloudstack/removeFromLoadBalancerRule_default.json
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/fixtures/cloudstack/removeFromLoadBalancerRule_default.json?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/fixtures/cloudstack/removeFromLoadBalancerRule_default.json (added)
+++ libcloud/trunk/test/loadbalancer/fixtures/cloudstack/removeFromLoadBalancerRule_default.json Thu Jun 30 12:26:18 2011
@@ -0,0 +1 @@
+{ "removefromloadbalancerruleresponse" : {"jobid":17340} }

Added: libcloud/trunk/test/loadbalancer/test_cloudstack.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/test/loadbalancer/test_cloudstack.py?rev=1141506&view=auto
==============================================================================
--- libcloud/trunk/test/loadbalancer/test_cloudstack.py (added)
+++ libcloud/trunk/test/loadbalancer/test_cloudstack.py Thu Jun 30 12:26:18 2011
@@ -0,0 +1,95 @@
+import httplib
+import sys
+import unittest
+import urlparse
+
+try:
+    import json
+except:
+    import simplejson as json
+
+from libcloud.common.types import LibcloudError
+from libcloud.loadbalancer.base import LoadBalancer, Member, Algorithm
+from libcloud.loadbalancer.drivers.cloudstack import CloudStackLBDriver
+
+from test import MockHttpTestCase
+from test.file_fixtures import LoadBalancerFileFixtures
+
+class CloudStackLBTests(unittest.TestCase):
+    def setUp(self):
+        CloudStackLBDriver.connectionCls.conn_classes = \
+            (None, CloudStackMockHttp)
+        self.driver = CloudStackLBDriver('apikey', 'secret')
+        self.driver.path = '/test/path'
+        self.driver.type = -1
+        self.driver.name = 'CloudStack'
+        CloudStackMockHttp.fixture_tag = 'default'
+
+    def test_list_balancers(self):
+        balancers = self.driver.list_balancers()
+        for balancer in balancers:
+            self.assertTrue(isinstance(balancer, LoadBalancer))
+
+    def test_create_balancer(self):
+        members = [Member(1, '1.1.1.1', 80), Member(2, '1.1.1.2', 80)]
+        balancer = self.driver.create_balancer('fake', members)
+        self.assertTrue(isinstance(balancer, LoadBalancer))
+
+    def test_destroy_balancer(self):
+        balancer = self.driver.list_balancers()[0]
+        self.driver.destroy_balancer(balancer)
+
+    def test_balancer_attach_member(self):
+        balancer = self.driver.list_balancers()[0]
+        member = Member(id=1234, ip='1.1.1.1', port=80)
+        balancer.attach_member(member)
+
+    def test_balancer_detach_member(self):
+        balancer = self.driver.list_balancers()[0]
+        member = balancer.list_members()[0]
+        balancer.detach_member(member)
+
+    def test_balancer_list_members(self):
+        balancer = self.driver.list_balancers()[0]
+        members = balancer.list_members()
+        for member in members:
+            self.assertTrue(isinstance(member, Member))
+
+class CloudStackMockHttp(MockHttpTestCase):
+    fixtures = LoadBalancerFileFixtures('cloudstack')
+    fixture_tag = 'default'
+
+    def _load_fixture(self, fixture):
+        body = self.fixtures.load(fixture)
+        return body, json.loads(body)
+
+    def _test_path(self, method, url, body, headers):
+        url = urlparse.urlparse(url)
+        query = dict(urlparse.parse_qsl(url.query))
+
+        self.assertTrue('apiKey' in query)
+        self.assertTrue('command' in query)
+        self.assertTrue('response' in query)
+        self.assertTrue('signature' in query)
+
+        self.assertTrue(query['response'] == 'json')
+
+        del query['apiKey']
+        del query['response']
+        del query['signature']
+        command = query.pop('command')
+
+        if hasattr(self, '_cmd_' + command):
+            return getattr(self, '_cmd_' + command)(**query)
+        else:
+            fixture = command + '_' + self.fixture_tag + '.json'
+            body, obj = self._load_fixture(fixture)
+            return (httplib.OK, body, obj, httplib.responses[httplib.OK])
+
+    def _cmd_queryAsyncJobResult(self, jobid):
+        fixture = 'queryAsyncJobResult' + '_' + str(jobid) + '.json'
+        body, obj = self._load_fixture(fixture)
+        return (httplib.OK, body, obj, httplib.responses[httplib.OK])
+
+if __name__ == "__main__":
+    sys.exit(unittest.main())