Skip to content
Draft
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_dev/changelog.d/24238.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add process autodiscovery E2E testing helpers.
2 changes: 1 addition & 1 deletion datadog_checks_dev/datadog_checks/dev/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Licensed under a 3-clause BSD style license (see LICENSE)
from .__about__ import __version__
from .conditions import WaitFor
from .docker import docker_run, get_docker_hostname, get_e2e_discovery_metadata
from .docker import docker_run, get_docker_hostname, get_e2e_discovery_metadata, get_e2e_process_discovery_metadata
from .env import environment_run
from .errors import RetryError
from .fs import chdir, get_here, temp_chdir, temp_dir
Expand Down
55 changes: 49 additions & 6 deletions datadog_checks_dev/datadog_checks/dev/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,13 @@ def _assert_no_log_patterns(logs: str, patterns: Sequence[str], candidate_index:
)


def _get_auto_conf_volume(check_root: str | os.PathLike[str] | None = None) -> str:
check_root = os.fspath(check_root or find_check_root(depth=2))
check_name = os.path.basename(check_root)
check_pkg = os.path.join(check_root, 'datadog_checks', check_name)
return f'{check_pkg}/data/auto_conf.yaml:/etc/datadog-agent/conf.d/{check_name}.d/auto_conf.yaml:ro'


def get_e2e_discovery_metadata(
check_root: str | os.PathLike[str] | None = None,
) -> dict[str, list[str]]:
Expand All @@ -240,19 +247,55 @@ def get_e2e_discovery_metadata(
per-env config is temporarily replaced with an empty-instances file, leaving
``auto_conf.yaml`` as the sole AD template driving config-discovery.
"""
check_root = os.fspath(check_root or find_check_root(depth=1))
check_name = os.path.basename(check_root)
check_pkg = os.path.join(check_root, 'datadog_checks', check_name)
auto_conf = os.path.join(check_pkg, 'data', 'auto_conf.yaml')

return {
'docker_volumes': [
f'{auto_conf}:/etc/datadog-agent/conf.d/{check_name}.d/auto_conf.yaml:ro',
_get_auto_conf_volume(check_root),
'/var/run/docker.sock:/var/run/docker.sock:ro',
],
}


def get_e2e_process_discovery_metadata(
check_root: str | os.PathLike[str] | None = None,
) -> dict[str, list[str] | dict[str, str]]:
"""Return metadata for an e2e process-autodiscovery run.
Mounts the integration's ``auto_conf.yaml`` into the agent container
(without the Docker socket) and sets ``DD_AUTOCONFIG_EXCLUDE_FEATURES=docker``
so only the process listener is active.
Use ``dd_agent_check_discovery`` alongside this metadata.
"""
return {
'docker_volumes': [
_get_auto_conf_volume(check_root),
],
'env_vars': {
# Reduce the default service collection interval from 60s to speed
# up tests.
'DD_DISCOVERY_SERVICE_COLLECTION_INTERVAL': '10s',
# Process autodiscovery will only match processes that are not
# inside a container. Since our test environment actually run the
# services inside containers, we need to exclude the docker
# features, so that the agent doesn't know about the containers.
'DD_AUTOCONFIG_EXCLUDE_FEATURES': 'docker',
# The agent container has /proc:/host/proc mounted by ddev. Without this,
# the workloadmeta process-collector scans /proc (the container's own PID
# namespace) instead of /host/proc, so host processes aren't visible.
#
# This is normally automatically detected by the agent when running
# inside a container, but it's not in this case since we
# explicitly disable the agent's docker-based features.
'DD_PROC_ROOT': '/host/proc',
},
# system-probe(-lite) needs these capabilities to read /proc entries of
# other processes for service discovery. Without them it falls back to
# only scanning the agent's own PID namespace, which finds no host
# processes.
'cap_add': ['SYS_PTRACE', 'DAC_READ_SEARCH'],
}


def compose_file_active(compose_file):
"""
Returns a `bool` indicating whether or not a compose file has any active services.
Expand Down
45 changes: 38 additions & 7 deletions datadog_checks_dev/datadog_checks/dev/plugin/pytest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import re
from base64 import urlsafe_b64encode
from collections import namedtuple # Not using dataclasses for Py2 compatibility
from collections.abc import Collection
from io import open
from typing import Dict, List, Literal, Optional, Tuple, overload # noqa: F401

Expand Down Expand Up @@ -238,7 +239,7 @@ def run_check(config=None, **kwargs):


@pytest.fixture
def dd_agent_check_discovery(dd_agent_check):
def dd_agent_check_discovery(dd_agent_check, is_process_e2e):
"""Wrapper around ``dd_agent_check`` for config-discovery e2e tests.

Passes the empty-instances config required to let ``auto_conf.yaml`` drive
Expand All @@ -248,7 +249,13 @@ def dd_agent_check_discovery(dd_agent_check):
if not e2e_testing():
pytest.skip('Not running E2E tests')

def run(*, discovery_min_instances=1, discovery_timeout=30, **kwargs):
# Process autodiscovery needs to wait for the agent to recognize the process
# as a service which happens after a minimum process age of 1 minute. This
# time can be reduced significantly once agent-side support for reducing the
# minimum age via configuration is available.
extra_timeout = 60 if is_process_e2e else 0

def run(*, discovery_min_instances=1, discovery_timeout=30 + extra_timeout, **kwargs):
return dd_agent_check(
{'init_config': {}, 'instances': []},
discovery_min_instances=discovery_min_instances,
Expand Down Expand Up @@ -483,12 +490,29 @@ def enum_object_items(data_source, machine_name, object_name, detail_level):
)


def _is_process_e2e_env() -> bool:
return bool(os.getenv('DDEV_E2E_PROCESS_DISCOVERY'))


def _should_skip_in_process_e2e(keywords: Collection[str]) -> bool:
"""In the process-autodiscovery e2e env, only ``process_e2e`` tests run."""
return 'process_e2e' not in keywords


@pytest.fixture(scope='session')
def is_process_e2e() -> bool:
return _is_process_e2e_env()


def pytest_configure(config):
# pytest will emit warnings if these aren't registered ahead of time
for ttype in TEST_TYPES:
config.addinivalue_line('markers', '{}: {}'.format(ttype.name, ttype.description))

config.addinivalue_line("markers", "latest_metrics: marker for verifying support of new metrics")
config.addinivalue_line(
"markers", "process_e2e: also run this e2e test in the process autodiscovery e2e environment"
)


def pytest_addoption(parser):
Expand All @@ -499,15 +523,22 @@ def pytest_collection_modifyitems(config, items):
# at test collection time, this function gets called by pytest, see:
# https://docs.pytest.org/en/latest/example/simple.html#control-skipping-of-tests-according-to-command-line-option
# if the particular option is not present, it will skip all tests marked `latest_metrics`
if config.getoption("--run-latest-metrics"):
# --run-check-metrics given in cli: do not skip slow tests
return
skip_latest_metrics = (
None
if config.getoption("--run-latest-metrics")
else pytest.mark.skip(reason="need --run-latest-metrics option to run")
)
skip_non_process_e2e = (
pytest.mark.skip(reason="not a process autodiscovery e2e test") if _is_process_e2e_env() else None
)

skip_latest_metrics = pytest.mark.skip(reason="need --run-latest-metrics option to run")
for item in items:
if "latest_metrics" in item.keywords:
if skip_latest_metrics and "latest_metrics" in item.keywords:
item.add_marker(skip_latest_metrics)

if skip_non_process_e2e and _should_skip_in_process_e2e(item.keywords):
item.add_marker(skip_non_process_e2e)

item_path = item.path
if item_path is None:
continue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: cel_selector
description: CEL selector for autodiscovery.
enabled: true
example: {}
1 change: 1 addition & 0 deletions ddev/changelog.d/24238.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pass capabilities from e2e metadata `cap_add` to the Agent container via `docker --cap-add`.
3 changes: 3 additions & 0 deletions ddev/src/ddev/e2e/agent/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ def start(self, *, agent_build: str | None, local_packages: dict[Path, str], env
for key, value in sorted(env_vars.items()):
command.extend(['-e', f'{key}={value}'])

for cap in self.metadata.get('cap_add', []):
command.extend(['--cap-add', cap])

# The docker `--add-host` command will reliably create entries in the `/etc/hosts` file,
# otherwise, edits to that file will be overwritten on container restarts
for host, ip in self.metadata.get('custom_hosts', []):
Expand Down
5 changes: 5 additions & 0 deletions krakend/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,9 @@ files:
overrides:
value.example:
- krakend
- template: auto_conf/cel_selector
overrides:
cel_selector.example:
processes:
- "process.name == 'krakend-this-will-never-match'"
- template: auto_conf/discovery
1 change: 1 addition & 0 deletions krakend/changelog.d/24238.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add process autodiscovery support.
6 changes: 6 additions & 0 deletions krakend/datadog_checks/krakend/data/auto_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
ad_identifiers:
- krakend

## CEL selector for autodiscovery.
#
cel_selector:
processes:
- process.name == 'krakend-this-will-never-match'

## Enables configuration discovery
#
discovery: {}
Expand Down
8 changes: 7 additions & 1 deletion krakend/hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@ check-types = true
python = ["3.13"]
krakend = ["2.10"]

[[envs.default.matrix]]
python = ["3.13"]
krakend = ["2.10"]
discovery = ["process"]

[envs.default.overrides]
matrix.krakend.env-vars = "KRAKEND_VERSION"
matrix.krakend.dependencies = ["httpx"]
matrix.discovery.env-vars = "DDEV_E2E_PROCESS_DISCOVERY"

[envs.lab]
dependencies = ["click", "requests", "rich", "httpx", "pyyaml"]
Expand All @@ -18,4 +24,4 @@ generate = "python -m tests.lab.traffic_generator generate {args}"
stop = "python -m tests.lab.traffic_generator stop {args}"

[envs.lab.env-vars]
KRAKEND_IS_LAB = "true"
KRAKEND_IS_LAB = "true"
15 changes: 14 additions & 1 deletion krakend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from datadog_checks.dev import docker_run, get_e2e_discovery_metadata
from datadog_checks.dev.conditions import CheckEndpoints
from datadog_checks.dev.docker import get_e2e_process_discovery_metadata
from datadog_checks.dev.structures import LazyFunction
from datadog_checks.krakend import KrakendCheck
from tests.helpers import BAKEND_API_ENDPOINT, GATEWAY_ENDPOINT, OPEN_METRICS_ENDPOINT, generate_sample_traffic
Expand Down Expand Up @@ -60,8 +61,18 @@ def run_docker_e2e(env_vars: dict[str, str], conditions: list[LazyFunction]):
)


def run_docker_process_e2e(env_vars: dict[str, str], conditions: list[LazyFunction]):
with docker_run(
compose_file=str(COMPOSE_FILE_E2E),
env_vars=env_vars,
conditions=conditions,
):
asyncio.run(generate_sample_traffic())
yield ({'instances': []}, get_e2e_process_discovery_metadata())


@pytest.fixture(scope="session")
def dd_environment(dd_save_state, is_lab):
def dd_environment(dd_save_state, is_lab, is_process_e2e):
"""
Integration test fixture that starts KrakenD and FastAPI services using Docker Compose.
Waits for services to be healthy and generates sample traffic to ensure metrics are available.
Expand All @@ -81,6 +92,8 @@ def dd_environment(dd_save_state, is_lab):

if is_lab:
yield from run_docker_lab(env_vars, conditions)
elif is_process_e2e:
yield from run_docker_process_e2e(env_vars, conditions)
else:
yield from run_docker_e2e(env_vars, conditions)

Expand Down
1 change: 1 addition & 0 deletions krakend/tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def test_e2e(dd_agent_check, instance: InstanceBuilder):


@pytest.mark.e2e
@pytest.mark.process_e2e
def test_e2e_discovery(dd_agent_check_discovery, is_lab):
# In the lab environment we currently do not mount auto_conf.yaml into the
# Agent container, so the Agent has no Autodiscovery template to trigger
Expand Down
Loading