diff --git a/cmd/ateapi/internal/controlapi/workflow_pause.go b/cmd/ateapi/internal/controlapi/workflow_pause.go index 4499ac6a..4152de44 100644 --- a/cmd/ateapi/internal/controlapi/workflow_pause.go +++ b/cmd/ateapi/internal/controlapi/workflow_pause.go @@ -142,7 +142,8 @@ func (s *CallAteletPauseStep) Execute(ctx context.Context, input *PauseInput, st SnapshotPrefix: state.Actor.InProgressSnapshot, }, }, - Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnPause), + Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnPause), + ActorUid: state.Actor.GetMetadata().Uid, } _, err = client.Checkpoint(ctx, req) diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 3b7c9ab5..2ebaa9fe 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -276,6 +276,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, ActorTemplateNamespace: state.Actor.GetActorTemplateNamespace(), ActorTemplateName: state.Actor.GetActorTemplateName(), Spec: workloadSpec, + ActorUid: state.Actor.GetMetadata().Uid, } switch d := data.(type) { case *ateapipb.SnapshotInfo_Local: @@ -318,7 +319,8 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, SnapshotUriPrefix: snapshot, }, }, - Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnCommit), + Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnCommit), + ActorUid: state.Actor.GetMetadata().Uid, } _, err = client.Restore(ctx, req) return maybeCrashActor(ctx, s.store, input.Atespace, input.ActorName, err, "while creating workload from golden snapshot") @@ -341,6 +343,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, ActorTemplateName: state.Actor.GetActorTemplateName(), SandboxAssets: sandboxAssets, Spec: workloadSpec, + ActorUid: state.Actor.GetMetadata().Uid, } _, err = client.Run(ctx, req) return maybeCrashActor(ctx, s.store, input.Atespace, input.ActorName, err, "while creating workload from spec") diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend.go b/cmd/ateapi/internal/controlapi/workflow_suspend.go index 6a4cc6cc..39d5c161 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend.go @@ -144,7 +144,8 @@ func (s *CallAteletSuspendStep) Execute(ctx context.Context, input *SuspendInput SnapshotUriPrefix: state.Actor.GetInProgressSnapshot(), }, }, - Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnCommit), + Scope: toAteletSnapshotScope(state.ActorTemplate.Spec.SnapshotsConfig.OnCommit), + ActorUid: state.Actor.GetMetadata().Uid, } _, err = client.Checkpoint(ctx, req) diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index 5a3572da..ad274d7f 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -219,7 +219,7 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele return nil, status.Error(codes.InvalidArgument, err.Error()) } - atespace, actorName := req.GetAtespace(), req.GetActorName() + actorUID, atespace, actorName := req.GetActorUid(), req.GetAtespace(), req.GetActorName() sandboxRec, err := recordFromRequest(req.GetSandboxAssets()) if err != nil { @@ -230,18 +230,18 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele return nil, err } - if err := resetActorDirs(atespace, actorName); err != nil { + if err := resetActorDirs(actorUID); err != nil { return nil, fmt.Errorf("while resetting actor dirs: %w", err) } // Record the sandbox binaries this actor is running so a later Checkpoint // (whose request no longer carries the sandbox config) can re-fetch the same // version and pin it into the snapshot manifest. - if err := writeSandboxRecord(atespace, actorName, sandboxRec); err != nil { + if err := writeSandboxRecord(actorUID, sandboxRec); err != nil { return nil, fmt.Errorf("while recording sandbox assets: %w", err) } - if err := s.prepareOCIBundles(ctx, atespace, actorName, + if err := s.prepareOCIBundles(ctx, actorUID, actorName, req.GetSpec(), req.GetTargetAteomUid(), ); err != nil { return nil, err @@ -262,6 +262,7 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele RunscPath: runscPathFor(assetPaths), RuntimeAssetPaths: assetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), + ActorUid: actorUID, }); err != nil { return nil, fmt.Errorf("while calling ateom.RunWorkload: %w", err) } @@ -310,13 +311,13 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe return nil, status.Error(codes.InvalidArgument, err.Error()) } - atespace, actorName := req.GetAtespace(), req.GetActorName() + actorUID, atespace, actorName := req.GetActorUid(), req.GetAtespace(), req.GetActorName() // Checkpoint requests no longer carry the sandbox config; recover the // version this actor was started with from the on-node record and re-fetch // it (a cache hit) so ateom can drive runsc, and so we can pin it into the // snapshot manifest below. - sandboxRec, err := readSandboxRecord(atespace, actorName) + sandboxRec, err := readSandboxRecord(actorUID) if err != nil { return nil, ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonInvalidSandboxAsset, ateerrors.ReasonTerminalFileSystemError) } @@ -325,7 +326,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe return nil, ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonInvalidSandboxAsset, ateerrors.ReasonTerminalFileSystemError, ateerrors.ReasonFailedGetExternalObject, ateerrors.ReasonInvalidObjectURL) } - checkpointDir := ateompath.CheckpointStateDir(atespace, actorName) + checkpointDir := ateompath.CheckpointStateDir(actorUID) client, err := s.dialAteom(ctx, req.GetTargetAteomUid()) if err != nil { @@ -344,6 +345,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe RuntimeAssetPaths: assetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), Scope: toAteomSnapshotScope(req.GetScope()), + ActorUid: actorUID, }) if err != nil { // TODO: Ateom should classify checkpoint failures, and set "should-crash" @@ -370,7 +372,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe } // Note: we do not crash the actor if resetting the directory fails. - if err := resetActorDirs(atespace, actorName); err != nil { + if err := resetActorDirs(actorUID); err != nil { return nil, fmt.Errorf("while resetting actor dirs: %w", err) } @@ -388,7 +390,7 @@ func toAteomSnapshotScope(scope ateletpb.SnapshotScope) ateompb.SnapshotScope { } func (s *AteomHerder) moveLocalCheckpoint(ctx context.Context, req *ateletpb.CheckpointRequest, checkpointDir string, rec *sandboxAssetsRecord) error { - localCheckpointPath := filepath.Join(ateompath.LocalCheckpointsDir(req.GetAtespace(), req.GetActorName()), req.GetLocalConfig().GetSnapshotPrefix()) + localCheckpointPath := filepath.Join(ateompath.LocalCheckpointsDir(req.GetActorUid()), req.GetLocalConfig().GetSnapshotPrefix()) if err := os.MkdirAll(localCheckpointPath, 0o700); err != nil { return fmt.Errorf("while creating local checkpoint directory: %w", err) } @@ -454,15 +456,15 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return nil, status.Error(codes.InvalidArgument, err.Error()) } - atespace, actorName := req.GetAtespace(), req.GetActorName() + actorUID, atespace, actorName := req.GetActorUid(), req.GetAtespace(), req.GetActorName() // Not crashing the actor, because terminal errors here indicate problems with atelet, // node or the disk itself. - if err := resetActorDirs(atespace, actorName); err != nil { + if err := resetActorDirs(actorUID); err != nil { return nil, fmt.Errorf("while resetting actor dirs: %w", err) } - checkpointDir := ateompath.RestoreStateDir(atespace, actorName) + checkpointDir := ateompath.RestoreStateDir(actorUID) // Per-step timing so we can attribute resume latency between the rustfs // download/decompress, the OCI image unpack, and ateom's own work. Logged at the end. @@ -485,7 +487,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return nil, ateerrors.CrashIfReason(ctx, fmt.Errorf("while unmarshalling sandbox record: %w", err), ateerrors.ReasonInvalidSandboxAsset) } case ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL: - localCheckpointDir := ateompath.LocalCheckpointsDir(atespace, actorName) + localCheckpointDir := ateompath.LocalCheckpointsDir(actorUID) snapshotPrefix := req.GetLocalConfig().GetSnapshotPrefix() manifest, err := os.ReadFile(filepath.Join(localCheckpointDir, snapshotPrefix, sandboxManifestName)) if err != nil { @@ -518,7 +520,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonFailedGetExternalObject, ateerrors.ReasonInvalidObjectURL, ateerrors.ReasonTerminalFileSystemError) } case ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL: - if err := s.copyLocalCheckpoint(gctx, req.GetLocalConfig().GetSnapshotPrefix(), ateompath.LocalCheckpointsDir(atespace, actorName), checkpointDir, sandboxRec.SnapshotFiles); err != nil { + if err := s.copyLocalCheckpoint(gctx, req.GetLocalConfig().GetSnapshotPrefix(), ateompath.LocalCheckpointsDir(actorUID), checkpointDir, sandboxRec.SnapshotFiles); err != nil { return ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonTerminalFileSystemError) } } @@ -531,7 +533,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonFailedGetExternalObject, ateerrors.ReasonInvalidObjectURL, ateerrors.ReasonTerminalFileSystemError, ateerrors.ReasonInvalidSandboxAsset) } t := time.Now() - if err := s.prepareOCIBundles(gctx, atespace, actorName, req.GetSpec(), req.GetTargetAteomUid()); err != nil { + if err := s.prepareOCIBundles(gctx, actorUID, actorName, req.GetSpec(), req.GetTargetAteomUid()); err != nil { return ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonTerminalFileSystemError) } dBundles = time.Since(t) @@ -558,6 +560,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) RuntimeAssetPaths: assetPaths, Spec: buildAteomWorkloadSpec(req.GetSpec()), Scope: toAteomSnapshotScope(req.GetScope()), + ActorUid: req.GetActorUid(), }); err != nil { // TODO: classify the errors returned by Ateom and crash the actor if needed. return nil, fmt.Errorf("while calling ateom.RestoreWorkload: %w", err) @@ -566,7 +569,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) // Record the (manifest-pinned) sandbox binaries on-node so a subsequent // Checkpoint of this restored actor can re-pin the same version. - if err := writeSandboxRecord(atespace, actorName, sandboxRec); err != nil { + if err := writeSandboxRecord(actorUID, sandboxRec); err != nil { // Note: crash the actor right away, if we cannot write the sandbox record now, we will not be able to checkpoint it later. return nil, ateerrors.CrashIfReason(ctx, err, ateerrors.ReasonTerminalFileSystemError) } @@ -643,14 +646,15 @@ func (s *AteomHerder) downloadExternalCheckpoint(ctx context.Context, snapshotUr // container and every application container in spec, in parallel. func (s *AteomHerder) prepareOCIBundles( ctx context.Context, - atespace, actorName string, + actorUID string, + actorName string, spec *ateletpb.WorkloadSpec, targetAteomUid string, ) error { // Populate the per-actor nameentity directory that gets bind-mounted into // the application containers. Regenerated on every resume, so it carries // the correct per-actor name even when restoring from the golden snapshot. - identityDir := ateompath.ActorIdentityDirPath(atespace, actorName) + identityDir := ateompath.ActorIdentityDirPath(actorUID) if err := os.MkdirAll(identityDir, 0o755); err != nil { return fmt.Errorf("while creating actor nameentity dir: %w", err) } @@ -663,7 +667,7 @@ func (s *AteomHerder) prepareOCIBundles( for _, vol := range spec.GetVolumes() { if vol.GetType() == ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR { ddVolumes[vol.GetName()] = true - volPath := ateompath.DurableDirVolumeMountPoint(atespace, actorName, vol.GetName()) + volPath := ateompath.DurableDirVolumeMountPoint(actorUID, vol.GetName()) if err := os.MkdirAll(volPath, 0o700); err != nil { return fmt.Errorf("while creating %q: %w", volPath, err) } @@ -684,14 +688,14 @@ func (s *AteomHerder) prepareOCIBundles( if vol.GetType() == ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR { annotations["dev.gvisor.spec.mount.durabledir.type"] = "bind" annotations["dev.gvisor.spec.mount.durabledir.share"] = "container" - annotations["dev.gvisor.spec.mount.durabledir.source"] = ateompath.DurableDirVolumeMountPoint(atespace, actorName, vol.GetName()) + annotations["dev.gvisor.spec.mount.durabledir.source"] = ateompath.DurableDirVolumeMountPoint(actorUID, vol.GetName()) } } if err := prepareOCIDirectory( gCtx, s.pullCache, - atespace, actorName, + actorUID, "pause", spec.GetPauseImage(), []string{"/pause"}, @@ -723,7 +727,7 @@ func (s *AteomHerder) prepareOCIBundles( if err := prepareOCIDirectory( gCtx, s.pullCache, - atespace, actorName, + actorUID, ctr.GetName(), ctr.GetImage(), ctr.GetCommand(), @@ -838,6 +842,7 @@ func validateRunRequest(req *ateletpb.RunRequest) error { var errs field.ErrorList errs = append(errs, resources.ValidateResourceName(req.GetAtespace(), field.NewPath("atespace"))...) errs = append(errs, resources.ValidateResourceName(req.GetActorName(), field.NewPath("actor_name"))...) + errs = append(errs, resources.ValidateResourceName(req.GetActorUid(), field.NewPath("actor_uid"))...) for _, msg := range content.IsDNS1123Label(req.GetActorTemplateNamespace()) { errs = append(errs, field.Invalid(field.NewPath("actor_template_namespace"), req.GetActorTemplateNamespace(), msg)) } @@ -862,6 +867,7 @@ func validateCheckpointRequest(req *ateletpb.CheckpointRequest) error { var errs field.ErrorList errs = append(errs, resources.ValidateResourceName(req.GetAtespace(), field.NewPath("atespace"))...) errs = append(errs, resources.ValidateResourceName(req.GetActorName(), field.NewPath("actor_name"))...) + errs = append(errs, resources.ValidateResourceName(req.GetActorUid(), field.NewPath("actor_uid"))...) for _, msg := range content.IsDNS1123Label(req.GetActorTemplateNamespace()) { errs = append(errs, field.Invalid(field.NewPath("actor_template_namespace"), req.GetActorTemplateNamespace(), msg)) } @@ -906,6 +912,7 @@ func validateRestoreRequest(req *ateletpb.RestoreRequest) error { var errs field.ErrorList errs = append(errs, resources.ValidateResourceName(req.GetAtespace(), field.NewPath("atespace"))...) errs = append(errs, resources.ValidateResourceName(req.GetActorName(), field.NewPath("actor_name"))...) + errs = append(errs, resources.ValidateResourceName(req.GetActorUid(), field.NewPath("actor_uid"))...) for _, msg := range content.IsDNS1123Label(req.GetActorTemplateNamespace()) { errs = append(errs, field.Invalid(field.NewPath("actor_template_namespace"), req.GetActorTemplateNamespace(), msg)) } @@ -997,13 +1004,13 @@ func writeFileAtomic(path string, data []byte, perm os.FileMode) error { return dir.Sync() } -func resetActorDirs(atespace, actorName string) error { +func resetActorDirs(actorUID string) error { // Explicitly leave runsc logs dir untouched. // removeAllWritable, not os.RemoveAll: the bundle holds unpacked actor-image // rootfs whose directories keep the image's (possibly read-only) modes, which // atelet can't remove as plain root without first making them writable. - bundleDir := ateompath.OCIBundleDir(atespace, actorName) + bundleDir := ateompath.OCIBundleDir(actorUID) if err := removeAllWritable(bundleDir); err != nil { return wrapFileSystemErr("while deleting bundle dir: %w", err) } @@ -1011,7 +1018,7 @@ func resetActorDirs(atespace, actorName string) error { return wrapFileSystemErr("while creating bundle dir: %w", err) } - runscDir := ateompath.RunSCStateDir(atespace, actorName) + runscDir := ateompath.RunSCStateDir(actorUID) if err := os.RemoveAll(runscDir); err != nil { return wrapFileSystemErr("while deleting runsc state dir: %w", err) } @@ -1019,7 +1026,7 @@ func resetActorDirs(atespace, actorName string) error { return wrapFileSystemErr("while creating runsc state dir: %w", err) } - pidFileDir := ateompath.PIDFileDir(atespace, actorName) + pidFileDir := ateompath.PIDFileDir(actorUID) if err := os.RemoveAll(pidFileDir); err != nil { return wrapFileSystemErr("while deleting PID file dir: %w", err) } @@ -1027,7 +1034,7 @@ func resetActorDirs(atespace, actorName string) error { return wrapFileSystemErr("while creating PID file dir: %w", err) } - checkpointDir := ateompath.CheckpointStateDir(atespace, actorName) + checkpointDir := ateompath.CheckpointStateDir(actorUID) if err := os.RemoveAll(checkpointDir); err != nil { return wrapFileSystemErr("while deleting checkpoint-state dir: %w", err) } @@ -1035,7 +1042,7 @@ func resetActorDirs(atespace, actorName string) error { return wrapFileSystemErr("while creating checkpoint-state dir: %w", err) } - restoreStateDir := ateompath.RestoreStateDir(atespace, actorName) + restoreStateDir := ateompath.RestoreStateDir(actorUID) if err := os.RemoveAll(restoreStateDir); err != nil { return wrapFileSystemErr("while deleting restore-state dir: %w", err) } @@ -1045,7 +1052,7 @@ func resetActorDirs(atespace, actorName string) error { // World-readable (0o755): bind-mounted into the actor, whose workload // reads it through the gofer. - identityDir := ateompath.ActorIdentityDirPath(atespace, actorName) + identityDir := ateompath.ActorIdentityDirPath(actorUID) if err := os.RemoveAll(identityDir); err != nil { return wrapFileSystemErr("while deleting actor nameentity dir: %w", err) } @@ -1053,7 +1060,7 @@ func resetActorDirs(atespace, actorName string) error { return wrapFileSystemErr("while creating actor nameentity dir: %w", err) } - durableDirVolumesMountDir := ateompath.DurableDirVolumeMountsDir(atespace, actorName) + durableDirVolumesMountDir := ateompath.DurableDirVolumeMountsDir(actorUID) if err := os.RemoveAll(durableDirVolumesMountDir); err != nil { return wrapFileSystemErr("while deleting durable-dir volumes mount dir: %w", err) } diff --git a/cmd/atelet/main_test.go b/cmd/atelet/main_test.go index fcbac250..ad0785f7 100644 --- a/cmd/atelet/main_test.go +++ b/cmd/atelet/main_test.go @@ -96,6 +96,7 @@ func validRunRequest() *ateletpb.RunRequest { ActorTemplateNamespace: "ate-demo", ActorTemplateName: "counter", TargetAteomUid: "422938ba-8860-4983-a25d-d6bcb0a69d4e", + ActorUid: "123e4567-e89b-12d3-a456-426614174000", Spec: &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "worker"}}}, } } @@ -107,6 +108,7 @@ func validCheckpointRequest() *ateletpb.CheckpointRequest { ActorTemplateNamespace: "ate-demo", ActorTemplateName: "counter", TargetAteomUid: "422938ba-8860-4983-a25d-d6bcb0a69d4e", + ActorUid: "123e4567-e89b-12d3-a456-426614174000", Spec: &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "worker"}}}, Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL, Config: &ateletpb.CheckpointRequest_ExternalConfig{ @@ -125,6 +127,7 @@ func validRestoreRequest() *ateletpb.RestoreRequest { ActorTemplateNamespace: "ate-demo", ActorTemplateName: "counter", TargetAteomUid: "422938ba-8860-4983-a25d-d6bcb0a69d4e", + ActorUid: "123e4567-e89b-12d3-a456-426614174000", Spec: &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "worker"}}}, Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL, Config: &ateletpb.RestoreRequest_ExternalConfig{ @@ -146,6 +149,7 @@ func TestValidateRunRequest(t *testing.T) { {"invalid ateom uid", func(r *ateletpb.RunRequest) { r.TargetAteomUid = "../escape" }, true}, {"invalid atespace", func(r *ateletpb.RunRequest) { r.Atespace = "../escape" }, true}, {"invalid actor name", func(r *ateletpb.RunRequest) { r.ActorName = "../escape" }, true}, + {"invalid actor uid", func(r *ateletpb.RunRequest) { r.ActorUid = "../escape" }, true}, {"invalid actor template namespace", func(r *ateletpb.RunRequest) { r.ActorTemplateNamespace = "Not_Valid" }, true}, {"invalid actor template name", func(r *ateletpb.RunRequest) { r.ActorTemplateName = "Not_Valid" }, true}, {"invalid container name", func(r *ateletpb.RunRequest) { @@ -185,6 +189,7 @@ func TestValidateCheckpointRequest(t *testing.T) { {"invalid ateom uid", makeReq(func(r *ateletpb.CheckpointRequest) { r.TargetAteomUid = "../escape" }), true}, {"invalid atespace", makeReq(func(r *ateletpb.CheckpointRequest) { r.Atespace = "../escape" }), true}, {"invalid actor name", makeReq(func(r *ateletpb.CheckpointRequest) { r.ActorName = "../escape" }), true}, + {"invalid actor uid", makeReq(func(r *ateletpb.CheckpointRequest) { r.ActorUid = "../escape" }), true}, {"invalid actor template namespace", makeReq(func(r *ateletpb.CheckpointRequest) { r.ActorTemplateNamespace = "Not_Valid" }), true}, {"invalid actor template name", makeReq(func(r *ateletpb.CheckpointRequest) { r.ActorTemplateName = "Not_Valid" }), true}, {"invalid container name", makeReq(func(r *ateletpb.CheckpointRequest) { @@ -227,6 +232,7 @@ func TestValidateRestoreRequest(t *testing.T) { {"invalid ateom uid", makeReq(func(r *ateletpb.RestoreRequest) { r.TargetAteomUid = "../escape" }), true}, {"invalid atespace", makeReq(func(r *ateletpb.RestoreRequest) { r.Atespace = "../escape" }), true}, {"invalid actor name", makeReq(func(r *ateletpb.RestoreRequest) { r.ActorName = "../escape" }), true}, + {"invalid actor uid", makeReq(func(r *ateletpb.RestoreRequest) { r.ActorUid = "../escape" }), true}, {"invalid actor template namespace", makeReq(func(r *ateletpb.RestoreRequest) { r.ActorTemplateNamespace = "Not_Valid" }), true}, {"invalid actor template name", makeReq(func(r *ateletpb.RestoreRequest) { r.ActorTemplateName = "Not_Valid" }), true}, {"invalid container name", makeReq(func(r *ateletpb.RestoreRequest) { @@ -415,7 +421,7 @@ func TestRPCBoundariesReject(t *testing.T) { s := &AteomHerder{} ctx := context.Background() badUID := "../escape" // valid actor ref, invalid ateom UID - const okAtespace, okID = "ate-demo", "counter-1" + const okAtespace, okID, okActorUID = "ate-demo", "counter-1", "123e4567-e89b-12d3-a456-426614174000" okSpec := &ateletpb.WorkloadSpec{Containers: []*ateletpb.Container{{Name: "worker"}}} wantInvalidArgument := func(t *testing.T, rpc string, err error) { @@ -432,21 +438,21 @@ func TestRPCBoundariesReject(t *testing.T) { t.Run("Run", func(t *testing.T) { _, err := s.Run(ctx, &ateletpb.RunRequest{ Atespace: okAtespace, ActorName: okID, - TargetAteomUid: badUID, Spec: okSpec, + ActorUid: okActorUID, TargetAteomUid: badUID, Spec: okSpec, }) wantInvalidArgument(t, "Run", err) }) t.Run("Checkpoint", func(t *testing.T) { _, err := s.Checkpoint(ctx, &ateletpb.CheckpointRequest{ Atespace: okAtespace, ActorName: okID, - TargetAteomUid: badUID, Spec: okSpec, + ActorUid: okActorUID, TargetAteomUid: badUID, Spec: okSpec, }) wantInvalidArgument(t, "Checkpoint", err) }) t.Run("Restore", func(t *testing.T) { _, err := s.Restore(ctx, &ateletpb.RestoreRequest{ Atespace: okAtespace, ActorName: okID, - TargetAteomUid: badUID, Spec: okSpec, + ActorUid: okActorUID, TargetAteomUid: badUID, Spec: okSpec, }) wantInvalidArgument(t, "Restore", err) }) diff --git a/cmd/atelet/oci.go b/cmd/atelet/oci.go index 9dcb2e00..69918393 100644 --- a/cmd/atelet/oci.go +++ b/cmd/atelet/oci.go @@ -56,14 +56,14 @@ const ( ActorIDFileName = "actor-id" ) -func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryPullCache, atespace, actorName, containerName, ref string, args []string, env []string, annotations map[string]string, netns string, identityDir string, durableDirVolumeMounts []*ateletpb.VolumeMount) error { +func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryPullCache, actorUID, containerName, ref string, args []string, env []string, annotations map[string]string, netns string, identityDir string, durableDirVolumeMounts []*ateletpb.VolumeMount) error { tracer := otel.Tracer("prepareOCIDirectory") ctx, span := tracer.Start(ctx, "prepareOCIDirectory") span.SetAttributes(attribute.String("image", ref)) defer span.End() - bundlePath := ateompath.OCIBundlePath(atespace, actorName, containerName) + bundlePath := ateompath.OCIBundlePath(actorUID, containerName) rootPath := path.Join(bundlePath, "rootfs") if err := removeAllWritable(rootPath); err != nil { @@ -93,7 +93,7 @@ func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryP } } - ociSpec := buildActorOCISpec(atespace, actorName, imageCfg, args, env, annotations, netns, identityDir, durableDirVolumeMounts) + ociSpec := buildActorOCISpec(actorUID, imageCfg, args, env, annotations, netns, identityDir, durableDirVolumeMounts) ociSpecBytes, err := json.MarshalIndent(ociSpec, "", " ") if err != nil { return fmt.Errorf("while marshaling OCI spec: %w", err) @@ -136,7 +136,7 @@ func mergeActorEnv(imageEnv, templateEnv []string) []string { // When identityDir is non-empty it adds a read-only bind mount of that host // directory at IdentityMountPath so the actor can read its own ID (see // IdentityMountPath for why this is a bind mount rather than env vars). -func buildActorOCISpec(atespace string, actorName string, imageCfg *v1.Config, args []string, env []string, annotations map[string]string, netns string, identityDir string, durableDirVolumeMounts []*ateletpb.VolumeMount) *specs.Spec { +func buildActorOCISpec(actorUID string, imageCfg *v1.Config, args []string, env []string, annotations map[string]string, netns string, identityDir string, durableDirVolumeMounts []*ateletpb.VolumeMount) *specs.Spec { var imageEnv []string if imageCfg != nil { imageEnv = imageCfg.Env @@ -255,7 +255,7 @@ func buildActorOCISpec(atespace string, actorName string, imageCfg *v1.Config, a spec.Mounts = append(spec.Mounts, specs.Mount{ Destination: vm.GetMountPath(), Type: "bind", - Source: ateompath.DurableDirVolumeMountPoint(atespace, actorName, vm.GetName()), + Source: ateompath.DurableDirVolumeMountPoint(actorUID, vm.GetName()), }) } diff --git a/cmd/atelet/oci_test.go b/cmd/atelet/oci_test.go index 9eca4901..6cb1b902 100644 --- a/cmd/atelet/oci_test.go +++ b/cmd/atelet/oci_test.go @@ -87,13 +87,13 @@ func runUntar(t *testing.T, entries []tarEntry) (string, error) { // With an identity dir, a read-only bind mount appears at IdentityMountPath. func TestBuildActorOCISpec_IdentityMount(t *testing.T) { spec := buildActorOCISpec( - "atespace", "id", + "actor_uid", nil, []string{"/app"}, []string{"FOO=bar"}, map[string]string{"k": "v"}, "/run/netns/x", - "/host/actors/atespace:id/identity", + "/host/actors/actor_uid/identity", nil, ) found := false @@ -102,7 +102,7 @@ func TestBuildActorOCISpec_IdentityMount(t *testing.T) { continue } found = true - if m.Source != "/host/actors/atespace:id/identity" { + if m.Source != "/host/actors/actor_uid/identity" { t.Errorf("identity mount source = %q, want the per-actor nameentity dir", m.Source) } if m.Type != "bind" { @@ -167,7 +167,7 @@ func TestMergeActorEnv(t *testing.T) { // Without an identity dir (the pause container), no identity mount appears. func TestBuildActorOCISpec_NoIdentityMountForPause(t *testing.T) { - bare := buildActorOCISpec("atespace", "id", nil, []string{"/pause"}, nil, nil, "/run/netns/x", "", nil) + bare := buildActorOCISpec("actor_uid", nil, []string{"/pause"}, nil, nil, "/run/netns/x", "", nil) for _, m := range bare.Mounts { if m.Destination == IdentityMountPath { t.Errorf("identity mount must be absent when identityDir is empty") @@ -178,13 +178,13 @@ func TestBuildActorOCISpec_NoIdentityMountForPause(t *testing.T) { // Each durable-dir volume mount becomes a bind mount whose source is the // per-actor on-host DurableDirVolumeMountPoint for that volume name. func TestBuildActorOCISpec_DurableDirVolumeMounts(t *testing.T) { - const atespace, id = "atespace", "id" + const actorUID = "actor_uid" durableDirs := []*ateletpb.VolumeMount{ {Name: "data", MountPath: "/var/data"}, {Name: "cache", MountPath: "/var/cache"}, } spec := buildActorOCISpec( - atespace, id, + actorUID, nil, []string{"/app"}, nil, nil, "/run/netns/x", "", @@ -192,7 +192,7 @@ func TestBuildActorOCISpec_DurableDirVolumeMounts(t *testing.T) { ) for _, vm := range durableDirs { - wantSrc := ateompath.DurableDirVolumeMountPoint(atespace, id, vm.Name) + wantSrc := ateompath.DurableDirVolumeMountPoint(actorUID, vm.Name) found := false for _, m := range spec.Mounts { if m.Destination != vm.MountPath { diff --git a/cmd/atelet/sandbox_assets.go b/cmd/atelet/sandbox_assets.go index 6c669a52..0154837c 100644 --- a/cmd/atelet/sandbox_assets.go +++ b/cmd/atelet/sandbox_assets.go @@ -206,12 +206,12 @@ func (s *AteomHerder) openAsset(ctx context.Context, url string) (io.ReadCloser, // writeSandboxRecord persists the actor's running sandbox assets on-node so a // later Checkpoint (whose request no longer carries the sandbox config) can // re-fetch the same binaries and pin them into the snapshot manifest. -func writeSandboxRecord(atespace, actorName string, rec *sandboxAssetsRecord) error { +func writeSandboxRecord(actorUID string, rec *sandboxAssetsRecord) error { data, err := json.Marshal(rec) if err != nil { return wrapFileSystemErr("while marshaling sandbox record", err) } - path := ateompath.ActorSandboxAssetsFile(atespace, actorName) + path := ateompath.ActorSandboxAssetsFile(actorUID) if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { return wrapFileSystemErr("while creating actor dir", err) } @@ -223,8 +223,8 @@ func writeSandboxRecord(atespace, actorName string, rec *sandboxAssetsRecord) er // readSandboxRecord loads the actor's on-node sandbox record written at // Run/Restore. -func readSandboxRecord(atespace, actorName string) (*sandboxAssetsRecord, error) { - path := ateompath.ActorSandboxAssetsFile(atespace, actorName) +func readSandboxRecord(actorUID string) (*sandboxAssetsRecord, error) { + path := ateompath.ActorSandboxAssetsFile(actorUID) data, err := os.ReadFile(path) if err != nil { return nil, wrapFileSystemErr("while reading sandbox record", err) diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index 5e856e7b..6d012945 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -181,7 +181,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload s.lock.Lock() defer s.lock.Unlock() - s.actorLogger.EmitLifecycleLog("Actor starting", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor starting", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) // Contract with atelet: // @@ -198,9 +198,8 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload }() rcmd := &runsc{ - path: req.GetRunscPath(), - atespace: req.GetAtespace(), - actorName: req.GetActorName(), + path: req.GetRunscPath(), + actorUID: req.GetActorUid(), } // Create and start pause container @@ -214,7 +213,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload // Create and start each application container, each with its own log pipe so // every line is tagged with the originating container (ate.dev/container_name). for _, ac := range req.GetSpec().GetContainers() { - pw, err := s.actorLogger.StartJSONLogPipe(req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName(), ac.GetName()) + pw, err := s.actorLogger.StartJSONLogPipe(req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName(), ac.GetName()) if err != nil { return nil, fmt.Errorf("while starting json log pipe for %q: %w", ac.GetName(), err) } @@ -232,7 +231,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload return nil, fmt.Errorf("while waiting for container readyz: %w", err) } - s.actorLogger.EmitLifecycleLog("Actor started", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor started", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) return &ateompb.RunWorkloadResponse{}, nil } @@ -241,7 +240,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec s.lock.Lock() defer s.lock.Unlock() - s.actorLogger.EmitLifecycleLog("Actor checkpointing", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor checkpointing", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) // Contract with atelet: // @@ -249,12 +248,11 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // * After we exit, atelet will tear down OCI bundles and reset the actor directory. rcmd := &runsc{ - path: req.GetRunscPath(), - atespace: req.GetAtespace(), - actorName: req.GetActorName(), + path: req.GetRunscPath(), + actorUID: req.GetActorUid(), } - checkpointPath := ateompath.CheckpointStateDir(req.GetAtespace(), req.GetActorName()) + checkpointPath := ateompath.CheckpointStateDir(req.GetActorUid()) if err := os.MkdirAll(checkpointPath, 0o700); err != nil { return nil, fmt.Errorf("while creating checkpoint directory: %w", err) } @@ -290,6 +288,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec slog.WarnContext(ctx, "Failed to clean up runsc containers after checkpoint", "actorName", req.GetActorName(), "atespace", req.GetAtespace(), + "actorUID", req.GetActorUid(), "err", err) } @@ -302,7 +301,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec return nil, fmt.Errorf("while listing checkpoint files: %w", err) } - s.actorLogger.EmitLifecycleLog("Actor checkpointed", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor checkpointed", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) return &ateompb.CheckpointWorkloadResponse{SnapshotFiles: snapshotFiles}, nil } @@ -354,7 +353,7 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore s.lock.Lock() defer s.lock.Unlock() - s.actorLogger.EmitLifecycleLog("Actor restoring", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor restoring", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) // Contract with atelet: // @@ -372,12 +371,11 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore }() rcmd := &runsc{ - path: req.GetRunscPath(), - atespace: req.GetAtespace(), - actorName: req.GetActorName(), + path: req.GetRunscPath(), + actorUID: req.GetActorUid(), } - checkpointDir := ateompath.RestoreStateDir(req.GetAtespace(), req.GetActorName()) + checkpointDir := ateompath.RestoreStateDir(req.GetActorUid()) switch req.GetScope() { case ateompb.SnapshotScope_SNAPSHOT_SCOPE_DATA: @@ -403,7 +401,7 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore // Create and restore each application container, each with its own log pipe so // every line is tagged with the originating container (ate.dev/container_name). for _, ac := range req.GetSpec().GetContainers() { - pw, err := s.actorLogger.StartJSONLogPipe(req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName(), ac.GetName()) + pw, err := s.actorLogger.StartJSONLogPipe(req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName(), ac.GetName()) if err != nil { return nil, fmt.Errorf("while starting json log pipe for %q: %w", ac.GetName(), err) } @@ -433,7 +431,7 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore return nil, fmt.Errorf("while waiting for container readyz: %w", err) } - s.actorLogger.EmitLifecycleLog("Actor restored", req.GetAtespace(), req.GetActorName(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) + s.actorLogger.EmitLifecycleLog("Actor restored", req.GetAtespace(), req.GetActorName(), req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) return &ateompb.RestoreWorkloadResponse{}, nil } diff --git a/cmd/ateom-gvisor/runsc.go b/cmd/ateom-gvisor/runsc.go index e66e4dea..2901c1d2 100644 --- a/cmd/ateom-gvisor/runsc.go +++ b/cmd/ateom-gvisor/runsc.go @@ -28,9 +28,8 @@ import ( ) type runsc struct { - path string - atespace string - actorName string + path string + actorUID string } func (r *runsc) cmdCreate(ctx context.Context, out io.Writer, containerName string, additionalArgs []string) error { @@ -43,14 +42,14 @@ func (r *runsc) cmdCreate(ctx context.Context, out io.Writer, containerName stri "-log-format", "json", "--alsologtostderr", // "-debug", - // "-debug-log", ateompath.RunscDebugLogDir(r.atespace, r.actorName, containerName) + "/", + // "-debug-log", ateompath.RunscDebugLogDir(r.actorUID, containerName) + "/", // "-debug-to-user-log", // "-log-packets", // "-strace", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "create", - "-bundle", ateompath.OCIBundlePath(r.atespace, r.actorName, containerName), - "-pid-file", ateompath.PIDFilePath(r.atespace, r.actorName, containerName), + "-bundle", ateompath.OCIBundlePath(r.actorUID, containerName), + "-pid-file", ateompath.PIDFilePath(r.actorUID, containerName), } args = append(args, additionalArgs...) @@ -83,12 +82,12 @@ func (r *runsc) cmdStart(ctx context.Context, out io.Writer, containerName strin "-log-format", "json", "--alsologtostderr", // "-debug", - // "-debug-log", ateompath.RunscDebugLogDir(r.atespace, r.actorName, containerName)+"/", + // "-debug-log", ateompath.RunscDebugLogDir(r.actorUID, containerName)+"/", // "-debug-to-user-log", // "-log-packets", // "-strace", "-allow-connected-on-save", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "start", containerName, // Name of the container ) @@ -115,11 +114,11 @@ func (r *runsc) cmdCheckpoint(ctx context.Context, containerName, checkpointPath "-log-format", "json", "--alsologtostderr", // "-debug", - // "-debug-log", ateompath.RunscDebugLogDir(r.atespace, r.actorName, containerName)+"/", + // "-debug-log", ateompath.RunscDebugLogDir(r.actorUID, containerName)+"/", // "-debug-to-user-log", // "-log-packets", // "-strace", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "checkpoint", "-image-path", checkpointPath, containerName, // Name of the container @@ -143,11 +142,11 @@ func (r *runsc) cmdFsCheckpoint(ctx context.Context, containerName, checkpointPa "-log-format", "json", "--alsologtostderr", // "-debug", - // "-debug-log", ateompath.RunscDebugLogDir(r.atespace, r.actorName, containerName)+"/", + // "-debug-log", ateompath.RunscDebugLogDir(r.actorUID, containerName)+"/", // "-debug-to-user-log", // "-log-packets", // "-strace", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "fscheckpoint", "-image-path", checkpointPath, } @@ -186,15 +185,15 @@ func (r *runsc) cmdRestore(ctx context.Context, out io.Writer, containerName, ch "-log-format", "json", "--alsologtostderr", // "-debug", - // "-debug-log", ateompath.RunscDebugLogDir(r.atespace, r.actorName, containerName)+"/", + // "-debug-log", ateompath.RunscDebugLogDir(r.actorUID, containerName)+"/", // "-debug-to-user-log", // "-log-packets", // "-strace", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "restore", - "-bundle", ateompath.OCIBundlePath(r.atespace, r.actorName, containerName), + "-bundle", ateompath.OCIBundlePath(r.actorUID, containerName), "-image-path", checkpointPath, - "-pid-file", ateompath.PIDFilePath(r.atespace, r.actorName, containerName), + "-pid-file", ateompath.PIDFilePath(r.actorUID, containerName), "-background", "-direct", "-detach", @@ -221,7 +220,7 @@ func (r *runsc) cmdDelete(ctx context.Context, containerName string) error { "-log-format", "json", "--alsologtostderr", // "-debug", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "delete", "-force", containerName, @@ -246,7 +245,7 @@ func (r *runsc) cmdState(ctx context.Context, containerName string) error { r.path, "-log-format", "json", "--alsologtostderr", - "-root", ateompath.RunSCStateDir(r.atespace, r.actorName), + "-root", ateompath.RunSCStateDir(r.actorUID), "state", containerName, ) diff --git a/cmd/ateom-microvm/checkpoint.go b/cmd/ateom-microvm/checkpoint.go index bcbb859f..50a19c16 100644 --- a/cmd/ateom-microvm/checkpoint.go +++ b/cmd/ateom-microvm/checkpoint.go @@ -47,15 +47,16 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec atespace := req.GetAtespace() name := req.GetActorName() + actorUID := req.GetActorUid() templateNS := req.GetActorTemplateNamespace() templateName := req.GetActorTemplateName() - s.actorLogger.EmitLifecycleLog("Actor checkpointing", atespace, name, templateNS, templateName) + s.actorLogger.EmitLifecycleLog("Actor checkpointing", atespace, name, actorUID, templateNS, templateName) // The actor's CH was booted by RunWorkload or relaunched by RestoreWorkload; // either way ateom owns it and tracks its api-socket. - ra := s.running[name] - chSocket := kata.CLHSocketPath(name) + ra := s.running[actorUID] + chSocket := kata.CLHSocketPath(actorUID) if ra != nil && ra.apiSocket != "" { chSocket = ra.apiSocket } @@ -70,7 +71,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec } dPause := time.Since(tPause) - checkpointDir := ateompath.CheckpointStateDir(atespace, name) + checkpointDir := ateompath.CheckpointStateDir(actorUID) // Start from a clean dir so CH's snapshot files are the only contents. if err := os.RemoveAll(checkpointDir); err != nil { return nil, fmt.Errorf("while clearing checkpoint dir %q: %w", checkpointDir, err) @@ -86,7 +87,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // reconstructed-from-image base at the path the guest expects. We can NOT derive // it from config.json (its socket paths get rewritten to the current id on every // restore, losing the invariant golden id). - baseID := name + baseID := actorUID if ra != nil && ra.baseID != "" { baseID = ra.baseID } @@ -94,7 +95,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec return nil, fmt.Errorf("while writing %s: %w", baseIDFile, err) } - slog.InfoContext(ctx, "Snapshotting guest", slog.String("id", name), slog.String("dir", checkpointDir)) + slog.InfoContext(ctx, "Snapshotting guest", slog.String("id", actorUID), slog.String("dir", checkpointDir)) tSnapshot := time.Now() if err := client.Snapshot(ctx, checkpointDir); err != nil { return nil, fmt.Errorf("while snapshotting guest: %w", err) @@ -118,7 +119,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec return nil, fmt.Errorf("while merging OnDemand delta into restore source: %w", err) } slog.InfoContext(ctx, "Merged OnDemand delta into base (complete snapshot)", - slog.String("id", name), slog.Duration("merge", time.Since(tMerge))) + slog.String("id", actorUID), slog.Duration("merge", time.Since(tMerge))) } // Nothing rootfs-related ships: the overlay's writable upper is a guest tmpfs, so @@ -136,17 +137,17 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // Tear down: the actor returns to "available". Best-effort; the snapshot is // already on disk for atelet to ship. tTeardown := time.Now() - s.teardownActor(ctx, name, ra, client) + s.teardownActor(ctx, actorUID, ra, client) dTeardown := time.Since(tTeardown) - delete(s.running, name) + delete(s.running, actorUID) // Tear down the per-activation actor network. if err := s.cleanupActorNetwork(ctx); err != nil { slog.WarnContext(ctx, "Failed to clean up actor network after checkpoint", slog.Any("err", err)) } - s.actorLogger.EmitLifecycleLog("Actor checkpointed", atespace, name, templateNS, templateName) - slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", name), slog.Any("snapshot_files", snapshotFiles), + s.actorLogger.EmitLifecycleLog("Actor checkpointed", atespace, name, actorUID, templateNS, templateName) + slog.InfoContext(ctx, "Actor checkpointed", slog.String("id", actorUID), slog.Any("snapshot_files", snapshotFiles), slog.Duration("pause", dPause), slog.Duration("snapshot", dSnapshot), slog.Duration("teardown", dTeardown)) return &ateompb.CheckpointWorkloadResponse{SnapshotFiles: snapshotFiles}, nil diff --git a/cmd/ateom-microvm/main.go b/cmd/ateom-microvm/main.go index c1998da2..85cdfea2 100644 --- a/cmd/ateom-microvm/main.go +++ b/cmd/ateom-microvm/main.go @@ -205,7 +205,7 @@ type AteomService struct { // with ateom-gvisor). actorLogger *actorlog.ActorLogger - // running maps actor name -> the live micro-VM, kept so CheckpointWorkload can + // running maps actor UID -> the live micro-VM, kept so CheckpointWorkload can // pause+snapshot+teardown the same sandbox (and RestoreWorkload can track the // CH it relaunched). running map[string]*runningActor diff --git a/cmd/ateom-microvm/restore.go b/cmd/ateom-microvm/restore.go index 54407693..b8778e25 100644 --- a/cmd/ateom-microvm/restore.go +++ b/cmd/ateom-microvm/restore.go @@ -55,28 +55,29 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore atespace := req.GetAtespace() name := req.GetActorName() + actorUID := req.GetActorUid() templateNS := req.GetActorTemplateNamespace() templateName := req.GetActorTemplateName() - restoreDir := ateompath.RestoreStateDir(atespace, name) + restoreDir := ateompath.RestoreStateDir(actorUID) tStart := time.Now() - s.actorLogger.EmitLifecycleLog("Actor restoring", atespace, name, templateNS, templateName) + s.actorLogger.EmitLifecycleLog("Actor restoring", atespace, name, actorUID, templateNS, templateName) rr := s.resolveRuntime(req.GetRuntimeAssetPaths()) - kata.CleanupSandboxState(ctx, name) + kata.CleanupSandboxState(ctx, actorUID) // Repoint the snapshot's vsock socket to this actor's VMDir (the disk + kernel // paths are content-addressed/per-actor and already line up on the same node). - if err := rewriteSnapshotSocketPaths(restoreDir, name); err != nil { + if err := rewriteSnapshotSocketPaths(restoreDir, actorUID); err != nil { return nil, fmt.Errorf("while rewriting snapshot socket paths: %w", err) } - srcID := name + srcID := actorUID if b, rerr := os.ReadFile(filepath.Join(restoreDir, baseIDFile)); rerr == nil { if v := strings.TrimSpace(string(b)); v != "" { srcID = v } } - if err := os.MkdirAll(kata.VMDir(name), 0o700); err != nil { + if err := os.MkdirAll(kata.VMDir(actorUID), 0o700); err != nil { return nil, fmt.Errorf("while creating VM dir: %w", err) } @@ -95,11 +96,11 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore if len(containers) > maxActorContainers { return nil, status.Errorf(codes.Unimplemented, "ateom-microvm supports at most %d containers, got %d", maxActorContainers, len(containers)) } - ctrs, err := s.buildActorContainers(atespace, name, containers) + ctrs, err := s.buildActorContainers(actorUID, containers) if err != nil { return nil, err } - vfsdCmd, err := s.stageOverlayLowers(ctx, rr, name, ctrs) + vfsdCmd, err := s.stageOverlayLowers(ctx, rr, actorUID, ctrs) if err != nil { return nil, err } @@ -148,7 +149,7 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore // Relaunch CH and restore with the tap FDs attached (SCM_RIGHTS). CH reopens // /dev/vda (image) + each /dev/vd{b+i} (actor rootfs) from the snapshot config paths. - apiSocket := filepath.Join(kata.VMDir(name), "clh-api-restore.sock") + apiSocket := filepath.Join(kata.VMDir(actorUID), "clh-api-restore.sock") chCmd, client, err := ch.LaunchVMM(ctx, ch.LaunchVMMOptions{ Binary: rr.chBinary, APISocket: apiSocket, Stdout: slogWriter{ctx}, Stderr: slogWriter{ctx}, }) @@ -186,22 +187,22 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore // resumes ReadStdout/ReadStderr. The overlay workload's container/exec id is // _ovl (same as the cold run). Best-effort — a failed dial must not fail the // restore (the actor is already running); forwarding is just skipped. - vsockPath := kata.VsockSocketPath(name) + vsockPath := kata.VsockSocketPath(actorUID) logAC, dialErr := dialAgentRetry(ctx, vsockPath, 15*time.Second) if dialErr != nil { slog.WarnContext(ctx, "post-restore agent dial failed; actor log forwarding disabled for this restore", - slog.String("id", name), slog.Any("err", dialErr)) + slog.String("id", actorUID), slog.Any("err", dialErr)) } else { ra.logAgent = logAC for _, c := range containers { - s.startActorLogForwarding(logAC, atespace, name, templateNS, templateName, overlayWorkloadID(c.GetName()), c.GetName()) + s.startActorLogForwarding(logAC, atespace, name, actorUID, templateNS, templateName, overlayWorkloadID(c.GetName()), c.GetName()) } } - s.running[name] = ra - s.actorLogger.EmitLifecycleLog("Actor restored", atespace, name, templateNS, templateName) + s.running[actorUID] = ra + s.actorLogger.EmitLifecycleLog("Actor restored", atespace, name, actorUID, templateNS, templateName) slog.InfoContext(ctx, "Actor restored (overlay rootfs)", - slog.String("id", name), slog.Duration("total", time.Since(tStart))) + slog.String("id", actorUID), slog.Duration("total", time.Since(tStart))) return &ateompb.RestoreWorkloadResponse{}, nil } diff --git a/cmd/ateom-microvm/run.go b/cmd/ateom-microvm/run.go index 2038e550..6348a30f 100644 --- a/cmd/ateom-microvm/run.go +++ b/cmd/ateom-microvm/run.go @@ -79,7 +79,7 @@ type runningActor struct { // to (/rootfs). It is the id the RO base was FIRST shared under (the golden // actor's cold-run id) and is INVARIANT across every restore of that actor's // lineage: the guest memory keeps referencing /rootfs, while the snapshot -// config.json's socket paths get rewritten to the current actor name on each restore. +// config.json's socket paths get rewritten to the current actor UID on each restore. // RestoreWorkload reads this to lay the reconstructed-from-image base at the path // the guest expects. (The config.json socket id is the WRONG source — it equals the // current id, not the frozen golden id, for any restored-then-checkpointed actor.) @@ -190,10 +190,11 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload atespace := req.GetAtespace() name := req.GetActorName() + actorUID := req.GetActorUid() templateNS := req.GetActorTemplateNamespace() templateName := req.GetActorTemplateName() - s.actorLogger.EmitLifecycleLog("Actor starting", atespace, name, templateNS, templateName) + s.actorLogger.EmitLifecycleLog("Actor starting", atespace, name, actorUID, templateNS, templateName) // All of the actor's containers share the one micro-VM (which is the pod // sandbox): each gets its own overlay rootfs and its own kata-agent @@ -231,7 +232,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload // Prepare each container's OCI spec + record its bundle rootfs (the overlay RO // lower). No host disk — the rootfs is overlay(virtio-fs lower + guest-tmpfs upper). - ctrs, err := s.buildActorContainers(atespace, name, containers) + ctrs, err := s.buildActorContainers(actorUID, containers) if err != nil { return nil, err } @@ -243,15 +244,15 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload } // Clean stale per-sandbox state + create the runtime dir for the sockets. - kata.CleanupSandboxState(ctx, name) - if err := os.MkdirAll(kata.VMDir(name), 0o700); err != nil { + kata.CleanupSandboxState(ctx, actorUID) + if err := os.MkdirAll(kata.VMDir(actorUID), 0o700); err != nil { return nil, fmt.Errorf("while creating VM dir: %w", err) } // Stage the overlay RO lowers (bind each image into the shared dir) + start the // virtiofsd that serves them. CH connects to it at vm.create and demand-pages for // the actor's lifetime, so ateom owns the process (killed in teardownActor). - vfsdCmd, err := s.stageOverlayLowers(ctx, rr, name, ctrs) + vfsdCmd, err := s.stageOverlayLowers(ctx, rr, actorUID, ctrs) if err != nil { return nil, err } @@ -263,7 +264,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload }() // Launch a bare VMM (CH + api-socket); ateom owns this process for teardown. - apiSocket := filepath.Join(kata.VMDir(name), "clh-api.sock") + apiSocket := filepath.Join(kata.VMDir(actorUID), "clh-api.sock") chCmd, client, err := ch.LaunchVMM(ctx, ch.LaunchVMMOptions{ Binary: rr.chBinary, APISocket: apiSocket, @@ -284,8 +285,8 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload // the virtio-fs device for the overlay RO lower; no actor virtio-blk disks — the // writable upper is a guest tmpfs). serialLog is also read on a failed agent dial // below, so keep it here. - serialLog := filepath.Join(kata.VMDir(name), "serial.log") - vmCfg := buildVMConfig(name, kernel, image, kparams, serialLog, memMiB, vcpus) + serialLog := filepath.Join(kata.VMDir(actorUID), "serial.log") + vmCfg := buildVMConfig(actorUID, kernel, image, kparams, serialLog, memMiB, vcpus) if err := client.CreateVM(ctx, vmCfg); err != nil { return nil, fmt.Errorf("while creating VM: %w", err) } @@ -313,13 +314,13 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload if err := client.BootVM(ctx); err != nil { return nil, fmt.Errorf("while booting VM: %w", err) } - slog.InfoContext(ctx, "Micro-VM booted", slog.String("id", name), slog.String("api", apiSocket)) + slog.InfoContext(ctx, "Micro-VM booted", slog.String("id", actorUID), slog.String("api", apiSocket)) // Dial the kata-agent over hybrid-vsock. The agent only starts listening once // the guest's init reaches kata-containers.target — well after CH creates the // vsock socket file — so poll the CONNECT until it answers (as the kata shim // does), rather than dialing once. - vsockPath := kata.VsockSocketPath(name) + vsockPath := kata.VsockSocketPath(actorUID) if !waitForFile(vsockPath, 15*time.Second) { return nil, fmt.Errorf("kata-agent vsock socket %q did not appear", vsockPath) } @@ -341,7 +342,7 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload }() // Post-boot kata-agent setup: sandbox, guest networking, start each container. - if err := s.startActorContainers(ctx, ac, name, vsockPath, ctrs); err != nil { + if err := s.startActorContainers(ctx, ac, actorUID, vsockPath, ctrs); err != nil { return nil, err } @@ -350,19 +351,19 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload return nil, fmt.Errorf("while waiting for container readyz: %w", err) } - ra := &runningActor{chCmd: chCmd, vfsdCmd: vfsdCmd, apiSocket: apiSocket, baseID: name, logAgent: ac} - s.running[name] = ra + ra := &runningActor{chCmd: chCmd, vfsdCmd: vfsdCmd, apiSocket: apiSocket, baseID: actorUID, logAgent: ac} + s.running[actorUID] = ra // Forward each container's stdout/stderr into the pod logs. The overlay workload's // container/exec id is _ovl (see startOverlayContainer), so key the streams by // that and tag with the display container name. The goroutines read over ac for the // actor's lifetime and exit (io.EOF) when teardownActor closes ac. for _, c := range ctrs { - s.startActorLogForwarding(ac, atespace, name, templateNS, templateName, overlayWorkloadID(c.name), c.name) + s.startActorLogForwarding(ac, atespace, name, actorUID, templateNS, templateName, overlayWorkloadID(c.name), c.name) } - s.actorLogger.EmitLifecycleLog("Actor started", atespace, name, templateNS, templateName) - slog.InfoContext(ctx, "Actor started (overlay rootfs)", slog.String("id", name)) + s.actorLogger.EmitLifecycleLog("Actor started", atespace, name, actorUID, templateNS, templateName) + slog.InfoContext(ctx, "Actor started (overlay rootfs)", slog.String("id", actorUID)) return &ateompb.RunWorkloadResponse{}, nil } @@ -372,13 +373,13 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload // built — the rootfs is overlay(virtio-fs RO lower + guest-tmpfs upper); the lowers // are bound into virtiofsd's shared dir in stageOverlayLowers after the sandbox state // is clean. Both RunWorkload and RestoreWorkload go through here. -func (s *AteomService) buildActorContainers(atespace, id string, containers []*ateompb.Container) ([]actorContainer, error) { +func (s *AteomService) buildActorContainers(actorUID string, containers []*ateompb.Container) ([]actorContainer, error) { netnsPath := ateompath.AteomNetNSPath(s.podUID) ctrs := make([]actorContainer, len(containers)) for i, c := range containers { cn := c.GetName() - bundle := ateompath.OCIBundlePath(atespace, id, cn) - spec, err := ensureKataCompatibleSpec(bundle, id, netnsPath) + bundle := ateompath.OCIBundlePath(actorUID, cn) + spec, err := ensureKataCompatibleSpec(bundle, actorUID, netnsPath) if err != nil { return nil, fmt.Errorf("while preparing kata OCI spec for %q: %w", cn, err) } @@ -550,9 +551,9 @@ func startOverlayContainer(ctx context.Context, ac *kata.AgentClient, vsockPath // ending WrapContainerLogs. This keeps the agent connection (which ttrpc allows // concurrent Calls on) alive for forwarding while guaranteeing no goroutine outlives // the connection. -func (s *AteomService) startActorLogForwarding(ac *kata.AgentClient, atespace, actorName, actorTemplateNamespace, actorTemplateName, streamID, containerName string) { - go s.actorLogger.WrapContainerLogs(kata.NewStdioReader(context.Background(), ac, streamID, streamID, false), atespace, actorName, actorTemplateNamespace, actorTemplateName, containerName) - go s.actorLogger.WrapContainerLogs(kata.NewStdioReader(context.Background(), ac, streamID, streamID, true), atespace, actorName, actorTemplateNamespace, actorTemplateName, containerName) +func (s *AteomService) startActorLogForwarding(ac *kata.AgentClient, atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, streamID, containerName string) { + go s.actorLogger.WrapContainerLogs(kata.NewStdioReader(context.Background(), ac, streamID, streamID, false), atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, containerName) + go s.actorLogger.WrapContainerLogs(kata.NewStdioReader(context.Background(), ac, streamID, streamID, true), atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, containerName) } // dialAgentRetry polls DialAgent until the kata-agent answers the hybrid-vsock diff --git a/docs/observability.md b/docs/observability.md index 0f00b414..6162464a 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -7,8 +7,9 @@ This guide explains how Agent Substrate achieves observability across these susp ## The Observability Model To make underlying infrastructure transitions transparent, Agent Substrate establishes a standardized metadata model to identify actors across worker pods: -* `ate.dev/actor_id`: The unique identifier of the actor (e.g., `my-counter-1` or `test`). +* `ate.dev/actor_name`: actor name visible to the caller (e.g., `my-counter-1` or `test`). * `ate.dev/actor_atespace`: The atespace the actor lives in (e.g., `ate-demo-counter`). +* `ate.dev/actor_uid`: Server-assigned UID of the actor, unique to the lifetime of an actor. * `ate.dev/actor_template_name`: The name of the actor's ActorTemplate (e.g., `counter`). * `ate.dev/actor_template_namespace`: The Kubernetes namespace of the actor's ActorTemplate (e.g., `ate-demo-counter`). * `ate.dev/container_name`: The name of the container within the actor that produced the log line (e.g., `counter`), so a multi-container actor's logs can be demultiplexed by container. diff --git a/internal/actorlog/logger.go b/internal/actorlog/logger.go index 86b5e476..f477c09e 100644 --- a/internal/actorlog/logger.go +++ b/internal/actorlog/logger.go @@ -66,13 +66,14 @@ func NewActorLogger(w io.Writer, isOnGCE bool) *ActorLogger { } // EmitLifecycleLog logs a synthetic actor lifecycle event. -func (al *ActorLogger) EmitLifecycleLog(msg, atespace, actorName, actorTemplateNamespace, actorTemplateName string) { +func (al *ActorLogger) EmitLifecycleLog(msg, atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName string) { envelope := map[string]any{ "time": time.Now().Format(time.RFC3339Nano), "message": msg, al.labelsKey: map[string]string{ "ate.dev/actor_atespace": atespace, "ate.dev/actor_name": actorName, + "ate.dev/actor_uid": actorUID, "ate.dev/actor_template_namespace": actorTemplateNamespace, "ate.dev/actor_template_name": actorTemplateName, }, @@ -87,13 +88,13 @@ func (al *ActorLogger) EmitLifecycleLog(msg, atespace, actorName, actorTemplateN // through the logger. containerName tags every line with the originating container; // callers that multiplex multiple containers should give each its own pipe so the // tag is meaningful. -func (al *ActorLogger) StartJSONLogPipe(atespace, actorName, actorTemplateNamespace, actorTemplateName, containerName string) (io.WriteCloser, error) { +func (al *ActorLogger) StartJSONLogPipe(atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, containerName string) (io.WriteCloser, error) { pr, pw, err := os.Pipe() if err != nil { return nil, err } go func() { - al.WrapContainerLogs(pr, atespace, actorName, actorTemplateNamespace, actorTemplateName, containerName) + al.WrapContainerLogs(pr, atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, containerName) pr.Close() }() return pw, nil @@ -102,7 +103,7 @@ func (al *ActorLogger) StartJSONLogPipe(atespace, actorName, actorTemplateNamesp // WrapContainerLogs reads log lines from r, parses them, and logs them in a unified // structured format. containerName is added as the ate.dev/container_name label so // multi-container actors can be demultiplexed. -func (al *ActorLogger) WrapContainerLogs(r io.Reader, atespace, actorName, actorTemplateNamespace, actorTemplateName, containerName string) { +func (al *ActorLogger) WrapContainerLogs(r io.Reader, atespace, actorName, actorUID, actorTemplateNamespace, actorTemplateName, containerName string) { rdr := bufio.NewReader(r) for { lineBytes, err := rdr.ReadBytes('\n') @@ -131,6 +132,7 @@ func (al *ActorLogger) WrapContainerLogs(r io.Reader, atespace, actorName, actor labels := map[string]string{ "ate.dev/actor_atespace": atespace, "ate.dev/actor_name": actorName, + "ate.dev/actor_uid": actorUID, "ate.dev/actor_template_namespace": actorTemplateNamespace, "ate.dev/actor_template_name": actorTemplateName, "ate.dev/container_name": containerName, diff --git a/internal/actorlog/logger_test.go b/internal/actorlog/logger_test.go index e3853331..17601636 100644 --- a/internal/actorlog/logger_test.go +++ b/internal/actorlog/logger_test.go @@ -28,7 +28,7 @@ func TestWrapContainerLogs(t *testing.T) { var buf bytes.Buffer al := NewActorLogger(&buf, false) - al.WrapContainerLogs(rdr, "default", "act-1", "tmpl-ns", "tmpl-1", "ctr-1") + al.WrapContainerLogs(rdr, "default", "act-1", "uid-1", "tmpl-ns", "tmpl-1", "ctr-1") var m map[string]any if err := json.Unmarshal(buf.Bytes(), &m); err != nil { @@ -60,6 +60,9 @@ func TestWrapContainerLogs(t *testing.T) { if labels["ate.dev/actor_atespace"] != "default" { t.Errorf("got actor_atespace = %v, want 'default'", labels["ate.dev/actor_atespace"]) } + if labels["ate.dev/actor_uid"] != "uid-1" { + t.Errorf("got actor_uid = %v, want 'uid-1'", labels["ate.dev/actor_uid"]) + } if labels["ate.dev/actor_template_namespace"] != "tmpl-ns" { t.Errorf("got actor_template_namespace = %v, want 'tmpl-ns'", labels["ate.dev/actor_template_namespace"]) } @@ -78,7 +81,7 @@ func TestWrapContainerLogs_JSONInput(t *testing.T) { var buf bytes.Buffer al := NewActorLogger(&buf, false) - al.WrapContainerLogs(rdr, "default", "act-1", "tmpl-ns", "tmpl-1", "ctr-1") + al.WrapContainerLogs(rdr, "default", "act-1", "uid-1", "tmpl-ns", "tmpl-1", "ctr-1") dec := json.NewDecoder(&buf) dec.UseNumber() @@ -173,7 +176,7 @@ func TestWrapContainerLogs_MergeLabels(t *testing.T) { var buf bytes.Buffer al := NewActorLogger(&buf, false) // labelsKey will be "labels" - al.WrapContainerLogs(rdr, "default", "act-1", "tmpl-ns", "tmpl-1", "ctr-1") + al.WrapContainerLogs(rdr, "default", "act-1", "uid-1", "tmpl-ns", "tmpl-1", "ctr-1") var m map[string]any if err := json.Unmarshal(buf.Bytes(), &m); err != nil { @@ -209,7 +212,7 @@ func TestWrapContainerLogs_LabelCollision(t *testing.T) { var buf bytes.Buffer al := NewActorLogger(&buf, false) - al.WrapContainerLogs(rdr, "default", "act-1", "tmpl-ns", "tmpl-1", "ctr-1") + al.WrapContainerLogs(rdr, "default", "act-1", "uid-1", "tmpl-ns", "tmpl-1", "ctr-1") var m map[string]any if err := json.Unmarshal(buf.Bytes(), &m); err != nil { @@ -239,7 +242,7 @@ func TestWrapContainerLogs_TrailingGarbage(t *testing.T) { var buf bytes.Buffer al := NewActorLogger(&buf, false) - al.WrapContainerLogs(rdr, "default", "act-1", "tmpl-ns", "tmpl-1", "ctr-1") + al.WrapContainerLogs(rdr, "default", "act-1", "uid-1", "tmpl-ns", "tmpl-1", "ctr-1") var m map[string]any if err := json.Unmarshal(buf.Bytes(), &m); err != nil { diff --git a/internal/ateompath/ateompath.go b/internal/ateompath/ateompath.go index 16f3c63a..812c2ba2 100644 --- a/internal/ateompath/ateompath.go +++ b/internal/ateompath/ateompath.go @@ -60,11 +60,11 @@ func AteomNetNSPath(podUID string) string { ) } -func ActorPath(atespace, actorName string) string { +func ActorPath(actorUID string) string { return filepath.Join( BasePath, "actors", - atespace+":"+actorName, + actorUID, ) } @@ -73,9 +73,9 @@ func ActorPath(atespace, actorName string) string { // bind-mounts read-only into the actor. It is per-actor and regenerated on // every resume, so (unlike the checkpointed process environment) it reflects // the correct ID after a restore from the golden snapshot. -func ActorIdentityDirPath(atespace, actorName string) string { +func ActorIdentityDirPath(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "identity", ) } @@ -86,69 +86,69 @@ func ActorIdentityDirPath(atespace, actorName string) string { // Checkpoint (when the request no longer carries the sandbox config). It lives // directly under ActorPath — NOT under a subdir wiped by atelet's // resetActorDirs — so it survives between Run and a later Checkpoint. -func ActorSandboxAssetsFile(atespace, actorName string) string { +func ActorSandboxAssetsFile(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "sandbox-assets.json", ) } -func RunSCStateDir(atespace, actorName string) string { +func RunSCStateDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "runsc-state", ) } -func OCIBundleDir(atespace, actorName string) string { +func OCIBundleDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "bundles", ) } -func OCIBundlePath(atespace, actorName, containerName string) string { +func OCIBundlePath(actorUID, containerName string) string { return filepath.Join( - OCIBundleDir(atespace, actorName), + OCIBundleDir(actorUID), containerName, ) } -func RunscDebugLogDir(atespace, actorName, containerName string) string { +func RunscDebugLogDir(actorUID, containerName string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "runsc-debug-logs", containerName, ) } -func CheckpointStateDir(atespace, actorName string) string { +func CheckpointStateDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "checkpoint-state", ) } -func LocalCheckpointsDir(atespace, actorName string) string { +func LocalCheckpointsDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "local-checkpoint", ) } // DurableDirVolumeMountsDir is the directory where individual durable-dir // volumes are mounted. -func DurableDirVolumeMountsDir(atespace, actorName string) string { +func DurableDirVolumeMountsDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "durable-dir", ) } // DurableDirVolumeMountPoint returns the path where a specific durable-dir volume is mounted on the nodeVM. -func DurableDirVolumeMountPoint(atespace, actorName, volumeName string) string { +func DurableDirVolumeMountPoint(actorUID, volumeName string) string { return filepath.Join( - DurableDirVolumeMountsDir(atespace, actorName), + DurableDirVolumeMountsDir(actorUID), volumeName, ) } @@ -164,23 +164,23 @@ func DurableDirVolumeMountPoint(atespace, actorName, volumeName string) string { // make sure we write the suspension checkpoint to a different location. This // will work properly, with `runsc checkpoint` paging in any data that hasn't // yet been loaded. -func RestoreStateDir(atespace, actorName string) string { +func RestoreStateDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "restore-state", ) } -func PIDFileDir(atespace, actorName string) string { +func PIDFileDir(actorUID string) string { return filepath.Join( - ActorPath(atespace, actorName), + ActorPath(actorUID), "pidfiles", ) } -func PIDFilePath(atespace, actorName, containerName string) string { +func PIDFilePath(actorUID, containerName string) string { return filepath.Join( - PIDFileDir(atespace, actorName), + PIDFileDir(actorUID), containerName+".pid", ) } diff --git a/internal/ateompath/ateompath_test.go b/internal/ateompath/ateompath_test.go index e6978a0e..edd934c9 100644 --- a/internal/ateompath/ateompath_test.go +++ b/internal/ateompath/ateompath_test.go @@ -58,3 +58,17 @@ func TestAteomPathUniqueness(t *testing.T) { t.Errorf("expected different paths for different pod UIDs, got %q", path1) } } + +func TestActorPathUsesUID(t *testing.T) { + uid1 := "123e4567-e89b-12d3-a456-426614174000" + uid2 := "987f6543-e21b-32d1-b654-246614174111" + + path1 := ActorPath(uid1) + path2 := ActorPath(uid2) + if path1 == path2 { + t.Fatalf("different actor UIDs produced the same path %q", path1) + } + if want := "/actors/" + uid1; !strings.HasSuffix(path1, want) { + t.Errorf("ActorPath(%q) = %q, want suffix %q", uid1, path1, want) + } +} diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index 4f51a5f2..b24d7036 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -193,13 +193,14 @@ type RunRequest struct { TargetAteomUid string `protobuf:"bytes,1,opt,name=target_ateom_uid,json=targetAteomUid,proto3" json:"target_ateom_uid,omitempty"` Atespace string `protobuf:"bytes,2,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,3,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` + ActorUid string `protobuf:"bytes,4,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,5,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,6,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // The sandbox binaries to use for booting this actor from scratch. atelet // fetches the relevant assets and records them with the actor's on-node state // so a later Checkpoint can pin the same version into the snapshot manifest. - SandboxAssets *SandboxAssets `protobuf:"bytes,7,opt,name=sandbox_assets,json=sandboxAssets,proto3" json:"sandbox_assets,omitempty"` + SandboxAssets *SandboxAssets `protobuf:"bytes,8,opt,name=sandbox_assets,json=sandboxAssets,proto3" json:"sandbox_assets,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -255,6 +256,13 @@ func (x *RunRequest) GetActorName() string { return "" } +func (x *RunRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *RunRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -1050,13 +1058,14 @@ type CheckpointRequest struct { TargetAteomUid string `protobuf:"bytes,1,opt,name=target_ateom_uid,json=targetAteomUid,proto3" json:"target_ateom_uid,omitempty"` Atespace string `protobuf:"bytes,2,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,3,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + ActorUid string `protobuf:"bytes,4,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,5,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,6,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` // Sandbox binary config is not sent on checkpoint: atelet uses the version the // actor is currently running (recorded with the actor's on-node state at // Run/Restore) and records it into the snapshot manifest. - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` - Type CheckpointType `protobuf:"varint,7,opt,name=type,proto3,enum=atelet.CheckpointType" json:"type,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` + Type CheckpointType `protobuf:"varint,8,opt,name=type,proto3,enum=atelet.CheckpointType" json:"type,omitempty"` // The checkpoint configuration, depending on the type. // // Types that are valid to be assigned to Config: @@ -1065,7 +1074,7 @@ type CheckpointRequest struct { // *CheckpointRequest_ExternalConfig Config isCheckpointRequest_Config `protobuf_oneof:"config"` // What should be included in the checkpoint. - Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=atelet.SnapshotScope" json:"scope,omitempty"` + Scope SnapshotScope `protobuf:"varint,11,opt,name=scope,proto3,enum=atelet.SnapshotScope" json:"scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1121,6 +1130,13 @@ func (x *CheckpointRequest) GetActorName() string { return "" } +func (x *CheckpointRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *CheckpointRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -1186,11 +1202,11 @@ type isCheckpointRequest_Config interface { } type CheckpointRequest_LocalConfig struct { - LocalConfig *LocalCheckpointConfiguration `protobuf:"bytes,8,opt,name=local_config,json=localConfig,proto3,oneof"` + LocalConfig *LocalCheckpointConfiguration `protobuf:"bytes,9,opt,name=local_config,json=localConfig,proto3,oneof"` } type CheckpointRequest_ExternalConfig struct { - ExternalConfig *ExternalCheckpointConfiguration `protobuf:"bytes,9,opt,name=external_config,json=externalConfig,proto3,oneof"` + ExternalConfig *ExternalCheckpointConfiguration `protobuf:"bytes,10,opt,name=external_config,json=externalConfig,proto3,oneof"` } func (*CheckpointRequest_LocalConfig) isCheckpointRequest_Config() {} @@ -1238,13 +1254,14 @@ type RestoreRequest struct { TargetAteomUid string `protobuf:"bytes,1,opt,name=target_ateom_uid,json=targetAteomUid,proto3" json:"target_ateom_uid,omitempty"` Atespace string `protobuf:"bytes,2,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,3,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + ActorUid string `protobuf:"bytes,4,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,5,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,6,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` // Sandbox binary config is not sent on restore: the snapshot is // self-describing. atelet reads the snapshot manifest to recover the pinned // sandbox version that created it. - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` - Type CheckpointType `protobuf:"varint,7,opt,name=type,proto3,enum=atelet.CheckpointType" json:"type,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` + Type CheckpointType `protobuf:"varint,8,opt,name=type,proto3,enum=atelet.CheckpointType" json:"type,omitempty"` // The checkpoint configuration, depending on the type. // // Types that are valid to be assigned to Config: @@ -1253,7 +1270,7 @@ type RestoreRequest struct { // *RestoreRequest_ExternalConfig Config isRestoreRequest_Config `protobuf_oneof:"config"` // What content to restore from the checkpoint. - Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=atelet.SnapshotScope" json:"scope,omitempty"` + Scope SnapshotScope `protobuf:"varint,11,opt,name=scope,proto3,enum=atelet.SnapshotScope" json:"scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1309,6 +1326,13 @@ func (x *RestoreRequest) GetActorName() string { return "" } +func (x *RestoreRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *RestoreRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -1374,11 +1398,11 @@ type isRestoreRequest_Config interface { } type RestoreRequest_LocalConfig struct { - LocalConfig *LocalCheckpointConfiguration `protobuf:"bytes,8,opt,name=local_config,json=localConfig,proto3,oneof"` + LocalConfig *LocalCheckpointConfiguration `protobuf:"bytes,9,opt,name=local_config,json=localConfig,proto3,oneof"` } type RestoreRequest_ExternalConfig struct { - ExternalConfig *ExternalCheckpointConfiguration `protobuf:"bytes,9,opt,name=external_config,json=externalConfig,proto3,oneof"` + ExternalConfig *ExternalCheckpointConfiguration `protobuf:"bytes,10,opt,name=external_config,json=externalConfig,proto3,oneof"` } func (*RestoreRequest_LocalConfig) isRestoreRequest_Config() {} @@ -1425,17 +1449,18 @@ var File_atelet_proto protoreflect.FileDescriptor const file_atelet_proto_rawDesc = "" + "\n" + - "\fatelet.proto\x12\x06atelet\"\xc3\x02\n" + + "\fatelet.proto\x12\x06atelet\"\xe0\x02\n" + "\n" + "RunRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x12\x1a\n" + "\batespace\x18\x02 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x03 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12(\n" + - "\x04spec\x18\x06 \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12<\n" + - "\x0esandbox_assets\x18\a \x01(\v2\x15.atelet.SandboxAssetsR\rsandboxAssets\"5\n" + + "actor_name\x18\x03 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x04 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x05 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x06 \x01(\tR\x11actorTemplateName\x12(\n" + + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12<\n" + + "\x0esandbox_assets\x18\b \x01(\v2\x15.atelet.SandboxAssetsR\rsandboxAssets\"5\n" + "\tAssetFile\x12\x10\n" + "\x03url\x18\x01 \x01(\tR\x03url\x12\x16\n" + "\x06sha256\x18\x02 \x01(\tR\x06sha256\"\x8e\x01\n" + @@ -1489,35 +1514,37 @@ const file_atelet_proto_rawDesc = "" + "\x1cLocalCheckpointConfiguration\x12'\n" + "\x0fsnapshot_prefix\x18\x01 \x01(\tR\x0esnapshotPrefix\"Q\n" + "\x1fExternalCheckpointConfiguration\x12.\n" + - "\x13snapshot_uri_prefix\x18\x01 \x01(\tR\x11snapshotUriPrefix\"\x8e\x04\n" + + "\x13snapshot_uri_prefix\x18\x01 \x01(\tR\x11snapshotUriPrefix\"\xab\x04\n" + "\x11CheckpointRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x12\x1a\n" + "\batespace\x18\x02 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x03 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12(\n" + - "\x04spec\x18\x06 \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12*\n" + - "\x04type\x18\a \x01(\x0e2\x16.atelet.CheckpointTypeR\x04type\x12I\n" + - "\flocal_config\x18\b \x01(\v2$.atelet.LocalCheckpointConfigurationH\x00R\vlocalConfig\x12R\n" + - "\x0fexternal_config\x18\t \x01(\v2'.atelet.ExternalCheckpointConfigurationH\x00R\x0eexternalConfig\x12+\n" + - "\x05scope\x18\n" + - " \x01(\x0e2\x15.atelet.SnapshotScopeR\x05scopeB\b\n" + + "actor_name\x18\x03 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x04 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x05 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x06 \x01(\tR\x11actorTemplateName\x12(\n" + + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12*\n" + + "\x04type\x18\b \x01(\x0e2\x16.atelet.CheckpointTypeR\x04type\x12I\n" + + "\flocal_config\x18\t \x01(\v2$.atelet.LocalCheckpointConfigurationH\x00R\vlocalConfig\x12R\n" + + "\x0fexternal_config\x18\n" + + " \x01(\v2'.atelet.ExternalCheckpointConfigurationH\x00R\x0eexternalConfig\x12+\n" + + "\x05scope\x18\v \x01(\x0e2\x15.atelet.SnapshotScopeR\x05scopeB\b\n" + "\x06config\"\x14\n" + - "\x12CheckpointResponse\"\x8b\x04\n" + + "\x12CheckpointResponse\"\xa8\x04\n" + "\x0eRestoreRequest\x12(\n" + "\x10target_ateom_uid\x18\x01 \x01(\tR\x0etargetAteomUid\x12\x1a\n" + "\batespace\x18\x02 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x03 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12(\n" + - "\x04spec\x18\x06 \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12*\n" + - "\x04type\x18\a \x01(\x0e2\x16.atelet.CheckpointTypeR\x04type\x12I\n" + - "\flocal_config\x18\b \x01(\v2$.atelet.LocalCheckpointConfigurationH\x00R\vlocalConfig\x12R\n" + - "\x0fexternal_config\x18\t \x01(\v2'.atelet.ExternalCheckpointConfigurationH\x00R\x0eexternalConfig\x12+\n" + - "\x05scope\x18\n" + - " \x01(\x0e2\x15.atelet.SnapshotScopeR\x05scopeB\b\n" + + "actor_name\x18\x03 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x04 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x05 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x06 \x01(\tR\x11actorTemplateName\x12(\n" + + "\x04spec\x18\a \x01(\v2\x14.atelet.WorkloadSpecR\x04spec\x12*\n" + + "\x04type\x18\b \x01(\x0e2\x16.atelet.CheckpointTypeR\x04type\x12I\n" + + "\flocal_config\x18\t \x01(\v2$.atelet.LocalCheckpointConfigurationH\x00R\vlocalConfig\x12R\n" + + "\x0fexternal_config\x18\n" + + " \x01(\v2'.atelet.ExternalCheckpointConfigurationH\x00R\x0eexternalConfig\x12+\n" + + "\x05scope\x18\v \x01(\x0e2\x15.atelet.SnapshotScopeR\x05scopeB\b\n" + "\x06config\"\x11\n" + "\x0fRestoreResponse*F\n" + "\n" + diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index 23803278..98706bf9 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -37,16 +37,17 @@ message RunRequest { string atespace = 2; string actor_name = 3; + string actor_uid = 4; - string actor_template_namespace = 4; - string actor_template_name = 5; + string actor_template_namespace = 5; + string actor_template_name = 6; - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; // The sandbox binaries to use for booting this actor from scratch. atelet // fetches the relevant assets and records them with the actor's on-node state // so a later Checkpoint can pin the same version into the snapshot manifest. - SandboxAssets sandbox_assets = 7; + SandboxAssets sandbox_assets = 8; } // AssetFile is one content-addressed file atelet fetches for a sandbox runtime @@ -180,25 +181,26 @@ message CheckpointRequest { string atespace = 2; string actor_name = 3; + string actor_uid = 4; - string actor_template_namespace = 4; - string actor_template_name = 5; + string actor_template_namespace = 5; + string actor_template_name = 6; // Sandbox binary config is not sent on checkpoint: atelet uses the version the // actor is currently running (recorded with the actor's on-node state at // Run/Restore) and records it into the snapshot manifest. - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; - CheckpointType type = 7; + CheckpointType type = 8; // The checkpoint configuration, depending on the type. oneof config { - LocalCheckpointConfiguration local_config = 8; - ExternalCheckpointConfiguration external_config = 9; + LocalCheckpointConfiguration local_config = 9; + ExternalCheckpointConfiguration external_config = 10; } // What should be included in the checkpoint. - SnapshotScope scope = 10; + SnapshotScope scope = 11; } message CheckpointResponse { @@ -209,25 +211,26 @@ message RestoreRequest { string atespace = 2; string actor_name = 3; + string actor_uid = 4; - string actor_template_namespace = 4; - string actor_template_name = 5; + string actor_template_namespace = 5; + string actor_template_name = 6; // Sandbox binary config is not sent on restore: the snapshot is // self-describing. atelet reads the snapshot manifest to recover the pinned // sandbox version that created it. - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; - CheckpointType type = 7; + CheckpointType type = 8; // The checkpoint configuration, depending on the type. oneof config { - LocalCheckpointConfiguration local_config = 8; - ExternalCheckpointConfiguration external_config = 9; + LocalCheckpointConfiguration local_config = 9; + ExternalCheckpointConfiguration external_config = 10; } // What content to restore from the checkpoint. - SnapshotScope scope = 10; + SnapshotScope scope = 11; } message RestoreResponse { diff --git a/internal/proto/ateompb/ateom.pb.go b/internal/proto/ateompb/ateom.pb.go index 2df3a192..12c4fa1a 100644 --- a/internal/proto/ateompb/ateom.pb.go +++ b/internal/proto/ateompb/ateom.pb.go @@ -94,15 +94,16 @@ type RunWorkloadRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,3,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,4,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - RunscPath string `protobuf:"bytes,5,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` + ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // runtime_asset_paths maps a runtime asset name (e.g. "cloud-hypervisor", // "virtiofsd", "kata-kernel", "kata-image", "kata-config") // to the local on-disk path atelet fetched it to (content-addressed, like // runsc_path). Empty for the gVisor runtime, which uses runsc_path. - RuntimeAssetPaths map[string]string `protobuf:"bytes,7,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + RuntimeAssetPaths map[string]string `protobuf:"bytes,8,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -151,6 +152,13 @@ func (x *RunWorkloadRequest) GetActorName() string { return "" } +func (x *RunWorkloadRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *RunWorkloadRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -432,10 +440,11 @@ type CheckpointWorkloadRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,3,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,4,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - RunscPath string `protobuf:"bytes,5,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` + ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // An object storage URI prefix below which the checkpoint data will be // stored. // @@ -444,12 +453,12 @@ type CheckpointWorkloadRequest struct { // memory, sentry state, and filesystem deltas. // // For example: "gs://bucket/actors/1234/snapshots/5678/" - SnapshotUriPrefix string `protobuf:"bytes,7,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` // runtime_asset_paths maps a runtime asset name to the local on-disk path // atelet fetched it to (see RunWorkloadRequest). Empty for gVisor. - RuntimeAssetPaths map[string]string `protobuf:"bytes,8,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + RuntimeAssetPaths map[string]string `protobuf:"bytes,9,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // What content to include in the checkpoint. - Scope SnapshotScope `protobuf:"varint,9,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` + Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -498,6 +507,13 @@ func (x *CheckpointWorkloadRequest) GetActorName() string { return "" } +func (x *CheckpointWorkloadRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *CheckpointWorkloadRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -598,17 +614,18 @@ type RestoreWorkloadRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,3,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,4,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - RunscPath string `protobuf:"bytes,5,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,6,opt,name=spec,proto3" json:"spec,omitempty"` + ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // The object storage URI prefix of the snapshot to restore. - SnapshotUriPrefix string `protobuf:"bytes,7,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` + SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` // runtime_asset_paths maps a runtime asset name to the local on-disk path // atelet fetched it to (see RunWorkloadRequest). Empty for gVisor. - RuntimeAssetPaths map[string]string `protobuf:"bytes,8,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + RuntimeAssetPaths map[string]string `protobuf:"bytes,9,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // What content to restore from the snapshot. - Scope SnapshotScope `protobuf:"varint,9,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` + Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -657,6 +674,13 @@ func (x *RestoreWorkloadRequest) GetActorName() string { return "" } +func (x *RestoreWorkloadRequest) GetActorUid() string { + if x != nil { + return x.ActorUid + } + return "" +} + func (x *RestoreWorkloadRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -746,17 +770,18 @@ var File_ateom_proto protoreflect.FileDescriptor const file_ateom_proto_rawDesc = "" + "\n" + - "\vateom.proto\x12\x05ateom\"\xa9\x03\n" + + "\vateom.proto\x12\x05ateom\"\xc6\x03\n" + "\x12RunWorkloadRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x02 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x03 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x04 \x01(\tR\x11actorTemplateName\x12\x1d\n" + + "actor_name\x18\x02 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12\x1d\n" + "\n" + - "runsc_path\x18\x05 \x01(\tR\trunscPath\x12'\n" + - "\x04spec\x18\x06 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12`\n" + - "\x13runtime_asset_paths\x18\a \x03(\v20.ateom.RunWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + + "runsc_path\x18\x06 \x01(\tR\trunscPath\x12'\n" + + "\x04spec\x18\a \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12`\n" + + "\x13runtime_asset_paths\x18\b \x03(\v20.ateom.RunWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + "\x16RuntimeAssetPathsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"@\n" + @@ -773,36 +798,40 @@ const file_ateom_proto_rawDesc = "" + "\rHTTPGetAction\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" + "\x04port\x18\x02 \x01(\x05R\x04port\"\x15\n" + - "\x13RunWorkloadResponse\"\x93\x04\n" + + "\x13RunWorkloadResponse\"\xb0\x04\n" + "\x19CheckpointWorkloadRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x02 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x03 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x04 \x01(\tR\x11actorTemplateName\x12\x1d\n" + + "actor_name\x18\x02 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12\x1d\n" + "\n" + - "runsc_path\x18\x05 \x01(\tR\trunscPath\x12'\n" + - "\x04spec\x18\x06 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\a \x01(\tR\x11snapshotUriPrefix\x12g\n" + - "\x13runtime_asset_paths\x18\b \x03(\v27.ateom.CheckpointWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x12*\n" + - "\x05scope\x18\t \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x1aD\n" + + "runsc_path\x18\x06 \x01(\tR\trunscPath\x12'\n" + + "\x04spec\x18\a \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + + "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\x12g\n" + + "\x13runtime_asset_paths\x18\t \x03(\v27.ateom.CheckpointWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x12*\n" + + "\x05scope\x18\n" + + " \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x1aD\n" + "\x16RuntimeAssetPathsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"C\n" + "\x1aCheckpointWorkloadResponse\x12%\n" + - "\x0esnapshot_files\x18\x01 \x03(\tR\rsnapshotFiles\"\x8d\x04\n" + + "\x0esnapshot_files\x18\x01 \x03(\tR\rsnapshotFiles\"\xaa\x04\n" + "\x16RestoreWorkloadRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1d\n" + "\n" + - "actor_name\x18\x02 \x01(\tR\tactorName\x128\n" + - "\x18actor_template_namespace\x18\x03 \x01(\tR\x16actorTemplateNamespace\x12.\n" + - "\x13actor_template_name\x18\x04 \x01(\tR\x11actorTemplateName\x12\x1d\n" + + "actor_name\x18\x02 \x01(\tR\tactorName\x12\x1b\n" + + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x128\n" + + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12\x1d\n" + "\n" + - "runsc_path\x18\x05 \x01(\tR\trunscPath\x12'\n" + - "\x04spec\x18\x06 \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + - "\x13snapshot_uri_prefix\x18\a \x01(\tR\x11snapshotUriPrefix\x12d\n" + - "\x13runtime_asset_paths\x18\b \x03(\v24.ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x12*\n" + - "\x05scope\x18\t \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x1aD\n" + + "runsc_path\x18\x06 \x01(\tR\trunscPath\x12'\n" + + "\x04spec\x18\a \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12.\n" + + "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\x12d\n" + + "\x13runtime_asset_paths\x18\t \x03(\v24.ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x12*\n" + + "\x05scope\x18\n" + + " \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x1aD\n" + "\x16RuntimeAssetPathsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x19\n" + diff --git a/internal/proto/ateompb/ateom.proto b/internal/proto/ateompb/ateom.proto index 619ff4f5..a282d81c 100644 --- a/internal/proto/ateompb/ateom.proto +++ b/internal/proto/ateompb/ateom.proto @@ -50,19 +50,20 @@ service Ateom { message RunWorkloadRequest { string atespace = 1; string actor_name = 2; + string actor_uid = 3; - string actor_template_namespace = 3; - string actor_template_name = 4; + string actor_template_namespace = 4; + string actor_template_name = 5; - string runsc_path = 5; + string runsc_path = 6; - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; // runtime_asset_paths maps a runtime asset name (e.g. "cloud-hypervisor", // "virtiofsd", "kata-kernel", "kata-image", "kata-config") // to the local on-disk path atelet fetched it to (content-addressed, like // runsc_path). Empty for the gVisor runtime, which uses runsc_path. - map runtime_asset_paths = 7; + map runtime_asset_paths = 8; } // WorkloadSpec parallels Pod, but with far fewer configurable fields. @@ -108,13 +109,14 @@ enum SnapshotScope { message CheckpointWorkloadRequest { string atespace = 1; string actor_name = 2; + string actor_uid = 3; - string actor_template_namespace = 3; - string actor_template_name = 4; + string actor_template_namespace = 4; + string actor_template_name = 5; - string runsc_path = 5; + string runsc_path = 6; - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; // An object storage URI prefix below which the checkpoint data will be // stored. @@ -124,14 +126,14 @@ message CheckpointWorkloadRequest { // memory, sentry state, and filesystem deltas. // // For example: "gs://bucket/actors/1234/snapshots/5678/" - string snapshot_uri_prefix = 7; + string snapshot_uri_prefix = 8; // runtime_asset_paths maps a runtime asset name to the local on-disk path // atelet fetched it to (see RunWorkloadRequest). Empty for gVisor. - map runtime_asset_paths = 8; + map runtime_asset_paths = 9; // What content to include in the checkpoint. - SnapshotScope scope = 9; + SnapshotScope scope = 10; } message CheckpointWorkloadResponse { @@ -144,23 +146,24 @@ message CheckpointWorkloadResponse { message RestoreWorkloadRequest { string atespace = 1; string actor_name = 2; + string actor_uid = 3; - string actor_template_namespace = 3; - string actor_template_name = 4; + string actor_template_namespace = 4; + string actor_template_name = 5; - string runsc_path = 5; + string runsc_path = 6; - WorkloadSpec spec = 6; + WorkloadSpec spec = 7; // The object storage URI prefix of the snapshot to restore. - string snapshot_uri_prefix = 7; + string snapshot_uri_prefix = 8; // runtime_asset_paths maps a runtime asset name to the local on-disk path // atelet fetched it to (see RunWorkloadRequest). Empty for gVisor. - map runtime_asset_paths = 8; + map runtime_asset_paths = 9; // What content to restore from the snapshot. - SnapshotScope scope = 9; + SnapshotScope scope = 10; } message RestoreWorkloadResponse {