From cacc8a9c9b915b5ab10a4ad59db034bd45d53b22 Mon Sep 17 00:00:00 2001 From: Alejandro Pereira Date: Mon, 31 Aug 2015 19:20:41 -0300 Subject: [PATCH] [LIBCLOUD-739] Add DNSimple provider implementation --- docs/dns/drivers/dnsimple.rst | 22 ++ .../dns/dnsimple/instantiate_driver.py | 6 + libcloud/common/dnsimple.py | 52 ++++ libcloud/dns/drivers/dnsimple.py | 281 ++++++++++++++++++ libcloud/dns/providers.py | 2 + libcloud/dns/types.py | 4 + .../dns/fixtures/dnsimple/create_domain.json | 20 ++ .../dnsimple/create_domain_record.json | 13 + .../dns/fixtures/dnsimple/get_domain.json | 20 ++ .../fixtures/dnsimple/get_domain_record.json | 13 + .../dnsimple/list_domain_records.json | 42 +++ .../dns/fixtures/dnsimple/list_domains.json | 42 +++ .../dnsimple/update_domain_record.json | 13 + libcloud/test/dns/test_dnsimple.py | 254 ++++++++++++++++ libcloud/test/secrets.py-dist | 1 + 15 files changed, 785 insertions(+) create mode 100644 docs/dns/drivers/dnsimple.rst create mode 100644 docs/examples/dns/dnsimple/instantiate_driver.py create mode 100644 libcloud/common/dnsimple.py create mode 100644 libcloud/dns/drivers/dnsimple.py create mode 100644 libcloud/test/dns/fixtures/dnsimple/create_domain.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/create_domain_record.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/get_domain.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/get_domain_record.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/list_domain_records.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/list_domains.json create mode 100644 libcloud/test/dns/fixtures/dnsimple/update_domain_record.json create mode 100644 libcloud/test/dns/test_dnsimple.py diff --git a/docs/dns/drivers/dnsimple.rst b/docs/dns/drivers/dnsimple.rst new file mode 100644 index 0000000000..24cf40c7b1 --- /dev/null +++ b/docs/dns/drivers/dnsimple.rst @@ -0,0 +1,22 @@ +DNSimple DNS Driver Documentation +================================== + +`DNSimple`_ is a hosted DNS Service Provider that you can use to manage your +domains. Offers both a web interface and an iPhone application for adding and +removing domains and DNS records as well as an HTTP API with various code +libraries and tools. + +Instantiating the driver +------------------------------------- + +.. literalinclude:: /examples/dns/dnsimple/instantiate_driver.py + :language: python + +API Docs +-------- + +.. autoclass:: libcloud.dns.drivers.dnsimple.DNSimpleDNSDriver + :members: + :inherited-members: + +.. _`DNSimple`: https://dnsimple.com/ \ No newline at end of file diff --git a/docs/examples/dns/dnsimple/instantiate_driver.py b/docs/examples/dns/dnsimple/instantiate_driver.py new file mode 100644 index 0000000000..b69301db57 --- /dev/null +++ b/docs/examples/dns/dnsimple/instantiate_driver.py @@ -0,0 +1,6 @@ +from libcloud.dns.types import Provider +from libcloud.dns.providers import get_driver + +cls = get_driver(Provider.DNSIMPLE) + +driver = cls('username', 'apikey') diff --git a/libcloud/common/dnsimple.py b/libcloud/common/dnsimple.py new file mode 100644 index 0000000000..cd3d852146 --- /dev/null +++ b/libcloud/common/dnsimple.py @@ -0,0 +1,52 @@ +# 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 httplib +from libcloud.common.base import ConnectionUserAndKey +from libcloud.common.base import JsonResponse + + +class DNSimpleDNSResponse(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 204? + return self.status in [httplib.OK, httplib.CREATED, httplib.NO_CONTENT] + + +class DNSimpleDNSConnection(ConnectionUserAndKey): + host = 'api.dnsimple.com' + responseCls = DNSimpleDNSResponse + + def add_default_headers(self, headers): + """ + Add headers that are necessary for every request + + This method adds ``token`` to the request. + """ + # TODO: fijarse sobre que info se paso como parametro y en base + # a esto, fijar el header + headers['X-DNSimple-Token'] = '%s:%s' % (self.user_id, self.key) + headers['Accept'] = 'application/json' + headers['Content-Type'] = 'application/json' + return headers diff --git a/libcloud/dns/drivers/dnsimple.py b/libcloud/dns/drivers/dnsimple.py new file mode 100644 index 0000000000..3c2e7527b0 --- /dev/null +++ b/libcloud/dns/drivers/dnsimple.py @@ -0,0 +1,281 @@ +# 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. +""" +DNSimple DNS Driver +""" + +__all__ = [ + 'DNSimpleDNSDriver' +] + +import json + +from libcloud.common.dnsimple import DNSimpleDNSConnection +from libcloud.dns.types import Provider, RecordType +from libcloud.dns.base import DNSDriver, Zone, Record + + +DEFAULT_ZONE_TTL = 3600 + + +class DNSimpleDNSDriver(DNSDriver): + type = Provider.DNSIMPLE + name = 'DNSimple' + website = 'https://dnsimple.com/' + connectionCls = DNSimpleDNSConnection + + RECORD_TYPE_MAP = { + RecordType.A: 'A', + RecordType.AAAA: 'AAAA', + RecordType.ALIAS: 'ALIAS', + RecordType.CNAME: 'CNAME', + RecordType.HINFO: 'HINFO', + RecordType.MX: 'MX', + RecordType.NAPTR: 'NAPTR', + RecordType.NS: 'NS', + 'POOL': 'POOL', + RecordType.SOA: 'SOA', + RecordType.SPF: 'SPF', + RecordType.SRV: 'SRV', + RecordType.SSHFP: 'SSHFP', + RecordType.TXT: 'TXT', + RecordType.URL: 'URL' + } + + def list_zones(self): + """ + Return a list of zones. + + :return: ``list`` of :class:`Zone` + """ + response = self.connection.request('/v1/domains') + + 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('/v1/domains/%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` + """ + response = self.connection.request('/v1/domains/%s' % zone_id) + 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` + """ + response = self.connection.request('/v1/domains/%s/records/%s' % + (zone_id, record_id)) + record = self._to_record(response.object, zone_id=zone_id) + return record + + def create_zone(self, domain, extra=None): + """ + Create a new zone. + + :param domain: Zone domain name (e.g. example.com) + :type domain: ``str`` + + :param extra: Extra attributes (driver specific). (optional) + :type extra: ``dict`` + + :rtype: :class:`Zone` + + For more info, please see: + http://developer.dnsimple.com/v1/domains/ + """ + r_json = {'name': domain} + if extra is not None: + r_json.update(extra) + r_data = json.dumps({'domain': r_json}) + response = self.connection.request('/v1/domains', method='POST', + 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, 'record_type': type, 'content': data} + if extra is not None: + r_json.update(extra) + r_data = json.dumps({'record': r_json}) + response = self.connection.request('/v1/domains/%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, 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 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, 'content': data} + if extra is not None: + r_json.update(extra) + r_data = json.dumps({'record': r_json}) + response = self.connection.request('/v1/domains/%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('/v1/domains/%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 + self.connection.request('/v1/domains/%s/records/%s' % (zone_id, + record.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): + domain = data.get('domain') + id = domain.get('id') + name = domain.get('name') + extra = {'registrant_id': domain.get('registrant_id'), + 'user_id': domain.get('user_id'), + 'unicode_name': domain.get('unicode_name'), + 'token': domain.get('token'), + 'state': domain.get('state'), + 'language': domain.get('language'), + 'lockable': domain.get('lockable'), + 'auto_renew': domain.get('auto_renew'), + 'whois_protected': domain.get('whois_protected'), + 'record_count': domain.get('record_count'), + 'service_count': domain.get('service_count'), + 'expires_on': domain.get('expires_on'), + 'created_at': domain.get('created_at'), + 'updated_at': domain.get('updated_at')} + + # All zones are primary by design + type = 'master' + + return Zone(id=id, domain=name, type=type, ttl=DEFAULT_ZONE_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('record') + id = record.get('id') + name = record.get('name') + type = record.get('record_type') + data = record.get('content') + extra = {'ttl': record.get('ttl'), + 'created_at': record.get('created_at'), + 'updated_at': record.get('updated_at'), + 'domain_id': record.get('domain_id'), + 'prio': record.get('prio')} + return Record(id, name, type, data, zone, self, extra=extra) diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py index aef0aa7bd7..497b0d61e6 100644 --- a/libcloud/dns/providers.py +++ b/libcloud/dns/providers.py @@ -37,6 +37,8 @@ ('libcloud.dns.drivers.softlayer', 'SoftLayerDNSDriver'), Provider.DIGITAL_OCEAN: ('libcloud.dns.drivers.digitalocean', 'DigitalOceanDNSDriver'), + Provider.DNSIMPLE: + ('libcloud.dns.drivers.dnsimple', 'DNSimpleDNSDriver'), # Deprecated Provider.RACKSPACE_US: ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'), diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py index ad2b34a613..463a3fb962 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' + DNSIMPLE = 'dnsimple' # Deprecated RACKSPACE_US = 'rackspace_us' @@ -51,15 +52,18 @@ class RecordType(object): """ A = 'A' AAAA = 'AAAA' + ALIAS = 'ALIAS' MX = 'MX' NS = 'NS' CNAME = 'CNAME' DNAME = 'DNAME' + HINFO = 'HINFO' TXT = 'TXT' PTR = 'PTR' SOA = 'SOA' SPF = 'SPF' SRV = 'SRV' + SSHFP = 'SSHFP' PTR = 'PTR' NAPTR = 'NAPTR' REDIRECT = 'REDIRECT' diff --git a/libcloud/test/dns/fixtures/dnsimple/create_domain.json b/libcloud/test/dns/fixtures/dnsimple/create_domain.json new file mode 100644 index 0000000000..ace9f32a04 --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/create_domain.json @@ -0,0 +1,20 @@ +{ + "domain": { + "id": 1, + "user_id": 21, + "registrant_id": 321, + "name": "example.com", + "unicode_name": "example.com", + "token": "domain-token", + "state": "hosted", + "language": null, + "lockable": false, + "auto_renew": true, + "whois_protected": false, + "record_count": 5, + "service_count": 1, + "expires_on": null, + "created_at": "2012-09-27T14:25:57.646Z", + "updated_at": "2014-12-15T20:27:04.552Z" + } +} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/create_domain_record.json b/libcloud/test/dns/fixtures/dnsimple/create_domain_record.json new file mode 100644 index 0000000000..4534a9d154 --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/create_domain_record.json @@ -0,0 +1,13 @@ +{ + "record": { + "content": "mail.example.com", + "created_at": "2013-01-29T14:25:38Z", + "domain_id": 28, + "id": 172, + "name": "", + "prio": 10, + "record_type": "MX", + "ttl": 3600, + "updated_at": "2013-01-29T14:25:38Z" + } +} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/get_domain.json b/libcloud/test/dns/fixtures/dnsimple/get_domain.json new file mode 100644 index 0000000000..ae2dda9cee --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/get_domain.json @@ -0,0 +1,20 @@ +{ + "domain": { + "id": 1, + "user_id": 21, + "registrant_id": 321, + "name": "example.com", + "unicode_name": "example.com", + "token": "domain-token", + "state": "registered", + "language": null, + "lockable": false, + "auto_renew": true, + "whois_protected": false, + "record_count": 5, + "service_count": 1, + "expires_on": "2015-09-27", + "created_at": "2012-09-27T14:25:57.646Z", + "updated_at": "2014-12-15T20:27:04.552Z" + } +} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/get_domain_record.json b/libcloud/test/dns/fixtures/dnsimple/get_domain_record.json new file mode 100644 index 0000000000..08d666549e --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/get_domain_record.json @@ -0,0 +1,13 @@ +{ + "record": { + "name": "www", + "ttl": 3600, + "created_at": "2010-07-01T08:01:18Z", + "updated_at": "2010-10-21T15:47:47Z", + "domain_id": 1, + "id": 123, + "content": "example.com", + "record_type": "CNAME", + "prio": null + } +} \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/list_domain_records.json b/libcloud/test/dns/fixtures/dnsimple/list_domain_records.json new file mode 100644 index 0000000000..71a6ff94fa --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/list_domain_records.json @@ -0,0 +1,42 @@ +[ + { + "record": { + "name": "", + "ttl": 3600, + "created_at": "2010-07-04T04:41:31Z", + "updated_at": "2010-10-21T15:47:47Z", + "domain_id": 1, + "id": 31, + "content": "1.2.3.4", + "record_type": "A", + "prio": null + } + }, + { + "record": { + "name": "www", + "ttl": 3600, + "created_at": "2010-07-01T08:01:18Z", + "updated_at": "2010-10-21T15:47:47Z", + "domain_id": 1, + "id": 2, + "content": "example.com", + "record_type": "CNAME", + "prio": null + } + }, + { + "record": { + "name": "", + "ttl": 3600, + "created_at": "2010-07-04T04:42:11Z", + "updated_at": "2010-10-21T15:47:47Z", + "pdns_identifier": "40", + "domain_id": 1, + "id": 32, + "content": "mail.example.com", + "record_type": "MX", + "prio": 10 + } + } +] \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/list_domains.json b/libcloud/test/dns/fixtures/dnsimple/list_domains.json new file mode 100644 index 0000000000..282cc30978 --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/list_domains.json @@ -0,0 +1,42 @@ +[ + { + "domain": { + "id": 1, + "user_id": 21, + "registrant_id": 321, + "name": "example.com", + "unicode_name": "example.com", + "token": "domain-token", + "state": "registered", + "language": null, + "lockable": false, + "auto_renew": true, + "whois_protected": false, + "record_count": 5, + "service_count": 1, + "expires_on": "2015-09-27", + "created_at": "2012-09-27T14:25:57.646Z", + "updated_at": "2014-12-15T20:27:04.552Z" + } + }, + { + "domain": { + "id": 2, + "user_id": 22, + "registrant_id": 322, + "name": "example.com", + "unicode_name": "example.it", + "token": "domain-token", + "state": "hosted", + "language": null, + "lockable": false, + "auto_renew": true, + "whois_protected": false, + "record_count": 5, + "service_count": 1, + "expires_on": null, + "created_at": "2012-09-27T14:25:57.646Z", + "updated_at": "2014-12-15T20:27:04.552Z" + } + } +] \ No newline at end of file diff --git a/libcloud/test/dns/fixtures/dnsimple/update_domain_record.json b/libcloud/test/dns/fixtures/dnsimple/update_domain_record.json new file mode 100644 index 0000000000..92036635c0 --- /dev/null +++ b/libcloud/test/dns/fixtures/dnsimple/update_domain_record.json @@ -0,0 +1,13 @@ +{ + "record": { + "name": "www", + "ttl": 4500, + "created_at": "2010-07-01T08:01:18Z", + "updated_at": "2010-10-21T15:47:47Z", + "domain_id": 1, + "id": 123, + "content": "updated.com", + "record_type": "CNAME", + "prio": null + } +} \ No newline at end of file diff --git a/libcloud/test/dns/test_dnsimple.py b/libcloud/test/dns/test_dnsimple.py new file mode 100644 index 0000000000..1f2f95c020 --- /dev/null +++ b/libcloud/test/dns/test_dnsimple.py @@ -0,0 +1,254 @@ +# 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.dnsimple import DNSimpleDNSDriver + +from libcloud.test import MockHttp +from libcloud.test.file_fixtures import DNSFileFixtures +from libcloud.test.secrets import DNS_PARAMS_DNSIMPLE + + +class DNSimpleDNSTests(unittest.TestCase): + def setUp(self): + DNSimpleDNSDriver.connectionCls.conn_classes = ( + None, DNSimpleDNSMockHttp) + DNSimpleDNSMockHttp.type = None + self.driver = DNSimpleDNSDriver(*DNS_PARAMS_DNSIMPLE) + + 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), 15) + 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.HINFO in record_types) + self.assertTrue(RecordType.MX in record_types) + self.assertTrue(RecordType.NAPTR in record_types) + self.assertTrue(RecordType.NS in record_types) + self.assertTrue('POOL' in record_types) + self.assertTrue(RecordType.SPF in record_types) + self.assertTrue(RecordType.SOA in record_types) + self.assertTrue(RecordType.SRV in record_types) + self.assertTrue(RecordType.SSHFP in record_types) + self.assertTrue(RecordType.TXT in record_types) + self.assertTrue(RecordType.URL in record_types) + + def test_list_zones_success(self): + 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, ['registrant_id', 'user_id', + 'unicode_name', 'token', 'state', + 'language', 'lockable', 'auto_renew', + 'whois_protected', 'record_count', + 'service_count', 'expires_on', + 'created_at', 'updated_at']) + + zone2 = zones[1] + self.assertEqual(zone2.id, '2') + self.assertEqual(zone2.type, 'master') + self.assertEqual(zone2.domain, 'example.com') + self.assertEqual(zone2.ttl, 3600) + self.assertHasKeys(zone2.extra, ['registrant_id', 'user_id', + 'unicode_name', 'token', 'state', + 'language', 'lockable', 'auto_renew', + 'whois_protected', 'record_count', + 'service_count', 'expires_on', + 'created_at', 'updated_at']) + + def test_list_records_success(self): + zone = self.driver.list_zones()[0] + records = self.driver.list_records(zone=zone) + self.assertEqual(len(records), 3) + + record1 = records[0] + self.assertEqual(record1.id, '31') + self.assertEqual(record1.name, '') + self.assertEqual(record1.type, RecordType.A) + self.assertEqual(record1.data, '1.2.3.4') + self.assertHasKeys(record1.extra, ['ttl', 'created_at', 'updated_at', + 'domain_id', 'prio']) + + record2 = records[1] + self.assertEqual(record2.id, '2') + self.assertEqual(record2.name, 'www') + self.assertEqual(record2.type, RecordType.CNAME) + self.assertEqual(record2.data, 'example.com') + self.assertHasKeys(record2.extra, ['ttl', 'created_at', 'updated_at', + 'domain_id', 'prio']) + + record3 = records[2] + self.assertEqual(record3.id, '32') + self.assertEqual(record3.name, '') + self.assertEqual(record3.type, RecordType.MX) + self.assertEqual(record3.data, 'mail.example.com') + self.assertHasKeys(record3.extra, ['ttl', 'created_at', 'updated_at', + 'domain_id', 'prio']) + + def test_get_zone_success(self): + 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, ['registrant_id', 'user_id', + 'unicode_name', 'token', 'state', + 'language', 'lockable', 'auto_renew', + 'whois_protected', 'record_count', + 'service_count', 'expires_on', + 'created_at', 'updated_at']) + + def test_get_record_success(self): + record = self.driver.get_record(zone_id='1', + record_id='123') + self.assertEqual(record.id, '123') + self.assertEqual(record.name, 'www') + self.assertEqual(record.type, RecordType.CNAME) + self.assertEqual(record.data, 'example.com') + self.assertHasKeys(record.extra, ['ttl', 'created_at', 'updated_at', + 'domain_id', 'prio']) + + def test_create_zone_success(self): + DNSimpleDNSMockHttp.type = 'CREATE' + zone = self.driver.create_zone(domain='example.com') + self.assertEqual(zone.id, '1') + self.assertEqual(zone.domain, 'example.com') + self.assertEqual(zone.ttl, 3600) + self.assertEqual(zone.type, 'master') + self.assertHasKeys(zone.extra, ['registrant_id', 'user_id', + 'unicode_name', 'token', 'state', + 'language', 'lockable', 'auto_renew', + 'whois_protected', 'record_count', + 'service_count', 'expires_on', + 'created_at', 'updated_at']) + + def test_create_record_success(self): + zone = self.driver.list_zones()[0] + DNSimpleDNSMockHttp.type = 'CREATE' + record = self.driver.create_record(name='domain4', zone=zone, + type=RecordType.MX, + data='mail.example.com') + self.assertEqual(record.id, '172') + self.assertEqual(record.name, '') + self.assertEqual(record.type, RecordType.MX) + self.assertEqual(record.data, 'mail.example.com') + self.assertHasKeys(record.extra, ['ttl', 'created_at', 'updated_at', + 'domain_id', 'prio']) + + def test_update_record_success(self): + record = self.driver.get_record(zone_id='1', + record_id='123') + DNSimpleDNSMockHttp.type = 'UPDATE' + extra = {'ttl': 4500} + record1 = self.driver.update_record(record=record, name='www', + data='updated.com', + extra=extra) + self.assertEqual(record.data, 'example.com') + self.assertEqual(record.extra.get('ttl'), 3600) + self.assertEqual(record1.data, 'updated.com') + self.assertEqual(record1.extra.get('ttl'), 4500) + + def test_delete_zone_success(self): + zone = self.driver.list_zones()[0] + DNSimpleDNSMockHttp.type = 'DELETE_200' + status = self.driver.delete_zone(zone=zone) + self.assertTrue(status) + + def test_delete_zone_success_future_implementation(self): + zone = self.driver.list_zones()[0] + DNSimpleDNSMockHttp.type = 'DELETE_204' + status = self.driver.delete_zone(zone=zone) + self.assertTrue(status) + + 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] + DNSimpleDNSMockHttp.type = 'DELETE_200' + status = self.driver.delete_record(record=record) + self.assertTrue(status) + + def test_delete_record_success_future_implementation(self): + zone = self.driver.list_zones()[0] + records = self.driver.list_records(zone=zone) + self.assertEqual(len(records), 3) + record = records[1] + DNSimpleDNSMockHttp.type = 'DELETE_204' + status = self.driver.delete_record(record=record) + self.assertTrue(status) + + +class DNSimpleDNSMockHttp(MockHttp): + fixtures = DNSFileFixtures('dnsimple') + + def _v1_domains(self, method, url, body, headers): + body = self.fixtures.load('list_domains.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_CREATE(self, method, url, body, headers): + body = self.fixtures.load('create_domain.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1(self, method, url, body, headers): + body = self.fixtures.load('get_domain.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_records(self, method, url, body, headers): + body = self.fixtures.load('list_domain_records.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_records_123(self, method, url, body, headers): + body = self.fixtures.load('get_domain_record.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_records_CREATE(self, method, url, body, headers): + body = self.fixtures.load('create_domain_record.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_records_123_UPDATE(self, method, url, body, headers): + body = self.fixtures.load('update_domain_record.json') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_DELETE_200(self, method, url, body, headers): + return (httplib.OK, '', {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_DELETE_204(self, method, url, body, headers): + return (httplib.OK, '', {}, httplib.responses[httplib.NO_CONTENT]) + + def _v1_domains_1_records_2_DELETE_200(self, method, url, body, headers): + return (httplib.OK, '', {}, httplib.responses[httplib.OK]) + + def _v1_domains_1_records_2_DELETE_204(self, method, url, body, headers): + return (httplib.OK, '', {}, httplib.responses[httplib.NO_CONTENT]) + + +if __name__ == '__main__': + sys.exit(unittest.main()) diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist index 3ebcac3cfe..c7b52f58cd 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_DNSIMPLE = ('user', 'key')