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). */
|
/** Where the shared library is built from — published before the base resolves it (ADR 0076). */
|
||||||
sdkSource: string;
|
sdkSource: string;
|
||||||
sdkRef?: 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. */
|
/** What of the catalogue to build. A branch under test is the usual reason this is not main. */
|
||||||
catalogRef?: string;
|
catalogRef?: string;
|
||||||
log?: (m: string) => void;
|
log?: (m: string) => void;
|
||||||
@@ -149,6 +154,7 @@ export async function genesis(o: GenesisOptions): Promise<GenesisResult> {
|
|||||||
`--node ${node}`,
|
`--node ${node}`,
|
||||||
`--registry ${registry}`,
|
`--registry ${registry}`,
|
||||||
`--tools-source ${o.toolsSource}`,
|
`--tools-source ${o.toolsSource}`,
|
||||||
|
`--tools-ref ${o.toolsRef ?? "main"}`,
|
||||||
`--catalog-source ${o.catalogSource}`,
|
`--catalog-source ${o.catalogSource}`,
|
||||||
`--catalog-ref ${o.catalogRef ?? "main"}`,
|
`--catalog-ref ${o.catalogRef ?? "main"}`,
|
||||||
`--sdk-source ${o.sdkSource}`,
|
`--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
|
// 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
|
// redundant on purpose: the day a second filter exists, this bed keeps working instead of
|
||||||
// refusing, and choosing becomes a thing it visibly does.
|
// refusing, and choosing becomes a thing it visibly does.
|
||||||
|
`--site ${o.site ?? "main"}`,
|
||||||
`--private-network wireguard`,
|
`--private-network wireguard`,
|
||||||
`--packet-filter nftables`,
|
`--packet-filter nftables`,
|
||||||
`--host ${HOST_PATH}`,
|
`--host ${HOST_PATH}`,
|
||||||
|
|||||||
@@ -217,7 +217,23 @@ async function must(machine: string, command: string, timeoutMs?: number): Promi
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
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,
|
source,
|
||||||
sourceRef,
|
sourceRef,
|
||||||
toolsSource: forgeUrl(BASE.repo),
|
toolsSource: forgeUrl(BASE.repo),
|
||||||
|
toolsRef: refFor(BASE.repo),
|
||||||
catalogSource: forgeUrl(MODULE.repo),
|
catalogSource: forgeUrl(MODULE.repo),
|
||||||
catalogRef: refFor(MODULE.repo),
|
catalogRef: refFor(MODULE.repo),
|
||||||
sdkSource: forgeUrl("mesh-sdk"),
|
sdkSource: forgeUrl("mesh-sdk"),
|
||||||
sdkRef: refFor("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),
|
log: (m) => console.log(m),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user