Repository: libcloud
Updated Branches:
  refs/heads/trunk e83dcb091 -> c81bb3abb


LIBCLOUD-735: Implementation of AuroraDNS driver

AuroraDNS is the new DNS product of PCextreme under the cloud name 'Aurora'.

Closes #563

Signed-off-by: Tomaz Muraus <[email protected]>


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

Branch: refs/heads/trunk
Commit: 810d3ad9cf9ec599602cb505d8adb240ccedc1ea
Parents: e83dcb0
Author: Wido den Hollander <[email protected]>
Authored: Thu Jul 2 21:57:01 2015 +0200
Committer: Tomaz Muraus <[email protected]>
Committed: Wed Aug 26 21:31:34 2015 +0200

----------------------------------------------------------------------
 CHANGES.rst                                     |   7 +
 docs/dns/drivers/auroradns.rst                  |  63 +++++
 .../dns/auroradns/enable_disable_record.py      |  13 +
 .../dns/auroradns/instantiate_driver.py         |   6 +
 libcloud/dns/drivers/auroradns.py               | 256 +++++++++++++++++++
 libcloud/dns/providers.py                       |   4 +-
 libcloud/dns/types.py                           |   1 +
 7 files changed, 349 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index 1510cfe..a846ec7 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -17,6 +17,13 @@ Compute
   (GITHUB-561)
   [ZuluPro]
 
+DNS
+~~~
+
+- Add new driver for AuroraDNS service.
+  (GITHUB-562, LIBCLOUD-735)
+  [Wido den Hollander]
+
 Storage
 ~~~~~~~
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/docs/dns/drivers/auroradns.rst
----------------------------------------------------------------------
diff --git a/docs/dns/drivers/auroradns.rst b/docs/dns/drivers/auroradns.rst
new file mode 100644
index 0000000..56b1da8
--- /dev/null
+++ b/docs/dns/drivers/auroradns.rst
@@ -0,0 +1,63 @@
+AuroraDNS DNS driver documentation
+==================================
+
+`PCextreme B.V.`_ is a Dutch cloud provider. It provides a public cloud 
offering
+under the name AuroraCompute. All cloud services are under the family name
+Aurora.
+
+`AuroraDNS`_ is a highly available DNS service which also provides health
+checking.
+
+Records can be attached to a health check. When this health check becomes
+unhealthy this record will no longer be served.
+
+This provides the possibility to create loadbalancing over multiple servers
+without the requirement of a central loadbalancer in the network. It is also
+provider agnostic, health checks can point to any IP/Host on the internet.
+
+IPv6
+----
+AuroraDNS fully supports IPv6:
+
+* DNS queries over IPv6
+* AAAA records
+* Health checks over IPv6
+
+Instantiating a driver
+----------------------
+
+When you instantiate a driver, you need to pass a your ``key`` and ``secret``
+to the driver constructor. These can be obtained in the control panel of
+AuroraDNS.
+
+For example:
+
+.. literalinclude:: /examples/dns/auroradns/instantiate_driver.py
+   :language: python
+
+Disabling and enabling records
+------------------------------
+
+Records in can be disabled and enabled. By default all new records are enabled,
+but this property can be set during creation and can be updated.
+
+For example:
+
+.. literalinclude:: /examples/dns/auroradns/instantiate_driver.py
+   :language: python
+
+In this example we create a record, but disable it. This means it will not be
+served.
+
+Afterwards we enable the record and this make the DNS server serve this 
specific
+record.
+
+API Docs
+--------
+
+.. autoclass:: libcloud.dns.drivers.auroradns.AuroraDNSDriver
+    :members:
+    :inherited-members:
+
+.. _`PCextreme B.V.`: https://www.pcextreme.nl/
+.. _`AuroraDNS`: https://www.pcextreme.nl/en/aurora/dns

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/docs/examples/dns/auroradns/enable_disable_record.py
----------------------------------------------------------------------
diff --git a/docs/examples/dns/auroradns/enable_disable_record.py 
b/docs/examples/dns/auroradns/enable_disable_record.py
new file mode 100644
index 0000000..ef1d76b
--- /dev/null
+++ b/docs/examples/dns/auroradns/enable_disable_record.py
@@ -0,0 +1,13 @@
+from libcloud.dns.types import Provider
+from libcloud.dns.providers import get_driver
+from libcloud.dns.types import RecordType
+
+cls = get_driver(Provider.AURORADNS)
+
+driver = cls('myapikey', 'mysecret')
+
+zone = driver.get_zone('auroradns.eu')
+
+record = zone.create_record(name='www', type=RecordType.AAAA,
+                            data='2a00:f10:452::1', ex_disabled=True)
+record.update(ex_disabled=False)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/docs/examples/dns/auroradns/instantiate_driver.py
----------------------------------------------------------------------
diff --git a/docs/examples/dns/auroradns/instantiate_driver.py 
b/docs/examples/dns/auroradns/instantiate_driver.py
new file mode 100644
index 0000000..1690617
--- /dev/null
+++ b/docs/examples/dns/auroradns/instantiate_driver.py
@@ -0,0 +1,6 @@
+from libcloud.dns.types import Provider
+from libcloud.dns.providers import get_driver
+
+cls = get_driver(Provider.AURORADNS)
+
+driver = cls('myapikey', 'mysecret')

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/libcloud/dns/drivers/auroradns.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/drivers/auroradns.py 
b/libcloud/dns/drivers/auroradns.py
new file mode 100644
index 0000000..614de15
--- /dev/null
+++ b/libcloud/dns/drivers/auroradns.py
@@ -0,0 +1,256 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import json
+import hmac
+import datetime
+
+from hashlib import sha256
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.py3 import b
+
+from libcloud.common.base import ConnectionUserAndKey, JsonResponse
+
+from libcloud.common.types import InvalidCredsError, ProviderError
+
+from libcloud.dns.base import DNSDriver, Zone, Record
+from libcloud.dns.types import RecordType, ZoneDoesNotExistError
+from libcloud.dns.types import ZoneAlreadyExistsError, RecordDoesNotExistError
+
+
+API_HOST = 'api.auroradns.eu'
+
+# Default TTL required by libcloud, but doesn't do anything in AuroraDNS
+DEFAULT_ZONE_TTL = 3600
+DEFAULT_ZONE_TYPE = 'master'
+
+VALID_RECORD_PARAMS_EXTRA = ['ttl' 'prio', 'health_check_id', 'disabled']
+
+
+class AuroraDNSResponse(JsonResponse):
+    def success(self):
+        return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED]
+
+    def parse_error(self):
+        status = int(self.status)
+
+        if status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError(value='Authentication failed', driver=self)
+        elif status == httplib.FORBIDDEN:
+            raise ProviderError(value='Authorization failed', http_code=status,
+                                driver=self)
+        elif status == httplib.NOT_FOUND:
+            context = self.connection.context
+            if context['resource'] == 'zone':
+                raise ZoneDoesNotExistError(value='', driver=self,
+                                            zone_id=context['id'])
+            elif context['resource'] == 'record':
+                raise RecordDoesNotExistError(value='', driver=self,
+                                              record_id=context['id'])
+        elif status == httplib.CONFLICT:
+            context = self.connection.context
+            if context['resource'] == 'zone':
+                raise ZoneAlreadyExistsError(value='', driver=self,
+                                             zone_id=context['id'])
+
+
+class AuroraDNSConnection(ConnectionUserAndKey):
+    host = API_HOST
+    responseCls = AuroraDNSResponse
+
+    def calculate_auth_signature(self, secret_key, method, url, timestamp):
+        b64_hmac = base64.b64encode(
+            hmac.new(b(secret_key),
+                     b(method) + b(url) + b(timestamp),
+                     digestmod=sha256).digest()
+        )
+
+        return b64_hmac.decode('utf-8')
+
+    def gen_auth_header(self, api_key, secret_key, method, url, timestamp):
+        signature = self.calculate_auth_signature(secret_key, method, url,
+                                                  timestamp)
+
+        auth_b64 = base64.b64encode(b('%s:%s' % (api_key, signature)))
+        return 'AuroraDNSv1 %s' % (auth_b64.decode('utf-8'))
+
+    def request(self, action, params=None, data='', headers=None,
+                method='GET'):
+        if not headers:
+            headers = {}
+        if not params:
+            params = {}
+
+        if method in ("POST", "PUT"):
+            headers = {'Content-Type': 'application/json; charset=UTF-8'}
+
+        t = datetime.datetime.utcnow()
+        timestamp = t.strftime('%Y%m%dT%H%M%SZ')
+
+        headers['X-AuroraDNS-Date'] = timestamp
+        headers['Authorization'] = self.gen_auth_header(self.user_id, self.key,
+                                                        method, action,
+                                                        timestamp)
+
+        return super(AuroraDNSConnection, self).request(action=action,
+                                                        params=params,
+                                                        data=data,
+                                                        method=method,
+                                                        headers=headers)
+
+
+class AuroraDNSDriver(DNSDriver):
+    name = 'AuroraDNS'
+    website = 'https://www.pcextreme.nl/en/aurora/dns'
+    connectionCls = AuroraDNSConnection
+
+    RECORD_TYPE_MAP = {
+        RecordType.A: 'A',
+        RecordType.AAAA: 'AAAA',
+        RecordType.CNAME: 'CNAME',
+        RecordType.MX: 'MX',
+        RecordType.NS: 'NS',
+        RecordType.SOA: 'SOA',
+        RecordType.SPF: 'SPF',
+        RecordType.SRV: 'SRV',
+        RecordType.TXT: 'TXT',
+    }
+
+    def list_zones(self):
+        zones = []
+
+        res = self.connection.request('/zones')
+        for zone in res.parse_body():
+            zones.append(self.__res_to_zone(zone))
+
+        return zones
+
+    def list_records(self, zone):
+        records = []
+        res = self.connection.request('/zones/%s/records' % zone.id)
+
+        for record in res.parse_body():
+            records.append(self.__res_to_record(zone, record))
+
+        return records
+
+    def get_zone(self, zone_id):
+        self.connection.set_context({'resource': 'zone', 'id': zone_id})
+        res = self.connection.request('/zones/%s' % zone_id)
+        zone = res.parse_body()
+        return self.__res_to_zone(zone)
+
+    def get_record(self, zone_id, record_id):
+        self.connection.set_context({'resource': 'record', 'id': record_id})
+        res = self.connection.request('/zones/%s/records/%s' % (zone_id,
+                                                                record_id))
+        record = res.parse_body()
+
+        zone = self.get_zone(zone_id)
+
+        return self.__res_to_record(zone, record)
+
+    def create_zone(self, domain, type='master', ttl=None, extra=None):
+        self.connection.set_context({'resource': 'zone', 'id': domain})
+        res = self.connection.request('/zones', method='POST',
+                                      data=json.dumps({'name': domain}))
+        zone = res.parse_body()
+        return self.__res_to_zone(zone)
+
+    def create_record(self, name, zone, type, data, extra=None):
+        if name is None:
+            name = ""
+
+        rdata = {
+            'name': name,
+            'type': type,
+            'content': data
+        }
+
+        rdata = self.__merge_extra_data(rdata, extra)
+
+        self.connection.set_context({'resource': 'zone', 'id': zone.id})
+        res = self.connection.request('/zones/%s/records' % zone.id,
+                                      method='POST',
+                                      data=json.dumps(rdata))
+
+        record = res.parse_body()
+        return self.__res_to_record(zone, record)
+
+    def delete_zone(self, zone):
+        self.connection.request('/zones/%s' % zone.id, method='DELETE')
+
+    def delete_record(self, record):
+        self.connection.set_context({'resource': 'record', 'id': record.id})
+        self.connection.request('/zones/%s/records/%s' % (record.zone.id,
+                                                          record.id),
+                                method='DELETE')
+
+    def update_record(self, record, name, type, data, extra):
+        rdata = {}
+
+        if name is not None:
+            rdata['name'] = name
+
+        if type is not None:
+            rdata['type'] = type
+
+        if data is not None:
+            rdata['content'] = data
+
+        rdata = self.__merge_extra_data(rdata, extra)
+
+        self.connection.set_context({'resource': 'record', 'id': record.id})
+        self.connection.request('/zones/%s/records/%s' % (record.zone.id,
+                                                          record.id),
+                                method='PUT',
+                                data=json.dumps(rdata))
+
+        return self.get_record(record.zone.id, record.id)
+
+    def __res_to_record(self, zone, record):
+        if len(record['name']) == 0:
+            name = None
+        else:
+            name = record['name']
+
+        extra = {}
+        extra['created'] = record['created']
+        extra['modified'] = record['modified']
+        extra['disabled'] = record['disabled']
+        extra['ttl'] = record['ttl']
+        extra['prio'] = record['prio']
+
+        return Record(id=record['id'], name=name, type=record['type'],
+                      data=record['content'], zone=zone, driver=self,
+                      extra=extra)
+
+    def __res_to_zone(self, zone):
+        return Zone(id=zone['id'], domain=zone['name'], type=DEFAULT_ZONE_TYPE,
+                    ttl=DEFAULT_ZONE_TTL, driver=self,
+                    extra={'created': zone['created'],
+                           'servers': zone['servers'],
+                           'account_id': zone['account_id'],
+                           'cluster_id': zone['cluster_id']})
+
+    def __merge_extra_data(self, rdata, extra):
+        if extra is not None:
+            for param in VALID_RECORD_PARAMS_EXTRA:
+                if param in extra:
+                    rdata[param] = extra[param]
+
+        return rdata

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/libcloud/dns/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py
index a6c92a8..aef0aa7 100644
--- a/libcloud/dns/providers.py
+++ b/libcloud/dns/providers.py
@@ -41,7 +41,9 @@ DRIVERS = {
     Provider.RACKSPACE_US:
     ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'),
     Provider.RACKSPACE_UK:
-    ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver')
+    ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver'),
+    Provider.AURORADNS:
+    ('libcloud.dns.drivers.auroradns', 'AuroraDNSDriver')
 }
 
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/810d3ad9/libcloud/dns/types.py
----------------------------------------------------------------------
diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py
index cd1ae85..ad2b34a 100644
--- a/libcloud/dns/types.py
+++ b/libcloud/dns/types.py
@@ -38,6 +38,7 @@ class Provider(object):
     GOOGLE = 'google'
     SOFTLAYER = 'softlayer'
     DIGITAL_OCEAN = 'digitalocean'
+    AURORADNS = 'auroradns'
 
     # Deprecated
     RACKSPACE_US = 'rackspace_us'

Reply via email to