You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2013/01/31 03:41:40 UTC

svn commit: r1440817 - in /libcloud/trunk: ./ libcloud/common/ libcloud/dns/ libcloud/dns/drivers/ libcloud/test/ libcloud/test/common/ libcloud/test/compute/ libcloud/test/dns/ libcloud/test/dns/fixtures/gandi/

Author: tomaz
Date: Thu Jan 31 02:41:40 2013
New Revision: 1440817

URL: http://svn.apache.org/viewvc?rev=1440817&view=rev
Log:
Also perform pep8 cleanup on existing gandi compute tests.

Add new DNS driver for Gandi.net provider.

Also perform pep8 cleanup on the existing compute driver tests.

Contributed by John Carr, part of LIBCLOUD-281.

Added:
    libcloud/trunk/libcloud/dns/drivers/gandi.py
    libcloud/trunk/libcloud/test/common/test_gandi.py
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_record.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_zone.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record_doesnotexist.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone_fail.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/get_zone.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records_empty.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_zones.xml
    libcloud/trunk/libcloud/test/dns/fixtures/gandi/new_version.xml
    libcloud/trunk/libcloud/test/dns/test_gandi.py
Modified:
    libcloud/trunk/CHANGES
    libcloud/trunk/libcloud/common/gandi.py
    libcloud/trunk/libcloud/dns/providers.py
    libcloud/trunk/libcloud/dns/types.py
    libcloud/trunk/libcloud/test/compute/test_gandi.py
    libcloud/trunk/libcloud/test/secrets.py-dist

Modified: libcloud/trunk/CHANGES
URL: http://svn.apache.org/viewvc/libcloud/trunk/CHANGES?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/CHANGES (original)
+++ libcloud/trunk/CHANGES Thu Jan 31 02:41:40 2013
@@ -173,6 +173,9 @@ Changes with Apache Libcloud in developm
     - Finish Amazon Route53 driver. (LIBCLOUD-132)
       [John Carr]
 
+    - Add new driver for Gandi provider (https://www.gandi.net). (LIBCLOUD-281)
+      [John Carr]
+
   *) Load-Balancer
 
     - Add new driver for AWS Elastic Load Balancing service. (LIBCLOUD-169)

Modified: libcloud/trunk/libcloud/common/gandi.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/common/gandi.py?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/libcloud/common/gandi.py (original)
+++ libcloud/trunk/libcloud/common/gandi.py Thu Jan 31 02:41:40 2013
@@ -93,7 +93,15 @@ class GandiConnection(ConnectionKey):
             return getattr(self._proxy, method)(self.key, *args)
         except xmlrpclib.Fault:
             e = sys.exc_info()[1]
-            raise GandiException(1001, e)
+            self.parse_error(e.faultCode, e.faultString)
+            raise GandiException(1001, e.faultString)
+
+    def parse_error(self, code, message):
+        """
+        This hook allows you to inspect any xmlrpclib errors and
+        potentially raise a more useful and specific exception.
+        """
+        pass
 
 
 class BaseGandiDriver(object):
@@ -121,8 +129,8 @@ class BaseGandiDriver(object):
         self.connection.driver = self
 
     # Specific methods for gandi
-    def _wait_operation(self, id, \
-        timeout=DEFAULT_TIMEOUT, check_interval=DEFAULT_INTERVAL):
+    def _wait_operation(self, id, timeout=DEFAULT_TIMEOUT,
+                        check_interval=DEFAULT_INTERVAL):
         """ Wait for an operation to succeed"""
 
         for i in range(0, timeout, check_interval):
@@ -131,7 +139,7 @@ class BaseGandiDriver(object):
 
                 if op['step'] == 'DONE':
                     return True
-                if op['step'] in  ['ERROR', 'CANCEL']:
+                if op['step'] in ['ERROR', 'CANCEL']:
                     return False
             except (KeyError, IndexError):
                 pass
@@ -172,8 +180,9 @@ class BaseObject(object):
         Note, for example, that this example will always produce the
         same UUID!
         """
-        return hashlib.sha1(b("%s:%s:%s" % \
-            (self.uuid_prefix, self.id, self.driver.type))).hexdigest()
+        hashstring = "%s:%s:%s" % \
+            (self.uuid_prefix, self.id, self.driver.type)
+        return hashlib.sha1(b(hashstring)).hexdigest()
 
 
 class IPAddress(BaseObject):
@@ -202,7 +211,7 @@ class NetworkInterface(BaseObject):
     uuid_prefix = 'if:'
 
     def __init__(self, id, state, mac_address, driver,
-            ips=None, node_id=None, extra=None):
+                 ips=None, node_id=None, extra=None):
         super(NetworkInterface, self).__init__(id, state, driver)
         self.mac = mac_address
         self.ips = ips or {}
@@ -225,5 +234,6 @@ class Disk(BaseObject):
         self.extra = extra or {}
 
     def __repr__(self):
-        return (('<Disk: id=%s, name=%s, state=%s, size=%s, driver=%s ...>')
+        return (
+            ('<Disk: id=%s, name=%s, state=%s, size=%s, driver=%s ...>')
             % (self.id, self.name, self.state, self.size, self.driver.name))

Added: libcloud/trunk/libcloud/dns/drivers/gandi.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/drivers/gandi.py?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/dns/drivers/gandi.py (added)
+++ libcloud/trunk/libcloud/dns/drivers/gandi.py Thu Jan 31 02:41:40 2013
@@ -0,0 +1,264 @@
+# 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.
+
+__all__ = [
+    'GandiDNSDriver'
+]
+
+from libcloud.common.gandi import BaseGandiDriver, GandiConnection
+from libcloud.dns.types import Provider, RecordType
+from libcloud.dns.types import RecordError
+from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError
+from libcloud.dns.base import DNSDriver, Zone, Record
+
+
+TTL_MIN = 30
+TTL_MAX = 2592000  # 30 days
+
+
+class NewZoneVersion(object):
+    """
+    Changes to a zone in the Gandi DNS service need to be wrapped in a new
+    version object. The changes are made to the new version, then that
+    version is made active.
+
+    In effect, this is a transaction.
+
+    Any calls made inside this context manager will be applied to a new version
+    id. If your changes are succesful (and only if they are successful) they
+    are activated.
+    """
+
+    def __init__(self, driver, zone):
+        self.driver = driver
+        self.connection = driver.connection
+        self.zone = zone
+
+    def __enter__(self):
+        zid = int(self.zone.id)
+        self.connection.set_context({'zone_id': self.zone.id})
+        vid = self.connection.request('domain.zone.version.new', zid)
+        self.vid = vid
+        return vid
+
+    def __exit__(self, type, value, traceback):
+        if not traceback:
+            zid = int(self.zone.id)
+            con = self.connection
+            con.set_context({'zone_id': self.zone.id})
+            con.request('domain.zone.version.set', zid, self.vid)
+
+
+class GandiDNSConnection(GandiConnection):
+
+    def parse_error(self, code, message):
+        if code == 581042:
+            zone_id = str(self.context.get('zone_id', None))
+            raise ZoneDoesNotExistError(value='', driver=self.driver,
+                                        zone_id=zone_id)
+
+
+class GandiDNSDriver(BaseGandiDriver, DNSDriver):
+    """
+    API reference can be found at:
+
+    http://doc.rpc.gandi.net/domain/reference.html
+    """
+
+    type = Provider.GANDI
+    name = 'Gandi DNS'
+    website = 'http://www.gandi.net/domain'
+
+    connectionCls = GandiDNSConnection
+
+    RECORD_TYPE_MAP = {
+        RecordType.NS: 'NS',
+        RecordType.MX: 'MX',
+        RecordType.A: 'A',
+        RecordType.AAAA: 'AAAA',
+        RecordType.CNAME: 'CNAME',
+        RecordType.TXT: 'TXT',
+        RecordType.SRV: 'SRV',
+        RecordType.SPF: 'SPF',
+        RecordType.WKS: 'WKS',
+        RecordType.LOC: 'LOC',
+    }
+
+    def _to_zone(self, zone):
+        return Zone(
+            id=zone['id'],
+            domain=zone['name'],
+            type='master',
+            ttl=0,
+            driver=self,
+            extra={}
+        )
+
+    def _to_zones(self, zones):
+        ret = []
+        for z in zones:
+            ret.append(self._to_zone(z))
+        return ret
+
+    def list_zones(self):
+        zones = self.connection.request('domain.zone.list')
+        return self._to_zones(zones)
+
+    def get_zone(self, zone_id):
+        zid = int(zone_id)
+        self.connection.set_context({'zone_id': zid})
+        zone = self.connection.request('domain.zone.info', zid)
+        return self._to_zone(zone)
+
+    def create_zone(self, domain, type='master', ttl=None, extra=None):
+        params = {'name': domain}
+        info = self.connection.request('domain.zone.create', params)
+        return self._to_zone(info)
+
+    def update_zone(self, zone, domain=None, type=None, ttl=None, extra=None):
+        zid = int(zone.id)
+        params = {'name': domain}
+        self.connection.set_context({'zone_id': zid})
+        zone = self.connection.request('domain.zone.update', zid, params)
+        return self._to_zone(zone)
+
+    def delete_zone(self, zone):
+        zid = int(zone.id)
+        self.connection.set_context({'zone_id': zid})
+        res = self.connection.request('domain.zone.delete', zid)
+        return res
+
+    def _to_record(self, record, zone):
+        return Record(
+            id='%s:%s' % (record['type'], record['name']),
+            name=record['name'],
+            type=self._string_to_record_type(record['type']),
+            data=record['value'],
+            zone=zone,
+            driver=self,
+            extra={'ttl': record['ttl']}
+        )
+
+    def _to_records(self, records, zone):
+        retval = []
+        for r in records:
+            retval.append(self._to_record(r, zone))
+        return retval
+
+    def list_records(self, zone):
+        zid = int(zone.id)
+        self.connection.set_context({'zone_id': zid})
+        records = self.connection.request('domain.zone.record.list', zid, 0)
+        return self._to_records(records, zone)
+
+    def get_record(self, zone_id, record_id):
+        zid = int(zone_id)
+        record_type, name = record_id.split(':', 1)
+        filter_opts = {
+            'name': name,
+            'type': record_type
+        }
+        self.connection.set_context({'zone_id': zid})
+        records = self.connection.request('domain.zone.record.list',
+                                          zid, 0, filter_opts)
+
+        if len(records) == 0:
+            raise RecordDoesNotExistError(value='', driver=self,
+                                          record_id=record_id)
+
+        return self._to_record(records[0], self.get_zone(zone_id))
+
+    def _validate_record(self, record_id, name, record_type, data, extra):
+        if len(data) > 1024:
+            raise RecordError('Record data must be <= 1024 characters',
+                              driver=self, record_id=record_id)
+        if extra and 'ttl' in extra:
+            if extra['ttl'] < TTL_MIN:
+                raise RecordError('TTL must be at least 30 seconds',
+                                  driver=self, record_id=record_id)
+            if extra['ttl'] > TTL_MAX:
+                raise RecordError('TTL must not excdeed 30 days',
+                                  driver=self, record_id=record_id)
+
+    def create_record(self, name, zone, type, data, extra=None):
+        self._validate_record(None, name, type, data, extra)
+
+        zid = int(zone.id)
+
+        create = {
+            'name': name,
+            'type': self.RECORD_TYPE_MAP[type],
+            'value': data
+        }
+
+        if 'ttl' in extra:
+            create['ttl'] = extra['ttl']
+
+        with NewZoneVersion(self, zone) as vid:
+            con = self.connection
+            con.set_context({'zone_id': zid})
+            rec = con.request('domain.zone.record.add',
+                              zid, vid, create)
+
+        return self._to_record(rec, zone)
+
+    def update_record(self, record, name, type, data, extra):
+        self._validate_record(record.id, name, type, data, extra)
+
+        filter_opts = {
+            'name': record.name,
+            'type': self.RECORD_TYPE_MAP[record.type]
+        }
+
+        update = {
+            'name': name,
+            'type': self.RECORD_TYPE_MAP[type],
+            'value': data
+        }
+
+        if 'ttl' in extra:
+            update['ttl'] = extra['ttl']
+
+        zid = int(record.zone.id)
+
+        with NewZoneVersion(self, record.zone) as vid:
+            con = self.connection
+            con.set_context({'zone_id': zid})
+            con.request('domain.zone.record.delete',
+                        zid, vid, filter_opts)
+            res = con.request('domain.zone.record.add',
+                              zid, vid, update)
+
+        return self._to_record(res, record.zone)
+
+    def delete_record(self, record):
+        zid = int(record.zone.id)
+
+        filter_opts = {
+            'name': record.name,
+            'type': self.RECORD_TYPE_MAP[record.type]
+        }
+
+        with NewZoneVersion(self, record.zone) as vid:
+            con = self.connection
+            con.set_context({'zone_id': zid})
+            count = con.request('domain.zone.record.delete',
+                                zid, vid, filter_opts)
+
+        if count == 1:
+            return True
+
+        raise RecordDoesNotExistError(value='No such record', driver=self,
+                                      record_id=record.id)

Modified: libcloud/trunk/libcloud/dns/providers.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/providers.py?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/providers.py (original)
+++ libcloud/trunk/libcloud/dns/providers.py Thu Jan 31 02:41:40 2013
@@ -32,6 +32,8 @@ DRIVERS = {
         ('libcloud.dns.drivers.hostvirtual', 'HostVirtualDNSDriver'),
     Provider.ROUTE53:
         ('libcloud.dns.drivers.route53', 'Route53DNSDriver'),
+    Provider.GANDI:
+        ('libcloud.dns.drivers.gandi', 'GandiDNSDriver')
 }
 
 

Modified: libcloud/trunk/libcloud/dns/types.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/dns/types.py?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/libcloud/dns/types.py (original)
+++ libcloud/trunk/libcloud/dns/types.py Thu Jan 31 02:41:40 2013
@@ -35,6 +35,7 @@ class Provider(object):
     RACKSPACE_UK = 'rackspace_uk'
     ROUTE53 = 'route53'
     HOSTVIRTUAL = 'hostvirtual'
+    GANDI = 'gandi'
 
 
 class RecordType(object):
@@ -57,6 +58,8 @@ class RecordType(object):
     REDIRECT = 13
     GEO = 14
     URL = 15
+    WKS = 16
+    LOC = 17
 
     @classmethod
     def __repr__(self, value):
@@ -67,7 +70,7 @@ class RecordType(object):
 class ZoneError(LibcloudError):
     error_type = 'ZoneError'
     kwargs = ('zone_id', )
-    
+
     def __init__(self, value, driver, zone_id):
         self.zone_id = zone_id
         super(ZoneError, self).__init__(value=value, driver=driver)

Added: libcloud/trunk/libcloud/test/common/test_gandi.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/common/test_gandi.py?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/common/test_gandi.py (added)
+++ libcloud/trunk/libcloud/test/common/test_gandi.py Thu Jan 31 02:41:40 2013
@@ -0,0 +1,33 @@
+import sys
+import unittest
+
+from xml.etree import ElementTree as ET
+
+from libcloud.utils.py3 import xmlrpclib
+
+
+class MockGandiTransport(xmlrpclib.Transport):
+
+    def request(self, host, handler, request_body, verbose=0):
+        self.verbose = 0
+        method = ET.XML(request_body).find('methodName').text
+        mock = self.mockCls(host, 80)
+        mock.request('POST', '%s/%s' % (handler, method))
+        resp = mock.getresponse()
+
+        if sys.version[0] == '2' and sys.version[2] == '7':
+            response = self.parse_response(resp)
+        else:
+            response = self.parse_response(resp.body)
+        return response
+
+
+class BaseGandiTests(unittest.TestCase):
+
+    def setUp(self):
+        d = self.driverCls
+        t = self.transportCls
+        t.mockCls.type = None
+        d.connectionCls.proxyCls.transportCls = \
+            [t, t]
+        self.driver = d(*self.params)

Modified: libcloud/trunk/libcloud/test/compute/test_gandi.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/compute/test_gandi.py?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/compute/test_gandi.py (original)
+++ libcloud/trunk/libcloud/test/compute/test_gandi.py Thu Jan 31 02:41:40 2013
@@ -21,7 +21,7 @@ import string
 from libcloud.utils.py3 import httplib
 from libcloud.utils.py3 import xmlrpclib
 
-from libcloud.compute.drivers.gandi import GandiNodeDriver as Gandi
+from libcloud.compute.drivers.gandi import GandiNodeDriver
 from libcloud.compute.base import StorageVolume
 from libcloud.common.gandi import GandiException
 from libcloud.compute.types import NodeState
@@ -30,32 +30,113 @@ from xml.etree import ElementTree as ET
 from libcloud.test import MockHttp
 from libcloud.test.file_fixtures import ComputeFileFixtures
 from libcloud.test.secrets import GANDI_PARAMS
+from libcloud.test.common.test_gandi import MockGandiTransport, BaseGandiTests
 
 
-class MockGandiTransport(xmlrpclib.Transport):
+class GandiMockHttp(MockHttp):
 
-    def request(self, host, handler, request_body, verbose=0):
-        self.verbose = 0
-        method = ET.XML(request_body).find('methodName').text
-        mock = GandiMockHttp(host, 80)
-        mock.request('POST', "%s/%s" % (handler, method))
-        resp = mock.getresponse()
-
-        if sys.version[0] == '2' and sys.version[2] == '7':
-            response = self.parse_response(resp)
-        else:
-            response = self.parse_response(resp.body)
-        return response
+    fixtures = ComputeFileFixtures('gandi')
 
+    def _xmlrpc__datacenter_list(self, method, url, body, headers):
+        body = self.fixtures.load('datacenter_list.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-class GandiTests(unittest.TestCase):
+    def _xmlrpc__image_list(self, method, url, body, headers):
+        body = self.fixtures.load('image_list_dc0.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-    node_name = 'test2'
+    def _xmlrpc__vm_list(self, method, url, body, headers):
+        body = self.fixtures.load('vm_list.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__ip_list(self, method, url, body, headers):
+        body = self.fixtures.load('ip_list.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__account_info(self, method, url, body, headers):
+        body = self.fixtures.load('account_info.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_info(self, method, url, body, headers):
+        body = self.fixtures.load('vm_info.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_delete(self, method, url, body, headers):
+        body = self.fixtures.load('vm_delete.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__operation_info(self, method, url, body, headers):
+        body = self.fixtures.load('operation_info.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_create_from(self, method, url, body, headers):
+        body = self.fixtures.load('vm_create_from.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_reboot(self, method, url, body, headers):
+        body = self.fixtures.load('vm_reboot.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_stop(self, method, url, body, headers):
+        body = self.fixtures.load('vm_stop.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__iface_list(self, method, url, body, headers):
+        body = self.fixtures.load('iface_list.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__disk_list(self, method, url, body, headers):
+        body = self.fixtures.load('disk_list.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_iface_attach(self, method, url, body, headers):
+        body = self.fixtures.load('iface_attach.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_iface_detach(self, method, url, body, headers):
+        body = self.fixtures.load('iface_detach.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_disk_attach(self, method, url, body, headers):
+        body = self.fixtures.load('disk_attach.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__vm_disk_detach(self, method, url, body, headers):
+        body = self.fixtures.load('disk_detach.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-    def setUp(self):
-        Gandi.connectionCls.proxyCls.transportCls = \
-            [MockGandiTransport, MockGandiTransport]
-        self.driver = Gandi(*GANDI_PARAMS)
+    def _xmlrpc__disk_create(self, method, url, body, headers):
+        body = self.fixtures.load('disk_create.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__disk_create_from(self, method, url, body, headers):
+        body = self.fixtures.load('disk_create_from.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__disk_info(self, method, url, body, headers):
+        body = self.fixtures.load('disk_info.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__disk_update(self, method, url, body, headers):
+        body = self.fixtures.load('disk_update.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__disk_delete(self, method, url, body, headers):
+        body = self.fixtures.load('disk_delete.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+class DummyTransport(MockGandiTransport):
+    mockCls = GandiMockHttp
+
+
+class GandiTests(BaseGandiTests):
+
+    driverCls = GandiNodeDriver
+    transportCls = DummyTransport
+    params = GANDI_PARAMS
+
+    node_name = 'test2'
 
     def test_list_nodes(self):
         nodes = self.driver.list_nodes()
@@ -64,12 +145,12 @@ class GandiTests(unittest.TestCase):
 
     def test_list_locations(self):
         loc = list(filter(lambda x: 'france' in x.country.lower(),
-            self.driver.list_locations()))[0]
+                          self.driver.list_locations()))[0]
         self.assertEqual(loc.country, 'France')
 
     def test_list_images(self):
         loc = list(filter(lambda x: 'france' in x.country.lower(),
-            self.driver.list_locations()))[0]
+                          self.driver.list_locations()))[0]
         images = self.driver.list_images(loc)
         self.assertTrue(len(images) > 2)
 
@@ -79,7 +160,8 @@ class GandiTests(unittest.TestCase):
 
     def test_destroy_node_running(self):
         nodes = self.driver.list_nodes()
-        test_node = list(filter(lambda x: x.state == NodeState.RUNNING, nodes))[0]
+        test_node = list(filter(lambda x: x.state == NodeState.RUNNING,
+                                nodes))[0]
         self.assertTrue(self.driver.destroy_node(test_node))
 
     def test_destroy_node_halted(self):
@@ -90,29 +172,34 @@ class GandiTests(unittest.TestCase):
 
     def test_reboot_node(self):
         nodes = self.driver.list_nodes()
-        test_node = list(filter(lambda x: x.state == NodeState.RUNNING, nodes))[0]
+        test_node = list(filter(lambda x: x.state == NodeState.RUNNING,
+                                nodes))[0]
         self.assertTrue(self.driver.reboot_node(test_node))
 
     def test_create_node(self):
         login = 'libcloud'
         passwd = ''.join(random.choice(string.ascii_letters)
-            for i in range(10))
+                         for i in range(10))
+
         # Get france datacenter
         loc = list(filter(lambda x: 'france' in x.country.lower(),
-            self.driver.list_locations()))[0]
+                          self.driver.list_locations()))[0]
+
         # Get a debian image
         images = self.driver.list_images(loc)
         images = [x for x in images if x.name.lower().startswith('debian')]
         img = list(filter(lambda x: '5' in x.name, images))[0]
+
         # Get a configuration size
         size = self.driver.list_sizes()[0]
         node = self.driver.create_node(name=self.node_name, login=login,
-            password=passwd, image=img, location=loc, size=size)
+                                       password=passwd, image=img,
+                                       location=loc, size=size)
         self.assertEqual(node.name, self.node_name)
 
     def test_create_volume(self):
         loc = list(filter(lambda x: 'france' in x.country.lower(),
-            self.driver.list_locations()))[0]
+                          self.driver.list_locations()))[0]
         volume = self.driver.create_volume(
             size=1024, name='libcloud', location=loc)
         self.assertEqual(volume.name, 'libcloud')
@@ -125,7 +212,7 @@ class GandiTests(unittest.TestCase):
     def test_destroy_volume(self):
         volumes = self.driver.list_volumes()
         test_vol = list(filter(lambda x: x.name == 'test_disk',
-                                volumes))[0]
+                               volumes))[0]
         self.assertTrue(self.driver.destroy_volume(test_vol))
 
     def test_attach_volume(self):
@@ -160,104 +247,12 @@ class GandiTests(unittest.TestCase):
         disks = self.driver.list_volumes()
         self.assertTrue(self.driver.ex_snapshot_disk(disks[2]))
         self.assertRaises(GandiException,
-            self.driver.ex_snapshot_disk, disks[0])
+                          self.driver.ex_snapshot_disk, disks[0])
 
     def test_ex_update_disk(self):
         disks = self.driver.list_volumes()
         self.assertTrue(self.driver.ex_update_disk(disks[0], new_size=4096))
 
 
-class GandiMockHttp(MockHttp):
-
-    fixtures = ComputeFileFixtures('gandi')
-
-    def _xmlrpc__datacenter_list(self, method, url, body, headers):
-        body = self.fixtures.load('datacenter_list.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__image_list(self, method, url, body, headers):
-        body = self.fixtures.load('image_list_dc0.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_list(self, method, url, body, headers):
-        body = self.fixtures.load('vm_list.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__ip_list(self, method, url, body, headers):
-        body = self.fixtures.load('ip_list.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__account_info(self, method, url, body, headers):
-        body = self.fixtures.load('account_info.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_info(self, method, url, body, headers):
-        body = self.fixtures.load('vm_info.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_delete(self, method, url, body, headers):
-        body = self.fixtures.load('vm_delete.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__operation_info(self, method, url, body, headers):
-        body = self.fixtures.load('operation_info.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_create_from(self, method, url, body, headers):
-        body = self.fixtures.load('vm_create_from.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_reboot(self, method, url, body, headers):
-        body = self.fixtures.load('vm_reboot.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_stop(self, method, url, body, headers):
-        body = self.fixtures.load('vm_stop.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__iface_list(self, method, url, body, headers):
-        body = self.fixtures.load('iface_list.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_list(self, method, url, body, headers):
-        body = self.fixtures.load('disk_list.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_iface_attach(self, method, url, body, headers):
-        body = self.fixtures.load('iface_attach.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_iface_detach(self, method, url, body, headers):
-        body = self.fixtures.load('iface_detach.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_disk_attach(self, method, url, body, headers):
-        body = self.fixtures.load('disk_attach.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__vm_disk_detach(self, method, url, body, headers):
-        body = self.fixtures.load('disk_detach.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_create(self, method, url, body, headers):
-        body = self.fixtures.load('disk_create.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_create_from(self, method, url, body, headers):
-        body = self.fixtures.load('disk_create_from.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_info(self, method, url, body, headers):
-        body = self.fixtures.load('disk_info.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_update(self, method, url, body, headers):
-        body = self.fixtures.load('disk_update.xml')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _xmlrpc__disk_delete(self, method, url, body, headers):
-            body = self.fixtures.load('disk_delete.xml')
-            return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
 if __name__ == '__main__':
     sys.exit(unittest.main())

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_record.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_record.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_record.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_record.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,31 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <struct>
+               <member>
+                  <name>id</name>
+                  <value><int>47234</int></value>
+               </member>
+               <member>
+                  <name>name</name>
+                  <value><string>www</string></value>
+               </member>
+               <member>
+                  <name>ttl</name>
+                  <value><int>0</int></value>
+               </member>
+               <member>
+                  <name>type</name>
+                  <value><string>A</string></value>
+               </member>
+               <member>
+                  <name>value</name>
+                  <value><string>127.0.0.1</string></value>
+               </member>
+            </struct>
+         </value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_zone.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_zone.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_zone.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/create_zone.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,43 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <struct>
+               <member>
+                  <name>date_updated</name>
+                  <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+               </member>
+               <member>
+                  <name>domains</name>
+                  <value><int>0</int></value>
+               </member>
+               <member>
+                  <name>id</name>
+                  <value><int>47234</int></value>
+               </member>
+               <member>
+                  <name>name</name>
+                  <value><string>t.com</string></value>
+               </member>
+               <member>
+                  <name>owner</name>
+                  <value><string>AB3917-GANDI</string></value>
+               </member>
+               <member>
+                  <name>public</name>
+                  <value><boolean>0</boolean></value>
+               </member>
+               <member>
+                  <name>version</name>
+                  <value><int>1</int></value>
+               </member>
+               <member>
+                  <name>versions</name>
+                  <value><array><data></data></array></value>
+               </member>
+            </struct>
+         </value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value><int>1</int></value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record_doesnotexist.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record_doesnotexist.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record_doesnotexist.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_record_doesnotexist.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value><int>0</int></value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value><boolean>1</boolean></value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone_fail.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone_fail.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone_fail.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/delete_zone_fail.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value><boolean>0</boolean></value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/get_zone.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/get_zone.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/get_zone.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/get_zone.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,43 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <struct>
+               <member>
+                  <name>date_updated</name>
+                  <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+               </member>
+               <member>
+                  <name>domains</name>
+                  <value><int>0</int></value>
+               </member>
+               <member>
+                  <name>id</name>
+                  <value><int>47234</int></value>
+               </member>
+               <member>
+                  <name>name</name>
+                  <value><string>t.com</string></value>
+               </member>
+               <member>
+                  <name>owner</name>
+                  <value><string>AB3917-GANDI</string></value>
+               </member>
+               <member>
+                  <name>public</name>
+                  <value><boolean>0</boolean></value>
+               </member>
+               <member>
+                  <name>version</name>
+                  <value><int>1</int></value>
+               </member>
+               <member>
+                  <name>versions</name>
+                  <value><array><data></data></array></value>
+               </member>
+            </struct>
+         </value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,88 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <array>
+               <data>
+                  <value>
+                     <struct>
+                        <member>
+                           <name>id</name>
+                           <value><int>47234</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>wibble</string></value>
+                        </member>
+                        <member>
+                           <name>ttl</name>
+                           <value><int>86400</int></value>
+                        </member>
+                        <member>
+                           <name>type</name>
+                           <value><string>CNAME</string></value>
+                        </member>
+                        <member>
+                           <name>value</name>
+                           <value><string>t.com</string></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>id</name>
+                           <value><int>47234</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>www</string></value>
+                        </member>
+                        <member>
+                           <name>ttl</name>
+                           <value><int>86400</int></value>
+                        </member>
+                        <member>
+                           <name>type</name>
+                           <value><string>A</string></value>
+                        </member>
+                        <member>
+                           <name>value</name>
+                           <value><string>208.111.35.173</string></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>id</name>
+                           <value><int>47234</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>blahblah</string></value>
+                        </member>
+                        <member>
+                           <name>ttl</name>
+                           <value><int>86400</int></value>
+                        </member>
+                        <member>
+                           <name>type</name>
+                           <value><string>A</string></value>
+                        </member>
+                        <member>
+                           <name>value</name>
+                           <value><string>208.111.35.173</string></value>
+                        </member>
+                     </struct>
+                  </value>
+               </data>
+            </array>
+         </value>
+      </param>
+   </params>
+</methodResponse>
+

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records_empty.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records_empty.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records_empty.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_records_empty.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,14 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <array>
+               <data>
+               </data>
+            </array>
+         </value>
+      </param>
+   </params>
+</methodResponse>
+

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_zones.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_zones.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_zones.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/list_zones.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,138 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value>
+            <array>
+               <data>
+                  <value>
+                     <struct>
+                        <member>
+                           <name>date_updated</name>
+                           <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+                        </member>
+                        <member>
+                           <name>id</name>
+                           <value><int>47234</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>t.com</string></value>
+                        </member>
+                        <member>
+                           <name>public</name>
+                           <value><boolean>0</boolean></value>
+                        </member>
+                        <member>
+                           <name>version</name>
+                           <value><int>1</int></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>date_updated</name>
+                           <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+                        </member>
+                        <member>
+                           <name>id</name>
+                           <value><int>48170</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>newbug.net</string></value>
+                        </member>
+                        <member>
+                           <name>public</name>
+                           <value><boolean>0</boolean></value>
+                        </member>
+                        <member>
+                           <name>version</name>
+                           <value><int>1</int></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>date_updated</name>
+                           <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+                        </member>
+                        <member>
+                           <name>id</name>
+                           <value><int>48017</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>newblah.com</string></value>
+                        </member>
+                        <member>
+                           <name>public</name>
+                           <value><boolean>0</boolean></value>
+                        </member>
+                        <member>
+                           <name>version</name>
+                           <value><int>1</int></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>date_updated</name>
+                           <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+                        </member>
+                        <member>
+                           <name>id</name>
+                           <value><int>47288</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>fromapi.com</string></value>
+                        </member>
+                        <member>
+                           <name>public</name>
+                           <value><boolean>0</boolean></value>
+                        </member>
+                        <member>
+                           <name>version</name>
+                           <value><int>1</int></value>
+                        </member>
+                     </struct>
+                  </value>
+
+                  <value>
+                     <struct>
+                        <member>
+                           <name>date_updated</name>
+                           <value><dateTime.iso8601>20101028T12:38:17</dateTime.iso8601></value>
+                        </member>
+                        <member>
+                           <name>id</name>
+                           <value><int>48008</int></value>
+                        </member>
+                        <member>
+                           <name>name</name>
+                           <value><string>blahnew.com</string></value>
+                        </member>
+                        <member>
+                           <name>public</name>
+                           <value><boolean>0</boolean></value>
+                        </member>
+                        <member>
+                           <name>version</name>
+                           <value><int>1</int></value>
+                        </member>
+                     </struct>
+                  </value>
+               </data>
+            </array>
+         </value>
+      </param>
+   </params>
+</methodResponse>
+

Added: libcloud/trunk/libcloud/test/dns/fixtures/gandi/new_version.xml
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/fixtures/gandi/new_version.xml?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/fixtures/gandi/new_version.xml (added)
+++ libcloud/trunk/libcloud/test/dns/fixtures/gandi/new_version.xml Thu Jan 31 02:41:40 2013
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<methodResponse>
+   <params>
+      <param>
+         <value><int>1</int></value>
+      </param>
+   </params>
+</methodResponse>

Added: libcloud/trunk/libcloud/test/dns/test_gandi.py
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/dns/test_gandi.py?rev=1440817&view=auto
==============================================================================
--- libcloud/trunk/libcloud/test/dns/test_gandi.py (added)
+++ libcloud/trunk/libcloud/test/dns/test_gandi.py Thu Jan 31 02:41:40 2013
@@ -0,0 +1,289 @@
+# 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 sys
+import unittest
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import xmlrpclib
+from libcloud.dns.types import RecordType, ZoneDoesNotExistError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.drivers.gandi import GandiDNSDriver
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import DNSFileFixtures
+from libcloud.test.secrets import DNS_GANDI
+from libcloud.test.common.test_gandi import MockGandiTransport, BaseGandiTests
+
+Fault = xmlrpclib.Fault
+
+class GandiMockHttp(MockHttp):
+    fixtures = DNSFileFixtures('gandi')
+
+    def _xmlrpc__domain_zone_create(self, method, url, body, headers):
+        body = self.fixtures.load('create_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_update(self, method, url, body, headers):
+        body = self.fixtures.load('get_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_list(self, method, url, body, headers):
+        body = self.fixtures.load('list_zones.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_list(self, method, url, body, headers):
+        body = self.fixtures.load('list_records.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_add(self, method, url, body, headers):
+        body = self.fixtures.load('create_record.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_delete(self, method, url, body, headers):
+        body = self.fixtures.load('delete_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_info(self, method, url, body, headers):
+        body = self.fixtures.load('get_zone.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_delete(self, method, url, body, headers):
+        body = self.fixtures.load('delete_record.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_update(self, method, url, body, headers):
+        body = self.fixtures.load('create_record.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_version_new(self, method, url, body, headers):
+        body = self.fixtures.load('new_version.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_version_set(self, method, url, body, headers):
+        body = self.fixtures.load('new_version.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_list_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        raise Fault(581042, "Zone does not exist")
+
+    def _xmlrpc__domain_zone_info_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        raise Fault(581042, "Zone does not exist")
+
+    def _xmlrpc__domain_zone_list_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        raise Fault(581042, "Zone does not exist")
+
+    def _xmlrpc__domain_zone_delete_ZONE_DOES_NOT_EXIST(self, method, url, body, headers):
+        raise Fault(581042, "Zone does not exist")
+
+    def _xmlrpc__domain_zone_record_list_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('list_records_empty.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_info_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('list_zones.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_record_delete_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('delete_record_doesnotexist.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_version_new_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('new_version.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _xmlrpc__domain_zone_version_set_RECORD_DOES_NOT_EXIST(self, method, url, body, headers):
+        body = self.fixtures.load('new_version.xml')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+
+class DummyTransport(MockGandiTransport):
+    mockCls = GandiMockHttp
+
+
+class GandiTests(BaseGandiTests):
+
+    driverCls = GandiDNSDriver
+    transportCls = DummyTransport
+    params = DNS_GANDI
+
+    def test_list_record_types(self):
+        record_types = self.driver.list_record_types()
+        self.assertEqual(len(record_types), 10)
+        self.assertTrue(RecordType.A in record_types)
+
+    def test_list_zones(self):
+        zones = self.driver.list_zones()
+        self.assertEqual(len(zones), 5)
+
+        zone = zones[0]
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_list_records(self):
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 3)
+
+        record = records[1]
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.id, 'A:www')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '208.111.35.173')
+
+    def test_get_zone(self):
+        zone = self.driver.get_zone(zone_id='47234')
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_get_record(self):
+        record = self.driver.get_record(zone_id='47234',
+                                        record_id='CNAME:t.com')
+        self.assertEqual(record.name, 'wibble')
+        self.assertEqual(record.type, RecordType.CNAME)
+        self.assertEqual(record.data, 't.com')
+
+    def test_list_records_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        GandiMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.list_records(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_zone_does_not_exist(self):
+        GandiMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_zone(zone_id='47234')
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, '47234')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_zone_does_not_exist(self):
+        GandiMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_record(zone_id='4444', record_id='CNAME:t.com')
+        except ZoneDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_record_does_not_exist(self):
+        GandiMockHttp.type = 'RECORD_DOES_NOT_EXIST'
+
+        try:
+            self.driver.get_record(zone_id='47234',
+                                   record_id='CNAME:t.com')
+        except RecordDoesNotExistError:
+            pass
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_create_zone(self):
+        zone = self.driver.create_zone(domain='t.com', type='master',
+                                       ttl=None, extra=None)
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_update_zone(self):
+        zone = self.driver.get_zone(zone_id='47234')
+        zone = self.driver.update_zone(zone, domain='t.com')
+        self.assertEqual(zone.id, '47234')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 't.com')
+
+    def test_create_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.create_record(
+            name='www', zone=zone,
+            type=RecordType.A, data='127.0.0.1',
+            extra={'ttl': 30}
+        )
+
+        self.assertEqual(record.id, 'A:www')
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.zone, zone)
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '127.0.0.1')
+
+    def test_update_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[1]
+
+        params = {
+            'record': record,
+            'name': 'www',
+            'type': RecordType.A,
+            'data': '127.0.0.1',
+            'extra': {'ttl': 30}}
+        updated_record = self.driver.update_record(**params)
+
+        self.assertEqual(record.data, '208.111.35.173')
+
+        self.assertEqual(updated_record.id, 'A:www')
+        self.assertEqual(updated_record.name, 'www')
+        self.assertEqual(updated_record.zone, record.zone)
+        self.assertEqual(updated_record.type, RecordType.A)
+        self.assertEqual(updated_record.data, '127.0.0.1')
+
+    def test_delete_zone(self):
+        zone = self.driver.list_zones()[0]
+        status = self.driver.delete_zone(zone=zone)
+        self.assertTrue(status)
+
+    def test_delete_zone_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+
+        GandiMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.delete_zone(zone=zone)
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, zone.id)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_delete_record(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        status = self.driver.delete_record(record=record)
+        self.assertTrue(status)
+
+    def test_delete_record_does_not_exist(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.list_records(zone=zone)[0]
+        GandiMockHttp.type = 'RECORD_DOES_NOT_EXIST'
+        try:
+            self.driver.delete_record(record=record)
+        except RecordDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.record_id, record.id)
+        else:
+            self.fail('Exception was not thrown')
+
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

Modified: libcloud/trunk/libcloud/test/secrets.py-dist
URL: http://svn.apache.org/viewvc/libcloud/trunk/libcloud/test/secrets.py-dist?rev=1440817&r1=1440816&r2=1440817&view=diff
==============================================================================
--- libcloud/trunk/libcloud/test/secrets.py-dist (original)
+++ libcloud/trunk/libcloud/test/secrets.py-dist Thu Jan 31 02:41:40 2013
@@ -54,3 +54,4 @@ DNS_PARAMS_ZERIGO = ('email', 'api token
 DNS_PARAMS_RACKSPACE = ('user', 'key')
 DNS_PARAMS_HOSTVIRTUAL = ('key',)
 DNS_PARAMS_ROUTE53 = ('access_id', 'secret')
+DNS_GANDI = ('user', )