@vm0/runner 3.9.4 → 3.10.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/index.js +61 -33
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1352,12 +1352,15 @@ var TapPool = class {
|
|
|
1352
1352
|
}
|
|
1353
1353
|
const guestMac = generateMacAddress(vmId);
|
|
1354
1354
|
try {
|
|
1355
|
-
await
|
|
1355
|
+
await Promise.all([
|
|
1356
|
+
this.config.setMac(resource.tapDevice, guestMac),
|
|
1357
|
+
clearArpEntry(resource.guestIp)
|
|
1358
|
+
]);
|
|
1356
1359
|
} catch (err) {
|
|
1357
1360
|
if (fromPool) {
|
|
1358
1361
|
this.queue.push(resource);
|
|
1359
1362
|
logger4.log(
|
|
1360
|
-
`Returned pair to pool after MAC
|
|
1363
|
+
`Returned pair to pool after MAC/ARP failure: ${resource.tapDevice}`
|
|
1361
1364
|
);
|
|
1362
1365
|
} else {
|
|
1363
1366
|
await releaseIP(resource.guestIp).catch(() => {
|
|
@@ -1367,14 +1370,11 @@ var TapPool = class {
|
|
|
1367
1370
|
}
|
|
1368
1371
|
throw err;
|
|
1369
1372
|
}
|
|
1370
|
-
|
|
1371
|
-
try {
|
|
1372
|
-
await assignVmIdToIP(resource.guestIp, vmId);
|
|
1373
|
-
} catch (err) {
|
|
1373
|
+
assignVmIdToIP(resource.guestIp, vmId).catch((err) => {
|
|
1374
1374
|
logger4.error(
|
|
1375
1375
|
`Failed to assign vmId to IP registry: ${err instanceof Error ? err.message : "Unknown"}`
|
|
1376
1376
|
);
|
|
1377
|
-
}
|
|
1377
|
+
});
|
|
1378
1378
|
logger4.log(
|
|
1379
1379
|
`Acquired: TAP ${resource.tapDevice}, MAC ${guestMac}, IP ${resource.guestIp}`
|
|
1380
1380
|
);
|
|
@@ -1695,12 +1695,9 @@ var FirecrackerVM = class {
|
|
|
1695
1695
|
/**
|
|
1696
1696
|
* Stop the VM
|
|
1697
1697
|
*
|
|
1698
|
-
* Note: With --no-api mode, we
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
* TODO(#2118): Implement graceful shutdown via vsock command to guest agent.
|
|
1702
|
-
* This would allow the guest to clean up before termination without
|
|
1703
|
-
* adding the startup latency of API mode.
|
|
1698
|
+
* Note: With --no-api mode, we force kill the Firecracker process.
|
|
1699
|
+
* Graceful shutdown (filesystem sync) should be done via vsock
|
|
1700
|
+
* before calling this method.
|
|
1704
1701
|
*/
|
|
1705
1702
|
async stop() {
|
|
1706
1703
|
if (this.state !== "running") {
|
|
@@ -1794,6 +1791,8 @@ var MSG_WRITE_FILE = 5;
|
|
|
1794
1791
|
var MSG_SPAWN_WATCH = 7;
|
|
1795
1792
|
var MSG_SPAWN_WATCH_RESULT = 8;
|
|
1796
1793
|
var MSG_PROCESS_EXIT = 9;
|
|
1794
|
+
var MSG_SHUTDOWN = 10;
|
|
1795
|
+
var MSG_SHUTDOWN_ACK = 11;
|
|
1797
1796
|
var MSG_ERROR = 255;
|
|
1798
1797
|
var FLAG_SUDO = 1;
|
|
1799
1798
|
function encode(type, seq, payload = Buffer.alloc(0)) {
|
|
@@ -2295,6 +2294,30 @@ var VsockClient = class {
|
|
|
2295
2294
|
getVsockPath() {
|
|
2296
2295
|
return this.vsockPath;
|
|
2297
2296
|
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Request graceful shutdown from guest
|
|
2299
|
+
*
|
|
2300
|
+
* Sends shutdown command to guest agent, which syncs filesystems
|
|
2301
|
+
* and returns acknowledgment. Falls back gracefully on timeout.
|
|
2302
|
+
*
|
|
2303
|
+
* @param timeoutMs Maximum time to wait for acknowledgment (default: 2000ms)
|
|
2304
|
+
* @returns true if guest acknowledged, false on timeout/error
|
|
2305
|
+
*/
|
|
2306
|
+
async shutdown(timeoutMs = 2e3) {
|
|
2307
|
+
if (!this.connected || !this.socket) {
|
|
2308
|
+
return false;
|
|
2309
|
+
}
|
|
2310
|
+
try {
|
|
2311
|
+
const response = await this.request(
|
|
2312
|
+
MSG_SHUTDOWN,
|
|
2313
|
+
Buffer.alloc(0),
|
|
2314
|
+
timeoutMs
|
|
2315
|
+
);
|
|
2316
|
+
return response.type === MSG_SHUTDOWN_ACK;
|
|
2317
|
+
} catch {
|
|
2318
|
+
return false;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2298
2321
|
/**
|
|
2299
2322
|
* Close the connection
|
|
2300
2323
|
*/
|
|
@@ -10033,6 +10056,7 @@ async function executeJob(context, config, options = {}) {
|
|
|
10033
10056
|
const vmId = getVmIdFromRunId(context.runId);
|
|
10034
10057
|
let vm = null;
|
|
10035
10058
|
let guestIp = null;
|
|
10059
|
+
let vsockClient = null;
|
|
10036
10060
|
logger10.log(`Starting job ${context.runId} in VM ${vmId}`);
|
|
10037
10061
|
try {
|
|
10038
10062
|
const vmConfig = {
|
|
@@ -10053,14 +10077,12 @@ async function executeJob(context, config, options = {}) {
|
|
|
10053
10077
|
}
|
|
10054
10078
|
logger10.log(`VM ${vmId} started, guest IP: ${guestIp}`);
|
|
10055
10079
|
const vsockPath = vm.getVsockPath();
|
|
10056
|
-
|
|
10080
|
+
vsockClient = new VsockClient(vsockPath);
|
|
10081
|
+
const guest = vsockClient;
|
|
10057
10082
|
logger10.log(`Using vsock for guest communication: ${vsockPath}`);
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
"guest_wait",
|
|
10061
|
-
() => guest.waitForGuestConnection(3e4)
|
|
10083
|
+
const envJson = JSON.stringify(
|
|
10084
|
+
buildEnvironmentVariables(context, config.server.url)
|
|
10062
10085
|
);
|
|
10063
|
-
logger10.log(`Guest client ready`);
|
|
10064
10086
|
const firewallConfig = context.experimentalFirewall;
|
|
10065
10087
|
if (firewallConfig?.enabled) {
|
|
10066
10088
|
const mitmEnabled = firewallConfig.experimental_mitm ?? false;
|
|
@@ -10068,19 +10090,18 @@ async function executeJob(context, config, options = {}) {
|
|
|
10068
10090
|
logger10.log(
|
|
10069
10091
|
`Setting up network security for VM ${guestIp} (mitm=${mitmEnabled}, sealSecrets=${sealSecretsEnabled})`
|
|
10070
10092
|
);
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
context.sandboxToken,
|
|
10076
|
-
{
|
|
10077
|
-
firewallRules: firewallConfig?.rules,
|
|
10078
|
-
mitmEnabled,
|
|
10079
|
-
sealSecretsEnabled
|
|
10080
|
-
}
|
|
10081
|
-
);
|
|
10093
|
+
getVMRegistry().register(guestIp, context.runId, context.sandboxToken, {
|
|
10094
|
+
firewallRules: firewallConfig?.rules,
|
|
10095
|
+
mitmEnabled,
|
|
10096
|
+
sealSecretsEnabled
|
|
10082
10097
|
});
|
|
10083
10098
|
}
|
|
10099
|
+
logger10.log(`Waiting for guest connection...`);
|
|
10100
|
+
await withSandboxTiming(
|
|
10101
|
+
"guest_wait",
|
|
10102
|
+
() => guest.waitForGuestConnection(3e4)
|
|
10103
|
+
);
|
|
10104
|
+
logger10.log(`Guest client ready`);
|
|
10084
10105
|
if (context.storageManifest) {
|
|
10085
10106
|
await withSandboxTiming(
|
|
10086
10107
|
"storage_download",
|
|
@@ -10098,8 +10119,6 @@ async function executeJob(context, config, options = {}) {
|
|
|
10098
10119
|
)
|
|
10099
10120
|
);
|
|
10100
10121
|
}
|
|
10101
|
-
const envVars = buildEnvironmentVariables(context, config.server.url);
|
|
10102
|
-
const envJson = JSON.stringify(envVars);
|
|
10103
10122
|
logger10.log(
|
|
10104
10123
|
`Writing env JSON (${envJson.length} bytes) to ${ENV_JSON_PATH}`
|
|
10105
10124
|
);
|
|
@@ -10196,6 +10215,15 @@ async function executeJob(context, config, options = {}) {
|
|
|
10196
10215
|
}
|
|
10197
10216
|
}
|
|
10198
10217
|
if (vm) {
|
|
10218
|
+
if (vsockClient) {
|
|
10219
|
+
const acked = await vsockClient.shutdown(2e3);
|
|
10220
|
+
if (acked) {
|
|
10221
|
+
logger10.log(`Guest acknowledged shutdown`);
|
|
10222
|
+
} else {
|
|
10223
|
+
logger10.log(`Guest shutdown timeout, proceeding with SIGKILL`);
|
|
10224
|
+
}
|
|
10225
|
+
vsockClient.close();
|
|
10226
|
+
}
|
|
10199
10227
|
logger10.log(`Cleaning up VM ${vmId}...`);
|
|
10200
10228
|
await withSandboxTiming("cleanup", () => vm.kill());
|
|
10201
10229
|
}
|
|
@@ -11232,7 +11260,7 @@ var benchmarkCommand = new Command4("benchmark").description(
|
|
|
11232
11260
|
});
|
|
11233
11261
|
|
|
11234
11262
|
// src/index.ts
|
|
11235
|
-
var version = true ? "3.
|
|
11263
|
+
var version = true ? "3.10.0" : "0.1.0";
|
|
11236
11264
|
program.name("vm0-runner").version(version).description("Self-hosted runner for VM0 agents");
|
|
11237
11265
|
program.addCommand(startCommand);
|
|
11238
11266
|
program.addCommand(doctorCommand);
|