Skip to content
Open
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
1 change: 1 addition & 0 deletions datadog_checks_base/changelog.d/23849.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ``resolve_db_host`` treating loopback IP literals (e.g. ``::1``) as DNS resolution failures, which caused database checks to submit metrics with the wrong host tag and miss agent host tags.
20 changes: 18 additions & 2 deletions datadog_checks_base/datadog_checks/base/utils/db/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import time
from concurrent.futures.thread import ThreadPoolExecutor
from enum import Enum, auto
from ipaddress import IPv4Address
from ipaddress import IPv4Address, IPv6Address, ip_address
from typing import Any, Callable, Dict, List, Optional, Tuple, Union # noqa: F401

from cachetools import TTLCache
Expand Down Expand Up @@ -162,12 +162,28 @@ def acquire(self, key):
return True


def _try_parse_db_host_ip(db_host: str) -> IPv4Address | IPv6Address | None:
"""Try to parse db_host as an IP address."""
try:
return ip_address(db_host.strip())
except ValueError:
return None


def _is_local_db_host(db_host: str | None) -> bool:
"""Return True when the DB is reached via localhost, a unix socket, or a loopback IP."""
if not db_host or db_host == 'localhost' or db_host.startswith('/'):
return True
addr = _try_parse_db_host_ip(db_host)
return addr is not None and addr.is_loopback


def resolve_db_host(db_host):
if db_host and db_host.endswith('.local'):
return db_host

agent_hostname = datadog_agent.get_hostname()
if not db_host or db_host in {'localhost', '127.0.0.1'} or db_host.startswith('/'):
if _is_local_db_host(db_host):
return agent_hostname

try:
Expand Down
18 changes: 16 additions & 2 deletions datadog_checks_base/tests/base/utils/db/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,25 @@
@pytest.mark.parametrize(
"db_host, agent_hostname, want",
[
# Unset host or local hostname
(None, "agent_hostname", "agent_hostname"),
("localhost", "agent_hostname", "agent_hostname"),
# Unix socket
("/var/run/mysqld.sock", "agent_hostname", "agent_hostname"),
# Loopback IP literals (incl. zone-scoped IPv6)
("127.0.0.1", "agent_hostname", "agent_hostname"),
("::1", "agent_hostname", "agent_hostname"),
("::1%lo0", "agent_hostname", "agent_hostname"),
# Non-loopback IP literals keep the configured host
("169.254.169.254", "agent_hostname", "169.254.169.254"),
("fe80::1", "agent_hostname", "fe80::1"),
("fe80::1%eth0", "agent_hostname", "fe80::1%eth0"),
("192.0.2.1", "agent_hostname", "192.0.2.1"),
("2001:db8::1", "agent_hostname", "2001:db8::1"),
# Resolved DB host shares the agent host IP
("192.0.2.1", "192.0.2.1", "192.0.2.1"),
("192.0.2.1", "192.0.2.254", "192.0.2.1"),
# Hostname resolution failures fall back to the configured db_host
("socket.gaierror", "agent_hostname", "socket.gaierror"),
(
"greater-than-or-equal-to-64-characters-causes-unicode-error-----",
Expand All @@ -44,8 +59,7 @@
),
("192.0.2.1", "socket.gaierror", "192.0.2.1"),
("192.0.2.1", "greater-than-or-equal-to-64-characters-causes-unicode-error-----", "192.0.2.1"),
("192.0.2.1", "192.0.2.1", "192.0.2.1"),
("192.0.2.1", "192.0.2.254", "192.0.2.1"),
# mDNS .local names are passed through unchanged
("postgres.svc.local", "some-pod", "postgres.svc.local"),
],
)
Expand Down
Loading