mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
Many providers make their modifications in the order that changes comes. In python3 this causes things to be inconsistently ordered. That mostly works, but could result in hidenbugs (e.g. Route53Provider's batching could be completely different based on the order it sees changes.) Sorting changes consistently is a good thing and it shouldn't hurt situations where providers are already doing their own ordering. All-in-all more consistent is better and we have to be explicit with python 3.
2558 lines
88 KiB
Python
2558 lines
88 KiB
Python
#
|
|
#
|
|
#
|
|
|
|
from __future__ import absolute_import, division, print_function, \
|
|
unicode_literals
|
|
|
|
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, \
|
|
_Route53DynamicValue, _Route53GeoRecord, _Route53Record, _mod_keyer, \
|
|
_octal_replace
|
|
from octodns.zone import Zone
|
|
|
|
from helpers import GeoProvider
|
|
|
|
|
|
class DummyR53Record(object):
|
|
|
|
def __init__(self, health_check_id):
|
|
self.health_check_id = health_check_id
|
|
|
|
|
|
class TestOctalReplace(TestCase):
|
|
|
|
def test_basic(self):
|
|
for expected, s in (
|
|
('', ''),
|
|
('abc', 'abc'),
|
|
('123', '123'),
|
|
('abc123', 'abc123'),
|
|
('*', '\\052'),
|
|
('abc*', 'abc\\052'),
|
|
('*abc', '\\052abc'),
|
|
('123*', '123\\052'),
|
|
('*123', '\\052123'),
|
|
('**', '\\052\\052'),
|
|
):
|
|
self.assertEquals(expected, _octal_replace(s))
|
|
|
|
|
|
dynamic_rrsets = [{
|
|
'Name': '_octodns-default-pool.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.1.2.1'},
|
|
{'Value': '1.1.2.2'}],
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
}, {
|
|
'HealthCheckId': '76',
|
|
'Name': '_octodns-ap-southeast-1-value.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.4.1.1'}],
|
|
'SetIdentifier': 'ap-southeast-1-000',
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
'Weight': 2
|
|
}, {
|
|
'HealthCheckId': '09',
|
|
'Name': '_octodns-ap-southeast-1-value.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.4.1.2'}],
|
|
'SetIdentifier': 'ap-southeast-1-001',
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
'Weight': 2
|
|
}, {
|
|
'HealthCheckId': 'ab',
|
|
'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
|
|
}, {
|
|
'HealthCheckId': '1e',
|
|
'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
|
|
}, {
|
|
'HealthCheckId': '2a',
|
|
'Name': '_octodns-us-east-1-value.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.5.1.1'}],
|
|
'SetIdentifier': 'us-east-1-000',
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
'Weight': 1
|
|
}, {
|
|
'HealthCheckId': '61',
|
|
'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,
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-default-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'Failover': 'SECONDARY',
|
|
'Name': '_octodns-us-east-1-pool.unit.tests.',
|
|
'SetIdentifier': 'us-east-1-Secondary-default',
|
|
'Type': 'A'
|
|
}, {
|
|
'AliasTarget': {
|
|
'DNSName': '_octodns-us-east-1-value.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'
|
|
},
|
|
'Failover': 'PRIMARY',
|
|
'Name': '_octodns-us-east-1-pool.unit.tests.',
|
|
'SetIdentifier': 'us-east-1-Primary',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-us-east-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'Failover': 'SECONDARY',
|
|
'Name': '_octodns-eu-central-1-pool.unit.tests.',
|
|
'SetIdentifier': 'eu-central-1-Secondary-default',
|
|
'Type': 'A'
|
|
}, {
|
|
'AliasTarget': {
|
|
'DNSName': '_octodns-eu-central-1-value.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'
|
|
},
|
|
'Failover': 'PRIMARY',
|
|
'Name': '_octodns-eu-central-1-pool.unit.tests.',
|
|
'SetIdentifier': 'eu-central-1-Primary',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-us-east-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'Failover': 'SECONDARY',
|
|
'Name': '_octodns-ap-southeast-1-pool.unit.tests.',
|
|
'SetIdentifier': 'ap-southeast-1-Secondary-default',
|
|
'Type': 'A'
|
|
}, {
|
|
'AliasTarget': {
|
|
'DNSName': '_octodns-ap-southeast-1-value.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'
|
|
},
|
|
'Failover': 'PRIMARY',
|
|
'Name': '_octodns-ap-southeast-1-pool.unit.tests.',
|
|
'SetIdentifier': 'ap-southeast-1-Primary',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'GeoLocation': {'CountryCode': 'JP'},
|
|
'Name': 'unit.tests.',
|
|
'SetIdentifier': '1-ap-southeast-1-AS-JP',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-ap-southeast-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'GeoLocation': {'CountryCode': 'CN'},
|
|
'Name': 'unit.tests.',
|
|
'SetIdentifier': '1-ap-southeast-1-AS-CN',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'GeoLocation': {'ContinentCode': 'NA-US-FL'},
|
|
'Name': 'unit.tests.',
|
|
'SetIdentifier': '2-eu-central-1-NA-US-FL',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-eu-central-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'GeoLocation': {'ContinentCode': 'EU'},
|
|
'Name': 'unit.tests.',
|
|
'SetIdentifier': '2-eu-central-1-EU',
|
|
'Type': 'A',
|
|
}, {
|
|
'AliasTarget': {'DNSName': '_octodns-us-east-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'Z2'},
|
|
'GeoLocation': {'CountryCode': '*'},
|
|
'Name': 'unit.tests.',
|
|
'SetIdentifier': '3-us-east-1-None',
|
|
'Type': 'A',
|
|
}]
|
|
|
|
dynamic_record_data = {
|
|
'dynamic': {
|
|
'pools': {
|
|
'ap-southeast-1': {
|
|
'fallback': 'us-east-1',
|
|
'values': [{
|
|
'weight': 2, 'value': '1.4.1.1'
|
|
}, {
|
|
'weight': 2, 'value': '1.4.1.2'
|
|
}]
|
|
},
|
|
'eu-central-1': {
|
|
'fallback': 'us-east-1',
|
|
'values': [{
|
|
'weight': 1, 'value': '1.3.1.1'
|
|
}, {
|
|
'weight': 1, 'value': '1.3.1.2'
|
|
}],
|
|
},
|
|
'us-east-1': {
|
|
'values': [{
|
|
'weight': 1, 'value': '1.5.1.1'
|
|
}, {
|
|
'weight': 1, 'value': '1.5.1.2'
|
|
}],
|
|
}
|
|
},
|
|
'rules': [{
|
|
'geos': ['AS-CN', 'AS-JP'],
|
|
'pool': 'ap-southeast-1',
|
|
}, {
|
|
'geos': ['EU', 'NA-US-FL'],
|
|
'pool': 'eu-central-1',
|
|
}, {
|
|
'pool': 'us-east-1',
|
|
}],
|
|
},
|
|
'ttl': 60,
|
|
'type': 'A',
|
|
'values': [
|
|
'1.1.2.1',
|
|
'1.1.2.2',
|
|
],
|
|
}
|
|
|
|
|
|
class TestRoute53Provider(TestCase):
|
|
expected = Zone('unit.tests.', [])
|
|
for name, data in (
|
|
('simple',
|
|
{'ttl': 60, 'type': 'A', 'values': ['1.2.3.4', '2.2.3.4']}),
|
|
('',
|
|
{'ttl': 61, 'type': 'A', 'values': ['2.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
'NA-US': ['5.2.3.4', '6.2.3.4'],
|
|
'NA-US-CA': ['7.2.3.4']}}),
|
|
('cname', {'ttl': 62, 'type': 'CNAME', 'value': 'unit.tests.'}),
|
|
('txt', {'ttl': 63, 'type': 'TXT', 'values': ['Hello World!',
|
|
'Goodbye World?']}),
|
|
('', {'ttl': 64, 'type': 'MX',
|
|
'values': [{
|
|
'preference': 10,
|
|
'exchange': 'smtp-1.unit.tests.',
|
|
}, {
|
|
'preference': 20,
|
|
'exchange': 'smtp-2.unit.tests.',
|
|
}]}),
|
|
('naptr', {'ttl': 65, 'type': 'NAPTR',
|
|
'value': {
|
|
'order': 10,
|
|
'preference': 20,
|
|
'flags': 'U',
|
|
'service': 'SIP+D2U',
|
|
'regexp': '!^.*$!sip:info@bar.example.com!',
|
|
'replacement': '.',
|
|
}}),
|
|
('_srv._tcp', {'ttl': 66, 'type': 'SRV', 'value': {
|
|
'priority': 10,
|
|
'weight': 20,
|
|
'port': 30,
|
|
'target': 'cname.unit.tests.'
|
|
}}),
|
|
('',
|
|
{'ttl': 67, 'type': 'NS', 'values': ['8.2.3.4.', '9.2.3.4.']}),
|
|
('sub',
|
|
{'ttl': 68, 'type': 'NS', 'values': ['5.2.3.4.', '6.2.3.4.']}),
|
|
('',
|
|
{'ttl': 69, 'type': 'CAA', 'value': {
|
|
'flags': 0,
|
|
'tag': 'issue',
|
|
'value': 'ca.unit.tests'
|
|
}}),
|
|
):
|
|
record = Record.new(expected, name, data)
|
|
expected.add_record(record)
|
|
|
|
caller_ref = '{}:A:unit.tests.:1324' \
|
|
.format(Route53Provider.HEALTH_CHECK_VERSION)
|
|
health_checks = [{
|
|
'Id': '42',
|
|
'CallerReference': caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': 'ignored-also',
|
|
'CallerReference': 'something-else',
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '5.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 42,
|
|
}, {
|
|
'Id': '43',
|
|
'CallerReference': caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '5.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '44',
|
|
'CallerReference': caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '7.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '45',
|
|
# won't match anything based on type
|
|
'CallerReference': caller_ref.replace(':A:', ':AAAA:'),
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '7.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}]
|
|
|
|
def _get_stubbed_provider(self):
|
|
provider = Route53Provider('test', 'abc', '123')
|
|
|
|
# Use the stubber
|
|
stubber = Stubber(provider._conn)
|
|
stubber.activate()
|
|
|
|
return (provider, stubber)
|
|
|
|
def _get_stubbed_fallback_auth_provider(self):
|
|
provider = Route53Provider('test')
|
|
|
|
# Use the stubber
|
|
stubber = Stubber(provider._conn)
|
|
stubber.activate()
|
|
|
|
return (provider, stubber)
|
|
|
|
def test_populate_with_fallback(self):
|
|
provider, stubber = self._get_stubbed_fallback_auth_provider()
|
|
|
|
got = Zone('unit.tests.', [])
|
|
with self.assertRaises(ClientError):
|
|
stubber.add_client_error('list_hosted_zones')
|
|
provider.populate(got)
|
|
|
|
def test_populate(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
got = Zone('unit.tests.', [])
|
|
with self.assertRaises(ClientError):
|
|
stubber.add_client_error('list_hosted_zones')
|
|
provider.populate(got)
|
|
|
|
with self.assertRaises(ClientError):
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp,
|
|
{})
|
|
stubber.add_client_error('list_resource_record_sets',
|
|
expected_params={'HostedZoneId': u'z42'})
|
|
provider.populate(got)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# list_hosted_zones has been cached from now on so we don't have to
|
|
# worry about stubbing it
|
|
|
|
list_resource_record_sets_resp_p1 = {
|
|
'ResourceRecordSets': [{
|
|
'Name': 'simple.unit.tests.',
|
|
'Type': 'A',
|
|
'ResourceRecords': [{
|
|
'Value': '1.2.3.4',
|
|
}, {
|
|
'Value': '2.2.3.4',
|
|
}],
|
|
'TTL': 60,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': '*',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '2.2.3.4',
|
|
}, {
|
|
'Value': '3.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'ContinentCode': 'AF',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '4.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': 'US',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '5.2.3.4',
|
|
}, {
|
|
'Value': '6.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': 'US',
|
|
'SubdivisionCode': 'CA',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '7.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}],
|
|
'IsTruncated': True,
|
|
'NextRecordName': 'next_name',
|
|
'NextRecordType': 'next_type',
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_resource_record_sets',
|
|
list_resource_record_sets_resp_p1,
|
|
{'HostedZoneId': 'z42'})
|
|
|
|
list_resource_record_sets_resp_p2 = {
|
|
'ResourceRecordSets': [{
|
|
'Name': 'cname.unit.tests.',
|
|
'Type': 'CNAME',
|
|
'ResourceRecords': [{
|
|
'Value': 'unit.tests.',
|
|
}],
|
|
'TTL': 62,
|
|
}, {
|
|
'Name': 'txt.unit.tests.',
|
|
'Type': 'TXT',
|
|
'ResourceRecords': [{
|
|
'Value': '"Hello World!"',
|
|
}, {
|
|
'Value': '"Goodbye World?"',
|
|
}],
|
|
'TTL': 63,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'MX',
|
|
'ResourceRecords': [{
|
|
'Value': '10 smtp-1.unit.tests.',
|
|
}, {
|
|
'Value': '20 smtp-2.unit.tests.',
|
|
}],
|
|
'TTL': 64,
|
|
}, {
|
|
'Name': 'naptr.unit.tests.',
|
|
'Type': 'NAPTR',
|
|
'ResourceRecords': [{
|
|
'Value': '10 20 "U" "SIP+D2U" '
|
|
'"!^.*$!sip:info@bar.example.com!" .',
|
|
}],
|
|
'TTL': 65,
|
|
}, {
|
|
'Name': '_srv._tcp.unit.tests.',
|
|
'Type': 'SRV',
|
|
'ResourceRecords': [{
|
|
'Value': '10 20 30 cname.unit.tests.',
|
|
}],
|
|
'TTL': 66,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'NS',
|
|
'ResourceRecords': [{
|
|
'Value': 'ns1.unit.tests.',
|
|
}],
|
|
'TTL': 67,
|
|
}, {
|
|
'Name': 'sub.unit.tests.',
|
|
'Type': 'NS',
|
|
'GeoLocation': {
|
|
'ContinentCode': 'AF',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '5.2.3.4.',
|
|
}, {
|
|
'Value': '6.2.3.4.',
|
|
}],
|
|
'TTL': 68,
|
|
}, {
|
|
'Name': 'soa.unit.tests.',
|
|
'Type': 'SOA',
|
|
'ResourceRecords': [{
|
|
'Value': 'ns1.unit.tests.',
|
|
}],
|
|
'TTL': 69,
|
|
}, {
|
|
'Name': 'unit.tests.',
|
|
'Type': 'CAA',
|
|
'ResourceRecords': [{
|
|
'Value': '0 issue "ca.unit.tests"',
|
|
}],
|
|
'TTL': 69,
|
|
}, {
|
|
'AliasTarget': {
|
|
'HostedZoneId': 'Z119WBBTVP5WFX',
|
|
'EvaluateTargetHealth': False,
|
|
'DNSName': 'unit.tests.'
|
|
},
|
|
'Type': 'A',
|
|
'Name': 'alias.unit.tests.'
|
|
}],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_resource_record_sets',
|
|
list_resource_record_sets_resp_p2,
|
|
{'HostedZoneId': 'z42',
|
|
'StartRecordName': 'next_name',
|
|
'StartRecordType': 'next_type'})
|
|
|
|
# Load everything
|
|
provider.populate(got)
|
|
# Make sure we got what we expected
|
|
changes = self.expected.changes(got, GeoProvider())
|
|
self.assertEquals(0, len(changes))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# Populate a zone that doesn't exist
|
|
nonexistent = Zone('does.not.exist.', [])
|
|
provider.populate(nonexistent)
|
|
self.assertEquals(set(), nonexistent.records)
|
|
|
|
def test_sync(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp,
|
|
{})
|
|
list_resource_record_sets_resp = {
|
|
'ResourceRecordSets': [],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_resource_record_sets',
|
|
list_resource_record_sets_resp,
|
|
{'HostedZoneId': 'z42'})
|
|
|
|
plan = provider.plan(self.expected)
|
|
self.assertEquals(9, len(plan.changes))
|
|
self.assertTrue(plan.exists)
|
|
for change in plan.changes:
|
|
self.assertIsInstance(change, Create)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
stubber.add_response('list_health_checks',
|
|
{
|
|
'HealthChecks': self.health_checks,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, {'HostedZoneId': 'z42', 'ChangeBatch': ANY})
|
|
|
|
self.assertEquals(9, provider.apply(plan))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# Delete by monkey patching in a populate that includes an extra record
|
|
def add_extra_populate(existing, target, lenient):
|
|
for record in self.expected.records:
|
|
existing.add_record(record)
|
|
record = Record.new(existing, 'extra',
|
|
{'ttl': 99, 'type': 'A',
|
|
'values': ['9.9.9.9']})
|
|
existing.add_record(record)
|
|
|
|
provider.populate = add_extra_populate
|
|
change_resource_record_sets_params = {
|
|
'ChangeBatch': {
|
|
'Changes': [{
|
|
'Action': 'DELETE', 'ResourceRecordSet': {
|
|
'Name': 'extra.unit.tests.',
|
|
'ResourceRecords': [{'Value': u'9.9.9.9'}],
|
|
'TTL': 99,
|
|
'Type': 'A'
|
|
}}],
|
|
u'Comment': ANY
|
|
},
|
|
'HostedZoneId': u'z42'
|
|
}
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, change_resource_record_sets_params)
|
|
plan = provider.plan(self.expected)
|
|
self.assertEquals(1, len(plan.changes))
|
|
self.assertIsInstance(plan.changes[0], Delete)
|
|
self.assertEquals(1, provider.apply(plan))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# Update by monkey patching in a populate that modifies the A record
|
|
# with geos
|
|
def mod_geo_populate(existing, target, lenient):
|
|
for record in self.expected.records:
|
|
if record._type != 'A' or not record.geo:
|
|
existing.add_record(record)
|
|
record = Record.new(existing, '', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['8.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
'NA-US': ['5.2.3.4', '6.2.3.4'],
|
|
'NA-US-KY': ['7.2.3.4']
|
|
}
|
|
})
|
|
existing.add_record(record)
|
|
|
|
provider.populate = mod_geo_populate
|
|
change_resource_record_sets_params = {
|
|
'ChangeBatch': {
|
|
'Changes': [{
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': {'CountryCode': 'US',
|
|
'SubdivisionCode': 'KY'},
|
|
'HealthCheckId': u'44',
|
|
'Name': 'unit.tests.',
|
|
'ResourceRecords': [{'Value': '7.2.3.4'}],
|
|
'SetIdentifier': 'NA-US-KY',
|
|
'TTL': 61,
|
|
'Type': 'A'
|
|
}
|
|
}, {
|
|
'Action': 'UPSERT',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': {'ContinentCode': 'AF'},
|
|
'Name': 'unit.tests.',
|
|
'HealthCheckId': u'42',
|
|
'ResourceRecords': [{'Value': '4.2.3.4'}],
|
|
'SetIdentifier': 'AF',
|
|
'TTL': 61,
|
|
'Type': 'A'
|
|
}
|
|
}, {
|
|
'Action': 'UPSERT',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': {'CountryCode': 'US'},
|
|
'HealthCheckId': u'43',
|
|
'Name': 'unit.tests.',
|
|
'ResourceRecords': [{'Value': '5.2.3.4'},
|
|
{'Value': '6.2.3.4'}],
|
|
'SetIdentifier': 'NA-US',
|
|
'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': {
|
|
'GeoLocation': {'CountryCode': '*'},
|
|
'Name': 'unit.tests.',
|
|
'ResourceRecords': [{'Value': '2.2.3.4'},
|
|
{'Value': '3.2.3.4'}],
|
|
'SetIdentifier': 'default',
|
|
'TTL': 61,
|
|
'Type': 'A'
|
|
}
|
|
}],
|
|
'Comment': ANY
|
|
},
|
|
'HostedZoneId': 'z42'
|
|
}
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, change_resource_record_sets_params)
|
|
plan = provider.plan(self.expected)
|
|
self.assertEquals(1, len(plan.changes))
|
|
self.assertIsInstance(plan.changes[0], Update)
|
|
self.assertEquals(1, provider.apply(plan))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# Update converting to non-geo by monkey patching in a populate that
|
|
# modifies the A record with geos
|
|
def mod_add_geo_populate(existing, target, lenient):
|
|
for record in self.expected.records:
|
|
if record._type != 'A' or record.geo:
|
|
existing.add_record(record)
|
|
record = Record.new(existing, 'simple', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['1.2.3.4', '2.2.3.4'],
|
|
'geo': {
|
|
'OC': ['3.2.3.4', '4.2.3.4'],
|
|
}
|
|
})
|
|
existing.add_record(record)
|
|
|
|
provider.populate = mod_add_geo_populate
|
|
change_resource_record_sets_params = {
|
|
'ChangeBatch': {
|
|
'Changes': [{
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': {'ContinentCode': 'OC'},
|
|
'Name': 'simple.unit.tests.',
|
|
'ResourceRecords': [{'Value': '3.2.3.4'},
|
|
{'Value': '4.2.3.4'}],
|
|
'SetIdentifier': 'OC',
|
|
'TTL': 61,
|
|
'Type': 'A'}
|
|
}, {
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': {'CountryCode': '*'},
|
|
'Name': 'simple.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.2.3.4'},
|
|
{'Value': '2.2.3.4'}],
|
|
'SetIdentifier': 'default',
|
|
'TTL': 61,
|
|
'Type': 'A'}
|
|
}, {
|
|
'Action': 'CREATE',
|
|
'ResourceRecordSet': {
|
|
'Name': 'simple.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.2.3.4'},
|
|
{'Value': '2.2.3.4'}],
|
|
'TTL': 60,
|
|
'Type': 'A'}
|
|
}],
|
|
'Comment': ANY
|
|
},
|
|
'HostedZoneId': 'z42'
|
|
}
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, change_resource_record_sets_params)
|
|
plan = provider.plan(self.expected)
|
|
self.assertEquals(1, len(plan.changes))
|
|
self.assertIsInstance(plan.changes[0], Update)
|
|
self.assertEquals(1, provider.apply(plan))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_sync_create(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
got = Zone('unit.tests.', [])
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp,
|
|
{})
|
|
|
|
plan = provider.plan(self.expected)
|
|
self.assertEquals(9, len(plan.changes))
|
|
self.assertFalse(plan.exists)
|
|
for change in plan.changes:
|
|
self.assertIsInstance(change, Create)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
create_hosted_zone_resp = {
|
|
'HostedZone': {
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
},
|
|
'ChangeInfo': {
|
|
'Id': 'a12',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
'Comment': 'hrm',
|
|
},
|
|
'DelegationSet': {
|
|
'Id': 'b23',
|
|
'CallerReference': 'blip',
|
|
'NameServers': [
|
|
'n12.unit.tests.',
|
|
],
|
|
},
|
|
'Location': 'us-east-1',
|
|
}
|
|
stubber.add_response('create_hosted_zone',
|
|
create_hosted_zone_resp, {
|
|
'Name': got.name,
|
|
'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,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, {'HostedZoneId': 'z42', 'ChangeBatch': ANY})
|
|
|
|
self.assertEquals(9, provider.apply(plan))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_health_checks_pagination(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
health_checks_p1 = [{
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '43',
|
|
'CallerReference': 'abc123',
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '9.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}]
|
|
stubber.add_response('list_health_checks',
|
|
{
|
|
'HealthChecks': health_checks_p1,
|
|
'IsTruncated': True,
|
|
'MaxItems': '2',
|
|
'Marker': '',
|
|
'NextMarker': 'moar',
|
|
})
|
|
|
|
health_checks_p2 = [{
|
|
'Id': '44',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '8.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}]
|
|
stubber.add_response('list_health_checks',
|
|
{
|
|
'HealthChecks': health_checks_p2,
|
|
'IsTruncated': False,
|
|
'MaxItems': '2',
|
|
'Marker': 'moar',
|
|
}, {'Marker': 'moar'})
|
|
|
|
health_checks = provider.health_checks
|
|
self.assertEquals({
|
|
'42': health_checks_p1[0],
|
|
'44': health_checks_p2[0],
|
|
}, health_checks)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# get without create
|
|
record = Record.new(self.expected, '', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['2.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
}
|
|
})
|
|
value = record.geo['AF'].values[0]
|
|
id = provider.get_health_check_id(record, value, True)
|
|
self.assertEquals('42', id)
|
|
|
|
def test_health_check_create(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
# No match based on type
|
|
caller_ref = \
|
|
'{}:AAAA:foo1234'.format(Route53Provider.HEALTH_CHECK_VERSION)
|
|
health_checks = [{
|
|
'Id': '42',
|
|
# No match based on version
|
|
'CallerReference': '9999:A:foo1234',
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '43',
|
|
'CallerReference': caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}]
|
|
stubber.add_response('list_health_checks', {
|
|
'HealthChecks': health_checks,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
health_check_config = {
|
|
'EnableSNI': False,
|
|
'FailureThreshold': 6,
|
|
'FullyQualifiedDomainName': 'foo.bar.com',
|
|
'IPAddress': '4.2.3.4',
|
|
'MeasureLatency': True,
|
|
'Port': 8080,
|
|
'RequestInterval': 10,
|
|
'ResourcePath': '/_status',
|
|
'Type': 'HTTP'
|
|
}
|
|
stubber.add_response('create_health_check', {
|
|
'HealthCheck': {
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': health_check_config,
|
|
'HealthCheckVersion': 1,
|
|
},
|
|
'Location': 'http://url',
|
|
}, {
|
|
'CallerReference': ANY,
|
|
'HealthCheckConfig': health_check_config,
|
|
})
|
|
stubber.add_response('change_tags_for_resource', {})
|
|
|
|
record = Record.new(self.expected, '', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['2.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
},
|
|
'octodns': {
|
|
'healthcheck': {
|
|
'host': 'foo.bar.com',
|
|
'path': '/_status',
|
|
'port': 8080,
|
|
'protocol': 'HTTP',
|
|
},
|
|
}
|
|
})
|
|
|
|
# if not allowed to create returns none
|
|
value = record.geo['AF'].values[0]
|
|
id = provider.get_health_check_id(record, value, False)
|
|
self.assertFalse(id)
|
|
|
|
# when allowed to create we do
|
|
id = provider.get_health_check_id(record, value, True)
|
|
self.assertEquals('42', id)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# A CNAME style healthcheck, without a value
|
|
|
|
health_check_config = {
|
|
'EnableSNI': False,
|
|
'FailureThreshold': 6,
|
|
'FullyQualifiedDomainName': 'target-1.unit.tests.',
|
|
'MeasureLatency': True,
|
|
'Port': 8080,
|
|
'RequestInterval': 10,
|
|
'ResourcePath': '/_status',
|
|
'Type': 'HTTP'
|
|
}
|
|
stubber.add_response('create_health_check', {
|
|
'HealthCheck': {
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': health_check_config,
|
|
'HealthCheckVersion': 1,
|
|
},
|
|
'Location': 'http://url',
|
|
}, {
|
|
'CallerReference': ANY,
|
|
'HealthCheckConfig': health_check_config,
|
|
})
|
|
stubber.add_response('change_tags_for_resource', {})
|
|
|
|
id = provider.get_health_check_id(record, 'target-1.unit.tests.', True)
|
|
self.assertEquals('42', id)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_health_check_measure_latency(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
record_true = Record.new(self.expected, 'a', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'octodns': {
|
|
'healthcheck': {
|
|
},
|
|
'route53': {
|
|
'healthcheck': {
|
|
'measure_latency': True
|
|
}
|
|
}
|
|
}
|
|
})
|
|
measure_latency = provider._healthcheck_measure_latency(record_true)
|
|
self.assertTrue(measure_latency)
|
|
|
|
record_default = Record.new(self.expected, 'a', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
})
|
|
measure_latency = provider._healthcheck_measure_latency(record_default)
|
|
self.assertTrue(measure_latency)
|
|
|
|
record_false = Record.new(self.expected, 'a', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'octodns': {
|
|
'healthcheck': {
|
|
},
|
|
'route53': {
|
|
'healthcheck': {
|
|
'measure_latency': False
|
|
}
|
|
}
|
|
}
|
|
})
|
|
measure_latency = provider._healthcheck_measure_latency(record_false)
|
|
self.assertFalse(measure_latency)
|
|
|
|
def test_create_health_checks_measure_latency(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
health_check_config = {
|
|
'EnableSNI': True,
|
|
'FailureThreshold': 6,
|
|
'FullyQualifiedDomainName': 'a.unit.tests',
|
|
'IPAddress': '1.2.3.4',
|
|
'MeasureLatency': False,
|
|
'Port': 443,
|
|
'RequestInterval': 10,
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS'
|
|
}
|
|
|
|
stubber.add_response('list_health_checks', {
|
|
'HealthChecks': [],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
stubber.add_response('create_health_check', {
|
|
'HealthCheck': {
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': health_check_config,
|
|
'HealthCheckVersion': 1,
|
|
},
|
|
'Location': 'http://url',
|
|
}, {
|
|
'CallerReference': ANY,
|
|
'HealthCheckConfig': health_check_config,
|
|
})
|
|
stubber.add_response('change_tags_for_resource', {})
|
|
stubber.add_response('change_tags_for_resource', {})
|
|
|
|
record = Record.new(self.expected, 'a', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'value': '2.2.3.4',
|
|
'geo': {
|
|
'AF': ['1.2.3.4'],
|
|
},
|
|
'octodns': {
|
|
'healthcheck': {
|
|
},
|
|
'route53': {
|
|
'healthcheck': {
|
|
'measure_latency': False
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
value = record.geo['AF'].values[0]
|
|
id = provider.get_health_check_id(record, value, True)
|
|
ml = provider.health_checks[id]['HealthCheckConfig']['MeasureLatency']
|
|
self.assertEqual(False, ml)
|
|
|
|
def test_health_check_gc(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
stubber.add_response('list_health_checks', {
|
|
'HealthChecks': self.health_checks,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
record = Record.new(self.expected, '', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['2.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
'NA-US': ['5.2.3.4', '6.2.3.4'],
|
|
# removed one geo
|
|
}
|
|
})
|
|
|
|
# gc no longer in_use records (directly)
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': '44',
|
|
})
|
|
provider._gc_health_checks(record, [
|
|
DummyR53Record('42'),
|
|
DummyR53Record('43'),
|
|
])
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# gc through _mod_Create
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': '44',
|
|
})
|
|
change = Create(record)
|
|
provider._mod_Create(change, 'z43', [])
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# gc through _mod_Update
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': '44',
|
|
})
|
|
# first record is ignored for our purposes, we have to pass something
|
|
change = Update(record, record)
|
|
provider._mod_Create(change, 'z43', [])
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# gc through _mod_Delete, expect 3 to go away, can't check order
|
|
# b/c it's not deterministic
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': ANY,
|
|
})
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': ANY,
|
|
})
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': ANY,
|
|
})
|
|
change = Delete(record)
|
|
provider._mod_Delete(change, 'z43', [])
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# gc only AAAA, leave the A's alone
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': '45',
|
|
})
|
|
record = Record.new(self.expected, '', {
|
|
'ttl': 61,
|
|
'type': 'AAAA',
|
|
'value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
|
|
})
|
|
provider._gc_health_checks(record, [])
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_legacy_health_check_gc(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
old_caller_ref = '0000:A:3333'
|
|
health_checks = [{
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '43',
|
|
'CallerReference': old_caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}, {
|
|
'Id': '44',
|
|
'CallerReference': old_caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'other.unit.tests',
|
|
'IPAddress': '4.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}]
|
|
|
|
stubber.add_response('list_health_checks', {
|
|
'HealthChecks': health_checks,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
# No changes to the record itself
|
|
record = Record.new(self.expected, '', {
|
|
'ttl': 61,
|
|
'type': 'A',
|
|
'values': ['2.2.3.4', '3.2.3.4'],
|
|
'geo': {
|
|
'AF': ['4.2.3.4'],
|
|
'NA-US': ['5.2.3.4', '6.2.3.4'],
|
|
'NA-US-CA': ['7.2.3.4']
|
|
}
|
|
})
|
|
|
|
# Expect to delete the legacy hc for our record, but not touch the new
|
|
# one or the other legacy record
|
|
stubber.add_response('delete_health_check', {}, {
|
|
'HealthCheckId': '43',
|
|
})
|
|
|
|
provider._gc_health_checks(record, [
|
|
DummyR53Record('42'),
|
|
])
|
|
|
|
def test_no_extra_changes(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
|
|
|
# empty is empty
|
|
desired = Zone('unit.tests.', [])
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals([], extra)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# single record w/o geo is empty
|
|
desired = Zone('unit.tests.', [])
|
|
record = Record.new(desired, 'a', {
|
|
'ttl': 30,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
})
|
|
desired.add_record(record)
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals([], extra)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# short-circuit for unknown zone
|
|
other = Zone('other.tests.', [])
|
|
extra = provider._extra_changes(desired=other, changes=[])
|
|
self.assertEquals([], extra)
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_extra_change_no_health_check(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
|
|
|
# record with geo and no health check returns change
|
|
desired = Zone('unit.tests.', [])
|
|
record = Record.new(desired, 'a', {
|
|
'ttl': 30,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'geo': {
|
|
'NA': ['2.2.3.4'],
|
|
}
|
|
})
|
|
desired.add_record(record)
|
|
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'})
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(1, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_extra_change_has_wrong_health_check(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
|
|
|
# record with geo and no health check returns change
|
|
desired = Zone('unit.tests.', [])
|
|
record = Record.new(desired, 'a', {
|
|
'ttl': 30,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'geo': {
|
|
'NA': ['2.2.3.4'],
|
|
}
|
|
})
|
|
desired.add_record(record)
|
|
list_resource_record_sets_resp = {
|
|
'ResourceRecordSets': [{
|
|
'Name': 'a.unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'ContinentCode': 'NA',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '2.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
'HealthCheckId': '42',
|
|
}],
|
|
'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': [{
|
|
'Id': '42',
|
|
'CallerReference': 'foo',
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'unit.tests',
|
|
'IPAddress': '2.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True,
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(1, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
for change in (Create(record), Update(record, record), Delete(record)):
|
|
extra = provider._extra_changes(desired=desired, changes=[change])
|
|
self.assertEquals(0, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_extra_change_has_health_check(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
|
|
|
# record with geo and no health check returns change
|
|
desired = Zone('unit.tests.', [])
|
|
record = Record.new(desired, 'a', {
|
|
'ttl': 30,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'geo': {
|
|
'NA': ['2.2.3.4'],
|
|
}
|
|
})
|
|
desired.add_record(record)
|
|
list_resource_record_sets_resp = {
|
|
'ResourceRecordSets': [{
|
|
# other name
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': '*',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '1.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
# matching name, other type
|
|
'Name': 'a.unit.tests.',
|
|
'Type': 'AAAA',
|
|
'ResourceRecords': [{
|
|
'Value': '2001:0db8:3c4d:0015:0000:0000:1a2f:1a4b'
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
# default geo
|
|
'Name': 'a.unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': '*',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '1.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
}, {
|
|
# match w/correct geo
|
|
'Name': 'a.unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'ContinentCode': 'NA',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '2.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
'HealthCheckId': '42',
|
|
}],
|
|
'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': [{
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'a.unit.tests',
|
|
'IPAddress': '2.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(0, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# change b/c of healthcheck path
|
|
record._octodns['healthcheck'] = {
|
|
'path': '/_ready'
|
|
}
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(1, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def test_extra_change_dynamic_has_health_check(self):
|
|
provider, stubber = self._get_stubbed_provider()
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [{
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
}],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
|
|
|
|
# record with geo and no health check returns change
|
|
desired = Zone('unit.tests.', [])
|
|
record = Record.new(desired, 'a', {
|
|
'ttl': 30,
|
|
'type': 'A',
|
|
'value': '1.2.3.4',
|
|
'dynamic': {
|
|
'pools': {
|
|
'one': {
|
|
'values': [{
|
|
'value': '2.2.3.4',
|
|
}],
|
|
},
|
|
},
|
|
'rules': [{
|
|
'pool': 'one',
|
|
}],
|
|
},
|
|
})
|
|
desired.add_record(record)
|
|
list_resource_record_sets_resp = {
|
|
'ResourceRecordSets': [{
|
|
# Not dynamic value and other name
|
|
'Name': 'unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': '*',
|
|
},
|
|
'ResourceRecords': [{
|
|
'Value': '1.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
# All the non-matches have a different Id so we'll fail if they
|
|
# match
|
|
'HealthCheckId': '33',
|
|
}, {
|
|
# 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-value.a.unit.tests.',
|
|
'Type': 'A',
|
|
'GeoLocation': {
|
|
'CountryCode': '*',
|
|
},
|
|
'ResourceRecords': [{
|
|
'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.',
|
|
'Type': 'A',
|
|
'ResourceRecords': [{
|
|
'Value': '2.2.3.4',
|
|
}],
|
|
'TTL': 61,
|
|
'HealthCheckId': '42',
|
|
}],
|
|
'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': [{
|
|
'Id': '42',
|
|
'CallerReference': self.caller_ref,
|
|
'HealthCheckConfig': {
|
|
'Type': 'HTTPS',
|
|
'FullyQualifiedDomainName': 'a.unit.tests',
|
|
'IPAddress': '2.2.3.4',
|
|
'ResourcePath': '/_dns',
|
|
'Type': 'HTTPS',
|
|
'Port': 443,
|
|
'MeasureLatency': True
|
|
},
|
|
'HealthCheckVersion': 2,
|
|
}],
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(0, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# change b/c of healthcheck path
|
|
record._octodns['healthcheck'] = {
|
|
'path': '/_ready'
|
|
}
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(1, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
# change b/c of healthcheck host
|
|
record._octodns['healthcheck'] = {
|
|
'host': 'foo.bar.io'
|
|
}
|
|
extra = provider._extra_changes(desired=desired, changes=[])
|
|
self.assertEquals(1, len(extra))
|
|
stubber.assert_no_pending_responses()
|
|
|
|
def _get_test_plan(self, max_changes):
|
|
|
|
provider = Route53Provider('test', 'abc', '123', max_changes)
|
|
|
|
# Use the stubber
|
|
stubber = Stubber(provider._conn)
|
|
stubber.activate()
|
|
|
|
got = Zone('unit.tests.', [])
|
|
|
|
list_hosted_zones_resp = {
|
|
'HostedZones': [],
|
|
'Marker': 'm',
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
}
|
|
stubber.add_response('list_hosted_zones', list_hosted_zones_resp,
|
|
{})
|
|
|
|
create_hosted_zone_resp = {
|
|
'HostedZone': {
|
|
'Name': 'unit.tests.',
|
|
'Id': 'z42',
|
|
'CallerReference': 'abc',
|
|
},
|
|
'ChangeInfo': {
|
|
'Id': 'a12',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
'Comment': 'hrm',
|
|
},
|
|
'DelegationSet': {
|
|
'Id': 'b23',
|
|
'CallerReference': 'blip',
|
|
'NameServers': [
|
|
'n12.unit.tests.',
|
|
],
|
|
},
|
|
'Location': 'us-east-1',
|
|
}
|
|
stubber.add_response('create_hosted_zone',
|
|
create_hosted_zone_resp, {
|
|
'Name': got.name,
|
|
'CallerReference': ANY,
|
|
})
|
|
|
|
stubber.add_response('list_health_checks',
|
|
{
|
|
'HealthChecks': self.health_checks,
|
|
'IsTruncated': False,
|
|
'MaxItems': '100',
|
|
'Marker': '',
|
|
})
|
|
|
|
stubber.add_response('change_resource_record_sets',
|
|
{'ChangeInfo': {
|
|
'Id': 'id',
|
|
'Status': 'PENDING',
|
|
'SubmittedAt': '2017-01-29T01:02:03Z',
|
|
}}, {'HostedZoneId': 'z42', 'ChangeBatch': ANY})
|
|
|
|
plan = provider.plan(self.expected)
|
|
|
|
return provider, plan
|
|
|
|
# _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, _):
|
|
|
|
# 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, _):
|
|
|
|
# 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, _):
|
|
|
|
# with a max of seven modifications, three calls
|
|
provider, plan = self._get_test_plan(7)
|
|
provider.apply(plan)
|
|
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, _):
|
|
|
|
# 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, _):
|
|
|
|
# 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 text_type(ctx.exception))
|
|
|
|
def test_semicolon_fixup(self):
|
|
provider = Route53Provider('test', 'abc', '123')
|
|
|
|
self.assertEquals({
|
|
'type': 'TXT',
|
|
'ttl': 30,
|
|
'values': [
|
|
'abcd\\; ef\\;g',
|
|
'hij\\; klm\\;n',
|
|
],
|
|
}, provider._data_for_quoted({
|
|
'ResourceRecords': [{
|
|
'Value': '"abcd; ef;g"',
|
|
}, {
|
|
'Value': '"hij\\; klm\\;n"',
|
|
}],
|
|
'TTL': 30,
|
|
'Type': 'TXT',
|
|
}))
|
|
|
|
def test_client_max_attempts(self):
|
|
provider = Route53Provider('test', 'abc', '123',
|
|
client_max_attempts=42)
|
|
# NOTE: this will break if boto ever changes the impl details...
|
|
self.assertEquals(43, provider._conn.meta.events
|
|
._unique_id_handlers['retry-config-route53']
|
|
['handler']._checker.__dict__['_max_attempts'])
|
|
|
|
def test_data_for_dynamic(self):
|
|
provider = Route53Provider('test', 'abc', '123')
|
|
|
|
data = provider._data_for_dynamic('', 'A', dynamic_rrsets)
|
|
self.assertEquals(dynamic_record_data, data)
|
|
|
|
@patch('octodns.provider.route53.Route53Provider._get_zone_id')
|
|
@patch('octodns.provider.route53.Route53Provider._load_records')
|
|
def test_dynamic_populate(self, load_records_mock, get_zone_id_mock):
|
|
provider = Route53Provider('test', 'abc', '123')
|
|
|
|
get_zone_id_mock.side_effect = ['z44']
|
|
load_records_mock.side_effect = [dynamic_rrsets]
|
|
|
|
got = Zone('unit.tests.', [])
|
|
provider.populate(got)
|
|
|
|
self.assertEquals(1, len(got.records))
|
|
record = list(got.records)[0]
|
|
self.assertEquals('', record.name)
|
|
self.assertEquals('A', record._type)
|
|
self.assertEquals([
|
|
'1.1.2.1',
|
|
'1.1.2.2',
|
|
], record.values)
|
|
self.assertTrue(record.dynamic)
|
|
|
|
self.assertEquals({
|
|
'ap-southeast-1': {
|
|
'fallback': 'us-east-1',
|
|
'values': [{
|
|
'weight': 2, 'value': '1.4.1.1'
|
|
}, {
|
|
'weight': 2, 'value': '1.4.1.2'
|
|
}]
|
|
},
|
|
'eu-central-1': {
|
|
'fallback': 'us-east-1',
|
|
'values': [{
|
|
'weight': 1, 'value': '1.3.1.1'
|
|
}, {
|
|
'weight': 1, 'value': '1.3.1.2'
|
|
}],
|
|
},
|
|
'us-east-1': {
|
|
'fallback': None,
|
|
'values': [{
|
|
'weight': 1, 'value': '1.5.1.1'
|
|
}, {
|
|
'weight': 1, 'value': '1.5.1.2'
|
|
}],
|
|
}
|
|
}, {k: v.data for k, v in record.dynamic.pools.items()})
|
|
|
|
self.assertEquals([
|
|
{
|
|
'geos': ['AS-CN', 'AS-JP'],
|
|
'pool': 'ap-southeast-1',
|
|
}, {
|
|
'geos': ['EU', 'NA-US-FL'],
|
|
'pool': 'eu-central-1',
|
|
}, {
|
|
'pool': 'us-east-1',
|
|
}], [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, '', {
|
|
'geo': {
|
|
'NA-US': ['2.2.2.2', '3.3.3.3'],
|
|
'OC': ['4.4.4.4', '5.5.5.5']
|
|
},
|
|
'ttl': 99,
|
|
'type': 'A',
|
|
'values': ['9.9.9.9']
|
|
})
|
|
|
|
def test_value_fors(self):
|
|
route53_record = _Route53Record(None, self.record_a, False)
|
|
|
|
for value in (None, '', 'foo', 'bar', '1.2.3.4'):
|
|
converted = route53_record._value_convert_value(value,
|
|
self.record_a)
|
|
self.assertEquals(value, converted)
|
|
|
|
record_txt = Record.new(self.existing, 'txt', {
|
|
'ttl': 98,
|
|
'type': 'TXT',
|
|
'value': 'Not Important',
|
|
})
|
|
|
|
# We don't really have to test the details fo chunked_value as that's
|
|
# tested elsewhere, we just need to make sure that it's plumbed up and
|
|
# working
|
|
self.assertEquals('"Not Important"', route53_record
|
|
._value_convert_quoted(record_txt.values[0],
|
|
record_txt))
|
|
|
|
def test_route53_record(self):
|
|
a = _Route53Record(None, self.record_a, False)
|
|
self.assertEquals(a, a)
|
|
b = _Route53Record(None, Record.new(self.existing, '',
|
|
{'ttl': 32, 'type': 'A',
|
|
'values': ['8.8.8.8',
|
|
'1.1.1.1']}),
|
|
False)
|
|
self.assertEquals(b, b)
|
|
c = _Route53Record(None, Record.new(self.existing, 'other',
|
|
{'ttl': 99, 'type': 'A',
|
|
'values': ['9.9.9.9']}),
|
|
False)
|
|
self.assertEquals(c, c)
|
|
d = _Route53Record(None, Record.new(self.existing, '',
|
|
{'ttl': 42, 'type': 'MX',
|
|
'value': {
|
|
'preference': 10,
|
|
'exchange': 'foo.bar.'}}),
|
|
False)
|
|
self.assertEquals(d, d)
|
|
|
|
# Same fqdn & type is same record
|
|
self.assertEquals(a, b)
|
|
# Same name & different type is not the same
|
|
self.assertNotEquals(a, d)
|
|
# Different name & same type is not the same
|
|
self.assertNotEquals(a, c)
|
|
|
|
# Same everything, different class is not the same
|
|
e = _Route53GeoDefault(None, self.record_a, False)
|
|
self.assertNotEquals(a, e)
|
|
|
|
provider = DummyProvider()
|
|
f = _Route53GeoRecord(provider, self.record_a, 'NA-US',
|
|
self.record_a.geo['NA-US'], False)
|
|
self.assertEquals(f, f)
|
|
g = _Route53GeoRecord(provider, self.record_a, 'OC',
|
|
self.record_a.geo['OC'], False)
|
|
self.assertEquals(g, g)
|
|
|
|
# Geo and non-geo are not the same, using Geo as primary to get it's
|
|
# __cmp__
|
|
self.assertNotEquals(f, a)
|
|
# Same everything, different geo's is not the same
|
|
self.assertNotEquals(f, g)
|
|
|
|
# Make sure it doesn't blow up
|
|
a.__repr__()
|
|
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')
|
|
|
|
# Just so boto won't try and make any calls
|
|
stubber = Stubber(provider._conn)
|
|
stubber.activate()
|
|
|
|
# We'll assume we create all healthchecks here, this functionality is
|
|
# thoroughly tested elsewhere
|
|
provider._health_checks = {}
|
|
# When asked for a healthcheck return dummy info
|
|
provider.get_health_check_id = lambda r, v, c: 'hc42'
|
|
|
|
zone = Zone('unit.tests.', [])
|
|
record = Record.new(zone, '', dynamic_record_data)
|
|
|
|
# Convert a record into _Route53Records
|
|
route53_records = _Route53Record.new(provider, record, 'z45',
|
|
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.1'}],
|
|
'SetIdentifier': 'ap-southeast-1-000',
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
'Weight': 2}
|
|
}, {
|
|
'Action': 'CREATE',
|
|
'ResourceRecordSet': {
|
|
'HealthCheckId': 'hc42',
|
|
'Name': '_octodns-ap-southeast-1-value.unit.tests.',
|
|
'ResourceRecords': [{'Value': '1.4.1.2'}],
|
|
'SetIdentifier': 'ap-southeast-1-001',
|
|
'TTL': 60,
|
|
'Type': 'A',
|
|
'Weight': 2}
|
|
}, {
|
|
'Action': 'CREATE',
|
|
'ResourceRecordSet': {
|
|
'Name': '_octodns-default-pool.unit.tests.',
|
|
'ResourceRecords': [
|
|
{'Value': '1.1.2.1'},
|
|
{'Value': '1.1.2.2'}],
|
|
'TTL': 60,
|
|
'Type': 'A'}
|
|
}, {
|
|
'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': {
|
|
'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-us-east-1-value.unit.tests.',
|
|
'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': {
|
|
'AliasTarget': {
|
|
'DNSName': '_octodns-ap-southeast-1-value.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'z45'},
|
|
'Failover': 'PRIMARY',
|
|
'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': {
|
|
'AliasTarget': {
|
|
'DNSName': '_octodns-us-east-1-pool.unit.tests.',
|
|
'EvaluateTargetHealth': True,
|
|
'HostedZoneId': 'z45'},
|
|
'Failover': 'SECONDARY',
|
|
'Name': '_octodns-eu-central-1-pool.unit.tests.',
|
|
'SetIdentifier': 'eu-central-1-Secondary-us-east-1',
|
|
'Type': 'A'}
|
|
}, {
|
|
'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
|
|
route53_record.__repr__()
|
|
|
|
|
|
class TestModKeyer(TestCase):
|
|
|
|
def test_mod_keyer(self):
|
|
|
|
# First "column" is the action priority for C/R/U
|
|
|
|
# Deletes come first
|
|
self.assertEquals((0, 0, 'something'), _mod_keyer({
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'Name': 'something',
|
|
}
|
|
}))
|
|
|
|
# Creates come next
|
|
self.assertEquals((1, 0, 'another'), _mod_keyer({
|
|
'Action': 'CREATE',
|
|
'ResourceRecordSet': {
|
|
'Name': 'another',
|
|
}
|
|
}))
|
|
|
|
# Upserts are the same as creates
|
|
self.assertEquals((1, 0, 'last'), _mod_keyer({
|
|
'Action': 'UPSERT',
|
|
'ResourceRecordSet': {
|
|
'Name': 'last',
|
|
}
|
|
}))
|
|
|
|
# Second "column" value records tested above
|
|
|
|
# AliasTarget primary second (to value)
|
|
self.assertEquals((0, -1, 'thing'), _mod_keyer({
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'AliasTarget': 'some-target',
|
|
'Failover': 'PRIMARY',
|
|
'Name': 'thing',
|
|
}
|
|
}))
|
|
|
|
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({
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'AliasTarget': 'some-target',
|
|
'Failover': 'SECONDARY',
|
|
'Name': 'thing',
|
|
}
|
|
}))
|
|
|
|
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({
|
|
'Action': 'DELETE',
|
|
'ResourceRecordSet': {
|
|
'GeoLocation': 'some-target',
|
|
'SetIdentifier': 'some-id',
|
|
}
|
|
}))
|
|
|
|
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
|