rightsize 0.7.8 → 0.7.10
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/backend-docker/backend.d.ts +4 -1
- package/dist/backend-docker/backend.d.ts.map +1 -1
- package/dist/backend-docker/backend.js +4 -0
- package/dist/backend-docker/backend.js.map +1 -1
- package/dist/backend-msb/backend.d.ts +618 -68
- package/dist/backend-msb/backend.d.ts.map +1 -1
- package/dist/backend-msb/backend.js +1236 -133
- package/dist/backend-msb/backend.js.map +1 -1
- package/dist/backend-msb/commands.d.ts +160 -16
- package/dist/backend-msb/commands.d.ts.map +1 -1
- package/dist/backend-msb/commands.js +197 -31
- package/dist/backend-msb/commands.js.map +1 -1
- package/dist/backend-msb/provisioner.d.ts +1 -1
- package/dist/backend-msb/provisioner.d.ts.map +1 -1
- package/dist/backend-msb/provisioner.js +1 -1
- package/dist/backend-msb/provisioner.js.map +1 -1
- package/dist/backend-msb/restore-access-denied.d.ts +50 -0
- package/dist/backend-msb/restore-access-denied.d.ts.map +1 -0
- package/dist/backend-msb/restore-access-denied.js +52 -0
- package/dist/backend-msb/restore-access-denied.js.map +1 -0
- package/dist/backend-msb/restore-broker.d.ts +181 -0
- package/dist/backend-msb/restore-broker.d.ts.map +1 -0
- package/dist/backend-msb/restore-broker.js +264 -0
- package/dist/backend-msb/restore-broker.js.map +1 -0
- package/dist/backend-msb/sandbox-already-exists.d.ts +32 -0
- package/dist/backend-msb/sandbox-already-exists.d.ts.map +1 -0
- package/dist/backend-msb/sandbox-already-exists.js +35 -0
- package/dist/backend-msb/sandbox-already-exists.js.map +1 -0
- package/dist/backend-msb/snapshot-create.d.ts +22 -0
- package/dist/backend-msb/snapshot-create.d.ts.map +1 -0
- package/dist/backend-msb/snapshot-create.js +33 -0
- package/dist/backend-msb/snapshot-create.js.map +1 -0
- package/dist/backend-msb/snapshot-import.d.ts +30 -14
- package/dist/backend-msb/snapshot-import.d.ts.map +1 -1
- package/dist/backend-msb/snapshot-import.js +35 -17
- package/dist/backend-msb/snapshot-import.js.map +1 -1
- package/dist/backend-msb/snapshot-rm.d.ts +28 -0
- package/dist/backend-msb/snapshot-rm.d.ts.map +1 -0
- package/dist/backend-msb/snapshot-rm.js +30 -0
- package/dist/backend-msb/snapshot-rm.js.map +1 -0
- package/dist/core/backend.d.ts +53 -22
- package/dist/core/backend.d.ts.map +1 -1
- package/dist/core/checkpoint/api.d.ts +2 -2
- package/dist/core/checkpoint/api.js +2 -2
- package/dist/core/checkpoint/registry.d.ts +34 -0
- package/dist/core/checkpoint/registry.d.ts.map +1 -1
- package/dist/core/checkpoint/registry.js +20 -1
- package/dist/core/checkpoint/registry.js.map +1 -1
- package/dist/core/errors.d.ts +49 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +61 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/generic-container.d.ts +58 -10
- package/dist/core/generic-container.d.ts.map +1 -1
- package/dist/core/generic-container.js +222 -27
- package/dist/core/generic-container.js.map +1 -1
- package/dist/core/model.d.ts +3 -2
- package/dist/core/model.d.ts.map +1 -1
- package/dist/core/sandbox-name.d.ts +3 -0
- package/dist/core/sandbox-name.d.ts.map +1 -0
- package/dist/core/sandbox-name.js +19 -0
- package/dist/core/sandbox-name.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/minio.d.ts +5 -3
- package/dist/modules/minio.d.ts.map +1 -1
- package/dist/modules/minio.js +14 -4
- package/dist/modules/minio.js.map +1 -1
- package/package.json +1 -1
- package/dist/backend-msb/snapshot-list.d.ts +0 -43
- package/dist/backend-msb/snapshot-list.d.ts.map +0 -1
- package/dist/backend-msb/snapshot-list.js +0 -53
- package/dist/backend-msb/snapshot-list.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SandboxBackend, SandboxHandle, FollowHandle, NetworkLink, ReaperKillCommand, BackendCapabilities } from "../core/backend.js";
|
|
2
2
|
import type { ContainerSpec, ExecResult } from "../core/model.js";
|
|
3
|
+
import { type RestoreBrokerLauncher } from "./restore-broker.js";
|
|
3
4
|
/**
|
|
4
5
|
* True if `output` (an `msb run` invocation's combined output) is msb
|
|
5
6
|
* refusing to run anything while its internal install lock is held. Captured
|
|
@@ -23,6 +24,25 @@ import type { ContainerSpec, ExecResult } from "../core/model.js";
|
|
|
23
24
|
* timestamp varies per occurrence.
|
|
24
25
|
*/
|
|
25
26
|
export declare function isMsbInstallLockActive(output: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Constructor options for {@link MsbCliBackend}. Every field is optional so
|
|
29
|
+
* every existing `new MsbCliBackend(msbPathPromise)` call site keeps
|
|
30
|
+
* compiling and behaving unchanged.
|
|
31
|
+
*/
|
|
32
|
+
export interface MsbCliBackendOptions {
|
|
33
|
+
/**
|
|
34
|
+
* The launcher POLICY v2's broker escalation calls through for every
|
|
35
|
+
* brokered restore attempt (see `RestoreLaunchMode`/`restore-broker.ts`).
|
|
36
|
+
* Defaults to `realRestoreBroker`, the real `powershell.exe`/WMI
|
|
37
|
+
* implementation — production code never needs to pass this. Tests inject
|
|
38
|
+
* a fake here instead of driving a real `powershell.exe`, the same role
|
|
39
|
+
* `checkpointRebootAlreadyExistsRetryBudgetMs` already plays for the
|
|
40
|
+
* retry budget itself, just as a constructor option rather than an
|
|
41
|
+
* unsafe-cast field, since production code (not only tests) needs a way to
|
|
42
|
+
* supply it.
|
|
43
|
+
*/
|
|
44
|
+
restoreBroker?: RestoreBrokerLauncher;
|
|
45
|
+
}
|
|
26
46
|
/**
|
|
27
47
|
* The attached-mode CLI driver: every sandbox this backend starts runs as a
|
|
28
48
|
* held child process (`msb run`, no `-d`) because detached mode never
|
|
@@ -50,7 +70,22 @@ export declare class MsbCliBackend implements SandboxBackend {
|
|
|
50
70
|
private readonly handles;
|
|
51
71
|
private readonly startedNames;
|
|
52
72
|
private resolvedMsbPath;
|
|
53
|
-
|
|
73
|
+
private checkpointRebootAlreadyExistsRetryBudgetMs;
|
|
74
|
+
private checkpointRebootAlreadyExistsRetryDelayMs;
|
|
75
|
+
private readonly restoreBroker;
|
|
76
|
+
private platformOverrideForTests;
|
|
77
|
+
constructor(msbPathPromise: Promise<string>, options?: MsbCliBackendOptions);
|
|
78
|
+
/**
|
|
79
|
+
* Whether this process should treat itself as running on Windows for
|
|
80
|
+
* POLICY v2's broker-escalation gate — `process.platform === "win32"` in
|
|
81
|
+
* production, or `platformOverrideForTests` when a test has set it. The
|
|
82
|
+
* ONLY thing this gates is whether `rebootUnderFreshName`/
|
|
83
|
+
* `retryRestoreAfterAccessDenied` ever set `RestoreLaunchMode` to
|
|
84
|
+
* `"broker"`; `bootOnce`/`bootRestoreOnce` themselves stay platform-
|
|
85
|
+
* agnostic dispatchers on whatever mode they're handed, so the broker path
|
|
86
|
+
* itself is exercisable on any host once a test picks it explicitly.
|
|
87
|
+
*/
|
|
88
|
+
private isWindowsPlatform;
|
|
54
89
|
private msbPath;
|
|
55
90
|
create(spec: ContainerSpec): Promise<SandboxHandle>;
|
|
56
91
|
/**
|
|
@@ -88,34 +123,288 @@ export declare class MsbCliBackend implements SandboxBackend {
|
|
|
88
123
|
* knows: the install-lock poll, the one-shot state-database retry, and the
|
|
89
124
|
* one-shot image-cache heal. Both the ordinary `start()` path and the
|
|
90
125
|
* checkpoint cycle's post-snapshot reboot come through here — a reboot
|
|
91
|
-
* from a snapshot
|
|
92
|
-
*
|
|
93
|
-
*
|
|
126
|
+
* from a snapshot (now `msb restore ...`, see `bootRestoreOnce`) is
|
|
127
|
+
* as exposed to msb's transients as any other boot, and skipping the
|
|
128
|
+
* classification there turned a passing install-lock poll into an
|
|
129
|
+
* immediate checkpoint failure on a live Windows run. The image-cache heal
|
|
130
|
+
* targets `handle.spec.image`, which for a restore is the checkpoint ref
|
|
131
|
+
* GenericContainer.fromCheckpoint() threaded through as the builder's
|
|
132
|
+
* "image" — never a real OCI reference `msb image remove` can act on
|
|
133
|
+
* meaningfully; this predates the 0.7.1 migration (the same was true of
|
|
134
|
+
* `--from-snapshot` boots) and is unchanged here.
|
|
135
|
+
*
|
|
136
|
+
* `retryAccessDenied` (default `true`) gates ONLY the
|
|
137
|
+
* `RestoreAccessDeniedError` branch below — every other classified retry
|
|
138
|
+
* always runs regardless. The ordinary `start()` path (including a plain
|
|
139
|
+
* `GenericContainer.fromCheckpoint(cp).start()` restore) leaves it at the
|
|
140
|
+
* default, delegating to `retryRestoreAfterAccessDenied` — see that
|
|
141
|
+
* method's own doc for why it, like `createCheckpoint`'s own reboot, must
|
|
142
|
+
* mint a fresh name for every retry rather than reusing the one that just
|
|
143
|
+
* failed: the live-verified dossier this backend's fresh-naming behavior
|
|
144
|
+
* is built from says a restore that fails PAST msb's own artifact
|
|
145
|
+
* validation (this access-denied signature included) leaves the attempted
|
|
146
|
+
* name behind as a stopped sandbox record, so a same-name retry only ever
|
|
147
|
+
* collides with that record instead of retrying the actual transient.
|
|
148
|
+
* `createCheckpoint`'s own reboot (`rebootUnderFreshName`) passes `false`:
|
|
149
|
+
* it owns this error class itself, one layer up, via its own fresh-naming
|
|
150
|
+
* loop, and needs this error to propagate on the first hit rather than
|
|
151
|
+
* being retried here at all.
|
|
152
|
+
*
|
|
153
|
+
* `restoreLaunchMode` (default `"direct"`) is threaded straight down to
|
|
154
|
+
* every `bootOnce` call this method makes, including its own internal
|
|
155
|
+
* install-lock/state-db/image-cache retries — POLICY v2's broker
|
|
156
|
+
* escalation, once triggered by the caller (`rebootUnderFreshName`/
|
|
157
|
+
* `retryRestoreAfterAccessDenied`), applies to EVERY remaining attempt of
|
|
158
|
+
* that reboot, not just the ones those callers themselves loop over, so an
|
|
159
|
+
* install-lock hit mid-brokered-attempt must retry brokered too rather
|
|
160
|
+
* than silently reverting to a direct spawn. See `RestoreLaunchMode`'s own
|
|
161
|
+
* doc.
|
|
94
162
|
*/
|
|
95
163
|
private bootClassified;
|
|
96
164
|
/**
|
|
97
|
-
* One boot attempt
|
|
98
|
-
*
|
|
99
|
-
* `
|
|
165
|
+
* One boot attempt, dispatched by shape: an ordinary spec (no
|
|
166
|
+
* `checkpointRef`) drives `run()`'s ATTACHED supervision model
|
|
167
|
+
* (`bootRunOnce`); a checkpoint-restore spec drives `msb restore`'s
|
|
168
|
+
* fundamentally different DETACHED shape (`bootRestoreOnce`) — see each
|
|
169
|
+
* method's own doc. Resets the per-attempt diagnostics tail before either:
|
|
170
|
+
* a retried boot must not blend its tail with the failed attempt's.
|
|
171
|
+
* `restoreLaunchMode` is passed straight through to `bootRestoreOnce`
|
|
172
|
+
* (irrelevant to `bootRunOnce`'s own ordinary `run` path, which never
|
|
173
|
+
* brokers — see `RestoreLaunchMode`'s own doc).
|
|
174
|
+
*/
|
|
175
|
+
private bootOnce;
|
|
176
|
+
/**
|
|
177
|
+
* One RUN boot attempt: spawns ATTACHED `msb run`, which stays alive as
|
|
178
|
+
* the sandbox's own supervisor for its whole lifetime, and polls
|
|
179
|
+
* `msb ls --format json` until the name shows Running.
|
|
180
|
+
*
|
|
181
|
+
* `state.attached` and (for non-keepAlive specs) `startedNames` are
|
|
182
|
+
* populated only on success; on any failure the child is reaped here (for
|
|
100
183
|
* the classified early-exit failures it has already exited; a readiness
|
|
101
184
|
* timeout leaves it alive and it is hard-killed) so a failed attempt leaves
|
|
102
185
|
* no live process or registered cleanup state behind — the caller owns
|
|
103
|
-
* retry policy, never cleanup (an exit-0
|
|
104
|
-
* exception:
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
186
|
+
* retry policy, never cleanup (an exit-0 success without a live child is
|
|
187
|
+
* the one exception: the fast-exit case below, see `isCompletedFastExit`'s
|
|
188
|
+
* own doc). An early exit is classified from the child's combined output:
|
|
189
|
+
* the image-cache-corruption signature throws `ImageCacheCorruptionError`
|
|
190
|
+
* (the one failure `start()` heals and retries), a host-port bind conflict
|
|
191
|
+
* throws `PortBindConflictError`, and otherwise — if the exit code was 0 —
|
|
192
|
+
* `isCompletedFastExit` gets a chance to reclassify the exit as a workload
|
|
193
|
+
* that ran to completion before this poll loop could ever observe Running
|
|
194
|
+
* (msb 0.6.16's convergent-lifecycle rework; see that method's own doc);
|
|
195
|
+
* anything else surfaces the raw output as an ordinary boot failure,
|
|
196
|
+
* unchanged.
|
|
114
197
|
*/
|
|
115
|
-
private
|
|
198
|
+
private bootRunOnce;
|
|
199
|
+
/**
|
|
200
|
+
* One RESTORE boot attempt. `msb restore` is not `run`'s attached
|
|
201
|
+
* supervision model at all — EMPIRICALLY VERIFIED against msb 0.7.1 (its
|
|
202
|
+
* own `restore.rs` doc: "Restore a snapshot into a new detached
|
|
203
|
+
* sandbox"), the restore CLI process exits — typically within seconds,
|
|
204
|
+
* often with little or no stdout — the INSTANT activation succeeds, while
|
|
205
|
+
* the sandbox keeps booting in the background and only reaches Running
|
|
206
|
+
* some time after that (live-confirmed: `msb ls` shows Running, and exec
|
|
207
|
+
* works, only once the restore process has already exited). A clean exit
|
|
208
|
+
* is therefore not itself a completed boot, and a nonzero exit is msb's
|
|
209
|
+
* own failure signal with the reason on stdout/stderr — the inverse of
|
|
210
|
+
* `run`'s attached child, whose exit before Running always means failure.
|
|
211
|
+
*
|
|
212
|
+
* Two phases follow directly from those two facts:
|
|
213
|
+
* 1. Spawn `msb restore ...` and wait for IT to exit, bounded by
|
|
214
|
+
* `FIRST_RUN_PULL_TIMEOUT_MS` — the same boot budget `bootRunOnce`
|
|
215
|
+
* polls against; a restore's own activation is exposed to the same
|
|
216
|
+
* "this could be a slow cold operation" uncertainty a pull is.
|
|
217
|
+
* Classified from the combined output the same way any boot's early
|
|
218
|
+
* exit is (install-lock, state-db, image-cache-corruption,
|
|
219
|
+
* port-bind-conflict — see `bootClassified`'s own doc on why a
|
|
220
|
+
* restore boot needs the identical classification an ordinary one
|
|
221
|
+
* gets) — a nonzero, unclassified exit throws an ordinary
|
|
222
|
+
* `BackendError` carrying the output, same shape as `bootRunOnce`'s.
|
|
223
|
+
* 2. Once it exits 0, poll `msb ls` for the sandbox to reach Running —
|
|
224
|
+
* the same `READINESS_POLL_MS` cadence and a fresh instance of the
|
|
225
|
+
* identical `FIRST_RUN_PULL_TIMEOUT_MS` budget the attached path's
|
|
226
|
+
* own poll uses (so a restore boot gets the identical readiness
|
|
227
|
+
* patience an ordinary one does, counted from when there is
|
|
228
|
+
* actually something to poll for rather than shaved out of phase
|
|
229
|
+
* 1's own budget). The sandbox settling on exactly `"Stopped"`, or
|
|
230
|
+
* its name disappearing from a `msb ls` listing that itself came
|
|
231
|
+
* back, is a definite failure the moment it is observed — never
|
|
232
|
+
* worth waiting out the rest of the budget for, unlike a status
|
|
233
|
+
* that simply hasn't reached Running yet.
|
|
234
|
+
* 3. The `ls` probe itself failing to run or return in time (spawn
|
|
235
|
+
* error, or the same `LOGS_TIMEOUT_MS` `invoke` enforces on every
|
|
236
|
+
* other probe) is a different thing entirely from the sandbox's name
|
|
237
|
+
* being missing from a listing that DID come back, even though both
|
|
238
|
+
* collapse to the same `undefined` once the probe's own promise is
|
|
239
|
+
* caught — see `isCompletedFastExit`'s own doc, whose sibling
|
|
240
|
+
* contract this mirrors: a probe failure must never be upgraded into
|
|
241
|
+
* a false, specific diagnosis ("disappeared from msb ls entirely" is
|
|
242
|
+
* exactly that kind of false claim when the truth is just that `ls`
|
|
243
|
+
* itself didn't answer in time). A failed probe is therefore treated
|
|
244
|
+
* as "not yet confirmed either way" and retried within budget —
|
|
245
|
+
* install-lock races and an overloaded msb daemon are the same
|
|
246
|
+
* transient shapes this file already documents for `run`/`restore`/
|
|
247
|
+
* `stop`, and one `ls` hiccup during the readiness window must not
|
|
248
|
+
* turn into a spurious restore failure.
|
|
249
|
+
*
|
|
250
|
+
* The `msb restore` CLI process itself is never held onto — it has already
|
|
251
|
+
* exited by the time phase 2 even starts, and msb itself (out-of-process)
|
|
252
|
+
* is the sandbox's own supervisor from here on, the same as it always was.
|
|
253
|
+
* But a restored sandbox reaches Running with ONLY its guest agent inside
|
|
254
|
+
* — the captured workload never re-executes on its own (EMPIRICALLY
|
|
255
|
+
* VERIFIED against msb 0.7.1) — so once Running is confirmed, this method
|
|
256
|
+
* calls `reviveWorkload` to start it itself BEFORE returning: THAT call is
|
|
257
|
+
* what populates `state.attached` (a workload-revival `msb exec` child,
|
|
258
|
+
* not the restore CLI process), and `stop()`'s ordinary attached-child
|
|
259
|
+
* handling (exit-based death detection, the SIGKILL escalation) applies to
|
|
260
|
+
* IT exactly as it always did for `bootRunOnce`'s own attached `msb run`
|
|
261
|
+
* child. See `reviveWorkload`'s own doc for the full revival contract,
|
|
262
|
+
* including the typed error a checkpoint predating workload-cmdline
|
|
263
|
+
* capture throws instead of booting silently idle.
|
|
264
|
+
*
|
|
265
|
+
* Phase 1 (spawning the restore itself and reading back its own exit code
|
|
266
|
+
* + combined output) is dispatched by `launchMode` to either
|
|
267
|
+
* `launchRestoreDirect` (the unconditional spawn this method has always
|
|
268
|
+
* used) or `launchRestoreViaBroker` (POLICY v2's Windows job-object
|
|
269
|
+
* escape hatch — see `RestoreLaunchMode`/`restore-broker.ts`'s own doc).
|
|
270
|
+
* Both return the exact same shape, classified identically right below —
|
|
271
|
+
* a brokered attempt's output goes through the SAME predicates
|
|
272
|
+
* (`isImageCacheCorruption`, `isRestoreAccessDeniedFailure`, ...) a direct
|
|
273
|
+
* attempt's always has, since the underlying `msb restore` invocation and
|
|
274
|
+
* its failure signatures are identical either way; only how its stdout/
|
|
275
|
+
* stderr and exit code got back to this process differs. A broker launch
|
|
276
|
+
* can also come back `"unconfirmed"` (its own `ecFile` never appeared
|
|
277
|
+
* within its bound) — see `RestoreBrokerUnconfirmed`'s own doc — which
|
|
278
|
+
* skips classification entirely and falls straight through to phase 2
|
|
279
|
+
* exactly as a confirmed exit 0 does, since that phase's own `msb ls` poll
|
|
280
|
+
* is activation-gated and settles the question on its own regardless.
|
|
281
|
+
*/
|
|
282
|
+
private bootRestoreOnce;
|
|
283
|
+
/**
|
|
284
|
+
* `bootRestoreOnce`'s own DIRECT launch — the unconditional child-process
|
|
285
|
+
* spawn every restore attempt used before POLICY v2's broker escalation
|
|
286
|
+
* existed, extracted unchanged from that method's own former body so the
|
|
287
|
+
* two `RestoreLaunchMode`s share one classification point (see
|
|
288
|
+
* `bootRestoreOnce`'s own doc). Bounded by the same `FIRST_RUN_PULL_TIMEOUT_MS`
|
|
289
|
+
* budget `bootRunOnce` polls against, and always resolves `"completed"` —
|
|
290
|
+
* a direct spawn has no analog of the broker's own `"unconfirmed"` outcome,
|
|
291
|
+
* since this process reads the child's real exit code directly.
|
|
292
|
+
*/
|
|
293
|
+
private launchRestoreDirect;
|
|
294
|
+
/**
|
|
295
|
+
* `bootRestoreOnce`'s own BROKERED launch — POLICY v2's Windows job-object
|
|
296
|
+
* escape hatch (see `RestoreLaunchMode`/`restore-broker.ts`'s own module
|
|
297
|
+
* doc on the root cause and the live-validated WMI mitigation). Calls the
|
|
298
|
+
* injected `restoreBroker` seam with the exact same argv a direct attempt
|
|
299
|
+
* would spawn (`MsbCommands.restore`, unmodified) and the same
|
|
300
|
+
* `FIRST_RUN_PULL_TIMEOUT_MS` budget.
|
|
301
|
+
*
|
|
302
|
+
* The broker's own result is either passed straight through
|
|
303
|
+
* (`"completed"`/`"unconfirmed"`, both appended to `state.logTail` for the
|
|
304
|
+
* same pre-Running diagnostics purpose `drainTail` serves on the direct
|
|
305
|
+
* path) or, if the broker call itself REJECTS — broker infrastructure
|
|
306
|
+
* failing (a missing `powershell.exe`, a script-file write failure, WMI
|
|
307
|
+
* refusing to even create the process; see `RestoreBrokerLauncher`'s own
|
|
308
|
+
* doc on what counts as this vs. an ordinary brokered-restore failure) —
|
|
309
|
+
* caught here and turned into a one-attempt fallback to
|
|
310
|
+
* `launchRestoreDirect` instead (POLICY v2 point 5: the broker must never
|
|
311
|
+
* become a new single point of failure). That fallback is scoped to THIS
|
|
312
|
+
* attempt only; it does not touch the caller's own `RestoreLaunchMode` for
|
|
313
|
+
* whatever attempt comes after it.
|
|
314
|
+
*/
|
|
315
|
+
private launchRestoreViaBroker;
|
|
316
|
+
/**
|
|
317
|
+
* Renders a restore boot's post-mortem message once its poll for Running
|
|
318
|
+
* has definitely failed (a settled `"Stopped"`, the name disappearing
|
|
319
|
+
* from `msb ls` outright, or the readiness budget itself running out).
|
|
320
|
+
* `msb restore` itself already exited 0 by this point, so the CLI
|
|
321
|
+
* process's own output (already surfaced for a nonzero exit, above) has
|
|
322
|
+
* nothing left to add — `msb logs --source system` is the one channel
|
|
323
|
+
* that can still show what happened to the sandbox's own background boot
|
|
324
|
+
* (see `MsbCommands.systemLog`'s own doc and `isCompletedFastExit`, its
|
|
325
|
+
* other caller). Best-effort: a failed probe falls back to a plain "no
|
|
326
|
+
* output" note rather than letting a diagnostics-gathering failure itself
|
|
327
|
+
* throw and mask the real one.
|
|
328
|
+
*/
|
|
329
|
+
private restoreBootFailureMessage;
|
|
330
|
+
/**
|
|
331
|
+
* Starts the workload a restore itself never re-runs. EMPIRICALLY VERIFIED
|
|
332
|
+
* against msb 0.7.1: a restored sandbox reaches Running with ONLY its
|
|
333
|
+
* guest agent inside (`guest ps` shows `/init.krun` and kernel threads —
|
|
334
|
+
* the captured workload command does not re-execute; `msb start`/`msb
|
|
335
|
+
* logs` on such a sandbox are equally idle/empty). Only ever called from
|
|
336
|
+
* `bootRestoreOnce`, once it has confirmed Running — see this class's own
|
|
337
|
+
* doc on `bootRestoreOnce`.
|
|
338
|
+
*
|
|
339
|
+
* Spawns a LONG-LIVED, attached `msb exec [-e K=V]... <name> -- <argv>`
|
|
340
|
+
* session (`MsbCommands.execWithEnv`, env from `handle.spec.env` — a
|
|
341
|
+
* restore's own `msb restore` has no `-e`/`--env` flag at all, so this exec
|
|
342
|
+
* is the one place a restored sandbox's guest ever sees it again) — this
|
|
343
|
+
* becomes the sandbox's own workload from here on, and exec sessions are
|
|
344
|
+
* exactly what msb's own log capture records (the primary session's
|
|
345
|
+
* stdout/stderr land in `exec.log`, served by `msb logs`/`-f` — see
|
|
346
|
+
* `MsbCommands.exec`'s own doc). This exec child slots into the EXACT SAME
|
|
347
|
+
* `state.attached` role `bootRunOnce`'s own attached `msb run` child fills
|
|
348
|
+
* for an ordinary boot: child-exit-based death detection, reap-on-stop
|
|
349
|
+
* (`stop()`'s SIGKILL escalation), and every other attached-child teardown
|
|
350
|
+
* semantic apply to it unchanged — the detached-restore round left that
|
|
351
|
+
* slot merely optional, never removed it.
|
|
352
|
+
*
|
|
353
|
+
* `handle.spec.command` is ALREADY the fully-resolved workload argv by the
|
|
354
|
+
* time this runs, in priority order: an explicit command the source
|
|
355
|
+
* container carried, or — when it had none — the guest cmdline
|
|
356
|
+
* `createCheckpoint` captured at checkpoint time, merged in by
|
|
357
|
+
* `createCheckpoint` itself (for its own immediate reboot) or by
|
|
358
|
+
* `fromCheckpointRegistryEntry` (for a registry-mediated restore, same or
|
|
359
|
+
* later process — see both functions' own docs). `undefined` here means
|
|
360
|
+
* NEITHER source exists — an old registry entry predating capture, or one
|
|
361
|
+
* whose capture attempt itself failed — so this throws
|
|
362
|
+
* `CheckpointWorkloadCommandMissingError` itself rather than depending on
|
|
363
|
+
* a caller to have checked first: never boot a restored sandbox silently
|
|
364
|
+
* idle.
|
|
365
|
+
*
|
|
366
|
+
* The exec child gets a brief settle window (`EXEC_REVIVE_SETTLE_MS`) to
|
|
367
|
+
* prove it isn't an immediate boot failure before this returns success —
|
|
368
|
+
* mirroring `bootRunOnce`'s own exit-vs-Running race, except a restore's
|
|
369
|
+
* exec session has no separate "Running" signal of its own to poll for
|
|
370
|
+
* (the SANDBOX already reports Running regardless of whether this exec
|
|
371
|
+
* succeeds), so a settle window is what that race collapses to here. An
|
|
372
|
+
* exit observed within the window is classified exactly like
|
|
373
|
+
* `bootRunOnce`'s own early exit: exit 0 counts as success only when
|
|
374
|
+
* `isCompletedFastExit` ALSO confirms it (the sandbox itself settled
|
|
375
|
+
* Stopped with the boot-completion marker — the repo's existing fast-exit-
|
|
376
|
+
* completion semantics, reused verbatim); any other exit — nonzero, or
|
|
377
|
+
* exit 0 without that confirmation — throws a `BackendError` carrying the
|
|
378
|
+
* exec child's own output, the same failure shape an attached run's early
|
|
379
|
+
* exit already has.
|
|
380
|
+
*/
|
|
381
|
+
private reviveWorkload;
|
|
382
|
+
/**
|
|
383
|
+
* Best-effort: execs `CAPTURE_WORKLOAD_CMDLINE_SCRIPT` in `handle`'s guest
|
|
384
|
+
* and parses its stdout via `parseCapturedWorkloadCmdline`. Only ever
|
|
385
|
+
* called from `createCheckpoint`, BEFORE it stops the sandbox (see that
|
|
386
|
+
* method's own doc), and only when `handle.spec.command` is undefined — an
|
|
387
|
+
* explicit command needs no capture. Never throws: an exec failure
|
|
388
|
+
* (nonzero exit, the exec channel itself erroring) and unparseable output
|
|
389
|
+
* both resolve `undefined` rather than failing the checkpoint — the
|
|
390
|
+
* captured cmdline is a best-effort fallback, and its absence is fully
|
|
391
|
+
* handled later, at restore time (`reviveWorkload` throws
|
|
392
|
+
* `CheckpointWorkloadCommandMissingError` when neither an explicit nor a
|
|
393
|
+
* captured command exists).
|
|
394
|
+
*/
|
|
395
|
+
private captureGuestWorkloadCmdline;
|
|
396
|
+
/**
|
|
397
|
+
* SPI implementation of `SandboxBackend.capturedWorkloadCommand` — see its
|
|
398
|
+
* own doc. Reads back whatever `createCheckpoint` most recently stashed on
|
|
399
|
+
* this sandbox's `HandleState` (keyed by name, so it survives the
|
|
400
|
+
* stop/snapshot/reboot cycle's own handle-object churn); `undefined` if
|
|
401
|
+
* this sandbox was never checkpointed, or its checkpoint needed no
|
|
402
|
+
* capture, or the capture attempt failed.
|
|
403
|
+
*/
|
|
404
|
+
capturedWorkloadCommand(handle: SandboxHandle): ReadonlyArray<string> | undefined;
|
|
116
405
|
/**
|
|
117
406
|
* The fast-exit post-mortem classification: only ever consulted from
|
|
118
|
-
* `
|
|
407
|
+
* `bootRunOnce` above, and only once the attached `msb run` child has already
|
|
119
408
|
* exited with code 0 before Running was observed. msb 0.6.16's
|
|
120
409
|
* convergent-lifecycle rework means a workload that finishes quickly is
|
|
121
410
|
* never observed `"Running"` at all — only `"Starting"`, then the attached
|
|
@@ -139,30 +428,237 @@ export declare class MsbCliBackend implements SandboxBackend {
|
|
|
139
428
|
private isCompletedFastExit;
|
|
140
429
|
stop(handle: SandboxHandle): Promise<void>;
|
|
141
430
|
remove(handle: SandboxHandle): Promise<void>;
|
|
431
|
+
/**
|
|
432
|
+
* `bootClassified`'s own retry for a `RestoreAccessDeniedError` hit on the
|
|
433
|
+
* ordinary `start()`/`fromCheckpoint().start()` path (its default
|
|
434
|
+
* `retryAccessDenied: true`) — the sibling of `rebootUnderFreshName`
|
|
435
|
+
* (`createCheckpoint`'s own reboot retry) for the SAME classified failure,
|
|
436
|
+
* built from the SAME live-verified policy: `msb restore --name X`
|
|
437
|
+
* validates the artifact FIRST — an integrity failure exits 1 and leaves
|
|
438
|
+
* NO sandbox record — but a failure AFTER validation (this access-denied
|
|
439
|
+
* signature included) leaves `X` behind as a STOPPED SANDBOX RECORD, and
|
|
440
|
+
* any retry of `restore --name X` then fails outright with msb's own
|
|
441
|
+
* "already exists" refusal rather than hitting the transient a second
|
|
442
|
+
* time. A same-name retry (what this method replaces) therefore only ever
|
|
443
|
+
* proves that collision — never the actual transient — so this mints a
|
|
444
|
+
* NEW name from `nextSandboxName()` for every retry instead, the same
|
|
445
|
+
* generator `GenericContainer.start()`'s own boot loop and
|
|
446
|
+
* `rebootUnderFreshName` both use, tracking it in the reaper ledger BEFORE
|
|
447
|
+
* that attempt's restore runs and best-effort `msb rm`-ing (then
|
|
448
|
+
* untracking) a failed attempt's own name before advancing — identical
|
|
449
|
+
* per-attempt bookkeeping to `rebootUnderFreshName`, just owned one layer
|
|
450
|
+
* lower.
|
|
451
|
+
*
|
|
452
|
+
* The difference from `rebootUnderFreshName` is entirely about WHO owns
|
|
453
|
+
* the re-keying, not the retry policy itself: `createCheckpoint` calls
|
|
454
|
+
* `rebootUnderFreshName` with no live sandbox left at all (the source was
|
|
455
|
+
* already stopped and removed as part of the snapshot step), so EVERY
|
|
456
|
+
* attempt — including the first — mints a fresh name, and the winning
|
|
457
|
+
* handle is handed back for `createCheckpoint` itself to re-key. Here,
|
|
458
|
+
* `handle` is already live under its own name when `first` (the initial
|
|
459
|
+
* `RestoreAccessDeniedError`) is caught, and `bootClassified`'s caller
|
|
460
|
+
* (`start()`) has no re-keying step of its own — it just awaits
|
|
461
|
+
* `bootClassified` and returns — so this method re-keys
|
|
462
|
+
* `this.handles`/`this.startedNames` AND mutates the caller's own `handle`
|
|
463
|
+
* object IN PLACE itself on success, the same "one carve-out"
|
|
464
|
+
* `SandboxHandle`'s own interface doc allows for `createCheckpoint`'s
|
|
465
|
+
* reboot (see that method's own doc on `handle.id`/`handle.spec`
|
|
466
|
+
* mutation).
|
|
467
|
+
*
|
|
468
|
+
* `handle`'s own original name was already tracked in the reaper ledger by
|
|
469
|
+
* `GenericContainer.start()`'s own boot loop before `create()`/`start()`
|
|
470
|
+
* ever ran (mirroring `rebootUnderFreshName`'s treatment of the checkpoint
|
|
471
|
+
* reboot's own `originalName`) — this method best-effort `msb rm`'s it
|
|
472
|
+
* once `first` is confirmed retryable, but deliberately leaves its own
|
|
473
|
+
* ledger entry alone, for the same not-found-tolerant sweep to find,
|
|
474
|
+
* rather than untracking a name this method never tracked itself.
|
|
475
|
+
*
|
|
476
|
+
* Retries up to `RESTORE_ACCESS_DENIED_RETRY_LIMIT` times,
|
|
477
|
+
* `RESTORE_ACCESS_DENIED_RETRY_DELAY_MS` apart — unchanged from the
|
|
478
|
+
* same-name retry this replaces. Each attempt goes through
|
|
479
|
+
* `bootClassified` itself with `retryAccessDenied: false` (so a
|
|
480
|
+
* `StateDbError`/`InstallLockActiveError`/`ImageCacheCorruptionError` hit
|
|
481
|
+
* mid-retry still gets its own ordinary classified handling; only
|
|
482
|
+
* `RestoreAccessDeniedError`'s own retry is owned here) and treats either
|
|
483
|
+
* `RestoreAccessDeniedError` or `SandboxAlreadyExistsError` as retryable —
|
|
484
|
+
* a freshly minted name should never collide, but `rebootUnderFreshName`'s
|
|
485
|
+
* own live-CI evidence says a loaded host can still refuse one, and the
|
|
486
|
+
* SAME fresh-naming response (rm the failed name, mint another) covers
|
|
487
|
+
* both signatures identically. Anything else propagates immediately,
|
|
488
|
+
* unretried.
|
|
489
|
+
*
|
|
490
|
+
* POLICY v2's broker escalation applies here too, on the exact same terms
|
|
491
|
+
* as `rebootUnderFreshName`'s own (see that method's own doc): this
|
|
492
|
+
* method is only ever entered once the reboot's first, always-direct
|
|
493
|
+
* attempt has already hit `RestoreAccessDeniedError` (`first`), so on a
|
|
494
|
+
* Windows host (`isWindowsPlatform()`) every retry THIS method itself
|
|
495
|
+
* performs is, by construction, an attempt after that trigger — there is
|
|
496
|
+
* no "first attempt still direct" sub-case to preserve inside this loop
|
|
497
|
+
* the way `rebootUnderFreshName` has to across its own. `launchMode` is
|
|
498
|
+
* therefore decided once, before the loop, not re-evaluated per attempt.
|
|
499
|
+
*/
|
|
500
|
+
private retryRestoreAfterAccessDenied;
|
|
501
|
+
/**
|
|
502
|
+
* `createCheckpoint`'s own reboot step. Mints a NEW sandbox name from
|
|
503
|
+
* `nextSandboxName()` (the same generator `GenericContainer.start()`'s own
|
|
504
|
+
* boot loop uses) for EVERY attempt, tracks it in the reaper ledger BEFORE
|
|
505
|
+
* that attempt's restore runs (mirroring `GenericContainer.start()`'s own
|
|
506
|
+
* `trackSandbox` call, keepAlive-excluded the same way), and retries two
|
|
507
|
+
* classified failure signatures on that basis instead of surfacing either
|
|
508
|
+
* immediately: msb's "sandbox already exists" refusal
|
|
509
|
+
* (`SandboxAlreadyExistsError`, see `isSandboxAlreadyExistsFailure`'s own
|
|
510
|
+
* doc) and its Windows access-denied refusal on the just-written snapshot
|
|
511
|
+
* artifact (`RestoreAccessDeniedError`, see `isRestoreAccessDeniedFailure`'s
|
|
512
|
+
* own doc — retried here rather than inside `bootClassified` itself, via
|
|
513
|
+
* that method's own `retryAccessDenied: false`, see its doc).
|
|
514
|
+
*
|
|
515
|
+
* LIVE-VERIFIED POLICY this retry is built from: `msb restore --name X`
|
|
516
|
+
* validates the artifact FIRST — an integrity failure exits 1 and leaves NO
|
|
517
|
+
* sandbox record — but a failure AFTER validation (a block-device open
|
|
518
|
+
* PermissionDenied on unix; `RestoreAccessDeniedError`'s own signature on
|
|
519
|
+
* Windows) leaves `X` behind as a STOPPED SANDBOX RECORD visible in `msb
|
|
520
|
+
* ls`, and any retry of `restore --name X` then fails with msb's own
|
|
521
|
+
* "already exists" refusal — so a same-name retry of EITHER classified
|
|
522
|
+
* failure only ever collides with itself. Reusing a failed attempt's name
|
|
523
|
+
* is therefore never safe (`msb rm` alone is not reliably enough either: on
|
|
524
|
+
* Windows it frees only the DB record, not the on-disk directory, which can
|
|
525
|
+
* outlive it well past this budget — see the fresh-name reboot's own doc on
|
|
526
|
+
* `createCheckpoint`) — the only universally safe policy is a NEW name per
|
|
527
|
+
* attempt, same as this backend's ordinary `start()` boot loop already uses
|
|
528
|
+
* for its own, unrelated port-conflict retries. On a classified failure
|
|
529
|
+
* this best-effort `msb rm`'s the failed attempt's own name anyway (result
|
|
530
|
+
* ignored — cheap cleanup, and correctness no longer depends on it now
|
|
531
|
+
* that the next attempt never reuses that name) and untracks it from the
|
|
532
|
+
* ledger, before advancing to a freshly minted name for the next attempt.
|
|
533
|
+
*
|
|
534
|
+
* `this.checkpointRebootAlreadyExistsRetryBudgetMs`/`_RetryDelayMs` back
|
|
535
|
+
* this loop's overall deadline/delay rather than the bare
|
|
536
|
+
* `CHECKPOINT_REBOOT_ALREADY_EXISTS_RETRY_BUDGET_MS`/`_DELAY_MS` constants
|
|
537
|
+
* directly, so a budget-exhaustion test can shrink them to milliseconds
|
|
538
|
+
* instead of actually blocking for the real ~30s — see those fields' own
|
|
539
|
+
* doc. Only `SandboxAlreadyExistsError`/`RestoreAccessDeniedError` are
|
|
540
|
+
* retried here; any other failure `bootClassified` throws (on the first
|
|
541
|
+
* attempt or a later one) propagates immediately, unretried, WITHOUT
|
|
542
|
+
* advancing to a new name — `createCheckpoint`'s own caller untracks that
|
|
543
|
+
* attempt's name on this path (see its own catch block). Not reached by
|
|
544
|
+
* the ordinary `start()` path: a `GenericContainer.fromCheckpoint(cp)
|
|
545
|
+
* .start()` restore's own `RestoreAccessDeniedError` is retried by its own
|
|
546
|
+
* sibling, `retryRestoreAfterAccessDenied` (see that method's own doc) —
|
|
547
|
+
* same fresh-naming policy, applied one layer lower since that path has no
|
|
548
|
+
* separate caller left to hand a replacement handle to for re-keying.
|
|
549
|
+
*
|
|
550
|
+
* Returns the WINNING attempt's own `SandboxHandle` (a fresh object, never
|
|
551
|
+
* `undefined`) — `createCheckpoint` re-keys its runtime registries and the
|
|
552
|
+
* caller's live handle from this, not from a name minted up front.
|
|
553
|
+
*
|
|
554
|
+
* POLICY v2's broker escalation (see `RestoreLaunchMode`/`restore-broker.ts`'s
|
|
555
|
+
* own module doc on the Windows job-object root cause and the live-
|
|
556
|
+
* validated WMI mitigation) lives here: `launchMode` starts `"direct"` —
|
|
557
|
+
* the first attempt of ANY reboot is always a direct spawn, unconditionally,
|
|
558
|
+
* even on Windows — and flips to `"broker"` the moment an attempt hits
|
|
559
|
+
* `RestoreAccessDeniedError` on a Windows host (`isWindowsPlatform()`);
|
|
560
|
+
* every attempt from then on, for the rest of THIS reboot, launches
|
|
561
|
+
* brokered instead, never downgrading back to direct even if a later
|
|
562
|
+
* attempt's own failure is only `SandboxAlreadyExistsError` (still
|
|
563
|
+
* retried, but no longer the trigger — only access-denied escalates).
|
|
564
|
+
* Off Windows, `launchMode` never leaves `"direct"` at all — this reboot's
|
|
565
|
+
* whole retry loop behaves exactly as it did before this policy existed.
|
|
566
|
+
*/
|
|
567
|
+
private rebootUnderFreshName;
|
|
142
568
|
/**
|
|
143
569
|
* The stop/snapshot/reboot cycle: `msb stop <name>` (reusing this
|
|
144
570
|
* backend's own `stop()`, which also quiesces the attached child and any
|
|
145
|
-
* network-link tunnels), `msb snapshot create --from <name> <ref
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* `
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
571
|
+
* network-link tunnels), `msb snapshot create --from-sandbox <name> <ref>`
|
|
572
|
+
* (whose printed artifact path — never `ref` itself — becomes the
|
|
573
|
+
* EFFECTIVE checkpoint ref this method returns; see
|
|
574
|
+
* `parseSnapshotCreateArtifactPath`), then `msb rm <name>` followed by
|
|
575
|
+
* `msb restore <effective-ref> --name <fresh-name>` of a FRESH name from
|
|
576
|
+
* that snapshot (via `rebootUnderFreshName`/`bootRestoreOnce`, see
|
|
577
|
+
* `MsbCommands.restore`'s own doc — no `--disk-only`, which a disk-scope
|
|
578
|
+
* snapshot rejects) — never `msb start`. The fresh name — never the
|
|
579
|
+
* original — is minted by `nextSandboxName()` (the same generator
|
|
580
|
+
* `GenericContainer.start()`'s own ordinary boot loop uses; see
|
|
581
|
+
* `core/sandbox-name.ts`), and a NEW one is minted for EVERY attempt, never
|
|
582
|
+
* just once for the whole reboot: EMPIRICALLY VERIFIED against a real msb
|
|
583
|
+
* 0.7.1 binary, a restore that fails PAST msb's own artifact validation
|
|
584
|
+
* (its Windows access-denied signature, `RestoreAccessDeniedError`) leaves
|
|
585
|
+
* its `--name` behind as a STOPPED SANDBOX RECORD, and any retry under that
|
|
586
|
+
* SAME name then collides with msb's own restore-time collision check
|
|
587
|
+
* (`existing.is_some() || dir_exists`) immediately — CONFIRMED on Windows
|
|
588
|
+
* CI, where five checkpoint tests collided on their fresh reboot names for
|
|
589
|
+
* the retry loop's ENTIRE budget once the first attempt hit that
|
|
590
|
+
* access-denied failure (see `CHECKPOINT_REBOOT_ALREADY_EXISTS_RETRY_BUDGET_MS`'s
|
|
591
|
+
* own doc). A same-name retry therefore only ever retries through a race of
|
|
592
|
+
* its own making; a NEW name per attempt sidesteps it structurally — a name
|
|
593
|
+
* nothing else has ever used can never collide with a record or a lingering
|
|
594
|
+
* directory left by a name nothing will ever restore under again (see
|
|
595
|
+
* `rebootUnderFreshName`'s own doc for the full retry policy, including its
|
|
596
|
+
* best-effort `msb rm` of each failed attempt's own name). On success, this method
|
|
597
|
+
* mutates `handle.id` and `handle.spec` (name only — see below) IN PLACE
|
|
598
|
+
* on the caller's own `handle`, so every subsequent operation against it
|
|
599
|
+
* (exec/logs/stop/rm, and — one layer up, via the SAME `SandboxHandle`
|
|
600
|
+
* reference — `GenericContainer.checkpoint()`'s own post-reboot
|
|
601
|
+
* `installNetworkLinks`/wait-strategy re-run and `capturedWorkloadCommand()`
|
|
602
|
+
* call) targets the sandbox actually running now. `SandboxBackend`'s own
|
|
603
|
+
* interface doc calls `SandboxHandle` "immutable" — this is the one
|
|
604
|
+
* carve-out, and only this method makes it: a checkpoint reboot is the one
|
|
605
|
+
* operation that changes what sandbox a handle even refers to. `ports`,
|
|
606
|
+
* `env`, and `memoryLimitMb` are untouched by the rename — same ports, env,
|
|
607
|
+
* and memory ceiling as before the reboot, only the name differs.
|
|
608
|
+
* Upstream's
|
|
609
|
+
* detached-start path (`Sandbox::start_detached`) passes
|
|
610
|
+
* `CREATE_BREAKAWAY_FROM_JOB` on Windows, which `ERROR_ACCESS_DENIED`s
|
|
611
|
+
* outright whenever the msb CLI runs inside a job object that doesn't
|
|
612
|
+
* grant breakaway rights — a Gradle/cargo/node test runner on a hosted
|
|
613
|
+
* Windows runner, or any process that embeds this library inside its own
|
|
614
|
+
* restrictive job object — and that denial is deterministic, not
|
|
615
|
+
* transient, so no retry shape fixes it. Before 0.7.1, attached `msb run`
|
|
616
|
+
* (including its `--from-snapshot` boots) never hit this, which is why the
|
|
617
|
+
* reboot has always gone through the classified boot path (`bootClassified`)
|
|
618
|
+
* rather than `msb start`. `msb restore` has no attached/detached
|
|
619
|
+
* distinction of its own — its CLI process always calls `sandbox.detach()`
|
|
620
|
+
* internally and exits once activation is confirmed (see
|
|
621
|
+
* `bootRestoreOnce`'s own doc on why its exit is a SUCCESS signal, not
|
|
622
|
+
* something to reap) — so whether it shares upstream's
|
|
623
|
+
* `CREATE_BREAKAWAY_FROM_JOB` path on Windows is NOT verified here: this
|
|
624
|
+
* backend never runs the real msb binary (see this repo's hard "never boot
|
|
625
|
+
* a sandbox" rule), so this is a real open question for CI, not something
|
|
626
|
+
* this migration could confirm either way. If it recurs, it will surface
|
|
627
|
+
* as an ordinary unclassified `BackendError` out of `bootRestoreOnce`
|
|
628
|
+
* (deterministic exit-code failure, not one of the classified transients
|
|
629
|
+
* above), the same shape a genuine breakaway denial always took.
|
|
630
|
+
* `rm`-ing the sandbox first and restoring a fresh one under a fresh
|
|
631
|
+
* name, same ports/memory (via a spec identical to `handle.spec` except
|
|
632
|
+
* `name`/`checkpointRef` set to the fresh name and the EFFECTIVE ref this
|
|
633
|
+
* method discovers, below — env is no longer threaded through at all, see
|
|
634
|
+
* `MsbCommands.restore`) reproduces the exact same observable contract
|
|
635
|
+
* MODULO the name itself, which was always an implementation detail, not
|
|
636
|
+
* part of what a checkpoint promises to preserve. This backend's own
|
|
637
|
+
* `handles`/`startedNames` registries ARE re-keyed from the original name
|
|
638
|
+
* to the WINNING attempt's name (see below), and each attempt's own fresh
|
|
639
|
+
* name is tracked in the reaping ledger before that attempt's restore is
|
|
640
|
+
* even attempted, exactly like an ordinary `create()` — a failed attempt's
|
|
641
|
+
* name is untracked again by `rebootUnderFreshName` itself, while the
|
|
642
|
+
* original name's own ledger entry is deliberately left alone throughout,
|
|
643
|
+
* for the ledger's existing not-found-tolerant sweep to find (it was
|
|
644
|
+
* already `msb rm`-ed above). Its workload restarts from scratch (the VM
|
|
645
|
+
* reboots), which is why `capabilities.checkpointRestartsWorkload` is
|
|
646
|
+
* `true` here and the generic layer re-runs the wait strategy after this
|
|
647
|
+
* returns — AFTER `bootRestoreOnce` has already revived the workload
|
|
648
|
+
* itself via `reviveWorkload` (see that method's own doc): `msb restore`
|
|
649
|
+
* boots the reboot idle, only the guest agent inside, so `bootRestoreOnce`
|
|
650
|
+
* no longer leaves `state.attached` untouched the way it did before this
|
|
651
|
+
* revival step existed — it now carries the workload-revival exec child,
|
|
652
|
+
* the same attached-child slot `bootRunOnce`'s own `msb run` child fills
|
|
653
|
+
* for an ordinary boot.
|
|
654
|
+
*
|
|
655
|
+
* Before stopping the source sandbox, this also best-effort captures its
|
|
656
|
+
* guest workload cmdline (`captureGuestWorkloadCmdline`) when
|
|
657
|
+
* `handle.spec.command` is undefined — the image's own default entrypoint
|
|
658
|
+
* was running, so there is no explicit command for `reviveWorkload` to
|
|
659
|
+
* fall back on at either this method's own immediate reboot or a later,
|
|
660
|
+
* registry-mediated restore. See that method's own doc for why a capture
|
|
661
|
+
* failure never fails the checkpoint itself.
|
|
166
662
|
*
|
|
167
663
|
* If the snapshot step fails, the sandbox is left stopped — no
|
|
168
664
|
* best-effort restart, since that restart would itself be the broken `msb
|
|
@@ -174,19 +670,52 @@ export declare class MsbCliBackend implements SandboxBackend {
|
|
|
174
670
|
*
|
|
175
671
|
* Refuses outright, before any of the above, when `handle.spec.tmpfsRootMb`
|
|
176
672
|
* is set: a tmpfs root has nothing on disk for a snapshot to capture.
|
|
673
|
+
*
|
|
674
|
+
* `ref` is the WORKING ref this method is asked to checkpoint under — its
|
|
675
|
+
* basename becomes the snapshot create call's `<name>` argument (still
|
|
676
|
+
* meaningful: it lands in msb's own index, see `MsbCommands.snapshotCreate`'s
|
|
677
|
+
* own doc) and, for a path ref, its dirname becomes `--dest-dir`. It is
|
|
678
|
+
* NOT necessarily where the artifact ends up: EMPIRICALLY VERIFIED against
|
|
679
|
+
* a real msb 0.7.1 binary, `snapshot create` always writes under
|
|
680
|
+
* `<destDir-or-default>/<sandbox>/snap_<32-hex-digest>`, a path `name`
|
|
681
|
+
* never determines. This method therefore parses that real artifact path
|
|
682
|
+
* back out of the command's own stdout (last non-empty line, required to
|
|
683
|
+
* be absolute — see `parseSnapshotCreateArtifactPath`) and returns THAT as
|
|
684
|
+
* the EFFECTIVE ref — the one used for the reboot below and the one the
|
|
685
|
+
* caller (`GenericContainer.checkpoint()`) stores in the registry and
|
|
686
|
+
* hands back on the `Checkpoint` it returns. Malformed or unrecognizable
|
|
687
|
+
* output (empty, no absolute last line) throws a `BackendError` quoting
|
|
688
|
+
* the raw, unparsed output verbatim rather than guessing a ref.
|
|
177
689
|
*/
|
|
178
|
-
createCheckpoint(handle: SandboxHandle, ref: string): Promise<
|
|
690
|
+
createCheckpoint(handle: SandboxHandle, ref: string): Promise<string>;
|
|
179
691
|
/**
|
|
180
|
-
* Best-effort `msb snapshot rm <
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
692
|
+
* Best-effort `msb snapshot rm <ref> -f` — "not found" is success, the
|
|
693
|
+
* same contract as `removeByName`. `ref` is passed FULL, never reduced to
|
|
694
|
+
* `path.basename(ref)`: EMPIRICALLY VERIFIED against a real msb 0.7.1
|
|
695
|
+
* binary, name-based removal does not resolve at all — the artifact PATH
|
|
696
|
+
* is the only address that reliably works (see `MsbCommands.snapshotRemove`'s
|
|
697
|
+
* own doc). Every real caller already hands this the EFFECTIVE ref
|
|
698
|
+
* `createCheckpoint` returned (an absolute path since 0.7.1), so this is
|
|
699
|
+
* also simply correct for the common case, not just a defensive choice.
|
|
700
|
+
*
|
|
701
|
+
* One failure shape is deliberately NOT swallowed alongside "not found":
|
|
702
|
+
* msb refuses to remove a snapshot that is still the current HEAD of
|
|
703
|
+
* older siblings from the same source sandbox (see
|
|
704
|
+
* `isSnapshotHeadRemovalRefused`) — that refusal propagates as a
|
|
705
|
+
* `BackendError` naming msb's own remedy, rather than silently doing
|
|
706
|
+
* nothing, since automatic head rotation is out of scope for this method
|
|
707
|
+
* (see the checkpoints guide's cleanup section for the documented
|
|
708
|
+
* limitation).
|
|
709
|
+
*
|
|
710
|
+
* msb's own removal deletes both its index entry and the dest-dir
|
|
711
|
+
* artifact for a path ref, but afterwards this also best-effort
|
|
712
|
+
* recursively deletes the ref path itself: if msb's index ever loses
|
|
713
|
+
* track of an artifact without deleting it, the directory would otherwise
|
|
714
|
+
* linger under the cache dir forever. That recursive delete is gated on
|
|
715
|
+
* `looksLikeCheckpointArtifactDir` first — a `ref` is caller-supplied (a
|
|
716
|
+
* corrupt registry entry, a hand-edited env var, …), and a `fs.rm(ref, {
|
|
717
|
+
* recursive: true })` on an unverified path would happily wipe out an
|
|
718
|
+
* arbitrary directory that merely happens to share its name.
|
|
190
719
|
*/
|
|
191
720
|
removeCheckpoint(ref: string): Promise<void>;
|
|
192
721
|
/**
|
|
@@ -233,24 +762,45 @@ export declare class MsbCliBackend implements SandboxBackend {
|
|
|
233
762
|
*/
|
|
234
763
|
exportCheckpoint(ref: string, destFile: string): Promise<void>;
|
|
235
764
|
/**
|
|
236
|
-
* `msb snapshot load <archive>`, then resolves the
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
* the
|
|
765
|
+
* `msb snapshot load <archive> --dest <checkpointsDir>`, then resolves the
|
|
766
|
+
* EFFECTIVE ref: EMPIRICALLY VERIFIED against a real msb 0.7.1 binary, a
|
|
767
|
+
* successful load prints a `group msb-<hex>: head snap_<digest>
|
|
768
|
+
* (Initialized)` line, a digest line, and finally the loaded artifact's
|
|
769
|
+
* own absolute path as its LAST line — `<checkpointsDir>/<generated-
|
|
770
|
+
* group>/snap_<digest>`, never the archive's own recorded `ref` — parsed
|
|
771
|
+
* back out by `parseImportedArtifactPath` (the same last-non-empty-line,
|
|
772
|
+
* must-be-absolute defensive pattern `parseSnapshotCreateArtifactPath`
|
|
773
|
+
* uses for `snapshot create`). `checkpointsDir` is always this backend's
|
|
774
|
+
* own checkpoints cache directory (`<cacheDir>/checkpoints`, the same
|
|
775
|
+
* directory `checkpointRef()` mints created-checkpoint refs under) —
|
|
776
|
+
* omitting `--dest` would import into msb's own global default snapshot
|
|
777
|
+
* store instead, outside anywhere this library looks for or cleans up
|
|
778
|
+
* checkpoints. Created fresh (mkdir -p) before the load, since nothing
|
|
779
|
+
* guarantees a caller ever created a checkpoint locally first (an
|
|
780
|
+
* imported-only machine has no reason to have this directory yet).
|
|
781
|
+
*
|
|
782
|
+
* This replaces the pre-0.7.1 shape entirely: `snapshot load` used to
|
|
783
|
+
* write under a digest-derived directory name with no `--dest` of its
|
|
784
|
+
* own, so this method had to parse out just that bare NAME and then
|
|
785
|
+
* separately CONFIRM it via `msb snapshot list --format json` before
|
|
786
|
+
* handing it back (the only way to get a usable ref at all, since the
|
|
787
|
+
* bare name alone still had to resolve against msb's default store). 0.7.1
|
|
788
|
+
* prints the full, directly-usable, already-`--dest`-scoped path as part
|
|
789
|
+
* of `load`'s own output, so that whole list-and-confirm round trip is
|
|
790
|
+
* gone — dead code that this migration deletes outright, not merely
|
|
791
|
+
* bypassed.
|
|
792
|
+
*
|
|
240
793
|
* An already-exists failure — msb's own content-addressed dedup — is
|
|
241
794
|
* treated as success, since the artifact is already present under that
|
|
242
|
-
* digest either way
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* other snapshot-ref call to keep working. `_ref` (the archive's own
|
|
252
|
-
* recorded ref) is unused here — msb's importer never takes one, unlike
|
|
253
|
-
* docker's, where the effective ref really is the ref passed in.
|
|
795
|
+
* digest either way, and its stderr is parsed the same way as a success's
|
|
796
|
+
* stdout; any OTHER import failure surfaces msb's own stderr in a
|
|
797
|
+
* `BackendError`, and unparseable output (no recognizable absolute last
|
|
798
|
+
* line — e.g. only the "group ... (Initialized)" line, msb's OWN wording
|
|
799
|
+
* for a state that is not itself a path) throws a `BackendError` quoting
|
|
800
|
+
* the raw output verbatim rather than misreading a status line as a ref.
|
|
801
|
+
* `_ref` (the archive's own recorded ref) is unused here — msb's importer
|
|
802
|
+
* never takes one, unlike docker's, where the effective ref really is the
|
|
803
|
+
* ref passed in.
|
|
254
804
|
*/
|
|
255
805
|
importCheckpoint(srcFile: string, _ref: string): Promise<string>;
|
|
256
806
|
private runningSandboxNames;
|