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
|
lifecycle contract (idempotent singleton). One
|
||||||
gateway shared by all bottles instead of one per
|
gateway shared by all bottles instead of one per
|
||||||
bottle; backend impls live under `backend/*/gateway`.
|
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
|
launch lifecycle (launch/teardown), manages the
|
||||||
shared gateway, attributes.
|
shared gateway, attributes.
|
||||||
* `server` — the HTTP control-plane RPC (launch / teardown /
|
* `server` — the HTTP control-plane RPC (launch / teardown /
|
||||||
@@ -42,7 +42,7 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
from .docker_broker import DockerBroker, DockerBrokerError
|
from .docker_broker import DockerBroker, DockerBrokerError
|
||||||
from ..gateway import Gateway, GatewayError
|
from ..gateway import Gateway, GatewayError
|
||||||
from .service import Orchestrator
|
from .service import OrchestratorCore
|
||||||
from .server import OrchestratorServer, dispatch, make_server
|
from .server import OrchestratorServer, dispatch, make_server
|
||||||
|
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ _LAZY: dict[str, str] = {
|
|||||||
"DockerBrokerError": ".docker_broker",
|
"DockerBrokerError": ".docker_broker",
|
||||||
"Gateway": "..gateway",
|
"Gateway": "..gateway",
|
||||||
"GatewayError": "..gateway",
|
"GatewayError": "..gateway",
|
||||||
"Orchestrator": ".service",
|
"OrchestratorCore": ".service",
|
||||||
"OrchestratorServer": ".server",
|
"OrchestratorServer": ".server",
|
||||||
"dispatch": ".server",
|
"dispatch": ".server",
|
||||||
"make_server": ".server",
|
"make_server": ".server",
|
||||||
@@ -96,7 +96,7 @@ __all__ = [
|
|||||||
"DockerBrokerError",
|
"DockerBrokerError",
|
||||||
"Gateway",
|
"Gateway",
|
||||||
"GatewayError",
|
"GatewayError",
|
||||||
"Orchestrator",
|
"OrchestratorCore",
|
||||||
"OrchestratorServer",
|
"OrchestratorServer",
|
||||||
"dispatch",
|
"dispatch",
|
||||||
"make_server",
|
"make_server",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from .broker import LaunchBroker, StubBroker
|
|||||||
from .server import make_server
|
from .server import make_server
|
||||||
from .docker_broker import DockerBroker
|
from .docker_broker import DockerBroker
|
||||||
from .store.registry_store import RegistryStore, default_db_path
|
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:
|
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).
|
# anything; 'docker' runs real containers (firecracker drops in later).
|
||||||
secret = secrets.token_bytes(32)
|
secret = secrets.token_bytes(32)
|
||||||
broker: LaunchBroker = DockerBroker(secret) if args.broker == "docker" else StubBroker(secret)
|
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)
|
server = make_server(orchestrator, host=args.host, port=args.port)
|
||||||
bound_host, bound_port = server.server_address[0], server.server_address[1]
|
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
|
Bridges the existing per-bottle `prepare` output to the consolidated
|
||||||
registry: turns a prepared bottle's egress plan into the backend-neutral
|
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**.
|
launch **metadata**.
|
||||||
|
|
||||||
The policy blob is the exact routes YAML the per-bottle egress daemon used
|
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)
|
@dataclass(frozen=True)
|
||||||
class RegistrationInputs:
|
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
|
from its prepared plan. `policy` is served verbatim by the gateway's
|
||||||
`/resolve`; `metadata` is opaque forward-compat state — it carries the
|
`/resolve`; `metadata` is opaque forward-compat state — it carries the
|
||||||
human slug so the console / supervise can show a name, not just 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
|
`POST /bottles` / `DELETE` drive the full launch lifecycle: they mint (or
|
||||||
tear down) the bottle in the registry AND broker the backend-native launch
|
tear down) the bottle in the registry AND broker the backend-native launch
|
||||||
via the orchestrator. Register/deregister without a launch are internal to
|
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
|
Routing/handling is the pure function `dispatch()` so it is unit-testable
|
||||||
without a socket; `Handler` / `OrchestratorServer` / `make_server` are a
|
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 ..orchestrator_auth import ROLE_CLI, ROLES, verify
|
||||||
from ..paths import ORCHESTRATOR_TOKEN_ENV
|
from ..paths import ORCHESTRATOR_TOKEN_ENV
|
||||||
from ..supervisor.types import TOOLS
|
from ..supervisor.types import TOOLS
|
||||||
from .service import Orchestrator
|
from .service import OrchestratorCore
|
||||||
|
|
||||||
# JSON body payload type (parsed request / rendered response).
|
# JSON body payload type (parsed request / rendered response).
|
||||||
Json = dict[str, object]
|
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
|
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]:
|
) -> tuple[int, Json]:
|
||||||
"""Route one control-plane request to a (status, payload) pair. Pure —
|
"""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.
|
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
|
daemon_threads = True
|
||||||
allow_reuse_address = 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.orchestrator = orchestrator
|
||||||
self._signing_key = os.environ.get(ORCHESTRATOR_TOKEN_ENV, "").strip()
|
self._signing_key = os.environ.get(ORCHESTRATOR_TOKEN_ENV, "").strip()
|
||||||
if not self._signing_key:
|
if not self._signing_key:
|
||||||
@@ -437,7 +437,7 @@ class OrchestratorServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
|||||||
|
|
||||||
|
|
||||||
def make_server(
|
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:
|
) -> OrchestratorServer:
|
||||||
"""Build (but do not start) a control-plane server. `port=0` binds an
|
"""Build (but do not start) a control-plane server. `port=0` binds an
|
||||||
ephemeral port — read `server.server_address` for the actual one."""
|
ephemeral port — read `server.server_address` for the actual one."""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""The per-host orchestrator core (PRD 0070).
|
"""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
|
to: it owns the registry (runtime state) and brokers agent launches. It
|
||||||
never branches on backend — the `LaunchBroker` abstracts the backend-native
|
never branches on backend — the `LaunchBroker` abstracts the backend-native
|
||||||
launch, so this same object drives docker / firecracker / apple once a real
|
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)
|
_APPLY_TOOLS = (TOOL_EGRESS_ALLOW, TOOL_EGRESS_BLOCK)
|
||||||
|
|
||||||
|
|
||||||
class Orchestrator:
|
class OrchestratorCore:
|
||||||
"""Owns the registry + brokers launches, and manages the single
|
"""Owns the registry + brokers launches, and manages the single
|
||||||
consolidated per-host gateway. Backend-neutral (broker and gateway
|
consolidated per-host gateway. Backend-neutral (broker and gateway
|
||||||
abstract the backend-native pieces)."""
|
abstract the backend-native pieces)."""
|
||||||
@@ -390,4 +390,4 @@ class Orchestrator:
|
|||||||
return {"configured": False}
|
return {"configured": False}
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["Orchestrator"]
|
__all__ = ["OrchestratorCore"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""The orchestrator's supervise service.
|
"""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,
|
the supervise queue + audit stores (queue proposals, record operator responses,
|
||||||
write audit entries, stage the DB at launch). Binding those operations to one
|
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
|
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
|
# proposal has no operator decision yet (`PENDING`) or no queued proposal with
|
||||||
# that id exists for the calling bottle (`UNKNOWN`). They are never a
|
# that id exists for the calling bottle (`UNKNOWN`). They are never a
|
||||||
# `Response.status` — only the poll wire contract (see
|
# `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`.
|
# `STATUSES`.
|
||||||
POLL_STATUS_PENDING = "pending"
|
POLL_STATUS_PENDING = "pending"
|
||||||
POLL_STATUS_UNKNOWN = "unknown"
|
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.broker import StubBroker
|
||||||
from bot_bottle.orchestrator.server import dispatch, make_server
|
from bot_bottle.orchestrator.server import dispatch, make_server
|
||||||
from bot_bottle.orchestrator.store.registry_store import BottleRecord, RegistryStore
|
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.store.store_manager import StoreManager
|
||||||
from bot_bottle.orchestrator.supervisor import (
|
from bot_bottle.orchestrator.supervisor import (
|
||||||
Proposal,
|
Proposal,
|
||||||
@@ -36,11 +36,11 @@ def _body(obj: object) -> bytes:
|
|||||||
return json.dumps(obj).encode()
|
return json.dumps(obj).encode()
|
||||||
|
|
||||||
|
|
||||||
def _orchestrator(db_path: Path) -> Orchestrator:
|
def _orchestrator(db_path: Path) -> OrchestratorCore:
|
||||||
store = RegistryStore(db_path)
|
store = RegistryStore(db_path)
|
||||||
store.migrate()
|
store.migrate()
|
||||||
secret = secrets.token_bytes(16)
|
secret = secrets.token_bytes(16)
|
||||||
return Orchestrator(store, StubBroker(secret), secret)
|
return OrchestratorCore(store, StubBroker(secret), secret)
|
||||||
|
|
||||||
|
|
||||||
class TestDispatch(unittest.TestCase):
|
class TestDispatch(unittest.TestCase):
|
||||||
@@ -408,7 +408,7 @@ class TestDispatchSupervise(unittest.TestCase):
|
|||||||
self.store.migrate()
|
self.store.migrate()
|
||||||
StoreManager(db).migrate()
|
StoreManager(db).migrate()
|
||||||
secret = secrets.token_bytes(16)
|
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:
|
def tearDown(self) -> None:
|
||||||
self._env.stop()
|
self._env.stop()
|
||||||
@@ -475,7 +475,7 @@ class TestDispatchSuperviseAgentRpc(unittest.TestCase):
|
|||||||
self.store.migrate()
|
self.store.migrate()
|
||||||
StoreManager(db).migrate()
|
StoreManager(db).migrate()
|
||||||
secret = secrets.token_bytes(16)
|
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:
|
def tearDown(self) -> None:
|
||||||
self._env.stop()
|
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
|
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.broker import LaunchBroker, LaunchRequest, StubBroker
|
||||||
from bot_bottle.orchestrator.store.registry_store import RegistryStore
|
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.secret_store import new_env_var_secret
|
||||||
from bot_bottle.orchestrator.store.store_manager import StoreManager
|
from bot_bottle.orchestrator.store.store_manager import StoreManager
|
||||||
from bot_bottle.orchestrator.supervisor import (
|
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 = RegistryStore(Path(self._tmp.name) / "r.db")
|
||||||
self.store.migrate()
|
self.store.migrate()
|
||||||
self.broker = StubBroker(self.secret)
|
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:
|
def tearDown(self) -> None:
|
||||||
self._tmp.cleanup()
|
self._tmp.cleanup()
|
||||||
@@ -137,7 +137,7 @@ class TestOrchestrator(unittest.TestCase):
|
|||||||
self.assertIsNone(self.orch.resolve("10.243.0.3", "wrong-token"))
|
self.assertIsNone(self.orch.resolve("10.243.0.3", "wrong-token"))
|
||||||
|
|
||||||
def test_launch_rolls_back_registry_on_broker_failure(self) -> None:
|
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):
|
with self.assertRaises(RuntimeError):
|
||||||
orch.launch_bottle("10.243.0.9")
|
orch.launch_bottle("10.243.0.9")
|
||||||
self.assertEqual([], self.store.all()) # no orphan
|
self.assertEqual([], self.store.all()) # no orphan
|
||||||
@@ -168,7 +168,7 @@ class TestOrchestratorSupervise(unittest.TestCase):
|
|||||||
self.store.migrate()
|
self.store.migrate()
|
||||||
StoreManager(db).migrate()
|
StoreManager(db).migrate()
|
||||||
secret = secrets.token_bytes(16)
|
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:
|
def tearDown(self) -> None:
|
||||||
self._env.stop()
|
self._env.stop()
|
||||||
@@ -360,7 +360,7 @@ class TestOrchestratorReconcile(unittest.TestCase):
|
|||||||
self.store = RegistryStore(self.db)
|
self.store = RegistryStore(self.db)
|
||||||
self.store.migrate()
|
self.store.migrate()
|
||||||
self.broker = StubBroker(self.secret)
|
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:
|
def tearDown(self) -> None:
|
||||||
self._tmp.cleanup()
|
self._tmp.cleanup()
|
||||||
|
|||||||
Reference in New Issue
Block a user