feat(orchestrator): slice 2 — launch lifecycle + signed launch-broker (#352)
Second slice of PRD 0070, still a backend-neutral dev-harness:
* orchestrator/broker.py — the launch-broker contract. A LaunchRequest is
structured (static ids/flags only — bottle id, pool slot, a
content-addressed image_ref; never a path/argv) and signed as a compact
HS256 JWT so the broker verifies PROVENANCE before acting: a compromised
co-located component can't forge a launch without the shared secret.
verify_request is fail-closed (bad sig / malformed / off-schema -> raise).
Stdlib only (no runtime deps). Ships a StubBroker that records verified
requests for the harness/tests.
* orchestrator/service.py — the Orchestrator: owns the registry and brokers
the lifecycle. launch_bottle mints the bottle + sends a signed launch,
rolling the registry entry back if the launch fails (no orphans);
teardown_bottle brokers teardown then deregisters; attribute delegates.
* control_plane.py — POST /bottles now launches, DELETE tears down (both go
through the Orchestrator + broker). dispatch/server take an Orchestrator.
* __main__.py wires an ephemeral secret + StubBroker for the harness.
Tests: broker sign/verify round-trip, tamper/wrong-secret/malformed/off-schema
rejection, StubBroker fail-closed; Orchestrator launch->registry->attribute,
teardown, rollback-on-broker-failure; control-plane updated for launch/teardown.
Full suite green (only the pre-existing /bin/sleep errors); harness does
launch -> attribute -> teardown over HTTP.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -12,11 +12,14 @@ container packaging. Wrapping this exact service in a backend-native unit
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
|
||||
from .. import log
|
||||
from .broker import StubBroker
|
||||
from .control_plane import make_server
|
||||
from .registry import RegistryStore, default_db_path
|
||||
from .service import Orchestrator
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
@@ -33,7 +36,13 @@ def main(argv: list[str] | None = None) -> int:
|
||||
registry = RegistryStore(args.db)
|
||||
registry.migrate()
|
||||
|
||||
server = make_server(registry, host=args.host, port=args.port)
|
||||
# Dev-harness wiring: an ephemeral signing secret + a stub broker that
|
||||
# records launches instead of starting anything. A real backend broker
|
||||
# (docker, then firecracker) drops in here later.
|
||||
secret = secrets.token_bytes(32)
|
||||
orchestrator = Orchestrator(registry, StubBroker(secret), secret)
|
||||
|
||||
server = make_server(orchestrator, host=args.host, port=args.port)
|
||||
bound_host, bound_port = server.server_address[0], server.server_address[1]
|
||||
log.info(
|
||||
"orchestrator control plane listening",
|
||||
|
||||
Reference in New Issue
Block a user