refactor: unify component naming — control_plane/control_auth -> orchestrator
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / integration-docker (pull_request) Successful in 18s
test / unit (pull_request) Successful in 46s
lint / lint (push) Failing after 54s
test / integration-firecracker (pull_request) Successful in 3m21s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / integration-docker (pull_request) Successful in 18s
test / unit (pull_request) Successful in 46s
lint / lint (push) Failing after 54s
test / integration-firecracker (pull_request) Successful in 3m21s
test / coverage (pull_request) Successful in 18s
test / publish-infra (pull_request) Has been skipped
The codebase used "control plane" both as an architectural role term AND
as an identifier alias for the orchestrator component, producing
duplicate names for one thing (control_plane_url vs orchestrator_url,
CONTROL_PLANE_PORT, host_control_plane_token, …). Going forward the
concrete component is always named for what it is — Gateway or
Orchestrator — and the plane vocabulary is reserved for prose (module
descriptions, the security argument).
Renamed (identifiers + the in-repo env/wire/file string values, all
setters/getters are in this repo so the change is atomic):
ControlPlaneServer -> OrchestratorServer
control_plane_url -> orchestrator_url
probe_control_plane_url -> probe_orchestrator_url
host_control_plane_token -> host_orchestrator_token
CONTROL_PLANE_PORT -> ORCHESTRATOR_PORT
CONTROL_PLANE_TOKEN_ENV/FILE -> ORCHESTRATOR_TOKEN_ENV/FILENAME
BOT_BOTTLE_CONTROL_PLANE_TOKEN-> BOT_BOTTLE_ORCHESTRATOR_TOKEN
control-plane-token (file) -> orchestrator-token
control_auth (module) -> orchestrator_auth (stays top-level;
the gateway imports it and must not
import the orchestrator/ package)
CONTROL_AUTH_HEADER -> ORCHESTRATOR_AUTH_HEADER
x-bot-bottle-control-auth -> x-bot-bottle-orchestrator-auth
CONTROL_AUTH_JWT_ENV -> ORCHESTRATOR_AUTH_JWT_ENV
BOT_BOTTLE_CONTROL_AUTH_JWT -> BOT_BOTTLE_ORCHESTRATOR_AUTH_JWT
_control_auth_headers -> _orchestrator_auth_headers
Prose plane-terms ("control plane", "data plane") are preserved,
including the test name test_data_plane_daemons_get_jwt_not_key (it
names the security invariant). Gateway and orchestrator verified to
agree on the renamed wire header; full unit suite green (2243).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,11 +4,11 @@ import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from ...control_auth import ROLE_GATEWAY, mint
|
||||
from ...orchestrator_auth import ROLE_GATEWAY, mint
|
||||
from .util import run_docker
|
||||
from ...paths import (
|
||||
CONTROL_AUTH_JWT_ENV,
|
||||
host_control_plane_token,
|
||||
ORCHESTRATOR_AUTH_JWT_ENV,
|
||||
host_orchestrator_token,
|
||||
host_gateway_ca_dir,
|
||||
)
|
||||
from ...gateway import (
|
||||
@@ -158,8 +158,8 @@ class DockerGateway(Gateway):
|
||||
# a `cli` token, so a compromised data-plane process can't drive the
|
||||
# operator routes (issue #469 review). Bare `--env NAME` keeps the value
|
||||
# off argv / `docker inspect`; only the gateway (not the agent) is given it.
|
||||
argv += ["--env", CONTROL_AUTH_JWT_ENV]
|
||||
run_env[CONTROL_AUTH_JWT_ENV] = mint(ROLE_GATEWAY, host_control_plane_token())
|
||||
argv += ["--env", ORCHESTRATOR_AUTH_JWT_ENV]
|
||||
run_env[ORCHESTRATOR_AUTH_JWT_ENV] = mint(ROLE_GATEWAY, host_orchestrator_token())
|
||||
argv.append(self.image_ref)
|
||||
proc = run_docker(argv, env=run_env)
|
||||
if proc.returncode != 0:
|
||||
|
||||
@@ -25,13 +25,13 @@ import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
from ... import log
|
||||
from ...control_auth import ROLE_GATEWAY, mint
|
||||
from ...orchestrator_auth import ROLE_GATEWAY, mint
|
||||
from .util import run_docker
|
||||
from ...paths import (
|
||||
CONTROL_AUTH_JWT_ENV,
|
||||
CONTROL_PLANE_TOKEN_ENV,
|
||||
ORCHESTRATOR_AUTH_JWT_ENV,
|
||||
ORCHESTRATOR_TOKEN_ENV,
|
||||
bot_bottle_root,
|
||||
host_control_plane_token,
|
||||
host_orchestrator_token,
|
||||
host_gateway_ca_dir,
|
||||
)
|
||||
from ...gateway import (
|
||||
@@ -176,7 +176,7 @@ class DockerInfraService:
|
||||
so a later `ensure_running` can detect a real code change."""
|
||||
self._ensure_network()
|
||||
run_docker(["docker", "rm", "--force", self._infra_name])
|
||||
_signing_key = host_control_plane_token()
|
||||
_signing_key = host_orchestrator_token()
|
||||
proc = run_docker([
|
||||
"docker", "run", "--detach",
|
||||
"--name", self._infra_name,
|
||||
@@ -204,8 +204,8 @@ class DockerInfraService:
|
||||
# pre-minted `gateway` JWT (data-plane daemons: present it). gateway_init
|
||||
# scopes each to its process, so a compromised data-plane daemon never
|
||||
# sees the key and can't mint a `cli` token (issue #469 review).
|
||||
"--env", CONTROL_PLANE_TOKEN_ENV,
|
||||
"--env", CONTROL_AUTH_JWT_ENV,
|
||||
"--env", ORCHESTRATOR_TOKEN_ENV,
|
||||
"--env", ORCHESTRATOR_AUTH_JWT_ENV,
|
||||
# Gateway daemons reach the orchestrator over loopback at its
|
||||
# fixed internal port (DEFAULT_PORT), independent of self.port.
|
||||
"--env", f"BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{DEFAULT_PORT}",
|
||||
@@ -214,8 +214,8 @@ class DockerInfraService:
|
||||
self.image,
|
||||
], env={
|
||||
**os.environ,
|
||||
CONTROL_PLANE_TOKEN_ENV: _signing_key,
|
||||
CONTROL_AUTH_JWT_ENV: mint(ROLE_GATEWAY, _signing_key),
|
||||
ORCHESTRATOR_TOKEN_ENV: _signing_key,
|
||||
ORCHESTRATOR_AUTH_JWT_ENV: mint(ROLE_GATEWAY, _signing_key),
|
||||
})
|
||||
if proc.returncode != 0:
|
||||
raise OrchestratorStartError(
|
||||
|
||||
@@ -120,4 +120,4 @@ class FirecrackerBottleBackend(
|
||||
|
||||
def ensure_orchestrator(self) -> str:
|
||||
from . import infra_vm
|
||||
return infra_vm.ensure_running().control_plane_url
|
||||
return infra_vm.ensure_running().orchestrator_url
|
||||
|
||||
@@ -124,7 +124,7 @@ def launch_consolidated(
|
||||
provision its git-gate state into the gateway VM. Returns the context the
|
||||
agent-VM launch needs. Raises on failure — the caller tears down."""
|
||||
infra = infra_vm.ensure_running()
|
||||
url = infra.control_plane_url
|
||||
url = infra.orchestrator_url
|
||||
client = OrchestratorClient(url)
|
||||
_reprovision_running_bottles(client)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
from ...log import die, info
|
||||
from ...paths import CONTROL_PLANE_TOKEN_FILENAME, bot_bottle_root
|
||||
from ...paths import ORCHESTRATOR_TOKEN_FILENAME, bot_bottle_root
|
||||
from .. import util as backend_util
|
||||
from ..docker import util as docker_mod
|
||||
from ..docker.gateway_provision import GatewayProvisionError
|
||||
@@ -42,7 +42,7 @@ from . import firecracker_vm, infra_artifact, netpool, util
|
||||
# Where the infra VM keeps its control-plane signing key (generated on the
|
||||
# persistent /dev/vdb volume mounted at BOT_BOTTLE_ROOT). The host mirrors it
|
||||
# back so the CLI signs `cli` tokens the VM verifies (issue #469 review).
|
||||
_GUEST_SIGNING_KEY_PATH = "/var/lib/bot-bottle/control-plane-token"
|
||||
_GUEST_SIGNING_KEY_PATH = "/var/lib/bot-bottle/orchestrator-token"
|
||||
|
||||
# The single infra-VM image: gateway data plane + baked control-plane source
|
||||
# (Dockerfile.infra FROM the gateway image). Built from source by default;
|
||||
@@ -52,7 +52,7 @@ _GATEWAY_IMAGE = "bot-bottle-gateway:latest"
|
||||
_ORCHESTRATOR_IMAGE = "bot-bottle-orchestrator:latest"
|
||||
_REPO_ROOT = Path(__file__).resolve().parents[3]
|
||||
|
||||
CONTROL_PLANE_PORT = 8099
|
||||
ORCHESTRATOR_PORT = 8099
|
||||
# Gateway data-plane ports (agent-facing): egress proxy, supervise MCP,
|
||||
# git-http. Reached by agent VMs over VM-to-VM routing (added next).
|
||||
EGRESS_PORT = 9099
|
||||
@@ -84,8 +84,8 @@ class InfraVm:
|
||||
vm: firecracker_vm.VmHandle | None = None
|
||||
|
||||
@property
|
||||
def control_plane_url(self) -> str:
|
||||
return f"http://{self.guest_ip}:{CONTROL_PLANE_PORT}"
|
||||
def orchestrator_url(self) -> str:
|
||||
return f"http://{self.guest_ip}:{ORCHESTRATOR_PORT}"
|
||||
|
||||
def terminate(self) -> None:
|
||||
"""Stop the infra VM — via the live handle if we booted it, else the
|
||||
@@ -168,7 +168,7 @@ def ensure_running() -> InfraVm:
|
||||
flock, so two simultaneous first launches don't both boot on the same
|
||||
rootfs/PID. The healthy fast-path takes no lock."""
|
||||
slot = netpool.orch_slot()
|
||||
url = f"http://{slot.guest_ip}:{CONTROL_PLANE_PORT}"
|
||||
url = f"http://{slot.guest_ip}:{ORCHESTRATOR_PORT}"
|
||||
key = _infra_dir() / "id_ed25519"
|
||||
want = _expected_version()
|
||||
if _adoptable(key, url, want):
|
||||
@@ -192,7 +192,7 @@ def ensure_running() -> InfraVm:
|
||||
|
||||
def _with_signing_key(infra: InfraVm) -> InfraVm:
|
||||
"""Mirror the infra VM's control-plane signing key (generated on its
|
||||
persistent volume) into the host's control-plane-token file, so the host CLI
|
||||
persistent volume) into the host's orchestrator-token file, so the host CLI
|
||||
signs `cli` tokens the VM verifies (issue #469 review). Best-effort: an
|
||||
unreadable key is logged, not fatal — the VM still enforces auth, but the CLI
|
||||
may then be rejected until the key is readable. Returns `infra` for chaining."""
|
||||
@@ -205,7 +205,7 @@ def _with_signing_key(infra: InfraVm) -> InfraVm:
|
||||
if proc.returncode != 0 or not signing_key:
|
||||
info("infra signing key not yet readable; control-plane auth may fail")
|
||||
return infra
|
||||
path = bot_bottle_root() / CONTROL_PLANE_TOKEN_FILENAME
|
||||
path = bot_bottle_root() / ORCHESTRATOR_TOKEN_FILENAME
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
@@ -458,7 +458,7 @@ def wait_for_health(
|
||||
) -> None:
|
||||
"""Poll the control plane's /health until it answers 200 or the deadline
|
||||
passes. Dies (with the console tail) if the VMM exits early."""
|
||||
url = f"{infra.control_plane_url}/health"
|
||||
url = f"{infra.orchestrator_url}/health"
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if infra.vm is not None and not infra.vm.is_alive():
|
||||
@@ -467,7 +467,7 @@ def wait_for_health(
|
||||
try:
|
||||
with urllib.request.urlopen(url, timeout=1.0) as resp:
|
||||
if resp.status == 200:
|
||||
info(f"infra control plane healthy at {infra.control_plane_url}")
|
||||
info(f"infra control plane healthy at {infra.orchestrator_url}")
|
||||
return
|
||||
except (urllib.error.URLError, TimeoutError, OSError):
|
||||
pass
|
||||
@@ -525,11 +525,11 @@ cd /app
|
||||
# same VM — reaching the orchestrator over 127.0.0.1, past the nft boundary that
|
||||
# only fences off the separate agent VM — could drive the operator routes
|
||||
# (approve its own supervise proposals, rewrite policy, read injected tokens).
|
||||
CP_KEY=$(BOT_BOTTLE_ROOT=/var/lib/bot-bottle python3 -c 'from bot_bottle.paths import host_control_plane_token as t; print(t())')
|
||||
GW_JWT=$(BB_SIGNING_KEY="$CP_KEY" python3 -c 'import os; from bot_bottle.control_auth import mint, ROLE_GATEWAY; print(mint(ROLE_GATEWAY, os.environ["BB_SIGNING_KEY"]))')
|
||||
CP_KEY=$(BOT_BOTTLE_ROOT=/var/lib/bot-bottle python3 -c 'from bot_bottle.paths import host_orchestrator_token as t; print(t())')
|
||||
GW_JWT=$(BB_SIGNING_KEY="$CP_KEY" python3 -c 'import os; from bot_bottle.orchestrator_auth import mint, ROLE_GATEWAY; print(mint(ROLE_GATEWAY, os.environ["BB_SIGNING_KEY"]))')
|
||||
|
||||
BOT_BOTTLE_ROOT=/var/lib/bot-bottle BOT_BOTTLE_CONTROL_PLANE_TOKEN="$CP_KEY" python3 -m bot_bottle.orchestrator \\
|
||||
--host 0.0.0.0 --port {CONTROL_PLANE_PORT} --broker stub &
|
||||
BOT_BOTTLE_ROOT=/var/lib/bot-bottle BOT_BOTTLE_ORCHESTRATOR_TOKEN="$CP_KEY" python3 -m bot_bottle.orchestrator \\
|
||||
--host 0.0.0.0 --port {ORCHESTRATOR_PORT} --broker stub &
|
||||
|
||||
# Gateway data plane, multi-tenant: each request resolves source-IP ->
|
||||
# policy against the local control plane. The VM backend reaches git over
|
||||
@@ -540,8 +540,8 @@ BOT_BOTTLE_ROOT=/var/lib/bot-bottle BOT_BOTTLE_CONTROL_PLANE_TOKEN="$CP_KEY" pyt
|
||||
# the pre-minted `gateway` JWT; gateway_init keeps the signing key out of the
|
||||
# data-plane daemons' env.
|
||||
BOT_BOTTLE_GATEWAY_DAEMONS=egress,git-http,supervise \\
|
||||
BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{CONTROL_PLANE_PORT} \\
|
||||
BOT_BOTTLE_CONTROL_AUTH_JWT="$GW_JWT" \\
|
||||
BOT_BOTTLE_ORCHESTRATOR_URL=http://127.0.0.1:{ORCHESTRATOR_PORT} \\
|
||||
BOT_BOTTLE_ORCHESTRATOR_AUTH_JWT="$GW_JWT" \\
|
||||
python3 -m bot_bottle.gateway.bootstrap &
|
||||
|
||||
# Reap as PID 1; children are backgrounded, so `wait` blocks.
|
||||
|
||||
@@ -102,7 +102,7 @@ class MacosContainerBottleBackend(
|
||||
(`supervise`) call when no control plane is running yet. Mirrors
|
||||
firecracker's infra-VM bring-up."""
|
||||
from .infra import MacosInfraService
|
||||
return MacosInfraService().ensure_running().control_plane_url
|
||||
return MacosInfraService().ensure_running().orchestrator_url
|
||||
|
||||
def prepare_cleanup(self) -> MacosContainerBottleCleanupPlan:
|
||||
return _cleanup.prepare_cleanup()
|
||||
|
||||
@@ -90,7 +90,7 @@ def ensure_gateway(
|
||||
service = service or MacosInfraService()
|
||||
infra = service.ensure_running()
|
||||
endpoint = GatewayEndpoint(
|
||||
orchestrator_url=infra.control_plane_url,
|
||||
orchestrator_url=infra.orchestrator_url,
|
||||
gateway_ip=infra.gateway_ip,
|
||||
gateway_ca_pem=service.ca_cert_pem(),
|
||||
network=service.network,
|
||||
|
||||
@@ -48,12 +48,12 @@ from ...orchestrator.lifecycle import (
|
||||
OrchestratorStartError,
|
||||
source_hash,
|
||||
)
|
||||
from ...control_auth import ROLE_GATEWAY, mint
|
||||
from ...orchestrator_auth import ROLE_GATEWAY, mint
|
||||
from ...paths import (
|
||||
CONTROL_AUTH_JWT_ENV,
|
||||
CONTROL_PLANE_TOKEN_ENV,
|
||||
ORCHESTRATOR_AUTH_JWT_ENV,
|
||||
ORCHESTRATOR_TOKEN_ENV,
|
||||
HOST_DB_FILENAME,
|
||||
host_control_plane_token,
|
||||
host_orchestrator_token,
|
||||
host_gateway_ca_dir,
|
||||
)
|
||||
from .. import util as backend_util
|
||||
@@ -117,7 +117,7 @@ class InfraEndpoint:
|
||||
"""How to reach the running infra container. The control plane and the
|
||||
gateway are the same container, so one address serves both."""
|
||||
|
||||
control_plane_url: str # http://<infra ip>:8099 — host CLI + registration
|
||||
orchestrator_url: str # http://<infra ip>:8099 — host CLI + registration
|
||||
gateway_ip: str # same container; agents' proxy / git-http / MCP target
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ class MacosInfraService:
|
||||
return None
|
||||
url = self._resolve_url()
|
||||
if url and self.is_healthy(url):
|
||||
return InfraEndpoint(control_plane_url=url, gateway_ip=_ip_of(url))
|
||||
return InfraEndpoint(orchestrator_url=url, gateway_ip=_ip_of(url))
|
||||
return None
|
||||
|
||||
def ensure_built(self) -> None:
|
||||
@@ -247,17 +247,17 @@ class MacosInfraService:
|
||||
# run process below, so neither lands on argv or in `container
|
||||
# inspect`'s command line. The agent runs in a SEPARATE container that
|
||||
# is never given these vars, which is the whole point.
|
||||
"--env", CONTROL_PLANE_TOKEN_ENV,
|
||||
"--env", CONTROL_AUTH_JWT_ENV,
|
||||
"--env", ORCHESTRATOR_TOKEN_ENV,
|
||||
"--env", ORCHESTRATOR_AUTH_JWT_ENV,
|
||||
"--entrypoint", "sh",
|
||||
self.image,
|
||||
"-c", _init_script(self.port),
|
||||
]
|
||||
_signing_key = host_control_plane_token()
|
||||
_signing_key = host_orchestrator_token()
|
||||
run_env = {
|
||||
**os.environ,
|
||||
CONTROL_PLANE_TOKEN_ENV: _signing_key,
|
||||
CONTROL_AUTH_JWT_ENV: mint(ROLE_GATEWAY, _signing_key),
|
||||
ORCHESTRATOR_TOKEN_ENV: _signing_key,
|
||||
ORCHESTRATOR_AUTH_JWT_ENV: mint(ROLE_GATEWAY, _signing_key),
|
||||
}
|
||||
result = container_mod.run_container_argv(argv, env=run_env)
|
||||
if result.returncode != 0:
|
||||
@@ -272,7 +272,7 @@ class MacosInfraService:
|
||||
url = self._resolve_url()
|
||||
if url and self.is_healthy(url):
|
||||
log.info("infra container healthy", context={"url": url})
|
||||
return InfraEndpoint(control_plane_url=url, gateway_ip=_ip_of(url))
|
||||
return InfraEndpoint(orchestrator_url=url, gateway_ip=_ip_of(url))
|
||||
if time.monotonic() >= deadline:
|
||||
raise OrchestratorStartError(
|
||||
f"infra container did not become healthy within "
|
||||
@@ -306,7 +306,7 @@ def _ip_of(url: str) -> str:
|
||||
return url.split("://", 1)[-1].rsplit(":", 1)[0]
|
||||
|
||||
|
||||
def probe_control_plane_url(port: int = DEFAULT_PORT) -> str:
|
||||
def probe_orchestrator_url(port: int = DEFAULT_PORT) -> str:
|
||||
"""The running infra container's control-plane URL, or "" if it isn't up.
|
||||
Used by host-side control-plane discovery (`discover_orchestrator_url`);
|
||||
safe to call on any host — returns "" when the container or the `container`
|
||||
|
||||
Reference in New Issue
Block a user