libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject [1/2] libcloud git commit: [Libcloud-732] Add implementation for World Wide DNS provider
Date Fri, 25 Sep 2015 12:13:28 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk 91c69db05 -> e136292a9


[Libcloud-732] Add implementation for World Wide DNS provider


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/4eed2b2d
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/4eed2b2d
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/4eed2b2d

Branch: refs/heads/trunk
Commit: 4eed2b2d16b48b0ff0993eb152d5ebfba0398ace
Parents: 1bf0f47
Author: Alejandro Pereira <alepereira86@gmail.com>
Authored: Tue Aug 18 08:27:10 2015 -0300
Committer: Alejandro Pereira <alepereira86@gmail.com>
Committed: Mon Sep 7 18:05:18 2015 -0300

----------------------------------------------------------------------
 CHANGES.rst                                     |   4 +
 docs/dns/drivers/worldwidedns.rst               |  22 +
 .../dns/worldwidedns/instantiate_driver.py      |  10 +
 libcloud/common/worldwidedns.py                 | 195 +++++++
 libcloud/dns/drivers/worldwidedns.py            | 502 +++++++++++++++++++
 libcloud/dns/providers.py                       |   2 +
 libcloud/dns/types.py                           |   1 +
 .../test/dns/fixtures/worldwidedns/api_dns_list |   1 +
 .../worldwidedns/api_dns_list_domain_asp        |  46 ++
 .../api_dns_list_domain_asp_CREATE_RECORD       |  46 ++
 .../api_dns_list_domain_asp_DELETE_RECORD       |  46 ++
 .../api_dns_list_domain_asp_UPDATE_RECORD       |  46 ++
 .../api_dns_list_domain_asp_UPDATE_ZONE         |  46 ++
 libcloud/test/dns/test_worldwidedns.py          | 339 +++++++++++++
 libcloud/test/secrets.py-dist                   |   1 +
 15 files changed, 1307 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 3fc74a7..1ff36e2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -60,6 +60,10 @@ Loadbalancer
 DNS
 ~~~
 
+- Add new driver for WorldWideDNS service.
+  (GITHUB-566, LIBCLOUD-732)
+  [Alejandro Pereira]
+
 - Add new driver for AuroraDNS service.
   (GITHUB-562, LIBCLOUD-735)
   [Wido den Hollander]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/docs/dns/drivers/worldwidedns.rst
----------------------------------------------------------------------
diff --git a/docs/dns/drivers/worldwidedns.rst b/docs/dns/drivers/worldwidedns.rst
new file mode 100644
index 0000000..c9a24a8
--- /dev/null
+++ b/docs/dns/drivers/worldwidedns.rst
@@ -0,0 +1,22 @@
+World Wide DNS Driver Documentation
+=====================================
+
+`WorldWideDNS`_ provides Primary DNS services, Secondary DNS services, and
+Dynamic DNS hosting services for thousands of businesses, website hosting,
+website developers, and consumers. Resellers can completely private
+label / white label DNS services with custom name servers.
+
+Instantiating the driver
+-------------------------------------
+
+.. literalinclude:: /examples/dns/worldwidedns/instantiate_driver.py
+   :language: python
+
+API Docs
+--------
+
+.. autoclass:: libcloud.dns.drivers.worldwidedns.WorldWideDNSDriver
+    :members:
+    :inherited-members:
+
+.. _`WorldWideDNS`: https://www.worldwidedns.net/

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/docs/examples/dns/worldwidedns/instantiate_driver.py
----------------------------------------------------------------------
diff --git a/docs/examples/dns/worldwidedns/instantiate_driver.py b/docs/examples/dns/worldwidedns/instantiate_driver.py
new file mode 100644
index 0000000..53bc1c9
--- /dev/null
+++ b/docs/examples/dns/worldwidedns/instantiate_driver.py
@@ -0,0 +1,10 @@
+from libcloud.dns.types import Provider
+from libcloud.dns.providers import get_driver
+
+cls = get_driver(Provider.WORLDWIDEDNS)
+
+# Normal account
+driver = cls('username', 'apikey')
+
+# Reseller account
+driver = cls('username', 'apikey', reseller_id='reseller_id')

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/common/worldwidedns.py
----------------------------------------------------------------------
diff --git a/libcloud/common/worldwidedns.py b/libcloud/common/worldwidedns.py
new file mode 100644
index 0000000..1c02a12
--- /dev/null
+++ b/libcloud/common/worldwidedns.py
@@ -0,0 +1,195 @@
+# 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 re
+
+from libcloud.common.base import ConnectionUserAndKey
+from libcloud.common.base import Response
+from libcloud.common.types import ProviderError
+
+
+OK_CODES = ['200', '211', '212', '213']
+ERROR_CODES = ['401', '403', '405', '406', '407', '408', '409', '410', '411',
+               '412', '413', '414', '450', '451']
+
+
+class WorldWideDNSException(ProviderError):
+    def __init__(self, value, http_code, code, driver=None):
+        self.code = code
+        super(WorldWideDNSException, self).__init__(value, http_code, driver)
+
+
+class SuspendedAccount(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Login ID you supplied is SUSPENDED, you need to renew" + \
+                " your account"
+        super(SuspendedAccount, self).__init__(value, http_code, 401,
+                                               driver)
+
+
+class LoginOrPasswordNotMatch(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Login ID and/or Password you supplied is not on file or" + \
+                " does not match"
+        super(LoginOrPasswordNotMatch, self).__init__(value, http_code, 403,
+                                                      driver)
+
+
+class NonExistentDomain(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Domain name supplied is not in your account"
+        super(NonExistentDomain, self).__init__(value, http_code, 405,
+                                                driver)
+
+
+class CouldntRemoveDomain(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Error occured removing domain from name server, try again"
+        super(CouldntRemoveDomain, self).__init__(value, http_code, 406,
+                                                  driver)
+
+
+class LimitExceeded(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Your limit was exceeded, you need to upgrade your account"
+        super(LimitExceeded, self).__init__(value, http_code, 407,
+                                            driver)
+
+
+class ExistentDomain(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Domain already exists on our servers"
+        super(ExistentDomain, self).__init__(value, http_code, 408,
+                                             driver)
+
+
+class DomainBanned(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Domain is listed in DNSBL and is banned from our servers"
+        super(DomainBanned, self).__init__(value, http_code, 409,
+                                           driver)
+
+
+class InvalidDomainName(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Invalid domain name"
+        super(InvalidDomainName, self).__init__(value, http_code, 410,
+                                                driver)
+
+
+class ErrorOnReloadInNameServer(WorldWideDNSException):
+    def __init__(self, server, http_code, driver=None):
+        if server == 1:
+            value = "Name server #1 kicked an error on reload, contact support"
+            code = 411
+        elif server == 2:
+            value = "Name server #2 kicked an error on reload, contact support"
+            code = 412
+        elif server == 3:
+            value = "Name server #3 kicked an error on reload, contact support"
+            code = 413
+        super(ErrorOnReloadInNameServer, self).__init__(value, http_code, code,
+                                                        driver)
+
+
+class NewUserNotValid(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "New userid is not valid"
+        super(NewUserNotValid, self).__init__(value, http_code, 414,
+                                              driver)
+
+
+class CouldntReachNameServer(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "Couldn't reach the name server, try again later"
+        super(CouldntReachNameServer, self).__init__(value, http_code, 450,
+                                                     driver)
+
+
+class NoZoneFile(WorldWideDNSException):
+    def __init__(self, http_code, driver=None):
+        value = "No zone file in the name server queried"
+        super(NoZoneFile, self).__init__(value, http_code, 451,
+                                         driver)
+
+
+ERROR_CODE_TO_EXCEPTION_CLS = {
+    '401': SuspendedAccount,
+    '403': LoginOrPasswordNotMatch,
+    '405': NonExistentDomain,
+    '406': CouldntRemoveDomain,
+    '407': LimitExceeded,
+    '408': ExistentDomain,
+    '409': DomainBanned,
+    '410': InvalidDomainName,
+    '411': ErrorOnReloadInNameServer,
+    '412': ErrorOnReloadInNameServer,
+    '413': ErrorOnReloadInNameServer,
+    '414': NewUserNotValid,
+    '450': CouldntReachNameServer,
+    '451': NoZoneFile,
+}
+
+
+class WorldWideDNSResponse(Response):
+
+    def parse_body(self):
+        """
+        Parse response body.
+
+        :return: Parsed body.
+        :rtype: ``str``
+        """
+        if self._code_response(self.body):
+            codes = re.split('\r?\n', self.body)
+            for code in codes:
+                if code in OK_CODES:
+                    continue
+                elif code in ERROR_CODES:
+                    exception = ERROR_CODE_TO_EXCEPTION_CLS.get(code)
+                    if code in ['411', '412', '413']:
+                        server = int(code[2])
+                        raise exception(server, self.status)
+                    raise exception(self.status)
+        return self.body
+
+    def _code_response(self, body):
+        """
+        Checks if the response body contains code status.
+
+        :rtype: ``bool``
+        """
+        available_response_codes = OK_CODES + ERROR_CODES
+        codes = re.split('\r?\n', body)
+        if codes[0] in available_response_codes:
+            return True
+        return False
+
+
+class WorldWideDNSConnection(ConnectionUserAndKey):
+    host = 'www.worldwidedns.net'
+    responseCls = WorldWideDNSResponse
+
+    def add_default_params(self, params):
+        """
+        Add parameters that are necessary for every request
+
+        This method adds ``NAME`` and ``PASSWORD`` to
+        the request.
+        """
+        params["NAME"] = self.user_id
+        params["PASSWORD"] = self.key
+        if hasattr(self, 'reseller_id'):
+            params["ID"] = self.reseller_id
+        return params

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/dns/drivers/worldwidedns.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/drivers/worldwidedns.py b/libcloud/dns/drivers/worldwidedns.py
new file mode 100644
index 0000000..d5fdd5a
--- /dev/null
+++ b/libcloud/dns/drivers/worldwidedns.py
@@ -0,0 +1,502 @@
+# 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.
+"""
+World Wide DNS Driver
+"""
+
+__all__ = [
+    'WorldWideDNSDriver'
+]
+import re
+
+from libcloud.common.types import LibcloudError
+from libcloud.common.worldwidedns import WorldWideDNSConnection
+from libcloud.dns.types import Provider, RecordType
+from libcloud.dns.types import ZoneDoesNotExistError
+from libcloud.dns.types import RecordError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.base import DNSDriver, Zone, Record
+
+
+MAX_RECORD_ENTRIES = 40  # Maximum record entries for zone
+
+
+class WorldWideDNSError(LibcloudError):
+
+    def __repr__(self):
+        return ("<WorldWideDNSError in " +
+                repr(self.driver) +
+                " " +
+                repr(self.value) + ">")
+
+
+class WorldWideDNSDriver(DNSDriver):
+    type = Provider.WORLDWIDEDNS
+    name = 'World Wide DNS'
+    website = 'https://www.worldwidedns.net/'
+    connectionCls = WorldWideDNSConnection
+
+    RECORD_TYPE_MAP = {
+        RecordType.MX: 'MX',
+        RecordType.CNAME: 'CNAME',
+        RecordType.A: 'A',
+        RecordType.NS: 'NS',
+        RecordType.SRV: 'SRV',
+        RecordType.TXT: 'TXT',
+    }
+
+    def __init__(self, key, secret=None, reseller_id=None, secure=True,
+                 host=None, port=None, **kwargs):
+        """
+        :param    key: API key or username to used (required)
+        :type     key: ``str``
+
+        :param    secret: Secret password to be used (required)
+        :type     secret: ``str``
+
+        :param    reseller_id: Reseller ID for reseller accounts
+        :type     reseller_id: ``str``
+
+        :param    secure: Weither to use HTTPS or HTTP. Note: Some providers
+                only support HTTPS, and it is on by default.
+        :type     secure: ``bool``
+
+        :param    host: Override hostname used for connections.
+        :type     host: ``str``
+
+        :param    port: Override port used for connections.
+        :type     port: ``int``
+
+        :return: ``None``
+        """
+        super(WorldWideDNSDriver, self).__init__(key=key, secret=secret,
+                                                 secure=secure, host=host,
+                                                 port=port, **kwargs)
+        self.reseller_id = reseller_id
+
+    def list_zones(self):
+        """
+        Return a list of zones.
+
+        :return: ``list`` of :class:`Zone`
+
+        For more info, please see:
+        https://www.worldwidedns.net/dns_api_protocol_list.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_list_reseller.asp
+        """
+        action = '/api_dns_list.asp'
+        if self.reseller_id is not None:
+            action = '/api_dns_list_reseller.asp'
+        zones = self.connection.request(action)
+        if len(zones.body) == 0:
+            return []
+        else:
+            return self._to_zones(zones.body)
+
+    def iterate_records(self, zone):
+        """
+        Return a generator to iterate over records for the provided zone.
+
+        :param zone: Zone to list records for.
+        :type zone: :class:`Zone`
+
+        :rtype: ``generator`` of :class:`Record`
+        """
+        records = self._to_records(zone)
+        for record in records:
+            yield record
+
+    def get_zone(self, zone_id):
+        """
+        Return a Zone instance.
+
+        :param zone_id: ID of the required zone
+        :type  zone_id: ``str``
+
+        :rtype: :class:`Zone`
+        """
+        zones = self.list_zones()
+        zone = [zone for zone in zones if zone.id == zone_id]
+        if len(zone) == 0:
+            raise ZoneDoesNotExistError(driver=self,
+                                        value="The zone doesn't exists",
+                                        zone_id=zone_id)
+        return zone[0]
+
+    def get_record(self, zone_id, record_id):
+        """
+        Return a Record instance.
+
+        :param zone_id: ID of the required zone
+        :type  zone_id: ``str``
+
+        :param record_id: ID of the required record
+        :type  record_id: ``str``
+
+        :rtype: :class:`Record`
+        """
+        zone = self.get_zone(zone_id)
+        r_entry = [i for i in range(MAX_RECORD_ENTRIES) if
+                   zone.extra.get('S%s' % i) == record_id]
+        if not r_entry:
+            raise RecordDoesNotExistError(value="Record doesn't exists",
+                                          driver=zone.driver,
+                                          record_id=record_id)
+        entry = r_entry[0]
+        type = zone.extra.get('T%s' % entry)
+        data = zone.extra.get('D%s' % entry)
+        record = self._to_record(record_id, type, data, zone)
+        return record
+
+    def update_zone(self, zone, domain, type='master', ttl=None, extra=None,
+                    ex_raw=False):
+        """
+        Update en existing zone.
+
+        :param zone: Zone to update.
+        :type  zone: :class:`Zone`
+
+        :param domain: Zone domain name (e.g. example.com)
+        :type  domain: ``str``
+
+        :param type: Zone type (master / slave).
+        :type  type: ``str``
+
+        :param ttl: TTL for new records. (optional)
+        :type  ttl: ``int``
+
+        :param extra: Extra attributes (driver specific) (optional). Values not
+        specified such as *SECURE*, *IP*, *FOLDER*, *HOSTMASTER*, *REFRESH*,
+        *RETRY* and *EXPIRE* will be kept as already is. The same will be for
+        *S(1 to 40)*, *T(1 to 40)* and *D(1 to 40)* if not in raw mode and
+        for *ZONENS* and *ZONEDATA* if it is.
+        :type  extra: ``dict``
+
+        :param ex_raw: Mode we use to do the update using zone file or not.
+        :type  ex_raw: ``bool``
+
+        :rtype: :class:`Zone`
+
+        For more info, please see
+        https://www.worldwidedns.net/dns_api_protocol_list_domain.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_list_domain_raw.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_list_domain_reseller.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_list_domain_raw_reseller.asp
+        """
+        if extra is not None:
+            not_specified = [key for key in zone.extra.keys() if key not in
+                             extra.keys()]
+        else:
+            not_specified = zone.extra.keys()
+
+        if ttl is None:
+            ttl = zone.ttl
+
+        params = {'DOMAIN': domain,
+                  'TTL': ttl}
+
+        for key in not_specified:
+            params[key] = zone.extra[key]
+        if extra is not None:
+            params.update(extra)
+        if ex_raw:
+            action = '/api_dns_modify_raw.asp'
+            if self.reseller_id is not None:
+                action = '/api_dns_modify_raw_reseller.asp'
+            method = 'POST'
+        else:
+            action = '/api_dns_modify.asp'
+            if self.reseller_id is not None:
+                action = '/api_dns_modify_reseller.asp'
+            method = 'GET'
+        response = self.connection.request(action, params=params,  # noqa
+                                           method=method)
+        zone = self.get_zone(zone.id)
+        return zone
+
+    def update_record(self, record, name, type, data, ex_entry):
+        """
+        Update an existing record.
+
+        :param record: Record to update.
+        :type  record: :class:`Record`
+
+        :param name: Record name without the domain name (e.g. www).
+                     Note: If you want to create a record for a base domain
+                     name, you should specify empty string ('') for this
+                     argument.
+        :type  name: ``str``
+
+        :param type: DNS record type (MX, CNAME, A, NS, SRV, TXT).
+        :type  type: :class:`RecordType`
+
+        :param data: Data for the record (depends on the record type).
+        :type  data: ``str``
+
+        :param ex_entry: Entry position (1 thru 40)
+        :type  ex_entry: ``int``
+
+        :rtype: :class:`Record`
+        """
+        if name == '':
+            name = '@'
+        if type not in self.RECORD_TYPE_MAP:
+            raise RecordError(value="Record type is not allowed",
+                              driver=record.zone.driver,
+                              record_id=name)
+        zone = record.zone
+        extra = {'S%s' % ex_entry: name,
+                 'T%s' % ex_entry: type,
+                 'D%s' % ex_entry: data}
+        zone = self.update_zone(zone, zone.domain, extra=extra)
+        record = self.get_record(zone.id, name)
+        return record
+
+    def create_zone(self, domain, type='master', ttl=None, extra=None):
+        """
+        Create a new zone.
+
+        :param domain: Zone domain name (e.g. example.com)
+        :type domain: ``str``
+
+        :param type: Zone type (master / slave).
+        :type  type: ``str``
+
+        :param ttl: TTL for new records. (optional)
+        :type  ttl: ``int``
+
+        :param extra: Extra attributes (driver specific). (optional). Possible
+        parameter in here should be *DYN* which values should be 1 for standart
+        and 2 for dynamic. Default is 1.
+        :type extra: ``dict``
+
+        :rtype: :class:`Zone`
+
+        For more info, please see
+        https://www.worldwidedns.net/dns_api_protocol_new_domain.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_new_domain_reseller.asp
+        """
+        if type == 'master':
+            _type = 0
+        elif type == 'slave':
+            _type = 1
+        if extra:
+            dyn = extra.get('DYN') or 1
+        else:
+            dyn = 1
+        params = {'DOMAIN': domain,
+                  'TYPE': _type}
+        action = '/api_dns_new_domain.asp'
+        if self.reseller_id is not None:
+            params['DYN'] = dyn
+            action = '/api_dns_new_domain_reseller.asp'
+        self.connection.request(action, params=params)
+        zone = self.get_zone(domain)
+        if ttl is not None:
+            zone = self.update_zone(zone, zone.domain, ttl=ttl)
+        return zone
+
+    def create_record(self, name, zone, type, data, ex_entry):
+        """
+        Create a new record.
+
+        :param name: Record name without the domain name (e.g. www).
+                     Note: If you want to create a record for a base domain
+                     name, you should specify empty string ('') for this
+                     argument.
+        :type  name: ``str``
+
+        :param zone: Zone where the requested record is created.
+        :type  zone: :class:`Zone`
+
+        :param type: DNS record type (MX, CNAME, A, NS, SRV, TXT).
+        :type  type: :class:`RecordType`
+
+        :param data: Data for the record (depends on the record type).
+        :type  data: ``str``
+
+        :param ex_entry: Entry position (1 thru 40)
+        :type  ex_entry: ``int``
+
+        :rtype: :class:`Record`
+        """
+        if name == '':
+            name = '@'
+        if type not in self.RECORD_TYPE_MAP:
+            raise RecordError(value="Record type is not allowed",
+                              driver=zone.driver,
+                              record_id=name)
+        extra = {'S%s' % ex_entry: name,
+                 'T%s' % ex_entry: type,
+                 'D%s' % ex_entry: data}
+        zone = self.update_zone(zone, zone.domain, extra=extra)
+        record = self.get_record(zone.id, name)
+        return record
+
+    def delete_zone(self, zone):
+        """
+        Delete a zone.
+
+        Note: This will delete all the records belonging to this zone.
+
+        :param zone: Zone to delete.
+        :type  zone: :class:`Zone`
+
+        :rtype: ``bool``
+
+        For more information, please see
+        https://www.worldwidedns.net/dns_api_protocol_delete_domain.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_delete_domain_reseller.asp
+        """
+        params = {'DOMAIN': zone.domain}
+        action = '/api_dns_delete_domain.asp'
+        if self.reseller_id is not None:
+            action = '/api_dns_delete_domain_reseller.asp'
+        response = self.connection.request(action, params=params)
+        return response.success()
+
+    def delete_record(self, record):
+        """
+        Delete a record.
+
+        :param record: Record to delete.
+        :type  record: :class:`Record`
+
+        :rtype: ``bool``
+        """
+        zone = record.zone
+        for index in range(MAX_RECORD_ENTRIES):
+            if record.name == zone.extra['S%s' % (index + 1)]:
+                entry = index + 1
+                break
+        extra = {'S%s' % entry: '',
+                 'T%s' % entry: 'NONE',
+                 'D%s' % entry: ''}
+        self.update_zone(zone, zone.domain, extra=extra)
+        return True
+
+    def ex_view_zone(self, domain, name_server):
+        """
+        View zone file from a name server
+
+        :param domain: Domain name.
+        :type  domain: ``str``
+
+        :param name_server: Name server to check. (1, 2 or 3)
+        :type  name_server: ``int``
+
+        :rtype: ``str``
+
+        For more info, please see:
+        https://www.worldwidedns.net/dns_api_protocol_viewzone.asp
+        or
+        https://www.worldwidedns.net/dns_api_protocol_viewzone_reseller.asp
+        """
+        params = {'DOMAIN': domain,
+                  'NS': name_server}
+        action = '/api_dns_viewzone.asp'
+        if self.reseller_id is not None:
+            action = '/api_dns_viewzone_reseller.asp'
+        response = self.connection.request(action, params=params)
+        return response.object
+
+    def ex_transfer_domain(self, domain, user_id):
+        """
+        This command will allow you, if you are a reseller, to change the
+        userid on a domain name to another userid in your account ONLY if that
+        new userid is already created.
+
+        :param domain: Domain name.
+        :type  domain: ``str``
+
+        :param user_id: The new userid to connect to the domain name.
+        :type  user_id: ``str``
+
+        :rtype: ``bool``
+
+        For more info, please see:
+        https://www.worldwidedns.net/dns_api_protocol_transfer.asp
+        """
+        if self.reseller_id is None:
+            raise WorldWideDNSError("This is not a reseller account",
+                                    driver=self)
+        params = {'DOMAIN': domain,
+                  'NEW_ID': user_id}
+        response = self.connection.request('/api_dns_transfer.asp',
+                                           params=params)
+        return response.success()
+
+    def _to_zones(self, data):
+        domain_list = re.split('\r?\n', data)
+        zones = []
+        for line in domain_list:
+            zone = self._to_zone(line)
+            zones.append(zone)
+
+        return zones
+
+    def _to_zone(self, line):
+        data = line.split('\x1f')
+        name = data[0]
+        if data[1] == "P":
+            type = "master"
+            domain_data = self._get_domain_data(name)
+            resp_lines = re.split('\r?\n', domain_data.body)
+            soa_block = resp_lines[:6]
+            zone_data = resp_lines[6:]
+            extra = {'HOSTMASTER': soa_block[0], 'REFRESH': soa_block[1],
+                     'RETRY': soa_block[2], 'EXPIRE': soa_block[3],
+                     'SECURE': soa_block[5]}
+            ttl = soa_block[4]
+            for line in range(MAX_RECORD_ENTRIES):
+                line_data = zone_data[line].split('\x1f')
+                extra['S%s' % (line + 1)] = line_data[0]
+                extra['T%s' % (line + 1)] = line_data[1]
+                try:
+                    extra['D%s' % (line + 1)] = line_data[2]
+                except IndexError:
+                    extra['D%s' % (line + 1)] = ''
+        elif data[1] == 'S':
+            type = 'slave'
+            extra = {}
+            ttl = 0
+        return Zone(id=name, domain=name, type=type,
+                    ttl=ttl, driver=self, extra=extra)
+
+    def _get_domain_data(self, name):
+        params = {'DOMAIN': name}
+        data = self.connection.request('/api_dns_list_domain.asp',
+                                       params=params)
+        return data
+
+    def _to_records(self, zone):
+        records = []
+        for entry in range(MAX_RECORD_ENTRIES):
+            subdomain = zone.extra['S%s' % (entry + 1)]
+            type = zone.extra['T%s' % (entry + 1)]
+            data = zone.extra['D%s' % (entry + 1)]
+            if subdomain and type and data:
+                record = self._to_record(subdomain, type, data, zone)
+                records.append(record)
+        return records
+
+    def _to_record(self, subdomain, type, data, zone):
+        return Record(subdomain, subdomain, type, data, zone, zone.driver)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/dns/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py
index aef0aa7..a3e4af3 100644
--- a/libcloud/dns/providers.py
+++ b/libcloud/dns/providers.py
@@ -37,6 +37,8 @@ DRIVERS = {
     ('libcloud.dns.drivers.softlayer', 'SoftLayerDNSDriver'),
     Provider.DIGITAL_OCEAN:
     ('libcloud.dns.drivers.digitalocean', 'DigitalOceanDNSDriver'),
+    Provider.WORLDWIDEDNS:
+    ('libcloud.dns.drivers.worldwidedns', 'WorldWideDNSDriver'),
     # Deprecated
     Provider.RACKSPACE_US:
     ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'),

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/dns/types.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py
index ad2b34a..ab81976 100644
--- a/libcloud/dns/types.py
+++ b/libcloud/dns/types.py
@@ -39,6 +39,7 @@ class Provider(object):
     SOFTLAYER = 'softlayer'
     DIGITAL_OCEAN = 'digitalocean'
     AURORADNS = 'auroradns'
+    WORLDWIDEDNS = 'worldwidedns'
 
     # Deprecated
     RACKSPACE_US = 'rackspace_us'

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list
new file mode 100644
index 0000000..004f582
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list
@@ -0,0 +1 @@
+niteowebsponsoredthisone.comP
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp
new file mode 100644
index 0000000..f58f2d1
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp
@@ -0,0 +1,46 @@
+hostmaster.niteowebsponsoredthisone.com
+21600
+10800
+604800
+43200
+
+wwwA0.0.0.0
+@A0.0.0.0
+@MX10 niteowebsponsoredthisone.com
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_CREATE_RECORD
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_CREATE_RECORD b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_CREATE_RECORD
new file mode 100644
index 0000000..1f0e876
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_CREATE_RECORD
@@ -0,0 +1,46 @@
+hostmaster.niteowebsponsoredthisone.com
+21600
+10800
+604800
+43200
+
+wwwA0.0.0.0
+domain2A0.0.0.2
+@MX10 niteowebsponsoredthisone.com
+domain4A0.0.0.4
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_DELETE_RECORD
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_DELETE_RECORD b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_DELETE_RECORD
new file mode 100644
index 0000000..5a40168
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_DELETE_RECORD
@@ -0,0 +1,46 @@
+hostmaster.niteowebsponsoredthisone.com
+21600
+10800
+604800
+43200
+
+wwwA0.0.0.0
+NONE
+@MX10 niteowebsponsoredthisone.com
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_RECORD
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_RECORD b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_RECORD
new file mode 100644
index 0000000..c469527
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_RECORD
@@ -0,0 +1,46 @@
+hostmaster.niteowebsponsoredthisone.com
+21600
+10800
+604800
+43200
+
+domain1A0.0.0.1
+@A0.0.0.0
+@MX10 niteowebsponsoredthisone.com
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_ZONE
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_ZONE b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_ZONE
new file mode 100644
index 0000000..4e1be63
--- /dev/null
+++ b/libcloud/test/dns/fixtures/worldwidedns/api_dns_list_domain_asp_UPDATE_ZONE
@@ -0,0 +1,46 @@
+mail.niteowebsponsoredthisone.com
+21600
+10800
+604800
+3800
+
+wwwA0.0.0.0
+@A0.0.0.0
+@MX10 niteowebsponsoredthisone.com
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
+NONE
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/dns/test_worldwidedns.py
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/test_worldwidedns.py b/libcloud/test/dns/test_worldwidedns.py
new file mode 100644
index 0000000..8ea966e
--- /dev/null
+++ b/libcloud/test/dns/test_worldwidedns.py
@@ -0,0 +1,339 @@
+# 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
+
+import sys
+import unittest
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.dns.types import RecordType, ZoneDoesNotExistError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.drivers.worldwidedns import WorldWideDNSDriver
+from libcloud.common.worldwidedns import NonExistentDomain
+from libcloud.common.worldwidedns import InvalidDomainName
+
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import DNSFileFixtures
+from libcloud.test.secrets import DNS_PARAMS_WORLDWIDEDNS
+
+
+class WorldWideDNSTests(unittest.TestCase):
+    def setUp(self):
+        WorldWideDNSDriver.connectionCls.conn_classes = (
+            None, WorldWideDNSMockHttp)
+        WorldWideDNSMockHttp.type = None
+        self.driver = WorldWideDNSDriver(*DNS_PARAMS_WORLDWIDEDNS)
+
+    def assertHasKeys(self, dictionary, keys):
+        for key in keys:
+            self.assertTrue(key in dictionary, 'key "%s" not in dictionary' %
+                            (key))
+
+    def test_list_record_types(self):
+        record_types = self.driver.list_record_types()
+        self.assertEqual(len(record_types), 6)
+        self.assertTrue(RecordType.A in record_types)
+        self.assertTrue(RecordType.CNAME in record_types)
+        self.assertTrue(RecordType.MX in record_types)
+        self.assertTrue(RecordType.TXT in record_types)
+        self.assertTrue(RecordType.SRV in record_types)
+        self.assertTrue(RecordType.NS in record_types)
+
+    def test_list_zones_success(self):
+        zones = self.driver.list_zones()
+        self.assertEqual(len(zones), 1)
+
+        zone = zones[0]
+        self.assertEqual(zone.id, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.ttl, '43200')
+        self.assertHasKeys(zone.extra, ['HOSTMASTER', 'REFRESH', 'RETRY',
+                                        'EXPIRE', 'SECURE', 'S1', 'T1', 'D1',
+                                        'S2', 'T2', 'D2', 'S3', 'T3', 'D3'])
+
+    def test_list_records_success(self):
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 3)
+
+        www = records[0]
+        self.assertEqual(www.id, 'www')
+        self.assertEqual(www.name, 'www')
+        self.assertEqual(www.type, RecordType.A)
+        self.assertEqual(www.data, '0.0.0.0')
+        self.assertEqual(www.extra, {})
+
+    def test_list_records_zone_does_not_exist(self):
+        WorldWideDNSMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+        try:
+            zone = self.driver.list_zones()[0]
+            self.driver.list_records(zone=zone)
+        except NonExistentDomain:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.code, 405)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_zone_success(self):
+        zone = self.driver.get_zone(zone_id='niteowebsponsoredthisone.com')
+        self.assertEqual(zone.id, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.type, 'master')
+        self.assertEqual(zone.domain, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.ttl, '43200')
+        self.assertHasKeys(zone.extra, ['HOSTMASTER', 'REFRESH', 'RETRY',
+                                        'EXPIRE', 'SECURE', 'S1', 'T1', 'D1',
+                                        'S2', 'T2', 'D2', 'S3', 'T3', 'D3'])
+
+    def test_get_zone_does_not_exist(self):
+        WorldWideDNSMockHttp.type = 'GET_ZONE_DOES_NOT_EXIST'
+        try:
+            self.driver.get_zone(zone_id='unexistentzone')
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, 'unexistentzone')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_success(self):
+        record = self.driver.get_record(zone_id='niteowebsponsoredthisone.com',
+                                        record_id='www')
+        self.assertEqual(record.id, 'www')
+        self.assertEqual(record.name, 'www')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '0.0.0.0')
+        self.assertEqual(record.extra, {})
+
+    def test_get_record_zone_does_not_exist(self):
+        try:
+            self.driver.get_record(zone_id='unexistentzone',
+                                   record_id='3585100')
+        except ZoneDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.zone_id, 'unexistentzone')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_get_record_record_does_not_exist(self):
+        try:
+            self.driver.get_record(zone_id='niteowebsponsoredthisone.com',
+                                   record_id='3585100')
+        except RecordDoesNotExistError:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.record_id, '3585100')
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_create_zone_success(self):
+        zone = self.driver.create_zone(domain='niteowebsponsoredthisone.com',
+                                       type='master')
+        self.assertEqual(zone.id, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.domain, 'niteowebsponsoredthisone.com')
+        self.assertEqual(zone.ttl, '43200')
+        self.assertEqual(zone.type, 'master')
+
+    def test_create_zone_validaton_error(self):
+        WorldWideDNSMockHttp.type = 'VALIDATION_ERROR'
+
+        try:
+            self.driver.create_zone(domain='foo.%.com', type='master',
+                                    ttl=None, extra=None)
+        except InvalidDomainName:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.code, 410)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_update_zone_success(self):
+        zone = self.driver.list_zones()[0]
+        WorldWideDNSMockHttp.type = 'UPDATE_ZONE'
+        updated_zone = self.driver.update_zone(zone=zone,
+                                               domain='niteowebsponsoredthisone.com',  # noqa
+                                               ttl=3800,
+                                               extra={'HOSTMASTER':
+                                                      'mail.niteowebsponsoredthisone.com'})  # noqa
+
+        self.assertEqual(zone.extra['HOSTMASTER'],
+                         'hostmaster.niteowebsponsoredthisone.com')
+
+        self.assertEqual(updated_zone.id, zone.id)
+        self.assertEqual(updated_zone.domain, 'niteowebsponsoredthisone.com')
+        self.assertEqual(updated_zone.type, zone.type)
+        self.assertEqual(updated_zone.ttl, '3800')
+        self.assertEqual(updated_zone.extra['HOSTMASTER'],
+                         'mail.niteowebsponsoredthisone.com')
+        self.assertEqual(updated_zone.extra['REFRESH'], zone.extra['REFRESH'])
+        self.assertEqual(updated_zone.extra['RETRY'], zone.extra['RETRY'])
+        self.assertEqual(updated_zone.extra['EXPIRE'], zone.extra['EXPIRE'])
+        self.assertEqual(updated_zone.extra['SECURE'], zone.extra['SECURE'])
+        self.assertEqual(updated_zone.extra['S1'], zone.extra['S1'])
+        self.assertEqual(updated_zone.extra['T1'], zone.extra['T1'])
+        self.assertEqual(updated_zone.extra['D1'], zone.extra['D1'])
+        self.assertEqual(updated_zone.extra['S2'], zone.extra['S2'])
+        self.assertEqual(updated_zone.extra['T2'], zone.extra['T2'])
+        self.assertEqual(updated_zone.extra['D2'], zone.extra['D2'])
+        self.assertEqual(updated_zone.extra['S3'], zone.extra['S3'])
+        self.assertEqual(updated_zone.extra['T3'], zone.extra['T3'])
+        self.assertEqual(updated_zone.extra['D3'], zone.extra['D3'])
+
+    def test_create_record_success(self):
+        zone = self.driver.list_zones()[0]
+        WorldWideDNSMockHttp.type = 'CREATE_RECORD'
+        record = self.driver.create_record(name='domain4', zone=zone,
+                                           type=RecordType.A, data='0.0.0.4',
+                                           ex_entry=4)
+
+        self.assertEqual(record.id, 'domain4')
+        self.assertEqual(record.name, 'domain4')
+        self.assertNotEqual(record.zone.extra.get('S4'), zone.extra.get('S4'))
+        self.assertNotEqual(record.zone.extra.get('D4'), zone.extra.get('D4'))
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '0.0.0.4')
+
+    def test_update_record_success(self):
+        zone = self.driver.list_zones()[0]
+        record = self.driver.get_record(zone.id, 'www')
+        WorldWideDNSMockHttp.type = 'UPDATE_RECORD'
+        record = self.driver.update_record(record=record, name='domain1',
+                                           type=RecordType.A, data='0.0.0.1',
+                                           ex_entry=1)
+
+        self.assertEqual(record.id, 'domain1')
+        self.assertEqual(record.name, 'domain1')
+        self.assertNotEqual(record.zone.extra.get('S1'), zone.extra.get('S1'))
+        self.assertNotEqual(record.zone.extra.get('D1'), zone.extra.get('D1'))
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '0.0.0.1')
+
+    def test_delete_zone_success(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]
+
+        WorldWideDNSMockHttp.type = 'ZONE_DOES_NOT_EXIST'
+
+        try:
+            self.driver.delete_zone(zone=zone)
+        except NonExistentDomain:
+            e = sys.exc_info()[1]
+            self.assertEqual(e.code, 405)
+        else:
+            self.fail('Exception was not thrown')
+
+    def test_delete_record_success(self):
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 3)
+        record = records[1]
+        WorldWideDNSMockHttp.type = 'DELETE_RECORD'
+        status = self.driver.delete_record(record=record)
+        self.assertTrue(status)
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 2)
+
+
+class WorldWideDNSMockHttp(MockHttp):
+    fixtures = DNSFileFixtures('worldwidedns')
+
+    def _api_dns_list_asp(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_domain_asp(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list_domain_asp')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_ZONE_DOES_NOT_EXIST(self, method, url, body,
+                                              headers):
+        return (httplib.OK, '405', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_GET_ZONE_DOES_NOT_EXIST(self, method, url, body,
+                                                  headers):
+        return (httplib.OK, '', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_new_domain_asp(self, method, url, body, headers):
+        return (httplib.OK, '200', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_new_domain_asp_VALIDATION_ERROR(self, method, url, body,
+                                                 headers):
+        return (httplib.OK, '410', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_modify_asp(self, method, url, body, headers):
+        return (httplib.OK, '211\r\n212\r\n213', {},
+                httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_UPDATE_ZONE(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_modify_asp_UPDATE_ZONE(self, method, url, body, headers):
+        return (httplib.OK, '211\r\n212\r\n213', {},
+                httplib.responses[httplib.OK])
+
+    def _api_dns_list_domain_asp_UPDATE_ZONE(self, method, url, body,
+                                             headers):
+        body = self.fixtures.load('api_dns_list_domain_asp_UPDATE_ZONE')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_CREATE_RECORD(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_modify_asp_CREATE_RECORD(self, method, url, body, headers):
+        return (httplib.OK, '211\r\n212\r\n213', {},
+                httplib.responses[httplib.OK])
+
+    def _api_dns_list_domain_asp_CREATE_RECORD(self, method, url, body,
+                                               headers):
+        body = self.fixtures.load('api_dns_list_domain_asp_CREATE_RECORD')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_UPDATE_RECORD(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_modify_asp_UPDATE_RECORD(self, method, url, body, headers):
+        return (httplib.OK, '211\r\n212\r\n213', {},
+                httplib.responses[httplib.OK])
+
+    def _api_dns_list_domain_asp_UPDATE_RECORD(self, method, url, body,
+                                               headers):
+        body = self.fixtures.load('api_dns_list_domain_asp_UPDATE_RECORD')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_delete_domain_asp(self, method, url, body, headers):
+        return (httplib.OK, '200', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_delete_domain_asp_ZONE_DOES_NOT_EXIST(self, method, url, body,
+                                                       headers):
+        return (httplib.OK, '405', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_asp_DELETE_RECORD(self, method, url, body, headers):
+        body = self.fixtures.load('api_dns_list')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _api_dns_modify_asp_DELETE_RECORD(self, method, url, body, headers):
+        return (httplib.OK, '200', {}, httplib.responses[httplib.OK])
+
+    def _api_dns_list_domain_asp_DELETE_RECORD(self, method, url, body,
+                                               headers):
+        body = self.fixtures.load('api_dns_list_domain_asp_DELETE_RECORD')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+if __name__ == '__main__':
+    sys.exit(unittest.main())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4eed2b2d/libcloud/test/secrets.py-dist
----------------------------------------------------------------------
diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist
index 3ebcac3..982add0 100644
--- a/libcloud/test/secrets.py-dist
+++ b/libcloud/test/secrets.py-dist
@@ -72,3 +72,4 @@ DNS_PARAMS_ROUTE53 = ('access_id', 'secret')
 DNS_GANDI = ('user', )
 DNS_PARAMS_GOOGLE = ('email_address', 'key')
 DNS_KEYWORD_PARAMS_GOOGLE = {'project': 'project_name'}
+DNS_PARAMS_WORLDWIDEDNS = ('user', 'key')


Mime
View raw message