-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheckpoint.go
More file actions
146 lines (129 loc) · 5.64 KB
/
Copy pathcheckpoint.go
File metadata and controls
146 lines (129 loc) · 5.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package devcontainer
import (
"context"
"fmt"
"github.com/crunchloop/devcontainer/runtime"
)
// CheckpointOptions configures Engine.Checkpoint.
type CheckpointOptions struct {
// ArchivePath is where the portable checkpoint archive is written.
// Required. Point it at durable, transferable storage (the workspace
// volume, object storage) — the archive is self-contained, so a
// later Restore can run on a different node by moving this file.
ArchivePath string
// StopAfter stops/removes the container once the archive is written
// — the spot-eviction path, where the node is going away anyway.
// False keeps the container running ("backup" checkpoint).
StopAfter bool
// TCPEstablished requests checkpoint of established TCP connections.
// Recommended true for devcontainers: a container holding a live
// connection at checkpoint time fails to checkpoint without it.
TCPEstablished bool
}
// RestoreOptions configures Engine.Restore.
type RestoreOptions struct {
// ArchivePath is the archive a prior Checkpoint wrote. Required.
ArchivePath string
// Name optionally names the restored container.
Name string
// TCPEstablished must match the checkpoint when the archive captured
// established connections.
TCPEstablished bool
// IgnoreVolumes skips restoring volume content from the archive,
// reusing an existing volume. Leave false for cross-node restore;
// set true for same-node restore-in-place. See
// ProjectRestoreOptions.IgnoreVolumes.
IgnoreVolumes bool
// LocalEnv overrides os.Environ() for the reattached workspace's
// substituter localEnv pass. Nil means use the current process
// environment — matches AttachOptions.LocalEnv. On a cross-node
// restore the destination's env may differ from the source's, so a
// caller that cares can pin it here.
LocalEnv map[string]string
}
// Checkpoint writes a portable checkpoint archive for the workspace's
// container (process + memory state plus the writable rootfs), so it can
// later be restored — possibly on another node — by Restore.
//
// Returns ErrCheckpointUnsupported (wrapped) if the active backend does
// not implement runtime.CheckpointRuntime or advertises
// Capabilities().Checkpoint == false. Callers can errors.Is against
// runtime.ErrCheckpointUnsupported and fall back to a cold path.
//
// Checkpoint is the primitive; deciding *when* to checkpoint (e.g. on a
// spot-reclaim notice) is the caller's job.
func (e *Engine) Checkpoint(ctx context.Context, ws *Workspace, opts CheckpointOptions) (runtime.CheckpointRef, error) {
if err := ctxIfDone(ctx); err != nil {
return runtime.CheckpointRef{}, err
}
if ws == nil || ws.Container == nil {
return runtime.CheckpointRef{}, fmt.Errorf("Checkpoint: workspace has no container")
}
if opts.ArchivePath == "" {
return runtime.CheckpointRef{}, fmt.Errorf("Checkpoint: ArchivePath is required")
}
cr, ok := e.runtime.(runtime.CheckpointRuntime)
if !ok || !e.runtime.Capabilities().Checkpoint {
return runtime.CheckpointRef{}, fmt.Errorf("Checkpoint: %w", runtime.ErrCheckpointUnsupported)
}
ref, err := cr.Checkpoint(ctx, ws.Container.ID, runtime.CheckpointSpec{
ArchivePath: opts.ArchivePath,
StopAfter: opts.StopAfter,
TCPEstablished: opts.TCPEstablished,
})
if err != nil {
return runtime.CheckpointRef{}, fmt.Errorf("checkpoint: %w", err)
}
return ref, nil
}
// Restore re-creates and resumes a container from a checkpoint archive
// written by Checkpoint, reconstructing its mounts and re-attaching
// networking, then rebuilds the *Workspace around it. The original
// container may be gone (the migration case).
//
// The returned Workspace has the MINIMAL config Attach produces — the
// devcontainer labels the checkpoint archive preserves plus the image's
// merged-config metadata — with the substituter bound to the restored
// container's live env and userEnv re-probed. It is enough to drive Exec
// and Down; callers needing the full devcontainer.json view should
// Resolve from source. See the Workspace type docs.
//
// Returns ErrCheckpointUnsupported (wrapped) when the backend can't, and
// a *runtime.RestoreFailedError (from the backend) on a restore failure
// — distinct from a cold-start failure, so callers can fall back to a
// cold Up on the (intact) workspace volume.
func (e *Engine) Restore(ctx context.Context, opts RestoreOptions) (*Workspace, error) {
if err := ctxIfDone(ctx); err != nil {
return nil, err
}
if opts.ArchivePath == "" {
return nil, fmt.Errorf("Restore: ArchivePath is required")
}
cr, ok := e.runtime.(runtime.CheckpointRuntime)
if !ok || !e.runtime.Capabilities().Checkpoint {
return nil, fmt.Errorf("Restore: %w", runtime.ErrCheckpointUnsupported)
}
c, err := cr.Restore(ctx, runtime.RestoreSpec{
ArchivePath: opts.ArchivePath,
Name: opts.Name,
TCPEstablished: opts.TCPEstablished,
IgnoreVolumes: opts.IgnoreVolumes,
})
if err != nil {
return nil, fmt.Errorf("restore: %w", err)
}
// Reattach: the restored container carries the devcontainer labels
// from the archive, so rebuild the Workspace the same way Attach
// does. inspectStable absorbs the post-restore state lag (the daemon
// reports state asynchronously after import-and-start). The workspace
// id is recovered from the container's label.
details, err := e.inspectStable(ctx, c.ID)
if err != nil {
return nil, fmt.Errorf("restore: inspect restored container %s: %w", c.ID, err)
}
id := WorkspaceID(details.Labels[LabelDevcontainerID])
if id == "" {
return nil, fmt.Errorf("restore: restored container %s has no %s label — not a devcontainer workspace archive", c.ID, LabelDevcontainerID)
}
return e.reattachWorkspace(ctx, details, id, opts.LocalEnv), nil
}