Skip to content
Closed
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
19 changes: 16 additions & 3 deletions cmd/ateapi/internal/apivalidation/actor_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,36 @@ func ValidateCustom_CreateActorTemplateRequest_ActorTemplate(_ context.Context,
value.GetSnapshotConfig().GetPreferredFidelity().String(),
"ROOTFS fidelity requires sandbox_config.sandbox_class SANDBOX_CLASS_MICROVM"))
}
declared := make(map[string]bool, len(value.GetVolumes()))
declared := make(map[string]*ateapipb.Volume, len(value.GetVolumes()))
for _, vol := range value.GetVolumes() {
declared[vol.GetName()] = true
declared[vol.GetName()] = vol
}
canCaptureVolumes := false
for i, ctr := range value.GetContainers() {
for j, mount := range ctr.GetVolumeMounts() {
name := mount.GetName()
if name == "" {
continue // required is enforced by tags
}
if !declared[name] {
volume := declared[name]
if volume == nil {
errs = append(errs, field.Invalid(
fldPath.Child("containers").Index(i).Child("volume_mounts").Index(j).Child("name"),
name, "must reference a volume declared in the template"))
continue
}
if volume.GetDurableDir() != nil ||
(value.GetSandboxConfig().GetSandboxClass() == ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM && volume.GetExternalVolumeTemplate() != nil) {
canCaptureVolumes = true
}
}
}
if value.GetSnapshotConfig().GetPreferredFidelity() == ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES && !canCaptureVolumes {
errs = append(errs, field.Invalid(
fldPath.Child("snapshot_config", "preferred_fidelity"),
value.GetSnapshotConfig().GetPreferredFidelity().String(),
"VOLUMES fidelity requires a mounted durable-dir volume, or a mounted external volume on MICROVM"))
}
return errs
}

Expand Down
41 changes: 40 additions & 1 deletion cmd/ateapi/internal/apivalidation/actor_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,50 @@ func TestValidateCreateActorTemplateRequest(t *testing.T) {
})},
field.ErrorList{field.Invalid(field.NewPath("actor_template", "metadata", "name"), "Tmpl_A", "").WithOrigin("format=k8s-short-name")},
}, {
"valid volumes fidelity",
"valid volumes fidelity with a mounted durable dir",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
tmpl.Volumes = []*ateapipb.Volume{{Name: "data", DurableDir: &ateapipb.DurableDirVolumeSource{}}}
tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "data", MountPath: "/data"}}
})},
nil,
}, {
"volumes fidelity without volumes rejected",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
})},
field.ErrorList{field.Invalid(field.NewPath("actor_template", "snapshot_config", "preferred_fidelity"), "SNAPSHOT_FIDELITY_VOLUMES", "")},
}, {
"volumes fidelity with an unmounted durable dir rejected",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
tmpl.Volumes = []*ateapipb.Volume{{Name: "data", DurableDir: &ateapipb.DurableDirVolumeSource{}}}
})},
field.ErrorList{field.Invalid(field.NewPath("actor_template", "snapshot_config", "preferred_fidelity"), "SNAPSHOT_FIDELITY_VOLUMES", "")},
}, {
"volumes fidelity with CSI only on gvisor rejected",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
tmpl.Volumes = []*ateapipb.Volume{{Name: "data", ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{Capacity: "10Gi", StorageClassName: "fast-ssd"}}}
tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "data", MountPath: "/data"}}
})},
field.ErrorList{field.Invalid(field.NewPath("actor_template", "snapshot_config", "preferred_fidelity"), "SNAPSHOT_FIDELITY_VOLUMES", "")},
}, {
"volumes fidelity with CSI only on microvm accepted",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
tmpl.SandboxConfig = &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM, ConfigName: "microvm-default"}
tmpl.Volumes = []*ateapipb.Volume{{Name: "data", ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{Capacity: "10Gi", StorageClassName: "fast-ssd"}}}
tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "data", MountPath: "/data"}}
})},
nil,
}, {
"volumes fidelity without volumes on microvm rejected",
&ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) {
tmpl.SnapshotConfig.PreferredFidelity = ateapipb.SnapshotFidelity_SNAPSHOT_FIDELITY_VOLUMES
tmpl.SandboxConfig = &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM, ConfigName: "microvm-default"}
})},
field.ErrorList{field.Invalid(field.NewPath("actor_template", "snapshot_config", "preferred_fidelity"), "SNAPSHOT_FIDELITY_VOLUMES", "")},
}, {
// Only the micro-VM runtime captures rootfs changes without memory;
// the fixture is a gVisor template.
Expand Down
8 changes: 5 additions & 3 deletions cmd/ateapi/internal/controlapi/functionaltest/actor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3046,10 +3046,12 @@ func createDataFidelityTemplate(t *testing.T, tc *testContext, ns string) *ateap
SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR,
ConfigName: "gvisor-default",
},
Volumes: []*ateapipb.Volume{{Name: "data", DurableDir: &ateapipb.DurableDirVolumeSource{}}},
Containers: []*ateapipb.Container{{
Name: "main",
Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
Command: []string{"/main"},
Name: "main",
Image: "main@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
Command: []string{"/main"},
VolumeMounts: []*ateapipb.VolumeMount{{Name: "data", MountPath: "/data"}},
}},
WorkerSelector: &ateapipb.Selector{
MatchLabels: map[string]string{poolLabelKey: ns},
Expand Down
4 changes: 4 additions & 0 deletions docs/api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,10 @@ When an `ActorTemplate` is created with golden snapshots enabled:

`CreateActor` uses an explicit `sourceTag` when supplied; otherwise it resolves the template's golden tag and records that snapshot on the new actor. If the golden tag is not ready yet, the actor starts without a snapshot and cold-boots even if the tag becomes ready before its first resume. The default does not populate the caller-owned `sourceTag` field. Deleting the template collects its golden tag and any unfinished golden actor.

VOLUMES templates require a volume mounted by a container. gVisor requires a
durable-dir mount; micro-VM also supports mounted external volumes. A declared
but unmounted volume does not satisfy this requirement.

### Resumption Lifecycle
Once a template is `Ready`, creating an actor logically (via `kubectl ate create actor`) allows it to be resumed instantly on any free worker in the referenced `WorkerPool`. Substrate bypasses the standard container boot and restores the process directly from its last saved state.

Expand Down
Loading