dee0121e8d
prd-number-check / require-numbered-prds (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 7s
test / image-input-builds (pull_request) Successful in 41s
test / unit (pull_request) Successful in 51s
test / integration-docker (pull_request) Successful in 58s
test / coverage (pull_request) Successful in 41s
test / image-input-builds (push) Successful in 48s
test / unit (push) Successful in 56s
lint / lint (push) Successful in 1m2s
Update Quality Badges / update-badges (push) Successful in 1m4s
test / integration-docker (push) Failing after 2m53s
test / coverage (push) Has been skipped
On a Firecracker gateway cold boot, reconcile every live agent VM against the fresh gateway: push the new mitmproxy CA into each agent's trust store, re-provision git-gate repos/creds from the persisted upstreams snapshot, and restore egress tokens. Per-bottle failures are logged and skipped so one unreachable VM does not block the rest. New modules / changes: - backend/firecracker/reconcile.py: attach_bottled_agents_to_gateway, _push_ca, _reprovision_git_gate, _guest_ip_from_config - git_gate/provision.py: write upstreams.json after key provisioning so the bring-up reconcile can reconstruct the upstream table without the manifest - backend/firecracker/infra.py: call attach_bottled_agents_to_gateway in the cold-boot branch of ensure_running() - backend/base.py: no-op default on BottleBackend - backend/firecracker/consolidated_launch.py: remove superseded _reprovision_running_bottles / _guest_ip_from_config - orchestrator: OrchestratorCore.update_agent_secret + POST /bottles/<id>/secret + client.update_agent_secret (single-secret in-place update, the reusable primitive from the 2026-07-26 hotfix) - tests: 15 new tests in test_firecracker_reconcile.py; updated test_firecracker_infra.py cold-boot cases; stale FC reprovision tests removed from test_backend_secret_reprovision.py Closes #516.
78 lines
3.2 KiB
Python
78 lines
3.2 KiB
Python
"""Unit: MacosInfraService composes the orchestrator + gateway services.
|
|
|
|
`MacosInfraService` no longer owns the container lifecycles — it composes the
|
|
`MacosOrchestrator` (control plane) and `MacosGateway` (data plane) services,
|
|
each tested in its own module. These tests isolate the *composition*: the
|
|
networks are ensured, both images built, the orchestrator comes up first, then
|
|
the gateway is connected to it with a freshly minted token."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from bot_bottle.backend.macos_container.infra import MacosInfraService
|
|
|
|
_INFRA = "bot_bottle.backend.macos_container.infra"
|
|
|
|
|
|
class TestInfraEnsureRunning(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.svc = MacosInfraService(repo_root=Path("/r"))
|
|
self.orch = MagicMock()
|
|
self.orch.url.return_value = "http://192.168.128.2:8099"
|
|
self.orch.mint_gateway_token.return_value = "gw.jwt"
|
|
self.gw = MagicMock()
|
|
for name, mock in (("orchestrator", self.orch), ("gateway", self.gw)):
|
|
p = patch.object(self.svc, name, return_value=mock)
|
|
p.start()
|
|
self.addCleanup(p.stop)
|
|
|
|
def test_composes_networks_builds_and_brings_up_orchestrator_first(self) -> None:
|
|
with patch(f"{_INFRA}.ensure_networks") as nets:
|
|
url = self.svc.ensure_running()
|
|
nets.assert_called_once()
|
|
self.orch.ensure_built.assert_called_once()
|
|
self.gw.ensure_built.assert_called_once()
|
|
self.orch.ensure_running.assert_called_once()
|
|
# Returns the host control-plane URL (the InfraService contract).
|
|
self.assertEqual("http://192.168.128.2:8099", url)
|
|
|
|
def test_connects_gateway_with_orchestrator_url_and_minted_token(self) -> None:
|
|
with patch(f"{_INFRA}.ensure_networks"):
|
|
self.svc.ensure_running()
|
|
self.gw.connect_to_orchestrator.assert_called_once_with(
|
|
"http://192.168.128.2:8099", "gw.jwt")
|
|
self.orch.mint_gateway_token.assert_called_once()
|
|
|
|
def test_is_healthy_delegates_to_the_orchestrator(self) -> None:
|
|
self.orch.is_healthy.return_value = True
|
|
self.assertTrue(self.svc.is_healthy())
|
|
self.orch.is_healthy.assert_called_once()
|
|
|
|
|
|
class TestCaCertPem(unittest.TestCase):
|
|
def test_delegates_to_the_gateway_service(self) -> None:
|
|
svc = MacosInfraService(repo_root=Path("/r"))
|
|
gw = MagicMock()
|
|
gw.ca_cert_pem.return_value = "-----BEGIN CERTIFICATE-----\n"
|
|
with patch.object(svc, "gateway", return_value=gw):
|
|
pem = svc.ca_cert_pem(timeout=5)
|
|
self.assertTrue(pem.startswith("-----BEGIN CERTIFICATE-----"))
|
|
gw.ca_cert_pem.assert_called_once_with(timeout=5)
|
|
|
|
|
|
class TestStop(unittest.TestCase):
|
|
def test_removes_both_containers(self) -> None:
|
|
svc = MacosInfraService(repo_root=Path("/r"))
|
|
with patch(f"{_INFRA}.container_mod") as mod:
|
|
svc.stop()
|
|
removed = {c.args[0] for c in mod.force_remove_container.call_args_list}
|
|
self.assertIn("bot-bottle-mac-infra", removed) # gateway
|
|
self.assertIn("bot-bottle-mac-orchestrator", removed) # orchestrator
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|