diff --git a/CHANGELOG.md b/CHANGELOG.md index 27b6a80..a0a92a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.9.11 - 2020-??-?? - ??????????????? + +* Added support for TCP health checking to dynamic records + ## v0.9.10 - 2020-04-20 - Dynamic NS1 and lots of misc * Added support for dynamic records to Ns1Provider, updated client and rate diff --git a/docs/dynamic_records.md b/docs/dynamic_records.md index 8a7cd09..706173a 100644 --- a/docs/dynamic_records.md +++ b/docs/dynamic_records.md @@ -103,7 +103,7 @@ test: | host | FQDN for host header and SNI | - | | path | path to check | _dns | | port | port to check | 443 | -| protocol | HTTP/HTTPS | HTTPS | +| protocol | HTTP/HTTPS/TCP | HTTPS | #### Route53 Healtch Check Options diff --git a/octodns/provider/dyn.py b/octodns/provider/dyn.py index f4f8e53..f7a15d2 100644 --- a/octodns/provider/dyn.py +++ b/octodns/provider/dyn.py @@ -1141,7 +1141,7 @@ class DynProvider(BaseProvider): pools[rpid] = get_response_pool(rpid, td) # now that we have full objects for the complete set of existing pools, # a list will be more useful - pools = pools.values() + pools = list(pools.values()) # Rulesets diff --git a/octodns/provider/ns1.py b/octodns/provider/ns1.py index db589d6..b850ed9 100644 --- a/octodns/provider/ns1.py +++ b/octodns/provider/ns1.py @@ -855,18 +855,13 @@ class Ns1Provider(BaseProvider): host = record.fqdn[:-1] _type = record._type - request = r'GET {path} HTTP/1.0\r\nHost: {host}\r\n' \ - r'User-agent: NS1\r\n\r\n'.format(path=record.healthcheck_path, - host=record.healthcheck_host) - - return { + ret = { 'active': True, 'config': { 'connect_timeout': 2000, 'host': value, 'port': record.healthcheck_port, 'response_timeout': 10000, - 'send': request, 'ssl': record.healthcheck_protocol == 'HTTPS', }, 'frequency': 60, @@ -880,12 +875,23 @@ class Ns1Provider(BaseProvider): 'rapid_recheck': False, 'region_scope': 'fixed', 'regions': self.monitor_regions, - 'rules': [{ + } + + if record.healthcheck_protocol != 'TCP': + # IF it's HTTP we need to send the request string + path = record.healthcheck_path + host = record.healthcheck_host + request = r'GET {path} HTTP/1.0\r\nHost: {host}\r\n' \ + r'User-agent: NS1\r\n\r\n'.format(path=path, host=host) + ret['config']['send'] = request + # We'll also expect a HTTP response + ret['rules'] = [{ 'comparison': 'contains', 'key': 'output', 'value': '200 OK', - }], - } + }] + + return ret def _monitor_is_match(self, expected, have): # Make sure what we have matches what's in expected exactly. Anything diff --git a/octodns/provider/route53.py b/octodns/provider/route53.py index 3c7b0ed..8e9ee4f 100644 --- a/octodns/provider/route53.py +++ b/octodns/provider/route53.py @@ -1046,8 +1046,11 @@ class Route53Provider(BaseProvider): # No value so give this a None to match value's config_ip_address = None - return host == config['FullyQualifiedDomainName'] and \ - path == config['ResourcePath'] and protocol == config['Type'] \ + fully_qualified_domain_name = config.get('FullyQualifiedDomainName', + None) + resource_path = config.get('ResourcePath', None) + return host == fully_qualified_domain_name and \ + path == resource_path and protocol == config['Type'] \ and port == config['Port'] and \ measure_latency == config['MeasureLatency'] and \ value == config_ip_address @@ -1103,13 +1106,14 @@ class Route53Provider(BaseProvider): config = { 'EnableSNI': healthcheck_protocol == 'HTTPS', 'FailureThreshold': 6, - 'FullyQualifiedDomainName': healthcheck_host, 'MeasureLatency': healthcheck_latency, 'Port': healthcheck_port, 'RequestInterval': 10, - 'ResourcePath': healthcheck_path, 'Type': healthcheck_protocol, } + if healthcheck_protocol != 'TCP': + config['FullyQualifiedDomainName'] = healthcheck_host + config['ResourcePath'] = healthcheck_path if value: config['IPAddress'] = value diff --git a/octodns/record/__init__.py b/octodns/record/__init__.py index 384c0a2..39bae2a 100644 --- a/octodns/record/__init__.py +++ b/octodns/record/__init__.py @@ -137,7 +137,7 @@ class Record(EqualityTupleMixin): reasons.append('missing ttl') try: if data['octodns']['healthcheck']['protocol'] \ - not in ('HTTP', 'HTTPS'): + not in ('HTTP', 'HTTPS', 'TCP'): reasons.append('invalid healthcheck protocol') except KeyError: pass @@ -181,15 +181,21 @@ class Record(EqualityTupleMixin): @property def healthcheck_host(self): + healthcheck = self._octodns.get('healthcheck', {}) + if healthcheck.get('protocol', None) == 'TCP': + return None try: - return self._octodns['healthcheck']['host'] + return healthcheck['host'] except KeyError: return self.fqdn[:-1] @property def healthcheck_path(self): + healthcheck = self._octodns.get('healthcheck', {}) + if healthcheck.get('protocol', None) == 'TCP': + return None try: - return self._octodns['healthcheck']['path'] + return healthcheck['path'] except KeyError: return '/_dns' diff --git a/tests/test_octodns_provider_ns1.py b/tests/test_octodns_provider_ns1.py index fa26a32..336c985 100644 --- a/tests/test_octodns_provider_ns1.py +++ b/tests/test_octodns_provider_ns1.py @@ -717,6 +717,13 @@ class TestNs1ProviderDynamic(TestCase): monitor = provider._monitor_gen(self.record, value) self.assertTrue(monitor['config']['ssl']) + self.record._octodns['healthcheck']['protocol'] = 'TCP' + monitor = provider._monitor_gen(self.record, value) + # No http send done + self.assertFalse('send' in monitor['config']) + # No http response expected + self.assertFalse('rules' in monitor) + def test_monitor_is_match(self): provider = Ns1Provider('test', 'api-key') diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py index 385b82a..543a506 100644 --- a/tests/test_octodns_provider_route53.py +++ b/tests/test_octodns_provider_route53.py @@ -1213,6 +1213,35 @@ class TestRoute53Provider(TestCase): self.assertEquals('42', id) stubber.assert_no_pending_responses() + # TCP health check + + health_check_config = { + 'EnableSNI': False, + 'FailureThreshold': 6, + 'MeasureLatency': True, + 'Port': 8080, + 'RequestInterval': 10, + 'Type': 'TCP' + } + 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._octodns['healthcheck']['protocol'] = 'TCP' + 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', { diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index f76f593..b6dc4e9 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -886,6 +886,40 @@ class TestRecord(TestCase): self.assertEquals('HTTPS', new.healthcheck_protocol) self.assertEquals(443, new.healthcheck_port) + def test_healthcheck_tcp(self): + new = Record.new(self.zone, 'a', { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': { + 'healthcheck': { + 'path': '/ignored', + 'host': 'completely.ignored', + 'protocol': 'TCP', + 'port': 8080, + } + } + }) + self.assertIsNone(new.healthcheck_path) + self.assertIsNone(new.healthcheck_host) + self.assertEquals('TCP', new.healthcheck_protocol) + self.assertEquals(8080, new.healthcheck_port) + + new = Record.new(self.zone, 'a', { + 'ttl': 44, + 'type': 'A', + 'value': '1.2.3.4', + 'octodns': { + 'healthcheck': { + 'protocol': 'TCP', + } + } + }) + self.assertIsNone(new.healthcheck_path) + self.assertIsNone(new.healthcheck_host) + self.assertEquals('TCP', new.healthcheck_protocol) + self.assertEquals(443, new.healthcheck_port) + def test_inored(self): new = Record.new(self.zone, 'txt', { 'ttl': 44,