Skip to content

Commit 4050f96

Browse files
tests: verify that metrics endpoints have metrics
1 parent 4dd40dc commit 4050f96

4 files changed

Lines changed: 131 additions & 9 deletions

File tree

poetry.lock

Lines changed: 17 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ types-pytz = "^2025.2.0.20250516"
3737
mypy = "^1.16.1"
3838
types-pyyaml = "^6.0.12.20250516"
3939
semver = "^3.0.4"
40+
prometheus-client = "^0.22.1"
4041

4142
[build-system]
4243
requires = ["poetry-core"]

tests/integration/lib/helpers.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@
66
import time
77

88
import pyhelm3
9+
from lightkube import AsyncClient
10+
from lightkube.models.core_v1 import (
11+
Capabilities,
12+
Container,
13+
PodSpec,
14+
SeccompProfile,
15+
SecurityContext,
16+
)
917
from lightkube.models.meta_v1 import ObjectMeta
10-
from lightkube.resources.core_v1 import ConfigMap, Endpoints, Namespace, Secret
18+
from lightkube.resources.core_v1 import ConfigMap, Endpoints, Namespace, Pod, Secret
1119

1220
from ..artifacts import CertKey, generate_cert
1321
from .utils import merge
@@ -98,3 +106,50 @@ async def get_deployment_marker(kube_client, generated_data, marker: str):
98106
name=f"{generated_data.release_name}-markers",
99107
)
100108
return configmap.data.get(marker)
109+
110+
111+
async def run_pod_with_args(kube_client: AsyncClient, namespace, image_name, pod_name, args):
112+
pod = Pod(
113+
metadata=ObjectMeta(name=pod_name + "-" + str(int(time.time()*1000)), namespace=namespace),
114+
spec=PodSpec(restartPolicy="Never",
115+
containers=[Container(name="cmd", image=image_name, args=args,
116+
securityContext=SecurityContext(
117+
seccompProfile=SeccompProfile(type="RuntimeDefault"),
118+
capabilities=Capabilities(drop=["ALL"]),
119+
readOnlyRootFilesystem=True,
120+
allowPrivilegeEscalation=False,
121+
runAsNonRoot=True,
122+
runAsUser=3000,
123+
runAsGroup=3000,
124+
),
125+
)
126+
])
127+
)
128+
try:
129+
await kube_client.create(pod)
130+
start_time = time.time()
131+
now = time.time()
132+
completed = False
133+
while start_time + 30 > now and not completed:
134+
found_pod = await kube_client.get(Pod, name=pod.metadata.name, namespace=pod.metadata.namespace)
135+
if (found_pod.status.containerStatuses
136+
and found_pod.status.containerStatuses[0].lastState
137+
and found_pod.status.containerStatuses[0].lastState.terminated
138+
and found_pod.status.containerStatuses[0].lastState.terminated.reason == "Completed"):
139+
completed = True
140+
else:
141+
now = time.time()
142+
await asyncio.sleep(1)
143+
else:
144+
if start_time + 30 > now:
145+
raise RuntimeError(f"Pod {pod.metadata.name} did not start in time "
146+
f"(failed after {time.time() - now} seconds), "
147+
f"pod status: {found_pod.status}")
148+
149+
log_lines = ""
150+
async for log_line in kube_client.log(pod.metadata.name, namespace=pod.metadata.namespace,
151+
container="cmd"):
152+
log_lines += log_line
153+
return log_lines
154+
finally:
155+
await kube_client.delete(Pod, name=pod.metadata.name, namespace=namespace)

tests/integration/test_networking.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
from lightkube import AsyncClient
1010
from lightkube import operators as op
1111
from lightkube.resources.core_v1 import Pod, Service
12+
from prometheus_client.parser import text_string_to_metric_families
1213

1314
from .fixtures.data import ESSData
14-
from .lib.helpers import wait_for_endpoint_ready
15+
from .lib.helpers import run_pod_with_args, wait_for_endpoint_ready
1516
from .lib.utils import read_service_monitor_kind
1617

1718

@@ -162,3 +163,58 @@ async def test_pods_monitored(
162163
assert all_monitorable_pods == monitored_pods, (
163164
f"Some pods are not monitored : {', '.join(list(set(all_monitorable_pods) ^ set(monitored_pods)))}"
164165
)
166+
167+
168+
169+
@pytest.mark.skipif(
170+
os.environ.get("SKIP_SERVICE_MONITORS_CRDS", "false") == "true", reason="ServiceMonitors not deployed"
171+
)
172+
@pytest.mark.asyncio_cooperative
173+
@pytest.mark.usefixtures("matrix_stack")
174+
async def test_service_monitors_point_to_metrics(
175+
kube_client: AsyncClient,
176+
generated_data: ESSData,
177+
):
178+
async for service_monitor in kube_client.list(
179+
await read_service_monitor_kind(kube_client),
180+
namespace=generated_data.ess_namespace,
181+
labels={"app.kubernetes.io/part-of": op.in_(["matrix-stack"])},
182+
):
183+
async for service in kube_client.list(
184+
Service, namespace=generated_data.ess_namespace, labels=service_monitor["spec"]["selector"]["matchLabels"]
185+
):
186+
assert service.metadata, f"Encountered a service without metadata : {service}"
187+
assert service.spec, f"Encountered a service without spec : {service}"
188+
assert service.spec.ports, f"Ecountered a service without port : {service}"
189+
assert service.spec.selector, f"Ecountered a service without selectors : {service}"
190+
191+
for endpoint in service_monitor["spec"]["endpoints"]:
192+
service_port_names = [port.name for port in service.spec.ports if port.name]
193+
if endpoint["port"] in service_port_names:
194+
break
195+
# This Service does not have the named port. Potentially there's another Service that covers it
196+
else:
197+
continue
198+
assert await has_actual_metrics_on_endpoint(
199+
kube_client, generated_data, service, service_monitor["spec"]["endpoints"]
200+
)
201+
202+
203+
async def has_actual_metrics_on_endpoint(
204+
kube_client: AsyncClient, generated_data: ESSData, service: Service, endpoints
205+
):
206+
found_metrics = False
207+
for endpoint in endpoints:
208+
for port_spec in service.spec.ports:
209+
if port_spec.name == endpoint["port"]:
210+
metrics_data = await run_pod_with_args(
211+
kube_client,
212+
generated_data.ess_namespace,
213+
"curlimages/curl:latest",
214+
"curl",
215+
["-s", f"http://{service.metadata.name}.{generated_data.ess_namespace}.svc.cluster.local:{port_spec.port}/metrics"],
216+
)
217+
for metric_family in text_string_to_metric_families(metrics_data):
218+
assert metric_family.name, "Metric family has no name"
219+
found_metrics = True
220+
return found_metrics

0 commit comments

Comments
 (0)