diff --git a/agentex/src/temporal/run_worker.py b/agentex/src/temporal/run_worker.py index de44cba6..08b86717 100644 --- a/agentex/src/temporal/run_worker.py +++ b/agentex/src/temporal/run_worker.py @@ -40,6 +40,37 @@ # Task queue name for agentex server operations AGENTEX_SERVER_TASK_QUEUE = "agentex-server" + +OTLP_METRICS_DEFAULT_PORT = 4317 + + +def build_metrics_url(host_url: str | None) -> str | None: + """Build the OTLP metrics endpoint URL from a ``host`` or ``host:port`` value. + + Accepts a bare hostname/IPv4, an IPv6 literal (bracketed or not), or any of + those with an explicit ``:port`` suffix. Per RFC 3986 only IPv6 literals are + wrapped in brackets, and the default OTLP gRPC port is appended only when the + value does not already carry one. Returns None when no host is configured. + """ + if not host_url: + return None + + host = host_url.strip() + port: str | None = None + + if host.startswith("["): + bracket_end = host.find("]") + if bracket_end != -1: + rest = host[bracket_end + 1 :] + port = rest[1:] if rest.startswith(":") else None + host = host[1:bracket_end] + elif host.count(":") == 1: + host, port = host.split(":", 1) + + bracketed = f"[{host}]" if ":" in host else host + return f"http://{bracketed}:{port or OTLP_METRICS_DEFAULT_PORT}" + + # Global worker instance health_check_worker: Worker | None = None @@ -84,7 +115,7 @@ async def run_worker( # Check for metrics configuration host_url = os.environ.get("DD_AGENT_HOST") - metrics_url = f"http://[{host_url}]:4317" if host_url else None + metrics_url = build_metrics_url(host_url) if metrics_url: logger.info(f"Configuring worker with metrics URL: {metrics_url}") diff --git a/agentex/tests/unit/temporal/test_run_worker_metrics_url.py b/agentex/tests/unit/temporal/test_run_worker_metrics_url.py new file mode 100644 index 00000000..6253808f --- /dev/null +++ b/agentex/tests/unit/temporal/test_run_worker_metrics_url.py @@ -0,0 +1,49 @@ +import pytest +from src.temporal.run_worker import build_metrics_url + + +@pytest.mark.unit +def test_metrics_url_is_none_when_host_unset(): + assert build_metrics_url(None) is None + assert build_metrics_url("") is None + + +@pytest.mark.unit +@pytest.mark.parametrize("host", ["localhost", "datadog-agent", "10.0.0.5"]) +def test_hostname_and_ipv4_are_not_bracketed(host): + assert build_metrics_url(host) == f"http://{host}:4317" + + +@pytest.mark.unit +@pytest.mark.parametrize( + "host,expected", + [("::1", "http://[::1]:4317"), ("fe80::1", "http://[fe80::1]:4317")], +) +def test_ipv6_literal_is_bracketed(host, expected): + assert build_metrics_url(host) == expected + + +@pytest.mark.unit +@pytest.mark.parametrize( + "host,expected", + [ + ("datadog-agent:4317", "http://datadog-agent:4317"), + ("10.0.0.5:4317", "http://10.0.0.5:4317"), + ("datadog-agent:5555", "http://datadog-agent:5555"), + ], +) +def test_hostname_with_explicit_port_is_not_bracketed(host, expected): + assert build_metrics_url(host) == expected + + +@pytest.mark.unit +@pytest.mark.parametrize( + "host,expected", + [ + ("[::1]", "http://[::1]:4317"), + ("[fe80::1]", "http://[fe80::1]:4317"), + ("[::1]:5555", "http://[::1]:5555"), + ], +) +def test_already_bracketed_ipv6_literal(host, expected): + assert build_metrics_url(host) == expected