diff --git a/CHANGES.rst b/CHANGES.rst index d6e80b0ac7..0f3c0f7cd8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -134,10 +134,14 @@ DNS (GITHUB-621) [Alejandro Pereira] -- Introduced GoDaddy DNS Driver with examples and documentation +- Introduce GoDaddy DNS Driver with examples and documentation. (LIBCLOUD-772, GITHUB-640, LIBCLOUD-778) [Anthony Shaw] +- Add new driver for CloudFlare DNS (https://www.cloudflare.com/dns/). + (GITHUB-637) + [Tomaz Muraus] + Changes with Apache Libcloud 0.19.0 ----------------------------------- diff --git a/libcloud/dns/base.py b/libcloud/dns/base.py index f557c85b87..6f7cb14f20 100644 --- a/libcloud/dns/base.py +++ b/libcloud/dns/base.py @@ -143,9 +143,10 @@ def _get_numeric_id(self): return record_id def __repr__(self): + zone = self.zone.domain if self.zone.domain else self.zone.id return ('' % - (self.zone.id, self.name, self.type, self.data, + (zone, self.name, self.type, self.data, self.driver.name, self.ttl)) diff --git a/libcloud/dns/drivers/cloudflare.py b/libcloud/dns/drivers/cloudflare.py new file mode 100644 index 0000000000..5812d6085e --- /dev/null +++ b/libcloud/dns/drivers/cloudflare.py @@ -0,0 +1,429 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__all__ = [ + 'CloudFlareDNSDriver' +] + +import copy + +from libcloud.common.base import JsonResponse, ConnectionUserAndKey +from libcloud.common.types import InvalidCredsError, LibcloudError +from libcloud.utils.py3 import httplib +from libcloud.dns.base import DNSDriver, Zone, Record +from libcloud.dns.types import Provider, RecordType +from libcloud.dns.types import ZoneDoesNotExistError, RecordDoesNotExistError + +API_URL = 'https://www.cloudflare.com/api_json.html' +API_HOST = 'www.cloudflare.com' +API_PATH = '/api_json.html' + +ZONE_EXTRA_ATTRIBUTES = [ + 'display_name', + 'zone_status', + 'zone_type', + 'host_id', + 'host_pubname', + 'host_website', + 'fqdns', + 'vtxt', + 'step', + 'zone_status_class', + 'zone_status_desc', + 'orig_registrar', + 'orig_dnshost', + 'orig_ns_names' +] + +RECORD_EXTRA_ATTRIBUTES = [ + 'rec_tag', + 'display_name', + 'pro', + 'display_content', + 'ttl_ceil', + 'ssl_id', + 'ssl_status', + 'ssl_expires_on', + 'auto_ttl', + 'service_mode' +] + + +class CloudFlareDNSResponse(JsonResponse): + def success(self): + return self.status in [httplib.OK, httplib.CREATED, httplib.ACCEPTED] + + def parse_body(self): + body = super(CloudFlareDNSResponse, self).parse_body() + + result = body.get('result', None) + error_code = body.get('err_code', None) + msg = body.get('msg', None) + is_error_result = result == 'error' + + context = self.connection.context or {} + context_record_id = context.get('record_id', None) + context_zone_domain = context.get('zone_domain', None) + + if (is_error_result and 'invalid record id' in msg.lower() and + context_record_id): + raise RecordDoesNotExistError(value=msg, + driver=self.connection.driver, + record_id=context_record_id) + elif (is_error_result and 'invalid zone' in msg.lower() and + context_zone_domain): + raise ZoneDoesNotExistError(value=msg, + driver=self.connection.driver, + zone_id=context_zone_domain) + + if error_code == 'E_UNAUTH': + raise InvalidCredsError(msg) + elif result == 'error' or error_code is not None: + msg = 'Request failed: %s' % (self.body) + raise LibcloudError(value=msg, driver=self.connection.driver) + + return body + + +class CloudFlareDNSConnection(ConnectionUserAndKey): + host = API_HOST + secure = True + responseCls = CloudFlareDNSResponse + + def request(self, action, params=None, data=None, headers=None, + method='GET'): + params = params or {} + data = data or {} + + base_params = { + 'email': self.user_id, + 'tkn': self.key, + 'a': action + } + params = copy.deepcopy(params) + params.update(base_params) + + return super(CloudFlareDNSConnection, self).request(action=API_PATH, + params=params, + data=None, + method=method, + headers=headers) + + +class CloudFlareDNSDriver(DNSDriver): + type = Provider.CLOUDFLARE + name = 'CloudFlare DNS' + website = 'https://www.cloudflare.com' + connectionCls = CloudFlareDNSConnection + + RECORD_TYPE_MAP = { + RecordType.A: 'A', + RecordType.AAAA: 'AAAA', + RecordType.CNAME: 'CNAME', + RecordType.MX: 'MX', + RecordType.TXT: 'TXT', + RecordType.SPF: 'SPF', + RecordType.NS: 'NS', + RecordType.SRV: 'SRV', + RecordType.URL: 'LOC' + } + + def iterate_zones(self): + # TODO: Support pagination + result = self.connection.request(action='zone_load_multi').object + zones = self._to_zones(data=result['response']['zones']['objs']) + + return zones + + def iterate_records(self, zone): + # TODO: Support pagination + params = {'z': zone.domain} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='rec_load_all', params=params) + data = resp.object['response']['recs']['objs'] + records = self._to_records(zone=zone, data=data) + return records + + def get_zone(self, zone_id): + # TODO: This is not efficient + zones = self.list_zones() + + try: + zone = [z for z in zones if z.id == zone_id][0] + except IndexError: + raise ZoneDoesNotExistError(value='', driver=self, zone_id=zone_id) + + return zone + + def create_record(self, name, zone, type, data, extra=None): + extra = extra or {} + params = {'name': name, 'z': zone.domain, 'type': type, + 'content': data} + + params['ttl'] = extra.get('ttl', 120) + + if 'priority' in extra: + # For MX and SRV records + params['prio'] = extra['priority'] + + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='rec_new', params=params) + item = resp.object['response']['rec']['obj'] + record = self._to_record(zone=zone, item=item) + return record + + def update_record(self, record, name=None, type=None, data=None, + extra=None): + extra = extra or {} + params = {'z': record.zone.domain, 'id': record.id} + + params['name'] = name or record.name + params['type'] = type or record.type + params['content'] = data or record.data + params['ttl'] = extra.get('ttl', None) or record.extra['ttl'] + + self.connection.set_context({'zone_domain': record.zone.domain}) + self.connection.set_context({'record_id': record.id}) + resp = self.connection.request(action='rec_edit', params=params) + item = resp.object['response']['rec']['obj'] + record = self._to_record(zone=record.zone, item=item) + return record + + def delete_record(self, record): + params = {'z': record.zone.domain, 'id': record.id} + self.connection.set_context({'zone_domain': record.zone.domain}) + self.connection.set_context({'record_id': record.id}) + resp = self.connection.request(action='rec_delete', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_get_zone_stats(self, zone, interval=30): + params = {'z': zone.domain, 'interval': interval} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='stats', params=params) + result = resp.object['response']['result']['objs'][0] + return result + + def ex_zone_check(self, zones): + zone_domains = [zone.domain for zone in zones] + zone_domains = ','.join(zone_domains) + params = {'zones': zone_domains} + resp = self.connection.request(action='zone_check', params=params) + result = resp.object['response']['zones'] + return result + + def ex_get_ip_threat_score(self, ip): + """ + Retrieve current threat score for a given IP. Note that scores are on + a logarithmic scale, where a higher score indicates a higher threat. + """ + params = {'ip': ip} + resp = self.connection.request(action='ip_lkup', params=params) + result = resp.object['response'] + return result + + def ex_get_zone_settings(self, zone): + """ + Retrieve all current settings for a given zone. + """ + params = {'z': zone.domain} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='zone_settings', params=params) + result = resp.object['response']['result']['objs'][0] + return result + + def ex_set_zone_security_level(self, zone, level): + """ + Set the zone Basic Security Level to I'M UNDER ATTACK! / HIGH / + MEDIUM / LOW / ESSENTIALLY OFF. + + :param level: Security level. Valid values are: help, high, med, low, + eoff. + :type level: ``str`` + """ + params = {'z': zone.domain, 'v': level} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='sec_lvl', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_set_zone_cache_level(self, zone, level): + """ + Set the zone caching level. + + :param level: Caching level. Valid values are: agg (aggresive), basic. + :type level: ``str`` + """ + params = {'z': zone.domain, 'v': level} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='cache_lvl', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_enable_development_mode(self, zone): + """ + Enable development mode. When Development Mode is on the cache is + bypassed. Development mode remains on for 3 hours or until when it is + toggled back off. + """ + params = {'z': zone.domain, 'v': 1} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='devmode', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_disable_development_mode(self, zone): + """ + Disable development mode. + """ + params = {'z': zone.domain, 'v': 0} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='devmode', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_purge_cached_files(self, zone): + """ + Purge CloudFlare of any cached files. + """ + params = {'z': zone.domain, 'v': 1} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='fpurge_ts', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_purge_cached_file(self, zone, url): + """ + Purgle single file from CloudFlare's cache. + + :param url: URL to the file to purge from cache. + :type url: ``str`` + """ + params = {'z': zone.domain, 'url': url} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='zone_file_purge', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_whitelist_ip(self, zone, ip): + """ + Whitelist the provided IP. + """ + params = {'z': zone.domain, 'key': ip} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='wl', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_blacklist_ip(self, zone, ip): + """ + Blacklist the provided IP. + """ + params = {'z': zone.domain, 'key': ip} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='ban', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_unlist_ip(self, zone, ip): + """ + Remove provided ip from the whitelist and blacklist. + """ + params = {'z': zone.domain, 'key': ip} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='nul', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_enable_ipv6_support(self, zone): + """ + Enable IPv6 support for the provided zone. + """ + params = {'z': zone.domain, 'v': 3} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='ipv46', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def ex_disable_ipv6_support(self, zone): + """ + Disable IPv6 support for the provided zone. + """ + params = {'z': zone.domain, 'v': 0} + self.connection.set_context({'zone_domain': zone.domain}) + resp = self.connection.request(action='ipv46', params=params) + result = resp.object + return result.get('result', None) == 'success' + + def _to_zones(self, data): + zones = [] + + for item in data: + zone = self._to_zone(item=item) + zones.append(zone) + + return zones + + def _to_zone(self, item): + type = 'master' + + extra = {} + extra['props'] = item.get('props', {}) + extra['confirm_code'] = item.get('confirm_code', {}) + extra['allow'] = item.get('allow', {}) + for attribute in ZONE_EXTRA_ATTRIBUTES: + value = item.get(attribute, None) + extra[attribute] = value + + zone = Zone(id=str(item['zone_id']), domain=item['zone_name'], + type=type, ttl=None, driver=self, extra=extra) + return zone + + def _to_records(self, zone, data): + records = [] + + for item in data: + record = self._to_record(zone=zone, item=item) + records.append(record) + + return records + + def _to_record(self, zone, item): + name = self._get_record_name(item=item) + type = item['type'] + data = item['content'] + + if item.get('ttl', None): + ttl = int(item['ttl']) + else: + ttl = None + + extra = {} + extra['ttl'] = ttl + extra['props'] = item.get('props', {}) + for attribute in RECORD_EXTRA_ATTRIBUTES: + value = item.get(attribute, None) + extra[attribute] = value + + record = Record(id=str(item['rec_id']), name=name, type=type, + data=data, zone=zone, driver=self, ttl=ttl, + extra=extra) + return record + + def _get_record_name(self, item): + name = item['name'].replace('.' + item['zone_name'], '') or None + if name: + name = name.replace(item['zone_name'], '') or None + return name diff --git a/libcloud/dns/drivers/dummy.py b/libcloud/dns/drivers/dummy.py index 04da354a61..5a1f4da795 100644 --- a/libcloud/dns/drivers/dummy.py +++ b/libcloud/dns/drivers/dummy.py @@ -76,7 +76,7 @@ def list_records(self, zone): >>> record = driver.create_record(name='libcloud', zone=zone, ... type=RecordType.A, data='127.0.0.1') >>> list(zone.list_records()) #doctest: +ELLIPSIS - [] + [] """ return self._zones[zone.id]['records'].values() @@ -152,7 +152,7 @@ def create_record(self, name, zone, type, data, extra=None): >>> record = driver.create_record(name='libcloud', zone=zone, ... type=RecordType.A, data='127.0.0.1') >>> record #doctest: +ELLIPSIS - + >>> record = driver.create_record(name='libcloud', zone=zone, ... type=RecordType.A, data='127.0.0.1') ... #doctest: +IGNORE_EXCEPTION_DETAIL diff --git a/libcloud/dns/providers.py b/libcloud/dns/providers.py index 8b22a11ea5..e8e1619c75 100644 --- a/libcloud/dns/providers.py +++ b/libcloud/dns/providers.py @@ -51,15 +51,18 @@ ('libcloud.dns.drivers.zonomi', 'ZonomiDNSDriver'), Provider.DURABLEDNS: ('libcloud.dns.drivers.durabledns', 'DurableDNSDriver'), + Provider.AURORADNS: + ('libcloud.dns.drivers.auroradns', 'AuroraDNSDriver'), + Provider.GODADDY: + ('libcloud.dns.drivers.godaddy', 'GoDaddyDNSDriver'), + Provider.CLOUDFLARE: + ('libcloud.dns.drivers.cloudflare', 'CloudFlareDNSDriver'), + # Deprecated Provider.RACKSPACE_US: ('libcloud.dns.drivers.rackspace', 'RackspaceUSDNSDriver'), Provider.RACKSPACE_UK: - ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver'), - Provider.AURORADNS: - ('libcloud.dns.drivers.auroradns', 'AuroraDNSDriver'), - Provider.GODADDY: - ('libcloud.dns.drivers.godaddy', 'GoDaddyDNSDriver') + ('libcloud.dns.drivers.rackspace', 'RackspaceUKDNSDriver') } diff --git a/libcloud/dns/types.py b/libcloud/dns/types.py index 9f0425d600..6fe1f05d59 100644 --- a/libcloud/dns/types.py +++ b/libcloud/dns/types.py @@ -47,6 +47,7 @@ class Provider(object): ZONOMI = 'zonomi' DURABLEDNS = 'durabledns' GODADDY = 'godaddy' + CLOUDFLARE = 'cloudflare' # Deprecated RACKSPACE_US = 'rackspace_us' diff --git a/libcloud/test/dns/fixtures/cloudflare/ban.json b/libcloud/test/dns/fixtures/cloudflare/ban.json new file mode 100644 index 0000000000..1dbf1bf0dd --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/ban.json @@ -0,0 +1,10 @@ +{ + "response": { + "result": { + "ip": "127.0.0.1", + "action": "BAN" + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/cache_lvl.json b/libcloud/test/dns/fixtures/cloudflare/cache_lvl.json new file mode 100644 index 0000000000..15abe82527 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/cache_lvl.json @@ -0,0 +1,4 @@ +{ + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/devmode.json b/libcloud/test/dns/fixtures/cloudflare/devmode.json new file mode 100644 index 0000000000..15abe82527 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/devmode.json @@ -0,0 +1,4 @@ +{ + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/fpurge_ts.json b/libcloud/test/dns/fixtures/cloudflare/fpurge_ts.json new file mode 100644 index 0000000000..e88b55ce89 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/fpurge_ts.json @@ -0,0 +1,10 @@ +{ + "response": { + "fpurge_ts": 1449381662 + }, + "result": "success", + "msg": null, + "attributes": { + "cooldown": 20 + } +} diff --git a/libcloud/test/dns/fixtures/cloudflare/ip_lkup.json b/libcloud/test/dns/fixtures/cloudflare/ip_lkup.json new file mode 100644 index 0000000000..d0118a1cda --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/ip_lkup.json @@ -0,0 +1,7 @@ +{ + "response": { + "127.0.0.1": false + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/ipv46.json b/libcloud/test/dns/fixtures/cloudflare/ipv46.json new file mode 100644 index 0000000000..15abe82527 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/ipv46.json @@ -0,0 +1,4 @@ +{ + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/nul.json b/libcloud/test/dns/fixtures/cloudflare/nul.json new file mode 100644 index 0000000000..6762b7ce9f --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/nul.json @@ -0,0 +1,10 @@ +{ + "response": { + "result": { + "ip": "127.0.0.1", + "action": "NUL" + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/rec_delete.json b/libcloud/test/dns/fixtures/cloudflare/rec_delete.json new file mode 100644 index 0000000000..0ffee1caad --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/rec_delete.json @@ -0,0 +1,12 @@ +{ + "request": { + "act": "rec_delete", + "tkn": "maybeno", + "a": "rec_delete", + "z": "example.com", + "id": "412563484", + "email": "example@example.com" + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/rec_edit.json b/libcloud/test/dns/fixtures/cloudflare/rec_edit.json new file mode 100644 index 0000000000..68128e84f2 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/rec_edit.json @@ -0,0 +1,48 @@ +{ + "request": { + "act": "rec_edit", + "a": "rec_edit", + "name": "test6", + "tkn": "maybeno", + "id": "412564825", + "content": "127.0.0.4", + "ttl": "120", + "z": "example.com", + "type": "A", + "email": "example@example.com" + }, + "response": { + "rec": { + "obj": { + "rec_id": "412564825", + "rec_hash": "0cb296652af55023e5bf5ee6c9ad9974", + "zone_name": "example.com", + "name": "test6.example.com", + "display_name": "test6", + "type": "A", + "prio": null, + "content": "127.0.0.4", + "display_content": "127.0.0.4", + "ttl": "120", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 0, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + } + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/rec_load_all.json b/libcloud/test/dns/fixtures/cloudflare/rec_load_all.json new file mode 100644 index 0000000000..dde804e267 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/rec_load_all.json @@ -0,0 +1,525 @@ +{ + "request": { + "act": "rec_load_all", + "tkn": "maybeno", + "a": "rec_load_all", + "z": "example.com", + "email": "example@example.com" + }, + "response": { + "recs": { + "has_more": false, + "count": 18, + "objs": [{ + "rec_id": "364797364", + "rec_hash": "479a684c73e0e75c433675e86957660a", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "A", + "prio": null, + "content": "192.30.252.153", + "display_content": "192.30.252.153", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "1", + "props": { + "proxiable": 1, + "cloud_on": 1, + "cf_open": 0, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797367", + "rec_hash": "acaee7d8c6e256f7bcc5dab4410489cf", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "A", + "prio": null, + "content": "192.30.252.154", + "display_content": "192.30.252.154", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "1", + "props": { + "proxiable": 1, + "cloud_on": 1, + "cf_open": 0, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "403456915", + "rec_hash": "77cabc2ba4a75814eeda78daabce0859", + "zone_name": "example.com", + "name": "test2.example.com", + "display_name": "test2", + "type": "A", + "prio": null, + "content": "127.0.0.2", + "display_content": "127.0.0.2", + "ttl": "120", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 0, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "403456984", + "rec_hash": "edd95959936d20583e38352831d1978b", + "zone_name": "example.com", + "name": "test3.example.com", + "display_name": "test3", + "type": "A", + "prio": null, + "content": "127.0.0.1", + "display_content": "127.0.0.1", + "ttl": "120", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 0, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364982413", + "rec_hash": "fa43ee2e176f9be7b8e464167bdff440", + "zone_name": "example.com", + "name": "yesyes.example.com", + "display_name": "yesyes", + "type": "CNAME", + "prio": null, + "content": "verify.bing.com", + "display_content": "verify.bing.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 1, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364982461", + "rec_hash": "4dfb7dd8d8a895174600fe9fb0f48380", + "zone_name": "example.com", + "name": "google.example.com", + "display_name": "google", + "type": "CNAME", + "prio": null, + "content": "google.com", + "display_content": "google.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 1, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797370", + "rec_hash": "537311442890afedc06e5febf7171023", + "zone_name": "example.com", + "name": "www.example.com", + "display_name": "www", + "type": "CNAME", + "prio": null, + "content": "kami.github.io", + "display_content": "kami.github.io", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "1", + "props": { + "proxiable": 1, + "cloud_on": 1, + "cf_open": 0, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797388", + "rec_hash": "a55e09423fa23c33906131aa678e86ba", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "20", + "content": "alt1.aspmx.l.google.com", + "display_content": "alt1.aspmx.l.google.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797382", + "rec_hash": "c1bc38b678f1c07af3d506a0b4f57da4", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "20", + "content": "alt2.aspmx.l.google.com", + "display_content": "alt2.aspmx.l.google.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797391", + "rec_hash": "90b3f10c8cbe2164b87302de99cf7760", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "30", + "content": "aspmx2.googlemail.com", + "display_content": "aspmx2.googlemail.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797385", + "rec_hash": "99cad8b4ae97d559716c838276d1bf43", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "30", + "content": "aspmx3.googlemail.com", + "display_content": "aspmx3.googlemail.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797379", + "rec_hash": "79e354b6d036b5eec0fe75974e0ffa98", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "30", + "content": "aspmx4.googlemail.com", + "display_content": "aspmx4.googlemail.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797376", + "rec_hash": "ad08cc1813d4061763aecfe0d984bea3", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "30", + "content": "aspmx5.googlemail.com", + "display_content": "aspmx5.googlemail.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364797373", + "rec_hash": "b38ed0e1e250e12f2c1b3a4a163e5526", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "MX", + "prio": "10", + "content": "aspmx.l.google.com", + "display_content": "aspmx.l.google.com", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + }, + "mx": { + "auto": false + } + }, { + "rec_id": "364982359", + "rec_hash": "2f4037b298fb88b77a901725824287c0", + "zone_name": "example.com", + "name": "google._domainkey.example.com", + "display_name": "google._domainkey", + "type": "TXT", + "prio": null, + "content": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNCHa8VeffMv+X\/fRkPgHC9MN2Eh5vQqMkWy4e\/YnFbWgF1JilL1Yn9nN54A5WV7lZpCTIvuOC2CrQrIcaBpfr+8SjYsjGO91dz8cwgqZkl7mAjKs7nz8U0PsstuI9i4V3LsHC4NVGOirAgnKA4HXVhxGRuyE94+tuNJ6XDLJoNQIDAQAB", + "display_content": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNCHa8VeffMv+X\/fRkPgHC9MN2Eh5vQqMkWy4e\/YnFbWgF1JilL1Yn9nN54A5WV7lZpCTIvuOC2CrQrIcaBpfr+8SjYsjGO91dz8cwgqZkl7mAjKs7nz8U0PsstuI9i4V3LsHC4NVGOirAgnKA4HXVhxGRuyE94+tuNJ6XDLJoNQIDAQAB", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797400", + "rec_hash": "75788bfdfe314c339e444bfc1c6cfd2e", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "TXT", + "prio": null, + "content": "google-site-verification=Rgex8ShgIRWUlb9j0Ivw5uHllb0p9skEdJqkSMqvX_o", + "display_content": "google-site-verification=Rgex8ShgIRWUlb9j0Ivw5uHllb0p9skEdJqkSMqvX_o", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797394", + "rec_hash": "c588426dbf098b57cfcee86815b482d0", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "TXT", + "prio": null, + "content": "keybase-site-verification=L_shC4yrIjo-yM4qBDKmro9kOH8devqvHrlQtgFa2Us", + "display_content": "keybase-site-verification=L_shC4yrIjo-yM4qBDKmro9kOH8devqvHrlQtgFa2Us", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }, { + "rec_id": "364797397", + "rec_hash": "e20556627e805701c5d0a383458201ae", + "zone_name": "example.com", + "name": "example.com", + "display_name": "example.com", + "type": "TXT", + "prio": null, + "content": "v=spf1 include:_spf.google.com ~all", + "display_content": "v=spf1 include:_spf.google.com ~all", + "ttl": "1", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 1, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + }] + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/rec_new.json b/libcloud/test/dns/fixtures/cloudflare/rec_new.json new file mode 100644 index 0000000000..3ea4dfc203 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/rec_new.json @@ -0,0 +1,47 @@ +{ + "request": { + "act": "rec_new", + "a": "rec_new", + "name": "test5", + "tkn": "maybeno", + "content": "127.0.0.3", + "ttl": "120", + "z": "example.com", + "type": "A", + "email": "example@example.com" + }, + "response": { + "rec": { + "obj": { + "rec_id": "412561327", + "rec_hash": "ed23e38bca17007e026d2da517adf10a", + "zone_name": "example.com", + "name": "test5.example.com", + "display_name": "test5", + "type": "A", + "prio": null, + "content": "127.0.0.3", + "display_content": "127.0.0.3", + "ttl": "120", + "ttl_ceil": 86400, + "ssl_id": "2245194", + "ssl_status": "V", + "ssl_expires_on": null, + "auto_ttl": 0, + "service_mode": "0", + "props": { + "proxiable": 0, + "cloud_on": 0, + "cf_open": 1, + "vanity_lock": 0, + "ssl": 1, + "expired_ssl": 0, + "expiring_ssl": 0, + "pending_ssl": 0 + } + } + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/sec_lvl.json b/libcloud/test/dns/fixtures/cloudflare/sec_lvl.json new file mode 100644 index 0000000000..15abe82527 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/sec_lvl.json @@ -0,0 +1,4 @@ +{ + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/stats.json b/libcloud/test/dns/fixtures/cloudflare/stats.json new file mode 100644 index 0000000000..b45884bdd9 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/stats.json @@ -0,0 +1,47 @@ +{ + "response": { + "result": { + "timeZero": 1448773672000, + "timeEnd": 1449378472000, + "count": 1, + "has_more": false, + "objs": [{ + "cachedServerTime": 1449378474000, + "cachedExpryTime": 1449379074000, + "trafficBreakdown": { + "pageviews": { + "regular": 1804, + "threat": 80, + "crawler": 438 + }, + "uniques": { + "regular": 1914, + "threat": 80, + "crawler": 438 + } + }, + "bandwidthServed": { + "cloudflare": 422053.22949219, + "user": 408767.30664062 + }, + "requestsServed": { + "cloudflare": 29333, + "user": 13367 + }, + "pro_zone": false, + "pageLoadTime": null, + "currentServerTime": 1449378474000, + "interval": 30, + "zoneCDate": 1373665252000, + "userSecuritySetting": "Medium", + "dev_mode": 0, + "ipv46": 3, + "ob": 1, + "cache_lvl": "agg", + "outboundLinks": "disabled" + }] + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/wl.json b/libcloud/test/dns/fixtures/cloudflare/wl.json new file mode 100644 index 0000000000..777b39218d --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/wl.json @@ -0,0 +1,10 @@ +{ + "response": { + "result": { + "ip": "127.0.0.1", + "action": "WL" + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/zone_check.json b/libcloud/test/dns/fixtures/cloudflare/zone_check.json new file mode 100644 index 0000000000..a6fdf34d55 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/zone_check.json @@ -0,0 +1,9 @@ +{ + "response": { + "zones": { + "example.com": 4025956 + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/zone_file_purge.json b/libcloud/test/dns/fixtures/cloudflare/zone_file_purge.json new file mode 100644 index 0000000000..086925b72d --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/zone_file_purge.json @@ -0,0 +1,15 @@ +{ + "request": { + "act": "zone_file_purge", + "url": "https:\/\/www.example.com\/aaaaa.html", + "tkn": "maybeno", + "z": "example.com", + "a": "zone_file_purge", + "email": "example@example.com" + }, + "response": { + "url": "https:\/\/www.example.com\/test.html" + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/zone_load_multi.json b/libcloud/test/dns/fixtures/cloudflare/zone_load_multi.json new file mode 100644 index 0000000000..8eb2ee5475 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/zone_load_multi.json @@ -0,0 +1,62 @@ +{ + "request": { + "act": "zone_load_multi", + "tkn": "maybeno", + "a": "zone_load_multi", + "email": "example@example.com" + }, + "response": { + "zones": { + "has_more": false, + "count": 1, + "objs": [{ + "zone_id": "1234", + "user_id": "54321", + "zone_name": "example.com", + "display_name": "example.com", + "zone_status": "V", + "zone_mode": "1", + "host_id": null, + "zone_type": "F", + "host_pubname": null, + "host_website": null, + "vtxt": null, + "fqdns": ["kara.ns.cloudflare.com", "noah.ns.cloudflare.com"], + "step": "4", + "zone_status_class": "status-ac_api_json_html_zone_load_multitive", + "zone_status_desc": "CloudFlare powered, this website will be accelerated and protected (info<\/a>)", + "ns_vanity_map": [], + "orig_registrar": "godaddy", + "orig_dnshost": null, + "orig_ns_names": "{dns1.stabletransit.com,dns2.stabletransit.com}", + "props": { + "dns_cname": 0, + "dns_partner": 0, + "dns_anon_partner": 0, + "plan": "FREE_ZONE", + "pro": 0, + "expired_pro": 0, + "pro_sub": 0, + "plan_sub": 0, + "ssl": 1, + "expired_ssl": 0, + "expired_rs_pro": 0, + "reseller_pro": 0, + "reseller_plans": [], + "force_interal": 0, + "ssl_needed": 0, + "alexa_rank": 1307220, + "has_vanity": 0 + }, + "confirm_code": { + "zone_delete": "maybeno", + "zone_deactivate": "maybeno", + "zone_dev_mode1": "maybeno" + }, + "allow": ["analytics", "threat_control", "zone_delete", "cf_apps", "dns_editor", "cf_settings", "page_rules", "zone_deactivate", "zone_dev_mode1"] + }] + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/fixtures/cloudflare/zone_settings.json b/libcloud/test/dns/fixtures/cloudflare/zone_settings.json new file mode 100644 index 0000000000..a6a4834a84 --- /dev/null +++ b/libcloud/test/dns/fixtures/cloudflare/zone_settings.json @@ -0,0 +1,44 @@ +{ + "request": { + "act": "zone_settings", + "tkn": "maybeno", + "a": "zone_settings", + "z": "example.com", + "email": "example@example.com" + }, + "response": { + "result": { + "objs": [{ + "userSecuritySetting": "Medium", + "dev_mode": 0, + "ipv46": 3, + "ob": 1, + "cache_lvl": "agg", + "outboundLinks": "disabled", + "bic": "1", + "chl_ttl": "3600", + "comodo_vc": "a.b", + "dnssec": "", + "exp_ttl": "86400", + "fpurge_ts": "1448144070", + "minify": "7", + "preload": "0", + "sec_lvl": "med", + "secureheader_settings": "{\"*\":{\"strict_transport_security\":{\"enable\":1,\"max_age\":2592000},\"content_type_options\":\"nosniff\"}}", + "ssl": "1", + "outlink": "0", + "geoloc": "1", + "host_spf": "0", + "waf_profile": "off", + "email_filter": "1", + "sse": "1", + "cache_ttl": "14400", + "lazy": "0", + "async": "0", + "ddos": "Off" + }] + } + }, + "result": "success", + "msg": null +} diff --git a/libcloud/test/dns/test_cloudflare.py b/libcloud/test/dns/test_cloudflare.py new file mode 100644 index 0000000000..b9f922788d --- /dev/null +++ b/libcloud/test/dns/test_cloudflare.py @@ -0,0 +1,355 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +from libcloud.test import unittest + +from libcloud.dns.drivers.cloudflare import CloudFlareDNSDriver +from libcloud.dns.drivers.cloudflare import ZONE_EXTRA_ATTRIBUTES +from libcloud.dns.drivers.cloudflare import RECORD_EXTRA_ATTRIBUTES +from libcloud.dns.types import RecordType, ZoneDoesNotExistError +from libcloud.utils.py3 import httplib +from libcloud.test.secrets import DNS_PARAMS_CLOUDFLARE +from libcloud.test.file_fixtures import DNSFileFixtures +from libcloud.test import MockHttp + + +class CloudFlareDNSDriverTestCase(unittest.TestCase): + + def setUp(self): + CloudFlareDNSDriver.connectionCls.conn_classes = ( + CloudFlareMockHttp, CloudFlareMockHttp) + CloudFlareMockHttp.type = None + CloudFlareMockHttp.use_param = 'a' + self.driver = CloudFlareDNSDriver(*DNS_PARAMS_CLOUDFLARE) + + def test_list_record_types(self): + record_types = self.driver.list_record_types() + self.assertEqual(len(record_types), 9) + self.assertTrue(RecordType.A in record_types) + + def test_list_zones(self): + zones = self.driver.list_zones() + self.assertEqual(len(zones), 1) + + zone = zones[0] + self.assertEqual(zone.id, '1234') + self.assertEqual(zone.domain, 'example.com') + self.assertEqual(zone.type, 'master') + + for attribute_name in ZONE_EXTRA_ATTRIBUTES: + self.assertTrue(attribute_name in zone.extra) + + def test_list_records(self): + zone = self.driver.list_zones()[0] + records = self.driver.list_records(zone=zone) + self.assertEqual(len(records), 18) + + record = records[0] + self.assertEqual(record.id, '364797364') + self.assertEqual(record.name, None) + self.assertEqual(record.type, 'A') + self.assertEqual(record.data, '192.30.252.153') + + for attribute_name in RECORD_EXTRA_ATTRIBUTES: + self.assertTrue(attribute_name in record.extra) + + record = records[4] + self.assertEqual(record.id, '364982413') + self.assertEqual(record.name, 'yesyes') + self.assertEqual(record.type, 'CNAME') + self.assertEqual(record.data, 'verify.bing.com') + + for attribute_name in RECORD_EXTRA_ATTRIBUTES: + self.assertTrue(attribute_name in record.extra) + + def test_get_zone(self): + zone = self.driver.get_zone(zone_id='1234') + self.assertEqual(zone.id, '1234') + self.assertEqual(zone.domain, 'example.com') + self.assertEqual(zone.type, 'master') + + def test_get_zone_zone_doesnt_exist(self): + self.assertRaises(ZoneDoesNotExistError, self.driver.get_zone, + zone_id='doenstexist') + + def test_create_record(self): + zone = self.driver.list_zones()[0] + record = self.driver.create_record(name='test5', zone=zone, type='A', + data='127.0.0.3') + self.assertEqual(record.id, '412561327') + self.assertEqual(record.name, 'test5') + self.assertEqual(record.type, 'A') + self.assertEqual(record.data, '127.0.0.3') + + def test_update_records(self): + zone = self.driver.list_zones()[0] + record = zone.list_records()[0] + updated_record = self.driver.update_record(record=record, + data='127.0.0.4') + + self.assertEqual(updated_record.name, 'test6') + self.assertEqual(updated_record.type, 'A') + self.assertEqual(updated_record.data, '127.0.0.4') + self.assertEqual(updated_record.ttl, 120) + + def test_delete_record(self): + zone = self.driver.list_zones()[0] + record = zone.list_records()[0] + result = self.driver.delete_record(record=record) + self.assertTrue(result) + + def test_delete_zone(self): + zone = self.driver.list_zones()[0] + self.assertRaises(NotImplementedError, self.driver.delete_zone, + zone=zone) + + def test_ex_get_zone_stats(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_get_zone_stats(zone=zone) + self.assertTrue('trafficBreakdown' in result) + self.assertTrue('bandwidthServed' in result) + self.assertTrue('requestsServed' in result) + self.assertTrue('pro_zone' in result) + self.assertTrue('userSecuritySetting' in result) + + def test_ex_zone_check(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_zone_check(zones=[zone]) + self.assertEqual(result, {'example.com': 4025956}) + + def test_ex_get_ip_threat_score(self): + result = self.driver.ex_get_ip_threat_score(ip='127.0.0.1') + self.assertEqual(result, {'127.0.0.1': False}) + + def test_get_ex_zone_settings(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_get_zone_settings(zone=zone) + self.assertTrue('dnssec' in result) + self.assertTrue('ddos' in result) + self.assertTrue('email_filter' in result) + self.assertTrue('secureheader_settings' in result) + + def test_ex_set_one_security_level(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_set_zone_security_level(zone=zone, level='med') + self.assertTrue(result) + + def test_ex_set_zone_cache_level(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_set_zone_cache_level(zone=zone, level='agg') + self.assertTrue(result) + + def test_ex_enable_development_mode(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_enable_development_mode(zone=zone) + self.assertTrue(result) + + def test_ex_disable_development_mode(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_disable_development_mode(zone=zone) + self.assertTrue(result) + + def test_ex_purge_cached_files(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_purge_cached_files(zone=zone) + self.assertTrue(result) + + def test_ex_purge_cached_file(self): + zone = self.driver.list_zones()[0] + url = 'https://www.example.com/test.html' + result = self.driver.ex_purge_cached_file(zone=zone, url=url) + self.assertTrue(result) + + def test_ex_whitelist_ip(self): + zone = self.driver.list_zones()[0] + ip = '127.0.0.1' + result = self.driver.ex_whitelist_ip(zone=zone, ip=ip) + self.assertTrue(result) + + def test_ex_blacklist_ip(self): + zone = self.driver.list_zones()[0] + ip = '127.0.0.1' + result = self.driver.ex_blacklist_ip(zone=zone, ip=ip) + self.assertTrue(result) + + def test_ex_unlist_ip(self): + zone = self.driver.list_zones()[0] + ip = '127.0.0.1' + result = self.driver.ex_unlist_ip(zone=zone, ip=ip) + self.assertTrue(result) + + def test_enable_ipv6_support(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_enable_development_mode(zone=zone) + self.assertTrue(result) + + def test_ex_disable_ipv6_support(self): + zone = self.driver.list_zones()[0] + result = self.driver.ex_disable_development_mode(zone=zone) + self.assertTrue(result) + + +class CloudFlareMockHttp(MockHttp): + fixtures = DNSFileFixtures('cloudflare') + + def _api_json_html_zone_load_multi( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('zone_load_multi.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_rec_load_all( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('rec_load_all.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_rec_new( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('rec_new.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_rec_delete( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('rec_delete.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_rec_edit( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('rec_edit.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_stats( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('stats.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_zone_check( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('zone_check.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_ip_lkup( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('ip_lkup.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_zone_settings( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('zone_settings.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_sec_lvl( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('sec_lvl.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_cache_lvl( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('cache_lvl.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_devmode( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('devmode.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_fpurge_ts( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('fpurge_ts.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_zone_file_purge( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('zone_file_purge.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_wl( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('wl.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_ban( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('ban.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_nul( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('nul.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + def _api_json_html_ipv46( + self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('ipv46.json') + else: + raise AssertionError('Unsupported method') + return (httplib.OK, body, {}, httplib.responses[httplib.OK]) + + +if __name__ == '__main__': + sys.exit(unittest.main()) diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist index 98b5512a95..411b6a4891 100644 --- a/libcloud/test/secrets.py-dist +++ b/libcloud/test/secrets.py-dist @@ -80,3 +80,4 @@ DNS_PARAMS_LIQUIDWEB = ('user', 'key') DNS_PARAMS_ZONOMI = ('key') DNS_PARAMS_DURABLEDNS = ('api_user', 'api_key') DNS_PARAMS_GODADDY = ('customer-id', 'api_user', 'api_key') +DNS_PARAMS_CLOUDFLARE = ('user@example.com', 'key')