@impz/testing 6.2.0 → 6.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +214 -95
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -20107,7 +20107,8 @@ var ImpEventDetailSchema = z17.object({
|
|
|
20107
20107
|
trigger: z17.string().optional(),
|
|
20108
20108
|
coldBootReason: z17.string().optional(),
|
|
20109
20109
|
steps: z17.record(z17.string(), z17.int()).readonly().optional(),
|
|
20110
|
-
released: z17.int().positive().optional()
|
|
20110
|
+
released: z17.int().positive().optional(),
|
|
20111
|
+
memFileMib: z17.int().nonnegative().optional()
|
|
20111
20112
|
}).readonly();
|
|
20112
20113
|
var envelope = { v: z17.literal(EVENT_VERSION), at: z17.date() };
|
|
20113
20114
|
var ImpEventSchema = z17.discriminatedUnion("ev", [
|
|
@@ -20623,6 +20624,14 @@ var GuestInfoSchema = z33.object({
|
|
|
20623
20624
|
scratchDir: z33.string(),
|
|
20624
20625
|
isClearedOnBoot: z33.boolean()
|
|
20625
20626
|
});
|
|
20627
|
+
var HostInfoSchema = z33.object({
|
|
20628
|
+
kernel: z33.string(),
|
|
20629
|
+
cpuModel: z33.string(),
|
|
20630
|
+
isNested: z33.boolean(),
|
|
20631
|
+
hugePages: z33.enum(["off", "2M"]),
|
|
20632
|
+
hugePool: z33.object({ totalMib: CountSchema, freeMib: CountSchema }).nullable(),
|
|
20633
|
+
bootTemplates: CountSchema.nullable()
|
|
20634
|
+
});
|
|
20626
20635
|
var SystemInfoSchema = z33.object({
|
|
20627
20636
|
version: z33.string(),
|
|
20628
20637
|
ramBudgetMib: z33.int().nonnegative(),
|
|
@@ -20668,7 +20677,8 @@ var SystemInfoSchema = z33.object({
|
|
|
20668
20677
|
egress: z33.object({ isEnforced: z33.boolean() }).optional(),
|
|
20669
20678
|
public: PublicInfoSchema.nullable().optional(),
|
|
20670
20679
|
https: HttpsInfoSchema.nullable().optional(),
|
|
20671
|
-
features: SystemFeaturesSchema.optional()
|
|
20680
|
+
features: SystemFeaturesSchema.optional(),
|
|
20681
|
+
host: HostInfoSchema.optional()
|
|
20672
20682
|
});
|
|
20673
20683
|
var SystemFeaturesInfoSchema = z33.object({
|
|
20674
20684
|
version: z33.string(),
|
|
@@ -21950,17 +21960,19 @@ function parseKsmConfig(parsed) {
|
|
|
21950
21960
|
}
|
|
21951
21961
|
|
|
21952
21962
|
// packages/daemon/src/create-impd.ts
|
|
21963
|
+
import { readFileSync as readFileSync36 } from "fs";
|
|
21953
21964
|
import { release as release2 } from "os";
|
|
21954
21965
|
import { join as join47 } from "path";
|
|
21955
21966
|
// packages/daemon/package.json
|
|
21956
21967
|
var package_default = {
|
|
21957
21968
|
name: "@impz/daemon",
|
|
21958
|
-
version: "6.
|
|
21969
|
+
version: "6.3.0",
|
|
21959
21970
|
private: true,
|
|
21960
21971
|
description: "impd: the imp host daemon",
|
|
21961
21972
|
type: "module",
|
|
21962
21973
|
exports: {
|
|
21963
21974
|
".": "./src/index.ts",
|
|
21975
|
+
"./real-clock": "./src/platform/process/real-clock.ts",
|
|
21964
21976
|
"./testing": "./testing/index.ts",
|
|
21965
21977
|
"./testing/build-stub-clock": "./testing/platform/build-stub-clock.ts",
|
|
21966
21978
|
"./testing/data-layout": "./src/vm/storage/data-layout.ts",
|
|
@@ -27277,7 +27289,7 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
|
|
|
27277
27289
|
writeRegularFile(paths.pidFile, `${String(child.pid)}
|
|
27278
27290
|
`);
|
|
27279
27291
|
const deadline = Date.now() + SOCKET_WAIT_MS;
|
|
27280
|
-
while (!
|
|
27292
|
+
while (!await probeListening(paths.apiSocket)) {
|
|
27281
27293
|
if (child.exitCode !== null || Date.now() > deadline) {
|
|
27282
27294
|
stopPid(child.pid);
|
|
27283
27295
|
throw new Error(`firecracker did not open its API socket (exit ${String(child.exitCode)}): ${readLogTail(paths.logFile)}`);
|
|
@@ -27286,6 +27298,18 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
|
|
|
27286
27298
|
}
|
|
27287
27299
|
return child.pid;
|
|
27288
27300
|
}
|
|
27301
|
+
async function probeListening(socketPath) {
|
|
27302
|
+
if (!existsSync6(socketPath)) {
|
|
27303
|
+
return false;
|
|
27304
|
+
}
|
|
27305
|
+
try {
|
|
27306
|
+
const connection = await Bun.connect({ unix: socketPath, socket: { data: () => {} } });
|
|
27307
|
+
connection.end();
|
|
27308
|
+
return true;
|
|
27309
|
+
} catch {
|
|
27310
|
+
return false;
|
|
27311
|
+
}
|
|
27312
|
+
}
|
|
27289
27313
|
function probeProcessAlive(pid, socket, procRoot = "/proc") {
|
|
27290
27314
|
const state = readStatFields(pid, procRoot)?.[0];
|
|
27291
27315
|
if (state === undefined || state === "Z" || state === "X") {
|
|
@@ -30507,7 +30531,8 @@ async function readSystemInfo(deps) {
|
|
|
30507
30531
|
ksm: readKsmInfo(deps, running, usage.headroomMib),
|
|
30508
30532
|
public: readPublicInfo(deps.config, imps, deps.publicRecords.readStatus()),
|
|
30509
30533
|
https: await readHttpsInfo(deps),
|
|
30510
|
-
features: SYSTEM_FEATURES
|
|
30534
|
+
features: SYSTEM_FEATURES,
|
|
30535
|
+
host: deps.readHostInfo()
|
|
30511
30536
|
};
|
|
30512
30537
|
}
|
|
30513
30538
|
function readKsmInfo(deps, running, headroomMib) {
|
|
@@ -32900,6 +32925,29 @@ function readLimit(request, maxBytes) {
|
|
|
32900
32925
|
return declared;
|
|
32901
32926
|
}
|
|
32902
32927
|
|
|
32928
|
+
// packages/daemon/src/surface/read-host-info.ts
|
|
32929
|
+
function readHostInfo(sources) {
|
|
32930
|
+
const cpuInfo = readCpuInfoIfPresent(sources);
|
|
32931
|
+
return {
|
|
32932
|
+
kernel: sources.readKernel(),
|
|
32933
|
+
cpuModel: cpuInfo === null ? UNKNOWN_CPU : parseCpuInfo(cpuInfo).cpuModel,
|
|
32934
|
+
isNested: cpuInfo !== null && isNestedCpu(cpuInfo),
|
|
32935
|
+
hugePages: sources.isHugePages ? "2M" : "off",
|
|
32936
|
+
hugePool: sources.readHugePagePool(),
|
|
32937
|
+
bootTemplates: sources.readBootTemplateCount?.() ?? null
|
|
32938
|
+
};
|
|
32939
|
+
}
|
|
32940
|
+
function readCpuInfoIfPresent(sources) {
|
|
32941
|
+
try {
|
|
32942
|
+
return sources.readCpuInfo();
|
|
32943
|
+
} catch {
|
|
32944
|
+
return null;
|
|
32945
|
+
}
|
|
32946
|
+
}
|
|
32947
|
+
function isNestedCpu(cpuInfo) {
|
|
32948
|
+
return /^flags\s*:.*\bhypervisor\b/mu.test(cpuInfo);
|
|
32949
|
+
}
|
|
32950
|
+
|
|
32903
32951
|
// packages/daemon/src/create-impd-app.ts
|
|
32904
32952
|
function createImpdApp(config, deps, parts) {
|
|
32905
32953
|
const now = deps.now ?? Date.now;
|
|
@@ -32956,7 +33004,12 @@ function createImpdApp(config, deps, parts) {
|
|
|
32956
33004
|
}),
|
|
32957
33005
|
imageKeepaliveMs: keepaliveMs,
|
|
32958
33006
|
...deps.keepaliveRepeat !== undefined && { imageKeepaliveRepeat: deps.keepaliveRepeat },
|
|
32959
|
-
moves: parts.moves
|
|
33007
|
+
moves: parts.moves,
|
|
33008
|
+
readHostInfo: () => readHostInfo({
|
|
33009
|
+
isHugePages: config.hugePages !== null,
|
|
33010
|
+
...parts.hostReaders,
|
|
33011
|
+
readBootTemplateCount: parts.imps.bootTemplates?.readReadyCount ?? null
|
|
33012
|
+
})
|
|
32960
33013
|
});
|
|
32961
33014
|
}
|
|
32962
33015
|
|
|
@@ -37572,15 +37625,15 @@ function buildDeferredAdmission() {
|
|
|
37572
37625
|
};
|
|
37573
37626
|
}
|
|
37574
37627
|
|
|
37575
|
-
// packages/daemon/src/platform/process/semaphore.ts
|
|
37576
|
-
function
|
|
37577
|
-
const waiters = [];
|
|
37628
|
+
// packages/daemon/src/platform/process/priority-semaphore.ts
|
|
37629
|
+
function createPrioritySemaphore(limit) {
|
|
37630
|
+
const waiters = { foreground: [], background: [] };
|
|
37578
37631
|
let active = 0;
|
|
37579
37632
|
return {
|
|
37580
|
-
run: async (task) => {
|
|
37633
|
+
run: async (priority, task) => {
|
|
37581
37634
|
if (active >= limit) {
|
|
37582
37635
|
const turn = Promise.withResolvers();
|
|
37583
|
-
waiters.push(turn.resolve);
|
|
37636
|
+
waiters[priority].push(turn.resolve);
|
|
37584
37637
|
await turn.promise;
|
|
37585
37638
|
} else {
|
|
37586
37639
|
active += 1;
|
|
@@ -37588,7 +37641,7 @@ function createSemaphore(limit) {
|
|
|
37588
37641
|
try {
|
|
37589
37642
|
return await task();
|
|
37590
37643
|
} finally {
|
|
37591
|
-
const next = waiters.shift();
|
|
37644
|
+
const next = waiters.foreground.shift() ?? waiters.background.shift();
|
|
37592
37645
|
if (next === undefined) {
|
|
37593
37646
|
active -= 1;
|
|
37594
37647
|
} else {
|
|
@@ -37709,7 +37762,7 @@ var ENFORCE_INTERVAL_MS = 5000;
|
|
|
37709
37762
|
var ENFORCE_TRIGGER = "RAM over budget";
|
|
37710
37763
|
var ORDINARY_TRIGGER = "RAM outside the huge page pool";
|
|
37711
37764
|
function createRamGovernor(deps) {
|
|
37712
|
-
const admission =
|
|
37765
|
+
const admission = createPrioritySemaphore(1);
|
|
37713
37766
|
const reservations = new Map;
|
|
37714
37767
|
const now = deps.now ?? Date.now;
|
|
37715
37768
|
let stuckOver = false;
|
|
@@ -37905,7 +37958,7 @@ function createRamGovernor(deps) {
|
|
|
37905
37958
|
}
|
|
37906
37959
|
};
|
|
37907
37960
|
return {
|
|
37908
|
-
admit: (request) => admission.run(async () => {
|
|
37961
|
+
admit: (request) => admission.run(toTaskPriority(request), async () => {
|
|
37909
37962
|
const hugePagesMib = request.hugePagesMib ?? 0;
|
|
37910
37963
|
const hostBudgets = readBudgets();
|
|
37911
37964
|
const budgets = hugePagesMib > 0 ? { ...hostBudgets, ordinaryMib: null } : hostBudgets;
|
|
@@ -37967,7 +38020,7 @@ function createRamGovernor(deps) {
|
|
|
37967
38020
|
reservations.delete(id);
|
|
37968
38021
|
pendingHugePages.delete(id);
|
|
37969
38022
|
},
|
|
37970
|
-
admitGrow: (request) => admission.run(async () => {
|
|
38023
|
+
admitGrow: (request) => admission.run("background", async () => {
|
|
37971
38024
|
const listed = await deps.listAwake();
|
|
37972
38025
|
if (!listed.some((imp) => imp.id === request.id)) {
|
|
37973
38026
|
return false;
|
|
@@ -38024,7 +38077,7 @@ function createRamGovernor(deps) {
|
|
|
38024
38077
|
headroomMib: usage.headroomMib
|
|
38025
38078
|
};
|
|
38026
38079
|
},
|
|
38027
|
-
enforce: () => admission.run(async () => {
|
|
38080
|
+
enforce: () => admission.run("background", async () => {
|
|
38028
38081
|
const budgets = readBudgets();
|
|
38029
38082
|
const room = await makeRoom({
|
|
38030
38083
|
excludeId: null,
|
|
@@ -38051,6 +38104,9 @@ function createRamGovernor(deps) {
|
|
|
38051
38104
|
function pickEffectiveShortfall(usage, budgets, requestedMib) {
|
|
38052
38105
|
return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
|
|
38053
38106
|
}
|
|
38107
|
+
function toTaskPriority(request) {
|
|
38108
|
+
return request.isBackground === true ? "background" : "foreground";
|
|
38109
|
+
}
|
|
38054
38110
|
|
|
38055
38111
|
// packages/daemon/src/vm/watchdog/agent-watchdog.ts
|
|
38056
38112
|
var HOUR_MS = 3600000;
|
|
@@ -38876,7 +38932,6 @@ function createStorageGate(clock = REAL_CLOCK) {
|
|
|
38876
38932
|
}
|
|
38877
38933
|
|
|
38878
38934
|
// packages/daemon/src/vm/templates/boot-templates.ts
|
|
38879
|
-
import { createHash as createHash4 } from "crypto";
|
|
38880
38935
|
import {
|
|
38881
38936
|
existsSync as existsSync9,
|
|
38882
38937
|
mkdirSync as mkdirSync12,
|
|
@@ -38890,8 +38945,18 @@ import {
|
|
|
38890
38945
|
} from "fs";
|
|
38891
38946
|
import { join as join18 } from "path";
|
|
38892
38947
|
import { withContext as withContext2 } from "@logtape/logtape";
|
|
38948
|
+
import { match as match10 } from "ts-pattern";
|
|
38893
38949
|
import * as z75 from "zod";
|
|
38894
38950
|
|
|
38951
|
+
// packages/daemon/src/platform/process/semaphore.ts
|
|
38952
|
+
function createSemaphore(limit) {
|
|
38953
|
+
const semaphore = createPrioritySemaphore(limit);
|
|
38954
|
+
return { run: (task) => semaphore.run("foreground", task) };
|
|
38955
|
+
}
|
|
38956
|
+
|
|
38957
|
+
// packages/daemon/src/vm/templates/build-template-key.ts
|
|
38958
|
+
import { createHash as createHash4 } from "crypto";
|
|
38959
|
+
|
|
38895
38960
|
// packages/daemon/src/vm/vmm/configure-vm.ts
|
|
38896
38961
|
function createMarks() {
|
|
38897
38962
|
const marks = {};
|
|
@@ -38947,8 +39012,41 @@ async function setupVm(api, config) {
|
|
|
38947
39012
|
await api.putBalloon(VM_DEVICES.balloon);
|
|
38948
39013
|
}
|
|
38949
39014
|
|
|
39015
|
+
// packages/daemon/src/vm/templates/build-template-key.ts
|
|
39016
|
+
var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
|
|
39017
|
+
function buildTemplateKey(identity, shape) {
|
|
39018
|
+
const fields = {
|
|
39019
|
+
guestKernel: identity.guestKernel,
|
|
39020
|
+
systemDrive: identity.systemDrive,
|
|
39021
|
+
firecrackerVersion: identity.firecrackerVersion,
|
|
39022
|
+
snapshotVersion: identity.snapshotVersion,
|
|
39023
|
+
hostKernel: identity.hostKernel,
|
|
39024
|
+
cpuModel: identity.cpuModel,
|
|
39025
|
+
cpuFlags: identity.cpuFlags,
|
|
39026
|
+
bootArgs: TEMPLATE_BOOT_ARGS,
|
|
39027
|
+
devices: VM_DEVICES,
|
|
39028
|
+
memoryMib: shape.memoryMib,
|
|
39029
|
+
vcpus: shape.vcpus,
|
|
39030
|
+
...shape.hugePages === "2M" && { hugePages: shape.hugePages }
|
|
39031
|
+
};
|
|
39032
|
+
return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
|
|
39033
|
+
}
|
|
39034
|
+
|
|
39035
|
+
// packages/daemon/src/vm/templates/pick-restore-failure-outcome.ts
|
|
39036
|
+
import { match as match7 } from "ts-pattern";
|
|
39037
|
+
function pickRestoreFailureOutcome(facts) {
|
|
39038
|
+
if (!facts.isCurrentBuild) {
|
|
39039
|
+
return "ignore";
|
|
39040
|
+
}
|
|
39041
|
+
return match7(facts.fault).with(null, () => "ignore").with("agent", () => facts.restoredNow > facts.restoredBefore ? "keep" : pickCountedOutcome(facts)).with("load", () => pickCountedOutcome(facts)).exhaustive();
|
|
39042
|
+
}
|
|
39043
|
+
var MAX_RESTORE_FAILURES = 3;
|
|
39044
|
+
function pickCountedOutcome(facts) {
|
|
39045
|
+
return facts.failuresBefore + 1 >= MAX_RESTORE_FAILURES && !facts.isOff ? "disable" : "remove";
|
|
39046
|
+
}
|
|
39047
|
+
|
|
38950
39048
|
// packages/daemon/src/vm/templates/template-locks.ts
|
|
38951
|
-
import { match as
|
|
39049
|
+
import { match as match9 } from "ts-pattern";
|
|
38952
39050
|
|
|
38953
39051
|
// packages/daemon/src/vm/templates/can-admit-lock.ts
|
|
38954
39052
|
var MAX_LOCKED_PERCENT = 10;
|
|
@@ -38959,10 +39057,10 @@ function canAdmitLock(admission) {
|
|
|
38959
39057
|
}
|
|
38960
39058
|
|
|
38961
39059
|
// packages/daemon/src/vm/templates/pick-lock-outcome.ts
|
|
38962
|
-
import { P as P3, match as
|
|
39060
|
+
import { P as P3, match as match8 } from "ts-pattern";
|
|
38963
39061
|
var MIB4 = 1024 * 1024;
|
|
38964
39062
|
function pickLockOutcome(result, isCurrent) {
|
|
38965
|
-
return
|
|
39063
|
+
return match8(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
|
|
38966
39064
|
kind: "declined",
|
|
38967
39065
|
lockMib: Math.ceil(declined.declinedBytes / MIB4)
|
|
38968
39066
|
})).with({ lock: P3.select() }, (lock) => isCurrent ? { kind: "held", lock, lockMib: Math.ceil(lock.lockedBytes / MIB4) } : { kind: "stale", lock }).exhaustive();
|
|
@@ -38990,7 +39088,7 @@ function createTemplateLocks(deps) {
|
|
|
38990
39088
|
}
|
|
38991
39089
|
return isAdmitted;
|
|
38992
39090
|
});
|
|
38993
|
-
|
|
39091
|
+
match9(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
|
|
38994
39092
|
deps.logger.warning("boot template {key} memory not locked (errno {errno}): a restore reads what the host dropped", { key, errno: String(failed.errno) });
|
|
38995
39093
|
}).with({ kind: "declined" }, (declined) => {
|
|
38996
39094
|
deps.logger.info("boot template {key} memory not locked: {lockMib} MiB would take the locks past a tenth of the RAM budget", { key, lockMib: String(declined.lockMib) });
|
|
@@ -39031,7 +39129,6 @@ function createTemplateLocks(deps) {
|
|
|
39031
39129
|
}
|
|
39032
39130
|
|
|
39033
39131
|
// packages/daemon/src/vm/templates/boot-templates.ts
|
|
39034
|
-
var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
|
|
39035
39132
|
var TEMPLATE_ADDRESS = {
|
|
39036
39133
|
slot: -1,
|
|
39037
39134
|
tap: "imp-tpl",
|
|
@@ -39052,7 +39149,6 @@ var MIB6 = 1024 * 1024;
|
|
|
39052
39149
|
var MIN_MISSES = 2;
|
|
39053
39150
|
var MAX_TEMPLATES = 4;
|
|
39054
39151
|
var MAX_BUILD_FAILURES = 3;
|
|
39055
|
-
var MAX_RESTORE_FAILURES = 3;
|
|
39056
39152
|
var BUILD_RETRY_MS = 60000;
|
|
39057
39153
|
var TemplateMetaSchema = z75.object({
|
|
39058
39154
|
buildId: z75.string(),
|
|
@@ -39065,23 +39161,6 @@ var TemplateMetaSchema = z75.object({
|
|
|
39065
39161
|
class BuildRefusedError extends Error {
|
|
39066
39162
|
name = "BuildRefusedError";
|
|
39067
39163
|
}
|
|
39068
|
-
function buildTemplateKey(identity, shape) {
|
|
39069
|
-
const fields = {
|
|
39070
|
-
guestKernel: identity.guestKernel,
|
|
39071
|
-
systemDrive: identity.systemDrive,
|
|
39072
|
-
firecrackerVersion: identity.firecrackerVersion,
|
|
39073
|
-
snapshotVersion: identity.snapshotVersion,
|
|
39074
|
-
hostKernel: identity.hostKernel,
|
|
39075
|
-
cpuModel: identity.cpuModel,
|
|
39076
|
-
cpuFlags: identity.cpuFlags,
|
|
39077
|
-
bootArgs: TEMPLATE_BOOT_ARGS,
|
|
39078
|
-
devices: VM_DEVICES,
|
|
39079
|
-
memoryMib: shape.memoryMib,
|
|
39080
|
-
vcpus: shape.vcpus,
|
|
39081
|
-
...shape.hugePages === "2M" && { hugePages: shape.hugePages }
|
|
39082
|
-
};
|
|
39083
|
-
return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
|
|
39084
|
-
}
|
|
39085
39164
|
function createBootTemplates(deps) {
|
|
39086
39165
|
const root = join18(deps.dataDir, "templates");
|
|
39087
39166
|
const now = deps.now ?? Date.now;
|
|
@@ -39089,6 +39168,7 @@ function createBootTemplates(deps) {
|
|
|
39089
39168
|
const building = createSingleFlight();
|
|
39090
39169
|
const records = new Map;
|
|
39091
39170
|
const lastUsed = new Map;
|
|
39171
|
+
const restoredByBuild = new Map;
|
|
39092
39172
|
const lane = createSemaphore(1);
|
|
39093
39173
|
const getRecord = (key) => {
|
|
39094
39174
|
const found = records.get(key);
|
|
@@ -39262,7 +39342,8 @@ function createBootTemplates(deps) {
|
|
|
39262
39342
|
reserveMib: Math.ceil(shape.memoryMib * deps.bootReservePercent / 100),
|
|
39263
39343
|
memoryMib: shape.memoryMib,
|
|
39264
39344
|
hugePagesMib: shape.hugePages === "2M" ? shape.memoryMib : 0,
|
|
39265
|
-
maySleepImps: false
|
|
39345
|
+
maySleepImps: false,
|
|
39346
|
+
isBackground: true
|
|
39266
39347
|
});
|
|
39267
39348
|
return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB6, writeAdmitted));
|
|
39268
39349
|
} catch (error) {
|
|
@@ -39345,21 +39426,33 @@ function createBootTemplates(deps) {
|
|
|
39345
39426
|
return null;
|
|
39346
39427
|
},
|
|
39347
39428
|
buildTemplate: createTemplateFiles,
|
|
39348
|
-
reportFailure: (files,
|
|
39349
|
-
if (!isTemplateFault || readMeta(files.key)?.buildId !== files.buildId) {
|
|
39350
|
-
return;
|
|
39351
|
-
}
|
|
39429
|
+
reportFailure: (files, failure) => {
|
|
39352
39430
|
const record = getRecord(files.key);
|
|
39353
|
-
|
|
39354
|
-
|
|
39355
|
-
|
|
39431
|
+
const outcome = pickRestoreFailureOutcome({
|
|
39432
|
+
fault: failure.fault,
|
|
39433
|
+
isCurrentBuild: readMeta(files.key)?.buildId === files.buildId,
|
|
39434
|
+
restoredBefore: failure.restoredBefore,
|
|
39435
|
+
restoredNow: restoredByBuild.get(files.buildId) ?? 0,
|
|
39436
|
+
failuresBefore: record.restoreFailures,
|
|
39437
|
+
isOff: record.isOff
|
|
39438
|
+
});
|
|
39439
|
+
const removeFailed = () => {
|
|
39440
|
+
record.restoreFailures += 1;
|
|
39441
|
+
removeDir(files.key);
|
|
39442
|
+
};
|
|
39443
|
+
match10(outcome).with("ignore", () => {}).with("keep", () => {
|
|
39444
|
+
getImpdLogger("boot").info("boot template {key} kept: its agent timed out while another restore of it succeeded", { key: files.key.slice(0, 12) });
|
|
39445
|
+
}).with("remove", removeFailed).with("disable", () => {
|
|
39446
|
+
removeFailed();
|
|
39356
39447
|
record.isOff = true;
|
|
39357
39448
|
getImpdLogger("boot").warning("boot template {key} off until impd restarts: {restoreFailures} restores failed", { key: files.key.slice(0, 12), restoreFailures: String(record.restoreFailures) });
|
|
39358
|
-
}
|
|
39449
|
+
}).exhaustive();
|
|
39359
39450
|
},
|
|
39360
39451
|
reportRestored: (files) => {
|
|
39361
39452
|
getRecord(files.key).restoreFailures = 0;
|
|
39453
|
+
restoredByBuild.set(files.buildId, (restoredByBuild.get(files.buildId) ?? 0) + 1);
|
|
39362
39454
|
},
|
|
39455
|
+
countRestored: (files) => restoredByBuild.get(files.buildId) ?? 0,
|
|
39363
39456
|
removeStale: () => {
|
|
39364
39457
|
if (!existsSync9(root)) {
|
|
39365
39458
|
return [];
|
|
@@ -39388,6 +39481,7 @@ function createBootTemplates(deps) {
|
|
|
39388
39481
|
}
|
|
39389
39482
|
},
|
|
39390
39483
|
readLockedMib: templateLocks.readLockedMib,
|
|
39484
|
+
readReadyCount: () => listTemplateKeys().filter((key) => readReady(key) !== null).length,
|
|
39391
39485
|
stop: async () => {
|
|
39392
39486
|
await Promise.all([templateLocks.stop(), building.waitForAll()]);
|
|
39393
39487
|
}
|
|
@@ -42345,11 +42439,13 @@ function makeSleepImpVm(deps) {
|
|
|
42345
42439
|
}
|
|
42346
42440
|
const sleepMs = Math.round(context.sleepTiming.now() - started);
|
|
42347
42441
|
const waited = waitedMs > 0 ? `, waited ${String(waitedMs)}ms for a young guest` : "";
|
|
42442
|
+
const memFileMib = readDiskMib(paths.memFile);
|
|
42348
42443
|
slept.detail = {
|
|
42349
42444
|
trigger: reason,
|
|
42350
42445
|
durationMs: sleepMs,
|
|
42351
42446
|
prepareMs: Math.round(started - options.requestedAt),
|
|
42352
|
-
steps: timings
|
|
42447
|
+
steps: timings,
|
|
42448
|
+
memFileMib
|
|
42353
42449
|
};
|
|
42354
42450
|
const plugged = pluggedMib === null ? "" : `, ${String(pluggedMib)} MiB plugged`;
|
|
42355
42451
|
getImpdLogger("imps").with({ imp: imp.name }).info("asleep in {sleepMs}ms ({reason}){waited}, ram {ramMib} MiB{plugged}, mem file {memFile} MiB on disk, {timings}", {
|
|
@@ -42358,7 +42454,7 @@ function makeSleepImpVm(deps) {
|
|
|
42358
42454
|
waited,
|
|
42359
42455
|
ramMib: String(ramMib),
|
|
42360
42456
|
plugged,
|
|
42361
|
-
memFile: String(
|
|
42457
|
+
memFile: String(memFileMib),
|
|
42362
42458
|
timings: formatTimings(timings)
|
|
42363
42459
|
});
|
|
42364
42460
|
} catch (error) {
|
|
@@ -43288,18 +43384,18 @@ async function setupRestoreCommand(plan, jails) {
|
|
|
43288
43384
|
}
|
|
43289
43385
|
|
|
43290
43386
|
class TemplateRestoreError extends Error {
|
|
43291
|
-
|
|
43292
|
-
constructor(message,
|
|
43387
|
+
fault;
|
|
43388
|
+
constructor(message, fault, cause) {
|
|
43293
43389
|
super(message, { cause });
|
|
43294
43390
|
this.name = "TemplateRestoreError";
|
|
43295
|
-
this.
|
|
43391
|
+
this.fault = fault;
|
|
43296
43392
|
}
|
|
43297
43393
|
}
|
|
43298
43394
|
async function loadTemplateVm(plan, jails, options = {}) {
|
|
43299
43395
|
const marks = createMarks();
|
|
43300
43396
|
const pid = await startRestoreProcess(plan, jails, options.mergeWrapper ?? null);
|
|
43301
43397
|
marks.setMark("spawn");
|
|
43302
|
-
const step = {
|
|
43398
|
+
const step = { fault: "load" };
|
|
43303
43399
|
try {
|
|
43304
43400
|
const api = (options.createClient ?? createFirecrackerClient)(plan.paths.apiSocket);
|
|
43305
43401
|
const isUffd = plan.hugePages === "2M";
|
|
@@ -43326,14 +43422,15 @@ async function loadTemplateVm(plan, jails, options = {}) {
|
|
|
43326
43422
|
jails.seal(plan.paths, pid);
|
|
43327
43423
|
await api.resume();
|
|
43328
43424
|
marks.setMark("resume");
|
|
43425
|
+
step.fault = "agent";
|
|
43329
43426
|
await waitForAgent(plan.paths.vsockSocket, {
|
|
43330
|
-
deadlineMs: VM_DEADLINES.agentMs,
|
|
43427
|
+
deadlineMs: options.parkedAgentMs ?? VM_DEADLINES.agentMs,
|
|
43331
43428
|
attemptMs: 200,
|
|
43332
43429
|
maxDelayMs: RESTORE_PING_DELAY_MS,
|
|
43333
43430
|
isParked: true
|
|
43334
43431
|
});
|
|
43335
43432
|
marks.setMark("parked");
|
|
43336
|
-
step.
|
|
43433
|
+
step.fault = null;
|
|
43337
43434
|
const diskBytes = await plan.diskReady;
|
|
43338
43435
|
if (plan.jail !== null) {
|
|
43339
43436
|
jails.setupDiskOwner(plan.paths, plan.jail);
|
|
@@ -43368,7 +43465,7 @@ async function loadTemplateVm(plan, jails, options = {}) {
|
|
|
43368
43465
|
await waitForExit(pid, plan.paths.apiSocket, VM_DEADLINES.killMs);
|
|
43369
43466
|
await jails.release(plan.paths.impId);
|
|
43370
43467
|
throw new TemplateRestoreError(`template restore failed: ${reason}
|
|
43371
|
-
${readLogTail(plan.paths.logFile, 5)}`, step.
|
|
43468
|
+
${readLogTail(plan.paths.logFile, 5)}`, step.fault, error);
|
|
43372
43469
|
}
|
|
43373
43470
|
}
|
|
43374
43471
|
|
|
@@ -43398,6 +43495,7 @@ async function startColdVm(context, start) {
|
|
|
43398
43495
|
const diskBytes = readDiskBytes();
|
|
43399
43496
|
Promise.allSettled([diskBytes]);
|
|
43400
43497
|
if (template !== null && template !== undefined) {
|
|
43498
|
+
const restoredBefore = context.templates?.countRestored(template) ?? 0;
|
|
43401
43499
|
try {
|
|
43402
43500
|
const vm = await context.vms.loadTemplateVm({
|
|
43403
43501
|
firecrackerBin: context.config.firecrackerBin,
|
|
@@ -43441,8 +43539,8 @@ async function startColdVm(context, start) {
|
|
|
43441
43539
|
key: template.key.slice(0, 12),
|
|
43442
43540
|
error: getErrorMessage(error)
|
|
43443
43541
|
});
|
|
43444
|
-
const
|
|
43445
|
-
context.templates?.reportFailure(template,
|
|
43542
|
+
const fault = error instanceof TemplateRestoreError ? error.fault : null;
|
|
43543
|
+
context.templates?.reportFailure(template, { fault, restoredBefore });
|
|
43446
43544
|
}
|
|
43447
43545
|
}
|
|
43448
43546
|
await diskBytes;
|
|
@@ -43476,9 +43574,19 @@ function formatGuestSteps(steps) {
|
|
|
43476
43574
|
|
|
43477
43575
|
// packages/daemon/src/imps/imps/imp-vm-ops.ts
|
|
43478
43576
|
var SLEEP_CONCURRENCY = 2;
|
|
43577
|
+
var RESTORE_CONCURRENCY = 4;
|
|
43479
43578
|
var NO_JAIL_CGROUP = "a jailed VM starts only in its own cgroup, and it has none: the cpu controller is not delegated to /sys/fs/cgroup/imps, or the setup of imps/<id> failed (see the log); set IMP_JAILER=false on a host without cgroup delegation";
|
|
43480
|
-
function createImpVmOps(
|
|
43579
|
+
function createImpVmOps(host) {
|
|
43481
43580
|
const sleepSlots = createSemaphore(SLEEP_CONCURRENCY);
|
|
43581
|
+
const wakeSlots = createSemaphore(RESTORE_CONCURRENCY);
|
|
43582
|
+
const templateSlots = createSemaphore(RESTORE_CONCURRENCY);
|
|
43583
|
+
const context = {
|
|
43584
|
+
...host,
|
|
43585
|
+
vms: {
|
|
43586
|
+
...host.vms,
|
|
43587
|
+
loadTemplateVm: (options) => templateSlots.run(() => host.vms.loadTemplateVm(options))
|
|
43588
|
+
}
|
|
43589
|
+
};
|
|
43482
43590
|
let isStopping = false;
|
|
43483
43591
|
const updateState = async (imp, change) => {
|
|
43484
43592
|
const updated = await updateLockedImpState(context.db, imp, change);
|
|
@@ -43639,7 +43747,7 @@ function createImpVmOps(context) {
|
|
|
43639
43747
|
}
|
|
43640
43748
|
return imp.isDiskGrowPending ? setGrowPending(imp, false) : imp;
|
|
43641
43749
|
};
|
|
43642
|
-
const
|
|
43750
|
+
const startWake = makeWakeImpVm({
|
|
43643
43751
|
context,
|
|
43644
43752
|
isStopping: () => isStopping,
|
|
43645
43753
|
updateState,
|
|
@@ -43648,6 +43756,7 @@ function createImpVmOps(context) {
|
|
|
43648
43756
|
startColdImpVm,
|
|
43649
43757
|
growGuestDisk
|
|
43650
43758
|
});
|
|
43759
|
+
const wakeImpVm = (imp) => wakeSlots.run(() => startWake(imp));
|
|
43651
43760
|
const requireRunningImp = async (imp) => {
|
|
43652
43761
|
if (imp.state === "running") {
|
|
43653
43762
|
return imp;
|
|
@@ -43703,7 +43812,7 @@ function countStepsMs(timings) {
|
|
|
43703
43812
|
}
|
|
43704
43813
|
|
|
43705
43814
|
// packages/daemon/src/imps/imps/services/service-api.ts
|
|
43706
|
-
import { match as
|
|
43815
|
+
import { match as match11 } from "ts-pattern";
|
|
43707
43816
|
|
|
43708
43817
|
// packages/daemon/src/imps/imps/services/create-log-decoders.ts
|
|
43709
43818
|
function createLogDecoders() {
|
|
@@ -43927,7 +44036,7 @@ function handleAgentError(service) {
|
|
|
43927
44036
|
if (!(error instanceof AgentError)) {
|
|
43928
44037
|
throw error;
|
|
43929
44038
|
}
|
|
43930
|
-
throw
|
|
44039
|
+
throw match11(error.code).with("NO_SERVICE", () => buildNotFoundError("service", service)).with("SERVICE_EXISTS", () => buildConflictError("service", service)).with("BAD_REQUEST", () => new ImpdError("BAD_REQUEST", { message: error.detail })).with("AGENT_OUTDATED", () => buildAgentOutdatedApiError(error.message)).otherwise(() => error);
|
|
43931
44040
|
};
|
|
43932
44041
|
}
|
|
43933
44042
|
function stopStreams(streams) {
|
|
@@ -45028,28 +45137,29 @@ function createVmRunner(jails, options = {}) {
|
|
|
45028
45137
|
setMark("spawn");
|
|
45029
45138
|
try {
|
|
45030
45139
|
const api = createClient(plan.paths.apiSocket);
|
|
45031
|
-
const
|
|
45032
|
-
|
|
45033
|
-
|
|
45034
|
-
|
|
45035
|
-
|
|
45036
|
-
|
|
45037
|
-
|
|
45038
|
-
|
|
45039
|
-
|
|
45040
|
-
|
|
45041
|
-
|
|
45042
|
-
|
|
45043
|
-
});
|
|
45044
|
-
const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
|
|
45045
|
-
if (regionMib > 0) {
|
|
45046
|
-
await api.putHotplugMemory({
|
|
45047
|
-
totalSizeMib: regionMib,
|
|
45048
|
-
slotSizeMib: SLOT_MIB,
|
|
45049
|
-
blockSizeMib: BLOCK_MIB
|
|
45140
|
+
const configure = async () => {
|
|
45141
|
+
await setupVm(api, {
|
|
45142
|
+
kernelPath: plan.kernelPath,
|
|
45143
|
+
bootArgs: buildBootArgs(plan),
|
|
45144
|
+
vcpus: plan.vcpus,
|
|
45145
|
+
memoryMib: plan.memoryMib,
|
|
45146
|
+
hugePages: plan.hugePages ?? "None",
|
|
45147
|
+
diskPath: plan.paths.disk,
|
|
45148
|
+
systemDrivePath: plan.systemDrivePath,
|
|
45149
|
+
vsockPath: plan.paths.vsockSocket,
|
|
45150
|
+
tap: plan.address.tap,
|
|
45151
|
+
guestMac: plan.address.guestMac
|
|
45050
45152
|
});
|
|
45051
|
-
|
|
45052
|
-
|
|
45153
|
+
const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
|
|
45154
|
+
if (regionMib > 0) {
|
|
45155
|
+
await api.putHotplugMemory({
|
|
45156
|
+
totalSizeMib: regionMib,
|
|
45157
|
+
slotSizeMib: SLOT_MIB,
|
|
45158
|
+
blockSizeMib: BLOCK_MIB
|
|
45159
|
+
});
|
|
45160
|
+
}
|
|
45161
|
+
};
|
|
45162
|
+
const [firecrackerVersion] = await Promise.all([api.readVersion(), configure()]);
|
|
45053
45163
|
setMark("configure");
|
|
45054
45164
|
jails.removeUffdDevice(plan.paths.impId);
|
|
45055
45165
|
jails.seal(plan.paths, pid);
|
|
@@ -46884,7 +46994,7 @@ function readSettled2(row, imp) {
|
|
|
46884
46994
|
|
|
46885
46995
|
// packages/daemon/src/imp-ops/moves/move-sender.ts
|
|
46886
46996
|
import invariant6 from "tiny-invariant";
|
|
46887
|
-
import { match as
|
|
46997
|
+
import { match as match12 } from "ts-pattern";
|
|
46888
46998
|
|
|
46889
46999
|
// packages/daemon/src/platform/db/move-send-steps.ts
|
|
46890
47000
|
var MOVE_SEND_STEPS = [
|
|
@@ -47447,7 +47557,7 @@ function createMoveSender(deps) {
|
|
|
47447
47557
|
};
|
|
47448
47558
|
const recoverSend = async (imp, row) => {
|
|
47449
47559
|
const log = getImpdLogger("move").with({ imp: imp.name });
|
|
47450
|
-
await
|
|
47560
|
+
await match12(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
|
|
47451
47561
|
log.info("a prepared send waits for its send after a restart");
|
|
47452
47562
|
}).with("abort", async () => {
|
|
47453
47563
|
const isUndone = await resetSend(imp, row?.peer_url ?? null, row?.ticket ?? null);
|
|
@@ -47569,7 +47679,7 @@ function buildPublicError(name) {
|
|
|
47569
47679
|
}
|
|
47570
47680
|
function pickSendRecovery(moveState, step) {
|
|
47571
47681
|
if (moveState === "sending") {
|
|
47572
|
-
return
|
|
47682
|
+
return match12(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
|
|
47573
47683
|
}
|
|
47574
47684
|
return moveState === "moved" && step === "sent" ? "commit" : "none";
|
|
47575
47685
|
}
|
|
@@ -47621,7 +47731,7 @@ function createMoveService(deps) {
|
|
|
47621
47731
|
// packages/daemon/src/surface/moves/create-move-peer-handler.ts
|
|
47622
47732
|
import { OpenAPIHandler } from "@orpc/openapi/fetch";
|
|
47623
47733
|
import { ORPCError as ORPCError7, implement as implement3, onError as onError3 } from "@orpc/server";
|
|
47624
|
-
import { match as
|
|
47734
|
+
import { match as match13 } from "ts-pattern";
|
|
47625
47735
|
function createMovePeerHandler(deps) {
|
|
47626
47736
|
const withTicket = async (step, context, call) => {
|
|
47627
47737
|
const startedAt = deps.now();
|
|
@@ -47672,7 +47782,7 @@ function createMovePeerHandler(deps) {
|
|
|
47672
47782
|
const respondReceive = async (context) => {
|
|
47673
47783
|
try {
|
|
47674
47784
|
const received = await withTicket("receive", context, (checked) => deps.calls.receive(checked, context.request));
|
|
47675
|
-
return
|
|
47785
|
+
return match13(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
|
|
47676
47786
|
} catch (error) {
|
|
47677
47787
|
const failure = toPeerError(error);
|
|
47678
47788
|
return Response.json(encodePeerError(failure), { status: failure.status });
|
|
@@ -51932,14 +52042,14 @@ function expandScopes(asked) {
|
|
|
51932
52042
|
}
|
|
51933
52043
|
|
|
51934
52044
|
// packages/daemon/src/platform/http/render-html.ts
|
|
51935
|
-
import { P as P4, match as
|
|
52045
|
+
import { P as P4, match as match14 } from "ts-pattern";
|
|
51936
52046
|
var MARKUP = Symbol("markup");
|
|
51937
52047
|
function renderHTML(strings, ...parts) {
|
|
51938
52048
|
const html = parts.reduce((text, part, index) => `${text}${toHTML(part)}${strings[index + 1] ?? ""}`, strings[0] ?? "");
|
|
51939
52049
|
return { [MARKUP]: true, html };
|
|
51940
52050
|
}
|
|
51941
52051
|
function toHTML(part) {
|
|
51942
|
-
return
|
|
52052
|
+
return match14(part).with(P4.string, (text) => escapeHTML(text)).with(P4.array(), (list) => list.map((each) => each.html).join("")).with({ html: P4.string }, (markup) => markup.html).exhaustive();
|
|
51943
52053
|
}
|
|
51944
52054
|
function escapeHTML(text) {
|
|
51945
52055
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -58167,6 +58277,11 @@ async function startImpdParts(config, boundaries, plan) {
|
|
|
58167
58277
|
checkDnsToken: dnsToken?.check ?? null,
|
|
58168
58278
|
readTailscale,
|
|
58169
58279
|
readTailnetNames: tailnetNames === null ? null : tailnetNames.readStatus,
|
|
58280
|
+
hostReaders: {
|
|
58281
|
+
readKernel: () => deps.hostKernelRelease ?? release2(),
|
|
58282
|
+
readCpuInfo: deps.readCpuInfo ?? (() => readFileSync36("/proc/cpuinfo", "utf8")),
|
|
58283
|
+
readHugePagePool: deps.imps?.readHugePagePool ?? (() => readHugePagePool())
|
|
58284
|
+
},
|
|
58170
58285
|
isReady: () => state.ready,
|
|
58171
58286
|
readKsmHostStats: readKsmStats,
|
|
58172
58287
|
...deps.keepaliveMs !== undefined && { keepaliveMs: deps.keepaliveMs }
|
|
@@ -58626,15 +58741,19 @@ log tail`);
|
|
|
58626
58741
|
loadTemplateVm: (plan) => runInGeneration(async () => {
|
|
58627
58742
|
restorePlans.push(plan);
|
|
58628
58743
|
const vm = await startFakeVm("restore", plan.paths).catch((error) => {
|
|
58629
|
-
throw new TemplateRestoreError("restore failed",
|
|
58744
|
+
throw new TemplateRestoreError("restore failed", "load", error);
|
|
58630
58745
|
});
|
|
58746
|
+
if (await chooseOutcome("parkedAgent") !== "ok") {
|
|
58747
|
+
alive.delete(vm.pid);
|
|
58748
|
+
throw new TemplateRestoreError("the parked agent never answered", "agent", null);
|
|
58749
|
+
}
|
|
58631
58750
|
await plan.diskReady.catch((error) => {
|
|
58632
58751
|
alive.delete(vm.pid);
|
|
58633
|
-
throw new TemplateRestoreError("disk failed",
|
|
58752
|
+
throw new TemplateRestoreError("disk failed", null, error);
|
|
58634
58753
|
});
|
|
58635
58754
|
if (await chooseOutcome("claim") !== "ok") {
|
|
58636
58755
|
alive.delete(vm.pid);
|
|
58637
|
-
throw new TemplateRestoreError("claim failed",
|
|
58756
|
+
throw new TemplateRestoreError("claim failed", null, null);
|
|
58638
58757
|
}
|
|
58639
58758
|
restores.push({
|
|
58640
58759
|
hostname: plan.claim.hostname,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impz/testing",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "A real impd on stand-ins, for Bun tests of code that drives impd through @impz/client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"ts-pattern": "^5.9.2",
|
|
44
44
|
"xstate": "^5.33.3",
|
|
45
45
|
"zod": "^4.6.7",
|
|
46
|
-
"@impz/client": "6.
|
|
46
|
+
"@impz/client": "6.3.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"bun": ">=1.4.3"
|