diff --git a/docs/records.md b/docs/records.md index a833d4b..1bfc7fd 100644 --- a/docs/records.md +++ b/docs/records.md @@ -72,8 +72,54 @@ 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 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 + 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 | + +#### 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 +``` -Octodns will automatically set up a monitor and check for **https:///_dns** and check for a 200 response. ## Config (`YamlProvider`) @@ -83,7 +129,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 +157,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..d02cab7 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -545,12 +545,19 @@ 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, - 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 +575,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port + 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 @@ -580,7 +588,9 @@ class Route53Provider(BaseProvider): if self._health_check_equivilent(healthcheck_host, healthcheck_path, healthcheck_protocol, - healthcheck_port, health_check, + healthcheck_port, + healthcheck_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 +607,7 @@ class Route53Provider(BaseProvider): 'FailureThreshold': 6, 'FullyQualifiedDomainName': healthcheck_host, 'IPAddress': first_value, - 'MeasureLatency': True, + 'MeasureLatency': healthcheck_latency, 'Port': healthcheck_port, 'RequestInterval': 10, 'ResourcePath': healthcheck_path, @@ -612,10 +622,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_latency, first_value) return id def _gc_health_checks(self, record, new): @@ -728,6 +740,7 @@ class Route53Provider(BaseProvider): healthcheck_path = record.healthcheck_path healthcheck_protocol = record.healthcheck_protocol healthcheck_port = record.healthcheck_port + healthcheck_latency = self._healthcheck_measure_latency(record) fqdn = record.fqdn # loop through all the r53 rrsets @@ -749,6 +762,7 @@ class Route53Provider(BaseProvider): healthcheck_path, healthcheck_protocol, healthcheck_port, + healthcheck_latency, health_check): # it has the right health check continue diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 75ee991..eaff0e7 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, }] @@ -858,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.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, + }) + + 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() @@ -947,6 +1058,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -959,6 +1071,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }, { @@ -971,6 +1084,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }] @@ -1147,6 +1261,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True, }, 'HealthCheckVersion': 2, }], @@ -1250,6 +1365,7 @@ class TestRoute53Provider(TestCase): 'ResourcePath': '/_dns', 'Type': 'HTTPS', 'Port': 443, + 'MeasureLatency': True }, 'HealthCheckVersion': 2, }],