Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/atelet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe
sandboxRec.ActorUID = req.GetActorUid()
sandboxRec.ActorTemplateNamespace = req.GetActorTemplateNamespace()
sandboxRec.ActorTemplateName = req.GetActorTemplateName()
sandboxRec.Scope = ateattr.SnapshotScopeValue(req.GetScope())

// No earlier pause snapshot can ever be restored again, so remove them
// all: the actor's current state was just captured by CheckpointWorkload,
Expand Down
26 changes: 25 additions & 1 deletion cmd/atelet/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"testing"
"time"

"github.com/agent-substrate/substrate/internal/ateattr"
"github.com/agent-substrate/substrate/internal/ateerrors"
"github.com/agent-substrate/substrate/internal/ateompath"
"github.com/agent-substrate/substrate/internal/proto/ateletpb"
Expand All @@ -52,18 +53,41 @@ func TestSnapshotManifestActorMetadata(t *testing.T) {
ActorUID: "actor-uid",
ActorTemplateNamespace: "templates",
ActorTemplateName: "agent",
Scope: ateattr.SnapshotScopeFull,
}
got, err := json.Marshal(rec)
if err != nil {
t.Fatal(err)
}
for _, want := range []string{`"atespace":"team-a"`, `"actorName":"actor-1"`, `"actorUid":"actor-uid"`, `"actorTemplateNamespace":"templates"`, `"actorTemplateName":"agent"`} {
for _, want := range []string{`"atespace":"team-a"`, `"actorName":"actor-1"`, `"actorUid":"actor-uid"`, `"actorTemplateNamespace":"templates"`, `"actorTemplateName":"agent"`, `"scope":"full"`} {
if !bytes.Contains(got, []byte(want)) {
t.Errorf("manifest %s missing %s", got, want)
}
}
}

// TestSnapshotManifestScopeAbsent pins backward compatibility: manifests
// written before the scope field existed must still parse, reporting an empty
// scope, and a scope-less record must not serialize a scope key at all.
func TestSnapshotManifestScopeAbsent(t *testing.T) {
legacy := []byte(`{"sandboxClass":"gvisor","snapshotFiles":["checkpoint.img"]}`)
rec, err := unmarshalSandboxRecord(legacy)
if err != nil {
t.Fatalf("unmarshalSandboxRecord(legacy manifest): %v", err)
}
if rec.Scope != "" {
t.Errorf("legacy manifest scope = %q, want empty", rec.Scope)
}

got, err := json.Marshal(sandboxAssetsRecord{SandboxClass: "gvisor"})
if err != nil {
t.Fatal(err)
}
if bytes.Contains(got, []byte(`"scope"`)) {
t.Errorf("scope-less record serialized a scope key: %s", got)
}
}

func TestWriteFileAtomic(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "actor-id")
Expand Down
5 changes: 5 additions & 0 deletions cmd/atelet/sandbox_assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ type sandboxAssetsRecord struct {
// (gVisor's image files, cloud-hypervisor's snapshot set, ...). Empty in the
// on-node record written at Run/Restore; populated at Checkpoint.
SnapshotFiles []string `json:"snapshotFiles,omitempty"`
// Scope is the snapshot scope the checkpoint captured, as the shared
// ateattr label ("full" or "data"), so a snapshot's content is knowable
// from the manifest alone. Empty in the on-node record written at
// Run/Restore and in snapshot manifests written before this field existed.
Scope string `json:"scope,omitempty"`
}

// recordFromRequest projects a request's per-architecture SandboxAssets onto the
Expand Down
Loading