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
36 changes: 25 additions & 11 deletions argo_workflows/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# (C) Datadog, Inc. 2024-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import json
import os
from contextlib import ExitStack

import pytest

from datadog_checks.dev import get_here
from datadog_checks.dev._env import get_state, save_state
from datadog_checks.dev.kind import kind_run
from datadog_checks.dev.kube_port_forward import port_forward
from datadog_checks.dev.subprocess import run_command

HERE = get_here()

CONTROLLER_IP_STATE = 'argo_workflows_controller_ip'


def setup_argo_wf():
run_command(["kubectl", "create", "ns", "argo"])
Expand All @@ -22,19 +24,31 @@ def setup_argo_wf():
)
# run_command(["kubectl", "wait", "pods", "--all", "--for=condition=Ready", "--timeout=300s"])

# This only runs once, when the Kind cluster is created, so the resolved pod IP is cached here
# rather than re-resolved by `dd_environment` on every invocation.
save_state(CONTROLLER_IP_STATE, get_workflow_controller_pod_ip())


def get_workflow_controller_pod_ip() -> str:
# There is no Service for workflow-controller, so the pod IP is fetched directly.
result = run_command(
["kubectl", "get", "pods", "--namespace", "argo", "--selector", "app=workflow-controller", "--output", "json"],
capture='out',
check=True,
)
pods = json.loads(result.stdout)['items']
if len(pods) != 1 or not pods[0].get('status', {}).get('podIP'):
raise RuntimeError(f'Expected exactly one ready workflow-controller pod, found {len(pods)}')
return pods[0]['status']['podIP']


@pytest.fixture(scope='session')
def dd_environment():
with kind_run(conditions=[setup_argo_wf]) as kubeconfig:
with ExitStack() as stack:
controller_host, controller_port = stack.enter_context(
# there is no service for workflow-controller
port_forward(kubeconfig, 'argo', 9090, 'deployment', 'workflow-controller')
)
# save this instance to use for openmetrics_v2 instance, since the endpoint is different each run
# dd_save_state("argocd_instance", instance)

yield {'openmetrics_endpoint': f'http://{controller_host}:{controller_port}/metrics'}
controller_ip = get_state(CONTROLLER_IP_STATE)
metadata = {'agent_type': 'kubernetes', 'kubernetes': {'kubeconfig': kubeconfig}}

yield {'openmetrics_endpoint': f'http://{controller_ip}:9090/metrics'}, metadata


@pytest.fixture
Expand Down
69 changes: 49 additions & 20 deletions fluxcd/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
# (C) Datadog, Inc. 2024-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import json
import os
from contextlib import ExitStack
from unittest import mock

import pytest

from datadog_checks.dev import get_here
from datadog_checks.dev._env import get_state, save_state
from datadog_checks.dev.kind import kind_run
from datadog_checks.dev.kube_port_forward import port_forward
from datadog_checks.dev.subprocess import run_command
from datadog_checks.fluxcd import FluxcdCheck

HERE = get_here()
opj = os.path.join

# The Services in flux-system (source-controller, notification-controller) only expose the
# controllers' API port (9090) as port 80, not the Prometheus metrics port (8080), so Service DNS
# cannot reach /metrics for any controller. All four controllers are single-replica Deployments, so
# their pod IP is fetched directly and used to reach the metrics port. The `allow-scraping`
# NetworkPolicy shipped in install.yaml explicitly permits cross-namespace ingress on port 8080,
# confirming this is the intended scrape path.
CONTROLLERS = ('source-controller', 'helm-controller', 'kustomize-controller', 'notification-controller')
METRICS_PORT = 8080
POD_IP_STATE_PREFIX = 'fluxcd_pod_ip_'


def setup_fluxcd():
run_command(["kubectl", "apply", "--filename", opj(HERE, 'kind', "install.yaml")])
Expand All @@ -31,29 +41,48 @@ def setup_fluxcd():
"--timeout=300s",
]
)
# Save each controller's pod IP now, while the cluster is guaranteed to be up. `dd_environment`
# runs again (without `conditions`, so without this function) on every `ddev env` invocation,
# including `stop`, when the cluster may already be gone.
for controller in CONTROLLERS:
save_state(POD_IP_STATE_PREFIX + controller, get_controller_pod_ip(controller))


def get_controller_pod_ip(controller: str) -> str:
result = run_command(
[
"kubectl",
"get",
"pods",
"--namespace",
"flux-system",
"--selector",
f"app={controller}",
"--output",
"json",
],
capture='out',
check=True,
)
pods = json.loads(result.stdout)['items']
if len(pods) != 1 or not pods[0].get('status', {}).get('podIP'):
raise RuntimeError(f'Expected exactly one ready {controller} pod, found {len(pods)}')
return pods[0]['status']['podIP']


@pytest.fixture(scope='session')
def dd_environment():
with kind_run(conditions=[setup_fluxcd]) as kubeconfig:
instances = []
with ExitStack() as stack:
for controller in (
'source-controller',
'helm-controller',
'kustomize-controller',
'notification-controller',
):
host, port = stack.enter_context(
port_forward(kubeconfig, 'flux-system', 8080, 'deployment', controller)
)
instances.append(
{
'openmetrics_endpoint': 'http://{}:{}/metrics'.format(host, port),
}
)

yield {'instances': instances}
instances = [
{
'openmetrics_endpoint': f'http://{get_state(POD_IP_STATE_PREFIX + controller)}:{METRICS_PORT}/metrics',
}
for controller in CONTROLLERS
]

metadata = {'agent_type': 'kubernetes', 'kubernetes': {'kubeconfig': kubeconfig}}

yield {'instances': instances}, metadata


@pytest.fixture
Expand Down
3 changes: 3 additions & 0 deletions gearmand/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,6 @@ files:
value.example:
- gearmand
- template: auto_conf/discovery
overrides:
discovery.example:
metrics_prefix: gearman
1 change: 1 addition & 0 deletions gearmand/changelog.d/24861.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``metrics_prefix`` to the ``discovery`` block of ``auto_conf.yaml``.
3 changes: 2 additions & 1 deletion gearmand/datadog_checks/gearmand/data/auto_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ ad_identifiers:

## Enables configuration discovery
#
discovery: {}
discovery:
metrics_prefix: gearman

## Unused init configuration
#
Expand Down
3 changes: 3 additions & 0 deletions krakend/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,6 @@ files:
processes:
- "process.name == 'krakend'"
- template: auto_conf/discovery
overrides:
discovery.example:
metrics_prefix: krakend.api
1 change: 1 addition & 0 deletions krakend/changelog.d/24861.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``metrics_prefix`` to the ``discovery`` block of ``auto_conf.yaml``.
3 changes: 2 additions & 1 deletion krakend/datadog_checks/krakend/data/auto_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ cel_selector:

## Enables configuration discovery
#
discovery: {}
discovery:
metrics_prefix: krakend.api

## Unused init configuration
#
Expand Down
3 changes: 3 additions & 0 deletions prefect/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,7 @@ files:
value.example:
- prefect
- template: auto_conf/discovery
overrides:
discovery.example:
metrics_prefix: prefect.server

1 change: 1 addition & 0 deletions prefect/changelog.d/24861.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ``metrics_prefix`` to the ``discovery`` block of ``auto_conf.yaml``.
3 changes: 2 additions & 1 deletion prefect/datadog_checks/prefect/data/auto_conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ ad_identifiers:

## Enables configuration discovery
#
discovery: {}
discovery:
metrics_prefix: prefect.server

## Unused init configuration
#
Expand Down
125 changes: 74 additions & 51 deletions weaviate/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,84 +3,107 @@
# Licensed under a 3-clause BSD style license (see LICENSE)
import json
import os
import time
from contextlib import ExitStack

import pytest
import requests

from datadog_checks.dev import get_here
from datadog_checks.dev._env import get_state, save_state
from datadog_checks.dev.kind import kind_run
from datadog_checks.dev.kube_port_forward import port_forward
from datadog_checks.dev.subprocess import run_command
from datadog_checks.weaviate.check import DEFAULT_LIVENESS_ENDPOINT

from .common import BATCH_OBJECTS, USE_AUTH

HERE = get_here()
opj = os.path.join

NAMESPACE = 'weaviate'
# No Service targets the StatefulSet's metrics port, only the API port (see weaviate_install.yaml /
# weaviate_auth.yaml), so the API endpoint uses Service DNS while metrics fall back to the pod IP.
WEAVIATE_API_ENDPOINT = f'http://weaviate.{NAMESPACE}.svc.cluster.local:80'
POD_IP_STATE = 'weaviate_pod_ip'


def setup_weaviate():
run_command(['kubectl', 'create', 'ns', 'weaviate'])
run_command(['kubectl', 'create', 'ns', 'weaviate'], check=True)

if USE_AUTH:
run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_auth.yaml'), '-n', 'weaviate'])
run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_auth.yaml'), '-n', 'weaviate'], check=True)
else:
run_command(['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_install.yaml'), '-n', 'weaviate'])
run_command(
['kubectl', 'apply', '-f', opj(HERE, 'kind', 'weaviate_install.yaml'), '-n', 'weaviate'], check=True
)

# Tries to ensure that the Kubernetes resources are deployed and ready before we do anything else
run_command(['kubectl', 'rollout', 'status', 'statefulset/weaviate', '-n', 'weaviate'])
run_command(['kubectl', 'wait', 'pods', '--all', '-n', 'weaviate', '--for=condition=Ready', '--timeout=600s'])
run_command(['kubectl', 'rollout', 'status', 'statefulset/weaviate', '-n', 'weaviate'], check=True)
run_command(
['kubectl', 'wait', 'pods', '--all', '-n', 'weaviate', '--for=condition=Ready', '--timeout=600s'],
check=True,
)

# `setup_weaviate` only runs once, on the initial `ddev env start`. Later invocations of the
# `dd_environment` fixture (e.g. during `ddev env stop`) run in a fresh process after the cluster
# is torn down, so the pod IP is cached here via `save_state`/`get_state` rather than looked up live.
save_state(POD_IP_STATE, get_weaviate_pod_ip())

make_weaviate_request()


def get_weaviate_pod_ip() -> str:
result = run_command(
['kubectl', 'get', 'pods', '--namespace', NAMESPACE, '--selector', 'app=weaviate', '--output', 'json'],
capture='out',
check=True,
)
pods = json.loads(result.stdout)['items']
if len(pods) != 1 or not pods[0].get('status', {}).get('podIP'):
raise RuntimeError(f'Expected one ready Weaviate pod, found {len(pods)}')
return pods[0]['status']['podIP']


def make_weaviate_request():
# This helps seed some dummy data in to Weaviate to make some metrics available. Run from a
# temporary pod since the host cannot reach the cluster directly.
weaviate_batch_endpoint = f'{WEAVIATE_API_ENDPOINT}/v1/batch/objects'

command = [
'kubectl',
'run',
'weaviate-seed-data',
'--namespace',
NAMESPACE,
'--image=curlimages/curl',
'--restart=Never',
'--attach',
'--rm',
'--quiet',
'--',
'curl',
'-sf',
'-X',
'POST',
weaviate_batch_endpoint,
'-H',
'Content-Type: application/json',
]
if USE_AUTH:
command.extend(['-H', 'Authorization: Bearer test123'])
command.extend(['-d', json.dumps(BATCH_OBJECTS)])

run_command(command, capture='both', check=True)


@pytest.fixture(scope='session')
def dd_environment():
with kind_run(conditions=[setup_weaviate]) as kubeconfig, ExitStack() as stack:
weaviate_host, weaviate_port = stack.enter_context(
port_forward(kubeconfig, 'weaviate', 2112, 'statefulset', 'weaviate')
)
weaviate_host, weaviate_api_port = stack.enter_context(
port_forward(kubeconfig, 'weaviate', 8080, 'statefulset', 'weaviate')
)
with kind_run(conditions=[setup_weaviate]) as kubeconfig:
weaviate_metrics_port = 2112

instance = {
'openmetrics_endpoint': f'http://{weaviate_host}:{weaviate_port}/metrics',
'weaviate_api_endpoint': f'http://{weaviate_host}:{weaviate_api_port}',
'openmetrics_endpoint': f'http://{get_state(POD_IP_STATE)}:{weaviate_metrics_port}/metrics',
'weaviate_api_endpoint': WEAVIATE_API_ENDPOINT,
}
if USE_AUTH:
instance['headers'] = {'Authorization': 'Bearer test123'}

make_weaviate_request(instance)
yield instance


def make_weaviate_request(instance):
# This helps seed some dummy data in to Weaviate to make some metrics available
weaviate_api_endpoint = instance.get('weaviate_api_endpoint')
weaviate_batch_endpoint = f'{weaviate_api_endpoint}/v1/batch/objects'
headers = {'content-type': 'application/json'}

if instance.get('headers'):
headers.update(instance['headers'])

if ready_check(weaviate_api_endpoint, 300):
requests.post(weaviate_batch_endpoint, headers=headers, data=json.dumps(BATCH_OBJECTS))


def ready_check(endpoint, timeout=300):
# Sometimes the API endpoint isn't ready when the cluster is ready. This will try to ensure the
# API is ready for requests before we seed some dummy data.
stop_time = time.time() + timeout
endpoint = f'{endpoint}{DEFAULT_LIVENESS_ENDPOINT}'
while time.time() < stop_time:
try:
response = requests.get(endpoint, timeout=5)
if response.ok:
return True
except requests.RequestException as e:
print(f'Request failed: {e}')

time.sleep(1)
metadata = {'agent_type': 'kubernetes', 'kubernetes': {'kubeconfig': kubeconfig}}

return False
yield instance, metadata
Loading