feat(system): manage compute contour network profile
This commit is contained in:
@@ -41,6 +41,7 @@ def test_contour_store_migrates_worker_006_as_first_configuration(
|
||||
assert contours[0].telemetry_mode == "agent-mqtt"
|
||||
assert contours[0].telemetry_poll_interval_seconds == 3
|
||||
assert contours[0].mqtt_publish_interval_seconds == 2
|
||||
assert contours[0].mqtt_bind_address == "127.0.0.1"
|
||||
assert not store.path.exists()
|
||||
|
||||
|
||||
@@ -53,6 +54,7 @@ def test_contour_store_creates_and_updates_private_catalog(tmp_path: Path) -> No
|
||||
platform="linux",
|
||||
address="192.0.2.25",
|
||||
mqtt_host="192.0.2.5",
|
||||
mqtt_bind_address="192.168.10.5",
|
||||
)
|
||||
)
|
||||
updated = store.update(
|
||||
@@ -67,6 +69,7 @@ def test_contour_store_creates_and_updates_private_catalog(tmp_path: Path) -> No
|
||||
ssh_port=22,
|
||||
mqtt_host="192.0.2.5",
|
||||
mqtt_port=1883,
|
||||
mqtt_bind_address="192.168.10.5",
|
||||
telemetry_poll_interval_seconds=1,
|
||||
mqtt_publish_interval_seconds=4,
|
||||
),
|
||||
@@ -91,6 +94,7 @@ def test_contour_store_creates_and_updates_private_catalog(tmp_path: Path) -> No
|
||||
ssh_port=22,
|
||||
mqtt_host="127.0.0.1",
|
||||
mqtt_port=1883,
|
||||
mqtt_bind_address="127.0.0.1",
|
||||
telemetry_poll_interval_seconds=3,
|
||||
mqtt_publish_interval_seconds=2,
|
||||
),
|
||||
@@ -119,6 +123,12 @@ def test_contour_router_exposes_catalog_and_safe_install_contract(
|
||||
assert "password" not in document["agent"]["environment"]
|
||||
assert document["agent"]["environment"]["MISSIONCORE_TELEMETRY_INTERVAL"] == "2s"
|
||||
assert document["ready"] is False
|
||||
network = _endpoint(
|
||||
router,
|
||||
"/api/v1/system/contours/{contour_id}/network",
|
||||
"GET",
|
||||
)
|
||||
assert callable(network)
|
||||
|
||||
|
||||
def test_contour_router_returns_404_for_unknown_contour(tmp_path: Path) -> None:
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import stat
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.web import compute_contour_network as network
|
||||
from k1link.web.compute_contour_network import (
|
||||
ComputeContourNetworkTarget,
|
||||
NetworkOperationError,
|
||||
)
|
||||
|
||||
|
||||
def _target(**updates: object) -> ComputeContourNetworkTarget:
|
||||
values: dict[str, object] = {
|
||||
"contour_id": "worker-006",
|
||||
"expected_node_id": "DESKTOP-OPJ8J04",
|
||||
"platform": "windows",
|
||||
"worker_address": "",
|
||||
"ssh_port": 22,
|
||||
"mqtt_host": "mission-core.local",
|
||||
"mqtt_port": 1883,
|
||||
"mqtt_bind_address": "192.168.68.56",
|
||||
"mqtt_publish_interval_seconds": 2,
|
||||
}
|
||||
values.update(updates)
|
||||
return ComputeContourNetworkTarget(**values) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_broker_apply_preserves_private_environment_and_changes_only_bind(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
root = tmp_path / "telemetry-plane"
|
||||
root.mkdir()
|
||||
environment = root / ".env"
|
||||
environment.write_text(
|
||||
"MISSIONCORE_MQTT_BIND_ADDRESS=192.168.1.5\n"
|
||||
"MISSIONCORE_MQTT_PASSWORD=do-not-copy\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
environment.chmod(0o600)
|
||||
(root / "compose.yaml").write_text("services: {}\n", encoding="utf-8")
|
||||
compose_calls: list[Path] = []
|
||||
monkeypatch.setattr(network, "_address_belongs_to_host", lambda _value: True)
|
||||
monkeypatch.setattr(network, "_tcp_reachable", lambda *_args, **_kwargs: True)
|
||||
monkeypatch.setattr(
|
||||
network,
|
||||
"_run_compose_broker",
|
||||
lambda path: compose_calls.append(path),
|
||||
)
|
||||
|
||||
result = network.apply_broker_network(_target(), root)
|
||||
|
||||
document = environment.read_text(encoding="utf-8")
|
||||
assert "MISSIONCORE_MQTT_BIND_ADDRESS=192.168.68.56" in document
|
||||
assert "MISSIONCORE_MQTT_PASSWORD=do-not-copy" in document
|
||||
assert stat.S_IMODE(environment.stat().st_mode) == 0o600
|
||||
assert compose_calls == [root]
|
||||
assert result["target"] == "broker"
|
||||
assert result["ready"] is True
|
||||
|
||||
|
||||
def test_broker_apply_rolls_back_environment_when_listener_does_not_open(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
root = tmp_path / "telemetry-plane"
|
||||
root.mkdir()
|
||||
environment = root / ".env"
|
||||
before = (
|
||||
"MISSIONCORE_MQTT_BIND_ADDRESS=192.168.1.5\n"
|
||||
"MISSIONCORE_MQTT_PASSWORD=still-private\n"
|
||||
)
|
||||
environment.write_text(before, encoding="utf-8")
|
||||
environment.chmod(0o600)
|
||||
(root / "compose.yaml").write_text("services: {}\n", encoding="utf-8")
|
||||
monkeypatch.setattr(network, "_address_belongs_to_host", lambda _value: True)
|
||||
monkeypatch.setattr(network, "_tcp_reachable", lambda *_args, **_kwargs: False)
|
||||
monkeypatch.setattr(network, "_run_compose_broker", lambda _path: None)
|
||||
|
||||
with pytest.raises(NetworkOperationError, match="не открыл"):
|
||||
network.apply_broker_network(_target(), root)
|
||||
|
||||
assert environment.read_text(encoding="utf-8") == before
|
||||
|
||||
|
||||
def test_worker_apply_uses_private_resolved_endpoint_and_confirms_service(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
captured: dict[str, Any] = {}
|
||||
monkeypatch.setattr(
|
||||
network,
|
||||
"_resolve_addresses",
|
||||
lambda _host, _port: ["192.168.68.56"],
|
||||
)
|
||||
|
||||
def run(
|
||||
target: ComputeContourNetworkTarget,
|
||||
script: str,
|
||||
*,
|
||||
timeout: float,
|
||||
) -> dict[str, Any]:
|
||||
captured.update({"target": target, "script": script, "timeout": timeout})
|
||||
return {
|
||||
"node_id": "DESKTOP-OPJ8J04",
|
||||
"service_status": "Running",
|
||||
"mqtt_host": "mission-core.local",
|
||||
"mqtt_port": 1883,
|
||||
"resolved_addresses": ["192.168.68.56"],
|
||||
"broker_reachable": True,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(network, "_run_worker_powershell", run)
|
||||
|
||||
result = network.apply_worker_network(_target())
|
||||
|
||||
assert result["target"] == "worker"
|
||||
assert result["ready"] is True
|
||||
assert "__PAYLOAD_BASE64__" not in captured["script"]
|
||||
assert "MISSIONCORE_MQTT_PASSWORD" not in captured["script"]
|
||||
assert captured["timeout"] == 35
|
||||
|
||||
|
||||
def test_worker_apply_rejects_public_mqtt_endpoint(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
network,
|
||||
"_resolve_addresses",
|
||||
lambda _host, _port: ["8.8.8.8"],
|
||||
)
|
||||
|
||||
with pytest.raises(NetworkOperationError, match="локальной сети"):
|
||||
network.apply_worker_network(_target(mqtt_host="broker.example.test"))
|
||||
Reference in New Issue
Block a user