Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tcp_check/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ files:
value:
type: boolean
example: false
- name: ip_cache_duration
value:
display_default: null
example: 120
Comment thread
ian28223 marked this conversation as resolved.
type: number
description: |
Time in seconds to cache the IP address of the given host. If duration is not set,
the cache will expire only upon error.
- template: instances/default
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def instance_empty_default_hostname(field, value):
return False


def instance_ip_cache_duration(field, value):
return get_default_field_value(field, value)


def instance_min_collection_interval(field, value):
return 15

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Config:
collect_response_time: Optional[bool]
empty_default_hostname: Optional[bool]
host: str
ip_cache_duration: Optional[float]
min_collection_interval: Optional[float]
name: str
port: int
Expand Down
6 changes: 6 additions & 0 deletions tcp_check/datadog_checks/tcp_check/data/conf.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ instances:
#
# collect_response_time: false

## @param ip_cache_duration - number - optional
## Time in seconds to cache the IP address of the given host. If duration is not set,
## the cache will expire only upon error.
#
# ip_cache_duration: 120

## @param tags - list of strings - optional
## A list of tags to attach to every metric and service check emitted by this instance.
##
Expand Down
33 changes: 29 additions & 4 deletions tcp_check/datadog_checks/tcp_check/tcp_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class TCPCheck(AgentCheck):

SOURCE_TYPE_NAME = 'system'
SERVICE_CHECK_NAME = 'tcp.can_connect'
CONFIGURATION_ERROR_MSG = "`{}` is an invalid `{}`; a {} must be specified."
DEFAULT_IP_CACHE_DURATION = None

def __init__(self, name, init_config, instances):
super(TCPCheck, self).__init__(name, init_config, instances)
Expand All @@ -24,16 +26,27 @@ def __init__(self, name, init_config, instances):
self.host = instance.get('host', None)
self.socket_type = None
self._addr = None
self.ip_cache_last_ts = 0
self.ip_cache_duration = self.DEFAULT_IP_CACHE_DURATION

ip_cache_duration = instance.get('ip_cache_duration', None)
if ip_cache_duration is not None:
try:
self.ip_cache_duration = int(ip_cache_duration)
except Exception:
raise ConfigurationError(
self.CONFIGURATION_ERROR_MSG.format(ip_cache_duration, 'ip_cache_duration', 'number')
)

port = instance.get('port', None)
try:
self.port = int(port)
except Exception:
raise ConfigurationError("{} is not a correct port.".format(str(port)))
raise ConfigurationError(self.CONFIGURATION_ERROR_MSG.format(port, 'port', 'number'))
try:
split_url = self.host.split(":")
except Exception: # Would be raised if url is not a string
raise ConfigurationError("A valid url must be specified")
raise ConfigurationError(self.CONFIGURATION_ERROR_MSG.format(self.host, 'url', 'string'))

custom_tags = instance.get('tags', [])
self.tags = [
Expand All @@ -51,7 +64,9 @@ def __init__(self, name, init_config, instances):
if len(split_url) == 8: # It may then be a IP V6 address, we check that
for block in split_url:
if len(block) != 4:
raise ConfigurationError("{} is not a correct IPv6 address.".format(self.host))
raise ConfigurationError(
self.CONFIGURATION_ERROR_MSG.format(self.host, 'IPv6 address', 'valid address')
)
# It's a correct IP V6 address
self._addr = self.host
self.socket_type = socket.AF_INET6
Expand All @@ -74,6 +89,11 @@ def resolve_ip(self):
self._addr = socket.gethostbyname(self.host)
self.log.debug("%s resolved to %s", self.host, self._addr)

def should_resolve_ip(self):
if self.ip_cache_duration is None:
Comment thread
ian28223 marked this conversation as resolved.
return False
return get_precise_time() - self.ip_cache_last_ts > self.ip_cache_duration

def connect(self):
with closing(socket.socket(self.socket_type)) as sock:
sock.settimeout(self.timeout)
Expand All @@ -84,7 +104,12 @@ def connect(self):

def check(self, _):
start = get_precise_time() # Avoid initialisation warning
self.log.debug("Connecting to %s %d", self.host, self.port)

if self.should_resolve_ip():
self.resolve_ip()
self.ip_cache_last_ts = start

self.log.debug("Connecting to %s on port %d", self.host, self.port)
try:
response_time = self.connect()
self.log.debug("%s:%d is UP", self.host, self.port)
Expand Down
22 changes: 21 additions & 1 deletion tcp_check/tests/test_tcp_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ def test_down(aggregator):
assert len(aggregator.service_checks('tcp.can_connect')) == 1


def test_reattempt_resolution():
def test_reattempt_resolution_on_error():
instance = deepcopy(common.INSTANCE)
check = TCPCheck(common.CHECK_NAME, {}, [instance])
check.check(instance)

assert check.ip_cache_duration is None

# IP is not normally re-resolved after the first check run
with mock.patch.object(check, 'resolve_ip', wraps=check.resolve_ip) as resolve_ip:
check.check(instance)
Expand All @@ -49,6 +51,24 @@ def test_reattempt_resolution():
assert resolve_ip.called


def test_reattempt_resolution_on_duration():
instance = deepcopy(common.INSTANCE)
instance['ip_cache_duration'] = 0
check = TCPCheck(common.CHECK_NAME, {}, [instance])
check.check(instance)

assert check.ip_cache_duration is not None

# ip_cache_duration = 0 means IP is re-resolved every check run
with mock.patch.object(check, 'resolve_ip', wraps=check.resolve_ip) as resolve_ip:
check.check(instance)
assert resolve_ip.called
check.check(instance)
assert resolve_ip.called
check.check(instance)
assert resolve_ip.called


def test_up(aggregator, check):
"""
Service expected to be up
Expand Down