refactor(orchestrator): rename in-guest core Orchestrator -> OrchestratorCore
Free the `Orchestrator` name for the incoming host-side control-plane service ABC (parallel to `Gateway`). The in-guest control-plane core (registry + broker, in orchestrator/service.py) becomes `OrchestratorCore`; server.py, __main__.py, the lazy facade, and the two service/server tests updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@ backend-neutral "consolidation core" that needs no VM packaging:
|
||||
lifecycle contract (idempotent singleton). One
|
||||
gateway shared by all bottles instead of one per
|
||||
bottle; backend impls live under `backend/*/gateway`.
|
||||
* `service` — the `Orchestrator`: owns the registry, brokers the
|
||||
* `service` — the `OrchestratorCore`: owns the registry, brokers the
|
||||
launch lifecycle (launch/teardown), manages the
|
||||
shared gateway, attributes.
|
||||
* `server` — the HTTP control-plane RPC (launch / teardown /
|
||||
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
|
||||
)
|
||||
from .docker_broker import DockerBroker, DockerBrokerError
|
||||
from ..gateway import Gateway, GatewayError
|
||||
from .service import Orchestrator
|
||||
from .service import OrchestratorCore
|
||||
from .server import OrchestratorServer, dispatch, make_server
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ _LAZY: dict[str, str] = {
|
||||
"DockerBrokerError": ".docker_broker",
|
||||
"Gateway": "..gateway",
|
||||
"GatewayError": "..gateway",
|
||||
"Orchestrator": ".service",
|
||||
"OrchestratorCore": ".service",
|
||||
"OrchestratorServer": ".server",
|
||||
"dispatch": ".server",
|
||||
"make_server": ".server",
|
||||
@@ -96,7 +96,7 @@ __all__ = [
|
||||
"DockerBrokerError",
|
||||
"Gateway",
|
||||
"GatewayError",
|
||||
"Orchestrator",
|
||||
"OrchestratorCore",
|
||||
"OrchestratorServer",
|
||||
"dispatch",
|
||||
"make_server",
|
||||
|
||||
@@ -21,7 +21,7 @@ from .broker import LaunchBroker, StubBroker
|
||||
from .server import make_server
|
||||
from .docker_broker import DockerBroker
|
||||
from .store.registry_store import RegistryStore, default_db_path
|
||||
from .service import Orchestrator
|
||||
from .service import OrchestratorCore
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
@@ -52,7 +52,7 @@ def main(argv: list[str] | None = None) -> int:
|
||||
# anything; 'docker' runs real containers (firecracker drops in later).
|
||||
secret = secrets.token_bytes(32)
|
||||
broker: LaunchBroker = DockerBroker(secret) if args.broker == "docker" else StubBroker(secret)
|
||||
orchestrator = Orchestrator(registry, broker, secret)
|
||||
orchestrator = OrchestratorCore(registry, broker, secret)
|
||||
|
||||
server = make_server(orchestrator, host=args.host, port=args.port)
|
||||
bound_host, bound_port = server.server_address[0], server.server_address[1]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Bridges the existing per-bottle `prepare` output to the consolidated
|
||||
registry: turns a prepared bottle's egress plan into the backend-neutral
|
||||
inputs `Orchestrator.launch_bottle` takes — the egress **policy** blob and
|
||||
inputs `OrchestratorCore.launch_bottle` takes — the egress **policy** blob and
|
||||
launch **metadata**.
|
||||
|
||||
The policy blob is the exact routes YAML the per-bottle egress daemon used
|
||||
@@ -26,7 +26,7 @@ from ..egress import EgressPlan, egress_render_routes
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RegistrationInputs:
|
||||
"""What `Orchestrator.launch_bottle` needs to register a bottle, derived
|
||||
"""What `OrchestratorCore.launch_bottle` needs to register a bottle, derived
|
||||
from its prepared plan. `policy` is served verbatim by the gateway's
|
||||
`/resolve`; `metadata` is opaque forward-compat state — it carries the
|
||||
human slug so the console / supervise can show a name, not just the
|
||||
|
||||
@@ -45,7 +45,7 @@ Both attribute the caller by `(source_ip, identity_token)` exactly like
|
||||
`POST /bottles` / `DELETE` drive the full launch lifecycle: they mint (or
|
||||
tear down) the bottle in the registry AND broker the backend-native launch
|
||||
via the orchestrator. Register/deregister without a launch are internal to
|
||||
`Orchestrator`, not exposed here.
|
||||
`OrchestratorCore`, not exposed here.
|
||||
|
||||
Routing/handling is the pure function `dispatch()` so it is unit-testable
|
||||
without a socket; `Handler` / `OrchestratorServer` / `make_server` are a
|
||||
@@ -66,7 +66,7 @@ from urllib.parse import urlsplit
|
||||
from ..orchestrator_auth import ROLE_CLI, ROLES, verify
|
||||
from ..paths import ORCHESTRATOR_TOKEN_ENV
|
||||
from ..supervisor.types import TOOLS
|
||||
from .service import Orchestrator
|
||||
from .service import OrchestratorCore
|
||||
|
||||
# JSON body payload type (parsed request / rendered response).
|
||||
Json = dict[str, object]
|
||||
@@ -109,7 +109,7 @@ def _parse_json_object(body: bytes) -> Json:
|
||||
|
||||
|
||||
def dispatch( # pylint: disable=too-many-return-statements,too-many-branches
|
||||
orch: Orchestrator, method: str, path: str, body: bytes, *, role: str | None = ROLE_CLI,
|
||||
orch: OrchestratorCore, method: str, path: str, body: bytes, *, role: str | None = ROLE_CLI,
|
||||
) -> tuple[int, Json]:
|
||||
"""Route one control-plane request to a (status, payload) pair. Pure —
|
||||
no I/O beyond the orchestrator — so it is fully testable without a socket.
|
||||
@@ -411,7 +411,7 @@ class OrchestratorServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
def __init__(self, address: tuple[str, int], orchestrator: Orchestrator) -> None:
|
||||
def __init__(self, address: tuple[str, int], orchestrator: OrchestratorCore) -> None:
|
||||
self.orchestrator = orchestrator
|
||||
self._signing_key = os.environ.get(ORCHESTRATOR_TOKEN_ENV, "").strip()
|
||||
if not self._signing_key:
|
||||
@@ -437,7 +437,7 @@ class OrchestratorServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||
|
||||
|
||||
def make_server(
|
||||
orchestrator: Orchestrator, host: str = "127.0.0.1", port: int = 0
|
||||
orchestrator: OrchestratorCore, host: str = "127.0.0.1", port: int = 0
|
||||
) -> OrchestratorServer:
|
||||
"""Build (but do not start) a control-plane server. `port=0` binds an
|
||||
ephemeral port — read `server.server_address` for the actual one."""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""The per-host orchestrator core (PRD 0070).
|
||||
|
||||
`Orchestrator` is the single backend-neutral object the control plane talks
|
||||
`OrchestratorCore` is the single backend-neutral object the control plane talks
|
||||
to: it owns the registry (runtime state) and brokers agent launches. It
|
||||
never branches on backend — the `LaunchBroker` abstracts the backend-native
|
||||
launch, so this same object drives docker / firecracker / apple once a real
|
||||
@@ -54,7 +54,7 @@ _RESPOND_STATUS = {
|
||||
_APPLY_TOOLS = (TOOL_EGRESS_ALLOW, TOOL_EGRESS_BLOCK)
|
||||
|
||||
|
||||
class Orchestrator:
|
||||
class OrchestratorCore:
|
||||
"""Owns the registry + brokers launches, and manages the single
|
||||
consolidated per-host gateway. Backend-neutral (broker and gateway
|
||||
abstract the backend-native pieces)."""
|
||||
@@ -390,4 +390,4 @@ class Orchestrator:
|
||||
return {"configured": False}
|
||||
|
||||
|
||||
__all__ = ["Orchestrator"]
|
||||
__all__ = ["OrchestratorCore"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""The orchestrator's supervise service.
|
||||
|
||||
`Supervisor` is the service object the `Orchestrator` owns and calls: it wraps
|
||||
`Supervisor` is the service object the `OrchestratorCore` owns and calls: it wraps
|
||||
the supervise queue + audit stores (queue proposals, record operator responses,
|
||||
write audit entries, stage the DB at launch). Binding those operations to one
|
||||
object — optionally scoped to a `db_path` — makes the orchestrator's supervise
|
||||
|
||||
@@ -43,7 +43,7 @@ STATUSES: tuple[str, ...] = (STATUS_APPROVED, STATUS_MODIFIED, STATUS_REJECTED)
|
||||
# proposal has no operator decision yet (`PENDING`) or no queued proposal with
|
||||
# that id exists for the calling bottle (`UNKNOWN`). They are never a
|
||||
# `Response.status` — only the poll wire contract (see
|
||||
# `Orchestrator.supervise_poll_response`) — so they are deliberately outside
|
||||
# `OrchestratorCore.supervise_poll_response`) — so they are deliberately outside
|
||||
# `STATUSES`.
|
||||
POLL_STATUS_PENDING = "pending"
|
||||
POLL_STATUS_UNKNOWN = "unknown"
|
||||
|
||||
@@ -23,7 +23,7 @@ from bot_bottle.orchestrator_auth import ROLE_CLI, ROLE_GATEWAY, mint
|
||||
from bot_bottle.orchestrator.broker import StubBroker
|
||||
from bot_bottle.orchestrator.server import dispatch, make_server
|
||||
from bot_bottle.orchestrator.store.registry_store import BottleRecord, RegistryStore
|
||||
from bot_bottle.orchestrator.service import Orchestrator
|
||||
from bot_bottle.orchestrator.service import OrchestratorCore
|
||||
from bot_bottle.orchestrator.store.store_manager import StoreManager
|
||||
from bot_bottle.orchestrator.supervisor import (
|
||||
Proposal,
|
||||
@@ -36,11 +36,11 @@ def _body(obj: object) -> bytes:
|
||||
return json.dumps(obj).encode()
|
||||
|
||||
|
||||
def _orchestrator(db_path: Path) -> Orchestrator:
|
||||
def _orchestrator(db_path: Path) -> OrchestratorCore:
|
||||
store = RegistryStore(db_path)
|
||||
store.migrate()
|
||||
secret = secrets.token_bytes(16)
|
||||
return Orchestrator(store, StubBroker(secret), secret)
|
||||
return OrchestratorCore(store, StubBroker(secret), secret)
|
||||
|
||||
|
||||
class TestDispatch(unittest.TestCase):
|
||||
@@ -408,7 +408,7 @@ class TestDispatchSupervise(unittest.TestCase):
|
||||
self.store.migrate()
|
||||
StoreManager(db).migrate()
|
||||
secret = secrets.token_bytes(16)
|
||||
self.orch = Orchestrator(self.store, StubBroker(secret), secret)
|
||||
self.orch = OrchestratorCore(self.store, StubBroker(secret), secret)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._env.stop()
|
||||
@@ -475,7 +475,7 @@ class TestDispatchSuperviseAgentRpc(unittest.TestCase):
|
||||
self.store.migrate()
|
||||
StoreManager(db).migrate()
|
||||
secret = secrets.token_bytes(16)
|
||||
self.orch = Orchestrator(self.store, StubBroker(secret), secret)
|
||||
self.orch = OrchestratorCore(self.store, StubBroker(secret), secret)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._env.stop()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for the Orchestrator launch lifecycle (PRD 0070)."""
|
||||
"""Unit tests for the OrchestratorCore launch lifecycle (PRD 0070)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -13,7 +13,7 @@ from unittest.mock import patch
|
||||
|
||||
from bot_bottle.orchestrator.broker import LaunchBroker, LaunchRequest, StubBroker
|
||||
from bot_bottle.orchestrator.store.registry_store import RegistryStore
|
||||
from bot_bottle.orchestrator.service import Orchestrator
|
||||
from bot_bottle.orchestrator.service import OrchestratorCore
|
||||
from bot_bottle.orchestrator.store.secret_store import new_env_var_secret
|
||||
from bot_bottle.orchestrator.store.store_manager import StoreManager
|
||||
from bot_bottle.orchestrator.supervisor import (
|
||||
@@ -42,7 +42,7 @@ class TestOrchestrator(unittest.TestCase):
|
||||
self.store = RegistryStore(Path(self._tmp.name) / "r.db")
|
||||
self.store.migrate()
|
||||
self.broker = StubBroker(self.secret)
|
||||
self.orch = Orchestrator(self.store, self.broker, self.secret)
|
||||
self.orch = OrchestratorCore(self.store, self.broker, self.secret)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
@@ -137,7 +137,7 @@ class TestOrchestrator(unittest.TestCase):
|
||||
self.assertIsNone(self.orch.resolve("10.243.0.3", "wrong-token"))
|
||||
|
||||
def test_launch_rolls_back_registry_on_broker_failure(self) -> None:
|
||||
orch = Orchestrator(self.store, _FailingBroker(self.secret), self.secret)
|
||||
orch = OrchestratorCore(self.store, _FailingBroker(self.secret), self.secret)
|
||||
with self.assertRaises(RuntimeError):
|
||||
orch.launch_bottle("10.243.0.9")
|
||||
self.assertEqual([], self.store.all()) # no orphan
|
||||
@@ -168,7 +168,7 @@ class TestOrchestratorSupervise(unittest.TestCase):
|
||||
self.store.migrate()
|
||||
StoreManager(db).migrate()
|
||||
secret = secrets.token_bytes(16)
|
||||
self.orch = Orchestrator(self.store, StubBroker(secret), secret)
|
||||
self.orch = OrchestratorCore(self.store, StubBroker(secret), secret)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._env.stop()
|
||||
@@ -360,7 +360,7 @@ class TestOrchestratorReconcile(unittest.TestCase):
|
||||
self.store = RegistryStore(self.db)
|
||||
self.store.migrate()
|
||||
self.broker = StubBroker(self.secret)
|
||||
self.orch = Orchestrator(self.store, self.broker, self.secret)
|
||||
self.orch = OrchestratorCore(self.store, self.broker, self.secret)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
Reference in New Issue
Block a user