From 1794f5ccd6ff449e8848d771e7f239d3744d1195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Runkel?= Date: Mon, 11 Feb 2019 20:02:03 +0100 Subject: [PATCH 1/3] Add healthcheck option 'measure_latency' for Route53 provider Route53 allows to monitor latency information on the dashboard and using CloudWatch. While that is a nice to have function, it is not necessary for a DNS failover scenario and increases Route 53 costs. To maintain backward compatibility, the default for this option when ommited is true. --- docs/records.md | 34 +++++++++++++++++++++++--- octodns/provider/route53.py | 23 +++++++++++------ octodns/record/__init__.py | 7 ++++++ tests/test_octodns_provider_route53.py | 15 ++++++++++++ tests/test_octodns_record.py | 3 +++ 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/docs/records.md b/docs/records.md index a833d4b..1b42055 100644 --- a/docs/records.md +++ b/docs/records.md @@ -72,8 +72,36 @@ So the example is saying: - Europe: gets an "A" record of 111.111.111.4 - Everyone else gets an "A" record of 111.111.111.5 +### Health Checks -Octodns will automatically set up a monitor and check for **https:///_dns** and check for a 200 response. +Octodns will automatically set up monitors for each IP and check for a 200 response for **https:///_dns**. + +These checks can be configured by adding a `healthcheck` configuration to the record: + +```yaml +--- +test: + geo: + AS: + - 1.2.3.4 + EU: + - 2.3.4.5 + octodns: + healthcheck: + host: my-host-name + measure_latency: 0 + path: /dns-health-check + port: 443 + protocol: HTTPS +``` + +| Key | Description | Default | +|--|--|--| +| host | FQDN for host header and SNI | - | +| path | path to check | _dns | +| port | port to check | 443 | +| protocol | HTTP/HTTPS | HTTPS | +| measure_latency | Route53 only: Show latency in AWS console | true | ## Config (`YamlProvider`) @@ -83,7 +111,7 @@ OctoDNS records and `YamlProvider`'s schema is essentially a 1:1 match. Properti Each top-level key in the yaml file is a record name. Two common special cases are the root record `''`, and a wildcard `'*'`. -``` +```yaml --- '': type: A @@ -111,7 +139,7 @@ The above config lays out 4 records, `A`s for `example.com.`, `www.example.com.` In the above example each name had a single record, but there are cases where a name will need to have multiple records associated with it. This can be accomplished by using a list. -``` +```yaml --- '': - type: A diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index ff83cdc..17a07d0 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -546,11 +546,13 @@ class Route53Provider(BaseProvider): return self._health_checks def _health_check_equivilent(self, host, path, protocol, port, - health_check, first_value=None): + measure_latency, health_check, + first_value=None): config = health_check['HealthCheckConfig'] return host == config['FullyQualifiedDomainName'] and \ path == config['ResourcePath'] and protocol == config['Type'] \ and port == config['Port'] and \ + measure_latency == config['MeasureLatency'] and \ (first_value is None or first_value == config['IPAddress']) def get_health_check_id(self, record, ident, geo, create): @@ -568,6 +570,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port + healthcheck_measure_latency = record.healthcheck_measure_latency # we're looking for a healthcheck with the current version & our record # type, we'll ignore anything else @@ -580,7 +583,9 @@ class Route53Provider(BaseProvider): if self._health_check_equivilent(healthcheck_host, healthcheck_path, healthcheck_protocol, - healthcheck_port, health_check, + healthcheck_port, + healthcheck_measure_latency, + health_check, first_value=first_value): # this is the health check we're looking for self.log.debug('get_health_check_id: found match id=%s', id) @@ -597,7 +602,7 @@ class Route53Provider(BaseProvider): 'FailureThreshold': 6, 'FullyQualifiedDomainName': healthcheck_host, 'IPAddress': first_value, - 'MeasureLatency': True, + 'MeasureLatency': healthcheck_measure_latency, 'Port': healthcheck_port, 'RequestInterval': 10, 'ResourcePath': healthcheck_path, @@ -612,10 +617,12 @@ class Route53Provider(BaseProvider): # store the new health check so that we'll be able to find it in the # future self._health_checks[id] = health_check - self.log.info('get_health_check_id: created id=%s, host=%s, path=%s, ' - 'protocol=%s, port=%d, first_value=%s', id, - healthcheck_host, healthcheck_path, healthcheck_protocol, - healthcheck_port, first_value) + self.log.info('get_health_check_id: created id=%s, host=%s, ' + 'path=%s, protocol=%s, port=%d, measure_latency=%r, ' + 'first_value=%s', + id, healthcheck_host, healthcheck_path, + healthcheck_protocol, healthcheck_port, + healthcheck_measure_latency, first_value) return id def _gc_health_checks(self, record, new): @@ -728,6 +735,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port + healthcheck_latency = record.healthcheck_measure_latency fqdn = record.fqdn # loop through all the r53 rrsets @@ -749,6 +757,7 @@ class Route53Provider(BaseProvider): healthcheck_path, healthcheck_protocol, healthcheck_port, + healthcheck_latency, health_check): # it has the right health check continue diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index ba0ab98..a111415 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -189,6 +189,13 @@ class Record(object): except KeyError: return 443 + @property + def healthcheck_measure_latency(self): + try: + return bool(self._octodns['healthcheck']['measure_latency']) + except KeyError: + return True + def changes(self, other, target): # We're assuming we have the same name and type if we're being compared if self.ttl != other.ttl: diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 75ee991..8cb9863 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -105,6 +105,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -117,6 +118,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 42, }, { @@ -129,6 +131,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -141,6 +144,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -154,6 +158,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -704,6 +709,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -716,6 +722,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -738,6 +745,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -785,6 +793,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -797,6 +806,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -947,6 +957,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -959,6 +970,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -971,6 +983,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -1147,6 +1160,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }], @@ -1250,6 +1264,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True }, 'HealthCheckVersion': 2, }], diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 4f05126..411e266 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -757,6 +757,7 @@ class TestRecord(TestCase): 'host': 'bleep.bloop', 'protocol': 'HTTP', 'port': 8080, + 'measure_latency': False } } }) @@ -764,6 +765,7 @@ class TestRecord(TestCase): self.assertEquals('bleep.bloop', new.healthcheck_host) self.assertEquals('HTTP', new.healthcheck_protocol) self.assertEquals(8080, new.healthcheck_port) + self.assertEquals(False, new.healthcheck_measure_latency) new = Record.new(self.zone, 'a', { 'ttl': 44, @@ -774,6 +776,7 @@ class TestRecord(TestCase): self.assertEquals('a.unit.tests', new.healthcheck_host) self.assertEquals('HTTPS', new.healthcheck_protocol) self.assertEquals(443, new.healthcheck_port) + self.assertEquals(True, new.healthcheck_measure_latency) def test_inored(self): new = Record.new(self.zone, 'txt', { From 54787529d50c40be6bc9576a29c37eafa590cef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Runkel?= Date: Wed, 13 Feb 2019 10:50:48 +0100 Subject: [PATCH 2/3] Move measure_latency option to Route53 provider --- docs/records.md | 22 +++++- octodns/provider/route53.py | 17 +++-- octodns/record/__init__.py | 7 -- tests/test_octodns_provider_route53.py | 101 +++++++++++++++++++++++++ tests/test_octodns_record.py | 3 - 5 files changed, 133 insertions(+), 17 deletions(-) diff --git a/docs/records.md b/docs/records.md index 1b42055..1bfc7fd 100644 --- a/docs/records.md +++ b/docs/records.md @@ -89,7 +89,6 @@ test: octodns: healthcheck: host: my-host-name - measure_latency: 0 path: /dns-health-check port: 443 protocol: HTTPS @@ -101,7 +100,26 @@ test: | path | path to check | _dns | | port | port to check | 443 | | protocol | HTTP/HTTPS | HTTPS | -| measure_latency | Route53 only: Show latency in AWS console | true | + +#### Route53 Healtch Check Options + +| Key | Description | Default | +|--|--|--| +| measure_latency | Show latency in AWS console | true | + +```yaml +--- + octodns: + healthcheck: + host: my-host-name + path: /dns-health-check + port: 443 + protocol: HTTPS + route53: + healthcheck: + measure_latency: false +``` + ## Config (`YamlProvider`) diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index 17a07d0..8046479 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -545,6 +545,13 @@ class Route53Provider(BaseProvider): # We've got a cached version use it return self._health_checks + def _healthcheck_measure_latency(self, record): + return ( + record._octodns.get('route53', {}) + .get('healthcheck', {}) + .get('measure_latency', True) + ) + def _health_check_equivilent(self, host, path, protocol, port, measure_latency, health_check, first_value=None): @@ -570,7 +577,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port - healthcheck_measure_latency = record.healthcheck_measure_latency + healthcheck_latency = self._healthcheck_measure_latency(record) # we're looking for a healthcheck with the current version & our record # type, we'll ignore anything else @@ -584,7 +591,7 @@ class Route53Provider(BaseProvider): healthcheck_path, healthcheck_protocol, healthcheck_port, - healthcheck_measure_latency, + healthcheck_latency, health_check, first_value=first_value): # this is the health check we're looking for @@ -602,7 +609,7 @@ class Route53Provider(BaseProvider): 'FailureThreshold': 6, 'FullyQualifiedDomainName': healthcheck_host, 'IPAddress': first_value, - 'MeasureLatency': healthcheck_measure_latency, + 'MeasureLatency': healthcheck_latency, 'Port': healthcheck_port, 'RequestInterval': 10, 'ResourcePath': healthcheck_path, @@ -622,7 +629,7 @@ class Route53Provider(BaseProvider): 'first_value=%s', id, healthcheck_host, healthcheck_path, healthcheck_protocol, healthcheck_port, - healthcheck_measure_latency, first_value) + healthcheck_latency, first_value) return id def _gc_health_checks(self, record, new): @@ -735,7 +742,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port - healthcheck_latency = record.healthcheck_measure_latency + healthcheck_latency = self._healthcheck_measure_latency(record) fqdn = record.fqdn # loop through all the r53 rrsets diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index a111415..ba0ab98 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -189,13 +189,6 @@ class Record(object): except KeyError: return 443 - @property - def healthcheck_measure_latency(self): - try: - return bool(self._octodns['healthcheck']['measure_latency']) - except KeyError: - return True - def changes(self, other, target): # We're assuming we have the same name and type if we're being compared if self.ttl != other.ttl: diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 8cb9863..dfd7d00 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -868,6 +868,107 @@ class TestRoute53Provider(TestCase): 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.assertEquals(True, 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.assertEquals(True, 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.assertEquals(False, 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, + }) + + 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 + } + } + } + }) + + id = provider.get_health_check_id(record, 'AF', record.geo['AF'], True) + ml = provider.health_checks[id]['HealthCheckConfig']['MeasureLatency'] + self.assertEqual(False, ml) + def test_health_check_gc(self): provider, stubber = self._get_stubbed_provider() diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 411e266..4f05126 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -757,7 +757,6 @@ class TestRecord(TestCase): 'host': 'bleep.bloop', 'protocol': 'HTTP', 'port': 8080, - 'measure_latency': False } } }) @@ -765,7 +764,6 @@ class TestRecord(TestCase): self.assertEquals('bleep.bloop', new.healthcheck_host) self.assertEquals('HTTP', new.healthcheck_protocol) self.assertEquals(8080, new.healthcheck_port) - self.assertEquals(False, new.healthcheck_measure_latency) new = Record.new(self.zone, 'a', { 'ttl': 44, @@ -776,7 +774,6 @@ class TestRecord(TestCase): self.assertEquals('a.unit.tests', new.healthcheck_host) self.assertEquals('HTTPS', new.healthcheck_protocol) self.assertEquals(443, new.healthcheck_port) - self.assertEquals(True, new.healthcheck_measure_latency) def test_inored(self): new = Record.new(self.zone, 'txt', { From c1911c10a306a5acef6749d5ff643bc9afef5f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Runkel?= Date: Wed, 13 Feb 2019 17:45:23 +0100 Subject: [PATCH 3/3] Some syntax and style improvements --- octodns/provider/route53.py | 6 ++---- tests/test_octodns_provider_route53.py | 6 +++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index 8046479..d02cab7 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -546,11 +546,9 @@ class Route53Provider(BaseProvider): return self._health_checks def _healthcheck_measure_latency(self, record): - return ( - record._octodns.get('route53', {}) - .get('healthcheck', {}) + return record._octodns.get('route53', {}) \ + .get('healthcheck', {}) \ .get('measure_latency', True) - ) def _health_check_equivilent(self, host, path, protocol, port, measure_latency, health_check, diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index dfd7d00..eaff0e7 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -885,7 +885,7 @@ class TestRoute53Provider(TestCase): } }) measure_latency = provider._healthcheck_measure_latency(record_true) - self.assertEquals(True, measure_latency) + self.assertTrue(measure_latency) record_default = Record.new(self.expected, 'a', { 'ttl': 61, @@ -893,7 +893,7 @@ class TestRoute53Provider(TestCase): 'value': '1.2.3.4', }) measure_latency = provider._healthcheck_measure_latency(record_default) - self.assertEquals(True, measure_latency) + self.assertTrue(measure_latency) record_false = Record.new(self.expected, 'a', { 'ttl': 61, @@ -910,7 +910,7 @@ class TestRoute53Provider(TestCase): } }) measure_latency = provider._healthcheck_measure_latency(record_false) - self.assertEquals(False, measure_latency) + self.assertFalse(measure_latency) def test_create_health_checks_measure_latency(self): provider, stubber = self._get_stubbed_provider()