Merge remote-tracking branch 'origin/master' into overriding-yaml-provider

This commit is contained in:
Ross McFarland
2020-01-06 13:41:27 -08:00
76 changed files with 7058 additions and 992 deletions
+3 -3
View File
@@ -19,7 +19,7 @@ a:
- value: 6.6.6.6
weight: 10
- value: 5.5.5.5
weight: 25
weight: 15
rules:
- geos:
- EU-GB
@@ -90,9 +90,9 @@ cname:
sea:
values:
- value: target-sea-1.unit.tests.
weight: 100
weight: 10
- value: target-sea-2.unit.tests.
weight: 175
weight: 14
rules:
- geos:
- EU-GB
+28
View File
@@ -0,0 +1,28 @@
providers:
yaml:
class: octodns.provider.yaml.YamlProvider
directory: ./config
simple_source:
class: helpers.SimpleSource
zones:
missing.sources.:
targets:
- yaml
missing.targets.:
sources:
- yaml
unknown.source.:
sources:
- not-there
targets:
- yaml
unknown.target.:
sources:
- yaml
targets:
- not-there-either
not.targetable.:
sources:
- yaml
targets:
- simple_source
+1 -1
View File
@@ -23,7 +23,7 @@ a:
fallback: null
values:
- value: 5.5.5.5
weight: 25
weight: 15
- value: 6.6.6.6
weight: 10
rules:
+2 -2
View File
@@ -21,9 +21,9 @@ cname:
fallback: null
values:
- value: target-sea-1.unit.tests.
weight: 100
weight: 10
- value: target-sea-2.unit.tests.
weight: 175
weight: 14
rules:
- geos:
- EU-GB
-16
View File
@@ -5,24 +5,8 @@ providers:
simple_source:
class: helpers.SimpleSource
zones:
missing.sources.:
targets:
- yaml
missing.targets.:
sources:
- yaml
unknown.source.:
sources:
- not-there
targets:
- yaml
unknown.target.:
sources:
- yaml
targets:
- not-there-either
not.targetable.:
sources:
- yaml
targets:
- simple_source
+28
View File
@@ -0,0 +1,28 @@
[{
"id": 123123,
"name": "unit.tests",
"soa": {
"primaryNameserver": "ns11.constellix.com.",
"email": "dns.constellix.com.",
"ttl": 86400,
"serial": 2015010102,
"refresh": 43200,
"retry": 3600,
"expire": 1209600,
"negCache": 180
},
"createdTs": "2019-08-07T03:36:02Z",
"modifiedTs": "2019-08-07T03:36:02Z",
"typeId": 1,
"domainTags": [],
"folder": null,
"hasGtdRegions": false,
"hasGeoIP": false,
"nameserverGroup": 1,
"nameservers": ["ns11.constellix.com.", "ns21.constellix.com.", "ns31.constellix.com.", "ns41.constellix.net.", "ns51.constellix.net.", "ns61.constellix.net."],
"note": "",
"version": 0,
"status": "ACTIVE",
"tags": [],
"contactIds": []
}]
+598
View File
@@ -0,0 +1,598 @@
[{
"id": 1808529,
"type": "CAA",
"recordType": "caa",
"name": "",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 3600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149569216,
"value": [{
"flag": 0,
"tag": "issue",
"data": "ca.unit.tests",
"caaProviderId": 1,
"disableFlag": false
}],
"roundRobin": [{
"flag": 0,
"tag": "issue",
"data": "ca.unit.tests",
"caaProviderId": 1,
"disableFlag": false
}]
}, {
"id": 1808516,
"type": "A",
"recordType": "a",
"name": "",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149623640,
"value": ["1.2.3.4", "1.2.3.5"],
"roundRobin": [{
"value": "1.2.3.4",
"disableFlag": false
}, {
"value": "1.2.3.5",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"roundRobinFailover": [],
"pools": [],
"poolsDetail": []
}, {
"id": 1808527,
"type": "SRV",
"recordType": "srv",
"name": "_srv._tcp",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149714387,
"value": [{
"value": "foo-1.unit.tests.",
"priority": 10,
"weight": 20,
"port": 30,
"disableFlag": false
}, {
"value": "foo-2.unit.tests.",
"priority": 12,
"weight": 20,
"port": 30,
"disableFlag": false
}],
"roundRobin": [{
"value": "foo-1.unit.tests.",
"priority": 10,
"weight": 20,
"port": 30,
"disableFlag": false
}, {
"value": "foo-2.unit.tests.",
"priority": 12,
"weight": 20,
"port": 30,
"disableFlag": false
}]
}, {
"id": 1808515,
"type": "AAAA",
"recordType": "aaaa",
"name": "aaaa",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149739464,
"value": ["2601:644:500:e210:62f8:1dff:feb8:947a"],
"roundRobin": [{
"value": "2601:644:500:e210:62f8:1dff:feb8:947a",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"pools": [],
"poolsDetail": [],
"roundRobinFailover": []
}, {
"id": 1808530,
"type": "ANAME",
"recordType": "aname",
"name": "",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 1800,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150251379,
"value": [{
"value": "aname.unit.tests.",
"disableFlag": false
}],
"roundRobin": [{
"value": "aname.unit.tests.",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"pools": [],
"poolsDetail": []
}, {
"id": 1808521,
"type": "CNAME",
"recordType": "cname",
"name": "cname",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565152113825,
"value": "",
"roundRobin": [{
"value": "",
"disableFlag": false
}],
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": [{
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 1,
"markedActive": false
}, {
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 2,
"markedActive": false
}]
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": [{
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 1,
"markedActive": false
}, {
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 2,
"markedActive": false
}]
},
"pools": [],
"poolsDetail": [],
"geolocation": null,
"host": ""
}, {
"id": 1808522,
"type": "CNAME",
"recordType": "cname",
"name": "included",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 3600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565152119137,
"value": "",
"roundRobin": [{
"value": "",
"disableFlag": false
}],
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": [{
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 1,
"markedActive": false
}, {
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 2,
"markedActive": false
}]
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": [{
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 1,
"markedActive": false
}, {
"id": null,
"value": "",
"disableFlag": false,
"failedFlag": false,
"status": "N/A",
"sortOrder": 2,
"markedActive": false
}]
},
"pools": [],
"poolsDetail": [],
"geolocation": null,
"host": ""
}, {
"id": 1808523,
"type": "MX",
"recordType": "mx",
"name": "mx",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149879856,
"value": [{
"value": "smtp-3.unit.tests.",
"level": 30,
"disableFlag": false
}, {
"value": "smtp-2.unit.tests.",
"level": 20,
"disableFlag": false
}, {
"value": "smtp-4.unit.tests.",
"level": 10,
"disableFlag": false
}, {
"value": "smtp-1.unit.tests.",
"level": 40,
"disableFlag": false
}],
"roundRobin": [{
"value": "smtp-3.unit.tests.",
"level": 30,
"disableFlag": false
}, {
"value": "smtp-2.unit.tests.",
"level": 20,
"disableFlag": false
}, {
"value": "smtp-4.unit.tests.",
"level": 10,
"disableFlag": false
}, {
"value": "smtp-1.unit.tests.",
"level": 40,
"disableFlag": false
}]
}, {
"id": 1808525,
"type": "PTR",
"recordType": "ptr",
"name": "ptr",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150115139,
"value": [{
"value": "foo.bar.com.",
"disableFlag": false
}],
"roundRobin": [{
"value": "foo.bar.com.",
"disableFlag": false
}]
}, {
"id": 1808526,
"type": "SPF",
"recordType": "spf",
"name": "spf",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149916132,
"value": [{
"value": "\"v=spf1 ip4:192.168.0.1/16-all\"",
"disableFlag": false
}],
"roundRobin": [{
"value": "\"v=spf1 ip4:192.168.0.1/16-all\"",
"disableFlag": false
}]
}, {
"id": 1808528,
"type": "TXT",
"recordType": "txt",
"name": "txt",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565149966915,
"value": [{
"value": "\"Bah bah black sheep\"",
"disableFlag": false
}, {
"value": "\"have you any wool.\"",
"disableFlag": false
}, {
"value": "\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\"",
"disableFlag": false
}],
"roundRobin": [{
"value": "\"Bah bah black sheep\"",
"disableFlag": false
}, {
"value": "\"have you any wool.\"",
"disableFlag": false
}, {
"value": "\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\"",
"disableFlag": false
}]
}, {
"id": 1808524,
"type": "NS",
"recordType": "ns",
"name": "under",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 3600,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150062850,
"value": [{
"value": "ns1.unit.tests.",
"disableFlag": false
}, {
"value": "ns2",
"disableFlag": false
}],
"roundRobin": [{
"value": "ns1.unit.tests.",
"disableFlag": false
}, {
"value": "ns2",
"disableFlag": false
}]
}, {
"id": 1808531,
"type": "HTTPRedirection",
"recordType": "httpredirection",
"name": "unsupported",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150348154,
"value": "https://redirect.unit.tests",
"roundRobin": [{
"value": "https://redirect.unit.tests"
}],
"title": "Unsupported Record",
"keywords": "unsupported",
"description": "unsupported record",
"hardlinkFlag": false,
"redirectTypeId": 1,
"url": "https://redirect.unit.tests"
}, {
"id": 1808519,
"type": "A",
"recordType": "a",
"name": "www",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150079027,
"value": ["2.2.3.6"],
"roundRobin": [{
"value": "2.2.3.6",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"roundRobinFailover": [],
"pools": [],
"poolsDetail": []
}, {
"id": 1808603,
"type": "ANAME",
"recordType": "aname",
"name": "sub",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 1800,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565153387855,
"value": [{
"value": "aname.unit.tests.",
"disableFlag": false
}],
"roundRobin": [{
"value": "aname.unit.tests.",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"pools": [],
"poolsDetail": []
}, {
"id": 1808520,
"type": "A",
"recordType": "a",
"name": "www.sub",
"recordOption": "roundRobin",
"noAnswer": false,
"note": "",
"ttl": 300,
"gtdRegion": 1,
"parentId": 123123,
"parent": "domain",
"source": "Domain",
"modifiedTs": 1565150090588,
"value": ["2.2.3.6"],
"roundRobin": [{
"value": "2.2.3.6",
"disableFlag": false
}],
"geolocation": null,
"recordFailover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"failover": {
"disabled": false,
"failoverType": 1,
"failoverTypeStr": "Normal (always lowest level)",
"values": []
},
"roundRobinFailover": [],
"pools": [],
"poolsDetail": []
}]
+11
View File
@@ -1,5 +1,16 @@
{
"domain_records": [{
"id": null,
"type": "SOA",
"name": "@",
"data": null,
"priority": null,
"port": null,
"ttl": null,
"weight": null,
"flags": null,
"tag": null
}, {
"id": 11189874,
"type": "NS",
"name": "@",
+35
View File
@@ -0,0 +1,35 @@
{
"recordsets": [
{
"rdata": [
"",
"12 20 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"",
"1 1"
],
"type": "SSHFP",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"",
"100 \"U\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" ."
],
"type": "NAPTR",
"name": "naptr.unit.tests",
"ttl": 600
}
],
"metadata": {
"totalElements": 3,
"showAll": true
}
}
+166
View File
@@ -0,0 +1,166 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.other.tests.",
"12 20 30 foo-2.other.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.other.tests",
"ttl": 600
},
{
"rdata": [
"10 20 30 foo-1.other.tests.",
"12 20 30 foo-2.other.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.other.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:9471"
],
"type": "AAAA",
"name": "aaaa.old.other.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "old.other.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "old.other.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "other.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "old.other.tests",
"ttl": 3600
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "old.cname.other.tests",
"ttl": 300
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "excluded.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"other.tests."
],
"type": "CNAME",
"name": "included.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.other.tests.",
"20 smtp-2.other.tests.",
"30 smtp-3.other.tests.",
"40 smtp-1.other.tests."
],
"type": "MX",
"name": "mx.old.other.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" ."
],
"type": "NAPTR",
"name": "naptr.old.other.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.old.other.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.old.other.tests",
"ttl": 600
},
{
"rdata": [
"ns1.other.tests.",
"ns2.other.tests."
],
"type": "NS",
"name": "under.old.other.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.old.other.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.7"
],
"type": "A",
"name": "www.other.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.old.other.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}
+166
View File
@@ -0,0 +1,166 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:9471"
],
"type": "AAAA",
"name": "aaaa.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "old.unit.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests"
],
"type": "CNAME",
"name": "old.cname.unit.tests",
"ttl": 300
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "excluded.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "included.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.unit.tests.",
"20 smtp-2.unit.tests.",
"30 smtp-3.unit.tests.",
"40 smtp-1.unit.tests."
],
"type": "MX",
"name": "mx.old.unit.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" ."
],
"type": "NAPTR",
"name": "naptr.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.old.unit.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.unit.tests.",
"ns2.unit.tests."
],
"type": "NS",
"name": "under.old.unit.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.old.unit.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.7"
],
"type": "A",
"name": "www.unit.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.old.unit.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}
+157
View File
@@ -0,0 +1,157 @@
{
"recordsets": [
{
"rdata": [
"10 20 30 foo-1.unit.tests.",
"12 20 30 foo-2.unit.tests."
],
"type": "SRV",
"name": "_srv._tcp.unit.tests",
"ttl": 600
},
{
"rdata": [
"2601:644:500:e210:62f8:1dff:feb8:947a"
],
"type": "AAAA",
"name": "aaaa.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.akam.net.",
"ns2.akam.net.",
"ns3.akam.net.",
"ns4.akam.net."
],
"type": "NS",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1.2.3.4",
"1.2.3.5"
],
"type": "A",
"name": "unit.tests",
"ttl": 300
},
{
"rdata": [
"ns1.akam.net hostmaster.akamai.com 1489074932 86400 7200 604800 300"
],
"type": "SOA",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49",
"1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73"
],
"type": "SSHFP",
"name": "unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "cname.unit.tests",
"ttl": 300
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "excluded.unit.tests",
"ttl": 3600
},
{
"rdata": [
"unit.tests."
],
"type": "CNAME",
"name": "included.unit.tests",
"ttl": 3600
},
{
"rdata": [
"10 smtp-4.unit.tests.",
"20 smtp-2.unit.tests.",
"30 smtp-3.unit.tests.",
"40 smtp-1.unit.tests."
],
"type": "MX",
"name": "mx.unit.tests",
"ttl": 300
},
{
"rdata": [
"10 100 \"S\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" .",
"100 100 \"U\" \"SIP+D2U\" \"!^.*$!sip:[email protected]!\" ."
],
"type": "NAPTR",
"name": "naptr.unit.tests",
"ttl": 600
},
{
"rdata": [
"foo.bar.com."
],
"type": "PTR",
"name": "ptr.unit.tests",
"ttl": 300
},
{
"rdata": [
"\"v=spf1 ip4:192.168.0.1/16-all\""
],
"type": "SPF",
"name": "spf.unit.tests",
"ttl": 600
},
{
"rdata": [
"ns1.unit.tests.",
"ns2.unit.tests."
],
"type": "NS",
"name": "under.unit.tests",
"ttl": 3600
},
{
"rdata": [
"\"Bah bah black sheep\"",
"\"have you any wool.\"",
"\"v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs\""
],
"type": "TXT",
"name": "txt.unit.tests",
"ttl": 600
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.unit.tests",
"ttl": 300
},
{
"rdata": [
"2.2.3.6"
],
"type": "A",
"name": "www.sub.unit.tests",
"ttl": 300
}
],
"metadata": {
"totalElements": 16,
"showAll": true
}
}
+25
View File
@@ -0,0 +1,25 @@
@ 3600 NS 6.2.3.4.
@ 3600 NS 7.2.3.4.
@ 300 A 1.2.3.4
@ 300 A 1.2.3.5
@ 3600 SSHFP 1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73
@ 3600 SSHFP 1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49
@ 3600 CAA 0 issue ca.unit.tests
_srv._tcp 600 SRV 10 20 30 foo-1.unit.tests.
_srv._tcp 600 SRV 12 20 30 foo-2.unit.tests.
aaaa 600 AAAA 2601:644:500:e210:62f8:1dff:feb8:947a
cname 300 CNAME unit.tests.
excluded 300 CNAME unit.tests.
ignored 300 A 9.9.9.9
included 3600 CNAME unit.tests.
mx 300 MX 10 smtp-4.unit.tests.
mx 300 MX 20 smtp-2.unit.tests.
mx 300 MX 30 smtp-3.unit.tests.
mx 300 MX 40 smtp-1.unit.tests.
sub 3600 NS 6.2.3.4.
sub 3600 NS 7.2.3.4.
txt 600 TXT "Bah bah black sheep"
txt 600 TXT "have you any wool."
txt 600 TXT "v=DKIM1;k=rsa;s=email;h=sha256;p=A/kinda+of/long/string+with+numb3rs"
www 300 A 2.2.3.6
www.sub 300 A 2.2.3.6
+68
View File
@@ -0,0 +1,68 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from unittest import TestCase
from octodns.equality import EqualityTupleMixin
class TestEqualityTupleMixin(TestCase):
def test_basics(self):
class Simple(EqualityTupleMixin):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def _equality_tuple(self):
return (self.a, self.b)
one = Simple(1, 2, 3)
same = Simple(1, 2, 3)
matches = Simple(1, 2, 'ignored')
doesnt = Simple(2, 3, 4)
# equality
self.assertEquals(one, one)
self.assertEquals(one, same)
self.assertEquals(same, one)
# only a & c are considered
self.assertEquals(one, matches)
self.assertEquals(matches, one)
self.assertNotEquals(one, doesnt)
self.assertNotEquals(doesnt, one)
# lt
self.assertTrue(one < doesnt)
self.assertFalse(doesnt < one)
self.assertFalse(one < same)
# le
self.assertTrue(one <= doesnt)
self.assertFalse(doesnt <= one)
self.assertTrue(one <= same)
# gt
self.assertFalse(one > doesnt)
self.assertTrue(doesnt > one)
self.assertFalse(one > same)
# ge
self.assertFalse(one >= doesnt)
self.assertTrue(doesnt >= one)
self.assertTrue(one >= same)
def test_not_implemented(self):
class MissingMethod(EqualityTupleMixin):
pass
with self.assertRaises(NotImplementedError):
MissingMethod() == MissingMethod()
+45 -42
View File
@@ -7,10 +7,12 @@ from __future__ import absolute_import, division, print_function, \
from os import environ
from os.path import dirname, join
from six import text_type
from unittest import TestCase
from octodns.record import Record
from octodns.manager import _AggregateTarget, MainThreadExecutor, Manager
from octodns.manager import _AggregateTarget, MainThreadExecutor, Manager, \
ManagerException
from octodns.yaml import safe_load
from octodns.zone import Zone
@@ -27,80 +29,81 @@ def get_config_filename(which):
class TestManager(TestCase):
def test_missing_provider_class(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-provider-class.yaml')).sync()
self.assertTrue('missing class' in ctx.exception.message)
self.assertTrue('missing class' in text_type(ctx.exception))
def test_bad_provider_class(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-provider-class.yaml')).sync()
self.assertTrue('Unknown provider class' in ctx.exception.message)
self.assertTrue('Unknown provider class' in text_type(ctx.exception))
def test_bad_provider_class_module(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-provider-class-module.yaml')) \
.sync()
self.assertTrue('Unknown provider class' in ctx.exception.message)
self.assertTrue('Unknown provider class' in text_type(ctx.exception))
def test_bad_provider_class_no_module(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-provider-class-no-module.yaml')) \
.sync()
self.assertTrue('Unknown provider class' in ctx.exception.message)
self.assertTrue('Unknown provider class' in text_type(ctx.exception))
def test_missing_provider_config(self):
# Missing provider config
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-provider-config.yaml')).sync()
self.assertTrue('provider config' in ctx.exception.message)
self.assertTrue('provider config' in text_type(ctx.exception))
def test_missing_env_config(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-provider-env.yaml')).sync()
self.assertTrue('missing env var' in ctx.exception.message)
self.assertTrue('missing env var' in text_type(ctx.exception))
def test_missing_source(self):
with self.assertRaises(Exception) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['missing.sources.'])
self.assertTrue('missing sources' in ctx.exception.message)
self.assertTrue('missing sources' in text_type(ctx.exception))
def test_missing_targets(self):
with self.assertRaises(Exception) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['missing.targets.'])
self.assertTrue('missing targets' in ctx.exception.message)
self.assertTrue('missing targets' in text_type(ctx.exception))
def test_unknown_source(self):
with self.assertRaises(Exception) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['unknown.source.'])
self.assertTrue('unknown source' in ctx.exception.message)
self.assertTrue('unknown source' in text_type(ctx.exception))
def test_unknown_target(self):
with self.assertRaises(Exception) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['unknown.target.'])
self.assertTrue('unknown target' in ctx.exception.message)
self.assertTrue('unknown target' in text_type(ctx.exception))
def test_bad_plan_output_class(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
name = 'bad-plan-output-missing-class.yaml'
Manager(get_config_filename(name)).sync()
self.assertEquals('plan_output bad is missing class',
ctx.exception.message)
text_type(ctx.exception))
def test_bad_plan_output_config(self):
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('bad-plan-output-config.yaml')).sync()
self.assertEqual('Incorrect plan_output config for bad',
ctx.exception.message)
text_type(ctx.exception))
def test_source_only_as_a_target(self):
with self.assertRaises(Exception) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('provider-problems.yaml')) \
.sync(['not.targetable.'])
self.assertTrue('does not support targeting' in ctx.exception.message)
self.assertTrue('does not support targeting' in
text_type(ctx.exception))
def test_always_dry_run(self):
with TemporaryDirectory() as tmpdir:
@@ -180,9 +183,9 @@ class TestManager(TestCase):
'unit.tests.')
self.assertEquals(14, len(changes))
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
manager.compare(['nope'], ['dump'], 'unit.tests.')
self.assertEquals('Unknown source: nope', ctx.exception.message)
self.assertEquals('Unknown source: nope', text_type(ctx.exception))
def test_aggregate_target(self):
simple = SimpleProvider()
@@ -220,10 +223,10 @@ class TestManager(TestCase):
environ['YAML_TMP_DIR'] = tmpdir.dirname
manager = Manager(get_config_filename('simple.yaml'))
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
manager.dump('unit.tests.', tmpdir.dirname, False, False,
'nope')
self.assertEquals('Unknown source: nope', ctx.exception.message)
self.assertEquals('Unknown source: nope', text_type(ctx.exception))
manager.dump('unit.tests.', tmpdir.dirname, False, False, 'in')
@@ -249,10 +252,10 @@ class TestManager(TestCase):
environ['YAML_TMP_DIR'] = tmpdir.dirname
manager = Manager(get_config_filename('simple-split.yaml'))
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
manager.dump('unit.tests.', tmpdir.dirname, False, True,
'nope')
self.assertEquals('Unknown source: nope', ctx.exception.message)
self.assertEquals('Unknown source: nope', text_type(ctx.exception))
manager.dump('unit.tests.', tmpdir.dirname, False, True, 'in')
@@ -265,15 +268,15 @@ class TestManager(TestCase):
def test_validate_configs(self):
Manager(get_config_filename('simple-validate.yaml')).validate_configs()
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('missing-sources.yaml')) \
.validate_configs()
self.assertTrue('missing sources' in ctx.exception.message)
self.assertTrue('missing sources' in text_type(ctx.exception))
with self.assertRaises(Exception) as ctx:
with self.assertRaises(ManagerException) as ctx:
Manager(get_config_filename('unknown-provider.yaml')) \
.validate_configs()
self.assertTrue('unknown source' in ctx.exception.message)
self.assertTrue('unknown source' in text_type(ctx.exception))
class TestMainThreadExecutor(TestCase):
+2 -2
View File
@@ -5,8 +5,8 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from StringIO import StringIO
from logging import getLogger
from six import StringIO, text_type
from unittest import TestCase
from octodns.provider.plan import Plan, PlanHtml, PlanLogger, PlanMarkdown
@@ -59,7 +59,7 @@ class TestPlanLogger(TestCase):
with self.assertRaises(Exception) as ctx:
PlanLogger('invalid', 'not-a-level')
self.assertEquals('Unsupported level: not-a-level',
ctx.exception.message)
text_type(ctx.exception))
def test_create(self):
+17 -16
View File
@@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, \
unicode_literals
from logging import getLogger
from six import text_type
from unittest import TestCase
from octodns.record import Create, Delete, Record, Update
@@ -48,7 +49,7 @@ class TestBaseProvider(TestCase):
with self.assertRaises(NotImplementedError) as ctx:
BaseProvider('base')
self.assertEquals('Abstract base class, log property missing',
ctx.exception.message)
text_type(ctx.exception))
class HasLog(BaseProvider):
log = getLogger('HasLog')
@@ -56,7 +57,7 @@ class TestBaseProvider(TestCase):
with self.assertRaises(NotImplementedError) as ctx:
HasLog('haslog')
self.assertEquals('Abstract base class, SUPPORTS_GEO property missing',
ctx.exception.message)
text_type(ctx.exception))
class HasSupportsGeo(HasLog):
SUPPORTS_GEO = False
@@ -65,14 +66,14 @@ class TestBaseProvider(TestCase):
with self.assertRaises(NotImplementedError) as ctx:
HasSupportsGeo('hassupportsgeo').populate(zone)
self.assertEquals('Abstract base class, SUPPORTS property missing',
ctx.exception.message)
text_type(ctx.exception))
class HasSupports(HasSupportsGeo):
SUPPORTS = set(('A',))
with self.assertRaises(NotImplementedError) as ctx:
HasSupports('hassupports').populate(zone)
self.assertEquals('Abstract base class, populate method missing',
ctx.exception.message)
text_type(ctx.exception))
# SUPPORTS_DYNAMIC has a default/fallback
self.assertFalse(HasSupports('hassupports').SUPPORTS_DYNAMIC)
@@ -118,7 +119,7 @@ class TestBaseProvider(TestCase):
with self.assertRaises(NotImplementedError) as ctx:
HasPopulate('haspopulate').apply(plan)
self.assertEquals('Abstract base class, _apply method missing',
ctx.exception.message)
text_type(ctx.exception))
def test_plan(self):
ignored = Zone('unit.tests.', [])
@@ -193,7 +194,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -225,7 +226,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -238,7 +239,7 @@ class TestBaseProvider(TestCase):
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True).raise_if_unsafe()
self.assertTrue('Too many updates' in ctx.exception.message)
self.assertTrue('Too many updates' in text_type(ctx.exception))
def test_safe_updates_min_existing_pcent(self):
# MAX_SAFE_UPDATE_PCENT is safe when more
@@ -251,7 +252,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -273,7 +274,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -286,7 +287,7 @@ class TestBaseProvider(TestCase):
with self.assertRaises(UnsafePlan) as ctx:
Plan(zone, zone, changes, True).raise_if_unsafe()
self.assertTrue('Too many deletes' in ctx.exception.message)
self.assertTrue('Too many deletes' in text_type(ctx.exception))
def test_safe_deletes_min_existing_pcent(self):
# MAX_SAFE_DELETE_PCENT is safe when more
@@ -299,7 +300,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -322,7 +323,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -336,7 +337,7 @@ class TestBaseProvider(TestCase):
Plan(zone, zone, changes, True,
update_pcent_threshold=safe_pcent).raise_if_unsafe()
self.assertTrue('Too many updates' in ctx.exception.message)
self.assertTrue('Too many updates' in text_type(ctx.exception))
def test_safe_deletes_min_existing_override(self):
safe_pcent = .4
@@ -350,7 +351,7 @@ class TestBaseProvider(TestCase):
})
for i in range(int(Plan.MIN_EXISTING_RECORDS)):
zone.add_record(Record.new(zone, unicode(i), {
zone.add_record(Record.new(zone, text_type(i), {
'ttl': 60,
'type': 'A',
'value': '2.3.4.5'
@@ -364,4 +365,4 @@ class TestBaseProvider(TestCase):
Plan(zone, zone, changes, True,
delete_pcent_threshold=safe_pcent).raise_if_unsafe()
self.assertTrue('Too many deletes' in ctx.exception.message)
self.assertTrue('Too many deletes' in text_type(ctx.exception))
+22 -19
View File
@@ -9,6 +9,7 @@ from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record, Update
@@ -65,7 +66,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals('CloudflareError', type(ctx.exception).__name__)
self.assertEquals('request was invalid', ctx.exception.message)
self.assertEquals('request was invalid', text_type(ctx.exception))
# Bad auth
with requests_mock() as mock:
@@ -80,7 +81,7 @@ class TestCloudflareProvider(TestCase):
self.assertEquals('CloudflareAuthenticationError',
type(ctx.exception).__name__)
self.assertEquals('Unknown X-Auth-Key or X-Auth-Email',
ctx.exception.message)
text_type(ctx.exception))
# Bad auth, unknown resp
with requests_mock() as mock:
@@ -91,7 +92,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals('CloudflareAuthenticationError',
type(ctx.exception).__name__)
self.assertEquals('Cloudflare error', ctx.exception.message)
self.assertEquals('Cloudflare error', text_type(ctx.exception))
# General error
with requests_mock() as mock:
@@ -102,7 +103,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=200, json=self.empty)
@@ -110,7 +111,7 @@ class TestCloudflareProvider(TestCase):
provider.populate(zone)
self.assertEquals(set(), zone.records)
# re-populating the same non-existant zone uses cache and makes no
# re-populating the same non-existent zone uses cache and makes no
# calls
again = Zone('unit.tests.', [])
provider.populate(again)
@@ -173,7 +174,7 @@ class TestCloudflareProvider(TestCase):
}, # zone create
] + [None] * 20 # individual record creates
# non-existant zone, create everything
# non-existent zone, create everything
plan = provider.plan(self.expected)
self.assertEquals(12, len(plan.changes))
self.assertEquals(12, provider.apply(plan))
@@ -742,23 +743,25 @@ class TestCloudflareProvider(TestCase):
# the CDN.
self.assertEquals(3, len(zone.records))
record = list(zone.records)[0]
self.assertEquals('multi', record.name)
self.assertEquals('multi.unit.tests.', record.fqdn)
self.assertEquals('CNAME', record._type)
self.assertEquals('multi.unit.tests.cdn.cloudflare.net.', record.value)
ordered = sorted(zone.records, key=lambda r: r.name)
record = list(zone.records)[1]
record = ordered[0]
self.assertEquals('a', record.name)
self.assertEquals('a.unit.tests.', record.fqdn)
self.assertEquals('CNAME', record._type)
self.assertEquals('a.unit.tests.cdn.cloudflare.net.', record.value)
record = ordered[1]
self.assertEquals('cname', record.name)
self.assertEquals('cname.unit.tests.', record.fqdn)
self.assertEquals('CNAME', record._type)
self.assertEquals('cname.unit.tests.cdn.cloudflare.net.', record.value)
record = list(zone.records)[2]
self.assertEquals('a', record.name)
self.assertEquals('a.unit.tests.', record.fqdn)
record = ordered[2]
self.assertEquals('multi', record.name)
self.assertEquals('multi.unit.tests.', record.fqdn)
self.assertEquals('CNAME', record._type)
self.assertEquals('a.unit.tests.cdn.cloudflare.net.', record.value)
self.assertEquals('multi.unit.tests.cdn.cloudflare.net.', record.value)
# CDN enabled records can't be updated, we don't know the real values
# never point a Cloudflare record to itself.
@@ -950,7 +953,7 @@ class TestCloudflareProvider(TestCase):
'value': 'ns1.unit.tests.'
})
data = provider._gen_data(record).next()
data = next(provider._gen_data(record))
self.assertFalse('proxied' in data)
@@ -965,7 +968,7 @@ class TestCloudflareProvider(TestCase):
}), False
)
data = provider._gen_data(record).next()
data = next(provider._gen_data(record))
self.assertFalse(data['proxied'])
@@ -980,7 +983,7 @@ class TestCloudflareProvider(TestCase):
}), True
)
data = provider._gen_data(record).next()
data = next(provider._gen_data(record))
self.assertTrue(data['proxied'])
+233
View File
@@ -0,0 +1,233 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
from octodns.provider.constellix import ConstellixClientNotFound, \
ConstellixProvider
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
import json
class TestConstellixProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
# Our test suite differs a bit, add our NS and remove the simple one
expected.add_record(Record.new(expected, 'under', {
'ttl': 3600,
'type': 'NS',
'values': [
'ns1.unit.tests.',
'ns2.unit.tests.',
]
}))
# Add some ALIAS records
expected.add_record(Record.new(expected, '', {
'ttl': 1800,
'type': 'ALIAS',
'value': 'aname.unit.tests.'
}))
expected.add_record(Record.new(expected, 'sub', {
'ttl': 1800,
'type': 'ALIAS',
'value': 'aname.unit.tests.'
}))
for record in list(expected.records):
if record.name == 'sub' and record._type == 'NS':
expected._remove_record(record)
break
def test_populate(self):
provider = ConstellixProvider('test', 'api', 'secret')
# Bad auth
with requests_mock() as mock:
mock.get(ANY, status_code=401,
text='{"errors": ["Unable to authenticate token"]}')
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('Unauthorized', text_type(ctx.exception))
# Bad request
with requests_mock() as mock:
mock.get(ANY, status_code=400,
text='{"errors": ["\\"unittests\\" is not '
'a valid domain name"]}')
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('\n - "unittests" is not a valid domain name',
text_type(ctx.exception))
# General error
with requests_mock() as mock:
mock.get(ANY, status_code=502, text='Things caught fire')
with self.assertRaises(HTTPError) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='<html><head></head><body></body></html>')
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(set(), zone.records)
# No diffs == no changes
with requests_mock() as mock:
base = 'https://api.dns.constellix.com/v1/domains'
with open('tests/fixtures/constellix-domains.json') as fh:
mock.get('{}{}'.format(base, '/'), text=fh.read())
with open('tests/fixtures/constellix-records.json') as fh:
mock.get('{}{}'.format(base, '/123123/records'),
text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(15, len(zone.records))
changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', [])
provider.populate(again)
self.assertEquals(15, len(again.records))
# bust the cache
del provider._zone_records[zone.name]
def test_apply(self):
provider = ConstellixProvider('test', 'api', 'secret')
resp = Mock()
resp.json = Mock()
provider._client._request = Mock(return_value=resp)
with open('tests/fixtures/constellix-domains.json') as fh:
domains = json.load(fh)
# non-existent domain, create everything
resp.json.side_effect = [
ConstellixClientNotFound, # no zone in populate
ConstellixClientNotFound, # no domain during apply
domains
]
plan = provider.plan(self.expected)
# No root NS, no ignored, no excluded, no unsupported
n = len(self.expected.records) - 5
self.assertEquals(n, len(plan.changes))
self.assertEquals(n, provider.apply(plan))
provider._client._request.assert_has_calls([
# created the domain
call('POST', '/', data={'names': ['unit.tests']}),
# get all domains to build the cache
call('GET', '/'),
])
# These two checks are broken up so that ordering doesn't break things.
# Python3 doesn't make the calls in a consistent order so different
# things follow the GET / on different runs
provider._client._request.assert_has_calls([
call('POST', '/123123/records/SRV', data={
'roundRobin': [{
'priority': 10,
'weight': 20,
'value': 'foo-1.unit.tests.',
'port': 30
}, {
'priority': 12,
'weight': 20,
'value': 'foo-2.unit.tests.',
'port': 30
}],
'name': '_srv._tcp',
'ttl': 600,
}),
])
self.assertEquals(20, provider._client._request.call_count)
provider._client._request.reset_mock()
provider._client.records = Mock(return_value=[
{
'id': 11189897,
'type': 'A',
'name': 'www',
'ttl': 300,
'value': [
'1.2.3.4',
'2.2.3.4',
]
}, {
'id': 11189898,
'type': 'A',
'name': 'ttl',
'ttl': 600,
'value': [
'3.2.3.4'
]
}, {
'id': 11189899,
'type': 'ALIAS',
'name': 'alias',
'ttl': 600,
'value': [{
'value': 'aname.unit.tests.'
}]
}
])
# Domain exists, we don't care about return
resp.json.side_effect = ['{}']
wanted = Zone('unit.tests.', [])
wanted.add_record(Record.new(wanted, 'ttl', {
'ttl': 300,
'type': 'A',
'value': '3.2.3.4'
}))
plan = provider.plan(wanted)
self.assertEquals(3, len(plan.changes))
self.assertEquals(3, provider.apply(plan))
# recreate for update, and deletes for the 2 parts of the other
provider._client._request.assert_has_calls([
call('POST', '/123123/records/A', data={
'roundRobin': [{
'value': '3.2.3.4'
}],
'name': 'ttl',
'ttl': 300
}),
call('DELETE', '/123123/records/A/11189897'),
call('DELETE', '/123123/records/A/11189898'),
call('DELETE', '/123123/records/ANAME/11189899')
], any_order=True)
+18 -4
View File
@@ -10,6 +10,7 @@ from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
@@ -50,7 +51,7 @@ class TestDigitalOceanProvider(TestCase):
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('Unauthorized', ctx.exception.message)
self.assertEquals('Unauthorized', text_type(ctx.exception))
# General error
with requests_mock() as mock:
@@ -61,7 +62,7 @@ class TestDigitalOceanProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"id":"not_found","message":"The resource you '
@@ -153,7 +154,7 @@ class TestDigitalOceanProvider(TestCase):
}
}
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DigitalOceanClientNotFound, # no zone in populate
DigitalOceanClientNotFound, # no domain during apply
@@ -175,7 +176,20 @@ class TestDigitalOceanProvider(TestCase):
call('GET', '/domains/unit.tests/records', {'page': 1}),
# delete the initial A record
call('DELETE', '/domains/unit.tests/records/11189877'),
# created at least one of the record with expected data
# created at least some of the record with expected data
call('POST', '/domains/unit.tests/records', data={
'data': '1.2.3.4',
'name': '@',
'ttl': 300, 'type': 'A'}),
call('POST', '/domains/unit.tests/records', data={
'data': '1.2.3.5',
'name': '@',
'ttl': 300, 'type': 'A'}),
call('POST', '/domains/unit.tests/records', data={
'data': 'ca.unit.tests.',
'flags': 0, 'name': '@',
'tag': 'issue',
'ttl': 3600, 'type': 'CAA'}),
call('POST', '/domains/unit.tests/records', data={
'name': '_srv._tcp',
'weight': 20,
+30 -4
View File
@@ -9,6 +9,7 @@ from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
@@ -47,7 +48,7 @@ class TestDnsimpleProvider(TestCase):
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('Unauthorized', ctx.exception.message)
self.assertEquals('Unauthorized', text_type(ctx.exception))
# General error
with requests_mock() as mock:
@@ -58,7 +59,7 @@ class TestDnsimpleProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"message": "Domain `foo.bar` not found"}')
@@ -122,7 +123,7 @@ class TestDnsimpleProvider(TestCase):
resp.json = Mock()
provider._client._request = Mock(return_value=resp)
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DnsimpleClientNotFound, # no zone in populate
DnsimpleClientNotFound, # no domain during apply
@@ -138,7 +139,32 @@ class TestDnsimpleProvider(TestCase):
provider._client._request.assert_has_calls([
# created the domain
call('POST', '/domains', data={'name': 'unit.tests'}),
# created at least one of the record with expected data
# created at least some of the record with expected data
call('POST', '/zones/unit.tests/records', data={
'content': '1.2.3.4',
'type': 'A',
'name': '',
'ttl': 300}),
call('POST', '/zones/unit.tests/records', data={
'content': '1.2.3.5',
'type': 'A',
'name': '',
'ttl': 300}),
call('POST', '/zones/unit.tests/records', data={
'content': '0 issue "ca.unit.tests"',
'type': 'CAA',
'name': '',
'ttl': 3600}),
call('POST', '/zones/unit.tests/records', data={
'content': '1 1 7491973e5f8b39d5327cd4e08bc81b05f7710b49',
'type': 'SSHFP',
'name': '',
'ttl': 3600}),
call('POST', '/zones/unit.tests/records', data={
'content': '1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73',
'type': 'SSHFP',
'name': '',
'ttl': 3600}),
call('POST', '/zones/unit.tests/records', data={
'content': '20 30 foo-1.unit.tests.',
'priority': 10,
+26 -5
View File
@@ -10,6 +10,7 @@ from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
@@ -65,7 +66,7 @@ class TestDnsMadeEasyProvider(TestCase):
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('Unauthorized', ctx.exception.message)
self.assertEquals('Unauthorized', text_type(ctx.exception))
# Bad request
with requests_mock() as mock:
@@ -76,7 +77,7 @@ class TestDnsMadeEasyProvider(TestCase):
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals('\n - Rate limit exceeded',
ctx.exception.message)
text_type(ctx.exception))
# General error
with requests_mock() as mock:
@@ -87,7 +88,7 @@ class TestDnsMadeEasyProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='<html><head></head><body></body></html>')
@@ -130,7 +131,7 @@ class TestDnsMadeEasyProvider(TestCase):
with open('tests/fixtures/dnsmadeeasy-domains.json') as fh:
domains = json.load(fh)
# non-existant domain, create everything
# non-existent domain, create everything
resp.json.side_effect = [
DnsMadeEasyClientNotFound, # no zone in populate
DnsMadeEasyClientNotFound, # no domain during apply
@@ -148,7 +149,27 @@ class TestDnsMadeEasyProvider(TestCase):
call('POST', '/', data={'name': 'unit.tests'}),
# get all domains to build the cache
call('GET', '/'),
# created at least one of the record with expected data
# created at least some of the record with expected data
call('POST', '/123123/records', data={
'type': 'A',
'name': '',
'value': '1.2.3.4',
'ttl': 300}),
call('POST', '/123123/records', data={
'type': 'A',
'name': '',
'value': '1.2.3.5',
'ttl': 300}),
call('POST', '/123123/records', data={
'type': 'ANAME',
'name': '',
'value': 'aname.unit.tests.',
'ttl': 1800}),
call('POST', '/123123/records', data={
'name': '',
'value': 'ca.unit.tests',
'issuerCritical': 0, 'caaType': 'issue',
'ttl': 3600, 'type': 'CAA'}),
call('POST', '/123123/records', data={
'name': '_srv._tcp',
'weight': 20,
+2 -2
View File
@@ -670,8 +670,8 @@ class TestDynProviderGeo(TestCase):
tds = provider.traffic_directors
self.assertEquals(set(['unit.tests.', 'geo.unit.tests.']),
set(tds.keys()))
self.assertEquals(['A'], tds['unit.tests.'].keys())
self.assertEquals(['A'], tds['geo.unit.tests.'].keys())
self.assertEquals(['A'], list(tds['unit.tests.'].keys()))
self.assertEquals(['A'], list(tds['geo.unit.tests.'].keys()))
provider.log.warn.assert_called_with("Unsupported TrafficDirector "
"'%s'", 'something else')
+151
View File
@@ -0,0 +1,151 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
# from mock import Mock, call
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
from octodns.provider.fastdns import AkamaiProvider
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
class TestFastdnsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
# Our test suite differs a bit, add our NS and remove the simple one
expected.add_record(Record.new(expected, 'under', {
'ttl': 3600,
'type': 'NS',
'values': [
'ns1.unit.tests.',
'ns2.unit.tests.',
]
}))
for record in list(expected.records):
if record.name == 'sub' and record._type == 'NS':
expected._remove_record(record)
break
def test_populate(self):
provider = AkamaiProvider("test", "secret", "akam.com", "atok", "ctok")
# Bad Auth
with requests_mock() as mock:
mock.get(ANY, status_code=401, text='{"message": "Unauthorized"}')
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(401, ctx.exception.response.status_code)
# general error
with requests_mock() as mock:
mock.get(ANY, status_code=502, text='Things caught fire')
with self.assertRaises(HTTPError) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=404,
text='{"message": "Domain `foo.bar` not found"}')
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(set(), zone.records)
# No diffs == no changes
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records.json') as fh:
mock.get(ANY, text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(16, len(zone.records))
changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes))
# 2nd populate makes no network calls/all from cache
again = Zone('unit.tests.', [])
provider.populate(again)
self.assertEquals(16, len(again.records))
# bust the cache
del provider._zone_records[zone.name]
def test_apply(self):
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok",
"cid", "gid")
# tests create update delete through previous state config json
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev.json') as fh:
mock.get(ANY, text=fh.read())
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(29, changes)
# Test against a zone that doesn't exist yet
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev-other.json') as fh:
mock.get(ANY, status_code=404)
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(14, changes)
# Test against a zone that doesn't exist yet, but gid not provided
with requests_mock() as mock:
with open('tests/fixtures/fastdns-records-prev-other.json') as fh:
mock.get(ANY, status_code=404)
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok",
"cid")
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
changes = provider.apply(plan)
self.assertEquals(14, changes)
# Test against a zone that doesn't exist, but cid not provided
with requests_mock() as mock:
mock.get(ANY, status_code=404)
provider = AkamaiProvider("test", "s", "akam.com", "atok", "ctok")
plan = provider.plan(self.expected)
mock.post(ANY, status_code=201)
mock.put(ANY, status_code=200)
mock.delete(ANY, status_code=204)
try:
changes = provider.apply(plan)
except NameError as e:
expected = "contractId not specified to create zone"
self.assertEquals(text_type(e), expected)
+7 -2
View File
@@ -193,8 +193,13 @@ class DummyIterator:
def __iter__(self):
return self
# python2
def next(self):
return self.iterable.next()
return next(self.iterable)
# python3
def __next__(self):
return next(self.iterable)
class TestGoogleCloudProvider(TestCase):
@@ -247,7 +252,7 @@ class TestGoogleCloudProvider(TestCase):
return_values_for_status = iter(
["pending"] * 11 + ['done', 'done'])
type(status_mock).status = PropertyMock(
side_effect=return_values_for_status.next)
side_effect=lambda: next(return_values_for_status))
gcloud_zone_mock.changes = Mock(return_value=status_mock)
provider = self._get_provider()
+451
View File
@@ -0,0 +1,451 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from os.path import dirname, join
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.provider.mythicbeasts import MythicBeastsProvider, \
add_trailing_dot, remove_trailing_dot
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
from octodns.record import Create, Update, Delete, Record
class TestMythicBeastsProvider(TestCase):
expected = Zone('unit.tests.', [])
source = YamlProvider('test_expected', join(dirname(__file__), 'config'))
source.populate(expected)
# Dump anything we don't support from expected
for record in list(expected.records):
if record._type not in MythicBeastsProvider.SUPPORTS:
expected._remove_record(record)
def test_trailing_dot(self):
with self.assertRaises(AssertionError) as err:
add_trailing_dot('unit.tests.')
self.assertEquals('Value already has trailing dot',
text_type(err.exception))
with self.assertRaises(AssertionError) as err:
remove_trailing_dot('unit.tests')
self.assertEquals('Value already missing trailing dot',
text_type(err.exception))
self.assertEquals(add_trailing_dot('unit.tests'), 'unit.tests.')
self.assertEquals(remove_trailing_dot('unit.tests.'), 'unit.tests')
def test_data_for_single(self):
test_data = {
'raw_values': [{'value': 'a:a::c', 'ttl': 0}],
'zone': 'unit.tests.',
}
test_single = MythicBeastsProvider._data_for_single('', test_data)
self.assertTrue(isinstance(test_single, dict))
self.assertEquals('a:a::c', test_single['value'])
def test_data_for_multiple(self):
test_data = {
'raw_values': [
{'value': 'b:b::d', 'ttl': 60},
{'value': 'a:a::c', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_multiple = MythicBeastsProvider._data_for_multiple('', test_data)
self.assertTrue(isinstance(test_multiple, dict))
self.assertEquals(2, len(test_multiple['values']))
def test_data_for_txt(self):
test_data = {
'raw_values': [
{'value': 'v=DKIM1; k=rsa; p=prawf', 'ttl': 60},
{'value': 'prawf prawf dyma prawf', 'ttl': 300}],
'zone': 'unit.tests.',
}
test_txt = MythicBeastsProvider._data_for_TXT('', test_data)
self.assertTrue(isinstance(test_txt, dict))
self.assertEquals(2, len(test_txt['values']))
self.assertEquals('v=DKIM1\\; k=rsa\\; p=prawf', test_txt['values'][0])
def test_data_for_MX(self):
test_data = {
'raw_values': [
{'value': '10 un.unit', 'ttl': 60},
{'value': '20 dau.unit', 'ttl': 60},
{'value': '30 tri.unit', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_MX = MythicBeastsProvider._data_for_MX('', test_data)
self.assertTrue(isinstance(test_MX, dict))
self.assertEquals(3, len(test_MX['values']))
with self.assertRaises(AssertionError) as err:
test_MX = MythicBeastsProvider._data_for_MX(
'',
{'raw_values': [{'value': '', 'ttl': 0}]}
)
self.assertEquals('Unable to parse MX data',
text_type(err.exception))
def test_data_for_CNAME(self):
test_data = {
'raw_values': [{'value': 'cname', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_cname = MythicBeastsProvider._data_for_CNAME('', test_data)
self.assertTrue(isinstance(test_cname, dict))
self.assertEquals('cname.unit.tests.', test_cname['value'])
def test_data_for_ANAME(self):
test_data = {
'raw_values': [{'value': 'aname', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_aname = MythicBeastsProvider._data_for_ANAME('', test_data)
self.assertTrue(isinstance(test_aname, dict))
self.assertEquals('aname', test_aname['value'])
def test_data_for_SRV(self):
test_data = {
'raw_values': [
{'value': '10 20 30 un.srv.unit', 'ttl': 60},
{'value': '20 30 40 dau.srv.unit', 'ttl': 60},
{'value': '30 30 50 tri.srv.unit', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_SRV = MythicBeastsProvider._data_for_SRV('', test_data)
self.assertTrue(isinstance(test_SRV, dict))
self.assertEquals(3, len(test_SRV['values']))
with self.assertRaises(AssertionError) as err:
test_SRV = MythicBeastsProvider._data_for_SRV(
'',
{'raw_values': [{'value': '', 'ttl': 0}]}
)
self.assertEquals('Unable to parse SRV data',
text_type(err.exception))
def test_data_for_SSHFP(self):
test_data = {
'raw_values': [
{'value': '1 1 0123456789abcdef', 'ttl': 60},
{'value': '1 2 0123456789abcdef', 'ttl': 60},
{'value': '2 3 0123456789abcdef', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_SSHFP = MythicBeastsProvider._data_for_SSHFP('', test_data)
self.assertTrue(isinstance(test_SSHFP, dict))
self.assertEquals(3, len(test_SSHFP['values']))
with self.assertRaises(AssertionError) as err:
test_SSHFP = MythicBeastsProvider._data_for_SSHFP(
'',
{'raw_values': [{'value': '', 'ttl': 0}]}
)
self.assertEquals('Unable to parse SSHFP data',
text_type(err.exception))
def test_data_for_CAA(self):
test_data = {
'raw_values': [{'value': '1 issue letsencrypt.org', 'ttl': 60}],
'zone': 'unit.tests.',
}
test_CAA = MythicBeastsProvider._data_for_CAA('', test_data)
self.assertTrue(isinstance(test_CAA, dict))
self.assertEquals(3, len(test_CAA['value']))
with self.assertRaises(AssertionError) as err:
test_CAA = MythicBeastsProvider._data_for_CAA(
'',
{'raw_values': [{'value': '', 'ttl': 0}]}
)
self.assertEquals('Unable to parse CAA data',
text_type(err.exception))
def test_command_generation(self):
zone = Zone('unit.tests.', [])
zone.add_record(Record.new(zone, 'prawf-alias', {
'ttl': 60,
'type': 'ALIAS',
'value': 'alias.unit.tests.',
}))
zone.add_record(Record.new(zone, 'prawf-ns', {
'ttl': 300,
'type': 'NS',
'values': [
'alias.unit.tests.',
'alias2.unit.tests.',
],
}))
zone.add_record(Record.new(zone, 'prawf-a', {
'ttl': 60,
'type': 'A',
'values': [
'1.2.3.4',
'5.6.7.8',
],
}))
zone.add_record(Record.new(zone, 'prawf-aaaa', {
'ttl': 60,
'type': 'AAAA',
'values': [
'a:a::a',
'b:b::b',
'c:c::c:c',
],
}))
zone.add_record(Record.new(zone, 'prawf-txt', {
'ttl': 60,
'type': 'TXT',
'value': 'prawf prawf dyma prawf',
}))
zone.add_record(Record.new(zone, 'prawf-txt2', {
'ttl': 60,
'type': 'TXT',
'value': 'v=DKIM1\\; k=rsa\\; p=prawf',
}))
with requests_mock() as mock:
mock.post(ANY, status_code=200, text='')
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
plan = provider.plan(zone)
changes = plan.changes
generated_commands = []
for change in changes:
generated_commands.extend(
provider._compile_commands('ADD', change.new)
)
expected_commands = [
'ADD prawf-alias.unit.tests 60 ANAME alias.unit.tests.',
'ADD prawf-ns.unit.tests 300 NS alias.unit.tests.',
'ADD prawf-ns.unit.tests 300 NS alias2.unit.tests.',
'ADD prawf-a.unit.tests 60 A 1.2.3.4',
'ADD prawf-a.unit.tests 60 A 5.6.7.8',
'ADD prawf-aaaa.unit.tests 60 AAAA a:a::a',
'ADD prawf-aaaa.unit.tests 60 AAAA b:b::b',
'ADD prawf-aaaa.unit.tests 60 AAAA c:c::c:c',
'ADD prawf-txt.unit.tests 60 TXT prawf prawf dyma prawf',
'ADD prawf-txt2.unit.tests 60 TXT v=DKIM1; k=rsa; p=prawf',
]
generated_commands.sort()
expected_commands.sort()
self.assertEquals(
generated_commands,
expected_commands
)
# Now test deletion
existing = 'prawf-txt 300 TXT prawf prawf dyma prawf\n' \
'prawf-txt2 300 TXT v=DKIM1; k=rsa; p=prawf\n' \
'prawf-a 60 A 1.2.3.4'
with requests_mock() as mock:
mock.post(ANY, status_code=200, text=existing)
wanted = Zone('unit.tests.', [])
plan = provider.plan(wanted)
changes = plan.changes
generated_commands = []
for change in changes:
generated_commands.extend(
provider._compile_commands('DELETE', change.existing)
)
expected_commands = [
'DELETE prawf-a.unit.tests 60 A 1.2.3.4',
'DELETE prawf-txt.unit.tests 300 TXT prawf prawf dyma prawf',
'DELETE prawf-txt2.unit.tests 300 TXT v=DKIM1; k=rsa; p=prawf',
]
generated_commands.sort()
expected_commands.sort()
self.assertEquals(
generated_commands,
expected_commands
)
def test_fake_command_generation(self):
class FakeChangeRecord(object):
def __init__(self):
self.__fqdn = 'prawf.unit.tests.'
self._type = 'NOOP'
self.value = 'prawf'
self.ttl = 60
@property
def record(self):
return self
@property
def fqdn(self):
return self.__fqdn
with requests_mock() as mock:
mock.post(ANY, status_code=200, text='')
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
record = FakeChangeRecord()
command = provider._compile_commands('ADD', record)
self.assertEquals([], command)
def test_populate(self):
provider = None
# Null passwords dict
with self.assertRaises(AssertionError) as err:
provider = MythicBeastsProvider('test', None)
self.assertEquals('Passwords must be a dictionary',
text_type(err.exception))
# Missing password
with requests_mock() as mock:
mock.post(ANY, status_code=401, text='ERR Not authenticated')
with self.assertRaises(AssertionError) as err:
provider = MythicBeastsProvider('test', dict())
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(
'Missing password for domain: unit.tests',
text_type(err.exception))
# Failed authentication
with requests_mock() as mock:
mock.post(ANY, status_code=401, text='ERR Not authenticated')
with self.assertRaises(Exception) as err:
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(
'Mythic Beasts unauthorized for zone: unit.tests',
err.exception.message)
# Check unmatched lines are ignored
test_data = 'This should not match'
with requests_mock() as mock:
mock.post(ANY, status_code=200, text=test_data)
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(0, len(zone.records))
# Check unsupported records are skipped
test_data = '@ 60 NOOP prawf\n@ 60 SPF prawf prawf prawf'
with requests_mock() as mock:
mock.post(ANY, status_code=200, text=test_data)
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(0, len(zone.records))
# Check no changes between what we support and what's parsed
# from the unit.tests. config YAML. Also make sure we see the same
# for both after we've thrown away records we don't support
with requests_mock() as mock:
with open('tests/fixtures/mythicbeasts-list.txt') as file_handle:
mock.post(ANY, status_code=200, text=file_handle.read())
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(15, len(zone.records))
self.assertEquals(15, len(self.expected.records))
changes = self.expected.changes(zone, provider)
self.assertEquals(0, len(changes))
def test_apply(self):
provider = MythicBeastsProvider('test', {
'unit.tests.': 'mypassword'
})
zone = Zone('unit.tests.', [])
# Create blank zone
with requests_mock() as mock:
mock.post(ANY, status_code=200, text='')
provider.populate(zone)
self.assertEquals(0, len(zone.records))
# Record change failed
with requests_mock() as mock:
mock.post(ANY, status_code=200, text='')
provider.populate(zone)
zone.add_record(Record.new(zone, 'prawf', {
'ttl': 300,
'type': 'TXT',
'value': 'prawf',
}))
plan = provider.plan(zone)
with requests_mock() as mock:
mock.post(ANY, status_code=400, text='NADD 300 TXT prawf')
with self.assertRaises(Exception) as err:
provider.apply(plan)
self.assertEquals(
'Mythic Beasts could not action command: unit.tests '
'ADD prawf.unit.tests 300 TXT prawf', err.exception.message)
# Check deleting and adding/changing test record
existing = 'prawf 300 TXT prawf prawf prawf\ndileu 300 TXT dileu'
with requests_mock() as mock:
mock.post(ANY, status_code=200, text=existing)
# Mash up a new zone with records so a plan
# is generated with changes and applied. For some reason
# passing self.expected, or just changing each record's zone
# doesn't work. Nor does this without a single add_record after
wanted = Zone('unit.tests.', [])
for record in list(self.expected.records):
data = {'type': record._type}
data.update(record.data)
wanted.add_record(Record.new(wanted, record.name, data))
wanted.add_record(Record.new(wanted, 'prawf', {
'ttl': 60,
'type': 'TXT',
'value': 'prawf yw e',
}))
plan = provider.plan(wanted)
# Octo ignores NS records (15-1)
self.assertEquals(1, len([c for c in plan.changes
if isinstance(c, Update)]))
self.assertEquals(1, len([c for c in plan.changes
if isinstance(c, Delete)]))
self.assertEquals(14, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertEquals(16, provider.apply(plan))
self.assertTrue(plan.exists)
+202 -150
View File
@@ -5,24 +5,18 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from mock import Mock, call, patch
from nsone.rest.errors import AuthException, RateLimitException, \
from collections import defaultdict
from mock import call, patch
from ns1.rest.errors import AuthException, RateLimitException, \
ResourceException
from six import text_type
from unittest import TestCase
from octodns.record import Delete, Record, Update
from octodns.provider.ns1 import Ns1Provider
from octodns.provider.ns1 import Ns1Client, Ns1Provider
from octodns.zone import Zone
class DummyZone(object):
def __init__(self, records):
self.data = {
'records': records
}
class TestNs1Provider(TestCase):
zone = Zone('unit.tests.', [])
expected = set()
@@ -115,7 +109,7 @@ class TestNs1Provider(TestCase):
},
}))
nsone_records = [{
ns1_records = [{
'type': 'A',
'ttl': 32,
'short_answers': ['1.2.3.4'],
@@ -171,43 +165,40 @@ class TestNs1Provider(TestCase):
'domain': 'unit.tests.',
}]
@patch('nsone.NSONE.loadZone')
def test_populate(self, load_mock):
@patch('ns1.rest.zones.Zones.retrieve')
def test_populate(self, zone_retrieve_mock):
provider = Ns1Provider('test', 'api-key')
# Bad auth
load_mock.side_effect = AuthException('unauthorized')
zone_retrieve_mock.side_effect = AuthException('unauthorized')
zone = Zone('unit.tests.', [])
with self.assertRaises(AuthException) as ctx:
provider.populate(zone)
self.assertEquals(load_mock.side_effect, ctx.exception)
self.assertEquals(zone_retrieve_mock.side_effect, ctx.exception)
# General error
load_mock.reset_mock()
load_mock.side_effect = ResourceException('boom')
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = ResourceException('boom')
zone = Zone('unit.tests.', [])
with self.assertRaises(ResourceException) as ctx:
provider.populate(zone)
self.assertEquals(load_mock.side_effect, ctx.exception)
self.assertEquals(('unit.tests',), load_mock.call_args[0])
self.assertEquals(zone_retrieve_mock.side_effect, ctx.exception)
self.assertEquals(('unit.tests',), zone_retrieve_mock.call_args[0])
# Non-existant zone doesn't populate anything
load_mock.reset_mock()
load_mock.side_effect = \
# Non-existent zone doesn't populate anything
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = \
ResourceException('server error: zone not found')
zone = Zone('unit.tests.', [])
exists = provider.populate(zone)
self.assertEquals(set(), zone.records)
self.assertEquals(('unit.tests',), load_mock.call_args[0])
self.assertEquals(('unit.tests',), zone_retrieve_mock.call_args[0])
self.assertFalse(exists)
# Existing zone w/o records
load_mock.reset_mock()
nsone_zone = DummyZone([])
load_mock.side_effect = [nsone_zone]
zone_search = Mock()
zone_search.return_value = [
{
zone_retrieve_mock.reset_mock()
ns1_zone = {
'records': [{
"domain": "geo.unit.tests",
"zone": "unit.tests",
"type": "A",
@@ -221,21 +212,18 @@ class TestNs1Provider(TestCase):
'meta': {'iso_region_code': ['NA-US-WA']}},
],
'ttl': 34,
},
]
nsone_zone.search = zone_search
}],
}
zone_retrieve_mock.side_effect = [ns1_zone]
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(1, len(zone.records))
self.assertEquals(('unit.tests',), load_mock.call_args[0])
self.assertEquals(('unit.tests',), zone_retrieve_mock.call_args[0])
# Existing zone w/records
load_mock.reset_mock()
nsone_zone = DummyZone(self.nsone_records)
load_mock.side_effect = [nsone_zone]
zone_search = Mock()
zone_search.return_value = [
{
zone_retrieve_mock.reset_mock()
ns1_zone = {
'records': self.ns1_records + [{
"domain": "geo.unit.tests",
"zone": "unit.tests",
"type": "A",
@@ -249,26 +237,23 @@ class TestNs1Provider(TestCase):
'meta': {'iso_region_code': ['NA-US-WA']}},
],
'ttl': 34,
},
]
nsone_zone.search = zone_search
}],
}
zone_retrieve_mock.side_effect = [ns1_zone]
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(self.expected, zone.records)
self.assertEquals(('unit.tests',), load_mock.call_args[0])
self.assertEquals(('unit.tests',), zone_retrieve_mock.call_args[0])
# Test skipping unsupported record type
load_mock.reset_mock()
nsone_zone = DummyZone(self.nsone_records + [{
'type': 'UNSUPPORTED',
'ttl': 42,
'short_answers': ['unsupported'],
'domain': 'unsupported.unit.tests.',
}])
load_mock.side_effect = [nsone_zone]
zone_search = Mock()
zone_search.return_value = [
{
zone_retrieve_mock.reset_mock()
ns1_zone = {
'records': self.ns1_records + [{
'type': 'UNSUPPORTED',
'ttl': 42,
'short_answers': ['unsupported'],
'domain': 'unsupported.unit.tests.',
}, {
"domain": "geo.unit.tests",
"zone": "unit.tests",
"type": "A",
@@ -282,17 +267,23 @@ class TestNs1Provider(TestCase):
'meta': {'iso_region_code': ['NA-US-WA']}},
],
'ttl': 34,
},
]
nsone_zone.search = zone_search
}],
}
zone_retrieve_mock.side_effect = [ns1_zone]
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertEquals(self.expected, zone.records)
self.assertEquals(('unit.tests',), load_mock.call_args[0])
self.assertEquals(('unit.tests',), zone_retrieve_mock.call_args[0])
@patch('nsone.NSONE.createZone')
@patch('nsone.NSONE.loadZone')
def test_sync(self, load_mock, create_mock):
@patch('ns1.rest.records.Records.delete')
@patch('ns1.rest.records.Records.update')
@patch('ns1.rest.records.Records.create')
@patch('ns1.rest.records.Records.retrieve')
@patch('ns1.rest.zones.Zones.create')
@patch('ns1.rest.zones.Zones.retrieve')
def test_sync(self, zone_retrieve_mock, zone_create_mock,
record_retrieve_mock, record_create_mock,
record_update_mock, record_delete_mock):
provider = Ns1Provider('test', 'api-key')
desired = Zone('unit.tests.', [])
@@ -306,124 +297,142 @@ class TestNs1Provider(TestCase):
self.assertTrue(plan.exists)
# Fails, general error
load_mock.reset_mock()
create_mock.reset_mock()
load_mock.side_effect = ResourceException('boom')
zone_retrieve_mock.reset_mock()
zone_create_mock.reset_mock()
zone_retrieve_mock.side_effect = ResourceException('boom')
with self.assertRaises(ResourceException) as ctx:
provider.apply(plan)
self.assertEquals(load_mock.side_effect, ctx.exception)
self.assertEquals(zone_retrieve_mock.side_effect, ctx.exception)
# Fails, bad auth
load_mock.reset_mock()
create_mock.reset_mock()
load_mock.side_effect = \
zone_retrieve_mock.reset_mock()
zone_create_mock.reset_mock()
zone_retrieve_mock.side_effect = \
ResourceException('server error: zone not found')
create_mock.side_effect = AuthException('unauthorized')
zone_create_mock.side_effect = AuthException('unauthorized')
with self.assertRaises(AuthException) as ctx:
provider.apply(plan)
self.assertEquals(create_mock.side_effect, ctx.exception)
self.assertEquals(zone_create_mock.side_effect, ctx.exception)
# non-existant zone, create
load_mock.reset_mock()
create_mock.reset_mock()
load_mock.side_effect = \
# non-existent zone, create
zone_retrieve_mock.reset_mock()
zone_create_mock.reset_mock()
zone_retrieve_mock.side_effect = \
ResourceException('server error: zone not found')
# ugh, need a mock zone with a mock prop since we're using getattr, we
# can actually control side effects on `meth` with that.
mock_zone = Mock()
mock_zone.add_SRV = Mock()
mock_zone.add_SRV.side_effect = [
zone_create_mock.side_effect = ['foo']
# Test out the create rate-limit handling, then 9 successes
record_create_mock.side_effect = [
RateLimitException('boo', period=0),
None,
]
create_mock.side_effect = [mock_zone]
] + ([None] * 9)
got_n = provider.apply(plan)
self.assertEquals(expected_n, got_n)
# Zone was created
zone_create_mock.assert_has_calls([call('unit.tests')])
# Checking that we got some of the expected records too
record_create_mock.assert_has_calls([
call('unit.tests', 'unit.tests', 'A', answers=[
{'answer': ['1.2.3.4'], 'meta': {}}
], filters=[], ttl=32),
call('unit.tests', 'unit.tests', 'CAA', answers=[
(0, 'issue', 'ca.unit.tests')
], ttl=40),
call('unit.tests', 'unit.tests', 'MX', answers=[
(10, 'mx1.unit.tests.'), (20, 'mx2.unit.tests.')
], ttl=35),
])
# Update & delete
load_mock.reset_mock()
create_mock.reset_mock()
nsone_zone = DummyZone(self.nsone_records + [{
'type': 'A',
'ttl': 42,
'short_answers': ['9.9.9.9'],
'domain': 'delete-me.unit.tests.',
}])
nsone_zone.data['records'][0]['short_answers'][0] = '2.2.2.2'
nsone_zone.loadRecord = Mock()
zone_search = Mock()
zone_search.return_value = [
{
zone_retrieve_mock.reset_mock()
zone_create_mock.reset_mock()
ns1_zone = {
'records': self.ns1_records + [{
'type': 'A',
'ttl': 42,
'short_answers': ['9.9.9.9'],
'domain': 'delete-me.unit.tests.',
}, {
"domain": "geo.unit.tests",
"zone": "unit.tests",
"type": "A",
"answers": [
{'answer': ['1.1.1.1'], 'meta': {}},
{'answer': ['1.2.3.4'],
'meta': {'ca_province': ['ON']}},
{'answer': ['2.3.4.5'], 'meta': {'us_state': ['NY']}},
{'answer': ['3.4.5.6'], 'meta': {'country': ['US']}},
{'answer': ['4.5.6.7'],
'meta': {'iso_region_code': ['NA-US-WA']}},
"short_answers": [
'1.1.1.1',
'1.2.3.4',
'2.3.4.5',
'3.4.5.6',
'4.5.6.7',
],
'tier': 3, # This flags it as advacned, full load required
'ttl': 34,
},
]
nsone_zone.search = zone_search
load_mock.side_effect = [nsone_zone, nsone_zone]
}],
}
ns1_zone['records'][0]['short_answers'][0] = '2.2.2.2'
record_retrieve_mock.side_effect = [{
"domain": "geo.unit.tests",
"zone": "unit.tests",
"type": "A",
"answers": [
{'answer': ['1.1.1.1'], 'meta': {}},
{'answer': ['1.2.3.4'],
'meta': {'ca_province': ['ON']}},
{'answer': ['2.3.4.5'], 'meta': {'us_state': ['NY']}},
{'answer': ['3.4.5.6'], 'meta': {'country': ['US']}},
{'answer': ['4.5.6.7'],
'meta': {'iso_region_code': ['NA-US-WA']}},
],
'tier': 3,
'ttl': 34,
}]
zone_retrieve_mock.side_effect = [ns1_zone, ns1_zone]
plan = provider.plan(desired)
self.assertEquals(3, len(plan.changes))
self.assertIsInstance(plan.changes[0], Update)
self.assertIsInstance(plan.changes[2], Delete)
# ugh, we need a mock record that can be returned from loadRecord for
# the update and delete targets, we can add our side effects to that to
# trigger rate limit handling
mock_record = Mock()
mock_record.update.side_effect = [
# Shouldn't rely on order so just count classes
classes = defaultdict(lambda: 0)
for change in plan.changes:
classes[change.__class__] += 1
self.assertEquals(1, classes[Delete])
self.assertEquals(2, classes[Update])
record_update_mock.side_effect = [
RateLimitException('one', period=0),
None,
None,
]
mock_record.delete.side_effect = [
record_delete_mock.side_effect = [
RateLimitException('two', period=0),
None,
None,
]
nsone_zone.loadRecord.side_effect = [mock_record, mock_record,
mock_record]
got_n = provider.apply(plan)
self.assertEquals(3, got_n)
nsone_zone.loadRecord.assert_has_calls([
call('unit.tests', u'A'),
call('geo', u'A'),
call('delete-me', u'A'),
])
mock_record.assert_has_calls([
call.update(answers=[{'answer': [u'1.2.3.4'], 'meta': {}}],
filters=[],
ttl=32),
call.update(answers=[{u'answer': [u'1.2.3.4'], u'meta': {}}],
filters=[],
ttl=32),
call.update(
answers=[
{u'answer': [u'101.102.103.104'], u'meta': {}},
{u'answer': [u'101.102.103.105'], u'meta': {}},
{
u'answer': [u'201.202.203.204'],
u'meta': {
u'iso_region_code': [u'NA-US-NY']
},
},
],
record_update_mock.assert_has_calls([
call('unit.tests', 'unit.tests', 'A', answers=[
{'answer': ['1.2.3.4'], 'meta': {}}],
filters=[],
ttl=32),
call('unit.tests', 'unit.tests', 'A', answers=[
{'answer': ['1.2.3.4'], 'meta': {}}],
filters=[],
ttl=32),
call('unit.tests', 'geo.unit.tests', 'A', answers=[
{'answer': ['101.102.103.104'], 'meta': {}},
{'answer': ['101.102.103.105'], 'meta': {}},
{
'answer': ['201.202.203.204'],
'meta': {'iso_region_code': ['NA-US-NY']}
}],
filters=[
{u'filter': u'shuffle', u'config': {}},
{u'filter': u'geotarget_country', u'config': {}},
{u'filter': u'select_first_n', u'config': {u'N': 1}},
],
ttl=34),
call.delete(),
call.delete()
{'filter': 'shuffle', 'config': {}},
{'filter': 'geotarget_country', 'config': {}},
{'filter': 'select_first_n', 'config': {'N': 1}}],
ttl=34)
])
def test_escaping(self):
@@ -462,7 +471,7 @@ class TestNs1Provider(TestCase):
def test_data_for_CNAME(self):
provider = Ns1Provider('test', 'api-key')
# answers from nsone
# answers from ns1
a_record = {
'ttl': 31,
'type': 'CNAME',
@@ -476,7 +485,7 @@ class TestNs1Provider(TestCase):
self.assertEqual(a_expected,
provider._data_for_CNAME(a_record['type'], a_record))
# no answers from nsone
# no answers from ns1
b_record = {
'ttl': 32,
'type': 'CNAME',
@@ -489,3 +498,46 @@ class TestNs1Provider(TestCase):
}
self.assertEqual(b_expected,
provider._data_for_CNAME(b_record['type'], b_record))
class TestNs1Client(TestCase):
@patch('ns1.rest.zones.Zones.retrieve')
def test_retry_behavior(self, zone_retrieve_mock):
client = Ns1Client('dummy-key')
# No retry required, just calls and is returned
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = ['foo']
self.assertEquals('foo', client.zones_retrieve('unit.tests'))
zone_retrieve_mock.assert_has_calls([call('unit.tests')])
# One retry required
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = [
RateLimitException('boo', period=0),
'foo'
]
self.assertEquals('foo', client.zones_retrieve('unit.tests'))
zone_retrieve_mock.assert_has_calls([call('unit.tests')])
# Two retries required
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = [
RateLimitException('boo', period=0),
'foo'
]
self.assertEquals('foo', client.zones_retrieve('unit.tests'))
zone_retrieve_mock.assert_has_calls([call('unit.tests')])
# Exhaust our retries
zone_retrieve_mock.reset_mock()
zone_retrieve_mock.side_effect = [
RateLimitException('first', period=0),
RateLimitException('boo', period=0),
RateLimitException('boo', period=0),
RateLimitException('last', period=0),
]
with self.assertRaises(RateLimitException) as ctx:
client.zones_retrieve('unit.tests')
self.assertEquals('last', text_type(ctx.exception))
+47 -48
View File
@@ -382,64 +382,63 @@ class TestOvhProvider(TestCase):
get_mock.side_effect = [[100], [101], [102], [103]]
provider.apply(plan)
wanted_calls = [
call(u'/domain/zone/unit.tests/record', fieldType=u'TXT',
subDomain='txt', target=u'TXT text', ttl=1400),
call(u'/domain/zone/unit.tests/record', fieldType=u'DKIM',
subDomain='dkim', target=self.valid_dkim_key,
ttl=1300),
call(u'/domain/zone/unit.tests/record', fieldType=u'A',
subDomain=u'', target=u'1.2.3.4', ttl=100),
call(u'/domain/zone/unit.tests/record', fieldType=u'SRV',
call('/domain/zone/unit.tests/record', fieldType='A',
subDomain='', target='1.2.3.4', ttl=100),
call('/domain/zone/unit.tests/record', fieldType='AAAA',
subDomain='', target='1:1ec:1::1', ttl=200),
call('/domain/zone/unit.tests/record', fieldType='MX',
subDomain='', target='10 mx1.unit.tests.', ttl=400),
call('/domain/zone/unit.tests/record', fieldType='SPF',
subDomain='',
target='v=spf1 include:unit.texts.redirect ~all',
ttl=1000),
call('/domain/zone/unit.tests/record', fieldType='SSHFP',
subDomain='',
target='1 1 bf6b6825d2977c511a475bbefb88aad54a92ac73',
ttl=1100),
call('/domain/zone/unit.tests/record', fieldType='PTR',
subDomain='4', target='unit.tests.', ttl=900),
call('/domain/zone/unit.tests/record', fieldType='SRV',
subDomain='_srv._tcp',
target=u'10 20 30 foo-1.unit.tests.', ttl=800),
call(u'/domain/zone/unit.tests/record', fieldType=u'SRV',
target='10 20 30 foo-1.unit.tests.', ttl=800),
call('/domain/zone/unit.tests/record', fieldType='SRV',
subDomain='_srv._tcp',
target=u'40 50 60 foo-2.unit.tests.', ttl=800),
call(u'/domain/zone/unit.tests/record', fieldType=u'PTR',
subDomain='4', target=u'unit.tests.', ttl=900),
call(u'/domain/zone/unit.tests/record', fieldType=u'NS',
subDomain='www3', target=u'ns3.unit.tests.', ttl=700),
call(u'/domain/zone/unit.tests/record', fieldType=u'NS',
subDomain='www3', target=u'ns4.unit.tests.', ttl=700),
call(u'/domain/zone/unit.tests/record',
fieldType=u'SSHFP', subDomain=u'', ttl=1100,
target=u'1 1 bf6b6825d2977c511a475bbefb88a'
u'ad54'
u'a92ac73',
),
call(u'/domain/zone/unit.tests/record', fieldType=u'AAAA',
subDomain=u'', target=u'1:1ec:1::1', ttl=200),
call(u'/domain/zone/unit.tests/record', fieldType=u'MX',
subDomain=u'', target=u'10 mx1.unit.tests.', ttl=400),
call(u'/domain/zone/unit.tests/record', fieldType=u'CNAME',
subDomain='www2', target=u'unit.tests.', ttl=300),
call(u'/domain/zone/unit.tests/record', fieldType=u'SPF',
subDomain=u'', ttl=1000,
target=u'v=spf1 include:unit.texts.'
u'redirect ~all',
),
call(u'/domain/zone/unit.tests/record', fieldType=u'A',
subDomain='sub', target=u'1.2.3.4', ttl=200),
call(u'/domain/zone/unit.tests/record', fieldType=u'NAPTR',
subDomain='naptr', ttl=500,
target=u'10 100 "S" "SIP+D2U" "!^.*$!sip:'
u'info@bar'
u'.example.com!" .'
),
call(u'/domain/zone/unit.tests/refresh')]
target='40 50 60 foo-2.unit.tests.', ttl=800),
call('/domain/zone/unit.tests/record', fieldType='DKIM',
subDomain='dkim',
target='p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxLaG'
'16G4SaEcXVdiIxTg7gKSGbHKQLm30CHib1h9FzS9nkcyvQSyQj1r'
'MFyqC//tft3ohx3nvJl+bGCWxdtLYDSmir9PW54e5CTdxEh8MWRk'
'BO3StF6QG/tAh3aTGDmkqhIJGLb87iHvpmVKqURmEUzJPv5KPJfW'
'LofADI+q9lQIDAQAB', ttl=1300),
call('/domain/zone/unit.tests/record', fieldType='NAPTR',
subDomain='naptr',
target='10 100 "S" "SIP+D2U" "!^.*$!sip:[email protected]'
'ple.com!" .', ttl=500),
call('/domain/zone/unit.tests/record', fieldType='A',
subDomain='sub', target='1.2.3.4', ttl=200),
call('/domain/zone/unit.tests/record', fieldType='TXT',
subDomain='txt', target='TXT text', ttl=1400),
call('/domain/zone/unit.tests/record', fieldType='CNAME',
subDomain='www2', target='unit.tests.', ttl=300),
call('/domain/zone/unit.tests/record', fieldType='NS',
subDomain='www3', target='ns3.unit.tests.', ttl=700),
call('/domain/zone/unit.tests/record', fieldType='NS',
subDomain='www3', target='ns4.unit.tests.', ttl=700),
call('/domain/zone/unit.tests/refresh')]
post_mock.assert_has_calls(wanted_calls)
# Get for delete calls
wanted_get_calls = [
call(u'/domain/zone/unit.tests/record', fieldType=u'TXT',
subDomain='txt'),
call(u'/domain/zone/unit.tests/record', fieldType=u'A',
subDomain=u''),
call(u'/domain/zone/unit.tests/record', fieldType=u'DKIM',
subDomain='dkim'),
call(u'/domain/zone/unit.tests/record', fieldType=u'A',
subDomain=u''),
call(u'/domain/zone/unit.tests/record', fieldType=u'A',
subDomain='fake')]
subDomain='fake'),
call(u'/domain/zone/unit.tests/record', fieldType=u'TXT',
subDomain='txt')]
get_mock.assert_has_calls(wanted_get_calls)
# 4 delete calls for update and delete
delete_mock.assert_has_calls(
+6 -5
View File
@@ -9,6 +9,7 @@ from json import loads, dumps
from os.path import dirname, join
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
from six import text_type
from unittest import TestCase
from octodns.record import Record
@@ -41,7 +42,7 @@ with open('./tests/fixtures/powerdns-full-data.json') as fh:
class TestPowerDnsProvider(TestCase):
def test_provider(self):
provider = PowerDnsProvider('test', 'non.existant', 'api-key',
provider = PowerDnsProvider('test', 'non.existent', 'api-key',
nameserver_values=['8.8.8.8.',
'9.9.9.9.'])
@@ -52,7 +53,7 @@ class TestPowerDnsProvider(TestCase):
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
provider.populate(zone)
self.assertTrue('unauthorized' in ctx.exception.message)
self.assertTrue('unauthorized' in text_type(ctx.exception))
# General error
with requests_mock() as mock:
@@ -63,7 +64,7 @@ class TestPowerDnsProvider(TestCase):
provider.populate(zone)
self.assertEquals(502, ctx.exception.response.status_code)
# Non-existant zone doesn't populate anything
# Non-existent zone doesn't populate anything
with requests_mock() as mock:
mock.get(ANY, status_code=422,
json={'error': "Could not find domain 'unit.tests.'"})
@@ -163,7 +164,7 @@ class TestPowerDnsProvider(TestCase):
provider.apply(plan)
def test_small_change(self):
provider = PowerDnsProvider('test', 'non.existant', 'api-key')
provider = PowerDnsProvider('test', 'non.existent', 'api-key')
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
@@ -203,7 +204,7 @@ class TestPowerDnsProvider(TestCase):
def test_existing_nameservers(self):
ns_values = ['8.8.8.8.', '9.9.9.9.']
provider = PowerDnsProvider('test', 'non.existant', 'api-key',
provider = PowerDnsProvider('test', 'non.existent', 'api-key',
nameserver_values=ns_values)
expected = Zone('unit.tests.', [])
+7 -7
View File
@@ -7,8 +7,9 @@ from __future__ import absolute_import, division, print_function, \
import json
import re
from six import text_type
from six.moves.urllib.parse import urlparse
from unittest import TestCase
from urlparse import urlparse
from requests import HTTPError
from requests_mock import ANY, mock as requests_mock
@@ -39,7 +40,6 @@ with open('./tests/fixtures/rackspace-sample-recordset-page2.json') as fh:
class TestRackspaceProvider(TestCase):
def setUp(self):
self.maxDiff = 1000
with requests_mock() as mock:
mock.post(ANY, status_code=200, text=AUTH_RESPONSE)
self.provider = RackspaceProvider('identity', 'test', 'api-key',
@@ -53,7 +53,7 @@ class TestRackspaceProvider(TestCase):
with self.assertRaises(Exception) as ctx:
zone = Zone('unit.tests.', [])
self.provider.populate(zone)
self.assertTrue('unauthorized' in ctx.exception.message)
self.assertTrue('unauthorized' in text_type(ctx.exception))
self.assertTrue(mock.called_once)
def test_server_error(self):
@@ -792,13 +792,13 @@ class TestRackspaceProvider(TestCase):
ExpectedUpdates = {
"records": [{
"name": "unit.tests",
"id": "A-222222",
"data": "1.2.3.5",
"id": "A-111111",
"data": "1.2.3.4",
"ttl": 3600
}, {
"name": "unit.tests",
"id": "A-111111",
"data": "1.2.3.4",
"id": "A-222222",
"data": "1.2.3.5",
"ttl": 3600
}, {
"name": "unit.tests",
+415 -188
View File
@@ -7,12 +7,14 @@ from __future__ import absolute_import, division, print_function, \
from botocore.exceptions import ClientError
from botocore.stub import ANY, Stubber
from six import text_type
from unittest import TestCase
from mock import patch
from octodns.record import Create, Delete, Record, Update
from octodns.provider.route53 import Route53Provider, _Route53GeoDefault, \
_Route53GeoRecord, _Route53Record, _mod_keyer, _octal_replace
_Route53DynamicValue, _Route53GeoRecord, _Route53Record, _mod_keyer, \
_octal_replace
from octodns.zone import Zone
from helpers import GeoProvider
@@ -502,7 +504,7 @@ class TestRoute53Provider(TestCase):
'ResourceRecords': [{
'Value': '10 smtp-1.unit.tests.',
}, {
'Value': '20 smtp-2.unit.tests.',
'Value': '20 smtp-2.unit.tests.',
}],
'TTL': 64,
}, {
@@ -699,18 +701,6 @@ class TestRoute53Provider(TestCase):
'TTL': 61,
'Type': 'A'
}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'GeoLocation': {'CountryCode': 'US',
'SubdivisionCode': 'CA'},
'HealthCheckId': u'44',
'Name': 'unit.tests.',
'ResourceRecords': [{'Value': '7.2.3.4'}],
'SetIdentifier': 'NA-US-CA',
'TTL': 61,
'Type': 'A'
}
}, {
'Action': 'UPSERT',
'ResourceRecordSet': {
@@ -734,6 +724,18 @@ class TestRoute53Provider(TestCase):
'TTL': 61,
'Type': 'A'
}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'GeoLocation': {'CountryCode': 'US',
'SubdivisionCode': 'CA'},
'HealthCheckId': u'44',
'Name': 'unit.tests.',
'ResourceRecords': [{'Value': '7.2.3.4'}],
'SetIdentifier': 'NA-US-CA',
'TTL': 61,
'Type': 'A'
}
}, {
'Action': 'UPSERT',
'ResourceRecordSet': {
@@ -874,6 +876,25 @@ class TestRoute53Provider(TestCase):
'CallerReference': ANY,
})
list_resource_record_sets_resp = {
'ResourceRecordSets': [{
'Name': 'a.unit.tests.',
'Type': 'A',
'GeoLocation': {
'ContinentCode': 'NA',
},
'ResourceRecords': [{
'Value': '2.2.3.4',
}],
'TTL': 61,
}],
'IsTruncated': False,
'MaxItems': '100',
}
stubber.add_response('list_resource_record_sets',
list_resource_record_sets_resp,
{'HostedZoneId': 'z42'})
stubber.add_response('list_health_checks',
{
'HealthChecks': self.health_checks,
@@ -1236,7 +1257,7 @@ class TestRoute53Provider(TestCase):
'HealthCheckId': '44',
})
change = Create(record)
provider._mod_Create(change, 'z43')
provider._mod_Create(change, 'z43', [])
stubber.assert_no_pending_responses()
# gc through _mod_Update
@@ -1245,7 +1266,7 @@ class TestRoute53Provider(TestCase):
})
# first record is ignored for our purposes, we have to pass something
change = Update(record, record)
provider._mod_Create(change, 'z43')
provider._mod_Create(change, 'z43', [])
stubber.assert_no_pending_responses()
# gc through _mod_Delete, expect 3 to go away, can't check order
@@ -1260,7 +1281,7 @@ class TestRoute53Provider(TestCase):
'HealthCheckId': ANY,
})
change = Delete(record)
provider._mod_Delete(change, 'z43')
provider._mod_Delete(change, 'z43', [])
stubber.assert_no_pending_responses()
# gc only AAAA, leave the A's alone
@@ -1653,7 +1674,7 @@ class TestRoute53Provider(TestCase):
desired.add_record(record)
list_resource_record_sets_resp = {
'ResourceRecordSets': [{
# other name
# Not dynamic value and other name
'Name': 'unit.tests.',
'Type': 'A',
'GeoLocation': {
@@ -1663,17 +1684,21 @@ class TestRoute53Provider(TestCase):
'Value': '1.2.3.4',
}],
'TTL': 61,
# All the non-matches have a different Id so we'll fail if they
# match
'HealthCheckId': '33',
}, {
# matching name, other type
# Not dynamic value, matching name, other type
'Name': 'a.unit.tests.',
'Type': 'AAAA',
'ResourceRecords': [{
'Value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
}],
'TTL': 61,
'HealthCheckId': '33',
}, {
# default value pool
'Name': '_octodns-default-pool.a.unit.tests.',
'Name': '_octodns-default-value.a.unit.tests.',
'Type': 'A',
'GeoLocation': {
'CountryCode': '*',
@@ -1682,6 +1707,37 @@ class TestRoute53Provider(TestCase):
'Value': '1.2.3.4',
}],
'TTL': 61,
'HealthCheckId': '33',
}, {
# different record
'Name': '_octodns-two-value.other.unit.tests.',
'Type': 'A',
'GeoLocation': {
'CountryCode': '*',
},
'ResourceRecords': [{
'Value': '1.2.3.4',
}],
'TTL': 61,
'HealthCheckId': '33',
}, {
# same everything, but different type
'Name': '_octodns-one-value.a.unit.tests.',
'Type': 'AAAA',
'ResourceRecords': [{
'Value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
}],
'TTL': 61,
'HealthCheckId': '33',
}, {
# same everything, sub
'Name': '_octodns-one-value.sub.a.unit.tests.',
'Type': 'A',
'ResourceRecords': [{
'Value': '1.2.3.4',
}],
'TTL': 61,
'HealthCheckId': '33',
}, {
# match
'Name': '_octodns-one-value.a.unit.tests.',
@@ -1804,46 +1860,51 @@ class TestRoute53Provider(TestCase):
# _get_test_plan() returns a plan with 11 modifications, 17 RRs
@patch('octodns.provider.route53.Route53Provider._load_records')
@patch('octodns.provider.route53.Route53Provider._really_apply')
def test_apply_1(self, really_apply_mock):
def test_apply_1(self, really_apply_mock, _):
# 18 RRs with max of 19 should only get applied in one call
provider, plan = self._get_test_plan(19)
provider.apply(plan)
really_apply_mock.assert_called_once()
@patch('octodns.provider.route53.Route53Provider._load_records')
@patch('octodns.provider.route53.Route53Provider._really_apply')
def test_apply_2(self, really_apply_mock):
def test_apply_2(self, really_apply_mock, _):
# 18 RRs with max of 17 should only get applied in two calls
provider, plan = self._get_test_plan(18)
provider.apply(plan)
self.assertEquals(2, really_apply_mock.call_count)
@patch('octodns.provider.route53.Route53Provider._load_records')
@patch('octodns.provider.route53.Route53Provider._really_apply')
def test_apply_3(self, really_apply_mock):
def test_apply_3(self, really_apply_mock, _):
# with a max of seven modifications, four calls
# with a max of seven modifications, three calls
provider, plan = self._get_test_plan(7)
provider.apply(plan)
self.assertEquals(4, really_apply_mock.call_count)
self.assertEquals(3, really_apply_mock.call_count)
@patch('octodns.provider.route53.Route53Provider._load_records')
@patch('octodns.provider.route53.Route53Provider._really_apply')
def test_apply_4(self, really_apply_mock):
def test_apply_4(self, really_apply_mock, _):
# with a max of 11 modifications, two calls
provider, plan = self._get_test_plan(11)
provider.apply(plan)
self.assertEquals(2, really_apply_mock.call_count)
@patch('octodns.provider.route53.Route53Provider._load_records')
@patch('octodns.provider.route53.Route53Provider._really_apply')
def test_apply_bad(self, really_apply_mock):
def test_apply_bad(self, really_apply_mock, _):
# with a max of 1 modifications, fail
provider, plan = self._get_test_plan(1)
with self.assertRaises(Exception) as ctx:
provider.apply(plan)
self.assertTrue('modifications' in ctx.exception.message)
self.assertTrue('modifications' in text_type(ctx.exception))
def test_semicolon_fixup(self):
provider = Route53Provider('test', 'abc', '123')
@@ -1939,6 +2000,12 @@ class TestRoute53Provider(TestCase):
}], [r.data for r in record.dynamic.rules])
class DummyProvider(object):
def get_health_check_id(self, *args, **kwargs):
return None
class TestRoute53Records(TestCase):
existing = Zone('unit.tests.', [])
record_a = Record.new(existing, '', {
@@ -2005,11 +2072,6 @@ class TestRoute53Records(TestCase):
e = _Route53GeoDefault(None, self.record_a, False)
self.assertNotEquals(a, e)
class DummyProvider(object):
def get_health_check_id(self, *args, **kwargs):
return None
provider = DummyProvider()
f = _Route53GeoRecord(provider, self.record_a, 'NA-US',
self.record_a.geo['NA-US'], False)
@@ -2029,6 +2091,153 @@ class TestRoute53Records(TestCase):
e.__repr__()
f.__repr__()
def test_route53_record_ordering(self):
# Matches
a = _Route53Record(None, self.record_a, False)
b = _Route53Record(None, self.record_a, False)
self.assertTrue(a == b)
self.assertFalse(a != b)
self.assertFalse(a < b)
self.assertTrue(a <= b)
self.assertFalse(a > b)
self.assertTrue(a >= b)
# Change the fqdn is greater
fqdn = _Route53Record(None, self.record_a, False,
fqdn_override='other')
self.assertFalse(a == fqdn)
self.assertTrue(a != fqdn)
self.assertFalse(a < fqdn)
self.assertFalse(a <= fqdn)
self.assertTrue(a > fqdn)
self.assertTrue(a >= fqdn)
provider = DummyProvider()
geo_a = _Route53GeoRecord(provider, self.record_a, 'NA-US',
self.record_a.geo['NA-US'], False)
geo_b = _Route53GeoRecord(provider, self.record_a, 'NA-US',
self.record_a.geo['NA-US'], False)
self.assertTrue(geo_a == geo_b)
self.assertFalse(geo_a != geo_b)
self.assertFalse(geo_a < geo_b)
self.assertTrue(geo_a <= geo_b)
self.assertFalse(geo_a > geo_b)
self.assertTrue(geo_a >= geo_b)
# Other base
geo_fqdn = _Route53GeoRecord(provider, self.record_a, 'NA-US',
self.record_a.geo['NA-US'], False)
geo_fqdn.fqdn = 'other'
self.assertFalse(geo_a == geo_fqdn)
self.assertTrue(geo_a != geo_fqdn)
self.assertFalse(geo_a < geo_fqdn)
self.assertFalse(geo_a <= geo_fqdn)
self.assertTrue(geo_a > geo_fqdn)
self.assertTrue(geo_a >= geo_fqdn)
# Other class
self.assertFalse(a == geo_a)
self.assertTrue(a != geo_a)
self.assertFalse(a < geo_a)
self.assertFalse(a <= geo_a)
self.assertTrue(a > geo_a)
self.assertTrue(a >= geo_a)
def test_dynamic_value_delete(self):
provider = DummyProvider()
geo = _Route53DynamicValue(provider, self.record_a, 'iad', '2.2.2.2',
1, 0, False)
rrset = {
'HealthCheckId': 'x12346z',
'Name': '_octodns-iad-value.unit.tests.',
'ResourceRecords': [{
'Value': '2.2.2.2'
}],
'SetIdentifier': 'iad-000',
'TTL': 99,
'Type': 'A',
'Weight': 1,
}
candidates = [
# Empty, will test no SetIdentifier
{},
# Non-matching
{
'SetIdentifier': 'not-a-match',
},
# Same set-id, different name
{
'Name': 'not-a-match',
'SetIdentifier': 'x12346z',
},
rrset,
]
# Provide a matching rrset so that we'll just use it for the delete
# rathr than building up an almost identical one, note the way we'll
# know that we got the one we passed in is that it'll have a
# HealthCheckId and one that was created wouldn't since DummyProvider
# stubs out the lookup for them
mod = geo.mod('DELETE', candidates)
self.assertEquals('x12346z', mod['ResourceRecordSet']['HealthCheckId'])
# If we don't provide the candidate rrsets we get back exactly what we
# put in minus the healthcheck
rrset['HealthCheckId'] = None
mod = geo.mod('DELETE', [])
self.assertEquals(rrset, mod['ResourceRecordSet'])
def test_geo_delete(self):
provider = DummyProvider()
geo = _Route53GeoRecord(provider, self.record_a, 'NA-US',
self.record_a.geo['NA-US'], False)
rrset = {
'GeoLocation': {
'CountryCode': 'US'
},
'HealthCheckId': 'x12346z',
'Name': 'unit.tests.',
'ResourceRecords': [{
'Value': '2.2.2.2'
}, {
'Value': '3.3.3.3'
}],
'SetIdentifier': 'NA-US',
'TTL': 99,
'Type': 'A'
}
candidates = [
# Empty, will test no SetIdentifier
{},
{
'SetIdentifier': 'not-a-match',
},
# Same set-id, different name
{
'Name': 'not-a-match',
'SetIdentifier': 'x12346z',
},
rrset,
]
# Provide a matching rrset so that we'll just use it for the delete
# rathr than building up an almost identical one, note the way we'll
# know that we got the one we passed in is that it'll have a
# HealthCheckId and one that was created wouldn't since DummyProvider
# stubs out the lookup for them
mod = geo.mod('DELETE', candidates)
self.assertEquals('x12346z', mod['ResourceRecordSet']['HealthCheckId'])
# If we don't provide the candidate rrsets we get back exactly what we
# put in minus the healthcheck
del rrset['HealthCheckId']
mod = geo.mod('DELETE', [])
self.assertEquals(rrset, mod['ResourceRecordSet'])
def test_new_dynamic(self):
provider = Route53Provider('test', 'abc', '123')
@@ -2050,136 +2259,38 @@ class TestRoute53Records(TestCase):
creating=True)
self.assertEquals(18, len(route53_records))
expected_mods = [r.mod('CREATE', []) for r in route53_records]
# Sort so that we get a consistent order and don't rely on set ordering
expected_mods.sort(key=_mod_keyer)
# Convert the route53_records into mods
self.assertEquals([{
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-ap-southeast-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.4.1.2'}],
'SetIdentifier': 'ap-southeast-1-001',
'ResourceRecords': [{'Value': '1.4.1.1'}],
'SetIdentifier': 'ap-southeast-1-000',
'TTL': 60,
'Type': 'A',
'Weight': 2
}
'Weight': 2}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-ap-southeast-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.4.1.1'}],
'SetIdentifier': 'ap-southeast-1-000',
'ResourceRecords': [{'Value': '1.4.1.2'}],
'SetIdentifier': 'ap-southeast-1-001',
'TTL': 60,
'Type': 'A',
'Weight': 2
}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'
},
'GeoLocation': {
'CountryCode': 'JP'},
'Name': 'unit.tests.',
'SetIdentifier': '0-ap-southeast-1-AS-JP',
'Type': 'A'
}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': 'US',
'SubdivisionCode': 'FL',
},
'Name': 'unit.tests.',
'SetIdentifier': '1-eu-central-1-NA-US-FL',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': '*'},
'Name': 'unit.tests.',
'SetIdentifier': '2-us-east-1-None',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'SECONDARY',
'Name': '_octodns-ap-southeast-1-pool.unit.tests.',
'SetIdentifier': 'ap-southeast-1-Secondary-us-east-1',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': 'CN'},
'Name': 'unit.tests.',
'SetIdentifier': '0-ap-southeast-1-AS-CN',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-value.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'PRIMARY',
'Name': '_octodns-us-east-1-pool.unit.tests.',
'SetIdentifier': 'us-east-1-Primary',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'ContinentCode': 'EU'},
'Name': 'unit.tests.',
'SetIdentifier': '1-eu-central-1-EU',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-value.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'PRIMARY',
'Name': '_octodns-eu-central-1-pool.unit.tests.',
'SetIdentifier': 'eu-central-1-Primary',
'Type': 'A'}
'Weight': 2}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'Name': '_octodns-default-pool.unit.tests.',
'ResourceRecords': [{
'Value': '1.1.2.1'},
{
'Value': '1.1.2.2'}],
'ResourceRecords': [
{'Value': '1.1.2.1'},
{'Value': '1.1.2.2'}],
'TTL': 60,
'Type': 'A'}
}, {
@@ -2187,56 +2298,41 @@ class TestRoute53Records(TestCase):
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-eu-central-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.3.1.2'}],
'ResourceRecords': [{'Value': '1.3.1.1'}],
'SetIdentifier': 'eu-central-1-000',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-eu-central-1-value.unit.tests.',
'ResourceRecords': [{'Value': '1.3.1.2'}],
'SetIdentifier': 'eu-central-1-001',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-eu-central-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.3.1.1'}],
'SetIdentifier': 'eu-central-1-000',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-default-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'SECONDARY',
'Name': '_octodns-us-east-1-pool.unit.tests.',
'SetIdentifier': 'us-east-1-Secondary-default',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-us-east-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.5.1.2'}],
'SetIdentifier': 'us-east-1-001',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-us-east-1-value.unit.tests.',
'ResourceRecords': [{
'Value': '1.5.1.1'}],
'ResourceRecords': [{'Value': '1.5.1.1'}],
'SetIdentifier': 'us-east-1-000',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'HealthCheckId': 'hc42',
'Name': '_octodns-us-east-1-value.unit.tests.',
'ResourceRecords': [{'Value': '1.5.1.2'}],
'SetIdentifier': 'us-east-1-001',
'TTL': 60,
'Type': 'A',
'Weight': 1}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
@@ -2248,6 +2344,39 @@ class TestRoute53Records(TestCase):
'Name': '_octodns-ap-southeast-1-pool.unit.tests.',
'SetIdentifier': 'ap-southeast-1-Primary',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-value.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'PRIMARY',
'Name': '_octodns-eu-central-1-pool.unit.tests.',
'SetIdentifier': 'eu-central-1-Primary',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-value.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'PRIMARY',
'Name': '_octodns-us-east-1-pool.unit.tests.',
'SetIdentifier': 'us-east-1-Primary',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'SECONDARY',
'Name': '_octodns-ap-southeast-1-pool.unit.tests.',
'SetIdentifier': 'ap-southeast-1-Secondary-us-east-1',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
@@ -2259,7 +2388,79 @@ class TestRoute53Records(TestCase):
'Name': '_octodns-eu-central-1-pool.unit.tests.',
'SetIdentifier': 'eu-central-1-Secondary-us-east-1',
'Type': 'A'}
}], [r.mod('CREATE') for r in route53_records])
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-default-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'Failover': 'SECONDARY',
'Name': '_octodns-us-east-1-pool.unit.tests.',
'SetIdentifier': 'us-east-1-Secondary-default',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': 'CN'},
'Name': 'unit.tests.',
'SetIdentifier': '0-ap-southeast-1-AS-CN',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': 'JP'},
'Name': 'unit.tests.',
'SetIdentifier': '0-ap-southeast-1-AS-JP',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'ContinentCode': 'EU'},
'Name': 'unit.tests.',
'SetIdentifier': '1-eu-central-1-EU',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': 'US',
'SubdivisionCode': 'FL'},
'Name': 'unit.tests.',
'SetIdentifier': '1-eu-central-1-NA-US-FL',
'Type': 'A'}
}, {
'Action': 'CREATE',
'ResourceRecordSet': {
'AliasTarget': {
'DNSName': '_octodns-us-east-1-pool.unit.tests.',
'EvaluateTargetHealth': True,
'HostedZoneId': 'z45'},
'GeoLocation': {
'CountryCode': '*'},
'Name': 'unit.tests.',
'SetIdentifier': '2-us-east-1-None',
'Type': 'A'}
}], expected_mods)
for route53_record in route53_records:
# Smoke test stringification
@@ -2270,7 +2471,7 @@ class TestModKeyer(TestCase):
def test_mod_keyer(self):
# First "column"
# First "column" is the action priority for C/R/U
# Deletes come first
self.assertEquals((0, 0, 'something'), _mod_keyer({
@@ -2288,8 +2489,8 @@ class TestModKeyer(TestCase):
}
}))
# Then upserts
self.assertEquals((2, 0, 'last'), _mod_keyer({
# Upserts are the same as creates
self.assertEquals((1, 0, 'last'), _mod_keyer({
'Action': 'UPSERT',
'ResourceRecordSet': {
'Name': 'last',
@@ -2299,7 +2500,7 @@ class TestModKeyer(TestCase):
# Second "column" value records tested above
# AliasTarget primary second (to value)
self.assertEquals((0, 1, 'thing'), _mod_keyer({
self.assertEquals((0, -1, 'thing'), _mod_keyer({
'Action': 'DELETE',
'ResourceRecordSet': {
'AliasTarget': 'some-target',
@@ -2308,8 +2509,17 @@ class TestModKeyer(TestCase):
}
}))
self.assertEquals((1, 1, 'thing'), _mod_keyer({
'Action': 'UPSERT',
'ResourceRecordSet': {
'AliasTarget': 'some-target',
'Failover': 'PRIMARY',
'Name': 'thing',
}
}))
# AliasTarget secondary third
self.assertEquals((0, 2, 'thing'), _mod_keyer({
self.assertEquals((0, -2, 'thing'), _mod_keyer({
'Action': 'DELETE',
'ResourceRecordSet': {
'AliasTarget': 'some-target',
@@ -2318,8 +2528,17 @@ class TestModKeyer(TestCase):
}
}))
self.assertEquals((1, 2, 'thing'), _mod_keyer({
'Action': 'UPSERT',
'ResourceRecordSet': {
'AliasTarget': 'some-target',
'Failover': 'SECONDARY',
'Name': 'thing',
}
}))
# GeoLocation fourth
self.assertEquals((0, 3, 'some-id'), _mod_keyer({
self.assertEquals((0, -3, 'some-id'), _mod_keyer({
'Action': 'DELETE',
'ResourceRecordSet': {
'GeoLocation': 'some-target',
@@ -2327,4 +2546,12 @@ class TestModKeyer(TestCase):
}
}))
self.assertEquals((1, 3, 'some-id'), _mod_keyer({
'Action': 'UPSERT',
'ResourceRecordSet': {
'GeoLocation': 'some-target',
'SetIdentifier': 'some-id',
}
}))
# The third "column" has already been tested above, Name/SetIdentifier
+402
View File
@@ -0,0 +1,402 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from unittest import TestCase
from six import text_type
import requests_mock
from octodns.provider.selectel import SelectelProvider
from octodns.record import Record, Update
from octodns.zone import Zone
class TestSelectelProvider(TestCase):
API_URL = 'https://api.selectel.ru/domains/v1'
api_record = []
zone = Zone('unit.tests.', [])
expected = set()
domain = [{"name": "unit.tests", "id": 100000}]
# A, subdomain=''
api_record.append({
'type': 'A',
'ttl': 100,
'content': '1.2.3.4',
'name': 'unit.tests',
'id': 1
})
expected.add(Record.new(zone, '', {
'ttl': 100,
'type': 'A',
'value': '1.2.3.4',
}))
# A, subdomain='sub'
api_record.append({
'type': 'A',
'ttl': 200,
'content': '1.2.3.4',
'name': 'sub.unit.tests',
'id': 2
})
expected.add(Record.new(zone, 'sub', {
'ttl': 200,
'type': 'A',
'value': '1.2.3.4',
}))
# CNAME
api_record.append({
'type': 'CNAME',
'ttl': 300,
'content': 'unit.tests',
'name': 'www2.unit.tests',
'id': 3
})
expected.add(Record.new(zone, 'www2', {
'ttl': 300,
'type': 'CNAME',
'value': 'unit.tests.',
}))
# MX
api_record.append({
'type': 'MX',
'ttl': 400,
'content': 'mx1.unit.tests',
'priority': 10,
'name': 'unit.tests',
'id': 4
})
expected.add(Record.new(zone, '', {
'ttl': 400,
'type': 'MX',
'values': [{
'preference': 10,
'exchange': 'mx1.unit.tests.',
}]
}))
# NS
api_record.append({
'type': 'NS',
'ttl': 600,
'content': 'ns1.unit.tests',
'name': 'unit.tests.',
'id': 6
})
api_record.append({
'type': 'NS',
'ttl': 600,
'content': 'ns2.unit.tests',
'name': 'unit.tests',
'id': 7
})
expected.add(Record.new(zone, '', {
'ttl': 600,
'type': 'NS',
'values': ['ns1.unit.tests.', 'ns2.unit.tests.'],
}))
# NS with sub
api_record.append({
'type': 'NS',
'ttl': 700,
'content': 'ns3.unit.tests',
'name': 'www3.unit.tests',
'id': 8
})
api_record.append({
'type': 'NS',
'ttl': 700,
'content': 'ns4.unit.tests',
'name': 'www3.unit.tests',
'id': 9
})
expected.add(Record.new(zone, 'www3', {
'ttl': 700,
'type': 'NS',
'values': ['ns3.unit.tests.', 'ns4.unit.tests.'],
}))
# SRV
api_record.append({
'type': 'SRV',
'ttl': 800,
'target': 'foo-1.unit.tests',
'weight': 20,
'priority': 10,
'port': 30,
'id': 10,
'name': '_srv._tcp.unit.tests'
})
api_record.append({
'type': 'SRV',
'ttl': 800,
'target': 'foo-2.unit.tests',
'name': '_srv._tcp.unit.tests',
'weight': 50,
'priority': 40,
'port': 60,
'id': 11
})
expected.add(Record.new(zone, '_srv._tcp', {
'ttl': 800,
'type': 'SRV',
'values': [{
'priority': 10,
'weight': 20,
'port': 30,
'target': 'foo-1.unit.tests.',
}, {
'priority': 40,
'weight': 50,
'port': 60,
'target': 'foo-2.unit.tests.',
}]
}))
# AAAA
aaaa_record = {
'type': 'AAAA',
'ttl': 200,
'content': '1:1ec:1::1',
'name': 'unit.tests',
'id': 15
}
api_record.append(aaaa_record)
expected.add(Record.new(zone, '', {
'ttl': 200,
'type': 'AAAA',
'value': '1:1ec:1::1',
}))
# TXT
api_record.append({
'type': 'TXT',
'ttl': 300,
'content': 'little text',
'name': 'text.unit.tests',
'id': 16
})
expected.add(Record.new(zone, 'text', {
'ttl': 200,
'type': 'TXT',
'value': 'little text',
}))
@requests_mock.Mocker()
def test_populate(self, fake_http):
zone = Zone('unit.tests.', [])
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=self.api_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
provider = SelectelProvider(123, 'secret_token')
provider.populate(zone)
self.assertEquals(self.expected, zone.records)
@requests_mock.Mocker()
def test_populate_invalid_record(self, fake_http):
more_record = self.api_record
more_record.append({"name": "unit.tests",
"id": 100001,
"content": "support.unit.tests.",
"ttl": 300, "ns": "ns1.unit.tests",
"type": "SOA",
"email": "[email protected]"})
zone = Zone('unit.tests.', [])
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=more_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
zone.add_record(Record.new(self.zone, 'unsup', {
'ttl': 200,
'type': 'NAPTR',
'value': {
'order': 40,
'preference': 70,
'flags': 'U',
'service': 'SIP+D2U',
'regexp': '!^.*$!sip:[email protected]!',
'replacement': '.',
}
}))
provider = SelectelProvider(123, 'secret_token')
provider.populate(zone)
self.assertNotEqual(self.expected, zone.records)
@requests_mock.Mocker()
def test_apply(self, fake_http):
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=list())
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': '0'})
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
fake_http.post('{}/100000/records/'.format(self.API_URL), json=list())
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])
for record in self.expected:
zone.add_record(record)
plan = provider.plan(zone)
self.assertEquals(8, len(plan.changes))
self.assertEquals(8, provider.apply(plan))
@requests_mock.Mocker()
def test_domain_list(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
expected = {'unit.tests': self.domain[0]}
provider = SelectelProvider(123, 'test_token')
result = provider.domain_list()
self.assertEquals(result, expected)
@requests_mock.Mocker()
def test_authentication_fail(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), status_code=401)
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
with self.assertRaises(Exception) as ctx:
SelectelProvider(123, 'fail_token')
self.assertEquals(text_type(ctx.exception),
'Authorization failed. Invalid or empty token.')
@requests_mock.Mocker()
def test_not_exist_domain(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), status_code=404, json='')
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
fake_http.post('{}/'.format(self.API_URL),
json={"name": "unit.tests",
"create_date": 1507154178,
"id": 100000})
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=list())
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.post('{}/100000/records/'.format(self.API_URL),
json=list())
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])
for record in self.expected:
zone.add_record(record)
plan = provider.plan(zone)
self.assertEquals(8, len(plan.changes))
self.assertEquals(8, provider.apply(plan))
@requests_mock.Mocker()
def test_delete_no_exist_record(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.get('{}/100000/records/'.format(self.API_URL), json=list())
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': '0'})
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])
provider.delete_record('unit.tests', 'NS', zone)
@requests_mock.Mocker()
def test_change_record(self, fake_http):
exist_record = [self.aaaa_record,
{"content": "6.6.5.7",
"ttl": 100,
"type": "A",
"id": 100001,
"name": "delete.unit.tests"},
{"content": "9.8.2.1",
"ttl": 100,
"type": "A",
"id": 100002,
"name": "unit.tests"}] # exist
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=exist_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.get('{}/100000/records/'.format(self.API_URL),
json=exist_record)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
headers={'X-Total-Count': str(len(exist_record))})
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
fake_http.head('{}/100000/records/'.format(self.API_URL),
headers={'X-Total-Count': str(len(exist_record))})
fake_http.post('{}/100000/records/'.format(self.API_URL),
json=list())
fake_http.delete('{}/100000/records/100001'.format(self.API_URL),
text="")
fake_http.delete('{}/100000/records/100002'.format(self.API_URL),
text="")
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])
for record in self.expected:
zone.add_record(record)
plan = provider.plan(zone)
self.assertEquals(8, len(plan.changes))
self.assertEquals(8, provider.apply(plan))
@requests_mock.Mocker()
def test_include_change_returns_false(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/'.format(self.API_URL),
headers={'X-Total-Count': str(len(self.domain))})
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])
exist_record = Record.new(zone, '', {
'ttl': 60,
'type': 'A',
'values': ['1.1.1.1', '2.2.2.2']
})
new = Record.new(zone, '', {
'ttl': 10,
'type': 'A',
'values': ['1.1.1.1', '2.2.2.2']
})
change = Update(exist_record, new)
include_change = provider._include_change(change)
self.assertFalse(include_change)
+275
View File
@@ -0,0 +1,275 @@
#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from os.path import dirname, join
from six import text_type
from suds import WebFault
from unittest import TestCase
from octodns.provider.transip import TransipProvider
from octodns.provider.yaml import YamlProvider
from octodns.zone import Zone
from transip.service.domain import DomainService
from transip.service.objects import DnsEntry
class MockFault(object):
faultstring = ""
faultcode = ""
def __init__(self, code, string, *args, **kwargs):
self.faultstring = string
self.faultcode = code
class MockResponse(object):
dnsEntries = []
class MockDomainService(DomainService):
def __init__(self, *args, **kwargs):
super(MockDomainService, self).__init__('MockDomainService', *args,
**kwargs)
self.mockupEntries = []
def mockup(self, records):
provider = TransipProvider('', '', '')
_dns_entries = []
for record in records:
if record._type in provider.SUPPORTS:
entries_for = getattr(provider,
'_entries_for_{}'.format(record._type))
# Root records have '@' as name
name = record.name
if name == '':
name = provider.ROOT_RECORD
_dns_entries.extend(entries_for(name, record))
# NS is not supported as a DNS Entry,
# so it should cover the if statement
_dns_entries.append(
DnsEntry('@', '3600', 'NS', 'ns01.transip.nl.'))
self.mockupEntries = _dns_entries
# Skips authentication layer and returns the entries loaded by "Mockup"
def get_info(self, domain_name):
# Special 'domain' to trigger error
if str(domain_name) == str('notfound.unit.tests'):
self.raiseZoneNotFound()
result = MockResponse()
result.dnsEntries = self.mockupEntries
return result
def set_dns_entries(self, domain_name, dns_entries):
# Special 'domain' to trigger error
if str(domain_name) == str('failsetdns.unit.tests'):
self.raiseSaveError()
return True
def raiseZoneNotFound(self):
fault = MockFault(str('102'), '102 is zone not found')
document = {}
raise WebFault(fault, document)
def raiseInvalidAuth(self):
fault = MockFault(str('200'), '200 is invalid auth')
document = {}
raise WebFault(fault, document)
def raiseSaveError(self):
fault = MockFault(str('200'), '202 random error')
document = {}
raise WebFault(fault, document)
class TestTransipProvider(TestCase):
bogus_key = str("""-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0U5HGCkLrz423IyUf3u4cKN2WrNz1x5KNr6PvH2M/zxas+zB
elbxkdT3AQ+wmfcIvOuTmFRTHv35q2um1aBrPxVw+2s+lWo28VwIRttwIB1vIeWu
lSBnkEZQRLyPI2tH0i5QoMX4CVPf9rvij3Uslimi84jdzDfPFIh6jZ6C8nLipOTG
0IMhge1ofVfB0oSy5H+7PYS2858QLAf5ruYbzbAxZRivS402wGmQ0d0Lc1KxraAj
kiMM5yj/CkH/Vm2w9I6+tLFeASE4ub5HCP5G/ig4dbYtqZMQMpqyAbGxd5SOVtyn
UHagAJUxf8DT3I8PyjEHjxdOPUsxNyRtepO/7QIDAQABAoIBAQC7fiZ7gxE/ezjD
2n6PsHFpHVTBLS2gzzZl0dCKZeFvJk6ODJDImaeuHhrh7X8ifMNsEI9XjnojMhl8
MGPzy88mZHugDNK0H8B19x5G8v1/Fz7dG5WHas660/HFkS+b59cfdXOugYiOOn9O
08HBBpLZNRUOmVUuQfQTjapSwGLG8PocgpyRD4zx0LnldnJcqYCxwCdev+AAsPnq
ibNtOd/MYD37w9MEGcaxLE8wGgkv8yd97aTjkgE+tp4zsM4QE4Rag133tsLLNznT
4Qr/of15M3NW/DXq/fgctyRcJjZpU66eCXLCz2iRTnLyyxxDC2nwlxKbubV+lcS0
S4hbfd/BAoGBAO8jXxEaiybR0aIhhSR5esEc3ymo8R8vBN3ZMJ+vr5jEPXr/ZuFj
/R4cZ2XV3VoQJG0pvIOYVPZ5DpJM7W+zSXtJ/7bLXy4Bnmh/rc+YYgC+AXQoLSil
iD2OuB2xAzRAK71DVSO0kv8gEEXCersPT2i6+vC2GIlJvLcYbOdRKWGxAoGBAOAQ
aJbRLtKujH+kMdoMI7tRlL8XwI+SZf0FcieEu//nFyerTePUhVgEtcE+7eQ7hyhG
fIXUFx/wALySoqFzdJDLc8U8pTLhbUaoLOTjkwnCTKQVprhnISqQqqh/0U5u47IE
RWzWKN6OHb0CezNTq80Dr6HoxmPCnJHBHn5LinT9AoGAQSpvZpbIIqz8pmTiBl2A
QQ2gFpcuFeRXPClKYcmbXVLkuhbNL1BzEniFCLAt4LQTaRf9ghLJ3FyCxwVlkpHV
zV4N6/8hkcTpKOraL38D/dXJSaEFJVVuee/hZl3tVJjEEpA9rDwx7ooLRSdJEJ6M
ciq55UyKBSdt4KssSiDI2RECgYBL3mJ7xuLy5bWfNsrGiVvD/rC+L928/5ZXIXPw
26oI0Yfun7ulDH4GOroMcDF/GYT/Zzac3h7iapLlR0WYI47xxGI0A//wBZLJ3QIu
krxkDo2C9e3Y/NqnHgsbOQR3aWbiDT4wxydZjIeXS3LKA2fl6Hyc90PN3cTEOb8I
hq2gRQKBgEt0SxhhtyB93SjgTzmUZZ7PiEf0YJatfM6cevmjWHexrZH+x31PB72s
fH2BQyTKKzoCLB1k/6HRaMnZdrWyWSZ7JKz3AHJ8+58d0Hr8LTrzDM1L6BbjeDct
N4OiVz1I3rbZGYa396lpxO6ku8yCglisL1yrSP6DdEUp66ntpKVd
-----END RSA PRIVATE KEY-----""")
def make_expected(self):
expected = Zone('unit.tests.', [])
source = YamlProvider('test', join(dirname(__file__), 'config'))
source.populate(expected)
return expected
def test_init(self):
with self.assertRaises(Exception) as ctx:
TransipProvider('test', 'unittest')
self.assertEquals(
str('Missing `key` of `key_file` parameter in config'),
str(ctx.exception))
TransipProvider('test', 'unittest', key=self.bogus_key)
# Existence and content of the key is tested in the SDK on client call
TransipProvider('test', 'unittest', key_file='/fake/path')
def test_populate(self):
_expected = self.make_expected()
# Unhappy Plan - Not authenticated
# Live test against API, will fail in an unauthorized error
with self.assertRaises(WebFault) as ctx:
provider = TransipProvider('test', 'unittest', self.bogus_key)
zone = Zone('unit.tests.', [])
provider.populate(zone, True)
self.assertEquals(str('WebFault'),
str(ctx.exception.__class__.__name__))
self.assertEquals(str('200'), ctx.exception.fault.faultcode)
# Unhappy Plan - Zone does not exists
# Will trigger an exception if provider is used as a target for a
# non-existing zone
with self.assertRaises(Exception) as ctx:
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
zone = Zone('notfound.unit.tests.', [])
provider.populate(zone, True)
self.assertEquals(str('TransipNewZoneException'),
str(ctx.exception.__class__.__name__))
self.assertEquals(
'populate: (102) Transip used as target' +
' for non-existing zone: notfound.unit.tests.',
text_type(ctx.exception))
# Happy Plan - Zone does not exists
# Won't trigger an exception if provider is NOT used as a target for a
# non-existing zone.
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
zone = Zone('notfound.unit.tests.', [])
provider.populate(zone, False)
# Happy Plan - Populate with mockup records
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
provider._client.mockup(_expected.records)
zone = Zone('unit.tests.', [])
provider.populate(zone, False)
# Transip allows relative values for types like cname, mx.
# Test is these are correctly appended with the domain
provider._currentZone = zone
self.assertEquals("www.unit.tests.", provider._parse_to_fqdn("www"))
self.assertEquals("www.unit.tests.",
provider._parse_to_fqdn("www.unit.tests."))
self.assertEquals("www.sub.sub.sub.unit.tests.",
provider._parse_to_fqdn("www.sub.sub.sub"))
self.assertEquals("unit.tests.",
provider._parse_to_fqdn("@"))
# Happy Plan - Even if the zone has no records the zone should exist
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
zone = Zone('unit.tests.', [])
exists = provider.populate(zone, True)
self.assertTrue(exists, 'populate should return true')
return
def test_plan(self):
_expected = self.make_expected()
# Test Happy plan, only create
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected)
self.assertEqual(12, plan.change_counts['Create'])
self.assertEqual(0, plan.change_counts['Update'])
self.assertEqual(0, plan.change_counts['Delete'])
return
def test_apply(self):
_expected = self.make_expected()
# Test happy flow. Create all supoorted records
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected)
self.assertEqual(12, len(plan.changes))
changes = provider.apply(plan)
self.assertEqual(changes, len(plan.changes))
# Test unhappy flow. Trigger 'not found error' in apply stage
# This should normally not happen as populate will capture it first
# but just in case.
changes = [] # reset changes
with self.assertRaises(Exception) as ctx:
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected)
plan.desired.name = 'notfound.unit.tests.'
changes = provider.apply(plan)
# Changes should not be set due to an Exception
self.assertEqual([], changes)
self.assertEquals(str('WebFault'),
str(ctx.exception.__class__.__name__))
self.assertEquals(str('102'), ctx.exception.fault.faultcode)
# Test unhappy flow. Trigger a unrecoverable error while saving
_expected = self.make_expected() # reset expected
changes = [] # reset changes
with self.assertRaises(Exception) as ctx:
provider = TransipProvider('test', 'unittest', self.bogus_key)
provider._client = MockDomainService('unittest', self.bogus_key)
plan = provider.plan(_expected)
plan.desired.name = 'failsetdns.unit.tests.'
changes = provider.apply(plan)
# Changes should not be set due to an Exception
self.assertEqual([], changes)
self.assertEquals(str('TransipException'),
str(ctx.exception.__class__.__name__))
+23 -25
View File
@@ -8,6 +8,7 @@ from __future__ import absolute_import, division, print_function, \
from os import makedirs
from os.path import basename, dirname, isdir, isfile, join
from unittest import TestCase
from six import text_type
from yaml import safe_load
from yaml.constructor import ConstructorError
@@ -57,8 +58,8 @@ class TestYamlProvider(TestCase):
# We add everything
plan = target.plan(zone)
self.assertEquals(15, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(15, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isfile(yaml_file))
# Now actually do it
@@ -67,8 +68,8 @@ class TestYamlProvider(TestCase):
# Dynamic plan
plan = target.plan(dynamic_zone)
self.assertEquals(5, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(5, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isfile(dynamic_yaml_file))
# Apply it
self.assertEquals(5, target.apply(plan))
@@ -79,16 +80,15 @@ class TestYamlProvider(TestCase):
target.populate(reloaded)
self.assertDictEqual(
{'included': ['test']},
filter(
lambda x: x.name == 'included', reloaded.records
)[0]._octodns)
[x for x in reloaded.records
if x.name == 'included'][0]._octodns)
self.assertFalse(zone.changes(reloaded, target=source))
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEquals(15, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(15, len([c for c in plan.changes
if isinstance(c, Create)]))
with open(yaml_file) as fh:
data = safe_load(fh.read())
@@ -116,7 +116,7 @@ class TestYamlProvider(TestCase):
self.assertTrue('value' in data.pop('www.sub'))
# make sure nothing is left
self.assertEquals([], data.keys())
self.assertEquals([], list(data.keys()))
with open(dynamic_yaml_file) as fh:
data = safe_load(fh.read())
@@ -145,7 +145,7 @@ class TestYamlProvider(TestCase):
# self.assertTrue('dynamic' in dyna)
# make sure nothing is left
self.assertEquals([], data.keys())
self.assertEquals([], list(data.keys()))
def test_empty(self):
source = YamlProvider('test', join(dirname(__file__), 'config'))
@@ -178,7 +178,7 @@ class TestYamlProvider(TestCase):
with self.assertRaises(SubzoneRecordException) as ctx:
source.populate(zone)
self.assertEquals('Record www.sub.unit.tests. is under a managed '
'subzone', ctx.exception.message)
'subzone', text_type(ctx.exception))
class TestSplitYamlProvider(TestCase):
@@ -201,9 +201,8 @@ class TestSplitYamlProvider(TestCase):
# This isn't great, but given the variable nature of the temp dir
# names, it's necessary.
self.assertItemsEqual(
yaml_files,
(basename(f) for f in _list_all_yaml_files(directory)))
d = list(basename(f) for f in _list_all_yaml_files(directory))
self.assertEqual(len(yaml_files), len(d))
def test_zone_directory(self):
source = SplitYamlProvider(
@@ -252,8 +251,8 @@ class TestSplitYamlProvider(TestCase):
# We add everything
plan = target.plan(zone)
self.assertEquals(15, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(15, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isdir(zone_dir))
# Now actually do it
@@ -261,8 +260,8 @@ class TestSplitYamlProvider(TestCase):
# Dynamic plan
plan = target.plan(dynamic_zone)
self.assertEquals(5, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(5, len([c for c in plan.changes
if isinstance(c, Create)]))
self.assertFalse(isdir(dynamic_zone_dir))
# Apply it
self.assertEquals(5, target.apply(plan))
@@ -273,16 +272,15 @@ class TestSplitYamlProvider(TestCase):
target.populate(reloaded)
self.assertDictEqual(
{'included': ['test']},
filter(
lambda x: x.name == 'included', reloaded.records
)[0]._octodns)
[x for x in reloaded.records
if x.name == 'included'][0]._octodns)
self.assertFalse(zone.changes(reloaded, target=source))
# A 2nd sync should still create everything
plan = target.plan(zone)
self.assertEquals(15, len(filter(lambda c: isinstance(c, Create),
plan.changes)))
self.assertEquals(15, len([c for c in plan.changes
if isinstance(c, Create)]))
yaml_file = join(zone_dir, '$unit.tests.yaml')
self.assertTrue(isfile(yaml_file))
@@ -371,7 +369,7 @@ class TestSplitYamlProvider(TestCase):
with self.assertRaises(SubzoneRecordException) as ctx:
source.populate(zone)
self.assertEquals('Record www.sub.unit.tests. is under a managed '
'subzone', ctx.exception.message)
'subzone', text_type(ctx.exception))
class TestOverridingYamlProvider(TestCase):
+424 -35
View File
@@ -5,12 +5,14 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from six import text_type
from unittest import TestCase
from octodns.record import ARecord, AaaaRecord, AliasRecord, CaaRecord, \
CnameRecord, Create, Delete, GeoValue, MxRecord, NaptrRecord, NaptrValue, \
NsRecord, PtrRecord, Record, SshfpRecord, SpfRecord, SrvRecord, \
TxtRecord, Update, ValidationError, _Dynamic, _DynamicPool, _DynamicRule
CaaValue, CnameRecord, Create, Delete, GeoValue, MxRecord, MxValue, \
NaptrRecord, NaptrValue, NsRecord, PtrRecord, Record, SshfpRecord, \
SshfpValue, SpfRecord, SrvRecord, SrvValue, TxtRecord, Update, \
ValidationError, _Dynamic, _DynamicPool, _DynamicRule
from octodns.zone import Zone
from helpers import DynamicProvider, GeoProvider, SimpleProvider
@@ -482,113 +484,140 @@ class TestRecord(TestCase):
# full sorting
# equivalent
b_naptr_value = b.values[0]
self.assertEquals(0, b_naptr_value.__cmp__(b_naptr_value))
self.assertTrue(b_naptr_value == b_naptr_value)
self.assertFalse(b_naptr_value != b_naptr_value)
self.assertTrue(b_naptr_value <= b_naptr_value)
self.assertTrue(b_naptr_value >= b_naptr_value)
# by order
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 10,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 40,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
}))
# by preference
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 30,
'preference': 10,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 30,
'preference': 40,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
}))
# by flags
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 30,
'preference': 31,
'flags': 'A',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 30,
'preference': 31,
'flags': 'Z',
'service': 'N',
'regexp': 'O',
'replacement': 'x',
})))
}))
# by service
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'A',
'regexp': 'O',
'replacement': 'x',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'Z',
'regexp': 'O',
'replacement': 'x',
})))
}))
# by regexp
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'A',
'replacement': 'x',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'Z',
'replacement': 'x',
})))
}))
# by replacement
self.assertEquals(1, b_naptr_value.__cmp__(NaptrValue({
self.assertTrue(b_naptr_value > NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'a',
})))
self.assertEquals(-1, b_naptr_value.__cmp__(NaptrValue({
}))
self.assertTrue(b_naptr_value < NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'z',
})))
}))
# __repr__ doesn't blow up
a.__repr__()
# Hash
v = NaptrValue({
'order': 30,
'preference': 31,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'z',
})
o = NaptrValue({
'order': 30,
'preference': 32,
'flags': 'M',
'service': 'N',
'regexp': 'O',
'replacement': 'z',
})
values = set()
values.add(v)
self.assertTrue(v in values)
self.assertFalse(o in values)
values.add(o)
self.assertTrue(o in values)
def test_ns(self):
a_values = ['5.6.7.8.', '6.7.8.9.', '7.8.9.0.']
a_data = {'ttl': 30, 'values': a_values}
@@ -758,14 +787,14 @@ class TestRecord(TestCase):
# Missing type
with self.assertRaises(Exception) as ctx:
Record.new(self.zone, 'unknown', {})
self.assertTrue('missing type' in ctx.exception.message)
self.assertTrue('missing type' in text_type(ctx.exception))
# Unknown type
with self.assertRaises(Exception) as ctx:
Record.new(self.zone, 'unknown', {
'type': 'XXX',
})
self.assertTrue('Unknown record type' in ctx.exception.message)
self.assertTrue('Unknown record type' in text_type(ctx.exception))
def test_change(self):
existing = Record.new(self.zone, 'txt', {
@@ -796,6 +825,38 @@ class TestRecord(TestCase):
self.assertEquals(values, geo.values)
self.assertEquals(['NA-US', 'NA'], list(geo.parents))
a = GeoValue('NA-US-CA', values)
b = GeoValue('AP-JP', values)
c = GeoValue('NA-US-CA', ['2.3.4.5'])
self.assertEqual(a, a)
self.assertEqual(b, b)
self.assertEqual(c, c)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertTrue(a > b)
self.assertTrue(a < c)
self.assertTrue(b < a)
self.assertTrue(b < c)
self.assertTrue(c > a)
self.assertTrue(c > b)
self.assertTrue(a >= a)
self.assertTrue(a >= b)
self.assertTrue(a <= c)
self.assertTrue(b <= a)
self.assertTrue(b <= b)
self.assertTrue(b <= c)
self.assertTrue(c > a)
self.assertTrue(c > b)
self.assertTrue(c >= b)
def test_healthcheck(self):
new = Record.new(self.zone, 'a', {
'ttl': 44,
@@ -851,11 +912,339 @@ class TestRecord(TestCase):
})
self.assertFalse(new.ignored)
def test_ordering_functions(self):
a = Record.new(self.zone, 'a', {
'ttl': 44,
'type': 'A',
'value': '1.2.3.4',
})
b = Record.new(self.zone, 'b', {
'ttl': 44,
'type': 'A',
'value': '1.2.3.4',
})
c = Record.new(self.zone, 'c', {
'ttl': 44,
'type': 'A',
'value': '1.2.3.4',
})
aaaa = Record.new(self.zone, 'a', {
'ttl': 44,
'type': 'AAAA',
'value': '2601:644:500:e210:62f8:1dff:feb8:947a',
})
self.assertEquals(a, a)
self.assertEquals(b, b)
self.assertEquals(c, c)
self.assertEquals(aaaa, aaaa)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, aaaa)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(b, aaaa)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertNotEqual(c, aaaa)
self.assertNotEqual(aaaa, a)
self.assertNotEqual(aaaa, b)
self.assertNotEqual(aaaa, c)
self.assertTrue(a < b)
self.assertTrue(a < c)
self.assertTrue(a < aaaa)
self.assertTrue(b > a)
self.assertTrue(b < c)
self.assertTrue(b > aaaa)
self.assertTrue(c > a)
self.assertTrue(c > b)
self.assertTrue(c > aaaa)
self.assertTrue(aaaa > a)
self.assertTrue(aaaa < b)
self.assertTrue(aaaa < c)
self.assertTrue(a <= a)
self.assertTrue(a <= b)
self.assertTrue(a <= c)
self.assertTrue(a <= aaaa)
self.assertTrue(b >= a)
self.assertTrue(b >= b)
self.assertTrue(b <= c)
self.assertTrue(b >= aaaa)
self.assertTrue(c >= a)
self.assertTrue(c >= b)
self.assertTrue(c >= c)
self.assertTrue(c >= aaaa)
self.assertTrue(aaaa >= a)
self.assertTrue(aaaa <= b)
self.assertTrue(aaaa <= c)
self.assertTrue(aaaa <= aaaa)
def test_caa_value(self):
a = CaaValue({'flags': 0, 'tag': 'a', 'value': 'v'})
b = CaaValue({'flags': 1, 'tag': 'a', 'value': 'v'})
c = CaaValue({'flags': 0, 'tag': 'c', 'value': 'v'})
d = CaaValue({'flags': 0, 'tag': 'a', 'value': 'z'})
self.assertEqual(a, a)
self.assertEqual(b, b)
self.assertEqual(c, c)
self.assertEqual(d, d)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, d)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(b, d)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertNotEqual(c, d)
self.assertTrue(a < b)
self.assertTrue(a < c)
self.assertTrue(a < d)
self.assertTrue(b > a)
self.assertTrue(b > c)
self.assertTrue(b > d)
self.assertTrue(c > a)
self.assertTrue(c < b)
self.assertTrue(c > d)
self.assertTrue(d > a)
self.assertTrue(d < b)
self.assertTrue(d < c)
self.assertTrue(a <= b)
self.assertTrue(a <= c)
self.assertTrue(a <= d)
self.assertTrue(a <= a)
self.assertTrue(a >= a)
self.assertTrue(b >= a)
self.assertTrue(b >= c)
self.assertTrue(b >= d)
self.assertTrue(b >= b)
self.assertTrue(b <= b)
self.assertTrue(c >= a)
self.assertTrue(c <= b)
self.assertTrue(c >= d)
self.assertTrue(c >= c)
self.assertTrue(c <= c)
self.assertTrue(d >= a)
self.assertTrue(d <= b)
self.assertTrue(d <= c)
self.assertTrue(d >= d)
self.assertTrue(d <= d)
def test_mx_value(self):
a = MxValue({'preference': 0, 'priority': 'a', 'exchange': 'v',
'value': '1'})
b = MxValue({'preference': 10, 'priority': 'a', 'exchange': 'v',
'value': '2'})
c = MxValue({'preference': 0, 'priority': 'b', 'exchange': 'z',
'value': '3'})
self.assertEqual(a, a)
self.assertEqual(b, b)
self.assertEqual(c, c)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertTrue(a < b)
self.assertTrue(a < c)
self.assertTrue(b > a)
self.assertTrue(b > c)
self.assertTrue(c > a)
self.assertTrue(c < b)
self.assertTrue(a <= b)
self.assertTrue(a <= c)
self.assertTrue(a <= a)
self.assertTrue(a >= a)
self.assertTrue(b >= a)
self.assertTrue(b >= c)
self.assertTrue(b >= b)
self.assertTrue(b <= b)
self.assertTrue(c >= a)
self.assertTrue(c <= b)
self.assertTrue(c >= c)
self.assertTrue(c <= c)
def test_sshfp_value(self):
a = SshfpValue({'algorithm': 0, 'fingerprint_type': 0,
'fingerprint': 'abcd'})
b = SshfpValue({'algorithm': 1, 'fingerprint_type': 0,
'fingerprint': 'abcd'})
c = SshfpValue({'algorithm': 0, 'fingerprint_type': 1,
'fingerprint': 'abcd'})
d = SshfpValue({'algorithm': 0, 'fingerprint_type': 0,
'fingerprint': 'bcde'})
self.assertEqual(a, a)
self.assertEqual(b, b)
self.assertEqual(c, c)
self.assertEqual(d, d)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, d)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(b, d)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertNotEqual(c, d)
self.assertNotEqual(d, a)
self.assertNotEqual(d, b)
self.assertNotEqual(d, c)
self.assertTrue(a < b)
self.assertTrue(a < c)
self.assertTrue(b > a)
self.assertTrue(b > c)
self.assertTrue(c > a)
self.assertTrue(c < b)
self.assertTrue(a <= b)
self.assertTrue(a <= c)
self.assertTrue(a <= a)
self.assertTrue(a >= a)
self.assertTrue(b >= a)
self.assertTrue(b >= c)
self.assertTrue(b >= b)
self.assertTrue(b <= b)
self.assertTrue(c >= a)
self.assertTrue(c <= b)
self.assertTrue(c >= c)
self.assertTrue(c <= c)
# Hash
values = set()
values.add(a)
self.assertTrue(a in values)
self.assertFalse(b in values)
values.add(b)
self.assertTrue(b in values)
def test_srv_value(self):
a = SrvValue({'priority': 0, 'weight': 0, 'port': 0, 'target': 'foo.'})
b = SrvValue({'priority': 1, 'weight': 0, 'port': 0, 'target': 'foo.'})
c = SrvValue({'priority': 0, 'weight': 2, 'port': 0, 'target': 'foo.'})
d = SrvValue({'priority': 0, 'weight': 0, 'port': 3, 'target': 'foo.'})
e = SrvValue({'priority': 0, 'weight': 0, 'port': 0, 'target': 'mmm.'})
self.assertEqual(a, a)
self.assertEqual(b, b)
self.assertEqual(c, c)
self.assertEqual(d, d)
self.assertEqual(e, e)
self.assertNotEqual(a, b)
self.assertNotEqual(a, c)
self.assertNotEqual(a, d)
self.assertNotEqual(a, e)
self.assertNotEqual(b, a)
self.assertNotEqual(b, c)
self.assertNotEqual(b, d)
self.assertNotEqual(b, e)
self.assertNotEqual(c, a)
self.assertNotEqual(c, b)
self.assertNotEqual(c, d)
self.assertNotEqual(c, e)
self.assertNotEqual(d, a)
self.assertNotEqual(d, b)
self.assertNotEqual(d, c)
self.assertNotEqual(d, e)
self.assertNotEqual(e, a)
self.assertNotEqual(e, b)
self.assertNotEqual(e, c)
self.assertNotEqual(e, d)
self.assertTrue(a < b)
self.assertTrue(a < c)
self.assertTrue(b > a)
self.assertTrue(b > c)
self.assertTrue(c > a)
self.assertTrue(c < b)
self.assertTrue(a <= b)
self.assertTrue(a <= c)
self.assertTrue(a <= a)
self.assertTrue(a >= a)
self.assertTrue(b >= a)
self.assertTrue(b >= c)
self.assertTrue(b >= b)
self.assertTrue(b <= b)
self.assertTrue(c >= a)
self.assertTrue(c <= b)
self.assertTrue(c >= c)
self.assertTrue(c <= c)
# Hash
values = set()
values.add(a)
self.assertTrue(a in values)
self.assertFalse(b in values)
values.add(b)
self.assertTrue(b in values)
class TestRecordValidation(TestCase):
zone = Zone('unit.tests.', [])
def test_base(self):
# fqdn length, DNS defins max as 253
with self.assertRaises(ValidationError) as ctx:
# The . will put this over the edge
name = 'x' * (253 - len(self.zone.name))
Record.new(self.zone, name, {
'ttl': 300,
'type': 'A',
'value': '1.2.3.4',
})
reason = ctx.exception.reasons[0]
self.assertTrue(reason.startswith('invalid fqdn, "xxxx'))
self.assertTrue(reason.endswith('.unit.tests." is too long at 254'
' chars, max is 253'))
# label length, DNS defins max as 63
with self.assertRaises(ValidationError) as ctx:
# The . will put this over the edge
name = 'x' * 64
Record.new(self.zone, name, {
'ttl': 300,
'type': 'A',
'value': '1.2.3.4',
})
reason = ctx.exception.reasons[0]
self.assertTrue(reason.startswith('invalid name, "xxxx'))
self.assertTrue(reason.endswith('xxx" is too long at 64'
' chars, max is 63'))
# no ttl
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, '', {
@@ -2460,7 +2849,7 @@ class TestDynamicRecords(TestCase):
'weight': 1,
'value': '6.6.6.6',
}, {
'weight': 256,
'weight': 16,
'value': '7.7.7.7',
}],
},
@@ -2484,7 +2873,7 @@ class TestDynamicRecords(TestCase):
}
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, 'bad', a_data)
self.assertEquals(['invalid weight "256" in pool "three" value 2'],
self.assertEquals(['invalid weight "16" in pool "three" value 2'],
ctx.exception.reasons)
# invalid non-int weight
@@ -2845,7 +3234,7 @@ class TestDynamicRecords(TestCase):
self.assertEquals(['rule 1 invalid pool "[]"'],
ctx.exception.reasons)
# rule references non-existant pool
# rule references non-existent pool
a_data = {
'dynamic': {
'pools': {
@@ -2864,7 +3253,7 @@ class TestDynamicRecords(TestCase):
},
'rules': [{
'geos': ['NA-US-CA'],
'pool': 'non-existant',
'pool': 'non-existent',
}, {
'pool': 'one',
}],
@@ -2878,7 +3267,7 @@ class TestDynamicRecords(TestCase):
}
with self.assertRaises(ValidationError) as ctx:
Record.new(self.zone, 'bad', a_data)
self.assertEquals(["rule 1 undefined pool \"non-existant\""],
self.assertEquals(["rule 1 undefined pool \"non-existent\""],
ctx.exception.reasons)
# rule with invalid geos
+3 -2
View File
@@ -9,6 +9,7 @@ import dns.zone
from dns.exception import DNSException
from mock import patch
from six import text_type
from unittest import TestCase
from octodns.source.axfr import AxfrSource, AxfrSourceZoneTransferFailed, \
@@ -38,7 +39,7 @@ class TestAxfrSource(TestCase):
zone = Zone('unit.tests.', [])
self.source.populate(zone)
self.assertEquals('Unable to Perform Zone Transfer',
ctx.exception.message)
text_type(ctx.exception))
class TestZoneFileSource(TestCase):
@@ -68,4 +69,4 @@ class TestZoneFileSource(TestCase):
zone = Zone('invalid.zone.', [])
self.source.populate(zone)
self.assertEquals('The DNS zone has no NS RRset at its origin.',
ctx.exception.message)
text_type(ctx.exception))
+1 -1
View File
@@ -5,7 +5,7 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
from StringIO import StringIO
from six import StringIO
from unittest import TestCase
from yaml.constructor import ConstructorError
+6 -5
View File
@@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, \
unicode_literals
from unittest import TestCase
from six import text_type
from octodns.record import ARecord, AaaaRecord, Create, Delete, Record, Update
from octodns.zone import DuplicateRecordException, InvalidNodeException, \
@@ -47,7 +48,7 @@ class TestZone(TestCase):
with self.assertRaises(DuplicateRecordException) as ctx:
zone.add_record(a)
self.assertEquals('Duplicate record a.unit.tests., type A',
ctx.exception.message)
text_type(ctx.exception))
self.assertEquals(zone.records, set([a]))
# can add duplicate with replace=True
@@ -137,7 +138,7 @@ class TestZone(TestCase):
def test_missing_dot(self):
with self.assertRaises(Exception) as ctx:
Zone('not.allowed', [])
self.assertTrue('missing ending dot' in ctx.exception.message)
self.assertTrue('missing ending dot' in text_type(ctx.exception))
def test_sub_zones(self):
@@ -160,7 +161,7 @@ class TestZone(TestCase):
})
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('not of type NS', ctx.exception.message)
self.assertTrue('not of type NS', text_type(ctx.exception))
# Can add it w/lenient
zone.add_record(record, lenient=True)
self.assertEquals(set([record]), zone.records)
@@ -174,7 +175,7 @@ class TestZone(TestCase):
})
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('under a managed sub-zone', ctx.exception.message)
self.assertTrue('under a managed sub-zone', text_type(ctx.exception))
# Can add it w/lenient
zone.add_record(record, lenient=True)
self.assertEquals(set([record]), zone.records)
@@ -188,7 +189,7 @@ class TestZone(TestCase):
})
with self.assertRaises(SubzoneRecordException) as ctx:
zone.add_record(record)
self.assertTrue('under a managed sub-zone', ctx.exception.message)
self.assertTrue('under a managed sub-zone', text_type(ctx.exception))
# Can add it w/lenient
zone.add_record(record, lenient=True)
self.assertEquals(set([record]), zone.records)