refactor(orchestrator): rename Sidecar -> Gateway for the consolidated data plane
Retire "sidecar" for the consolidated per-host path (PRD 0070 naming decision): the orchestrator is the umbrella/control plane, and the egress/git/supervise data-plane unit it runs is the "gateway". - git mv sidecar.py -> gateway.py and the two integration + one unit test files; DockerSidecar->DockerGateway, Sidecar->Gateway, SidecarError->GatewayError, SIDECAR_*->GATEWAY_*, ensure_sidecar-> ensure_gateway, sidecar_status->gateway_status, container name bot-bottle-orch-sidecar->bot-bottle-orch-gateway. - Prose rename across broker/registry/egress/policy_resolver + PRD 0070. - Preserved: the image name bot-bottle-sidecars, the BOT_BOTTLE_SIDECAR_IMAGE env var, Dockerfile.sidecars, and PRD 0069's own stage-name cross-references (that doc still uses "sidecar"). No behavior change. Full unit suite green (1679 tests; the 13 test_sidecar_init /bin/sleep errors are pre-existing NixOS-local noise). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
+6
-6
@@ -1,7 +1,7 @@
|
||||
"""Integration: the consolidated Docker sidecar is an idempotent singleton.
|
||||
"""Integration: the consolidated Docker gateway is an idempotent singleton.
|
||||
|
||||
Gated on a reachable Docker daemon. Uses a unique name so it never
|
||||
collides with a real per-host sidecar or a leftover from another run.
|
||||
collides with a real per-host gateway or a leftover from another run.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -10,17 +10,17 @@ import secrets
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from bot_bottle.orchestrator.sidecar import DockerSidecar
|
||||
from bot_bottle.orchestrator.gateway import DockerGateway
|
||||
from tests._docker import skip_unless_docker
|
||||
|
||||
IMAGE = "busybox"
|
||||
|
||||
|
||||
@skip_unless_docker()
|
||||
class TestDockerSidecarIntegration(unittest.TestCase):
|
||||
class TestDockerGatewayIntegration(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.name = "bot-bottle-orch-sidecar-itest-" + secrets.token_hex(4)
|
||||
self.sc = DockerSidecar(IMAGE, name=self.name)
|
||||
self.name = "bot-bottle-orch-gateway-itest-" + secrets.token_hex(4)
|
||||
self.sc = DockerGateway(IMAGE, name=self.name)
|
||||
self.addCleanup(self.sc.stop)
|
||||
|
||||
def _count(self) -> int:
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
"""Integration: DockerSidecar.image_exists reflects real docker state.
|
||||
"""Integration: DockerGateway.image_exists reflects real docker state.
|
||||
|
||||
Gated on a reachable Docker daemon. Deliberately does NOT build the full
|
||||
sidecar bundle (a heavy, slow image build) — it exercises the `image_exists`
|
||||
@@ -11,14 +11,14 @@ from __future__ import annotations
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from bot_bottle.orchestrator.sidecar import DockerSidecar
|
||||
from bot_bottle.orchestrator.gateway import DockerGateway
|
||||
from tests._docker import skip_unless_docker
|
||||
|
||||
IMAGE = "busybox"
|
||||
|
||||
|
||||
@skip_unless_docker()
|
||||
class TestDockerSidecarImageExists(unittest.TestCase):
|
||||
class TestDockerGatewayImageExists(unittest.TestCase):
|
||||
def test_image_exists_true_for_present_false_for_absent(self) -> None:
|
||||
# Ensure the tiny image is present (build_if_missing is disabled here
|
||||
# so this never triggers a Dockerfile.sidecars build).
|
||||
@@ -26,9 +26,9 @@ class TestDockerSidecarImageExists(unittest.TestCase):
|
||||
["docker", "pull", IMAGE],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False,
|
||||
)
|
||||
self.assertTrue(DockerSidecar(IMAGE, dockerfile=None).image_exists())
|
||||
self.assertTrue(DockerGateway(IMAGE, dockerfile=None).image_exists())
|
||||
self.assertFalse(
|
||||
DockerSidecar("bot-bottle-nonexistent:doesnotexist", dockerfile=None).image_exists()
|
||||
DockerGateway("bot-bottle-nonexistent:doesnotexist", dockerfile=None).image_exists()
|
||||
)
|
||||
|
||||
|
||||
@@ -118,8 +118,8 @@ class TestDispatch(unittest.TestCase):
|
||||
status, _ = dispatch(self.orch, "GET", "/health/", b"")
|
||||
self.assertEqual(200, status)
|
||||
|
||||
def test_sidecar_status_unconfigured(self) -> None:
|
||||
status, payload = dispatch(self.orch, "GET", "/sidecar", b"")
|
||||
def test_gateway_status_unconfigured(self) -> None:
|
||||
status, payload = dispatch(self.orch, "GET", "/gateway", b"")
|
||||
self.assertEqual(200, status)
|
||||
self.assertEqual(False, payload["configured"])
|
||||
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
"""Unit tests for the consolidated Docker sidecar (PRD 0070). Docker mocked."""
|
||||
"""Unit tests for the consolidated Docker gateway (PRD 0070). Docker mocked."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bot_bottle.orchestrator.sidecar import (
|
||||
SIDECAR_NAME,
|
||||
DockerSidecar,
|
||||
SidecarError,
|
||||
from bot_bottle.orchestrator.gateway import (
|
||||
GATEWAY_NAME,
|
||||
DockerGateway,
|
||||
GatewayError,
|
||||
)
|
||||
|
||||
_RUN_DOCKER = "bot_bottle.orchestrator.sidecar.run_docker"
|
||||
_RUN_DOCKER = "bot_bottle.orchestrator.gateway.run_docker"
|
||||
|
||||
|
||||
def _proc(returncode: int = 0, stdout: str = "", stderr: str = "") -> Mock:
|
||||
return Mock(returncode=returncode, stdout=stdout, stderr=stderr)
|
||||
|
||||
|
||||
class TestDockerSidecar(unittest.TestCase):
|
||||
class TestDockerGateway(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.sc = DockerSidecar("bot-bottle-sidecars:latest")
|
||||
self.sc = DockerGateway("bot-bottle-sidecars:latest")
|
||||
|
||||
def test_default_name(self) -> None:
|
||||
self.assertEqual(SIDECAR_NAME, self.sc.name)
|
||||
self.assertEqual(GATEWAY_NAME, self.sc.name)
|
||||
|
||||
def test_is_running_reads_docker_ps(self) -> None:
|
||||
with patch(_RUN_DOCKER, return_value=_proc(stdout=self.sc.name + "\n")):
|
||||
@@ -61,7 +61,7 @@ class TestDockerSidecar(unittest.TestCase):
|
||||
return _proc()
|
||||
|
||||
with patch(_RUN_DOCKER, side_effect=fake):
|
||||
with self.assertRaises(SidecarError):
|
||||
with self.assertRaises(GatewayError):
|
||||
self.sc.ensure_running()
|
||||
|
||||
def test_stop_is_idempotent_on_missing(self) -> None:
|
||||
@@ -71,13 +71,13 @@ class TestDockerSidecar(unittest.TestCase):
|
||||
|
||||
def test_stop_raises_on_other_failure(self) -> None:
|
||||
with patch(_RUN_DOCKER, return_value=_proc(returncode=1, stderr="daemon down")):
|
||||
with self.assertRaises(SidecarError):
|
||||
with self.assertRaises(GatewayError):
|
||||
self.sc.stop()
|
||||
|
||||
|
||||
class TestDockerSidecarBuild(unittest.TestCase):
|
||||
class TestDockerGatewayBuild(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.sc = DockerSidecar() # defaults to the real bundle image + dockerfile
|
||||
self.sc = DockerGateway() # defaults to the real bundle image + dockerfile
|
||||
|
||||
def test_image_exists_reads_docker_inspect(self) -> None:
|
||||
with patch(_RUN_DOCKER, return_value=_proc(returncode=0)):
|
||||
@@ -109,7 +109,7 @@ class TestDockerSidecarBuild(unittest.TestCase):
|
||||
self.assertTrue(any(a.endswith("Dockerfile.sidecars") for a in builds[0]))
|
||||
|
||||
def test_ensure_built_noop_when_no_dockerfile(self) -> None:
|
||||
sc = DockerSidecar("busybox", dockerfile=None)
|
||||
sc = DockerGateway("busybox", dockerfile=None)
|
||||
with patch(_RUN_DOCKER) as m:
|
||||
sc.ensure_built()
|
||||
m.assert_not_called()
|
||||
@@ -121,7 +121,7 @@ class TestDockerSidecarBuild(unittest.TestCase):
|
||||
return _proc(returncode=1, stderr="build boom")
|
||||
|
||||
with patch(_RUN_DOCKER, side_effect=fake):
|
||||
with self.assertRaises(SidecarError):
|
||||
with self.assertRaises(GatewayError):
|
||||
self.sc.ensure_built()
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
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.sidecar import Sidecar
|
||||
from bot_bottle.orchestrator.gateway import Gateway
|
||||
|
||||
|
||||
class _FailingBroker(LaunchBroker):
|
||||
@@ -24,11 +24,11 @@ class _FailingBroker(LaunchBroker):
|
||||
pass
|
||||
|
||||
|
||||
class _FakeSidecar(Sidecar):
|
||||
"""In-memory sidecar for wiring tests."""
|
||||
class _FakeGateway(Gateway):
|
||||
"""In-memory gateway for wiring tests."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.name = "fake-sidecar"
|
||||
self.name = "fake-gateway"
|
||||
self.ensured = 0
|
||||
self.built = 0
|
||||
self._running = False
|
||||
@@ -120,23 +120,23 @@ class TestOrchestrator(unittest.TestCase):
|
||||
orch.launch_bottle("10.243.0.9")
|
||||
self.assertEqual([], self.store.all()) # no orphan
|
||||
|
||||
def test_sidecar_unconfigured_by_default(self) -> None:
|
||||
self.assertEqual({"configured": False}, self.orch.sidecar_status())
|
||||
self.orch.ensure_sidecar() # no-op, must not raise
|
||||
def test_gateway_unconfigured_by_default(self) -> None:
|
||||
self.assertEqual({"configured": False}, self.orch.gateway_status())
|
||||
self.orch.ensure_gateway() # no-op, must not raise
|
||||
|
||||
def test_sidecar_wired_and_ensured(self) -> None:
|
||||
sc = _FakeSidecar()
|
||||
orch = Orchestrator(self.store, self.broker, self.secret, sidecar=sc)
|
||||
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-sidecar", "running": False},
|
||||
orch.sidecar_status(),
|
||||
{"configured": True, "name": "fake-gateway", "running": False},
|
||||
orch.gateway_status(),
|
||||
)
|
||||
orch.ensure_sidecar()
|
||||
self.assertEqual(1, sc.built) # ensure_sidecar builds first,
|
||||
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-sidecar", "running": True},
|
||||
orch.sidecar_status(),
|
||||
{"configured": True, "name": "fake-gateway", "running": True},
|
||||
orch.gateway_status(),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for the sidecar-side PolicyResolver (PRD 0070). HTTP mocked."""
|
||||
"""Unit tests for the gateway-side PolicyResolver (PRD 0070). HTTP mocked."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user