Browse Source

Merge pull request #668 from ucc/srv_null_records

Fix handling of NULL SRV records in Cloudflare and Digital Ocean providers, add warnings to DNS Made Easy and DNSimple providers
pull/679/head
Ross McFarland 5 years ago
committed by GitHub
parent
commit
fa5cff78ff
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 395 additions and 62 deletions
  1. +6
    -2
      octodns/provider/cloudflare.py
  2. +5
    -1
      octodns/provider/digitalocean.py
  3. +38
    -2
      octodns/provider/dnsimple.py
  4. +24
    -0
      octodns/provider/dnsmadeeasy.py
  5. +16
    -0
      tests/config/unit.tests.yaml
  6. +50
    -0
      tests/fixtures/cloudflare-dns_records-page-2.json
  7. +56
    -0
      tests/fixtures/constellix-records.json
  8. +22
    -0
      tests/fixtures/digitalocean-page-2.json
  9. +24
    -2
      tests/fixtures/easydns-records.json
  10. +18
    -2
      tests/fixtures/edgedns-records.json
  11. +18
    -0
      tests/fixtures/gandi-no-changes.json
  12. +2
    -0
      tests/fixtures/mythicbeasts-list.txt
  13. +24
    -0
      tests/fixtures/powerdns-full-data.json
  14. +7
    -7
      tests/test_octodns_manager.py
  15. +6
    -6
      tests/test_octodns_provider_cloudflare.py
  16. +3
    -3
      tests/test_octodns_provider_constellix.py
  17. +21
    -3
      tests/test_octodns_provider_digitalocean.py
  18. +2
    -2
      tests/test_octodns_provider_dnsimple.py
  19. +1
    -1
      tests/test_octodns_provider_dnsmadeeasy.py
  20. +3
    -3
      tests/test_octodns_provider_easydns.py
  21. +5
    -5
      tests/test_octodns_provider_edgedns.py
  22. +18
    -2
      tests/test_octodns_provider_gandi.py
  23. +4
    -4
      tests/test_octodns_provider_mythicbeasts.py
  24. +3
    -3
      tests/test_octodns_provider_powerdns.py
  25. +2
    -2
      tests/test_octodns_provider_transip.py
  26. +4
    -4
      tests/test_octodns_provider_ultra.py
  27. +6
    -4
      tests/test_octodns_provider_yaml.py
  28. +4
    -4
      tests/test_octodns_source_axfr.py
  29. +3
    -0
      tests/zones/unit.tests.tst

+ 6
- 2
octodns/provider/cloudflare.py View File

@ -269,11 +269,13 @@ class CloudflareProvider(BaseProvider):
def _data_for_SRV(self, _type, records): def _data_for_SRV(self, _type, records):
values = [] values = []
for r in records: for r in records:
target = ('{}.'.format(r['data']['target'])
if r['data']['target'] != "." else ".")
values.append({ values.append({
'priority': r['data']['priority'], 'priority': r['data']['priority'],
'weight': r['data']['weight'], 'weight': r['data']['weight'],
'port': r['data']['port'], 'port': r['data']['port'],
'target': '{}.'.format(r['data']['target']),
'target': target,
}) })
return { return {
'type': _type, 'type': _type,
@ -454,6 +456,8 @@ class CloudflareProvider(BaseProvider):
name = subdomain name = subdomain
for value in record.values: for value in record.values:
target = value.target[:-1] if value.target != "." else "."
yield { yield {
'data': { 'data': {
'service': service, 'service': service,
@ -462,7 +466,7 @@ class CloudflareProvider(BaseProvider):
'priority': value.priority, 'priority': value.priority,
'weight': value.weight, 'weight': value.weight,
'port': value.port, 'port': value.port,
'target': value.target[:-1],
'target': target,
} }
} }


+ 5
- 1
octodns/provider/digitalocean.py View File

@ -186,10 +186,14 @@ class DigitalOceanProvider(BaseProvider):
def _data_for_SRV(self, _type, records): def _data_for_SRV(self, _type, records):
values = [] values = []
for record in records: for record in records:
target = (
'{}.'.format(record['data'])
if record['data'] != "." else "."
)
values.append({ values.append({
'port': record['port'], 'port': record['port'],
'priority': record['priority'], 'priority': record['priority'],
'target': '{}.'.format(record['data']),
'target': target,
'weight': record['weight'] 'weight': record['weight']
}) })
return { return {


+ 38
- 2
octodns/provider/dnsimple.py View File

@ -218,12 +218,23 @@ class DnsimpleProvider(BaseProvider):
try: try:
weight, port, target = record['content'].split(' ', 2) weight, port, target = record['content'].split(' ', 2)
except ValueError: except ValueError:
# see _data_for_NAPTR's continue
# their api/website will let you create invalid records, this
# essentially handles that by ignoring them for values
# purposes. That will cause updates to happen to delete them if
# they shouldn't exist or update them if they're wrong
self.log.warning(
'_data_for_SRV: unsupported %s record (%s)',
_type,
record['content']
)
continue continue
target = '{}.'.format(target) if target != "." else "."
values.append({ values.append({
'port': port, 'port': port,
'priority': record['priority'], 'priority': record['priority'],
'target': '{}.'.format(target),
'target': target,
'weight': weight 'weight': weight
}) })
return { return {
@ -270,6 +281,10 @@ class DnsimpleProvider(BaseProvider):
for record in self.zone_records(zone): for record in self.zone_records(zone):
_type = record['type'] _type = record['type']
if _type not in self.SUPPORTS: if _type not in self.SUPPORTS:
self.log.warning(
'populate: skipping unsupported %s record',
_type
)
continue continue
elif _type == 'TXT' and record['content'].startswith('ALIAS for'): elif _type == 'TXT' and record['content'].startswith('ALIAS for'):
# ALIAS has a "ride along" TXT record with 'ALIAS for XXXX', # ALIAS has a "ride along" TXT record with 'ALIAS for XXXX',
@ -290,6 +305,27 @@ class DnsimpleProvider(BaseProvider):
len(zone.records) - before, exists) len(zone.records) - before, exists)
return exists return exists
def supports(self, record):
# DNSimple does not support empty/NULL SRV records
#
# Fails silently and leaves a corrupt record
#
# Skip the record and continue
if record._type == "SRV":
if 'value' in record.data:
targets = (record.data['value']['target'],)
else:
targets = [value['target'] for value in record.data['values']]
if "." in targets:
self.log.warning(
'supports: unsupported %s record with target (%s)',
record._type, targets
)
return False
return super(DnsimpleProvider, self).supports(record)
def _params_for_multiple(self, record): def _params_for_multiple(self, record):
for value in record.values: for value in record.values:
yield { yield {


+ 24
- 0
octodns/provider/dnsmadeeasy.py View File

@ -284,6 +284,30 @@ class DnsMadeEasyProvider(BaseProvider):
len(zone.records) - before, exists) len(zone.records) - before, exists)
return exists return exists
def supports(self, record):
# DNS Made Easy does not support empty/NULL SRV records
#
# Attempting to sync such a record would generate the following error
#
# octodns.provider.dnsmadeeasy.DnsMadeEasyClientBadRequest:
# - Record value may not be a standalone dot.
#
# Skip the record and continue
if record._type == "SRV":
if 'value' in record.data:
targets = (record.data['value']['target'],)
else:
targets = [value['target'] for value in record.data['values']]
if "." in targets:
self.log.warning(
'supports: unsupported %s record with target (%s)',
record._type, targets
)
return False
return super(DnsMadeEasyProvider, self).supports(record)
def _params_for_multiple(self, record): def _params_for_multiple(self, record):
for value in record.values: for value in record.values:
yield { yield {


+ 16
- 0
tests/config/unit.tests.yaml View File

@ -36,6 +36,22 @@
- flags: 0 - flags: 0
tag: issue tag: issue
value: ca.unit.tests value: ca.unit.tests
_imap._tcp:
ttl: 600
type: SRV
values:
- port: 0
priority: 0
target: .
weight: 0
_pop3._tcp:
ttl: 600
type: SRV
values:
- port: 0
priority: 0
target: .
weight: 0
_srv._tcp: _srv._tcp:
ttl: 600 ttl: 600
type: SRV type: SRV


+ 50
- 0
tests/fixtures/cloudflare-dns_records-page-2.json View File

@ -173,6 +173,56 @@
"meta": { "meta": {
"auto_added": false "auto_added": false
} }
},
{
"id": "fc12ab34cd5611334422ab3322997656",
"type": "SRV",
"name": "_imap._tcp.unit.tests",
"data": {
"service": "_imap",
"proto": "_tcp",
"name": "unit.tests",
"priority": 0,
"weight": 0,
"port": 0,
"target": "."
},
"proxiable": true,
"proxied": false,
"ttl": 600,
"locked": false,
"zone_id": "ff12ab34cd5611334422ab3322997650",
"zone_name": "unit.tests",
"modified_on": "2017-03-11T18:01:43.940682Z",
"created_on": "2017-03-11T18:01:43.940682Z",
"meta": {
"auto_added": false
}
},
{
"id": "fc12ab34cd5611334422ab3322997656",
"type": "SRV",
"name": "_pop3._tcp.unit.tests",
"data": {
"service": "_imap",
"proto": "_pop3",
"name": "unit.tests",
"priority": 0,
"weight": 0,
"port": 0,
"target": "."
},
"proxiable": true,
"proxied": false,
"ttl": 600,
"locked": false,
"zone_id": "ff12ab34cd5611334422ab3322997650",
"zone_name": "unit.tests",
"modified_on": "2017-03-11T18:01:43.940682Z",
"created_on": "2017-03-11T18:01:43.940682Z",
"meta": {
"auto_added": false
}
} }
], ],
"result_info": { "result_info": {


+ 56
- 0
tests/fixtures/constellix-records.json View File

@ -64,6 +64,62 @@
"roundRobinFailover": [], "roundRobinFailover": [],
"pools": [], "pools": [],
"poolsDetail": [] "poolsDetail": []
}, {
"id": 1898527,
"type": "SRV",
"recordType": "srv",
"name": "_imap._tcp",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149714387,
"value": [{
"value": ".",
"priority": 0,
"weight": 0,
"port": 0,
"disableFlag": false
}],
"roundRobin": [{
"value": ".",
"priority": 0,
"weight": 0,
"port": 0,
"disableFlag": false
}]
}, {
"id": 1898528,
"type": "SRV",
"recordType": "srv",
"name": "_pop3._tcp",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149714387,
"value": [{
"value": ".",
"priority": 0,
"weight": 0,
"port": 0,
"disableFlag": false
}],
"roundRobin": [{
"value": ".",
"priority": 0,
"weight": 0,
"port": 0,
"disableFlag": false
}]
}, { }, {
"id": 1808527, "id": 1808527,
"type": "SRV", "type": "SRV",


+ 22
- 0
tests/fixtures/digitalocean-page-2.json View File

@ -76,6 +76,28 @@
"weight": null, "weight": null,
"flags": null, "flags": null,
"tag": null "tag": null
}, {
"id": 11189896,
"type": "SRV",
"name": "_imap._tcp",
"data": ".",
"priority": 0,
"port": 0,
"ttl": 600,
"weight": 0,
"flags": null,
"tag": null
}, {
"id": 11189897,
"type": "SRV",
"name": "_pop3._tcp",
"data": ".",
"priority": 0,
"port": 0,
"ttl": 600,
"weight": 0,
"flags": null,
"tag": null
}], }],
"links": { "links": {
"pages": { "pages": {


+ 24
- 2
tests/fixtures/easydns-records.json View File

@ -264,10 +264,32 @@
"rdata": "v=DKIM1;k=rsa;s=email;h=sha256;p=A\/kinda+of\/long\/string+with+numb3rs", "rdata": "v=DKIM1;k=rsa;s=email;h=sha256;p=A\/kinda+of\/long\/string+with+numb3rs",
"geozone_id": "0", "geozone_id": "0",
"last_mod": "2020-01-01 01:01:01" "last_mod": "2020-01-01 01:01:01"
},
{
"id": "12340025",
"domain": "unit.tests",
"host": "_imap._tcp",
"ttl": "600",
"prio": "0",
"type": "SRV",
"rdata": "0 0 0 .",
"geozone_id": "0",
"last_mod": "2020-01-01 01:01:01"
},
{
"id": "12340026",
"domain": "unit.tests",
"host": "_pop3._tcp",
"ttl": "600",
"prio": "0",
"type": "SRV",
"rdata": "0 0 0 .",
"geozone_id": "0",
"last_mod": "2020-01-01 01:01:01"
} }
], ],
"count": 24,
"total": 24,
"count": 26,
"total": 26,
"start": 0, "start": 0,
"max": 1000, "max": 1000,
"status": 200 "status": 200


+ 18
- 2
tests/fixtures/edgedns-records.json View File

@ -9,6 +9,22 @@
"name": "_srv._tcp.unit.tests", "name": "_srv._tcp.unit.tests",
"ttl": 600 "ttl": 600
}, },
{
"rdata": [
"0 0 0 ."
],
"type": "SRV",
"name": "_imap._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"0 0 0 ."
],
"type": "SRV",
"name": "_pop3._tcp.unit.tests",
"ttl": 600
},
{ {
"rdata": [ "rdata": [
"2601:644:500:e210:62f8:1dff:feb8:947a" "2601:644:500:e210:62f8:1dff:feb8:947a"
@ -151,7 +167,7 @@
} }
], ],
"metadata": { "metadata": {
"totalElements": 16,
"totalElements": 18,
"showAll": true "showAll": true
} }
}
}

+ 18
- 0
tests/fixtures/gandi-no-changes.json View File

@ -123,6 +123,24 @@
"2.2.3.6" "2.2.3.6"
] ]
}, },
{
"rrset_type": "SRV",
"rrset_ttl": 600,
"rrset_name": "_imap._tcp",
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_imap._tcp/SRV",
"rrset_values": [
"0 0 0 ."
]
},
{
"rrset_type": "SRV",
"rrset_ttl": 600,
"rrset_name": "_pop3._tcp",
"rrset_href": "https://api.gandi.net/v5/livedns/domains/unit.tests/records/_pop3._tcp/SRV",
"rrset_values": [
"0 0 0 ."
]
},
{ {
"rrset_type": "SRV", "rrset_type": "SRV",
"rrset_ttl": 600, "rrset_ttl": 600,


+ 2
- 0
tests/fixtures/mythicbeasts-list.txt View File

@ -5,6 +5,8 @@
@ 3600 SSHFP 1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73 @ 3600 SSHFP 1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73
@ 3600 SSHFP 1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49 @ 3600 SSHFP 1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49
@ 3600 CAA 0 issue ca.unit.tests @ 3600 CAA 0 issue ca.unit.tests
_imap._tcp 600 SRV 0 0 0 .
_pop3._tcp 600 SRV 0 0 0 .
_srv._tcp 600 SRV 10 20 30 foo-1.unit.tests. _srv._tcp 600 SRV 10 20 30 foo-1.unit.tests.
_srv._tcp 600 SRV 12 20 30 foo-2.unit.tests. _srv._tcp 600 SRV 12 20 30 foo-2.unit.tests.
aaaa 600 AAAA 2601:644:500:e210:62f8:1dff:feb8:947a aaaa 600 AAAA 2601:644:500:e210:62f8:1dff:feb8:947a


+ 24
- 0
tests/fixtures/powerdns-full-data.json View File

@ -75,6 +75,30 @@
"ttl": 300, "ttl": 300,
"type": "A" "type": "A"
}, },
{
"comments": [],
"name": "_imap._tcp.unit.tests.",
"records": [
{
"content": "0 0 0 .",
"disabled": false
}
],
"ttl": 600,
"type": "SRV"
},
{
"comments": [],
"name": "_pop3._tcp.unit.tests.",
"records": [
{
"content": "0 0 0 .",
"disabled": false
}
],
"ttl": 600,
"type": "SRV"
},
{ {
"comments": [], "comments": [],
"name": "_srv._tcp.unit.tests.", "name": "_srv._tcp.unit.tests.",


+ 7
- 7
tests/test_octodns_manager.py View File

@ -118,12 +118,12 @@ class TestManager(TestCase):
environ['YAML_TMP_DIR'] = tmpdir.dirname environ['YAML_TMP_DIR'] = tmpdir.dirname
tc = Manager(get_config_filename('simple.yaml')) \ tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False) .sync(dry_run=False)
self.assertEquals(23, tc)
self.assertEquals(25, tc)
# try with just one of the zones # try with just one of the zones
tc = Manager(get_config_filename('simple.yaml')) \ tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, eligible_zones=['unit.tests.']) .sync(dry_run=False, eligible_zones=['unit.tests.'])
self.assertEquals(17, tc)
self.assertEquals(19, tc)
# the subzone, with 2 targets # the subzone, with 2 targets
tc = Manager(get_config_filename('simple.yaml')) \ tc = Manager(get_config_filename('simple.yaml')) \
@ -138,18 +138,18 @@ class TestManager(TestCase):
# Again with force # Again with force
tc = Manager(get_config_filename('simple.yaml')) \ tc = Manager(get_config_filename('simple.yaml')) \
.sync(dry_run=False, force=True) .sync(dry_run=False, force=True)
self.assertEquals(23, tc)
self.assertEquals(25, tc)
# Again with max_workers = 1 # Again with max_workers = 1
tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \ tc = Manager(get_config_filename('simple.yaml'), max_workers=1) \
.sync(dry_run=False, force=True) .sync(dry_run=False, force=True)
self.assertEquals(23, tc)
self.assertEquals(25, tc)
# Include meta # Include meta
tc = Manager(get_config_filename('simple.yaml'), max_workers=1, tc = Manager(get_config_filename('simple.yaml'), max_workers=1,
include_meta=True) \ include_meta=True) \
.sync(dry_run=False, force=True) .sync(dry_run=False, force=True)
self.assertEquals(27, tc)
self.assertEquals(29, tc)
def test_eligible_sources(self): def test_eligible_sources(self):
with TemporaryDirectory() as tmpdir: with TemporaryDirectory() as tmpdir:
@ -215,13 +215,13 @@ class TestManager(TestCase):
fh.write('---\n{}') fh.write('---\n{}')
changes = manager.compare(['in'], ['dump'], 'unit.tests.') changes = manager.compare(['in'], ['dump'], 'unit.tests.')
self.assertEquals(17, len(changes))
self.assertEquals(19, len(changes))
# Compound sources with varying support # Compound sources with varying support
changes = manager.compare(['in', 'nosshfp'], changes = manager.compare(['in', 'nosshfp'],
['dump'], ['dump'],
'unit.tests.') 'unit.tests.')
self.assertEquals(16, len(changes))
self.assertEquals(18, len(changes))
with self.assertRaises(ManagerException) as ctx: with self.assertRaises(ManagerException) as ctx:
manager.compare(['nope'], ['dump'], 'unit.tests.') manager.compare(['nope'], ['dump'], 'unit.tests.')


+ 6
- 6
tests/test_octodns_provider_cloudflare.py View File

@ -184,7 +184,7 @@ class TestCloudflareProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(14, len(zone.records))
self.assertEquals(16, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
@ -193,7 +193,7 @@ class TestCloudflareProvider(TestCase):
# re-populating the same zone/records comes out of cache, no calls # re-populating the same zone/records comes out of cache, no calls
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
provider.populate(again) provider.populate(again)
self.assertEquals(14, len(again.records))
self.assertEquals(16, len(again.records))
def test_apply(self): def test_apply(self):
provider = CloudflareProvider('test', 'email', 'token', retry_period=0) provider = CloudflareProvider('test', 'email', 'token', retry_period=0)
@ -207,12 +207,12 @@ class TestCloudflareProvider(TestCase):
'id': 42, 'id': 42,
} }
}, # zone create }, # zone create
] + [None] * 24 # individual record creates
] + [None] * 25 # individual record creates
# non-existent zone, create everything # non-existent zone, create everything
plan = provider.plan(self.expected) plan = provider.plan(self.expected)
self.assertEquals(14, len(plan.changes))
self.assertEquals(14, provider.apply(plan))
self.assertEquals(16, len(plan.changes))
self.assertEquals(16, provider.apply(plan))
self.assertFalse(plan.exists) self.assertFalse(plan.exists)
provider._request.assert_has_calls([ provider._request.assert_has_calls([
@ -238,7 +238,7 @@ class TestCloudflareProvider(TestCase):
}), }),
], True) ], True)
# expected number of total calls # expected number of total calls
self.assertEquals(25, provider._request.call_count)
self.assertEquals(27, provider._request.call_count)
provider._request.reset_mock() provider._request.reset_mock()


+ 3
- 3
tests/test_octodns_provider_constellix.py View File

@ -101,14 +101,14 @@ class TestConstellixProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(14, len(zone.records))
self.assertEquals(16, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache # 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
provider.populate(again) provider.populate(again)
self.assertEquals(14, len(again.records))
self.assertEquals(16, len(again.records))
# bust the cache # bust the cache
del provider._zone_records[zone.name] del provider._zone_records[zone.name]
@ -163,7 +163,7 @@ class TestConstellixProvider(TestCase):
}), }),
]) ])
self.assertEquals(17, provider._client._request.call_count)
self.assertEquals(19, provider._client._request.call_count)
provider._client._request.reset_mock() provider._client._request.reset_mock()


+ 21
- 3
tests/test_octodns_provider_digitalocean.py View File

@ -83,14 +83,14 @@ class TestDigitalOceanProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(12, len(zone.records))
self.assertEquals(14, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache # 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
provider.populate(again) provider.populate(again)
self.assertEquals(12, len(again.records))
self.assertEquals(14, len(again.records))
# bust the cache # bust the cache
del provider._zone_records[zone.name] del provider._zone_records[zone.name]
@ -190,6 +190,24 @@ class TestDigitalOceanProvider(TestCase):
'flags': 0, 'name': '@', 'flags': 0, 'name': '@',
'tag': 'issue', 'tag': 'issue',
'ttl': 3600, 'type': 'CAA'}), 'ttl': 3600, 'type': 'CAA'}),
call('POST', '/domains/unit.tests/records', data={
'name': '_imap._tcp',
'weight': 0,
'data': '.',
'priority': 0,
'ttl': 600,
'type': 'SRV',
'port': 0
}),
call('POST', '/domains/unit.tests/records', data={
'name': '_pop3._tcp',
'weight': 0,
'data': '.',
'priority': 0,
'ttl': 600,
'type': 'SRV',
'port': 0
}),
call('POST', '/domains/unit.tests/records', data={ call('POST', '/domains/unit.tests/records', data={
'name': '_srv._tcp', 'name': '_srv._tcp',
'weight': 20, 'weight': 20,
@ -200,7 +218,7 @@ class TestDigitalOceanProvider(TestCase):
'port': 30 'port': 30
}), }),
]) ])
self.assertEquals(24, provider._client._request.call_count)
self.assertEquals(26, provider._client._request.call_count)
provider._client._request.reset_mock() provider._client._request.reset_mock()


+ 2
- 2
tests/test_octodns_provider_dnsimple.py View File

@ -136,8 +136,8 @@ class TestDnsimpleProvider(TestCase):
] ]
plan = provider.plan(self.expected) plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 5
# No root NS, no ignored, no excluded
n = len(self.expected.records) - 7
self.assertEquals(n, len(plan.changes)) self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan)) self.assertEquals(n, provider.apply(plan))
self.assertFalse(plan.exists) self.assertFalse(plan.exists)


+ 1
- 1
tests/test_octodns_provider_dnsmadeeasy.py View File

@ -134,7 +134,7 @@ class TestDnsMadeEasyProvider(TestCase):
plan = provider.plan(self.expected) plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported # No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 7
n = len(self.expected.records) - 9
self.assertEquals(n, len(plan.changes)) self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan)) self.assertEquals(n, provider.apply(plan))


+ 3
- 3
tests/test_octodns_provider_easydns.py View File

@ -80,14 +80,14 @@ class TestEasyDNSProvider(TestCase):
text=fh.read()) text=fh.read())
provider.populate(zone) provider.populate(zone)
self.assertEquals(13, len(zone.records))
self.assertEquals(15, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache # 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
provider.populate(again) provider.populate(again)
self.assertEquals(13, len(again.records))
self.assertEquals(15, len(again.records))
# bust the cache # bust the cache
del provider._zone_records[zone.name] del provider._zone_records[zone.name]
@ -379,7 +379,7 @@ class TestEasyDNSProvider(TestCase):
self.assertEquals(n, provider.apply(plan)) self.assertEquals(n, provider.apply(plan))
self.assertFalse(plan.exists) self.assertFalse(plan.exists)
self.assertEquals(23, provider._client._request.call_count)
self.assertEquals(25, provider._client._request.call_count)
provider._client._request.reset_mock() provider._client._request.reset_mock()


+ 5
- 5
tests/test_octodns_provider_edgedns.py View File

@ -77,14 +77,14 @@ class TestEdgeDnsProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(16, len(zone.records))
self.assertEquals(18, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache # 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
provider.populate(again) provider.populate(again)
self.assertEquals(16, len(again.records))
self.assertEquals(18, len(again.records))
# bust the cache # bust the cache
del provider._zone_records[zone.name] del provider._zone_records[zone.name]
@ -105,7 +105,7 @@ class TestEdgeDnsProvider(TestCase):
mock.delete(ANY, status_code=204) mock.delete(ANY, status_code=204)
changes = provider.apply(plan) changes = provider.apply(plan)
self.assertEquals(29, changes)
self.assertEquals(31, changes)
# Test against a zone that doesn't exist yet # Test against a zone that doesn't exist yet
with requests_mock() as mock: with requests_mock() as mock:
@ -118,7 +118,7 @@ class TestEdgeDnsProvider(TestCase):
mock.delete(ANY, status_code=204) mock.delete(ANY, status_code=204)
changes = provider.apply(plan) changes = provider.apply(plan)
self.assertEquals(14, changes)
self.assertEquals(16, changes)
# Test against a zone that doesn't exist yet, but gid not provided # Test against a zone that doesn't exist yet, but gid not provided
with requests_mock() as mock: with requests_mock() as mock:
@ -132,7 +132,7 @@ class TestEdgeDnsProvider(TestCase):
mock.delete(ANY, status_code=204) mock.delete(ANY, status_code=204)
changes = provider.apply(plan) changes = provider.apply(plan)
self.assertEquals(14, changes)
self.assertEquals(16, changes)
# Test against a zone that doesn't exist, but cid not provided # Test against a zone that doesn't exist, but cid not provided


+ 18
- 2
tests/test_octodns_provider_gandi.py View File

@ -117,7 +117,7 @@ class TestGandiProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(14, len(zone.records))
self.assertEquals(16, len(zone.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
@ -284,6 +284,22 @@ class TestGandiProvider(TestCase):
'12 20 30 foo-2.unit.tests.' '12 20 30 foo-2.unit.tests.'
] ]
}), }),
call('POST', '/livedns/domains/unit.tests/records', data={
'rrset_name': '_pop3._tcp',
'rrset_ttl': 600,
'rrset_type': 'SRV',
'rrset_values': [
'0 0 0 .',
]
}),
call('POST', '/livedns/domains/unit.tests/records', data={
'rrset_name': '_imap._tcp',
'rrset_ttl': 600,
'rrset_type': 'SRV',
'rrset_values': [
'0 0 0 .',
]
}),
call('POST', '/livedns/domains/unit.tests/records', data={ call('POST', '/livedns/domains/unit.tests/records', data={
'rrset_name': '@', 'rrset_name': '@',
'rrset_ttl': 3600, 'rrset_ttl': 3600,
@ -307,7 +323,7 @@ class TestGandiProvider(TestCase):
}) })
]) ])
# expected number of total calls # expected number of total calls
self.assertEquals(17, provider._client._request.call_count)
self.assertEquals(19, provider._client._request.call_count)
provider._client._request.reset_mock() provider._client._request.reset_mock()


+ 4
- 4
tests/test_octodns_provider_mythicbeasts.py View File

@ -378,8 +378,8 @@ class TestMythicBeastsProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(15, len(zone.records))
self.assertEquals(15, len(self.expected.records))
self.assertEquals(17, len(zone.records))
self.assertEquals(17, len(self.expected.records))
changes = self.expected.changes(zone, provider) changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
@ -445,7 +445,7 @@ class TestMythicBeastsProvider(TestCase):
if isinstance(c, Update)])) if isinstance(c, Update)]))
self.assertEquals(1, len([c for c in plan.changes self.assertEquals(1, len([c for c in plan.changes
if isinstance(c, Delete)])) if isinstance(c, Delete)]))
self.assertEquals(14, len([c for c in plan.changes
self.assertEquals(16, len([c for c in plan.changes
if isinstance(c, Create)])) if isinstance(c, Create)]))
self.assertEquals(16, provider.apply(plan))
self.assertEquals(18, provider.apply(plan))
self.assertTrue(plan.exists) self.assertTrue(plan.exists)

+ 3
- 3
tests/test_octodns_provider_powerdns.py View File

@ -186,7 +186,7 @@ class TestPowerDnsProvider(TestCase):
source = YamlProvider('test', join(dirname(__file__), 'config')) source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected) source.populate(expected)
expected_n = len(expected.records) - 3 expected_n = len(expected.records) - 3
self.assertEquals(17, expected_n)
self.assertEquals(19, expected_n)
# No diffs == no changes # No diffs == no changes
with requests_mock() as mock: with requests_mock() as mock:
@ -194,7 +194,7 @@ class TestPowerDnsProvider(TestCase):
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
provider.populate(zone) provider.populate(zone)
self.assertEquals(17, len(zone.records))
self.assertEquals(19, len(zone.records))
changes = expected.changes(zone, provider) changes = expected.changes(zone, provider)
self.assertEquals(0, len(changes)) self.assertEquals(0, len(changes))
@ -291,7 +291,7 @@ class TestPowerDnsProvider(TestCase):
expected = Zone('unit.tests.', []) expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config')) source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected) source.populate(expected)
self.assertEquals(20, len(expected.records))
self.assertEquals(22, len(expected.records))
# A small change to a single record # A small change to a single record
with requests_mock() as mock: with requests_mock() as mock:


+ 2
- 2
tests/test_octodns_provider_transip.py View File

@ -222,7 +222,7 @@ N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd
provider._client = MockDomainService('unittest', self.bogus_key) provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected) plan = provider.plan(_expected)
self.assertEqual(12, plan.change_counts['Create'])
self.assertEqual(14, plan.change_counts['Create'])
self.assertEqual(0, plan.change_counts['Update']) self.assertEqual(0, plan.change_counts['Update'])
self.assertEqual(0, plan.change_counts['Delete']) self.assertEqual(0, plan.change_counts['Delete'])
@ -235,7 +235,7 @@ N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd
provider = TransipProvider('test', 'unittest', self.bogus_key) provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key) provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected) plan = provider.plan(_expected)
self.assertEqual(12, len(plan.changes))
self.assertEqual(14, len(plan.changes))
changes = provider.apply(plan) changes = provider.apply(plan)
self.assertEqual(changes, len(plan.changes)) self.assertEqual(changes, len(plan.changes))


+ 4
- 4
tests/test_octodns_provider_ultra.py View File

@ -285,12 +285,12 @@ class TestUltraProvider(TestCase):
provider._request.side_effect = [ provider._request.side_effect = [
UltraNoZonesExistException('No Zones'), UltraNoZonesExistException('No Zones'),
None, # zone create None, # zone create
] + [None] * 13 # individual record creates
] + [None] * 15 # individual record creates
# non-existent zone, create everything # non-existent zone, create everything
plan = provider.plan(self.expected) plan = provider.plan(self.expected)
self.assertEquals(13, len(plan.changes))
self.assertEquals(13, provider.apply(plan))
self.assertEquals(15, len(plan.changes))
self.assertEquals(15, provider.apply(plan))
self.assertFalse(plan.exists) self.assertFalse(plan.exists)
provider._request.assert_has_calls([ provider._request.assert_has_calls([
@ -320,7 +320,7 @@ class TestUltraProvider(TestCase):
'p=A/kinda+of/long/string+with+numb3rs']}), 'p=A/kinda+of/long/string+with+numb3rs']}),
], True) ], True)
# expected number of total calls # expected number of total calls
self.assertEquals(15, provider._request.call_count)
self.assertEquals(17, provider._request.call_count)
# Create sample rrset payload to attempt to alter # Create sample rrset payload to attempt to alter
page1 = json_load(open('tests/fixtures/ultra-records-page-1.json')) page1 = json_load(open('tests/fixtures/ultra-records-page-1.json'))


+ 6
- 4
tests/test_octodns_provider_yaml.py View File

@ -35,7 +35,7 @@ class TestYamlProvider(TestCase):
# without it we see everything # without it we see everything
source.populate(zone) source.populate(zone)
self.assertEquals(20, len(zone.records))
self.assertEquals(22, len(zone.records))
source.populate(dynamic_zone) source.populate(dynamic_zone)
self.assertEquals(5, len(dynamic_zone.records)) self.assertEquals(5, len(dynamic_zone.records))
@ -58,12 +58,12 @@ class TestYamlProvider(TestCase):
# We add everything # We add everything
plan = target.plan(zone) plan = target.plan(zone)
self.assertEquals(17, len([c for c in plan.changes
self.assertEquals(19, len([c for c in plan.changes
if isinstance(c, Create)])) if isinstance(c, Create)]))
self.assertFalse(isfile(yaml_file)) self.assertFalse(isfile(yaml_file))
# Now actually do it # Now actually do it
self.assertEquals(17, target.apply(plan))
self.assertEquals(19, target.apply(plan))
self.assertTrue(isfile(yaml_file)) self.assertTrue(isfile(yaml_file))
# Dynamic plan # Dynamic plan
@ -87,7 +87,7 @@ class TestYamlProvider(TestCase):
# A 2nd sync should still create everything # A 2nd sync should still create everything
plan = target.plan(zone) plan = target.plan(zone)
self.assertEquals(17, len([c for c in plan.changes
self.assertEquals(19, len([c for c in plan.changes
if isinstance(c, Create)])) if isinstance(c, Create)]))
with open(yaml_file) as fh: with open(yaml_file) as fh:
@ -108,6 +108,8 @@ class TestYamlProvider(TestCase):
self.assertTrue('values' in data.pop('txt')) self.assertTrue('values' in data.pop('txt'))
self.assertTrue('values' in data.pop('loc')) self.assertTrue('values' in data.pop('loc'))
# these are stored as singular 'value' # these are stored as singular 'value'
self.assertTrue('value' in data.pop('_imap._tcp'))
self.assertTrue('value' in data.pop('_pop3._tcp'))
self.assertTrue('value' in data.pop('aaaa')) self.assertTrue('value' in data.pop('aaaa'))
self.assertTrue('value' in data.pop('cname')) self.assertTrue('value' in data.pop('cname'))
self.assertTrue('value' in data.pop('dname')) self.assertTrue('value' in data.pop('dname'))


+ 4
- 4
tests/test_octodns_source_axfr.py View File

@ -36,7 +36,7 @@ class TestAxfrSource(TestCase):
] ]
self.source.populate(got) self.source.populate(got)
self.assertEquals(13, len(got.records))
self.assertEquals(15, len(got.records))
with self.assertRaises(AxfrSourceZoneTransferFailed) as ctx: with self.assertRaises(AxfrSourceZoneTransferFailed) as ctx:
zone = Zone('unit.tests.', []) zone = Zone('unit.tests.', [])
@ -73,18 +73,18 @@ class TestZoneFileSource(TestCase):
# Load zonefiles without a specified file extension # Load zonefiles without a specified file extension
valid = Zone('unit.tests.', []) valid = Zone('unit.tests.', [])
source.populate(valid) source.populate(valid)
self.assertEquals(13, len(valid.records))
self.assertEquals(15, len(valid.records))
def test_populate(self): def test_populate(self):
# Valid zone file in directory # Valid zone file in directory
valid = Zone('unit.tests.', []) valid = Zone('unit.tests.', [])
self.source.populate(valid) self.source.populate(valid)
self.assertEquals(13, len(valid.records))
self.assertEquals(15, len(valid.records))
# 2nd populate does not read file again # 2nd populate does not read file again
again = Zone('unit.tests.', []) again = Zone('unit.tests.', [])
self.source.populate(again) self.source.populate(again)
self.assertEquals(13, len(again.records))
self.assertEquals(15, len(again.records))
# bust the cache # bust the cache
del self.source._zone_records[valid.name] del self.source._zone_records[valid.name]


+ 3
- 0
tests/zones/unit.tests.tst View File

@ -20,6 +20,9 @@ caa 1800 IN CAA 0 iodef "mailto:admin@unit.tests"
; SRV Records ; SRV Records
_srv._tcp 600 IN SRV 10 20 30 foo-1.unit.tests. _srv._tcp 600 IN SRV 10 20 30 foo-1.unit.tests.
_srv._tcp 600 IN SRV 10 20 30 foo-2.unit.tests. _srv._tcp 600 IN SRV 10 20 30 foo-2.unit.tests.
; NULL SRV Records
_pop3._tcp 600 IN SRV 0 0 0 .
_imap._tcp 600 IN SRV 0 0 0 .
; TXT Records ; TXT Records
txt 600 IN TXT "Bah bah black sheep" txt 600 IN TXT "Bah bah black sheep"


Loading…
Cancel
Save