libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From to...@apache.org
Subject [1/5] libcloud git commit: [LIBCLOUD-740] Add Point DNS provider implementation
Date Fri, 25 Sep 2015 16:56:41 GMT
Repository: libcloud
Updated Branches:
  refs/heads/trunk b912123fd -> b99949e76


[LIBCLOUD-740] Add Point DNS provider implementation

Closes #576


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

Branch: refs/heads/trunk
Commit: 763dfb38fc0261c088fb1b4156fa312522ac2c2c
Parents: b912123
Author: Alejandro Pereira <alepereira86@gmail.com>
Authored: Mon Aug 31 19:21:02 2015 -0300
Committer: Tomaz Muraus <tomaz@apache.org>
Committed: Fri Sep 25 18:39:46 2015 +0200

----------------------------------------------------------------------
 docs/dns/drivers/pointdns.rst                   |  23 +
 .../examples/dns/pointdns/instantiate_driver.py |   5 +
 libcloud/common/pointdns.py                     |  55 ++
 libcloud/dns/drivers/pointdns.py                | 626 +++++++++++++++++++
 libcloud/dns/providers.py                       |   3 +
 libcloud/dns/types.py                           |   1 +
 .../dns/fixtures/pointdns/_zones_1_DELETE.json  |   5 +
 .../pointdns/_zones_1_records_141_GET.json      |  11 +
 .../pointdns/_zones_1_records_141_UPDATE.json   |  11 +
 .../pointdns/_zones_1_records_150_DELETE.json   |   5 +
 .../fixtures/pointdns/_zones_1_records_GET.json |  22 +
 .../dns/fixtures/pointdns/_zones_CREATE.json    |   9 +
 .../test/dns/fixtures/pointdns/_zones_GET.json  |  20 +
 .../dns/fixtures/pointdns/_zones_GET_1.json     |   9 +
 .../pointdns/_zones_example_com_UPDATE.json     |   9 +
 .../_zones_example_com_records_CREATE.json      |  11 +
 libcloud/test/dns/test_pointdns.py              | 210 +++++++
 libcloud/test/secrets.py-dist                   |   1 +
 18 files changed, 1036 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/docs/dns/drivers/pointdns.rst
----------------------------------------------------------------------
diff --git a/docs/dns/drivers/pointdns.rst b/docs/dns/drivers/pointdns.rst
new file mode 100644
index 0000000..649e0c2
--- /dev/null
+++ b/docs/dns/drivers/pointdns.rst
@@ -0,0 +1,23 @@
+Point DNS Driver Documentation
+===============================
+
+`PointDNS`_ provides an API that gives access to point zone, records, http and
+email redirects management. The API is built using RESTful principles.
+
+XML and JSON are supported as responses to API calls but this provider only
+support JSON.
+
+Instantiating the driver
+-------------------------------------
+
+.. literalinclude:: /examples/dns/pointdns/instantiate_driver.py
+   :language: python
+
+API Docs
+--------
+
+.. autoclass:: libcloud.dns.drivers.pointdns.PointDNSDriver
+    :members:
+    :inherited-members:
+
+.. _`PointDNS`: https://pointhq.com

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/docs/examples/dns/pointdns/instantiate_driver.py
----------------------------------------------------------------------
diff --git a/docs/examples/dns/pointdns/instantiate_driver.py b/docs/examples/dns/pointdns/instantiate_driver.py
new file mode 100644
index 0000000..1383b66
--- /dev/null
+++ b/docs/examples/dns/pointdns/instantiate_driver.py
@@ -0,0 +1,5 @@
+from libcloud.dns.types import Provider
+from libcloud.dns.providers import get_driver
+
+cls = get_driver(Provider.POINTDNS)
+driver = cls('username', 'apikey')

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/common/pointdns.py
----------------------------------------------------------------------
diff --git a/libcloud/common/pointdns.py b/libcloud/common/pointdns.py
new file mode 100644
index 0000000..cfb911f
--- /dev/null
+++ b/libcloud/common/pointdns.py
@@ -0,0 +1,55 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import base64
+from libcloud.utils.py3 import b
+from libcloud.utils.py3 import httplib
+
+from libcloud.common.base import ConnectionUserAndKey
+from libcloud.common.base import JsonResponse
+
+
+class PointDNSDNSResponse(JsonResponse):
+
+    def success(self):
+        """
+        Determine if our request was successful.
+
+        The meaning of this can be arbitrary; did we receive OK status? Did
+        the node get created? Were we authenticated?
+
+        :rtype: ``bool``
+        :return: ``True`` or ``False``
+        """
+        # response.success() only checks for 200 and 201 codes. Should we
+        # add 202?
+        return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
+
+
+class PointDNSConnection(ConnectionUserAndKey):
+    host = 'pointhq.com'
+    responseCls = PointDNSDNSResponse
+
+    def add_default_headers(self, headers):
+        """
+        Add headers that are necessary for every request
+
+        This method adds ``token`` to the request.
+        """
+        b64string = b('%s:%s' % (self.user_id, self.key))
+        token = base64.b64encode(b64string)
+        headers['Authorization'] = 'Basic %s' % token
+        headers['Accept'] = 'application/json'
+        headers['Content-Type'] = 'application/json'
+        return headers

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/dns/drivers/pointdns.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/drivers/pointdns.py b/libcloud/dns/drivers/pointdns.py
new file mode 100644
index 0000000..9f20aa8
--- /dev/null
+++ b/libcloud/dns/drivers/pointdns.py
@@ -0,0 +1,626 @@
+# 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 Licenseself.
+"""
+Point DNS Driver
+"""
+
+__all__ = [
+    'PointDNSDriver'
+]
+import json
+
+from libcloud.common.types import MalformedResponseError
+from libcloud.common.pointdns import PointDNSConnection
+from libcloud.dns.types import Provider, RecordType
+from libcloud.dns.types import ZoneDoesNotExistError
+from libcloud.dns.types import RecordDoesNotExistError
+from libcloud.dns.base import DNSDriver, Zone, Record
+
+
+class Redirect(object):
+    """
+    Point DNS redirect.
+    """
+
+    def __init__(self, id, name, data, type, driver, zone_id, iframe=None,
+                 query=False):
+        """
+        :param id: Redirect id.
+        :type id: ``str``
+
+        :param name: The FQDN for the record.
+        :type name: ``str``
+
+        :param data: The data field. (redirect_to)
+        :type data: ``str``
+
+        :param type: The type of redirects 301, 302 or 0 for iframes.
+        :type type: ``str``
+
+        :param driver: DNSDriver instance.
+        :type driver: :class:`DNSDriver`
+
+        :param zone_id: Zone id
+        :type zone_id: ``str``
+
+        :param iframe: Title of iframe (optional).
+        :type iframe: ``str``
+
+        :param query: boolean Information about including query string when
+                      redirecting. (optional).
+        :type query: ``bool``
+        """
+        self.id = str(id) if id else None
+        self.name = name
+        self.data = data
+        self.type = str(type) if type else None
+        self.driver = driver
+        self.zone_id = zone_id
+        self.iframe = iframe
+        self.query = query
+
+    def update(self, data, name=None, type=None, iframe=None, query=None):
+        return self.driver.ex_update_redirect(redirect=self, name=name,
+                                              data=data, type=type,
+                                              iframe=iframe, query=query)
+
+    def delete(self):
+        return self.driver.ex_delete_redirect(redirect=self)
+
+    def __repr__(self):
+        return ('<PointDNSRedirect: name=%s, data=%s, type=%s ...>' %
+                (self.name, self.data, self.type))
+
+
+class MailRedirect(object):
+    """
+    Point DNS mail redirect.
+    """
+
+    def __init__(self, id, source, destination, zone_id, driver):
+        """
+        :param id: MailRedirect id.
+        :type id: ``str``
+
+        :param source: The source address of mail redirect.
+        :type source: ``str``
+
+        :param destination: The destination address of mail redirect.
+        :type destination: ``str``
+
+        :param zone_id: Zone id
+        :type zone_id: ``str``
+
+        :param driver: DNSDriver instance.
+        :type driver: :class:`DNSDriver`
+        """
+        self.id = str(id) if id else None
+        self.source = source
+        self.destination = destination
+        self.zone_id = zone_id
+        self.driver = driver
+
+    def update(self, destination, source=None, zone_id=None):
+        return self.driver.ex_update_mailredirect(mail_r=self,
+                                                  destination=destination,
+                                                  source=None)
+
+    def delete(self):
+        return self.driver.ex_delete_mailredirect(mail_r=self)
+
+    def __repr__(self):
+        return ('<PointDNSMailRedirect: source=%s, destination=%s,zone=%s ...>'
+                % (self.source, self.destination, self.zone_id))
+
+
+class PointDNSDriver(DNSDriver):
+    type = Provider.POINTDNS
+    name = 'Point DNS'
+    website = 'https://pointhq.com/'
+    connectionCls = PointDNSConnection
+
+    RECORD_TYPE_MAP = {
+        RecordType.A: 'A',
+        RecordType.AAAA: 'AAAA',
+        RecordType.ALIAS: 'ALIAS',
+        RecordType.CNAME: 'CNAME',
+        RecordType.MX: 'MX',
+        RecordType.NS: 'NS',
+        RecordType.PTR: 'PTR',
+        RecordType.SRV: 'SRV',
+        RecordType.SSHFP: 'SSHFP',
+        RecordType.TXT: 'TXT'
+    }
+
+    def list_zones(self):
+        """
+        Return a list of zones.
+
+        :return: ``list`` of :class:`Zone`
+        """
+        response = self.connection.request('/zones')
+        zones = self._to_zones(response.object)
+        return zones
+
+    def list_records(self, zone):
+        """
+        Return a list of records for the provided zone.
+
+        :param zone: Zone to list records for.
+        :type zone: :class:`Zone`
+
+        :return: ``list`` of :class:`Record`
+        """
+        response = self.connection.request('/zones/%s/records' % zone.id)
+        records = self._to_records(response.object, zone)
+        return records
+
+    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`
+        """
+        try:
+            response = self.connection.request('/zones/%s' % zone_id)
+        except MalformedResponseError as e:
+            if e.body == 'Not found':
+                raise ZoneDoesNotExistError(driver=self,
+                                            value="The zone doesn't exists",
+                                            zone_id=zone_id)
+            raise e
+
+        zone = self._to_zone(response.object)
+        return zone
+
+    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`
+        """
+        try:
+            response = self.connection.request('/zones/%s/records/%s' %
+                                               (zone_id, record_id))
+        except MalformedResponseError as e:
+            if e.body == 'Not found':
+                raise RecordDoesNotExistError(value="Record doesn't exists",
+                                              driver=self,
+                                              record_id=record_id)
+            raise e
+
+        record = self._to_record(response.object, zone_id=zone_id)
+        return record
+
+    def create_zone(self, domain, ttl=None, extra=None):
+        """
+        Create a new zone.
+
+        :param domain: Zone domain name (e.g. example.com)
+        :type domain: ``str``
+
+        :param ttl: TTL for new records. (optional)
+        :type  ttl: ``int``
+
+        :param extra: Extra attributes (driver specific). (optional)
+        :type extra: ``dict``
+
+        :rtype: :class:`Zone`
+        """
+        r_json = {'name': domain}
+        if ttl is not None:
+            r_json['ttl'] = ttl
+        if extra is not None:
+            r_json.update(extra)
+        r_data = json.dumps({'zone': r_json})
+        response = self.connection.request('/zones', method='POST',
+                                           data=r_data)
+        zone = self._to_zone(response.object)
+        return zone
+
+    def update_zone(self, zone, domain, ttl=None, extra=None):
+        """
+        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 ttl: TTL for new records. (optional)
+        :type  ttl: ``int``
+
+        :param extra: Extra attributes (group, user-id). (optional)
+        :type  extra: ``dict``
+
+        :rtype: :class:`Zone`
+        """
+        r_json = {'name': domain}
+        if extra is not None:
+            r_json.update(extra)
+        r_data = json.dumps({'zone': r_json})
+        response = self.connection.request('/zones/%s' % zone.id,
+                                           method='PUT', data=r_data)
+        zone = self._to_zone(response.object)
+        return zone
+
+    def create_record(self, name, zone, type, data, extra=None):
+        """
+        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 (A, AAAA, ...).
+        :type  type: :class:`RecordType`
+
+        :param data: Data for the record (depends on the record type).
+        :type  data: ``str``
+
+        :param extra: Extra attributes (driver specific). (optional)
+        :type extra: ``dict``
+
+        :rtype: :class:`Record`
+        """
+        r_json = {'name': name, 'data': data, 'record_type': type}
+        if extra is not None:
+            r_json.update(extra)
+        r_data = json.dumps({'zone_record': r_json})
+        response = self.connection.request('/zones/%s/records' % zone.id,
+                                           method='POST', data=r_data)
+        record = self._to_record(response.object, zone=zone)
+        return record
+
+    def update_record(self, record, name, type, data, extra=None):
+        """
+        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 (A, AAAA, ...).
+        :type  type: :class:`RecordType`
+
+        :param data: Data for the record (depends on the record type).
+        :type  data: ``str``
+
+        :param extra: (optional) Extra attributes (driver specific).
+        :type  extra: ``dict``
+
+        :rtype: :class:`Record`
+        """
+        zone = record.zone
+        r_json = {'name': name, 'data': data, 'record_type': type}
+        if extra is not None:
+            r_json.update(extra)
+        r_data = json.dumps({'zone_record': r_json})
+        response = self.connection.request('/zones/%s/records/%s' %
+                                           (zone.id, record.id),
+                                           method='PUT', data=r_data)
+        record = self._to_record(response.object, zone=zone)
+        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``
+        """
+        self.connection.request('/zones/%s' % zone.id, method='DELETE')
+        return True
+
+    def delete_record(self, record):
+        """
+        Delete a record.
+
+        :param record: Record to delete.
+        :type  record: :class:`Record`
+
+        :rtype: ``bool``
+        """
+        zone_id = record.zone.id
+        record_id = record.id
+        self.connection.request('/zones/%s/records/%s' % (zone_id, record_id),
+                                method='DELETE')
+        return True
+
+    def ex_list_redirects(self, zone):
+        """
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+        """
+        response = self.connection.request('/zones/%s/redirects' % zone.id)
+        redirects = self._to_redirects(response.object, zone)
+        return redirects
+
+    def ex_list_mailredirects(self, zone):
+        """
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+        """
+        response = self.connection.request('/zones/%s/mail_redirects' %
+                                           zone.id)
+        mailredirects = self._to_mailredirects(response.object, zone)
+        return mailredirects
+
+    def ex_create_redirect(self, redirect_to, name, type, zone, iframe=None,
+                           query=None):
+        """
+        :param redirect_to: The data field. (redirect_to)
+        :type redirect_to: ``str``
+
+        :param name: The FQDN for the record.
+        :type name: ``str``
+
+        :param type: The type of redirects 301, 302 or 0 for iframes.
+        :type type: ``str``
+
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+
+        :param iframe: Title of iframe (optional).
+        :type iframe: ``str``
+
+        :param query: boolean Information about including query string when
+                      redirecting. (optional).
+        :type query: ``bool``
+        """
+        r_json = {'name': name, 'redirect_to': redirect_to}
+        if type is not None:
+            r_json['redirect_type'] = type
+        if iframe is not None:
+            r_json['iframe_title'] = iframe
+        if query is not None:
+            r_json['redirect_query_string'] = query
+        r_data = json.dumps({'zone_redirect': r_json})
+        response = self.connection.request('/zones/%s/redirects' % zone.id,
+                                           method='POST', data=r_data)
+        redirect = self._to_redirect(response.object, zone.id)
+        return redirect
+
+    def ex_create_mailredirect(self, destination, source, zone):
+        """
+        :param destination: The destination address of mail redirect.
+        :type destination: ``str``
+
+        :param source: The source address of mail redirect.
+        :type source: ``str``
+
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+        """
+        r_json = {'destination_address': destination, 'source_address': source}
+        r_data = json.dumps({'zone_mail_redirect': r_json})
+        response = self.connection.request('/zones/%s/mail_redirects' %
+                                           zone.id, method='POST',
+                                           data=r_data)
+        mailredirect = self._to_mailredirect(response.object, zone.id)
+        return mailredirect
+
+    def ex_get_redirect(self, zone, redirect_id):
+        """
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+
+        :param redirect_id: Redirect id.
+        :type redirect_id: ``str``
+        """
+        response = self.connection.request('/zones/%s/redirects/%s' %
+                                           (zone.id, redirect_id))
+        redirect = self._to_redirect(response.object, zone.id)
+        return redirect
+
+    def ex_get_mailredirects(self, zone, mail_r_id):
+        """
+        :param zone: Zone to list redirects for.
+        :type zone: :class:`Zone`
+
+        :param mail_r_id: Mail redirect id.
+        :type mail_r_id: ``str``
+        """
+        response = self.connection.request('/zones/%s/mail_redirects/%s' %
+                                           (zone.id, mail_r_id))
+        mailredirect = self._to_mailredirect(response.object, zone.id)
+        return mailredirect
+
+    def ex_update_redirect(self, redirect, redirect_to=None, name=None,
+                           type=None, iframe=None, query=None):
+        """
+        :param redirect: Record to update
+        :type id: :class:`Redirect`
+
+        :param redirect_to: The data field. (optional).
+        :type redirect_to: ``str``
+
+        :param name: The FQDN for the record.
+        :type name: ``str``
+
+        :param type: The type of redirects 301, 302 or 0 for iframes.
+                     (optional).
+        :type type: ``str``
+
+        :param iframe: Title of iframe (optional).
+        :type iframe: ``str``
+
+        :param query: boolean Information about including query string when
+                      redirecting. (optional).
+        :type query: ``bool``
+        """
+        zone_id = redirect.zone_id
+        r_json = {}
+        if redirect_to is not None:
+            r_json['redirect_to'] = redirect_to
+        if name is not None:
+            r_json['name'] = name
+        if type is not None:
+            r_json['record_type'] = type
+        if iframe is not None:
+            r_json['iframe_title'] = iframe
+        if query is not None:
+            r_json['redirect_query_string'] = query
+        r_data = json.dumps({'zone_redirect': r_json})
+        response = self.connection.request('/zones/%s/redirects/%s' %
+                                           (zone_id, redirect.id),
+                                           method='PUT', data=r_data)
+        redirect = self._to_redirect(response.object, zone_id=zone_id)
+        return redirect
+
+    def ex_update_mailredirect(self, mail_r, destination, source=None):
+        """
+        :param mail_r: Mail redirect to update
+        :type mail_r: :class:`MailRedirect`
+
+        :param destination: The destination address of mail redirect.
+        :type destination: ``str``
+
+        :param source: The source address of mail redirect. (optional)
+        :type source: ``str``
+        """
+        zone_id = mail_r.zone_id
+        r_json = {'destination_address': destination}
+        if source is not None:
+            r_json['source_address'] = source
+        r_data = json.dumps({'zone_redirect': r_json})
+        response = self.connection.request('/zones/%s/mail_redirects/%s' %
+                                           (zone_id, mail_r.id),
+                                           method='PUT', data=r_data)
+        mailredirect = self._to_mailredirect(response.object, zone_id=zone_id)
+        return mailredirect
+
+    def ex_delete_redirect(self, redirect):
+        """
+        :param mail_r: Redirect to delete
+        :type mail_r: :class:`Redirect`
+        """
+        zone_id = redirect.zone_id
+        redirect_id = redirect.id
+        self.connection.request('/zones/%s/redirects/%s' % (zone_id,
+                                redirect_id), method='DELETE')
+        return True
+
+    def ex_delete_mailredirect(self, mail_r):
+        """
+        :param mail_r: Mail redirect to update
+        :type mail_r: :class:`MailRedirect`
+        """
+        zone_id = mail_r.zone_id
+        mail_r_id = mail_r.id
+        self.connection.request('/zones/%s/mail_redirects/%s' % (zone_id,
+                                mail_r_id), method='DELETE')
+        return True
+
+    def _to_zones(self, data):
+        zones = []
+        for zone in data:
+            _zone = self._to_zone(zone)
+            zones.append(_zone)
+
+        return zones
+
+    def _to_zone(self, data):
+        zone = data.get('zone')
+        id = zone.get('id')
+        name = zone.get('name')
+        ttl = zone.get('ttl')
+        extra = {'group': zone.get('group'),
+                 'user-id': zone.get('user-id')}
+
+        # All zones are a primary ones by design, so they
+        # assume that are the master source of info about the
+        # zone, which is the case when domain DNS records
+        # points to PointDNS nameservers.
+        type = 'master'
+
+        return Zone(id=id, domain=name, type=type, ttl=ttl, driver=self,
+                    extra=extra)
+
+    def _to_records(self, data, zone):
+        records = []
+        for item in data:
+            record = self._to_record(item, zone=zone)
+            records.append(record)
+        return records
+
+    def _to_record(self, data, zone_id=None, zone=None):
+        if not zone:  # We need zone_id or zone
+            zone = self.get_zone(zone_id)
+        record = data.get('zone_record')
+        id = record.get('id')
+        name = record.get('name')
+        type = record.get('record_type')
+        data = record.get('data')
+        extra = {'ttl': record.get('ttl'),
+                 'zone_id': record.get('zone_id'),
+                 'aux': record.get('aux')}
+        return Record(id, name, type, data, zone, self, extra=extra)
+
+    def _to_redirects(self, data, zone):
+        redirects = []
+        for item in data:
+            redirect = self._to_redirect(item, zone.id)
+            redirects.append(redirect)
+        return redirects
+
+    def _to_redirect(self, data, zone_id):
+        record = data.get('zone_redirect')
+        id = record.get('id')
+        name = record.get('name')
+        redirect_to = record.get('redirect_to')
+        type = record.get('redirect_type')
+        iframe = record.get('iframe_title')
+        query = record.get('redirect_query_string')
+        return Redirect(id, name, redirect_to, type, self, zone_id,
+                        iframe=iframe, query=query)
+
+    def _to_mailredirects(self, data, zone):
+        mailredirects = []
+        for item in data:
+            mailredirect = self._to_mailredirect(item, zone=zone)
+            mailredirects.append(mailredirect)
+        return mailredirects
+
+    def _to_mailredirect(self, data, zone_id):
+        record = data.get('zone_mail_redirect')
+        id = record.get('id')
+        destination = record.get('destination_address')
+        source = record.get('source_address')
+        return MailRedirect(id, source, destination, zone_id, self)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/dns/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py
index 4479474..b54fe4d 100644
--- a/libcloud/dns/providers.py
+++ b/libcloud/dns/providers.py
@@ -41,6 +41,9 @@ DRIVERS = {
     ('libcloud.dns.drivers.worldwidedns', 'WorldWideDNSDriver'),
     Provider.DNSIMPLE:
     ('libcloud.dns.drivers.dnsimple', 'DNSimpleDNSDriver'),
+    Provider.POINTDNS:
+    ('libcloud.dns.drivers.pointdns', 'PointDNSDriver'),
+    # Deprecated
     Provider.RACKSPACE_US:
     ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'),
     Provider.RACKSPACE_UK:

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/dns/types.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py
index fe54344..01fa06b 100644
--- a/libcloud/dns/types.py
+++ b/libcloud/dns/types.py
@@ -41,6 +41,7 @@ class Provider(object):
     AURORADNS = 'auroradns'
     WORLDWIDEDNS = 'worldwidedns'
     DNSIMPLE = 'dnsimple'
+    POINTDNS = 'pointdns'
 
     # Deprecated
     RACKSPACE_US = 'rackspace_us'

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_1_DELETE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_DELETE.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_DELETE.json
new file mode 100644
index 0000000..755ad0d
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_DELETE.json
@@ -0,0 +1,5 @@
+{
+    "zone": {
+        "status": "OK"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_GET.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_GET.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_GET.json
new file mode 100644
index 0000000..8b3c0d8
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_GET.json
@@ -0,0 +1,11 @@
+{
+    "zone_record": {
+        "name": "site.example.com",
+        "data": "1.2.3.4",
+        "id": 141,
+        "aux": null,
+        "record_type": "A",
+        "ttl": 3600,
+        "zone_id": 1
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json
new file mode 100644
index 0000000..f13b76a
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_141_UPDATE.json
@@ -0,0 +1,11 @@
+{
+    "zone_record": {
+        "name": "updated.com",
+        "data": "1.2.3.5",
+        "id": 141,
+        "aux": null,
+        "record_type": "A",
+        "ttl": 4500,
+        "zone_id": 1
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_1_records_150_DELETE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_150_DELETE.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_150_DELETE.json
new file mode 100644
index 0000000..6c6dd68
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_150_DELETE.json
@@ -0,0 +1,5 @@
+{
+    "zone_record": {
+        "status": "OK"
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json
new file mode 100644
index 0000000..607a68d
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_1_records_GET.json
@@ -0,0 +1,22 @@
+[{
+    "zone_record": {
+        "name": "site.example.com",
+        "data": "1.2.3.4",
+        "id": 141,
+        "aux": null,
+        "record_type": "A",
+        "ttl": 3600,
+        "zone_id": 1
+    }
+},
+{
+    "zone_record": {
+        "name": "site.example1.com",
+        "data": "1.2.3.6",
+        "id": 150,
+        "aux": null,
+        "record_type": "A",
+        "ttl": 3600,
+        "zone_id": 1
+    }
+}]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_CREATE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_CREATE.json b/libcloud/test/dns/fixtures/pointdns/_zones_CREATE.json
new file mode 100644
index 0000000..a5fdeb3
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_CREATE.json
@@ -0,0 +1,9 @@
+{
+    "zone": {
+        "name": "example.com",
+        "id": 2,
+        "group": "Default Group",
+        "user-id": 4,
+        "ttl": 3600
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_GET.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_GET.json b/libcloud/test/dns/fixtures/pointdns/_zones_GET.json
new file mode 100644
index 0000000..46d1c0b
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_GET.json
@@ -0,0 +1,20 @@
+[
+    {
+        "zone": {
+            "name": "example.com",
+            "id": 1,
+            "group": "Default Group",
+            "user-id": 3,
+            "ttl": 3600
+        }
+    },
+    {
+        "zone": {
+            "name": "example2.com",
+            "id": 2,
+            "group": "Default Group",
+            "user-id": 4,
+            "ttl": 3600
+        }
+    }
+]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_GET_1.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_GET_1.json b/libcloud/test/dns/fixtures/pointdns/_zones_GET_1.json
new file mode 100644
index 0000000..8f21822
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_GET_1.json
@@ -0,0 +1,9 @@
+{
+    "zone": {
+        "name": "example.com",
+        "id": 1,
+        "group": "Default Group",
+        "user-id": 3,
+        "ttl": 3600
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_example_com_UPDATE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_example_com_UPDATE.json b/libcloud/test/dns/fixtures/pointdns/_zones_example_com_UPDATE.json
new file mode 100644
index 0000000..075b289
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_example_com_UPDATE.json
@@ -0,0 +1,9 @@
+{
+    "zone": {
+        "name": "example.com",
+        "id": 1,
+        "group": "Other Group",
+        "user-id": 3,
+        "ttl": 3600
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/fixtures/pointdns/_zones_example_com_records_CREATE.json
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/fixtures/pointdns/_zones_example_com_records_CREATE.json b/libcloud/test/dns/fixtures/pointdns/_zones_example_com_records_CREATE.json
new file mode 100644
index 0000000..51b393d
--- /dev/null
+++ b/libcloud/test/dns/fixtures/pointdns/_zones_example_com_records_CREATE.json
@@ -0,0 +1,11 @@
+{
+    "zone_record": {
+        "name": "site.example.com",
+        "data": "1.2.3.4",
+        "id": 143,
+        "aux": null,
+        "record_type": "A",
+        "ttl": 3600,
+        "zone_id": 1
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/763dfb38/libcloud/test/dns/test_pointdns.py
----------------------------------------------------------------------
diff --git a/libcloud/test/dns/test_pointdns.py b/libcloud/test/dns/test_pointdns.py
new file mode 100644
index 0000000..aa85e9b
--- /dev/null
+++ b/libcloud/test/dns/test_pointdns.py
@@ -0,0 +1,210 @@
+# 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
+from libcloud.dns.drivers.pointdns import PointDNSDriver
+
+from libcloud.test import MockHttp
+from libcloud.test.file_fixtures import DNSFileFixtures
+from libcloud.test.secrets import DNS_PARAMS_POINTDNS
+
+
+class PointDNSTests(unittest.TestCase):
+    def setUp(self):
+        PointDNSDriver.connectionCls.conn_classes = (
+            None, PointDNSMockHttp)
+        PointDNSMockHttp.type = None
+        self.driver = PointDNSDriver(*DNS_PARAMS_POINTDNS)
+
+    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), 10)
+        self.assertTrue(RecordType.A in record_types)
+        self.assertTrue(RecordType.AAAA in record_types)
+        self.assertTrue(RecordType.ALIAS in record_types)
+        self.assertTrue(RecordType.CNAME in record_types)
+        self.assertTrue(RecordType.MX in record_types)
+        self.assertTrue(RecordType.NS in record_types)
+        self.assertTrue(RecordType.PTR in record_types)
+        self.assertTrue(RecordType.SRV in record_types)
+        self.assertTrue(RecordType.SSHFP in record_types)
+        self.assertTrue(RecordType.TXT in record_types)
+
+    def test_list_zones_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zones = self.driver.list_zones()
+        self.assertEqual(len(zones), 2)
+
+        zone1 = zones[0]
+        self.assertEqual(zone1.id, '1')
+        self.assertEqual(zone1.type, 'master')
+        self.assertEqual(zone1.domain, 'example.com')
+        self.assertEqual(zone1.ttl, 3600)
+        self.assertHasKeys(zone1.extra, ['group', 'user-id'])
+
+        zone2 = zones[1]
+        self.assertEqual(zone2.id, '2')
+        self.assertEqual(zone2.type, 'master')
+        self.assertEqual(zone2.domain, 'example2.com')
+        self.assertEqual(zone2.ttl, 3600)
+        self.assertHasKeys(zone2.extra, ['group', 'user-id'])
+
+    def test_list_records_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 2)
+
+        record1 = records[0]
+        self.assertEqual(record1.id, '141')
+        self.assertEqual(record1.name, 'site.example.com')
+        self.assertEqual(record1.type, RecordType.A)
+        self.assertEqual(record1.data, '1.2.3.4')
+        self.assertHasKeys(record1.extra, ['ttl', 'zone_id', 'aux'])
+
+        record2 = records[1]
+        self.assertEqual(record2.id, '150')
+        self.assertEqual(record2.name, 'site.example1.com')
+        self.assertEqual(record2.type, RecordType.A)
+        self.assertEqual(record2.data, '1.2.3.6')
+        self.assertHasKeys(record2.extra, ['ttl', 'zone_id', 'aux'])
+
+    def test_get_zone_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zone1 = self.driver.get_zone(zone_id='1')
+        self.assertEqual(zone1.id, '1')
+        self.assertEqual(zone1.type, 'master')
+        self.assertEqual(zone1.domain, 'example.com')
+        self.assertEqual(zone1.ttl, 3600)
+        self.assertHasKeys(zone1.extra, ['group', 'user-id'])
+
+    def test_get_record_success(self):
+        PointDNSMockHttp.type = 'GET'
+        record = self.driver.get_record(zone_id='1',
+                                        record_id='141')
+        self.assertEqual(record.id, '141')
+        self.assertEqual(record.name, 'site.example.com')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '1.2.3.4')
+        self.assertHasKeys(record.extra, ['ttl', 'zone_id', 'aux'])
+
+    def test_create_zone_success(self):
+        PointDNSMockHttp.type = 'CREATE'
+        zone = self.driver.create_zone(domain='example.com')
+        self.assertEqual(zone.id, '2')
+        self.assertEqual(zone.domain, 'example.com')
+        self.assertEqual(zone.ttl, 3600)
+        self.assertEqual(zone.type, 'master')
+        self.assertHasKeys(zone.extra, ['group', 'user-id'])
+
+    def test_create_record_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zone = self.driver.list_zones()[0]
+        PointDNSMockHttp.type = 'CREATE'
+        record = self.driver.create_record(name='site.example.com', zone=zone,
+                                           type=RecordType.A,
+                                           data='1.2.3.4')
+        self.assertEqual(record.id, '143')
+        self.assertEqual(record.name, 'site.example.com')
+        self.assertEqual(record.type, RecordType.A)
+        self.assertEqual(record.data, '1.2.3.4')
+        self.assertHasKeys(record.extra, ['ttl', 'zone_id', 'aux'])
+
+    def test_update_record_success(self):
+        PointDNSMockHttp.type = 'GET'
+        record = self.driver.get_record(zone_id='1',
+                                        record_id='141')
+        PointDNSMockHttp.type = 'UPDATE'
+        extra = {'ttl': 4500}
+        record1 = self.driver.update_record(record=record, name='updated.com',
+                                            type=RecordType.A, data='1.2.3.5',
+                                            extra=extra)
+        self.assertEqual(record.data, '1.2.3.4')
+        self.assertEqual(record.extra.get('ttl'), 3600)
+        self.assertEqual(record1.data, '1.2.3.5')
+        self.assertEqual(record1.extra.get('ttl'), 4500)
+
+    def test_delete_zone_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zone = self.driver.list_zones()[0]
+        PointDNSMockHttp.type = 'DELETE'
+        status = self.driver.delete_zone(zone=zone)
+        self.assertTrue(status)
+
+    def test_delete_record_success(self):
+        PointDNSMockHttp.type = 'GET'
+        zone = self.driver.list_zones()[0]
+        records = self.driver.list_records(zone=zone)
+        self.assertEqual(len(records), 2)
+        record = records[1]
+        PointDNSMockHttp.type = 'DELETE'
+        status = self.driver.delete_record(record=record)
+        self.assertTrue(status)
+
+
+class PointDNSMockHttp(MockHttp):
+    fixtures = DNSFileFixtures('pointdns')
+
+    def _zones_GET(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_GET.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_CREATE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_CREATE.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_GET(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_GET_1.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_example_com_UPDATE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_example_com_UPDATE.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_DELETE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_1_DELETE.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_records_CREATE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_example_com_records_CREATE.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_records_GET(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_1_records_GET.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_records_141_GET(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_1_records_141_GET.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_records_141_UPDATE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_1_records_141_UPDATE.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _zones_1_records_150_DELETE(self, method, url, body, headers):
+        body = self.fixtures.load('_zones_1_records_150_DELETE.json')
+        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/763dfb38/libcloud/test/secrets.py-dist
----------------------------------------------------------------------
diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist
index 2a74b57..50f2970 100644
--- a/libcloud/test/secrets.py-dist
+++ b/libcloud/test/secrets.py-dist
@@ -74,3 +74,4 @@ DNS_PARAMS_GOOGLE = ('email_address', 'key')
 DNS_KEYWORD_PARAMS_GOOGLE = {'project': 'project_name'}
 DNS_PARAMS_WORLDWIDEDNS = ('user', 'key')
 DNS_PARAMS_DNSIMPLE = ('user', 'key')
+DNS_PARAMS_POINTDNS = ('user', 'key')


Mime
View raw message