237 lines
7.1 KiB
Python
237 lines
7.1 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
NGINX_CONFIG = """server {
|
|
listen 80;
|
|
server_name mo.wooo.work;
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name mo.wooo.work;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:5003;
|
|
}
|
|
}
|
|
"""
|
|
|
|
NGINX_FRAGMENT = """location = /metrics {
|
|
allow 127.0.0.1;
|
|
allow 172.16.0.0/12;
|
|
deny all;
|
|
proxy_pass http://127.0.0.1:5003/metrics;
|
|
}
|
|
"""
|
|
|
|
PROMETHEUS_CONFIG = """global:
|
|
scrape_interval: 60s
|
|
|
|
scrape_configs:
|
|
- job_name: existing
|
|
static_configs:
|
|
- targets: ['127.0.0.1:9090']
|
|
"""
|
|
|
|
PROMETHEUS_SNIPPET = """ - job_name: 'ewoooc-app'
|
|
scheme: http
|
|
metrics_path: /metrics
|
|
static_configs:
|
|
- targets: ['172.20.0.1:19191']
|
|
"""
|
|
|
|
NGINX_BRIDGE = """server {
|
|
listen 172.20.0.1:19191;
|
|
server_name ewooooc-metrics.internal;
|
|
location = /metrics {
|
|
allow 172.16.0.0/12;
|
|
deny all;
|
|
proxy_pass https://192.168.0.188/metrics;
|
|
}
|
|
}
|
|
"""
|
|
|
|
|
|
def test_render_nginx_config_targets_only_tls_server_and_is_idempotent():
|
|
from services.metrics_edge_controlled_apply_service import render_nginx_config
|
|
|
|
rendered = render_nginx_config(NGINX_CONFIG, NGINX_FRAGMENT)
|
|
second = render_nginx_config(rendered, NGINX_FRAGMENT)
|
|
|
|
assert rendered == second
|
|
assert rendered.count("location = /metrics") == 1
|
|
assert rendered.index("listen 443") < rendered.index("location = /metrics")
|
|
assert rendered.index("location = /metrics") < rendered.index("location / {")
|
|
|
|
|
|
def test_render_prometheus_config_is_idempotent():
|
|
from services.metrics_edge_controlled_apply_service import render_prometheus_config
|
|
|
|
rendered = render_prometheus_config(PROMETHEUS_CONFIG, PROMETHEUS_SNIPPET)
|
|
second = render_prometheus_config(rendered, PROMETHEUS_SNIPPET)
|
|
|
|
assert rendered == second
|
|
assert rendered.count("job_name: 'ewoooc-app'") == 1
|
|
|
|
|
|
def test_render_standalone_nginx_bridge_is_idempotent():
|
|
from services.metrics_edge_controlled_apply_service import render_nginx_config
|
|
|
|
rendered = render_nginx_config("", NGINX_BRIDGE)
|
|
second = render_nginx_config(rendered, NGINX_BRIDGE)
|
|
|
|
assert rendered == second
|
|
assert rendered.count("listen 172.20.0.1:19191") == 1
|
|
|
|
|
|
def test_build_plan_is_no_write_and_commit_safe(tmp_path: Path):
|
|
from services.metrics_edge_controlled_apply_service import build_metrics_edge_plan
|
|
|
|
paths = {
|
|
"nginx_config": tmp_path / "nginx.conf",
|
|
"prometheus_config": tmp_path / "prometheus.yml",
|
|
"nginx_fragment": tmp_path / "location.conf",
|
|
"prometheus_snippet": tmp_path / "scrape.yml",
|
|
}
|
|
paths["nginx_config"].write_text(NGINX_CONFIG, encoding="utf-8")
|
|
paths["prometheus_config"].write_text(PROMETHEUS_CONFIG, encoding="utf-8")
|
|
paths["nginx_fragment"].write_text(NGINX_FRAGMENT, encoding="utf-8")
|
|
paths["prometheus_snippet"].write_text(PROMETHEUS_SNIPPET, encoding="utf-8")
|
|
|
|
plan = build_metrics_edge_plan(
|
|
**paths,
|
|
run_id="sec-p0-001-test",
|
|
work_item_id="SEC-P0-001",
|
|
)
|
|
|
|
assert plan["mode"] == "check"
|
|
assert plan["nginx"]["changed"] is True
|
|
assert plan["prometheus"]["changed"] is True
|
|
assert paths["nginx_config"].read_text(encoding="utf-8") == NGINX_CONFIG
|
|
|
|
|
|
def test_prometheus_write_preserves_bind_mount_inode(tmp_path: Path):
|
|
from services.metrics_edge_controlled_apply_service import _inplace_write
|
|
|
|
config = tmp_path / "prometheus.yml"
|
|
config.write_text(PROMETHEUS_CONFIG, encoding="utf-8")
|
|
inode_before = config.stat().st_ino
|
|
|
|
_inplace_write(config, "global:\n scrape_interval: 30s\n")
|
|
|
|
assert config.stat().st_ino == inode_before
|
|
assert config.read_text(encoding="utf-8") == "global:\n scrape_interval: 30s\n"
|
|
|
|
|
|
def test_prometheus_config_sha_detects_stale_container_mount(tmp_path: Path, monkeypatch):
|
|
from services import metrics_edge_controlled_apply_service as service
|
|
|
|
config = tmp_path / "prometheus.yml"
|
|
config.write_text("candidate", encoding="utf-8")
|
|
stale_sha = "0" * 64
|
|
monkeypatch.setattr(
|
|
service,
|
|
"_run",
|
|
lambda _command: type("Completed", (), {"stdout": f"{stale_sha} config"})(),
|
|
)
|
|
|
|
host_sha, container_sha = service._prometheus_config_sha(config)
|
|
|
|
assert host_sha != container_sha
|
|
assert container_sha == stale_sha
|
|
|
|
|
|
def test_metrics_canary_requires_ewoooc_product_markers():
|
|
from services.metrics_edge_controlled_apply_service import _parse_metrics_canary
|
|
|
|
valid = (
|
|
"momo_app_info 1\nmomo_app_health 1\nmomo_database_up 1"
|
|
"\n__EWOOOC_HTTP_STATUS__:200"
|
|
)
|
|
wrong_exporter = "fail2ban_total_failed 0\n__EWOOOC_HTTP_STATUS__:200"
|
|
|
|
assert _parse_metrics_canary(valid) == ("200", True)
|
|
assert _parse_metrics_canary(wrong_exporter) == ("200", False)
|
|
|
|
|
|
def test_metrics_canary_waits_for_new_nginx_worker(monkeypatch):
|
|
from services import metrics_edge_controlled_apply_service as service
|
|
|
|
outputs = iter([
|
|
"forbidden\n__EWOOOC_HTTP_STATUS__:403",
|
|
(
|
|
"momo_app_info 1\nmomo_app_health 1\nmomo_database_up 1"
|
|
"\n__EWOOOC_HTTP_STATUS__:200"
|
|
),
|
|
])
|
|
|
|
monkeypatch.setattr(
|
|
service,
|
|
"_run",
|
|
lambda _command: type("Completed", (), {"stdout": next(outputs)})(),
|
|
)
|
|
monkeypatch.setattr(service.time, "sleep", lambda _seconds: None)
|
|
|
|
assert service._wait_metrics_canary(["curl"], 10) == ("200", True, 2)
|
|
|
|
|
|
def test_target_health_rejects_stale_scrape_url():
|
|
from services.metrics_edge_controlled_apply_service import _target_health_from_payload
|
|
|
|
payload = {
|
|
"data": {
|
|
"activeTargets": [
|
|
{
|
|
"labels": {"job": "ewoooc-app"},
|
|
"scrapeUrl": "http://172.20.0.1:9191/metrics",
|
|
"health": "up",
|
|
},
|
|
{
|
|
"labels": {"job": "ewoooc-app"},
|
|
"scrapeUrl": "http://172.20.0.1:19191/metrics",
|
|
"health": "down",
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
health = _target_health_from_payload(
|
|
payload,
|
|
"http://172.20.0.1:19191/metrics",
|
|
)
|
|
|
|
assert health == "down"
|
|
|
|
|
|
def test_prometheus_identity_verifier_requires_same_image_and_data_volume():
|
|
from services.metrics_edge_controlled_apply_service import (
|
|
_prometheus_identity_preserved,
|
|
)
|
|
|
|
before = {"image_id": "image-a", "data_volume": "volume-a"}
|
|
|
|
assert _prometheus_identity_preserved(before, dict(before)) is True
|
|
assert _prometheus_identity_preserved(
|
|
before,
|
|
{"image_id": "image-b", "data_volume": "volume-a"},
|
|
) is False
|
|
assert _prometheus_identity_preserved(
|
|
before,
|
|
{"image_id": "image-a", "data_volume": "volume-b"},
|
|
) is False
|
|
|
|
|
|
def test_render_rejects_incomplete_templates():
|
|
from services.metrics_edge_controlled_apply_service import (
|
|
render_nginx_config,
|
|
render_prometheus_config,
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="incomplete"):
|
|
render_nginx_config(NGINX_CONFIG, "location = /metrics {}")
|
|
with pytest.raises(ValueError, match="incomplete"):
|
|
render_prometheus_config(PROMETHEUS_CONFIG, "job_name: other")
|