The one-node bed places at the site it operates, and tolerates the CP recreating
Genesis places the anchor at the same site the test re-places it at, so that step is a no-op rather than a change that recreates the control plane. And mesh() — which runs commands inside the control-plane container — retries a transient "container not running", because the control plane is a live mesh-managed container the mesh recreates when its declaration changes (e.g. its first .internal add-host). Also passes --sdk-source/--tools-ref for the SDK build. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -67,6 +67,11 @@ export interface GenesisOptions {
|
||||
/** Where the shared library is built from — published before the base resolves it (ADR 0076). */
|
||||
sdkSource: string;
|
||||
sdkRef?: string;
|
||||
/** What of the base repo to build. Defaults to main; a feature branch under test overrides it. */
|
||||
toolsRef?: string;
|
||||
/** The site the anchor is placed at on the private network. Must match how the operator/test
|
||||
* places it afterwards, or the first re-place changes the overlay and recreates the control plane. */
|
||||
site?: string;
|
||||
/** What of the catalogue to build. A branch under test is the usual reason this is not main. */
|
||||
catalogRef?: string;
|
||||
log?: (m: string) => void;
|
||||
@@ -149,6 +154,7 @@ export async function genesis(o: GenesisOptions): Promise<GenesisResult> {
|
||||
`--node ${node}`,
|
||||
`--registry ${registry}`,
|
||||
`--tools-source ${o.toolsSource}`,
|
||||
`--tools-ref ${o.toolsRef ?? "main"}`,
|
||||
`--catalog-source ${o.catalogSource}`,
|
||||
`--catalog-ref ${o.catalogRef ?? "main"}`,
|
||||
`--sdk-source ${o.sdkSource}`,
|
||||
@@ -156,6 +162,7 @@ export async function genesis(o: GenesisOptions): Promise<GenesisResult> {
|
||||
// The choices, answered the unattended way. Both have one option today, so these are
|
||||
// redundant on purpose: the day a second filter exists, this bed keeps working instead of
|
||||
// refusing, and choosing becomes a thing it visibly does.
|
||||
`--site ${o.site ?? "main"}`,
|
||||
`--private-network wireguard`,
|
||||
`--packet-filter nftables`,
|
||||
`--host ${HOST_PATH}`,
|
||||
|
||||
@@ -217,7 +217,23 @@ async function must(machine: string, command: string, timeoutMs?: number): Promi
|
||||
return out;
|
||||
}
|
||||
async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||
return must(CONTROL, `docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
// The control plane is a live, mesh-MANAGED container: the mesh recreates it whenever its
|
||||
// declaration changes — most visibly when the machine first gets its `.internal` name on the
|
||||
// private network, which becomes the container's `--add-host` (containers are immutable, so a new
|
||||
// spec is a new container). A `docker exec mesh-control` that lands in that brief recreate window
|
||||
// fails with "container ... is not running". That is not the mesh being wrong — it is a command
|
||||
// racing a legitimate restart — so it is retried until the control plane answers again. A real
|
||||
// command failure (anything else) still throws at once.
|
||||
const deadline = Date.now() + (timeoutMs ?? 120_000);
|
||||
for (;;) {
|
||||
const { out, ok } = await on(CONTROL, `docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
if (ok) return out;
|
||||
if (/is not running|No such container/i.test(out) && Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 2_000));
|
||||
continue;
|
||||
}
|
||||
throw new Error(`${CONTROL}: docker exec mesh-control /mesh-control ${command}\n${out}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -468,10 +484,14 @@ before(async () => {
|
||||
source,
|
||||
sourceRef,
|
||||
toolsSource: forgeUrl(BASE.repo),
|
||||
toolsRef: refFor(BASE.repo),
|
||||
catalogSource: forgeUrl(MODULE.repo),
|
||||
catalogRef: refFor(MODULE.repo),
|
||||
sdkSource: forgeUrl("mesh-sdk"),
|
||||
sdkRef: refFor("mesh-sdk"),
|
||||
// The same site N1 re-places the anchor at, so N1 is a no-op and the control plane is not
|
||||
// recreated mid-test (its --add-host would otherwise change).
|
||||
site: "hosting",
|
||||
log: (m) => console.log(m),
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user