Skip to content

Commit 037d8cc

Browse files
committed
fix(e2e): add Brev readiness diagnostics
Signed-off-by: Julie Yaunches <jyaunches@nvidia.com>
1 parent 6aa38e8 commit 037d8cc

2 files changed

Lines changed: 308 additions & 160 deletions

File tree

test/brev-launchable-e2e.test.ts

Lines changed: 123 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ function executable(file: string, source: string): void {
2323
function fixture(
2424
options: {
2525
bootImage?: string;
26+
brevContainerStatus?: number;
27+
brevHostStatus?: number;
2628
deleteFails?: boolean;
2729
e2eFails?: boolean;
2830
imageRepositorySha?: string;
@@ -38,8 +40,11 @@ function fixture(
3840
repoSha?: string;
3941
runtimeOverrides?: boolean;
4042
schemaVersion?: number;
43+
hostAliasConfigured?: boolean;
44+
plainAliasConfigured?: boolean;
4145
sshDefaultStatus?: number;
4246
sshHostError?: string;
47+
sshHostProbeStatus?: number;
4348
sshReadyAfter?: number;
4449
sourceRepository?: string;
4550
sourcePath?: string;
@@ -52,6 +57,7 @@ function fixture(
5257
const workDir = path.join(root, "evidence");
5358
const state = path.join(root, "workspace.json");
5459
const calls = path.join(root, "calls.log");
60+
const diagnosticPhase = path.join(root, "diagnostic-phase");
5561
const refreshAttempts = path.join(root, "refresh-attempts");
5662
const sshAttempts = path.join(root, "ssh-attempts");
5763
const timeoutBlock = path.join(root, "timeout-block");
@@ -133,6 +139,14 @@ case "$1" in
133139
jq -cn --arg name "$INSTANCE_NAME" --arg shell "$shell" --arg build "$build" \
134140
'{workspaces:[{id:"ws-1",name:$name,status:"RUNNING",shell_status:$shell,build_status:$build}]}' > "$FAKE_STATE" ;;
135141
exec)
142+
if [ "\${3:-}" = true ]; then
143+
if [ "\${4:-}" = --host ]; then
144+
[ "$FAKE_BREV_HOST_STATUS" -eq 0 ] || printf '%s\n' "$FAKE_BREV_HOST_ERROR" >&2
145+
exit "$FAKE_BREV_HOST_STATUS"
146+
fi
147+
[ "$FAKE_BREV_CONTAINER_STATUS" -eq 0 ] || printf '%s\n' "$FAKE_BREV_CONTAINER_ERROR" >&2
148+
exit "$FAKE_BREV_CONTAINER_STATUS"
149+
fi
136150
case "$3" in
137151
*NEMOCLAW_BOOT_IMAGE*)
138152
printf 'NEMOCLAW_BOOT_IMAGE=%s\\n' "$FAKE_BOOT_IMAGE"
@@ -174,6 +188,20 @@ esac
174188
path.join(bin, "ssh"),
175189
`#!/usr/bin/env bash
176190
set -euo pipefail
191+
if [ "\${1:-}" = -G ]; then
192+
touch "$FAKE_DIAGNOSTIC_PHASE"
193+
alias="\${2:-}"
194+
configured=0
195+
if [ "$alias" = "$INSTANCE_NAME" ]; then
196+
configured="$FAKE_PLAIN_ALIAS_CONFIGURED"
197+
elif [ "$alias" = "$INSTANCE_NAME-host" ]; then
198+
configured="$FAKE_HOST_ALIAS_CONFIGURED"
199+
fi
200+
if [ "$configured" = 1 ]; then hostname=203.0.113.20; else hostname="$alias"; fi
201+
printf 'hostname %s\n' "$hostname"
202+
printf 'user hidden-user\nidentityfile /hidden/private-key\nproxycommand none\n'
203+
exit 0
204+
fi
177205
if [ "\${*: -1}" = true ]; then
178206
required=(-T "-o BatchMode=yes" "-o ConnectTimeout=10" "-o ConnectionAttempts=1" "-o NumberOfPasswordPrompts=0" "-o RequestTTY=no" "-o LogLevel=ERROR")
179207
for argument in "\${required[@]}"; do
@@ -189,6 +217,13 @@ if [ "\${*: -1}" = true ]; then
189217
exit 0
190218
fi
191219
[ "$target" = "$INSTANCE_NAME-host" ]
220+
if [ -f "$FAKE_DIAGNOSTIC_PHASE" ]; then
221+
if [ "$FAKE_SSH_HOST_PROBE_STATUS" -ne 0 ]; then
222+
printf '%s\n' "$FAKE_SSH_HOST_ERROR" >&2
223+
exit "$FAKE_SSH_HOST_PROBE_STATUS"
224+
fi
225+
exit 0
226+
fi
192227
attempts=0
193228
[ ! -f "$FAKE_SSH_ATTEMPTS" ] || attempts="$(cat "$FAKE_SSH_ATTEMPTS")"
194229
attempts=$((attempts + 1))
@@ -225,15 +260,24 @@ printf 'NEMOCLAW_FULL_E2E_PASSED\\n'
225260
CANDIDATE_SHA: candidateSha,
226261
CORRELATION_ID: "11111111-1111-4111-8111-111111111111",
227262
FAKE_BOOT_IMAGE: options.bootImage ?? "projects/brevdevprod/global/images/nemoclaw-test-image",
263+
FAKE_BREV_CONTAINER_ERROR:
264+
"Brev container safe detail; credential=container-secret; endpoint=container.hidden.internal",
265+
FAKE_BREV_CONTAINER_STATUS: String(options.brevContainerStatus ?? 31),
266+
FAKE_BREV_HOST_ERROR:
267+
"Brev host safe detail; token=host-token; endpoint=host-exec.hidden.internal",
268+
FAKE_BREV_HOST_STATUS: String(options.brevHostStatus ?? 32),
228269
FAKE_CALLS: calls,
229270
FAKE_DELETE_FAILS: options.deleteFails ? "1" : "0",
271+
FAKE_DIAGNOSTIC_PHASE: diagnosticPhase,
230272
FAKE_E2E_FAILS: options.e2eFails ? "1" : "0",
231273
FAKE_IMAGE_REPOSITORY_SHA: options.imageRepositorySha ?? "b".repeat(40),
274+
FAKE_HOST_ALIAS_CONFIGURED: options.hostAliasConfigured === false ? "0" : "1",
232275
FAKE_MISSING_PROVISION_RECEIPT: options.missingProvisionReceipt ? "1" : "0",
233276
FAKE_OMIT_RECEIPT_FIELD: options.omitReceiptField ?? "",
234277
FAKE_PROVISION_IMAGE_REPOSITORY_SHA:
235278
options.provisionImageRepositorySha ?? options.imageRepositorySha ?? "b".repeat(40),
236279
FAKE_PROVISION_SHA: options.provisionSha ?? candidateSha,
280+
FAKE_PLAIN_ALIAS_CONFIGURED: options.plainAliasConfigured === false ? "0" : "1",
237281
FAKE_READY: options.ready === false ? "0" : "1",
238282
FAKE_RECEIPT_SHA: options.receiptSha ?? candidateSha,
239283
FAKE_REPO_CLEAN: options.repoClean === false ? "false" : "true",
@@ -252,6 +296,7 @@ printf 'NEMOCLAW_FULL_E2E_PASSED\\n'
252296
FAKE_SSH_HOST_ERROR:
253297
options.sshHostError ??
254298
"ssh: Could not resolve hostname host.hidden.internal: host SSH safe detail; password=ssh-secret; identityfile=/hidden/private-key",
299+
FAKE_SSH_HOST_PROBE_STATUS: String(options.sshHostProbeStatus ?? 34),
255300
FAKE_SSH_READY_AFTER: String(options.sshReadyAfter ?? 1),
256301
FAKE_SOURCE_REPOSITORY: options.sourceRepository ?? "NVIDIA/NemoClaw",
257302
FAKE_SOURCE_PATH: options.sourcePath ?? "/opt/nemoclaw-image/NemoClaw",
@@ -268,7 +313,7 @@ printf 'NEMOCLAW_FULL_E2E_PASSED\\n'
268313
RUNNER_TEMP: root,
269314
WORK_DIR: workDir,
270315
};
271-
return { calls, env, sshAttempts, state, workDir };
316+
return { calls, env, refreshAttempts, sshAttempts, state, workDir };
272317
}
273318

274319
function run(env: NodeJS.ProcessEnv) {
@@ -281,7 +326,9 @@ function emittedOutput(result: ReturnType<typeof run>, workDir: string): string
281326

282327
describe("focused staging Brev Launchable lane", () => {
283328
it("binds the producer run, verifies the clean booted SHA, runs E2E, and deletes (#6943)", () => {
284-
const { calls, env, sshAttempts, state, workDir } = fixture({ sshReadyAfter: 6 });
329+
const { calls, env, sshAttempts, state, workDir } = fixture({
330+
sshReadyAfter: 6,
331+
});
285332
const result = run(env);
286333
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
287334
const commands = fs.readFileSync(calls, "utf8");
@@ -315,28 +362,33 @@ describe("focused staging Brev Launchable lane", () => {
315362
.find((line) => line.startsWith("ssh host readiness attempt 1: "));
316363
expect(readinessCall).toBeDefined();
317364
const readinessArgs = readinessCall?.split(": ").at(1)?.split(" ") ?? [];
318-
expect(readinessArgs).toEqual(
319-
expect.arrayContaining([
320-
"-T",
321-
"BatchMode=yes",
322-
"ConnectTimeout=10",
323-
"ConnectionAttempts=1",
324-
"NumberOfPasswordPrompts=0",
325-
"RequestTTY=no",
326-
"LogLevel=ERROR",
327-
]),
328-
);
329-
expect(readinessArgs.slice(-2)).toEqual(["nclaw-e2e-test-1-host", "true"]);
365+
expect(readinessArgs).toEqual([
366+
"-T",
367+
"-o",
368+
"BatchMode=yes",
369+
"-o",
370+
"ConnectTimeout=10",
371+
"-o",
372+
"ConnectionAttempts=1",
373+
"-o",
374+
"NumberOfPasswordPrompts=0",
375+
"-o",
376+
"RequestTTY=no",
377+
"-o",
378+
"LogLevel=ERROR",
379+
"nclaw-e2e-test-1-host",
380+
"true",
381+
]);
330382
expect(fs.readFileSync(sshAttempts, "utf8").trim()).toBe("6");
331383
expect(commands).toContain("ssh preinstalled full-e2e.test.ts");
332384
expect(commands).not.toContain("nvapi-test-value");
333385
expect(commands).not.toMatch(/rsync|install\.sh|npm (?:ci|install)|git clone/u);
386+
expect(fs.readFileSync(path.join(workDir, "lane.log"), "utf8")).not.toMatch(
387+
/last failure|readiness diagnostics|Readiness classification/u,
388+
);
334389
expect(fs.readFileSync(path.join(workDir, "lane.log"), "utf8")).toContain(
335390
"Waiting up to 900 seconds for host SSH access",
336391
);
337-
expect(fs.readFileSync(path.join(workDir, "lane.log"), "utf8")).not.toMatch(
338-
/last failure|Readiness classification/u,
339-
);
340392
expect(fs.existsSync(state)).toBe(false);
341393
expect(fs.readdirSync(workDir).sort()).toEqual([
342394
"cleanup.json",
@@ -431,61 +483,77 @@ describe("focused staging Brev Launchable lane", () => {
431483
});
432484
});
433485

434-
it("reports only final refresh and direct host SSH failures with bounded diagnostic redaction", () => {
486+
it("reports only the final sanitized refresh and direct host SSH failures", () => {
435487
const { calls, env, state, workDir } = fixture({
436-
refreshError: `refresh safe detail; api_key=brev-test-secret; endpoint=https://refresh.hidden.internal/path; ${"r".repeat(6_000)}`,
488+
plainAliasConfigured: false,
489+
refreshError: `refresh final safe detail\npassword=hunter2\n${"x".repeat(5_000)}`,
437490
refreshStatus: 35,
438-
sshHostError: `ssh: Could not resolve hostname host.hidden.internal: host SSH safe detail; password=ssh-secret; identityfile=/hidden/private-key; Authorization: Bearer short-token; ${"s".repeat(6_000)}`,
491+
sshHostError:
492+
"hidden-user@example.internal: Permission denied (publickey); host SSH final safe detail; password=ssh-secret; identityfile=/hidden/private-key\nAuthorization: Bearer short-token",
439493
sshReadyAfter: Number.MAX_SAFE_INTEGER,
440494
});
441-
const result = run({ ...env, BREV_HOST_SSH_TIMEOUT_SECONDS: "5" });
495+
const result = run({ ...env, BREV_HOST_SSH_TIMEOUT_SECONDS: "2" });
442496
expect(result.status).not.toBe(0);
443497
expect(result.stderr).toContain("host SSH readiness timed out");
444498
const commands = fs.readFileSync(calls, "utf8");
499+
expect(commands).toContain("timeout 5s ssh -G nclaw-e2e-test-1");
500+
expect(commands).toContain("timeout 5s ssh -G nclaw-e2e-test-1-host");
501+
expect(commands).toContain("timeout 15s brev exec nclaw-e2e-test-1 true");
502+
expect(commands).toContain("timeout 15s brev exec nclaw-e2e-test-1 true --host");
445503
expect(commands).toMatch(
446-
/timeout [1-5]s ssh -T -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o NumberOfPasswordPrompts=0 -o RequestTTY=no -o LogLevel=ERROR nclaw-e2e-test-1 true/u,
504+
/timeout 15s ssh -T -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o NumberOfPasswordPrompts=0 -o RequestTTY=no -o LogLevel=ERROR nclaw-e2e-test-1 true/u,
447505
);
448506
expect(commands).toMatch(
449-
/timeout [1-5]s ssh -T -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o NumberOfPasswordPrompts=0 -o RequestTTY=no -o LogLevel=ERROR nclaw-e2e-test-1-host true/u,
507+
/timeout 15s ssh -T -o BatchMode=yes -o ConnectTimeout=10 -o ConnectionAttempts=1 -o NumberOfPasswordPrompts=0 -o RequestTTY=no -o LogLevel=ERROR nclaw-e2e-test-1-host true/u,
450508
);
451-
expect(commands).not.toMatch(/ssh -G|brev exec .* true --host/u);
452509
expect(commands).not.toMatch(/NEMOCLAW_BOOT_IMAGE|full-e2e\.test\.ts/u);
453510

454511
const output = emittedOutput(result, workDir);
455512
expect(output).toContain(
456-
"Readiness Brev refresh last failure: status 35; error: refresh safe detail",
513+
"Readiness Brev refresh last failure: status 35; error: refresh final safe detail",
457514
);
458515
expect(output).toContain("Readiness direct host SSH last failure: status 34; error:");
459-
expect(output).toContain("host SSH safe detail");
460-
expect(output).toContain(
461-
"Readiness initial default Brev container probe: status 33; error: default SSH safe detail",
462-
);
516+
expect(output).toContain("host SSH final safe detail");
463517
expect(output).toContain("kex_exchange_identification");
464-
expect(output).toContain(
465-
"Readiness classification: initial default Brev container probe failed; direct host SSH did not succeed before deadline",
466-
);
518+
expect(output).toContain("Readiness SSH alias nclaw-e2e-test-1: missing");
519+
expect(output).toContain("Readiness SSH alias nclaw-e2e-test-1-host: configured");
520+
expect(output).toContain("Readiness probe brev exec container: failure; status 31;");
521+
expect(output).toContain("Readiness probe brev exec host: failure; status 32;");
522+
expect(output).toContain("Readiness probe direct SSH container: failure; status 33;");
523+
expect(output).toContain("Readiness probe direct SSH host: failure; status 34;");
524+
expect(output).toContain("Readiness classification: Brev refresh/configuration failure");
467525
expect(output).not.toContain("stale refresh detail");
468526
expect(output).not.toContain("stale host SSH detail");
469-
const errorLines = fs
527+
const diagnosticErrorLines = fs
470528
.readFileSync(path.join(workDir, "lane.log"), "utf8")
471529
.split("\n")
472530
.filter((line) => line.includes("; error:"));
473-
expect(errorLines).not.toHaveLength(0);
474-
for (const line of errorLines) {
475-
expect(Buffer.byteLength(line.split("; error: ", 2)[1] ?? "")).toBeLessThanOrEqual(512);
531+
expect(diagnosticErrorLines).not.toHaveLength(0);
532+
for (const line of diagnosticErrorLines) {
533+
const error = line.split("; error: ", 2)[1]?.replace(/\)$/u, "") ?? "";
534+
expect(Buffer.byteLength(error)).toBeLessThanOrEqual(512);
476535
}
477-
for (const sensitiveValue of [
536+
for (const secretOrConfiguration of [
478537
"brev-test-secret",
538+
"container-secret",
479539
"default-secret",
540+
"host-token",
480541
"ssh-secret",
481542
"short-token",
543+
"hunter2",
544+
"hidden-user",
482545
"github-test-token",
483546
"nvapi-test-value",
484547
"/hidden/private-key",
485548
"host.hidden.internal",
549+
"host-exec.hidden.internal",
550+
"container.hidden.internal",
486551
"refresh.hidden.internal",
552+
"203.0.113.20",
553+
"identityfile /hidden/private-key",
554+
"user hidden-user",
487555
]) {
488-
expect(output).not.toContain(sensitiveValue);
556+
expect(output).not.toContain(secretOrConfiguration);
489557
}
490558
expect(fs.existsSync(state)).toBe(false);
491559
expect(JSON.parse(fs.readFileSync(path.join(workDir, "cleanup.json"), "utf8"))).toMatchObject({
@@ -494,28 +562,22 @@ describe("focused staging Brev Launchable lane", () => {
494562
});
495563

496564
it.each([
497-
[
498-
0,
499-
"initial default Brev container probe succeeded; direct host SSH did not succeed before deadline",
500-
],
501-
[
502-
33,
503-
"initial default Brev container probe failed; direct host SSH did not succeed before deadline",
504-
],
505-
[
506-
125,
507-
"initial default Brev container probe failed; direct host SSH did not succeed before deadline",
508-
],
509-
])("classifies initial default container status %i as %s", (sshDefaultStatus, classification) => {
565+
["default container reachable but host unreachable", { brevContainerStatus: 0 }],
566+
["Brev host execution works but direct host SSH fails", { brevHostStatus: 0 }],
567+
["neither target reachable", {}],
568+
])("classifies %s after the shared readiness deadline", (classification, probeOptions) => {
510569
const { calls, env, state, workDir } = fixture({
511-
sshDefaultStatus,
570+
...probeOptions,
512571
sshReadyAfter: Number.MAX_SAFE_INTEGER,
513572
});
514-
const result = run({ ...env, BREV_HOST_SSH_TIMEOUT_SECONDS: "5" });
573+
const result = run({ ...env, BREV_HOST_SSH_TIMEOUT_SECONDS: "1" });
515574
expect(result.status).not.toBe(0);
516575
expect(emittedOutput(result, workDir)).toContain(`Readiness classification: ${classification}`);
517576
const commands = fs.readFileSync(calls, "utf8");
518-
expect(commands).toContain("ssh default diagnostic probe");
577+
expect(commands).toContain("timeout 15s brev exec nclaw-e2e-test-1 true");
578+
expect(commands).toContain("timeout 15s brev exec nclaw-e2e-test-1 true --host");
579+
expect(commands).toMatch(/timeout 15s ssh -T .* nclaw-e2e-test-1 true/u);
580+
expect(commands).toMatch(/timeout 15s ssh -T .* nclaw-e2e-test-1-host true/u);
519581
expect(commands).not.toMatch(/NEMOCLAW_BOOT_IMAGE|full-e2e\.test\.ts/u);
520582
expect(fs.existsSync(state)).toBe(false);
521583
expect(JSON.parse(fs.readFileSync(path.join(workDir, "cleanup.json"), "utf8"))).toMatchObject({
@@ -554,15 +616,11 @@ describe("focused staging Brev Launchable lane", () => {
554616
const elapsedMs = performance.now() - startedAt;
555617
expect(result.status).not.toBe(0);
556618
expect(result.stderr).toContain("host SSH readiness timed out");
557-
expect(elapsedMs).toBeLessThan(10_000);
558-
expect(emittedOutput(result, workDir)).toContain(
559-
"Readiness classification: default Brev container and direct host SSH were not probed before deadline",
560-
);
619+
expect(elapsedMs).toBeLessThan(80_000);
561620
const commands = fs.readFileSync(calls, "utf8");
562621
expect(commands).toContain("timeout 1s brev refresh");
563-
expect(commands).not.toMatch(
564-
/ssh default diagnostic probe|ssh host readiness attempt|NEMOCLAW_BOOT_IMAGE|full-e2e\.test\.ts/u,
565-
);
622+
expect(commands).not.toMatch(/NEMOCLAW_BOOT_IMAGE|full-e2e\.test\.ts/u);
623+
expect(commands).toContain("timeout 15s brev exec nclaw-e2e-test-1 true");
566624
expect(fs.existsSync(state)).toBe(false);
567625
expect(JSON.parse(fs.readFileSync(path.join(workDir, "cleanup.json"), "utf8"))).toMatchObject({
568626
status: "ABSENT",
@@ -587,22 +645,22 @@ describe("focused staging Brev Launchable lane", () => {
587645
});
588646
}, 90_000);
589647

590-
it("caps the five-second poll sleep by the shared readiness deadline", () => {
648+
it("caps the poll sleep by the shared readiness deadline", () => {
591649
const { calls, env, state, workDir } = fixture({
592650
sshReadyAfter: Number.MAX_SAFE_INTEGER,
593651
});
594652
const result = run({
595653
...env,
596-
BREV_HOST_SSH_TIMEOUT_SECONDS: "5",
654+
BREV_HOST_SSH_TIMEOUT_SECONDS: "2",
597655
POLL_SECONDS: "9",
598656
});
599657
expect(result.status).not.toBe(0);
600658
const commands = fs.readFileSync(calls, "utf8");
601659
const readinessCommands = commands.slice(
602-
commands.indexOf("timeout 5s brev refresh"),
660+
commands.indexOf("timeout 2s brev refresh"),
603661
commands.indexOf("timeout 60s brev delete"),
604662
);
605-
expect(readinessCommands).toMatch(/sleep [1-5]/u);
663+
expect(readinessCommands).toMatch(/sleep [12]/u);
606664
expect(readinessCommands).not.toContain("sleep 9");
607665
expect(commands).not.toMatch(/NEMOCLAW_BOOT_IMAGE|full-e2e\.test\.ts/u);
608666
expect(fs.existsSync(state)).toBe(false);

0 commit comments

Comments
 (0)