@impz/testing 6.1.1 → 6.2.1
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 +159 -89
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -21955,7 +21955,7 @@ import { join as join47 } from "path";
|
|
|
21955
21955
|
// packages/daemon/package.json
|
|
21956
21956
|
var package_default = {
|
|
21957
21957
|
name: "@impz/daemon",
|
|
21958
|
-
version: "6.
|
|
21958
|
+
version: "6.2.1",
|
|
21959
21959
|
private: true,
|
|
21960
21960
|
description: "impd: the imp host daemon",
|
|
21961
21961
|
type: "module",
|
|
@@ -27277,7 +27277,7 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
|
|
|
27277
27277
|
writeRegularFile(paths.pidFile, `${String(child.pid)}
|
|
27278
27278
|
`);
|
|
27279
27279
|
const deadline = Date.now() + SOCKET_WAIT_MS;
|
|
27280
|
-
while (!
|
|
27280
|
+
while (!await probeListening(paths.apiSocket)) {
|
|
27281
27281
|
if (child.exitCode !== null || Date.now() > deadline) {
|
|
27282
27282
|
stopPid(child.pid);
|
|
27283
27283
|
throw new Error(`firecracker did not open its API socket (exit ${String(child.exitCode)}): ${readLogTail(paths.logFile)}`);
|
|
@@ -27286,6 +27286,18 @@ async function startFirecracker(command, paths, cgroupProcs = null, mergeWrapper
|
|
|
27286
27286
|
}
|
|
27287
27287
|
return child.pid;
|
|
27288
27288
|
}
|
|
27289
|
+
async function probeListening(socketPath) {
|
|
27290
|
+
if (!existsSync6(socketPath)) {
|
|
27291
|
+
return false;
|
|
27292
|
+
}
|
|
27293
|
+
try {
|
|
27294
|
+
const connection = await Bun.connect({ unix: socketPath, socket: { data: () => {} } });
|
|
27295
|
+
connection.end();
|
|
27296
|
+
return true;
|
|
27297
|
+
} catch {
|
|
27298
|
+
return false;
|
|
27299
|
+
}
|
|
27300
|
+
}
|
|
27289
27301
|
function probeProcessAlive(pid, socket, procRoot = "/proc") {
|
|
27290
27302
|
const state = readStatFields(pid, procRoot)?.[0];
|
|
27291
27303
|
if (state === undefined || state === "Z" || state === "X") {
|
|
@@ -37572,15 +37584,15 @@ function buildDeferredAdmission() {
|
|
|
37572
37584
|
};
|
|
37573
37585
|
}
|
|
37574
37586
|
|
|
37575
|
-
// packages/daemon/src/platform/process/semaphore.ts
|
|
37576
|
-
function
|
|
37577
|
-
const waiters = [];
|
|
37587
|
+
// packages/daemon/src/platform/process/priority-semaphore.ts
|
|
37588
|
+
function createPrioritySemaphore(limit) {
|
|
37589
|
+
const waiters = { foreground: [], background: [] };
|
|
37578
37590
|
let active = 0;
|
|
37579
37591
|
return {
|
|
37580
|
-
run: async (task) => {
|
|
37592
|
+
run: async (priority, task) => {
|
|
37581
37593
|
if (active >= limit) {
|
|
37582
37594
|
const turn = Promise.withResolvers();
|
|
37583
|
-
waiters.push(turn.resolve);
|
|
37595
|
+
waiters[priority].push(turn.resolve);
|
|
37584
37596
|
await turn.promise;
|
|
37585
37597
|
} else {
|
|
37586
37598
|
active += 1;
|
|
@@ -37588,7 +37600,7 @@ function createSemaphore(limit) {
|
|
|
37588
37600
|
try {
|
|
37589
37601
|
return await task();
|
|
37590
37602
|
} finally {
|
|
37591
|
-
const next = waiters.shift();
|
|
37603
|
+
const next = waiters.foreground.shift() ?? waiters.background.shift();
|
|
37592
37604
|
if (next === undefined) {
|
|
37593
37605
|
active -= 1;
|
|
37594
37606
|
} else {
|
|
@@ -37709,7 +37721,7 @@ var ENFORCE_INTERVAL_MS = 5000;
|
|
|
37709
37721
|
var ENFORCE_TRIGGER = "RAM over budget";
|
|
37710
37722
|
var ORDINARY_TRIGGER = "RAM outside the huge page pool";
|
|
37711
37723
|
function createRamGovernor(deps) {
|
|
37712
|
-
const admission =
|
|
37724
|
+
const admission = createPrioritySemaphore(1);
|
|
37713
37725
|
const reservations = new Map;
|
|
37714
37726
|
const now = deps.now ?? Date.now;
|
|
37715
37727
|
let stuckOver = false;
|
|
@@ -37905,7 +37917,7 @@ function createRamGovernor(deps) {
|
|
|
37905
37917
|
}
|
|
37906
37918
|
};
|
|
37907
37919
|
return {
|
|
37908
|
-
admit: (request) => admission.run(async () => {
|
|
37920
|
+
admit: (request) => admission.run(toTaskPriority(request), async () => {
|
|
37909
37921
|
const hugePagesMib = request.hugePagesMib ?? 0;
|
|
37910
37922
|
const hostBudgets = readBudgets();
|
|
37911
37923
|
const budgets = hugePagesMib > 0 ? { ...hostBudgets, ordinaryMib: null } : hostBudgets;
|
|
@@ -37967,7 +37979,7 @@ function createRamGovernor(deps) {
|
|
|
37967
37979
|
reservations.delete(id);
|
|
37968
37980
|
pendingHugePages.delete(id);
|
|
37969
37981
|
},
|
|
37970
|
-
admitGrow: (request) => admission.run(async () => {
|
|
37982
|
+
admitGrow: (request) => admission.run("background", async () => {
|
|
37971
37983
|
const listed = await deps.listAwake();
|
|
37972
37984
|
if (!listed.some((imp) => imp.id === request.id)) {
|
|
37973
37985
|
return false;
|
|
@@ -38024,7 +38036,7 @@ function createRamGovernor(deps) {
|
|
|
38024
38036
|
headroomMib: usage.headroomMib
|
|
38025
38037
|
};
|
|
38026
38038
|
},
|
|
38027
|
-
enforce: () => admission.run(async () => {
|
|
38039
|
+
enforce: () => admission.run("background", async () => {
|
|
38028
38040
|
const budgets = readBudgets();
|
|
38029
38041
|
const room = await makeRoom({
|
|
38030
38042
|
excludeId: null,
|
|
@@ -38051,6 +38063,9 @@ function createRamGovernor(deps) {
|
|
|
38051
38063
|
function pickEffectiveShortfall(usage, budgets, requestedMib) {
|
|
38052
38064
|
return pickShortfall({ ramMib: usage.effectiveMib, ordinaryMib: usage.ordinaryEffectiveMib }, budgets, requestedMib);
|
|
38053
38065
|
}
|
|
38066
|
+
function toTaskPriority(request) {
|
|
38067
|
+
return request.isBackground === true ? "background" : "foreground";
|
|
38068
|
+
}
|
|
38054
38069
|
|
|
38055
38070
|
// packages/daemon/src/vm/watchdog/agent-watchdog.ts
|
|
38056
38071
|
var HOUR_MS = 3600000;
|
|
@@ -38876,7 +38891,6 @@ function createStorageGate(clock = REAL_CLOCK) {
|
|
|
38876
38891
|
}
|
|
38877
38892
|
|
|
38878
38893
|
// packages/daemon/src/vm/templates/boot-templates.ts
|
|
38879
|
-
import { createHash as createHash4 } from "crypto";
|
|
38880
38894
|
import {
|
|
38881
38895
|
existsSync as existsSync9,
|
|
38882
38896
|
mkdirSync as mkdirSync12,
|
|
@@ -38890,8 +38904,18 @@ import {
|
|
|
38890
38904
|
} from "fs";
|
|
38891
38905
|
import { join as join18 } from "path";
|
|
38892
38906
|
import { withContext as withContext2 } from "@logtape/logtape";
|
|
38907
|
+
import { match as match10 } from "ts-pattern";
|
|
38893
38908
|
import * as z75 from "zod";
|
|
38894
38909
|
|
|
38910
|
+
// packages/daemon/src/platform/process/semaphore.ts
|
|
38911
|
+
function createSemaphore(limit) {
|
|
38912
|
+
const semaphore = createPrioritySemaphore(limit);
|
|
38913
|
+
return { run: (task) => semaphore.run("foreground", task) };
|
|
38914
|
+
}
|
|
38915
|
+
|
|
38916
|
+
// packages/daemon/src/vm/templates/build-template-key.ts
|
|
38917
|
+
import { createHash as createHash4 } from "crypto";
|
|
38918
|
+
|
|
38895
38919
|
// packages/daemon/src/vm/vmm/configure-vm.ts
|
|
38896
38920
|
function createMarks() {
|
|
38897
38921
|
const marks = {};
|
|
@@ -38947,8 +38971,41 @@ async function setupVm(api, config) {
|
|
|
38947
38971
|
await api.putBalloon(VM_DEVICES.balloon);
|
|
38948
38972
|
}
|
|
38949
38973
|
|
|
38974
|
+
// packages/daemon/src/vm/templates/build-template-key.ts
|
|
38975
|
+
var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
|
|
38976
|
+
function buildTemplateKey(identity, shape) {
|
|
38977
|
+
const fields = {
|
|
38978
|
+
guestKernel: identity.guestKernel,
|
|
38979
|
+
systemDrive: identity.systemDrive,
|
|
38980
|
+
firecrackerVersion: identity.firecrackerVersion,
|
|
38981
|
+
snapshotVersion: identity.snapshotVersion,
|
|
38982
|
+
hostKernel: identity.hostKernel,
|
|
38983
|
+
cpuModel: identity.cpuModel,
|
|
38984
|
+
cpuFlags: identity.cpuFlags,
|
|
38985
|
+
bootArgs: TEMPLATE_BOOT_ARGS,
|
|
38986
|
+
devices: VM_DEVICES,
|
|
38987
|
+
memoryMib: shape.memoryMib,
|
|
38988
|
+
vcpus: shape.vcpus,
|
|
38989
|
+
...shape.hugePages === "2M" && { hugePages: shape.hugePages }
|
|
38990
|
+
};
|
|
38991
|
+
return createHash4("sha256").update(JSON.stringify(fields)).digest("hex");
|
|
38992
|
+
}
|
|
38993
|
+
|
|
38994
|
+
// packages/daemon/src/vm/templates/pick-restore-failure-outcome.ts
|
|
38995
|
+
import { match as match7 } from "ts-pattern";
|
|
38996
|
+
function pickRestoreFailureOutcome(facts) {
|
|
38997
|
+
if (!facts.isCurrentBuild) {
|
|
38998
|
+
return "ignore";
|
|
38999
|
+
}
|
|
39000
|
+
return match7(facts.fault).with(null, () => "ignore").with("agent", () => facts.restoredNow > facts.restoredBefore ? "keep" : pickCountedOutcome(facts)).with("load", () => pickCountedOutcome(facts)).exhaustive();
|
|
39001
|
+
}
|
|
39002
|
+
var MAX_RESTORE_FAILURES = 3;
|
|
39003
|
+
function pickCountedOutcome(facts) {
|
|
39004
|
+
return facts.failuresBefore + 1 >= MAX_RESTORE_FAILURES && !facts.isOff ? "disable" : "remove";
|
|
39005
|
+
}
|
|
39006
|
+
|
|
38950
39007
|
// packages/daemon/src/vm/templates/template-locks.ts
|
|
38951
|
-
import { match as
|
|
39008
|
+
import { match as match9 } from "ts-pattern";
|
|
38952
39009
|
|
|
38953
39010
|
// packages/daemon/src/vm/templates/can-admit-lock.ts
|
|
38954
39011
|
var MAX_LOCKED_PERCENT = 10;
|
|
@@ -38959,10 +39016,10 @@ function canAdmitLock(admission) {
|
|
|
38959
39016
|
}
|
|
38960
39017
|
|
|
38961
39018
|
// packages/daemon/src/vm/templates/pick-lock-outcome.ts
|
|
38962
|
-
import { P as P3, match as
|
|
39019
|
+
import { P as P3, match as match8 } from "ts-pattern";
|
|
38963
39020
|
var MIB4 = 1024 * 1024;
|
|
38964
39021
|
function pickLockOutcome(result, isCurrent) {
|
|
38965
|
-
return
|
|
39022
|
+
return match8(result).with({ errno: P3.number }, (failed) => ({ kind: "failed", errno: failed.errno })).with({ declinedBytes: P3.number }, (declined) => ({
|
|
38966
39023
|
kind: "declined",
|
|
38967
39024
|
lockMib: Math.ceil(declined.declinedBytes / MIB4)
|
|
38968
39025
|
})).with({ lock: P3.select() }, (lock) => isCurrent ? { kind: "held", lock, lockMib: Math.ceil(lock.lockedBytes / MIB4) } : { kind: "stale", lock }).exhaustive();
|
|
@@ -38990,7 +39047,7 @@ function createTemplateLocks(deps) {
|
|
|
38990
39047
|
}
|
|
38991
39048
|
return isAdmitted;
|
|
38992
39049
|
});
|
|
38993
|
-
|
|
39050
|
+
match9(pickLockOutcome(result, deps.probeCurrent(target))).with({ kind: "failed" }, (failed) => {
|
|
38994
39051
|
deps.logger.warning("boot template {key} memory not locked (errno {errno}): a restore reads what the host dropped", { key, errno: String(failed.errno) });
|
|
38995
39052
|
}).with({ kind: "declined" }, (declined) => {
|
|
38996
39053
|
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 +39088,6 @@ function createTemplateLocks(deps) {
|
|
|
39031
39088
|
}
|
|
39032
39089
|
|
|
39033
39090
|
// packages/daemon/src/vm/templates/boot-templates.ts
|
|
39034
|
-
var TEMPLATE_BOOT_ARGS = [...BASE_BOOT_ARGS, "imp.template=1"].join(" ");
|
|
39035
39091
|
var TEMPLATE_ADDRESS = {
|
|
39036
39092
|
slot: -1,
|
|
39037
39093
|
tap: "imp-tpl",
|
|
@@ -39052,7 +39108,6 @@ var MIB6 = 1024 * 1024;
|
|
|
39052
39108
|
var MIN_MISSES = 2;
|
|
39053
39109
|
var MAX_TEMPLATES = 4;
|
|
39054
39110
|
var MAX_BUILD_FAILURES = 3;
|
|
39055
|
-
var MAX_RESTORE_FAILURES = 3;
|
|
39056
39111
|
var BUILD_RETRY_MS = 60000;
|
|
39057
39112
|
var TemplateMetaSchema = z75.object({
|
|
39058
39113
|
buildId: z75.string(),
|
|
@@ -39065,23 +39120,6 @@ var TemplateMetaSchema = z75.object({
|
|
|
39065
39120
|
class BuildRefusedError extends Error {
|
|
39066
39121
|
name = "BuildRefusedError";
|
|
39067
39122
|
}
|
|
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
39123
|
function createBootTemplates(deps) {
|
|
39086
39124
|
const root = join18(deps.dataDir, "templates");
|
|
39087
39125
|
const now = deps.now ?? Date.now;
|
|
@@ -39089,6 +39127,7 @@ function createBootTemplates(deps) {
|
|
|
39089
39127
|
const building = createSingleFlight();
|
|
39090
39128
|
const records = new Map;
|
|
39091
39129
|
const lastUsed = new Map;
|
|
39130
|
+
const restoredByBuild = new Map;
|
|
39092
39131
|
const lane = createSemaphore(1);
|
|
39093
39132
|
const getRecord = (key) => {
|
|
39094
39133
|
const found = records.get(key);
|
|
@@ -39262,7 +39301,8 @@ function createBootTemplates(deps) {
|
|
|
39262
39301
|
reserveMib: Math.ceil(shape.memoryMib * deps.bootReservePercent / 100),
|
|
39263
39302
|
memoryMib: shape.memoryMib,
|
|
39264
39303
|
hugePagesMib: shape.hugePages === "2M" ? shape.memoryMib : 0,
|
|
39265
|
-
maySleepImps: false
|
|
39304
|
+
maySleepImps: false,
|
|
39305
|
+
isBackground: true
|
|
39266
39306
|
});
|
|
39267
39307
|
return await (deps.diskBudget === undefined ? writeAdmitted() : deps.diskBudget.withRoom(shape.memoryMib * MIB6, writeAdmitted));
|
|
39268
39308
|
} catch (error) {
|
|
@@ -39345,21 +39385,33 @@ function createBootTemplates(deps) {
|
|
|
39345
39385
|
return null;
|
|
39346
39386
|
},
|
|
39347
39387
|
buildTemplate: createTemplateFiles,
|
|
39348
|
-
reportFailure: (files,
|
|
39349
|
-
if (!isTemplateFault || readMeta(files.key)?.buildId !== files.buildId) {
|
|
39350
|
-
return;
|
|
39351
|
-
}
|
|
39388
|
+
reportFailure: (files, failure) => {
|
|
39352
39389
|
const record = getRecord(files.key);
|
|
39353
|
-
|
|
39354
|
-
|
|
39355
|
-
|
|
39390
|
+
const outcome = pickRestoreFailureOutcome({
|
|
39391
|
+
fault: failure.fault,
|
|
39392
|
+
isCurrentBuild: readMeta(files.key)?.buildId === files.buildId,
|
|
39393
|
+
restoredBefore: failure.restoredBefore,
|
|
39394
|
+
restoredNow: restoredByBuild.get(files.buildId) ?? 0,
|
|
39395
|
+
failuresBefore: record.restoreFailures,
|
|
39396
|
+
isOff: record.isOff
|
|
39397
|
+
});
|
|
39398
|
+
const removeFailed = () => {
|
|
39399
|
+
record.restoreFailures += 1;
|
|
39400
|
+
removeDir(files.key);
|
|
39401
|
+
};
|
|
39402
|
+
match10(outcome).with("ignore", () => {}).with("keep", () => {
|
|
39403
|
+
getImpdLogger("boot").info("boot template {key} kept: its agent timed out while another restore of it succeeded", { key: files.key.slice(0, 12) });
|
|
39404
|
+
}).with("remove", removeFailed).with("disable", () => {
|
|
39405
|
+
removeFailed();
|
|
39356
39406
|
record.isOff = true;
|
|
39357
39407
|
getImpdLogger("boot").warning("boot template {key} off until impd restarts: {restoreFailures} restores failed", { key: files.key.slice(0, 12), restoreFailures: String(record.restoreFailures) });
|
|
39358
|
-
}
|
|
39408
|
+
}).exhaustive();
|
|
39359
39409
|
},
|
|
39360
39410
|
reportRestored: (files) => {
|
|
39361
39411
|
getRecord(files.key).restoreFailures = 0;
|
|
39412
|
+
restoredByBuild.set(files.buildId, (restoredByBuild.get(files.buildId) ?? 0) + 1);
|
|
39362
39413
|
},
|
|
39414
|
+
countRestored: (files) => restoredByBuild.get(files.buildId) ?? 0,
|
|
39363
39415
|
removeStale: () => {
|
|
39364
39416
|
if (!existsSync9(root)) {
|
|
39365
39417
|
return [];
|
|
@@ -43288,18 +43340,18 @@ async function setupRestoreCommand(plan, jails) {
|
|
|
43288
43340
|
}
|
|
43289
43341
|
|
|
43290
43342
|
class TemplateRestoreError extends Error {
|
|
43291
|
-
|
|
43292
|
-
constructor(message,
|
|
43343
|
+
fault;
|
|
43344
|
+
constructor(message, fault, cause) {
|
|
43293
43345
|
super(message, { cause });
|
|
43294
43346
|
this.name = "TemplateRestoreError";
|
|
43295
|
-
this.
|
|
43347
|
+
this.fault = fault;
|
|
43296
43348
|
}
|
|
43297
43349
|
}
|
|
43298
43350
|
async function loadTemplateVm(plan, jails, options = {}) {
|
|
43299
43351
|
const marks = createMarks();
|
|
43300
43352
|
const pid = await startRestoreProcess(plan, jails, options.mergeWrapper ?? null);
|
|
43301
43353
|
marks.setMark("spawn");
|
|
43302
|
-
const step = {
|
|
43354
|
+
const step = { fault: "load" };
|
|
43303
43355
|
try {
|
|
43304
43356
|
const api = (options.createClient ?? createFirecrackerClient)(plan.paths.apiSocket);
|
|
43305
43357
|
const isUffd = plan.hugePages === "2M";
|
|
@@ -43326,14 +43378,15 @@ async function loadTemplateVm(plan, jails, options = {}) {
|
|
|
43326
43378
|
jails.seal(plan.paths, pid);
|
|
43327
43379
|
await api.resume();
|
|
43328
43380
|
marks.setMark("resume");
|
|
43381
|
+
step.fault = "agent";
|
|
43329
43382
|
await waitForAgent(plan.paths.vsockSocket, {
|
|
43330
|
-
deadlineMs: VM_DEADLINES.agentMs,
|
|
43383
|
+
deadlineMs: options.parkedAgentMs ?? VM_DEADLINES.agentMs,
|
|
43331
43384
|
attemptMs: 200,
|
|
43332
43385
|
maxDelayMs: RESTORE_PING_DELAY_MS,
|
|
43333
43386
|
isParked: true
|
|
43334
43387
|
});
|
|
43335
43388
|
marks.setMark("parked");
|
|
43336
|
-
step.
|
|
43389
|
+
step.fault = null;
|
|
43337
43390
|
const diskBytes = await plan.diskReady;
|
|
43338
43391
|
if (plan.jail !== null) {
|
|
43339
43392
|
jails.setupDiskOwner(plan.paths, plan.jail);
|
|
@@ -43368,7 +43421,7 @@ async function loadTemplateVm(plan, jails, options = {}) {
|
|
|
43368
43421
|
await waitForExit(pid, plan.paths.apiSocket, VM_DEADLINES.killMs);
|
|
43369
43422
|
await jails.release(plan.paths.impId);
|
|
43370
43423
|
throw new TemplateRestoreError(`template restore failed: ${reason}
|
|
43371
|
-
${readLogTail(plan.paths.logFile, 5)}`, step.
|
|
43424
|
+
${readLogTail(plan.paths.logFile, 5)}`, step.fault, error);
|
|
43372
43425
|
}
|
|
43373
43426
|
}
|
|
43374
43427
|
|
|
@@ -43398,6 +43451,7 @@ async function startColdVm(context, start) {
|
|
|
43398
43451
|
const diskBytes = readDiskBytes();
|
|
43399
43452
|
Promise.allSettled([diskBytes]);
|
|
43400
43453
|
if (template !== null && template !== undefined) {
|
|
43454
|
+
const restoredBefore = context.templates?.countRestored(template) ?? 0;
|
|
43401
43455
|
try {
|
|
43402
43456
|
const vm = await context.vms.loadTemplateVm({
|
|
43403
43457
|
firecrackerBin: context.config.firecrackerBin,
|
|
@@ -43441,8 +43495,8 @@ async function startColdVm(context, start) {
|
|
|
43441
43495
|
key: template.key.slice(0, 12),
|
|
43442
43496
|
error: getErrorMessage(error)
|
|
43443
43497
|
});
|
|
43444
|
-
const
|
|
43445
|
-
context.templates?.reportFailure(template,
|
|
43498
|
+
const fault = error instanceof TemplateRestoreError ? error.fault : null;
|
|
43499
|
+
context.templates?.reportFailure(template, { fault, restoredBefore });
|
|
43446
43500
|
}
|
|
43447
43501
|
}
|
|
43448
43502
|
await diskBytes;
|
|
@@ -43476,9 +43530,19 @@ function formatGuestSteps(steps) {
|
|
|
43476
43530
|
|
|
43477
43531
|
// packages/daemon/src/imps/imps/imp-vm-ops.ts
|
|
43478
43532
|
var SLEEP_CONCURRENCY = 2;
|
|
43533
|
+
var RESTORE_CONCURRENCY = 4;
|
|
43479
43534
|
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(
|
|
43535
|
+
function createImpVmOps(host) {
|
|
43481
43536
|
const sleepSlots = createSemaphore(SLEEP_CONCURRENCY);
|
|
43537
|
+
const wakeSlots = createSemaphore(RESTORE_CONCURRENCY);
|
|
43538
|
+
const templateSlots = createSemaphore(RESTORE_CONCURRENCY);
|
|
43539
|
+
const context = {
|
|
43540
|
+
...host,
|
|
43541
|
+
vms: {
|
|
43542
|
+
...host.vms,
|
|
43543
|
+
loadTemplateVm: (options) => templateSlots.run(() => host.vms.loadTemplateVm(options))
|
|
43544
|
+
}
|
|
43545
|
+
};
|
|
43482
43546
|
let isStopping = false;
|
|
43483
43547
|
const updateState = async (imp, change) => {
|
|
43484
43548
|
const updated = await updateLockedImpState(context.db, imp, change);
|
|
@@ -43639,7 +43703,7 @@ function createImpVmOps(context) {
|
|
|
43639
43703
|
}
|
|
43640
43704
|
return imp.isDiskGrowPending ? setGrowPending(imp, false) : imp;
|
|
43641
43705
|
};
|
|
43642
|
-
const
|
|
43706
|
+
const startWake = makeWakeImpVm({
|
|
43643
43707
|
context,
|
|
43644
43708
|
isStopping: () => isStopping,
|
|
43645
43709
|
updateState,
|
|
@@ -43648,6 +43712,7 @@ function createImpVmOps(context) {
|
|
|
43648
43712
|
startColdImpVm,
|
|
43649
43713
|
growGuestDisk
|
|
43650
43714
|
});
|
|
43715
|
+
const wakeImpVm = (imp) => wakeSlots.run(() => startWake(imp));
|
|
43651
43716
|
const requireRunningImp = async (imp) => {
|
|
43652
43717
|
if (imp.state === "running") {
|
|
43653
43718
|
return imp;
|
|
@@ -43703,7 +43768,7 @@ function countStepsMs(timings) {
|
|
|
43703
43768
|
}
|
|
43704
43769
|
|
|
43705
43770
|
// packages/daemon/src/imps/imps/services/service-api.ts
|
|
43706
|
-
import { match as
|
|
43771
|
+
import { match as match11 } from "ts-pattern";
|
|
43707
43772
|
|
|
43708
43773
|
// packages/daemon/src/imps/imps/services/create-log-decoders.ts
|
|
43709
43774
|
function createLogDecoders() {
|
|
@@ -43927,7 +43992,7 @@ function handleAgentError(service) {
|
|
|
43927
43992
|
if (!(error instanceof AgentError)) {
|
|
43928
43993
|
throw error;
|
|
43929
43994
|
}
|
|
43930
|
-
throw
|
|
43995
|
+
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
43996
|
};
|
|
43932
43997
|
}
|
|
43933
43998
|
function stopStreams(streams) {
|
|
@@ -45028,28 +45093,29 @@ function createVmRunner(jails, options = {}) {
|
|
|
45028
45093
|
setMark("spawn");
|
|
45029
45094
|
try {
|
|
45030
45095
|
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
|
|
45096
|
+
const configure = async () => {
|
|
45097
|
+
await setupVm(api, {
|
|
45098
|
+
kernelPath: plan.kernelPath,
|
|
45099
|
+
bootArgs: buildBootArgs(plan),
|
|
45100
|
+
vcpus: plan.vcpus,
|
|
45101
|
+
memoryMib: plan.memoryMib,
|
|
45102
|
+
hugePages: plan.hugePages ?? "None",
|
|
45103
|
+
diskPath: plan.paths.disk,
|
|
45104
|
+
systemDrivePath: plan.systemDrivePath,
|
|
45105
|
+
vsockPath: plan.paths.vsockSocket,
|
|
45106
|
+
tap: plan.address.tap,
|
|
45107
|
+
guestMac: plan.address.guestMac
|
|
45050
45108
|
});
|
|
45051
|
-
|
|
45052
|
-
|
|
45109
|
+
const regionMib = getRegionMib(plan.memoryMib, plan.maxMemoryMib);
|
|
45110
|
+
if (regionMib > 0) {
|
|
45111
|
+
await api.putHotplugMemory({
|
|
45112
|
+
totalSizeMib: regionMib,
|
|
45113
|
+
slotSizeMib: SLOT_MIB,
|
|
45114
|
+
blockSizeMib: BLOCK_MIB
|
|
45115
|
+
});
|
|
45116
|
+
}
|
|
45117
|
+
};
|
|
45118
|
+
const [firecrackerVersion] = await Promise.all([api.readVersion(), configure()]);
|
|
45053
45119
|
setMark("configure");
|
|
45054
45120
|
jails.removeUffdDevice(plan.paths.impId);
|
|
45055
45121
|
jails.seal(plan.paths, pid);
|
|
@@ -46884,7 +46950,7 @@ function readSettled2(row, imp) {
|
|
|
46884
46950
|
|
|
46885
46951
|
// packages/daemon/src/imp-ops/moves/move-sender.ts
|
|
46886
46952
|
import invariant6 from "tiny-invariant";
|
|
46887
|
-
import { match as
|
|
46953
|
+
import { match as match12 } from "ts-pattern";
|
|
46888
46954
|
|
|
46889
46955
|
// packages/daemon/src/platform/db/move-send-steps.ts
|
|
46890
46956
|
var MOVE_SEND_STEPS = [
|
|
@@ -47447,7 +47513,7 @@ function createMoveSender(deps) {
|
|
|
47447
47513
|
};
|
|
47448
47514
|
const recoverSend = async (imp, row) => {
|
|
47449
47515
|
const log = getImpdLogger("move").with({ imp: imp.name });
|
|
47450
|
-
await
|
|
47516
|
+
await match12(pickSendRecovery(imp.moveState, row?.step)).with("unmark", () => removeMark(imp)).with("wait", () => {
|
|
47451
47517
|
log.info("a prepared send waits for its send after a restart");
|
|
47452
47518
|
}).with("abort", async () => {
|
|
47453
47519
|
const isUndone = await resetSend(imp, row?.peer_url ?? null, row?.ticket ?? null);
|
|
@@ -47569,7 +47635,7 @@ function buildPublicError(name) {
|
|
|
47569
47635
|
}
|
|
47570
47636
|
function pickSendRecovery(moveState, step) {
|
|
47571
47637
|
if (moveState === "sending") {
|
|
47572
|
-
return
|
|
47638
|
+
return match12(step).with(undefined, () => "unmark").with("prepared", () => "wait").with("streaming", () => "abort").with("sent", () => "commit").exhaustive();
|
|
47573
47639
|
}
|
|
47574
47640
|
return moveState === "moved" && step === "sent" ? "commit" : "none";
|
|
47575
47641
|
}
|
|
@@ -47621,7 +47687,7 @@ function createMoveService(deps) {
|
|
|
47621
47687
|
// packages/daemon/src/surface/moves/create-move-peer-handler.ts
|
|
47622
47688
|
import { OpenAPIHandler } from "@orpc/openapi/fetch";
|
|
47623
47689
|
import { ORPCError as ORPCError7, implement as implement3, onError as onError3 } from "@orpc/server";
|
|
47624
|
-
import { match as
|
|
47690
|
+
import { match as match13 } from "ts-pattern";
|
|
47625
47691
|
function createMovePeerHandler(deps) {
|
|
47626
47692
|
const withTicket = async (step, context, call) => {
|
|
47627
47693
|
const startedAt = deps.now();
|
|
@@ -47672,7 +47738,7 @@ function createMovePeerHandler(deps) {
|
|
|
47672
47738
|
const respondReceive = async (context) => {
|
|
47673
47739
|
try {
|
|
47674
47740
|
const received = await withTicket("receive", context, (checked) => deps.calls.receive(checked, context.request));
|
|
47675
|
-
return
|
|
47741
|
+
return match13(received).with({ kind: "part" }, (sent) => Response.json({ part: sent.part }, { status: 202 })).with({ kind: "receipt" }, (sent) => Response.json(sent.receipt)).exhaustive();
|
|
47676
47742
|
} catch (error) {
|
|
47677
47743
|
const failure = toPeerError(error);
|
|
47678
47744
|
return Response.json(encodePeerError(failure), { status: failure.status });
|
|
@@ -51932,14 +51998,14 @@ function expandScopes(asked) {
|
|
|
51932
51998
|
}
|
|
51933
51999
|
|
|
51934
52000
|
// packages/daemon/src/platform/http/render-html.ts
|
|
51935
|
-
import { P as P4, match as
|
|
52001
|
+
import { P as P4, match as match14 } from "ts-pattern";
|
|
51936
52002
|
var MARKUP = Symbol("markup");
|
|
51937
52003
|
function renderHTML(strings, ...parts) {
|
|
51938
52004
|
const html = parts.reduce((text, part, index) => `${text}${toHTML(part)}${strings[index + 1] ?? ""}`, strings[0] ?? "");
|
|
51939
52005
|
return { [MARKUP]: true, html };
|
|
51940
52006
|
}
|
|
51941
52007
|
function toHTML(part) {
|
|
51942
|
-
return
|
|
52008
|
+
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
52009
|
}
|
|
51944
52010
|
function escapeHTML(text) {
|
|
51945
52011
|
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -58626,15 +58692,19 @@ log tail`);
|
|
|
58626
58692
|
loadTemplateVm: (plan) => runInGeneration(async () => {
|
|
58627
58693
|
restorePlans.push(plan);
|
|
58628
58694
|
const vm = await startFakeVm("restore", plan.paths).catch((error) => {
|
|
58629
|
-
throw new TemplateRestoreError("restore failed",
|
|
58695
|
+
throw new TemplateRestoreError("restore failed", "load", error);
|
|
58630
58696
|
});
|
|
58697
|
+
if (await chooseOutcome("parkedAgent") !== "ok") {
|
|
58698
|
+
alive.delete(vm.pid);
|
|
58699
|
+
throw new TemplateRestoreError("the parked agent never answered", "agent", null);
|
|
58700
|
+
}
|
|
58631
58701
|
await plan.diskReady.catch((error) => {
|
|
58632
58702
|
alive.delete(vm.pid);
|
|
58633
|
-
throw new TemplateRestoreError("disk failed",
|
|
58703
|
+
throw new TemplateRestoreError("disk failed", null, error);
|
|
58634
58704
|
});
|
|
58635
58705
|
if (await chooseOutcome("claim") !== "ok") {
|
|
58636
58706
|
alive.delete(vm.pid);
|
|
58637
|
-
throw new TemplateRestoreError("claim failed",
|
|
58707
|
+
throw new TemplateRestoreError("claim failed", null, null);
|
|
58638
58708
|
}
|
|
58639
58709
|
restores.push({
|
|
58640
58710
|
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.2.1",
|
|
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.2.1"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"bun": ">=1.4.3"
|