refactor(gateway): move DockerGateway to the backend layer, drop the dead standalone-gateway path
lint / lint (push) Successful in 54s
lint / lint (push) Successful in 54s
The `DockerGateway` container-lifecycle impl now lives in `backend/docker/gateway.py` (the shape backend gateway classes will share); `orchestrator/gateway.py` keeps only the backend-neutral pieces (the `Gateway` ABC, constants, `rotate_gateway_ca`). Delete the standalone-gateway path it was the only consumer of. `--gateway` on `python -m bot_bottle.orchestrator` was invoked nowhere — the production docker flow runs the gateway data plane inside the combined `bot-bottle-infra` container via `OrchestratorService`, never this class. Removing it takes with it `Orchestrator.ensure_gateway()` and the `gateway` ctor arg; `gateway_status()` becomes a stub reporting `configured: false` so the documented `GET /gateway` control-plane route keeps its contract. Fix a latent bug the move surfaced: `launch.py` read the shared gateway CA via `docker exec bot-bottle-orch-gateway`, a container the consolidated flow never creates — so the agent CA install was reaching a nonexistent name. Read it from `bot-bottle-infra` (INFRA_NAME) instead. Repoint the affected tests to the new module; drop the unit test + fake that covered the deleted standalone wiring. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import secrets
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from bot_bottle.orchestrator.gateway import DockerGateway
|
||||
from bot_bottle.backend.docker.gateway import DockerGateway
|
||||
from tests._docker import skip_unless_docker
|
||||
|
||||
IMAGE = "busybox"
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from bot_bottle.orchestrator.gateway import DockerGateway
|
||||
from bot_bottle.backend.docker.gateway import DockerGateway
|
||||
from tests._docker import skip_unless_docker
|
||||
|
||||
IMAGE = "busybox"
|
||||
|
||||
@@ -7,10 +7,10 @@ import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bot_bottle.backend.docker.gateway import DockerGateway
|
||||
from bot_bottle.orchestrator.gateway import (
|
||||
GATEWAY_CA_CERT,
|
||||
GATEWAY_NAME,
|
||||
DockerGateway,
|
||||
GatewayError,
|
||||
rotate_gateway_ca,
|
||||
)
|
||||
@@ -20,7 +20,7 @@ from tests.unit import use_bottle_root
|
||||
|
||||
_CA_PEM = "-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----\n"
|
||||
|
||||
_RUN_DOCKER = "bot_bottle.orchestrator.gateway.run_docker"
|
||||
_RUN_DOCKER = "bot_bottle.backend.docker.gateway.run_docker"
|
||||
|
||||
|
||||
def _proc(returncode: int = 0, stdout: str = "", stderr: str = "") -> Mock:
|
||||
@@ -162,7 +162,7 @@ class TestDockerGateway(unittest.TestCase):
|
||||
# First read: CA not there yet; second read: present.
|
||||
seq = [_proc(returncode=1, stderr="No such file"), _proc(stdout=_CA_PEM)]
|
||||
with patch(_RUN_DOCKER, side_effect=seq), \
|
||||
patch("bot_bottle.orchestrator.gateway.time.sleep"):
|
||||
patch("bot_bottle.backend.docker.gateway.time.sleep"):
|
||||
self.assertEqual(_CA_PEM, self.sc.ca_cert_pem(timeout=5))
|
||||
|
||||
def test_ensure_running_reuses_existing_network(self) -> None:
|
||||
|
||||
@@ -14,7 +14,6 @@ from unittest.mock import patch
|
||||
from bot_bottle.orchestrator.broker import LaunchBroker, LaunchRequest, StubBroker
|
||||
from bot_bottle.orchestrator.registry import RegistryStore
|
||||
from bot_bottle.orchestrator.service import Orchestrator
|
||||
from bot_bottle.orchestrator.gateway import Gateway
|
||||
from bot_bottle.orchestrator.secret_store import new_env_var_secret
|
||||
from bot_bottle.store_manager import StoreManager
|
||||
from bot_bottle.supervise import (
|
||||
@@ -38,29 +37,6 @@ class _FailingBroker(LaunchBroker):
|
||||
pass
|
||||
|
||||
|
||||
class _FakeGateway(Gateway):
|
||||
"""In-memory gateway for wiring tests."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.name = "fake-gateway"
|
||||
self.ensured = 0
|
||||
self.built = 0
|
||||
self._running = False
|
||||
|
||||
def ensure_built(self) -> None:
|
||||
self.built += 1
|
||||
|
||||
def ensure_running(self) -> None:
|
||||
self.ensured += 1
|
||||
self._running = True
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._running
|
||||
|
||||
def stop(self) -> None:
|
||||
self._running = False
|
||||
|
||||
|
||||
class TestOrchestrator(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
@@ -168,24 +144,11 @@ class TestOrchestrator(unittest.TestCase):
|
||||
orch.launch_bottle("10.243.0.9")
|
||||
self.assertEqual([], self.store.all()) # no orphan
|
||||
|
||||
def test_gateway_unconfigured_by_default(self) -> None:
|
||||
def test_gateway_status_reports_unconfigured(self) -> None:
|
||||
# The orchestrator no longer owns a standalone gateway lifecycle; the
|
||||
# consolidated flow runs the gateway data plane in the per-host infra
|
||||
# container/VM. `GET /gateway` therefore always reports unconfigured.
|
||||
self.assertEqual({"configured": False}, self.orch.gateway_status())
|
||||
self.orch.ensure_gateway() # no-op, must not raise
|
||||
|
||||
def test_gateway_wired_and_ensured(self) -> None:
|
||||
sc = _FakeGateway()
|
||||
orch = Orchestrator(self.store, self.broker, self.secret, gateway=sc)
|
||||
self.assertEqual(
|
||||
{"configured": True, "name": "fake-gateway", "running": False},
|
||||
orch.gateway_status(),
|
||||
)
|
||||
orch.ensure_gateway()
|
||||
self.assertEqual(1, sc.built) # ensure_gateway builds first,
|
||||
self.assertEqual(1, sc.ensured) # then runs
|
||||
self.assertEqual(
|
||||
{"configured": True, "name": "fake-gateway", "running": True},
|
||||
orch.gateway_status(),
|
||||
)
|
||||
|
||||
|
||||
class TestOrchestratorSupervise(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user