diff --git a/cmd/ateapi/internal/controlapi/create_actor.go b/cmd/ateapi/internal/controlapi/create_actor.go index 50eb05473..577edabb4 100644 --- a/cmd/ateapi/internal/controlapi/create_actor.go +++ b/cmd/ateapi/internal/controlapi/create_actor.go @@ -33,12 +33,11 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ if err := validateCreateActorRequest(req); err != nil { return nil, err } - in := req.GetActor() templateNamespace := in.GetActorTemplateNamespace() templateName := in.GetActorTemplateName() - _, err := s.actorTemplateLister.ActorTemplates(templateNamespace).Get(templateName) + template, err := s.actorTemplateLister.ActorTemplates(templateNamespace).Get(templateName) if err != nil { if k8serrors.IsNotFound(err) { return nil, status.Errorf(codes.FailedPrecondition, "ActorTemplate %s/%s not found", templateNamespace, templateName) @@ -58,6 +57,16 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ return nil, status.Errorf(codes.FailedPrecondition, "Atespace %s not found", atespace) } + actorRef := &ateapipb.ObjectRef{ + Atespace: atespace, + Name: name, + } + + volumes, err := s.createActorVolumes(ctx, actorRef, template) + if err != nil { + return nil, err + } + actor := &ateapipb.Actor{ Metadata: &ateapipb.ResourceMetadata{ Atespace: atespace, @@ -67,9 +76,12 @@ func (s *Service) CreateActor(ctx context.Context, req *ateapipb.CreateActorRequ ActorTemplateNamespace: templateNamespace, ActorTemplateName: templateName, WorkerSelector: in.GetWorkerSelector(), + ActorVolumes: volumes, } stored, err := s.persistence.CreateActor(ctx, actor) if err != nil { + // Cleanup created volumes if DB write fails + s.deleteActorVolumes(ctx, actorRef, volumes) if errors.Is(err, store.ErrAlreadyExists) { return nil, status.Errorf(codes.AlreadyExists, "Actor %s already exists", name) } diff --git a/cmd/ateapi/internal/controlapi/delete_actor.go b/cmd/ateapi/internal/controlapi/delete_actor.go index e58df4cb3..72fe60375 100644 --- a/cmd/ateapi/internal/controlapi/delete_actor.go +++ b/cmd/ateapi/internal/controlapi/delete_actor.go @@ -32,7 +32,21 @@ func (s *Service) DeleteActor(ctx context.Context, req *ateapipb.DeleteActorRequ return nil, err } - deleted, err := s.persistence.DeleteActor(ctx, req.GetActor().GetAtespace(), req.GetActor().GetName()) + atespace := req.GetActor().GetAtespace() + name := req.GetActor().GetName() + + actor, err := s.persistence.GetActor(ctx, atespace, name) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + return nil, status.Errorf(codes.NotFound, "Actor %s not found", name) + } + return nil, fmt.Errorf("while fetching actor: %w", err) + } + + // Delete associated volumes (TODO: best effort?) + s.deleteActorVolumes(ctx, req.GetActor(), actor.GetActorVolumes()) + + deleted, err := s.persistence.DeleteActor(ctx, atespace, name) if err != nil { if errors.Is(err, store.ErrNotFound) { return nil, status.Errorf(codes.NotFound, "Actor %s not found", req.GetActor().GetName()) diff --git a/cmd/ateapi/internal/controlapi/volumes.go b/cmd/ateapi/internal/controlapi/volumes.go new file mode 100644 index 000000000..b415074e3 --- /dev/null +++ b/cmd/ateapi/internal/controlapi/volumes.go @@ -0,0 +1,111 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlapi + +import ( + "context" + "fmt" + "log/slog" + + "github.com/agent-substrate/substrate/internal/volume" + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + globalVolumePlugin = volume.NewMockVolumePlugin() +) + +// TODO: Replace with actual volume plugin search +func getVolumePlugin() volume.VolumePlugin { + return globalVolumePlugin +} + +// TODO: we should persist creation first so that we can handle background cleanup. +// this probably requires us to add a PROVISIONING actor state. + +// createActorVolumes provisions external volumes specified in the actor template. +// It returns the list of created external volumes, or an error if any creation fails. +// If any volume creation fails, it cleans up any volumes created in this call on a best-effort basis. +func (s *Service) createActorVolumes(ctx context.Context, ref *ateapipb.ObjectRef, template *atev1alpha1.ActorTemplate) ([]*ateapipb.ExternalVolume, error) { + var volumes []*ateapipb.ExternalVolume + for _, vol := range template.Spec.Volumes { + if vol.ExternalVolumeTemplate != nil { + // Use a unique name for the volume to ensure idempotency + uniqueVolName := actorVolumeID(ref, vol.Name) + storageVolumeID, err := getVolumePlugin().CreateVolume(ctx, uniqueVolName, vol.ExternalVolumeTemplate.Capacity.String(), vol.ExternalVolumeTemplate.StorageClassName) + if err != nil { + // TODO: need better system - best effort cleanup of already created volumes + s.deleteActorVolumes(ctx, ref, volumes) + return nil, status.Errorf(codes.Internal, "failed to create volume %q: %v", vol.Name, err) + } + volumes = append(volumes, &ateapipb.ExternalVolume{ + ActorVolumeId: uniqueVolName, + StorageVolumeId: storageVolumeID, + VolumeType: "mock", // TODO fix when we support multiple plugins + Status: ateapipb.ExternalVolume_CREATED, + }) + } + } + return volumes, nil +} + +// deleteActorVolumes deletes all external volumes in the list on a best-effort basis. +func (s *Service) deleteActorVolumes(ctx context.Context, ref *ateapipb.ObjectRef, volumes []*ateapipb.ExternalVolume) { + for _, vol := range volumes { + if err := getVolumePlugin().DeleteVolume(ctx, vol.GetStorageVolumeId()); err != nil { + slog.ErrorContext(ctx, "failed to delete volume", + slog.String("atespace", ref.GetAtespace()), + slog.String("actor_id", ref.GetName()), + slog.String("volume_id", vol.GetStorageVolumeId()), + slog.Any("error", err)) + } + } +} + +// getMountedActorVolumes filters the actor's volumes and returns only those that are declared and mounted in the ActorTemplate. +func getMountedActorVolumes(ctx context.Context, ref *ateapipb.ObjectRef, volumes []*ateapipb.ExternalVolume, template *atev1alpha1.ActorTemplate) []*ateapipb.ExternalVolume { + var mounted []*ateapipb.ExternalVolume + for _, vol := range volumes { + // Find the corresponding volume in the ActorTemplate to check if it's mounted + var matchedTemplateVol *atev1alpha1.Volume + for _, tVol := range template.Spec.Volumes { + expectedID := actorVolumeID(ref, tVol.Name) + if vol.GetActorVolumeId() == expectedID { + matchedTemplateVol = &tVol + break + } + } + + if matchedTemplateVol == nil { + slog.WarnContext(ctx, "Volume not found in template, skipping", slog.String("volume_id", vol.GetStorageVolumeId())) + continue + } + + if !isVolumeMounted(matchedTemplateVol.Name, template) { + slog.InfoContext(ctx, "Volume not mounted in template, skipping", slog.String("volume_id", vol.GetStorageVolumeId())) + continue + } + mounted = append(mounted, vol) + } + return mounted +} + +func actorVolumeID(ref *ateapipb.ObjectRef, volumeName string) string { + // TODO consider if this should be actor UUID + return fmt.Sprintf("%s-%s-%s", ref.GetAtespace(), ref.GetName(), volumeName) +} diff --git a/cmd/ateapi/internal/controlapi/workflow.go b/cmd/ateapi/internal/controlapi/workflow.go index 496e1bb89..ae1d321ad 100644 --- a/cmd/ateapi/internal/controlapi/workflow.go +++ b/cmd/ateapi/internal/controlapi/workflow.go @@ -167,6 +167,7 @@ func (w *ActorWorkflow) ResumeActor(ctx context.Context, atespace, name string, steps := []WorkflowStep[*ResumeInput, *ResumeState]{ &LoadActorForResumeStep{store: w.store, actorTemplateLister: w.actorTemplateLister}, &AssignWorkerStep{store: w.store, workerCache: w.workerCache}, + &AttachVolumesStep{store: w.store}, &CallAteletRestoreStep{store: w.store, dialer: w.dialer, kubeClient: w.kubeClient, secretCache: w.secretCache, workerPoolLister: w.workerPoolLister, sandboxConfigLister: w.sandboxConfigLister}, &FinalizeRunningStep{store: w.store}, } @@ -198,6 +199,7 @@ func (w *ActorWorkflow) SuspendActor(ctx context.Context, atespace, name string) &LoadActorForSuspendStep{store: w.store, actorTemplateLister: w.actorTemplateLister}, &MarkSuspendingStep{store: w.store}, &CallAteletSuspendStep{store: w.store, dialer: w.dialer}, + &DetachVolumesStep{store: w.store}, &FinalizeSuspendedStep{store: w.store}, } @@ -228,6 +230,7 @@ func (w *ActorWorkflow) PauseActor(ctx context.Context, atespace, name string) ( &LoadActorForPauseStep{store: w.store, actorTemplateLister: w.actorTemplateLister}, &MarkPausingStep{store: w.store}, &CallAteletPauseStep{store: w.store, dialer: w.dialer}, + &DetachVolumesForPauseStep{store: w.store}, &FinalizePausedStep{store: w.store}, } diff --git a/cmd/ateapi/internal/controlapi/workflow_pause.go b/cmd/ateapi/internal/controlapi/workflow_pause.go index 5ac831ce2..8dd438521 100644 --- a/cmd/ateapi/internal/controlapi/workflow_pause.go +++ b/cmd/ateapi/internal/controlapi/workflow_pause.go @@ -124,7 +124,10 @@ func (s *CallAteletPauseStep) Execute(ctx context.Context, input *PauseInput, st } client := ateletpb.NewAteomHerderClient(ateletConn) - workloadSpec := workloadSpecFromActorTemplate(state.ActorTemplate) + workloadSpec, err := workloadSpecFromActorTemplate(state.ActorTemplate, state.Actor) + if err != nil { + return err + } // Checkpoint does not carry the sandbox config: atelet uses the version the // actor is currently running (recorded on-node at Run/Restore) and pins it @@ -151,6 +154,51 @@ func (s *CallAteletPauseStep) Execute(ctx context.Context, input *PauseInput, st func (s *CallAteletPauseStep) RetryBackoff() *wait.Backoff { return nil } +type DetachVolumesForPauseStep struct { + store store.Interface +} + +func (s *DetachVolumesForPauseStep) Name() string { return "DetachVolumesForPause" } + +func (s *DetachVolumesForPauseStep) IsComplete(ctx context.Context, input *PauseInput, state *PauseState) (bool, error) { + // TODO replace with a proper check on the volumes. + return state.Actor.GetStatus() == ateapipb.Actor_STATUS_PAUSED && state.Actor.GetAteomPodNamespace() == "", nil +} + +func (s *DetachVolumesForPauseStep) Execute(ctx context.Context, input *PauseInput, state *PauseState) error { + if state.Actor.GetAteomPodNamespace() == "" { + slog.WarnContext(ctx, "Actor has no assigned worker pod during pause, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + + worker, err := s.store.GetWorker(ctx, state.Actor.GetAteomPodNamespace(), state.Actor.GetWorkerPoolName(), state.Actor.GetAteomPodName()) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + slog.WarnContext(ctx, "Worker not found in store during pause, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + return fmt.Errorf("failed to get worker: %w", err) + } + + node := worker.GetNodeName() + if node == "" { + slog.WarnContext(ctx, "Worker has no assigned node name during pause, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + + ref := &ateapipb.ObjectRef{Atespace: state.Actor.GetMetadata().GetAtespace(), Name: state.Actor.GetMetadata().GetName()} + for _, vol := range getMountedActorVolumes(ctx, ref, state.Actor.GetActorVolumes(), state.ActorTemplate) { + slog.InfoContext(ctx, "Detaching volume from node", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) + err := getVolumePlugin().DetachVolume(ctx, vol.GetStorageVolumeId(), node) + if err != nil { + return fmt.Errorf("failed to detach volume %q from node %q: %w", vol.GetStorageVolumeId(), node, err) + } + } + return nil +} + +func (s *DetachVolumesForPauseStep) RetryBackoff() *wait.Backoff { return nil } + type FinalizePausedStep struct { store store.Interface } diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 992b2755a..ff2d114f5 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -208,6 +208,45 @@ func (s *AssignWorkerStep) RetryBackoff() *wait.Backoff { } } +type AttachVolumesStep struct { + store store.Interface +} + +func (s *AttachVolumesStep) Name() string { return "AttachVolumes" } + +func (s *AttachVolumesStep) IsComplete(ctx context.Context, input *ResumeInput, state *ResumeState) (bool, error) { + // TODO replace with a proper check on the volumes. + return state.Actor.GetStatus() == ateapipb.Actor_STATUS_RUNNING, nil +} + +func (s *AttachVolumesStep) Execute(ctx context.Context, input *ResumeInput, state *ResumeState) error { + if state.Actor.GetAteomPodNamespace() == "" { + return fmt.Errorf("actor has no assigned worker pod") + } + + worker, err := s.store.GetWorker(ctx, state.Actor.GetAteomPodNamespace(), state.Actor.GetWorkerPoolName(), state.Actor.GetAteomPodName()) + if err != nil { + return fmt.Errorf("failed to get worker for volume attachment: %w", err) + } + + node := worker.GetNodeName() + if node == "" { + return fmt.Errorf("assigned worker has no node name") + } + + ref := &ateapipb.ObjectRef{Atespace: state.Actor.GetMetadata().GetAtespace(), Name: state.Actor.GetMetadata().GetName()} + for _, vol := range getMountedActorVolumes(ctx, ref, state.Actor.GetActorVolumes(), state.ActorTemplate) { + slog.InfoContext(ctx, "Attaching volume to node", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) + err := getVolumePlugin().AttachVolume(ctx, vol.GetStorageVolumeId(), node) + if err != nil { + return fmt.Errorf("failed to attach volume %q to node %q: %w", vol.GetStorageVolumeId(), node, err) + } + } + return nil +} + +func (s *AttachVolumesStep) RetryBackoff() *wait.Backoff { return nil } + func (s *AssignWorkerStep) findFreeWorker( workers []*ateapipb.Worker, templateClass atev1alpha1.SandboxClass, @@ -261,7 +300,7 @@ func (s *CallAteletRestoreStep) Execute(ctx context.Context, input *ResumeInput, } client := ateletpb.NewAteomHerderClient(ateletConn) - workloadSpec, err := workloadSpecFromActorTemplateWithEnv(ctx, s.kubeClient, s.secretCache, state.ActorTemplate) + workloadSpec, err := workloadSpecFromActorTemplateWithEnv(ctx, s.kubeClient, s.secretCache, state.ActorTemplate, state.Actor) if err != nil { return err } diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend.go b/cmd/ateapi/internal/controlapi/workflow_suspend.go index 28297d1fd..01c5d12d4 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend.go @@ -126,7 +126,10 @@ func (s *CallAteletSuspendStep) Execute(ctx context.Context, input *SuspendInput } client := ateletpb.NewAteomHerderClient(ateletConn) - workloadSpec := workloadSpecFromActorTemplate(state.ActorTemplate) + workloadSpec, err := workloadSpecFromActorTemplate(state.ActorTemplate, state.Actor) + if err != nil { + return err + } // Checkpoint does not carry the sandbox config: atelet uses the version the // actor is currently running (recorded on-node at Run/Restore) and pins it @@ -153,6 +156,51 @@ func (s *CallAteletSuspendStep) Execute(ctx context.Context, input *SuspendInput func (s *CallAteletSuspendStep) RetryBackoff() *wait.Backoff { return nil } +type DetachVolumesStep struct { + store store.Interface +} + +func (s *DetachVolumesStep) Name() string { return "DetachVolumes" } + +func (s *DetachVolumesStep) IsComplete(ctx context.Context, input *SuspendInput, state *SuspendState) (bool, error) { + // TODO replace with a proper check on the volumes. + return state.Actor.GetStatus() == ateapipb.Actor_STATUS_SUSPENDED && state.Actor.GetAteomPodNamespace() == "", nil +} + +func (s *DetachVolumesStep) Execute(ctx context.Context, input *SuspendInput, state *SuspendState) error { + if state.Actor.GetAteomPodNamespace() == "" { + slog.WarnContext(ctx, "Actor has no assigned worker pod during suspend, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + + worker, err := s.store.GetWorker(ctx, state.Actor.GetAteomPodNamespace(), state.Actor.GetWorkerPoolName(), state.Actor.GetAteomPodName()) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + slog.WarnContext(ctx, "Worker not found in store during suspend, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + return fmt.Errorf("failed to get worker: %w", err) + } + + node := worker.GetNodeName() + if node == "" { + slog.WarnContext(ctx, "Worker has no assigned node name during suspend, skipping detach volumes", slog.String("actor_id", input.ActorName)) + return nil + } + + ref := &ateapipb.ObjectRef{Atespace: state.Actor.GetMetadata().GetAtespace(), Name: state.Actor.GetMetadata().GetName()} + for _, vol := range getMountedActorVolumes(ctx, ref, state.Actor.GetActorVolumes(), state.ActorTemplate) { + slog.InfoContext(ctx, "Detaching volume from node", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) + err := getVolumePlugin().DetachVolume(ctx, vol.GetStorageVolumeId(), node) + if err != nil { + return fmt.Errorf("failed to detach volume %q from node %q: %w", vol.GetStorageVolumeId(), node, err) + } + } + return nil +} + +func (s *DetachVolumesStep) RetryBackoff() *wait.Backoff { return nil } + type FinalizeSuspendedStep struct { store store.Interface } diff --git a/cmd/ateapi/internal/controlapi/workload_spec.go b/cmd/ateapi/internal/controlapi/workload_spec.go index a0d0a806b..eb89339ae 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec.go +++ b/cmd/ateapi/internal/controlapi/workload_spec.go @@ -22,6 +22,7 @@ import ( "github.com/agent-substrate/substrate/internal/proto/ateletpb" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" corev1 "k8s.io/api/core/v1" @@ -35,7 +36,7 @@ const envSecretCacheTTL = 30 * time.Second // workloadSpecFromActorTemplate builds a WorkloadSpec without resolving // container env vars. Use this when downstream consumers (e.g. checkpoint // requests) don't need env entries materialized. -func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate) *ateletpb.WorkloadSpec { +func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) (*ateletpb.WorkloadSpec, error) { workloadSpec := &ateletpb.WorkloadSpec{ PauseImage: actorTemplate.Spec.PauseImage, } @@ -54,6 +55,10 @@ func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate) *at } } + if err := appendExternalVolumes(workloadSpec, actorTemplate, actor); err != nil { + return nil, err + } + for _, ctr := range actorTemplate.Spec.Containers { ateletCtr := &ateletpb.Container{ Name: ctr.Name, @@ -70,14 +75,17 @@ func workloadSpecFromActorTemplate(actorTemplate *atev1alpha1.ActorTemplate) *at workloadSpec.Containers = append(workloadSpec.Containers, ateletCtr) } - return workloadSpec + return workloadSpec, nil } // workloadSpecFromActorTemplateWithEnv builds a WorkloadSpec and resolves each // container's env vars against the cluster. kubeClient must be non-nil; // secretCache is optional and, when supplied, deduplicates Secret reads. -func workloadSpecFromActorTemplateWithEnv(ctx context.Context, kubeClient kubernetes.Interface, secretCache *envSecretCache, actorTemplate *atev1alpha1.ActorTemplate) (*ateletpb.WorkloadSpec, error) { - workloadSpec := workloadSpecFromActorTemplate(actorTemplate) +func workloadSpecFromActorTemplateWithEnv(ctx context.Context, kubeClient kubernetes.Interface, secretCache *envSecretCache, actorTemplate *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) (*ateletpb.WorkloadSpec, error) { + workloadSpec, err := workloadSpecFromActorTemplate(actorTemplate, actor) + if err != nil { + return nil, err + } resolver := envResolver{ kubeClient: kubeClient, @@ -100,6 +108,60 @@ func workloadSpecFromActorTemplateWithEnv(ctx context.Context, kubeClient kubern return workloadSpec, nil } +// appendExternalVolumes maps template external volumes to resolved actor volumes and appends them to workloadSpec +// if they are referenced in container volumeMounts. +func appendExternalVolumes(workloadSpec *ateletpb.WorkloadSpec, template *atev1alpha1.ActorTemplate, actor *ateapipb.Actor) error { + if template == nil { + return nil + } + for _, vol := range template.Spec.Volumes { + if vol.ExternalVolumeTemplate != nil { + if !isVolumeMounted(vol.Name, template) { + continue + } + if actor == nil { + return fmt.Errorf("actor is required when externalVolumeTemplate is present") + } + + var storageVolID string + var volType string + expectedID := actorVolumeID(&ateapipb.ObjectRef{Atespace: actor.GetMetadata().GetAtespace(), Name: actor.GetMetadata().GetName()}, vol.Name) + for _, dbVol := range actor.GetActorVolumes() { + if dbVol.GetActorVolumeId() == expectedID { + storageVolID = dbVol.GetStorageVolumeId() + volType = dbVol.GetVolumeType() + break + } + } + if storageVolID == "" { + return fmt.Errorf("volume %s not found for actor %s", vol.Name, actor.GetMetadata().GetName()) + } + workloadSpec.Volumes = append(workloadSpec.Volumes, &ateletpb.Volume{ + Name: vol.Name, + Type: ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL, + Source: &ateletpb.Volume_External{ + External: &ateletpb.ExternalVolumeSource{ + StorageVolumeId: storageVolID, + VolumeType: volType, + }, + }, + }) + } + } + return nil +} + +func isVolumeMounted(volumeName string, template *atev1alpha1.ActorTemplate) bool { + for _, ctr := range template.Spec.Containers { + for _, mount := range ctr.VolumeMounts { + if mount.Name == volumeName { + return true + } + } + } + return false +} + // toAteletReadyz projects the CRD readyz field onto the ateletpb wire type. // Returns nil when the source is nil so containers without a probe stay // unchanged on the wire. diff --git a/cmd/ateapi/internal/controlapi/workload_spec_test.go b/cmd/ateapi/internal/controlapi/workload_spec_test.go index 24918f17f..39605cfcb 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec_test.go +++ b/cmd/ateapi/internal/controlapi/workload_spec_test.go @@ -20,6 +20,7 @@ import ( "github.com/agent-substrate/substrate/internal/proto/ateletpb" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/google/go-cmp/cmp" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -172,7 +173,10 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := workloadSpecFromActorTemplate(tt.template) + got, err := workloadSpecFromActorTemplate(tt.template, nil) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate failed: %v", err) + } if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" { t.Errorf("WorkloadSpec mismatch (-want +got):\n%s", diff) } @@ -304,7 +308,7 @@ func TestWorkloadSpecFromActorTemplateWithEnv(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { kubeClient := fake.NewSimpleClientset(tt.secrets...) - got, err := workloadSpecFromActorTemplateWithEnv(context.Background(), kubeClient, nil, tt.template) + got, err := workloadSpecFromActorTemplateWithEnv(context.Background(), kubeClient, nil, tt.template, nil) if tt.wantErrCode != codes.OK { if status.Code(err) != tt.wantErrCode { t.Fatalf("error code = %v, want %v: %v", status.Code(err), tt.wantErrCode, err) @@ -322,7 +326,7 @@ func TestWorkloadSpecFromActorTemplateWithEnv(t *testing.T) { } func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) { - got := workloadSpecFromActorTemplate(&atev1alpha1.ActorTemplate{ + got, err := workloadSpecFromActorTemplate(&atev1alpha1.ActorTemplate{ ObjectMeta: metav1.ObjectMeta{Name: "tmpl-readyz", Namespace: "agent-ns"}, Spec: atev1alpha1.ActorTemplateSpec{ Containers: []atev1alpha1.Container{ @@ -339,7 +343,10 @@ func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) { }, }, }, - }) + }, nil) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate failed: %v", err) + } want := &ateletpb.WorkloadSpec{ Containers: []*ateletpb.Container{ @@ -401,10 +408,10 @@ func TestWorkloadSpecFromActorTemplateWithEnvCachesSecretsAcrossCalls(t *testing }, } - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate); err != nil { + if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { t.Fatalf("first workloadSpecFromActorTemplateWithEnv failed: %v", err) } - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate); err != nil { + if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { t.Fatalf("second workloadSpecFromActorTemplateWithEnv failed: %v", err) } if got := secretGetCount(kubeClient); got != 1 { @@ -412,7 +419,7 @@ func TestWorkloadSpecFromActorTemplateWithEnvCachesSecretsAcrossCalls(t *testing } expireSecretCache(secretCache) - if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate); err != nil { + if _, err := workloadSpecFromActorTemplateWithEnv(ctx, kubeClient, secretCache, actorTemplate, nil); err != nil { t.Fatalf("third workloadSpecFromActorTemplateWithEnv failed: %v", err) } if got := secretGetCount(kubeClient); got != 2 { @@ -439,3 +446,92 @@ func secretGetCount(kubeClient *fake.Clientset) int { } return count } + +func TestAppendExternalVolumes(t *testing.T) { + template := &atev1alpha1.ActorTemplate{ + Spec: atev1alpha1.ActorTemplateSpec{ + Containers: []atev1alpha1.Container{ + { + Name: "main", + VolumeMounts: []atev1alpha1.VolumeMount{ + {Name: "vol-1", MountPath: "/mnt/vol1"}, + }, + }, + }, + Volumes: []atev1alpha1.Volume{ + { + Name: "vol-1", + VolumeSource: atev1alpha1.VolumeSource{ + ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{ + StorageClassName: "pd-standard", + }, + }, + }, + { + Name: "vol-2", + VolumeSource: atev1alpha1.VolumeSource{ + DurableDir: &atev1alpha1.DurableDirVolumeSource{}, + }, + }, + { + Name: "unmounted-vol", + VolumeSource: atev1alpha1.VolumeSource{ + ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{ + StorageClassName: "pd-standard", + }, + }, + }, + }, + }, + } + + actor := &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{ + Atespace: "space-abc", + Name: "actor-123", + }, + ActorVolumes: []*ateapipb.ExternalVolume{ + { + ActorVolumeId: "space-abc-actor-123-vol-1", + StorageVolumeId: "vol-gce-pd-123", + VolumeType: "pd-standard", + }, + }, + } + + workloadSpec := &ateletpb.WorkloadSpec{} + if err := appendExternalVolumes(workloadSpec, template, actor); err != nil { + t.Fatalf("appendExternalVolumes unexpected error: %v", err) + } + + want := &ateletpb.WorkloadSpec{ + Volumes: []*ateletpb.Volume{ + { + Name: "vol-1", + Type: ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL, + Source: &ateletpb.Volume_External{ + External: &ateletpb.ExternalVolumeSource{ + StorageVolumeId: "vol-gce-pd-123", + VolumeType: "pd-standard", + }, + }, + }, + }, + } + + if diff := cmp.Diff(want, workloadSpec, protocmp.Transform()); diff != "" { + t.Errorf("appendExternalVolumes mismatch (-want +got):\n%s", diff) + } + + // Test missing mounted volume returns an error + missingActor := &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{ + Atespace: "space-abc", + Name: "actor-123", + }, + ActorVolumes: []*ateapipb.ExternalVolume{}, + } + if err := appendExternalVolumes(&ateletpb.WorkloadSpec{}, template, missingActor); err == nil { + t.Errorf("appendExternalVolumes expected error for missing volume, got nil") + } +} diff --git a/cmd/atecontroller/internal/controllers/workerpool_apply.go b/cmd/atecontroller/internal/controllers/workerpool_apply.go index faa94f572..9f2983e20 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_apply.go +++ b/cmd/atecontroller/internal/controllers/workerpool_apply.go @@ -47,7 +47,8 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool) *appsv1ac.Deployment ). WithVolumeMounts(corev1ac.VolumeMount(). WithName("run-ateom"). - WithMountPath(ateompath.BasePath)) + WithMountPath(ateompath.BasePath). + WithMountPropagation(corev1.MountPropagationHostToContainer)) podSpecAC := corev1ac.PodSpec(). WithSecurityContext(corev1ac.PodSecurityContext(). diff --git a/cmd/atecontroller/internal/controllers/workerpool_apply_test.go b/cmd/atecontroller/internal/controllers/workerpool_apply_test.go index 1cdc972d6..f2ad7e5c9 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_apply_test.go +++ b/cmd/atecontroller/internal/controllers/workerpool_apply_test.go @@ -299,7 +299,8 @@ func expectedDeploymentApplyConfig(mutatePodSpec func(*corev1ac.PodSpecApplyConf WithFieldPath("metadata.uid")))). WithVolumeMounts(corev1ac.VolumeMount(). WithName("run-ateom"). - WithMountPath(ateompath.BasePath)). + WithMountPath(ateompath.BasePath). + WithMountPropagation(corev1.MountPropagationHostToContainer)). WithResources(corev1ac.ResourceRequirements())) podSpecAC.NodeSelector = map[string]string{} diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index 4071ee645..9ccd20ec8 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -212,7 +212,7 @@ func NewService( return wms } -func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*ateletpb.RunResponse, error) { +func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (resp *ateletpb.RunResponse, err error) { if err := validateRunRequest(req); err != nil { // status.Error so the interceptor surfaces InvalidArgument and the // message instead of masking both as Internal. @@ -234,6 +234,16 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (*atele return nil, fmt.Errorf("while resetting actor dirs: %w", err) } + if err := s.mountExternalVolumes(ctx, atespace, actorID, req.GetSpec().GetVolumes()); err != nil { + return nil, err + } + defer func() { + if err != nil { + // TODO cleanup orphaned volumes + _ = s.unmountExternalVolumes(ctx, atespace, actorID, req.GetSpec().GetVolumes()) + } + }() + // 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. @@ -350,6 +360,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe // in the metadata if the error is not retriable. return nil, fmt.Errorf("while calling ateom.CheckpointWorkload: %w", err) } + sandboxRec.SnapshotFiles = resp.GetSnapshotFiles() if len(sandboxRec.SnapshotFiles) == 0 { return nil, ateerrors.NewGRPCError(ctx, codes.DataLoss, ateerrors.ReasonInvalidCheckpointResult, ateerrors.ActorCrashedMetadata(), errors.New("ateom reported no snapshot files for checkpoint")) @@ -369,6 +380,9 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe return nil, fmt.Errorf("unexpected checkpoint type: %v", req.GetType()) } + // TODO cleanup orphaned volumes + _ = s.unmountExternalVolumes(ctx, atespace, actorID, req.GetSpec().GetVolumes()) + // Note: we do not crash the actor if resetting the directory fails. if err := resetActorDirs(atespace, actorID); err != nil { return nil, fmt.Errorf("while resetting actor dirs: %w", err) @@ -449,7 +463,7 @@ func (s *AteomHerder) uploadExternalCheckpoint(ctx context.Context, req *ateletp return nil } -func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) (*ateletpb.RestoreResponse, error) { +func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) (resp *ateletpb.RestoreResponse, err error) { if err := validateRestoreRequest(req); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } @@ -462,6 +476,16 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) return nil, fmt.Errorf("while resetting actor dirs: %w", err) } + if err := s.mountExternalVolumes(ctx, atespace, actorID, req.GetSpec().GetVolumes()); err != nil { + return nil, err + } + defer func() { + if err != nil { + // TODO cleanup orphaned mounts + _ = s.unmountExternalVolumes(ctx, atespace, actorID, req.GetSpec().GetVolumes()) + } + }() + checkpointDir := ateompath.RestoreStateDir(atespace, actorID) // Per-step timing so we can attribute resume latency between the rustfs @@ -657,12 +681,9 @@ func (s *AteomHerder) prepareOCIBundles( if err := writeFileAtomic(filepath.Join(identityDir, ActorIDFileName), []byte(actorID), 0o644); err != nil { return fmt.Errorf("while writing actor identity file: %w", err) } - - ddVolumes := make(map[string]bool) // make directories for all durable-dir volumes for _, vol := range spec.GetVolumes() { if vol.GetType() == ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR { - ddVolumes[vol.GetName()] = true volPath := ateompath.DurableDirVolumeMountPoint(atespace, actorID, vol.GetName()) if err := os.MkdirAll(volPath, 0o700); err != nil { return fmt.Errorf("while creating %q: %w", volPath, err) @@ -700,6 +721,7 @@ func (s *AteomHerder) prepareOCIBundles( ateompath.AteomNetNSPath(targetAteomUid), "", // pause is sandbox infra; it gets no actor identity mount. nil, + nil, ); err != nil { return wrapFileSystemErr("while creating pause OCI bundle", err) } @@ -713,12 +735,6 @@ func (s *AteomHerder) prepareOCIBundles( for _, env := range ctr.GetEnv() { envs = append(envs, fmt.Sprintf("%s=%s", env.GetName(), env.GetValue())) } - var ddMounts []*ateletpb.VolumeMount - for _, vm := range ctr.GetVolumeMounts() { - if ddVolumes[vm.GetName()] { - ddMounts = append(ddMounts, vm) - } - } g.Go(func() error { if err := prepareOCIDirectory( gCtx, @@ -735,7 +751,8 @@ func (s *AteomHerder) prepareOCIBundles( }, ateompath.AteomNetNSPath(targetAteomUid), identityDir, - ddMounts, + spec.GetVolumes(), + ctr.GetVolumeMounts(), ); err != nil { return wrapFileSystemErr(fmt.Sprintf("while creating %q OCI bundle", ctr.GetName()), err) } @@ -1058,5 +1075,22 @@ func resetActorDirs(atespace, actorID string) error { return wrapFileSystemErr("while creating durable-dir volumes mount dir: %w", err) } + // Do not call RemoveAll on volume directories in case the unmount failed. + // We do not want to delete mount content. + volumesDir := ateompath.VolumesDir(atespace, actorID) + entries, err := os.ReadDir(volumesDir) + if err != nil && !os.IsNotExist(err) { + return wrapFileSystemErr("while reading volumes dir: %w", err) + } + for _, entry := range entries { + volPath := filepath.Join(volumesDir, entry.Name()) + if err := os.Remove(volPath); err != nil { + return wrapFileSystemErr("while removing volume dir: %w", err) + } + } + if err := os.MkdirAll(volumesDir, 0o755); err != nil { + return wrapFileSystemErr("while creating volumes dir: %w", err) + } + return nil } diff --git a/cmd/atelet/oci.go b/cmd/atelet/oci.go index 68b6f34a5..a5b4a0980 100644 --- a/cmd/atelet/oci.go +++ b/cmd/atelet/oci.go @@ -29,6 +29,7 @@ import ( "github.com/agent-substrate/substrate/cmd/atelet/internal/memorypullcache" "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/opencontainers/runtime-spec/specs-go" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -53,7 +54,7 @@ const ( ActorIDFileName = "actor-id" ) -func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryPullCache, atespace, actorID, 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, atespace, actorID, containerName, ref string, args []string, env []string, annotations map[string]string, netns string, identityDir string, volumes []*ateletpb.Volume, volumeMounts []*ateletpb.VolumeMount) error { tracer := otel.Tracer("prepareOCIDirectory") ctx, span := tracer.Start(ctx, "prepareOCIDirectory") @@ -90,7 +91,14 @@ func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryP } } - ociSpec := buildActorOCISpec(atespace, actorID, args, env, annotations, netns, identityDir, durableDirVolumeMounts) + // Create directories in rootfs to mount all volumes. + for _, vm := range volumeMounts { + if err := createMountPoint(rootPath, vm.GetMountPath()); err != nil { + return fmt.Errorf("while creating volume mount point %q: %w", vm.GetMountPath(), err) + } + } + + ociSpec := buildActorOCISpec(atespace, actorID, args, env, annotations, netns, identityDir, volumes, volumeMounts) ociSpecBytes, err := json.MarshalIndent(ociSpec, "", " ") if err != nil { return fmt.Errorf("while marshaling OCI spec: %w", err) @@ -107,7 +115,7 @@ func prepareOCIDirectory(ctx context.Context, pullCache *memorypullcache.MemoryP // 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, actorID string, args []string, env []string, annotations map[string]string, netns string, identityDir string, durableDirVolumeMounts []*ateletpb.VolumeMount) *specs.Spec { +func buildActorOCISpec(atespace string, actorID string, args []string, env []string, annotations map[string]string, netns string, identityDir string, volumes []*ateletpb.Volume, volumeMounts []*ateletpb.VolumeMount) *specs.Spec { envVars := []string{ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", } @@ -220,12 +228,27 @@ func buildActorOCISpec(atespace string, actorID string, args []string, env []str Annotations: annotations, } - // Prepare and mount durable-dir volumes. - for _, vm := range durableDirVolumeMounts { + // Prepare and mount all volumes. + volumeTypes := make(map[string]ateletpb.VolumeType) + for _, vol := range volumes { + volumeTypes[vol.GetName()] = vol.GetType() + } + + for _, vm := range volumeMounts { + var srcPath string + switch volumeTypes[vm.GetName()] { + case ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR: + srcPath = ateompath.DurableDirVolumeMountPoint(atespace, actorID, vm.GetName()) + case ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL: + srcPath = ateompath.VolumeHostPath(atespace, actorID, vm.GetName()) + default: + continue + } spec.Mounts = append(spec.Mounts, specs.Mount{ Destination: vm.GetMountPath(), Type: "bind", - Source: ateompath.DurableDirVolumeMountPoint(atespace, actorID, vm.GetName()), + Source: srcPath, + Options: []string{"bind", "rw"}, }) } diff --git a/cmd/atelet/oci_test.go b/cmd/atelet/oci_test.go index 737f5c0db..28b02b499 100644 --- a/cmd/atelet/oci_test.go +++ b/cmd/atelet/oci_test.go @@ -94,6 +94,7 @@ func TestBuildActorOCISpec_IdentityMount(t *testing.T) { "/run/netns/x", "/host/actors/atespace:id/identity", nil, + nil, ) found := false for _, m := range spec.Mounts { @@ -118,7 +119,7 @@ func TestBuildActorOCISpec_IdentityMount(t *testing.T) { // Without an identity dir (the pause container), no identity mount appears. func TestBuildActorOCISpec_NoIdentityMountForPause(t *testing.T) { - bare := buildActorOCISpec("atespace", "id", []string{"/pause"}, nil, nil, "/run/netns/x", "", nil) + bare := buildActorOCISpec("atespace", "id", []string{"/pause"}, nil, nil, "/run/netns/x", "", nil, nil) for _, m := range bare.Mounts { if m.Destination == IdentityMountPath { t.Errorf("identity mount must be absent when identityDir is empty") @@ -134,11 +135,16 @@ func TestBuildActorOCISpec_DurableDirVolumeMounts(t *testing.T) { {Name: "data", MountPath: "/var/data"}, {Name: "cache", MountPath: "/var/cache"}, } + volumes := []*ateletpb.Volume{ + {Name: "data", Type: ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR}, + {Name: "cache", Type: ateletpb.VolumeType_VOLUME_TYPE_DURABLE_DIR}, + } spec := buildActorOCISpec( atespace, id, []string{"/app"}, nil, nil, "/run/netns/x", "", + volumes, durableDirs, ) diff --git a/cmd/atelet/volumes.go b/cmd/atelet/volumes.go new file mode 100644 index 000000000..70e6eda35 --- /dev/null +++ b/cmd/atelet/volumes.go @@ -0,0 +1,74 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "log/slog" + "os" + + "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "github.com/agent-substrate/substrate/internal/volume" +) + +var ( + globalVolumePlugin = volume.NewMockVolumePlugin() +) + +// TODO: Replace with actual volume plugin search +func getVolumePlugin() volume.VolumePlugin { + return globalVolumePlugin +} + +func (s *AteomHerder) mountExternalVolumes(ctx context.Context, atespace, actorID string, volumes []*ateletpb.Volume) error { + for _, vol := range volumes { + if vol.GetType() != ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL { + continue + } + ext := vol.GetExternal() + if ext == nil { + continue + } + hostPath := ateompath.VolumeHostPath(atespace, actorID, vol.GetName()) + if err := os.MkdirAll(hostPath, 0o750); err != nil { + return fmt.Errorf("failed to create mount point %q: %w", hostPath, err) + } + slog.InfoContext(ctx, "Mounting volume", slog.String("volume_id", ext.GetStorageVolumeId()), slog.String("host_path", hostPath)) + if err := getVolumePlugin().MountVolume(ctx, ext.GetStorageVolumeId(), hostPath); err != nil { + return fmt.Errorf("failed to mount volume %q to %q: %w", ext.GetStorageVolumeId(), hostPath, err) + } + } + return nil +} + +func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, atespace, actorID string, volumes []*ateletpb.Volume) error { + for _, vol := range volumes { + if vol.GetType() != ateletpb.VolumeType_VOLUME_TYPE_EXTERNAL { + continue + } + ext := vol.GetExternal() + if ext == nil { + continue + } + hostPath := ateompath.VolumeHostPath(atespace, actorID, vol.GetName()) + slog.InfoContext(ctx, "Unmounting volume", slog.String("volume_id", ext.GetStorageVolumeId()), slog.String("host_path", hostPath)) + if err := getVolumePlugin().UnmountVolume(ctx, ext.GetStorageVolumeId(), hostPath); err != nil { + slog.ErrorContext(ctx, "failed to unmount volume", slog.String("volume_id", ext.GetStorageVolumeId()), slog.String("host_path", hostPath), slog.Any("error", err)) + } + } + return nil +} diff --git a/demos/counter/counter.go b/demos/counter/counter.go index bd5b90848..0b18f0ff3 100644 --- a/demos/counter/counter.go +++ b/demos/counter/counter.go @@ -27,6 +27,7 @@ import ( "net" "net/http" "os" + "path/filepath" "strconv" "sync" "sync/atomic" @@ -41,20 +42,18 @@ var ( fileMutex sync.Mutex ) -const fileCounterPath = "/home/counter/a.txt" - -func incrementFileCounter() int { +func incrementFileCounter(filePath string) int { fileMutex.Lock() defer fileMutex.Unlock() counter := 0 - data, err := os.ReadFile(fileCounterPath) + data, err := os.ReadFile(filePath) if err == nil { if i, err := strconv.Atoi(string(data)); err == nil { counter = i } } counter++ - err = os.WriteFile(fileCounterPath, []byte(strconv.Itoa(counter)), 0o644) + err = os.WriteFile(filePath, []byte(strconv.Itoa(counter)), 0o644) if err != nil { return -1 } @@ -62,6 +61,8 @@ func incrementFileCounter() int { } func main() { + fileCounterDirectory := pflag.String("file-counter-directory", "/home/counter", "Directory for file counter") + validateExistingFilePath := pflag.String("validate-existing-file-path", "", "Path to existing file to validate reading") pflag.Parse() ctx := context.Background() @@ -70,11 +71,23 @@ func main() { defaultMux := http.NewServeMux() defaultMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - fileCounter := incrementFileCounter() - + fileCounter := incrementFileCounter(filepath.Join(*fileCounterDirectory, "a.txt")) memoryCounter := atomic.AddUint64(&requestCount, 1) currentIP := getCurrentIP() - response := fmt.Sprintf("hello from: %s | preserved memory count: %d | preserved file counter: %d\n", currentIP, memoryCounter, fileCounter) + + fileContentStr := "" + if *validateExistingFilePath != "" { + fileContent, err := os.ReadFile(*validateExistingFilePath) + if err != nil { + fileResponse := fmt.Sprintf("failed to read test file: %s\n", err.Error()) + w.WriteHeader(http.StatusOK) + w.Write([]byte(fileResponse)) + return + } + fileContentStr = fmt.Sprintf(" | file content: %s", string(fileContent)) + } + + response := fmt.Sprintf("hello from: %s | preserved memory count: %d | preserved file counter: %d%s\n", currentIP, memoryCounter, fileCounter, fileContentStr) slog.InfoContext(ctx, "Handled request", slog.String("response", response)) w.WriteHeader(http.StatusOK) diff --git a/demos/counter/counter.yaml.tmpl b/demos/counter/counter.yaml.tmpl index f1dc416b7..99d139455 100644 --- a/demos/counter/counter.yaml.tmpl +++ b/demos/counter/counter.yaml.tmpl @@ -42,7 +42,7 @@ spec: containers: - name: counter image: ko://github.com/agent-substrate/substrate/demos/counter - command: ["/ko-app/counter"] + command: ["/ko-app/counter", "--validate-existing-file-path=/external-data/test.txt"] readyz: httpGet: path: /readyz @@ -50,6 +50,8 @@ spec: volumeMounts: - name: data mountPath: /home/counter + - name: external-data + mountPath: /external-data workerSelector: matchLabels: workload: counter @@ -60,3 +62,7 @@ spec: volumes: - name: data durableDir: {} + - name: external-data + externalVolumeTemplate: + capacity: 1Gi + storageClassName: standard diff --git a/hack/run-demo-counter-kind.sh b/hack/run-demo-counter-kind.sh new file mode 100755 index 000000000..5b5f6d502 --- /dev/null +++ b/hack/run-demo-counter-kind.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit -o nounset -o pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "${ROOT}" + +# ANSI color codes for pretty output +COLOR_CYAN='\033[1;36m' +COLOR_GREEN='\033[1;32m' +COLOR_YELLOW='\033[1;33m' +COLOR_RED='\033[1;31m' +COLOR_RESET='\033[0m' + +function log_step() { + echo -e "${COLOR_CYAN}[step]: $1${COLOR_RESET}" +} + +function log_success() { + echo -e "${COLOR_GREEN}[success]: $1${COLOR_RESET}" +} + +function log_warn() { + echo -e "${COLOR_YELLOW}[warning]: $1${COLOR_RESET}" +} + +function log_error() { + echo -e "${COLOR_RED}[error]: $1${COLOR_RESET}" +} + +log_step "Cleaning up previous test" +./hack/delete-kind-cluster.sh || true + +log_step "Installing kind cluster" +./hack/create-kind-cluster.sh + +log_step "Installing ATE control plane, Valkey, and RustFS..." +./hack/install-ate-kind.sh --deploy-ate-system + +log_step "Installing counter demo..." +./hack/install-ate-kind.sh --deploy-demo-counter + +log_step "Installing kubectl-ate CLI..." +go install ./cmd/kubectl-ate + +log_step "Creating atespace (demo)..." +kubectl ate create atespace demo + +log_step "Creating counter actor (my-counter-1)..." +kubectl ate create actor my-counter-1 --template ate-demo-counter/counter --atespace demo + +log_success "Counter actor my-counter-1 created" +echo "" +echo -e "${COLOR_YELLOW}========================================================================${COLOR_RESET}" +echo -e "To interact with the counter actor, open a separate terminal and run:" +echo -e " curl -X POST -H \"Host: my-counter-1.demo.actors.resources.substrate.ate.dev\" -i http://localhost:8000/" +echo -e "${COLOR_YELLOW}========================================================================${COLOR_RESET}" +echo "" +log_step "Starting port-forwarding for the network router (press Ctrl+C to stop)..." +kubectl port-forward -n ate-system svc/atenet-router 8000:80 diff --git a/internal/ateompath/ateompath.go b/internal/ateompath/ateompath.go index f72091080..0c7ec8251 100644 --- a/internal/ateompath/ateompath.go +++ b/internal/ateompath/ateompath.go @@ -184,3 +184,17 @@ func PIDFilePath(atespace, actorID, containerName string) string { containerName+".pid", ) } + +func VolumesDir(atespace, actorID string) string { + return filepath.Join( + ActorPath(atespace, actorID), + "volumes", + ) +} + +func VolumeHostPath(atespace, actorID, volumeName string) string { + return filepath.Join( + VolumesDir(atespace, actorID), + volumeName, + ) +} diff --git a/internal/e2e/suites/demo/demo_test.go b/internal/e2e/suites/demo/demo_test.go index 69d251de0..db3083ab2 100644 --- a/internal/e2e/suites/demo/demo_test.go +++ b/internal/e2e/suites/demo/demo_test.go @@ -81,164 +81,208 @@ func TestActorLifecycle(t *testing.T) { } } -// Verify that file and memory counters behavior after pause and suspend, for different snapshot scopes. -// Test case: -// 1. Create actor. -// 2. Call to actor and validate memory and file counters. -// 3. Pause & Resume actor. -// 4. Call to actor and validate memory and file counters. -// 5. Suspend & Resume actor. -// 6. Call to actor and validate memory and file counters. func TestDurableDirLifecycle(t *testing.T) { if isMicroVMEnvironment() { t.Skip("Skipping TestDurableDirLifecycle for microVM environment") } tests := []struct { - name string - onCommit v1alpha1.SnapshotScope - onPause v1alpha1.SnapshotScope - wantMemoryAfterPause int - wantFileAfterPause int - wantMemoryAfterSuspend int - wantFileAfterSuspend int + name string + tc actorLifecycleTestCase }{ { - name: "onCommit:Full, onPause:Full", - onCommit: v1alpha1.SnapshotScopeFull, - onPause: v1alpha1.SnapshotScopeFull, - wantMemoryAfterPause: 2, - wantFileAfterPause: 2, - wantMemoryAfterSuspend: 3, - wantFileAfterSuspend: 3, + name: "onCommit:Full, onPause:Full", + tc: actorLifecycleTestCase{ + onCommit: v1alpha1.SnapshotScopeFull, + onPause: v1alpha1.SnapshotScopeFull, + wantMemoryAfterPause: 2, + wantFileAfterPause: 2, + wantMemoryAfterSuspend: 3, + wantFileAfterSuspend: 3, + }, }, { - name: "onCommit:Data, onPause:Full", - onCommit: v1alpha1.SnapshotScopeData, - onPause: v1alpha1.SnapshotScopeFull, - wantMemoryAfterPause: 2, - wantFileAfterPause: 2, - wantMemoryAfterSuspend: 1, - wantFileAfterSuspend: 3, + name: "onCommit:Data, onPause:Full", + tc: actorLifecycleTestCase{ + onCommit: v1alpha1.SnapshotScopeData, + onPause: v1alpha1.SnapshotScopeFull, + wantMemoryAfterPause: 2, + wantFileAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + }, }, { - name: "onCommit:Data, onPause:Data", - onCommit: v1alpha1.SnapshotScopeData, - onPause: v1alpha1.SnapshotScopeData, - wantMemoryAfterPause: 1, - wantFileAfterPause: 2, - wantMemoryAfterSuspend: 1, - wantFileAfterSuspend: 3, + name: "onCommit:Data, onPause:Data", + tc: actorLifecycleTestCase{ + onCommit: v1alpha1.SnapshotScopeData, + onPause: v1alpha1.SnapshotScopeData, + wantMemoryAfterPause: 1, + wantFileAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + }, }, } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { t.Parallel() - // Create namespace - nsObj := e2e.CreateNamespace(t) + runActorLifecycleTestCase(t, "durabledir-lifecycle", createActorTemplate, test.tc) + }) + } +} - ctx := context.Background() - clients := e2e.GetClients() +func TestExternalVolumeLifecycle(t *testing.T) { + if isMicroVMEnvironment() { + t.Skip("Skipping TestExternalVolumeLifecycle for microVM environment") + } - // CreateActor requires the atespace to exist first. - _, _ = clients.SubstrateAPI.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{Atespace: &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: demoAtespace}}}) + tests := []struct { + name string + tc actorLifecycleTestCase + }{ + { + name: "onCommit:Data, onPause:Data", + tc: actorLifecycleTestCase{ + onCommit: v1alpha1.SnapshotScopeData, + onPause: v1alpha1.SnapshotScopeData, + wantMemoryAfterPause: 1, + wantFileAfterPause: 2, + wantMemoryAfterSuspend: 1, + wantFileAfterSuspend: 3, + }, + }, + } - // Create actor template. - at, err := createActorTemplate(ctx, t, clients, nsObj, tc.onCommit, tc.onPause) - if err != nil { - t.Fatalf("failed to initialize ActorTemplate: %v", err) - } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + runActorLifecycleTestCase(t, "extvol-lifecycle", createActorTemplateWithExternalVolume, test.tc) + }) + } +} - // - // Create an Actor. - // - actorID := "durabledir-lifecycle" + "-" + nsObj.Name - - t.Logf("Creating Actor %q using Substrate API...", actorID) - createResp, err := clients.SubstrateAPI.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Atespace: demoAtespace, Name: actorID}, - ActorTemplateNamespace: nsObj.Name, - ActorTemplateName: at.Name, - }}) - if err != nil { - t.Fatalf("failed to create Actor: %v", err) - } - t.Logf("Successfully created Actor: %s", createResp.GetMetadata().GetName()) - defer func() { - clients.SubstrateAPI.DeleteActor(ctx, &ateapipb.DeleteActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }) - }() - - // Resuming the actor - t.Logf("Resuming Actor %q...", actorID) - if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }); err != nil { - t.Fatalf("failed to resume Actor: %v", err) - } - waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) +// Verify that file and memory counters behavior after pause and suspend, for different snapshot scopes. +// Test case: +// 1. Create actor. +// 2. Call to actor and validate memory and file counters. +// 3. Pause & Resume actor. +// 4. Call to actor and validate memory and file counters. +// 5. Suspend & Resume actor. +// 6. Call to actor and validate memory and file counters. +type actorLifecycleTestCase struct { + onCommit v1alpha1.SnapshotScope + onPause v1alpha1.SnapshotScope + wantMemoryAfterPause int + wantFileAfterPause int + wantMemoryAfterSuspend int + wantFileAfterSuspend int +} - resp, err := callActor(t, demoAtespace, actorID) - if err != nil { - t.Fatalf("failed to call actor: %v", err) - } - validateCounterResponse(t, resp, "after creation", 1, 1) - - // - // Pausing the actor - // - t.Logf("Pausing Actor %q...", actorID) - if _, err := clients.SubstrateAPI.PauseActor(ctx, &ateapipb.PauseActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }); err != nil { - t.Fatalf("failed to pause Actor: %v", err) - } - waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_PAUSED) - - // Resuming the actor - t.Logf("Resuming Actor %q again...", actorID) - if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }); err != nil { - t.Fatalf("failed to resume Actor again: %v", err) - } - waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) +func runActorLifecycleTestCase(t *testing.T, prefix string, createTemplate func(context.Context, *testing.T, *e2e.Clients, *e2e.Namespace, v1alpha1.SnapshotScope, v1alpha1.SnapshotScope) (*v1alpha1.ActorTemplate, error), tc actorLifecycleTestCase) { + // Create namespace + nsObj := e2e.CreateNamespace(t) - resp, err = callActor(t, demoAtespace, actorID) - if err != nil { - t.Fatalf("failed to call actor again: %v", err) - } - validateCounterResponse(t, resp, "after pause", tc.wantMemoryAfterPause, tc.wantFileAfterPause) - - // - // Suspending the actor - // - t.Logf("Suspending Actor %q...", actorID) - if _, err := clients.SubstrateAPI.SuspendActor(ctx, &ateapipb.SuspendActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }); err != nil { - t.Fatalf("failed to suspend Actor: %v", err) - } - waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_SUSPENDED) - - // Resuming the actor - t.Logf("Resuming Actor %q again...", actorID) - if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ - Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, - }); err != nil { - t.Fatalf("failed to resume Actor again: %v", err) - } - waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) + ctx := context.Background() + clients := e2e.GetClients() - resp, err = callActor(t, demoAtespace, actorID) - if err != nil { - t.Fatalf("failed to call actor again: %v", err) - } - validateCounterResponse(t, resp, "after suspend", tc.wantMemoryAfterSuspend, tc.wantFileAfterSuspend) + // CreateActor requires the atespace to exist first. + _, _ = clients.SubstrateAPI.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{Atespace: &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: demoAtespace}}}) + + // Create actor template. + at, err := createTemplate(ctx, t, clients, nsObj, tc.onCommit, tc.onPause) + if err != nil { + t.Fatalf("failed to initialize ActorTemplate: %v", err) + } + + // + // Create an Actor. + // + actorID := prefix + "-" + nsObj.Name + + t.Logf("Creating Actor %q using Substrate API...", actorID) + createResp, err := clients.SubstrateAPI.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: demoAtespace, Name: actorID}, + ActorTemplateNamespace: nsObj.Name, + ActorTemplateName: at.Name, + }}) + if err != nil { + t.Fatalf("failed to create Actor: %v", err) + } + t.Logf("Successfully created Actor: %s", createResp.GetMetadata().GetName()) + defer func() { + clients.SubstrateAPI.DeleteActor(ctx, &ateapipb.DeleteActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, }) + }() + + // Resuming the actor + t.Logf("Resuming Actor %q...", actorID) + if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, + }); err != nil { + t.Fatalf("failed to resume Actor: %v", err) } + waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) + + resp, err := callActor(t, demoAtespace, actorID) + if err != nil { + t.Fatalf("failed to call actor: %v", err) + } + validateCounterResponse(t, resp, "after creation", 1, 1) + + // + // Pausing the actor + // + t.Logf("Pausing Actor %q...", actorID) + if _, err := clients.SubstrateAPI.PauseActor(ctx, &ateapipb.PauseActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, + }); err != nil { + t.Fatalf("failed to pause Actor: %v", err) + } + waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_PAUSED) + + // Resuming the actor + t.Logf("Resuming Actor %q again...", actorID) + if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, + }); err != nil { + t.Fatalf("failed to resume Actor again: %v", err) + } + waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) + + resp, err = callActor(t, demoAtespace, actorID) + if err != nil { + t.Fatalf("failed to call actor again: %v", err) + } + validateCounterResponse(t, resp, "after pause", tc.wantMemoryAfterPause, tc.wantFileAfterPause) + + // + // Suspending the actor + // + t.Logf("Suspending Actor %q...", actorID) + if _, err := clients.SubstrateAPI.SuspendActor(ctx, &ateapipb.SuspendActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, + }); err != nil { + t.Fatalf("failed to suspend Actor: %v", err) + } + waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_SUSPENDED) + + // Resuming the actor + t.Logf("Resuming Actor %q again...", actorID) + if _, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{ + Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: actorID}, + }); err != nil { + t.Fatalf("failed to resume Actor again: %v", err) + } + waitForActorStatus(ctx, t, clients, actorID, ateapipb.Actor_STATUS_RUNNING) + + resp, err = callActor(t, demoAtespace, actorID) + if err != nil { + t.Fatalf("failed to call actor again: %v", err) + } + validateCounterResponse(t, resp, "after suspend", tc.wantMemoryAfterSuspend, tc.wantFileAfterSuspend) } func validateCounterResponse(t *testing.T, resp string, stage string, wantMemory, wantFile int) { @@ -482,7 +526,7 @@ func suspendActor(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj return nil } -func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, onCommit, onPause v1alpha1.SnapshotScope) (*v1alpha1.ActorTemplate, error) { +func createActorTemplateInternal(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, name string, onCommit, onPause v1alpha1.SnapshotScope, modifyContainers func([]v1alpha1.Container) []v1alpha1.Container) (*v1alpha1.ActorTemplate, error) { env, err := e2e.CheckEnv("BUCKET_NAME", "KO_DOCKER_REPO") if err != nil { t.Fatalf("CheckEnv failed: %v", err) @@ -516,7 +560,7 @@ func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients // (or eligible to receive) any other namespace's actors. wp := &v1alpha1.WorkerPool{ ObjectMeta: metav1.ObjectMeta{ - Name: "counter", + Name: name, Namespace: nsObj.Name, Labels: map[string]string{"demo": nsObj.Name}, }, @@ -532,10 +576,15 @@ func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients t.Fatalf("failed to create WorkerPool: %v", err) } + containers := existingAt.Spec.Containers + if modifyContainers != nil { + containers = modifyContainers(containers) + } + // Create ActorTemplate at := &v1alpha1.ActorTemplate{ ObjectMeta: metav1.ObjectMeta{ - Name: "counter", + Name: name, Namespace: nsObj.Name, }, Spec: v1alpha1.ActorTemplateSpec{ @@ -547,9 +596,9 @@ func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients // "microvm"; the gVisor source leaves it "" — copying keeps both correct. SandboxClass: existingAt.Spec.SandboxClass, PauseImage: existingAt.Spec.PauseImage, - Containers: existingAt.Spec.Containers, + Containers: containers, SnapshotsConfig: v1alpha1.SnapshotsConfig{ - Location: "gs://" + env["BUCKET_NAME"] + "/ate-demo-counter", + Location: "gs://" + env["BUCKET_NAME"] + "/ate-demo-" + name, OnPause: onPause, OnCommit: onCommit, }, @@ -599,6 +648,25 @@ func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients return at, nil } +func createActorTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, onCommit, onPause v1alpha1.SnapshotScope) (*v1alpha1.ActorTemplate, error) { + return createActorTemplateInternal(ctx, t, clients, nsObj, "counter", onCommit, onPause, nil) +} + +func createActorTemplateWithExternalVolume(ctx context.Context, t *testing.T, clients *e2e.Clients, nsObj *e2e.Namespace, onCommit, onPause v1alpha1.SnapshotScope) (*v1alpha1.ActorTemplate, error) { + modify := func(containers []v1alpha1.Container) []v1alpha1.Container { + var res []v1alpha1.Container + for _, c := range containers { + if c.Name == "counter" { + // Use external volume for file counter instead of durabledir + c.Command = []string{"/ko-app/counter", "--file-counter-directory=/external-data"} + } + res = append(res, c) + } + return res + } + return createActorTemplateInternal(ctx, t, clients, nsObj, "counter-ext-vol", onCommit, onPause, modify) +} + func waitForActorStatus(ctx context.Context, t *testing.T, clients *e2e.Clients, actorID string, expectedStatus ateapipb.Actor_Status) { t.Helper() t.Logf("Waiting for Actor %q to be %v...", actorID, expectedStatus) diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index 3aadf65eb..509552234 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -40,6 +40,7 @@ type VolumeType int32 const ( VolumeType_VOLUME_TYPE_UNSPECIFIED VolumeType = 0 VolumeType_VOLUME_TYPE_DURABLE_DIR VolumeType = 1 + VolumeType_VOLUME_TYPE_EXTERNAL VolumeType = 2 ) // Enum value maps for VolumeType. @@ -47,10 +48,12 @@ var ( VolumeType_name = map[int32]string{ 0: "VOLUME_TYPE_UNSPECIFIED", 1: "VOLUME_TYPE_DURABLE_DIR", + 2: "VOLUME_TYPE_EXTERNAL", } VolumeType_value = map[string]int32{ "VOLUME_TYPE_UNSPECIFIED": 0, "VOLUME_TYPE_DURABLE_DIR": 1, + "VOLUME_TYPE_EXTERNAL": 2, } ) @@ -538,6 +541,58 @@ func (*DurableDirVolume) Descriptor() ([]byte, []int) { return file_atelet_proto_rawDescGZIP(), []int{5} } +type ExternalVolumeSource struct { + state protoimpl.MessageState `protogen:"open.v1"` + StorageVolumeId string `protobuf:"bytes,1,opt,name=storage_volume_id,json=storageVolumeId,proto3" json:"storage_volume_id,omitempty"` + VolumeType string `protobuf:"bytes,2,opt,name=volume_type,json=volumeType,proto3" json:"volume_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExternalVolumeSource) Reset() { + *x = ExternalVolumeSource{} + mi := &file_atelet_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExternalVolumeSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExternalVolumeSource) ProtoMessage() {} + +func (x *ExternalVolumeSource) ProtoReflect() protoreflect.Message { + mi := &file_atelet_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExternalVolumeSource.ProtoReflect.Descriptor instead. +func (*ExternalVolumeSource) Descriptor() ([]byte, []int) { + return file_atelet_proto_rawDescGZIP(), []int{6} +} + +func (x *ExternalVolumeSource) GetStorageVolumeId() string { + if x != nil { + return x.StorageVolumeId + } + return "" +} + +func (x *ExternalVolumeSource) GetVolumeType() string { + if x != nil { + return x.VolumeType + } + return "" +} + type Volume struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -545,6 +600,7 @@ type Volume struct { // Types that are valid to be assigned to Source: // // *Volume_DurableDir + // *Volume_External Source isVolume_Source `protobuf_oneof:"source"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -552,7 +608,7 @@ type Volume struct { func (x *Volume) Reset() { *x = Volume{} - mi := &file_atelet_proto_msgTypes[6] + mi := &file_atelet_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -564,7 +620,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[6] + mi := &file_atelet_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -577,7 +633,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{6} + return file_atelet_proto_rawDescGZIP(), []int{7} } func (x *Volume) GetName() string { @@ -610,6 +666,15 @@ func (x *Volume) GetDurableDir() *DurableDirVolume { return nil } +func (x *Volume) GetExternal() *ExternalVolumeSource { + if x != nil { + if x, ok := x.Source.(*Volume_External); ok { + return x.External + } + } + return nil +} + type isVolume_Source interface { isVolume_Source() } @@ -618,8 +683,14 @@ type Volume_DurableDir struct { DurableDir *DurableDirVolume `protobuf:"bytes,3,opt,name=durable_dir,json=durableDir,proto3,oneof"` } +type Volume_External struct { + External *ExternalVolumeSource `protobuf:"bytes,4,opt,name=external,proto3,oneof"` +} + func (*Volume_DurableDir) isVolume_Source() {} +func (*Volume_External) isVolume_Source() {} + type VolumeMount struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -630,7 +701,7 @@ type VolumeMount struct { func (x *VolumeMount) Reset() { *x = VolumeMount{} - mi := &file_atelet_proto_msgTypes[7] + mi := &file_atelet_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -642,7 +713,7 @@ func (x *VolumeMount) String() string { func (*VolumeMount) ProtoMessage() {} func (x *VolumeMount) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[7] + mi := &file_atelet_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -655,7 +726,7 @@ func (x *VolumeMount) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMount.ProtoReflect.Descriptor instead. func (*VolumeMount) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{7} + return file_atelet_proto_rawDescGZIP(), []int{8} } func (x *VolumeMount) GetName() string { @@ -686,7 +757,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} - mi := &file_atelet_proto_msgTypes[8] + mi := &file_atelet_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -698,7 +769,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[8] + mi := &file_atelet_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -711,7 +782,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{8} + return file_atelet_proto_rawDescGZIP(), []int{9} } func (x *Container) GetName() string { @@ -766,7 +837,7 @@ type EnvEntry struct { func (x *EnvEntry) Reset() { *x = EnvEntry{} - mi := &file_atelet_proto_msgTypes[9] + mi := &file_atelet_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -778,7 +849,7 @@ func (x *EnvEntry) String() string { func (*EnvEntry) ProtoMessage() {} func (x *EnvEntry) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[9] + mi := &file_atelet_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -791,7 +862,7 @@ func (x *EnvEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use EnvEntry.ProtoReflect.Descriptor instead. func (*EnvEntry) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{9} + return file_atelet_proto_rawDescGZIP(), []int{10} } func (x *EnvEntry) GetName() string { @@ -819,7 +890,7 @@ type Readyz struct { func (x *Readyz) Reset() { *x = Readyz{} - mi := &file_atelet_proto_msgTypes[10] + mi := &file_atelet_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -831,7 +902,7 @@ func (x *Readyz) String() string { func (*Readyz) ProtoMessage() {} func (x *Readyz) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[10] + mi := &file_atelet_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -844,7 +915,7 @@ func (x *Readyz) ProtoReflect() protoreflect.Message { // Deprecated: Use Readyz.ProtoReflect.Descriptor instead. func (*Readyz) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{10} + return file_atelet_proto_rawDescGZIP(), []int{11} } func (x *Readyz) GetHttpGet() *HTTPGetAction { @@ -867,7 +938,7 @@ type HTTPGetAction struct { func (x *HTTPGetAction) Reset() { *x = HTTPGetAction{} - mi := &file_atelet_proto_msgTypes[11] + mi := &file_atelet_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -879,7 +950,7 @@ func (x *HTTPGetAction) String() string { func (*HTTPGetAction) ProtoMessage() {} func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[11] + mi := &file_atelet_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -892,7 +963,7 @@ func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPGetAction.ProtoReflect.Descriptor instead. func (*HTTPGetAction) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{11} + return file_atelet_proto_rawDescGZIP(), []int{12} } func (x *HTTPGetAction) GetPath() string { @@ -917,7 +988,7 @@ type RunResponse struct { func (x *RunResponse) Reset() { *x = RunResponse{} - mi := &file_atelet_proto_msgTypes[12] + mi := &file_atelet_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -929,7 +1000,7 @@ func (x *RunResponse) String() string { func (*RunResponse) ProtoMessage() {} func (x *RunResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[12] + mi := &file_atelet_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -942,7 +1013,7 @@ func (x *RunResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RunResponse.ProtoReflect.Descriptor instead. func (*RunResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{12} + return file_atelet_proto_rawDescGZIP(), []int{13} } type LocalCheckpointConfiguration struct { @@ -956,7 +1027,7 @@ type LocalCheckpointConfiguration struct { func (x *LocalCheckpointConfiguration) Reset() { *x = LocalCheckpointConfiguration{} - mi := &file_atelet_proto_msgTypes[13] + mi := &file_atelet_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -968,7 +1039,7 @@ func (x *LocalCheckpointConfiguration) String() string { func (*LocalCheckpointConfiguration) ProtoMessage() {} func (x *LocalCheckpointConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[13] + mi := &file_atelet_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -981,7 +1052,7 @@ func (x *LocalCheckpointConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use LocalCheckpointConfiguration.ProtoReflect.Descriptor instead. func (*LocalCheckpointConfiguration) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{13} + return file_atelet_proto_rawDescGZIP(), []int{14} } func (x *LocalCheckpointConfiguration) GetSnapshotPrefix() string { @@ -1010,7 +1081,7 @@ type ExternalCheckpointConfiguration struct { func (x *ExternalCheckpointConfiguration) Reset() { *x = ExternalCheckpointConfiguration{} - mi := &file_atelet_proto_msgTypes[14] + mi := &file_atelet_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1022,7 +1093,7 @@ func (x *ExternalCheckpointConfiguration) String() string { func (*ExternalCheckpointConfiguration) ProtoMessage() {} func (x *ExternalCheckpointConfiguration) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[14] + mi := &file_atelet_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1035,7 +1106,7 @@ func (x *ExternalCheckpointConfiguration) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalCheckpointConfiguration.ProtoReflect.Descriptor instead. func (*ExternalCheckpointConfiguration) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{14} + return file_atelet_proto_rawDescGZIP(), []int{15} } func (x *ExternalCheckpointConfiguration) GetSnapshotUriPrefix() string { @@ -1072,7 +1143,7 @@ type CheckpointRequest struct { func (x *CheckpointRequest) Reset() { *x = CheckpointRequest{} - mi := &file_atelet_proto_msgTypes[15] + mi := &file_atelet_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1084,7 +1155,7 @@ func (x *CheckpointRequest) String() string { func (*CheckpointRequest) ProtoMessage() {} func (x *CheckpointRequest) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[15] + mi := &file_atelet_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1097,7 +1168,7 @@ func (x *CheckpointRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckpointRequest.ProtoReflect.Descriptor instead. func (*CheckpointRequest) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{15} + return file_atelet_proto_rawDescGZIP(), []int{16} } func (x *CheckpointRequest) GetTargetAteomUid() string { @@ -1205,7 +1276,7 @@ type CheckpointResponse struct { func (x *CheckpointResponse) Reset() { *x = CheckpointResponse{} - mi := &file_atelet_proto_msgTypes[16] + mi := &file_atelet_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1217,7 +1288,7 @@ func (x *CheckpointResponse) String() string { func (*CheckpointResponse) ProtoMessage() {} func (x *CheckpointResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[16] + mi := &file_atelet_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1230,7 +1301,7 @@ func (x *CheckpointResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckpointResponse.ProtoReflect.Descriptor instead. func (*CheckpointResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{16} + return file_atelet_proto_rawDescGZIP(), []int{17} } type RestoreRequest struct { @@ -1260,7 +1331,7 @@ type RestoreRequest struct { func (x *RestoreRequest) Reset() { *x = RestoreRequest{} - mi := &file_atelet_proto_msgTypes[17] + mi := &file_atelet_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1272,7 +1343,7 @@ func (x *RestoreRequest) String() string { func (*RestoreRequest) ProtoMessage() {} func (x *RestoreRequest) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[17] + mi := &file_atelet_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1285,7 +1356,7 @@ func (x *RestoreRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreRequest.ProtoReflect.Descriptor instead. func (*RestoreRequest) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{17} + return file_atelet_proto_rawDescGZIP(), []int{18} } func (x *RestoreRequest) GetTargetAteomUid() string { @@ -1393,7 +1464,7 @@ type RestoreResponse struct { func (x *RestoreResponse) Reset() { *x = RestoreResponse{} - mi := &file_atelet_proto_msgTypes[18] + mi := &file_atelet_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1405,7 +1476,7 @@ func (x *RestoreResponse) String() string { func (*RestoreResponse) ProtoMessage() {} func (x *RestoreResponse) ProtoReflect() protoreflect.Message { - mi := &file_atelet_proto_msgTypes[18] + mi := &file_atelet_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1418,7 +1489,7 @@ func (x *RestoreResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreResponse.ProtoReflect.Descriptor instead. func (*RestoreResponse) Descriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{18} + return file_atelet_proto_rawDescGZIP(), []int{19} } var File_atelet_proto protoreflect.FileDescriptor @@ -1458,12 +1529,17 @@ const file_atelet_proto_rawDesc = "" + "\vpause_image\x18\x02 \x01(\tR\n" + "pauseImage\x12(\n" + "\avolumes\x18\x03 \x03(\v2\x0e.atelet.VolumeR\avolumes\"\x12\n" + - "\x10DurableDirVolume\"\x8b\x01\n" + + "\x10DurableDirVolume\"c\n" + + "\x14ExternalVolumeSource\x12*\n" + + "\x11storage_volume_id\x18\x01 \x01(\tR\x0fstorageVolumeId\x12\x1f\n" + + "\vvolume_type\x18\x02 \x01(\tR\n" + + "volumeType\"\xc7\x01\n" + "\x06Volume\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12&\n" + "\x04type\x18\x02 \x01(\x0e2\x12.atelet.VolumeTypeR\x04type\x12;\n" + "\vdurable_dir\x18\x03 \x01(\v2\x18.atelet.DurableDirVolumeH\x00R\n" + - "durableDirB\b\n" + + "durableDir\x12:\n" + + "\bexternal\x18\x04 \x01(\v2\x1c.atelet.ExternalVolumeSourceH\x00R\bexternalB\b\n" + "\x06source\"@\n" + "\vVolumeMount\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + @@ -1516,11 +1592,12 @@ const file_atelet_proto_rawDesc = "" + "\x05scope\x18\n" + " \x01(\x0e2\x15.atelet.SnapshotScopeR\x05scopeB\b\n" + "\x06config\"\x11\n" + - "\x0fRestoreResponse*F\n" + + "\x0fRestoreResponse*`\n" + "\n" + "VolumeType\x12\x1b\n" + "\x17VOLUME_TYPE_UNSPECIFIED\x10\x00\x12\x1b\n" + - "\x17VOLUME_TYPE_DURABLE_DIR\x10\x01*j\n" + + "\x17VOLUME_TYPE_DURABLE_DIR\x10\x01\x12\x18\n" + + "\x14VOLUME_TYPE_EXTERNAL\x10\x02*j\n" + "\x0eCheckpointType\x12\x1f\n" + "\x1bCHECKPOINT_TYPE_UNSPECIFIED\x10\x00\x12\x19\n" + "\x15CHECKPOINT_TYPE_LOCAL\x10\x01\x12\x1c\n" + @@ -1548,7 +1625,7 @@ func file_atelet_proto_rawDescGZIP() []byte { } var file_atelet_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 22) var file_atelet_proto_goTypes = []any{ (VolumeType)(0), // 0: atelet.VolumeType (CheckpointType)(0), // 1: atelet.CheckpointType @@ -1559,58 +1636,60 @@ var file_atelet_proto_goTypes = []any{ (*SandboxAssets)(nil), // 6: atelet.SandboxAssets (*WorkloadSpec)(nil), // 7: atelet.WorkloadSpec (*DurableDirVolume)(nil), // 8: atelet.DurableDirVolume - (*Volume)(nil), // 9: atelet.Volume - (*VolumeMount)(nil), // 10: atelet.VolumeMount - (*Container)(nil), // 11: atelet.Container - (*EnvEntry)(nil), // 12: atelet.EnvEntry - (*Readyz)(nil), // 13: atelet.Readyz - (*HTTPGetAction)(nil), // 14: atelet.HTTPGetAction - (*RunResponse)(nil), // 15: atelet.RunResponse - (*LocalCheckpointConfiguration)(nil), // 16: atelet.LocalCheckpointConfiguration - (*ExternalCheckpointConfiguration)(nil), // 17: atelet.ExternalCheckpointConfiguration - (*CheckpointRequest)(nil), // 18: atelet.CheckpointRequest - (*CheckpointResponse)(nil), // 19: atelet.CheckpointResponse - (*RestoreRequest)(nil), // 20: atelet.RestoreRequest - (*RestoreResponse)(nil), // 21: atelet.RestoreResponse - nil, // 22: atelet.ArchAssets.FilesEntry - nil, // 23: atelet.SandboxAssets.AssetsEntry + (*ExternalVolumeSource)(nil), // 9: atelet.ExternalVolumeSource + (*Volume)(nil), // 10: atelet.Volume + (*VolumeMount)(nil), // 11: atelet.VolumeMount + (*Container)(nil), // 12: atelet.Container + (*EnvEntry)(nil), // 13: atelet.EnvEntry + (*Readyz)(nil), // 14: atelet.Readyz + (*HTTPGetAction)(nil), // 15: atelet.HTTPGetAction + (*RunResponse)(nil), // 16: atelet.RunResponse + (*LocalCheckpointConfiguration)(nil), // 17: atelet.LocalCheckpointConfiguration + (*ExternalCheckpointConfiguration)(nil), // 18: atelet.ExternalCheckpointConfiguration + (*CheckpointRequest)(nil), // 19: atelet.CheckpointRequest + (*CheckpointResponse)(nil), // 20: atelet.CheckpointResponse + (*RestoreRequest)(nil), // 21: atelet.RestoreRequest + (*RestoreResponse)(nil), // 22: atelet.RestoreResponse + nil, // 23: atelet.ArchAssets.FilesEntry + nil, // 24: atelet.SandboxAssets.AssetsEntry } var file_atelet_proto_depIdxs = []int32{ 7, // 0: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec 6, // 1: atelet.RunRequest.sandbox_assets:type_name -> atelet.SandboxAssets - 22, // 2: atelet.ArchAssets.files:type_name -> atelet.ArchAssets.FilesEntry - 23, // 3: atelet.SandboxAssets.assets:type_name -> atelet.SandboxAssets.AssetsEntry - 11, // 4: atelet.WorkloadSpec.containers:type_name -> atelet.Container - 9, // 5: atelet.WorkloadSpec.volumes:type_name -> atelet.Volume + 23, // 2: atelet.ArchAssets.files:type_name -> atelet.ArchAssets.FilesEntry + 24, // 3: atelet.SandboxAssets.assets:type_name -> atelet.SandboxAssets.AssetsEntry + 12, // 4: atelet.WorkloadSpec.containers:type_name -> atelet.Container + 10, // 5: atelet.WorkloadSpec.volumes:type_name -> atelet.Volume 0, // 6: atelet.Volume.type:type_name -> atelet.VolumeType 8, // 7: atelet.Volume.durable_dir:type_name -> atelet.DurableDirVolume - 12, // 8: atelet.Container.env:type_name -> atelet.EnvEntry - 13, // 9: atelet.Container.readyz:type_name -> atelet.Readyz - 10, // 10: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount - 14, // 11: atelet.Readyz.http_get:type_name -> atelet.HTTPGetAction - 7, // 12: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 13: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType - 16, // 14: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 17, // 15: atelet.CheckpointRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration - 2, // 16: atelet.CheckpointRequest.scope:type_name -> atelet.SnapshotScope - 7, // 17: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 18: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType - 16, // 19: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 17, // 20: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration - 2, // 21: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope - 4, // 22: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile - 5, // 23: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets - 3, // 24: atelet.AteomHerder.Run:input_type -> atelet.RunRequest - 18, // 25: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest - 20, // 26: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest - 15, // 27: atelet.AteomHerder.Run:output_type -> atelet.RunResponse - 19, // 28: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse - 21, // 29: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse - 27, // [27:30] is the sub-list for method output_type - 24, // [24:27] is the sub-list for method input_type - 24, // [24:24] is the sub-list for extension type_name - 24, // [24:24] is the sub-list for extension extendee - 0, // [0:24] is the sub-list for field type_name + 9, // 8: atelet.Volume.external:type_name -> atelet.ExternalVolumeSource + 13, // 9: atelet.Container.env:type_name -> atelet.EnvEntry + 14, // 10: atelet.Container.readyz:type_name -> atelet.Readyz + 11, // 11: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount + 15, // 12: atelet.Readyz.http_get:type_name -> atelet.HTTPGetAction + 7, // 13: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec + 1, // 14: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType + 17, // 15: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 18, // 16: atelet.CheckpointRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration + 2, // 17: atelet.CheckpointRequest.scope:type_name -> atelet.SnapshotScope + 7, // 18: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec + 1, // 19: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType + 17, // 20: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 18, // 21: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration + 2, // 22: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope + 4, // 23: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile + 5, // 24: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets + 3, // 25: atelet.AteomHerder.Run:input_type -> atelet.RunRequest + 19, // 26: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest + 21, // 27: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest + 16, // 28: atelet.AteomHerder.Run:output_type -> atelet.RunResponse + 20, // 29: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse + 22, // 30: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse + 28, // [28:31] is the sub-list for method output_type + 25, // [25:28] is the sub-list for method input_type + 25, // [25:25] is the sub-list for extension type_name + 25, // [25:25] is the sub-list for extension extendee + 0, // [0:25] is the sub-list for field type_name } func init() { file_atelet_proto_init() } @@ -1618,14 +1697,15 @@ func file_atelet_proto_init() { if File_atelet_proto != nil { return } - file_atelet_proto_msgTypes[6].OneofWrappers = []any{ + file_atelet_proto_msgTypes[7].OneofWrappers = []any{ (*Volume_DurableDir)(nil), + (*Volume_External)(nil), } - file_atelet_proto_msgTypes[15].OneofWrappers = []any{ + file_atelet_proto_msgTypes[16].OneofWrappers = []any{ (*CheckpointRequest_LocalConfig)(nil), (*CheckpointRequest_ExternalConfig)(nil), } - file_atelet_proto_msgTypes[17].OneofWrappers = []any{ + file_atelet_proto_msgTypes[18].OneofWrappers = []any{ (*RestoreRequest_LocalConfig)(nil), (*RestoreRequest_ExternalConfig)(nil), } @@ -1635,7 +1715,7 @@ func file_atelet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_atelet_proto_rawDesc), len(file_atelet_proto_rawDesc)), NumEnums: 3, - NumMessages: 21, + NumMessages: 22, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index c2fe29bb7..ff23263e9 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -83,22 +83,29 @@ message WorkloadSpec { enum VolumeType { VOLUME_TYPE_UNSPECIFIED = 0; VOLUME_TYPE_DURABLE_DIR = 1; + VOLUME_TYPE_EXTERNAL = 2; } message DurableDirVolume { } +message ExternalVolumeSource { + string storage_volume_id = 1; + string volume_type = 2; +} + message Volume { string name = 1; VolumeType type = 2; - oneof source{ + oneof source { DurableDirVolume durable_dir = 3; + ExternalVolumeSource external = 4; } } -message VolumeMount{ +message VolumeMount { string name = 1; string mount_path = 2; } @@ -108,7 +115,7 @@ message Container { string image = 2; repeated string command = 3; repeated EnvEntry env = 4; - Readyz readyz = 5; + Readyz readyz = 5; repeated VolumeMount volume_mounts = 6; } diff --git a/internal/volume/mock.go b/internal/volume/mock.go new file mode 100644 index 000000000..d9e3aed6b --- /dev/null +++ b/internal/volume/mock.go @@ -0,0 +1,181 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volume + +import ( + "context" + "fmt" + "log/slog" + "os" + "path/filepath" + "sync" + + "github.com/agent-substrate/substrate/internal/ateompath" +) + +// Use a directory that is shared between atelet and ateom but not cleaned up by atelet +var mockVolumeDirectories string = filepath.Join(ateompath.BasePath, "mockvolumes") + +// MockVolumePlugin is a simple implementation of VolumePlugin for testing purposes. +// +// It creates a subdirectory on the host for each actor. This only persists data if the actor +// is scheduled to the same host. +// +// This plugin also does not cleanup the subdirectories, so that has to be done by the test infrastructure. +type MockVolumePlugin struct { + mu sync.Mutex + volumes map[string]*MockVolumeState + counter int +} + +// MockVolumeState tracks the state of a mock volume. +type MockVolumeState struct { + ID string + Name string + Capacity string + StorageClass string + Node string + Mounts map[string]bool // targetPath -> mounted +} + +// NewMockVolumePlugin creates a new MockVolumePlugin. +func NewMockVolumePlugin() *MockVolumePlugin { + return &MockVolumePlugin{ + volumes: make(map[string]*MockVolumeState), + } +} + +// CreateVolume simulates volume provisioning. +func (p *MockVolumePlugin) CreateVolume(ctx context.Context, name string, capacity string, storageClass string) (string, error) { + p.mu.Lock() + defer p.mu.Unlock() + p.counter++ + volumeID := fmt.Sprintf("mock-vol-%d", p.counter) + slog.InfoContext(ctx, "MockVolumePlugin.CreateVolume", slog.String("name", name), slog.String("capacity", capacity), slog.String("storageClass", storageClass), slog.String("volumeID", volumeID)) + p.volumes[volumeID] = &MockVolumeState{ + ID: volumeID, + Name: name, + Capacity: capacity, + StorageClass: storageClass, + Mounts: make(map[string]bool), + } + return volumeID, nil +} + +// DeleteVolume simulates volume deletion. +func (p *MockVolumePlugin) DeleteVolume(ctx context.Context, volumeID string) error { + p.mu.Lock() + defer p.mu.Unlock() + slog.InfoContext(ctx, "MockVolumePlugin.DeleteVolume", slog.String("volumeID", volumeID)) + if _, ok := p.volumes[volumeID]; !ok { + slog.ErrorContext(ctx, "MockVolumePlugin.DeleteVolume failed: volume not found", slog.String("volumeID", volumeID)) + return fmt.Errorf("volume %s not found", volumeID) + } + delete(p.volumes, volumeID) + return nil +} + +// AttachVolume simulates volume attachment to a node. +func (p *MockVolumePlugin) AttachVolume(ctx context.Context, volumeID string, node string) error { + p.mu.Lock() + defer p.mu.Unlock() + slog.InfoContext(ctx, "MockVolumePlugin.AttachVolume", slog.String("volumeID", volumeID), slog.String("node", node)) + vol, ok := p.volumes[volumeID] + if !ok { + slog.ErrorContext(ctx, "MockVolumePlugin.AttachVolume failed: volume not found", slog.String("volumeID", volumeID)) + return fmt.Errorf("volume %s not found", volumeID) + } + vol.Node = node + return nil +} + +// DetachVolume simulates volume detachment from a node. +func (p *MockVolumePlugin) DetachVolume(ctx context.Context, volumeID string, node string) error { + p.mu.Lock() + defer p.mu.Unlock() + slog.InfoContext(ctx, "MockVolumePlugin.DetachVolume", slog.String("volumeID", volumeID), slog.String("node", node)) + vol, ok := p.volumes[volumeID] + if !ok { + slog.ErrorContext(ctx, "MockVolumePlugin.DetachVolume failed: volume not found", slog.String("volumeID", volumeID)) + return fmt.Errorf("volume %s not found", volumeID) + } + if vol.Node != node { + slog.ErrorContext(ctx, "MockVolumePlugin.DetachVolume failed: volume not attached to node", slog.String("volumeID", volumeID), slog.String("node", node), slog.String("attachedNode", vol.Node)) + return fmt.Errorf("volume %s not attached to node %s", volumeID, node) + } + vol.Node = "" + return nil +} + +// MountVolume simulates mounting volume on the host. +func (p *MockVolumePlugin) MountVolume(ctx context.Context, volumeID string, targetPath string) error { + slog.InfoContext(ctx, "MockVolumePlugin.MountVolume", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath)) + + volumeDir := filepath.Join(mockVolumeDirectories, volumeID) + if err := os.MkdirAll(volumeDir, 0755); err != nil { + slog.ErrorContext(ctx, "MockVolumePlugin.MountVolume failed: mkdir error", slog.String("volumeID", volumeID), slog.Any("error", err)) + return fmt.Errorf("failed to create mock volume directory %q: %w", volumeDir, err) + } + + testFilePath := filepath.Join(volumeDir, "test.txt") + if err := os.WriteFile(testFilePath, []byte("test content\n"), 0644); err != nil { + slog.ErrorContext(ctx, "MockVolumePlugin.MountVolume failed: create test file error", slog.String("volumeID", volumeID), slog.Any("error", err)) + return fmt.Errorf("failed to create test file in %q: %w", volumeDir, err) + } + + // Use symlink instead of bind mount to avoid atelet requiring bidirectional mount propagation. + _ = os.Remove(targetPath) + if err := os.Symlink(volumeDir, targetPath); err != nil { + return fmt.Errorf("failed to symlink %q to %q: %w", volumeDir, targetPath, err) + } + return nil +} + +// UnmountVolume simulates unmounting volume from the host. +func (p *MockVolumePlugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string) error { + slog.InfoContext(ctx, "MockVolumePlugin.UnmountVolume", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath)) + + if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) { + slog.ErrorContext(ctx, "MockVolumePlugin.UnmountVolume failed: remove error", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath), slog.Any("error", err)) + return fmt.Errorf("failed to remove target path %q: %w", targetPath, err) + } + return nil +} + +// GetVolumeState returns the state of a mock volume for verification in tests. +// This only works for controller methods. +func (p *MockVolumePlugin) GetVolumeState(volumeID string) (*MockVolumeState, error) { + p.mu.Lock() + defer p.mu.Unlock() + slog.Info("MockVolumePlugin.GetVolumeState", slog.String("volumeID", volumeID)) + vol, ok := p.volumes[volumeID] + if !ok { + slog.Error("MockVolumePlugin.GetVolumeState failed: volume not found", slog.String("volumeID", volumeID)) + return nil, fmt.Errorf("volume %s not found", volumeID) + } + // Return a copy to avoid concurrent access issues in tests + mountsCopy := make(map[string]bool) + for k, v := range vol.Mounts { + mountsCopy[k] = v + } + return &MockVolumeState{ + ID: vol.ID, + Name: vol.Name, + Capacity: vol.Capacity, + StorageClass: vol.StorageClass, + Node: vol.Node, + Mounts: mountsCopy, + }, nil +} diff --git a/internal/volume/plugin.go b/internal/volume/plugin.go new file mode 100644 index 000000000..8313664df --- /dev/null +++ b/internal/volume/plugin.go @@ -0,0 +1,29 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package volume + +import ( + "context" +) + +// VolumePlugin abstracts storage operations. +type VolumePlugin interface { + CreateVolume(ctx context.Context, name string, capacity string, storageClass string) (volumeID string, err error) + DeleteVolume(ctx context.Context, volumeID string) error + AttachVolume(ctx context.Context, volumeID string, node string) error + DetachVolume(ctx context.Context, volumeID string, node string) error + MountVolume(ctx context.Context, volumeID string, targetPath string) error + UnmountVolume(ctx context.Context, volumeID string, targetPath string) error +} diff --git a/manifests/ate-install/generated/ate.dev_actortemplates.yaml b/manifests/ate-install/generated/ate.dev_actortemplates.yaml index dfc94dca3..f7c5e240a 100644 --- a/manifests/ate-install/generated/ate.dev_actortemplates.yaml +++ b/manifests/ate-install/generated/ate.dev_actortemplates.yaml @@ -306,6 +306,28 @@ spec: durableDir represents a durable directory on rootfs that persists across resumes and participates in snapshots. type: object + externalVolumeTemplate: + description: |- + externalVolumeTemplate represents an external volume dynamically provisioned + for each actor. The volume only lives as long as the actor and is deleted + when the actor is deleted. + properties: + capacity: + anyOf: + - type: integer + - type: string + description: capacity specifies the size of the volume to + create. + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + storageClassName: + description: storageClassName refers to the StorageClass + to create the volume from. + type: string + required: + - capacity + - storageClassName + type: object name: description: name of the volume. maxLength: 63 @@ -317,8 +339,10 @@ spec: - name type: object x-kubernetes-validations: - - message: exactly one of the fields in [durableDir] must be set - rule: '[has(self.durableDir)].filter(x,x==true).size() == 1' + - message: exactly one of the fields in [durableDir externalVolumeTemplate] + must be set + rule: '[has(self.durableDir),has(self.externalVolumeTemplate)].filter(x,x==true).size() + == 1' maxItems: 32 type: array workerSelector: diff --git a/pkg/api/v1alpha1/actortemplate_types.go b/pkg/api/v1alpha1/actortemplate_types.go index 192a5932f..3c0184601 100644 --- a/pkg/api/v1alpha1/actortemplate_types.go +++ b/pkg/api/v1alpha1/actortemplate_types.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -39,12 +40,27 @@ type DurableDirVolumeSource struct { // // When adding a new source type, list it in the ExactlyOneOf marker below. // -// +kubebuilder:validation:ExactlyOneOf={durableDir} +// +kubebuilder:validation:ExactlyOneOf={durableDir,externalVolumeTemplate} type VolumeSource struct { // durableDir represents a durable directory on rootfs that persists across // resumes and participates in snapshots. // +optional DurableDir *DurableDirVolumeSource `json:"durableDir,omitempty" protobuf:"bytes,2,opt,name=durableDir"` + + // externalVolumeTemplate represents an external volume dynamically provisioned + // for each actor. The volume only lives as long as the actor and is deleted + // when the actor is deleted. + // +optional + ExternalVolumeTemplate *ExternalVolumeTemplate `json:"externalVolumeTemplate,omitempty" protobuf:"bytes,3,opt,name=externalVolumeTemplate"` +} + +type ExternalVolumeTemplate struct { + // capacity specifies the size of the volume to create. + // +required + Capacity resource.Quantity `json:"capacity" protobuf:"bytes,1,opt,name=capacity"` + // storageClassName refers to the StorageClass to create the volume from. + // +required + StorageClassName string `json:"storageClassName" protobuf:"bytes,2,opt,name=storageClassName"` } type Volume struct { diff --git a/pkg/api/v1alpha1/actortemplate_validation_test.go b/pkg/api/v1alpha1/actortemplate_validation_test.go index 86557ef9d..e233ed8f4 100644 --- a/pkg/api/v1alpha1/actortemplate_validation_test.go +++ b/pkg/api/v1alpha1/actortemplate_validation_test.go @@ -23,6 +23,7 @@ import ( "github.com/agent-substrate/substrate/internal/envtestbins" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -612,6 +613,96 @@ func TestActorTemplateValidation(t *testing.T) { } }, wantErr: false, + }, { + name: "Volumes: 1 ExternalVolumeTemplate mount is valid", + mutate: func(at *ActorTemplate) { + at.Spec.Volumes = []Volume{ + { + Name: "vol1", + VolumeSource: VolumeSource{ + ExternalVolumeTemplate: &ExternalVolumeTemplate{ + Capacity: resource.MustParse("10Gi"), + StorageClassName: "standard", + }, + }, + }, + } + at.Spec.Containers[0].VolumeMounts = []VolumeMount{ + {Name: "vol1", MountPath: "/mnt/data"}, + } + }, + wantErr: false, + }, { + name: "Volumes: multiple ExternalVolumeTemplate mounts are valid", + mutate: func(at *ActorTemplate) { + at.Spec.Volumes = []Volume{ + { + Name: "vol1", + VolumeSource: VolumeSource{ + ExternalVolumeTemplate: &ExternalVolumeTemplate{ + Capacity: resource.MustParse("10Gi"), + StorageClassName: "standard", + }, + }, + }, + { + Name: "vol2", + VolumeSource: VolumeSource{ + ExternalVolumeTemplate: &ExternalVolumeTemplate{ + Capacity: resource.MustParse("20Gi"), + StorageClassName: "pd-ssd", + }, + }, + }, + } + at.Spec.Containers[0].VolumeMounts = []VolumeMount{ + {Name: "vol1", MountPath: "/mnt/vol1"}, + {Name: "vol2", MountPath: "/mnt/vol2"}, + } + }, + wantErr: false, + }, { + name: "Volumes: 1 DurableDir and 1 ExternalVolumeTemplate on same ActorTemplate is valid", + mutate: func(at *ActorTemplate) { + at.Spec.Volumes = []Volume{ + {Name: "home", VolumeSource: VolumeSource{DurableDir: &DurableDirVolumeSource{}}}, + { + Name: "ext", + VolumeSource: VolumeSource{ + ExternalVolumeTemplate: &ExternalVolumeTemplate{ + Capacity: resource.MustParse("10Gi"), + StorageClassName: "standard", + }, + }, + }, + } + at.Spec.Containers[0].VolumeMounts = []VolumeMount{ + {Name: "home", MountPath: "/home"}, + {Name: "ext", MountPath: "/mnt/ext"}, + } + }, + wantErr: false, + }, { + name: "Volumes: VolumeSource with both DurableDir and ExternalVolumeTemplate set is invalid", + mutate: func(at *ActorTemplate) { + at.Spec.Volumes = []Volume{ + { + Name: "vol1", + VolumeSource: VolumeSource{ + DurableDir: &DurableDirVolumeSource{}, + ExternalVolumeTemplate: &ExternalVolumeTemplate{ + Capacity: resource.MustParse("10Gi"), + StorageClassName: "standard", + }, + }, + }, + } + at.Spec.Containers[0].VolumeMounts = []VolumeMount{ + {Name: "vol1", MountPath: "/mnt/data"}, + } + }, + wantErr: true, + errMsg: "exactly one of the fields in [durableDir externalVolumeTemplate] must be set", }, { name: "Volumes: VolumeSource with no source set is invalid", mutate: func(at *ActorTemplate) { @@ -620,7 +711,7 @@ func TestActorTemplateValidation(t *testing.T) { } }, wantErr: true, - errMsg: "exactly one of the fields in [durableDir] must be set", + errMsg: "exactly one of the fields in [durableDir externalVolumeTemplate] must be set", }, { name: "Volumes: VolumeSource with no source set is invalid (mixed with a valid DurableDir volume)", mutate: func(at *ActorTemplate) { @@ -634,7 +725,7 @@ func TestActorTemplateValidation(t *testing.T) { } }, wantErr: true, - errMsg: "exactly one of the fields in [durableDir] must be set", + errMsg: "exactly one of the fields in [durableDir externalVolumeTemplate] must be set", }, { name: "Volumes: DurableDir MountPath with nested absolute path is valid", mutate: func(at *ActorTemplate) { diff --git a/pkg/api/v1alpha1/zz_generated.deepcopy.go b/pkg/api/v1alpha1/zz_generated.deepcopy.go index e977908e9..e45f96b0c 100644 --- a/pkg/api/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -273,6 +273,22 @@ func (in *EnvVarSource) DeepCopy() *EnvVarSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExternalVolumeTemplate) DeepCopyInto(out *ExternalVolumeTemplate) { + *out = *in + out.Capacity = in.Capacity.DeepCopy() +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalVolumeTemplate. +func (in *ExternalVolumeTemplate) DeepCopy() *ExternalVolumeTemplate { + if in == nil { + return nil + } + out := new(ExternalVolumeTemplate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPGetAction) DeepCopyInto(out *HTTPGetAction) { *out = *in @@ -453,6 +469,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { *out = new(DurableDirVolumeSource) **out = **in } + if in.ExternalVolumeTemplate != nil { + in, out := &in.ExternalVolumeTemplate, &out.ExternalVolumeTemplate + *out = new(ExternalVolumeTemplate) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSource. diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 0350f9fd9..b17ae2b94 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -38,6 +38,55 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ExternalVolume_Status int32 + +const ( + ExternalVolume_PROVISIONING ExternalVolume_Status = 0 + ExternalVolume_CREATED ExternalVolume_Status = 1 + ExternalVolume_DELETING ExternalVolume_Status = 2 +) + +// Enum value maps for ExternalVolume_Status. +var ( + ExternalVolume_Status_name = map[int32]string{ + 0: "PROVISIONING", + 1: "CREATED", + 2: "DELETING", + } + ExternalVolume_Status_value = map[string]int32{ + "PROVISIONING": 0, + "CREATED": 1, + "DELETING": 2, + } +) + +func (x ExternalVolume_Status) Enum() *ExternalVolume_Status { + p := new(ExternalVolume_Status) + *p = x + return p +} + +func (x ExternalVolume_Status) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ExternalVolume_Status) Descriptor() protoreflect.EnumDescriptor { + return file_ateapi_proto_enumTypes[0].Descriptor() +} + +func (ExternalVolume_Status) Type() protoreflect.EnumType { + return &file_ateapi_proto_enumTypes[0] +} + +func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ExternalVolume_Status.Descriptor instead. +func (ExternalVolume_Status) EnumDescriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{5, 0} +} + type Actor_Status int32 const ( @@ -86,11 +135,11 @@ func (x Actor_Status) String() string { } func (Actor_Status) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[0].Descriptor() + return file_ateapi_proto_enumTypes[1].Descriptor() } func (Actor_Status) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[0] + return &file_ateapi_proto_enumTypes[1] } func (x Actor_Status) Number() protoreflect.EnumNumber { @@ -99,7 +148,7 @@ func (x Actor_Status) Number() protoreflect.EnumNumber { // Deprecated: Use Actor_Status.Descriptor instead. func (Actor_Status) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5, 0} + return file_ateapi_proto_rawDescGZIP(), []int{6, 0} } type ExternalSnapshotInfo struct { @@ -421,6 +470,77 @@ func (x *ResourceMetadata) GetUpdateTime() *timestamppb.Timestamp { return nil } +type ExternalVolume struct { + state protoimpl.MessageState `protogen:"open.v1"` + // actor_id + the volume name specified in the actor template. + ActorVolumeId string `protobuf:"bytes,1,opt,name=actor_volume_id,json=actorVolumeId,proto3" json:"actor_volume_id,omitempty"` + // The volume_id returned from the storage system. + StorageVolumeId string `protobuf:"bytes,2,opt,name=storage_volume_id,json=storageVolumeId,proto3" json:"storage_volume_id,omitempty"` + // Internal volume plugin name or CSI driver name. + VolumeType string `protobuf:"bytes,3,opt,name=volume_type,json=volumeType,proto3" json:"volume_type,omitempty"` + Status ExternalVolume_Status `protobuf:"varint,4,opt,name=status,proto3,enum=ateapi.ExternalVolume_Status" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExternalVolume) Reset() { + *x = ExternalVolume{} + mi := &file_ateapi_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExternalVolume) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExternalVolume) ProtoMessage() {} + +func (x *ExternalVolume) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExternalVolume.ProtoReflect.Descriptor instead. +func (*ExternalVolume) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{5} +} + +func (x *ExternalVolume) GetActorVolumeId() string { + if x != nil { + return x.ActorVolumeId + } + return "" +} + +func (x *ExternalVolume) GetStorageVolumeId() string { + if x != nil { + return x.StorageVolumeId + } + return "" +} + +func (x *ExternalVolume) GetVolumeType() string { + if x != nil { + return x.VolumeType + } + return "" +} + +func (x *ExternalVolume) GetStatus() ExternalVolume_Status { + if x != nil { + return x.Status + } + return ExternalVolume_PROVISIONING +} + type Actor struct { state protoimpl.MessageState `protogen:"open.v1"` // Common resource metadata: atespace, name, uid, version, timestamps. @@ -446,13 +566,16 @@ type Actor struct { // suspend/pause since eligibility is no longer a single fixed pool // reference on the ActorTemplate. WorkerPoolName string `protobuf:"bytes,12,opt,name=worker_pool_name,json=workerPoolName,proto3" json:"worker_pool_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Volumes attached to the actor. These volumes only live as long as the actor. + // They are deleted when the actor is deleted. + ActorVolumes []*ExternalVolume `protobuf:"bytes,16,rep,name=actor_volumes,json=actorVolumes,proto3" json:"actor_volumes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Actor) Reset() { *x = Actor{} - mi := &file_ateapi_proto_msgTypes[5] + mi := &file_ateapi_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -464,7 +587,7 @@ func (x *Actor) String() string { func (*Actor) ProtoMessage() {} func (x *Actor) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[5] + mi := &file_ateapi_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -477,7 +600,7 @@ func (x *Actor) ProtoReflect() protoreflect.Message { // Deprecated: Use Actor.ProtoReflect.Descriptor instead. func (*Actor) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5} + return file_ateapi_proto_rawDescGZIP(), []int{6} } func (x *Actor) GetMetadata() *ResourceMetadata { @@ -564,6 +687,13 @@ func (x *Actor) GetWorkerPoolName() string { return "" } +func (x *Actor) GetActorVolumes() []*ExternalVolume { + if x != nil { + return x.ActorVolumes + } + return nil +} + // Atespace is the isolation boundary an Actor is created into. Global-scoped: // metadata.atespace is always empty; the atespace's identity is metadata.name. type Atespace struct { @@ -576,7 +706,7 @@ type Atespace struct { func (x *Atespace) Reset() { *x = Atespace{} - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -588,7 +718,7 @@ func (x *Atespace) String() string { func (*Atespace) ProtoMessage() {} func (x *Atespace) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -601,7 +731,7 @@ func (x *Atespace) ProtoReflect() protoreflect.Message { // Deprecated: Use Atespace.ProtoReflect.Descriptor instead. func (*Atespace) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6} + return file_ateapi_proto_rawDescGZIP(), []int{7} } func (x *Atespace) GetMetadata() *ResourceMetadata { @@ -625,7 +755,7 @@ type ObjectRef struct { func (x *ObjectRef) Reset() { *x = ObjectRef{} - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -637,7 +767,7 @@ func (x *ObjectRef) String() string { func (*ObjectRef) ProtoMessage() {} func (x *ObjectRef) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -650,7 +780,7 @@ func (x *ObjectRef) ProtoReflect() protoreflect.Message { // Deprecated: Use ObjectRef.ProtoReflect.Descriptor instead. func (*ObjectRef) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{8} } func (x *ObjectRef) GetAtespace() string { @@ -677,7 +807,7 @@ type CreateAtespaceRequest struct { func (x *CreateAtespaceRequest) Reset() { *x = CreateAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -689,7 +819,7 @@ func (x *CreateAtespaceRequest) String() string { func (*CreateAtespaceRequest) ProtoMessage() {} func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -702,7 +832,7 @@ func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAtespaceRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{8} + return file_ateapi_proto_rawDescGZIP(), []int{9} } func (x *CreateAtespaceRequest) GetAtespace() *Atespace { @@ -721,7 +851,7 @@ type GetAtespaceRequest struct { func (x *GetAtespaceRequest) Reset() { *x = GetAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -733,7 +863,7 @@ func (x *GetAtespaceRequest) String() string { func (*GetAtespaceRequest) ProtoMessage() {} func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -746,7 +876,7 @@ func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{9} + return file_ateapi_proto_rawDescGZIP(), []int{10} } func (x *GetAtespaceRequest) GetAtespace() *ObjectRef { @@ -764,7 +894,7 @@ type ListAtespacesRequest struct { func (x *ListAtespacesRequest) Reset() { *x = ListAtespacesRequest{} - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -776,7 +906,7 @@ func (x *ListAtespacesRequest) String() string { func (*ListAtespacesRequest) ProtoMessage() {} func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -789,7 +919,7 @@ func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesRequest.ProtoReflect.Descriptor instead. func (*ListAtespacesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{10} + return file_ateapi_proto_rawDescGZIP(), []int{11} } type ListAtespacesResponse struct { @@ -801,7 +931,7 @@ type ListAtespacesResponse struct { func (x *ListAtespacesResponse) Reset() { *x = ListAtespacesResponse{} - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -813,7 +943,7 @@ func (x *ListAtespacesResponse) String() string { func (*ListAtespacesResponse) ProtoMessage() {} func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -826,7 +956,7 @@ func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesResponse.ProtoReflect.Descriptor instead. func (*ListAtespacesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{11} + return file_ateapi_proto_rawDescGZIP(), []int{12} } func (x *ListAtespacesResponse) GetAtespaces() []*Atespace { @@ -845,7 +975,7 @@ type DeleteAtespaceRequest struct { func (x *DeleteAtespaceRequest) Reset() { *x = DeleteAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -857,7 +987,7 @@ func (x *DeleteAtespaceRequest) String() string { func (*DeleteAtespaceRequest) ProtoMessage() {} func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -870,7 +1000,7 @@ func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAtespaceRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{12} + return file_ateapi_proto_rawDescGZIP(), []int{13} } func (x *DeleteAtespaceRequest) GetAtespace() *ObjectRef { @@ -889,7 +1019,7 @@ type GetActorRequest struct { func (x *GetActorRequest) Reset() { *x = GetActorRequest{} - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -901,7 +1031,7 @@ func (x *GetActorRequest) String() string { func (*GetActorRequest) ProtoMessage() {} func (x *GetActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -914,7 +1044,7 @@ func (x *GetActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorRequest.ProtoReflect.Descriptor instead. func (*GetActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{13} + return file_ateapi_proto_rawDescGZIP(), []int{14} } func (x *GetActorRequest) GetActor() *ObjectRef { @@ -935,7 +1065,7 @@ type CreateActorRequest struct { func (x *CreateActorRequest) Reset() { *x = CreateActorRequest{} - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -947,7 +1077,7 @@ func (x *CreateActorRequest) String() string { func (*CreateActorRequest) ProtoMessage() {} func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -960,7 +1090,7 @@ func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorRequest.ProtoReflect.Descriptor instead. func (*CreateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{14} + return file_ateapi_proto_rawDescGZIP(), []int{15} } func (x *CreateActorRequest) GetActor() *Actor { @@ -985,7 +1115,7 @@ type UpdateActorRequest struct { func (x *UpdateActorRequest) Reset() { *x = UpdateActorRequest{} - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -997,7 +1127,7 @@ func (x *UpdateActorRequest) String() string { func (*UpdateActorRequest) ProtoMessage() {} func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1010,7 +1140,7 @@ func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorRequest.ProtoReflect.Descriptor instead. func (*UpdateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{15} + return file_ateapi_proto_rawDescGZIP(), []int{16} } func (x *UpdateActorRequest) GetActor() *ObjectRef { @@ -1036,7 +1166,7 @@ type UpdateActorResponse struct { func (x *UpdateActorResponse) Reset() { *x = UpdateActorResponse{} - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1048,7 +1178,7 @@ func (x *UpdateActorResponse) String() string { func (*UpdateActorResponse) ProtoMessage() {} func (x *UpdateActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1061,7 +1191,7 @@ func (x *UpdateActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorResponse.ProtoReflect.Descriptor instead. func (*UpdateActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{16} + return file_ateapi_proto_rawDescGZIP(), []int{17} } func (x *UpdateActorResponse) GetActor() *Actor { @@ -1080,7 +1210,7 @@ type SuspendActorRequest struct { func (x *SuspendActorRequest) Reset() { *x = SuspendActorRequest{} - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1092,7 +1222,7 @@ func (x *SuspendActorRequest) String() string { func (*SuspendActorRequest) ProtoMessage() {} func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1105,7 +1235,7 @@ func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorRequest.ProtoReflect.Descriptor instead. func (*SuspendActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{17} + return file_ateapi_proto_rawDescGZIP(), []int{18} } func (x *SuspendActorRequest) GetActor() *ObjectRef { @@ -1124,7 +1254,7 @@ type SuspendActorResponse struct { func (x *SuspendActorResponse) Reset() { *x = SuspendActorResponse{} - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1136,7 +1266,7 @@ func (x *SuspendActorResponse) String() string { func (*SuspendActorResponse) ProtoMessage() {} func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1149,7 +1279,7 @@ func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorResponse.ProtoReflect.Descriptor instead. func (*SuspendActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{18} + return file_ateapi_proto_rawDescGZIP(), []int{19} } func (x *SuspendActorResponse) GetActor() *Actor { @@ -1168,7 +1298,7 @@ type PauseActorRequest struct { func (x *PauseActorRequest) Reset() { *x = PauseActorRequest{} - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1180,7 +1310,7 @@ func (x *PauseActorRequest) String() string { func (*PauseActorRequest) ProtoMessage() {} func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1193,7 +1323,7 @@ func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorRequest.ProtoReflect.Descriptor instead. func (*PauseActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{19} + return file_ateapi_proto_rawDescGZIP(), []int{20} } func (x *PauseActorRequest) GetActor() *ObjectRef { @@ -1212,7 +1342,7 @@ type PauseActorResponse struct { func (x *PauseActorResponse) Reset() { *x = PauseActorResponse{} - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1224,7 +1354,7 @@ func (x *PauseActorResponse) String() string { func (*PauseActorResponse) ProtoMessage() {} func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1237,7 +1367,7 @@ func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorResponse.ProtoReflect.Descriptor instead. func (*PauseActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{20} + return file_ateapi_proto_rawDescGZIP(), []int{21} } func (x *PauseActorResponse) GetActor() *Actor { @@ -1258,7 +1388,7 @@ type ResumeActorRequest struct { func (x *ResumeActorRequest) Reset() { *x = ResumeActorRequest{} - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1270,7 +1400,7 @@ func (x *ResumeActorRequest) String() string { func (*ResumeActorRequest) ProtoMessage() {} func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1283,7 +1413,7 @@ func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorRequest.ProtoReflect.Descriptor instead. func (*ResumeActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{21} + return file_ateapi_proto_rawDescGZIP(), []int{22} } func (x *ResumeActorRequest) GetActor() *ObjectRef { @@ -1309,7 +1439,7 @@ type ResumeActorResponse struct { func (x *ResumeActorResponse) Reset() { *x = ResumeActorResponse{} - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1321,7 +1451,7 @@ func (x *ResumeActorResponse) String() string { func (*ResumeActorResponse) ProtoMessage() {} func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1334,7 +1464,7 @@ func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorResponse.ProtoReflect.Descriptor instead. func (*ResumeActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{22} + return file_ateapi_proto_rawDescGZIP(), []int{23} } func (x *ResumeActorResponse) GetActor() *Actor { @@ -1353,7 +1483,7 @@ type DeleteActorRequest struct { func (x *DeleteActorRequest) Reset() { *x = DeleteActorRequest{} - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1365,7 +1495,7 @@ func (x *DeleteActorRequest) String() string { func (*DeleteActorRequest) ProtoMessage() {} func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1378,7 +1508,7 @@ func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorRequest.ProtoReflect.Descriptor instead. func (*DeleteActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{23} + return file_ateapi_proto_rawDescGZIP(), []int{24} } func (x *DeleteActorRequest) GetActor() *ObjectRef { @@ -1396,7 +1526,7 @@ type ListWorkersRequest struct { func (x *ListWorkersRequest) Reset() { *x = ListWorkersRequest{} - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1408,7 +1538,7 @@ func (x *ListWorkersRequest) String() string { func (*ListWorkersRequest) ProtoMessage() {} func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1421,7 +1551,7 @@ func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersRequest.ProtoReflect.Descriptor instead. func (*ListWorkersRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{24} + return file_ateapi_proto_rawDescGZIP(), []int{25} } type ListWorkersResponse struct { @@ -1433,7 +1563,7 @@ type ListWorkersResponse struct { func (x *ListWorkersResponse) Reset() { *x = ListWorkersResponse{} - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1445,7 +1575,7 @@ func (x *ListWorkersResponse) String() string { func (*ListWorkersResponse) ProtoMessage() {} func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1458,7 +1588,7 @@ func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersResponse.ProtoReflect.Descriptor instead. func (*ListWorkersResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{25} + return file_ateapi_proto_rawDescGZIP(), []int{26} } func (x *ListWorkersResponse) GetWorkers() []*Worker { @@ -1487,7 +1617,7 @@ type ListActorsRequest struct { func (x *ListActorsRequest) Reset() { *x = ListActorsRequest{} - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1499,7 +1629,7 @@ func (x *ListActorsRequest) String() string { func (*ListActorsRequest) ProtoMessage() {} func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1512,7 +1642,7 @@ func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsRequest.ProtoReflect.Descriptor instead. func (*ListActorsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{26} + return file_ateapi_proto_rawDescGZIP(), []int{27} } func (x *ListActorsRequest) GetPageSize() int32 { @@ -1550,7 +1680,7 @@ type ListActorsResponse struct { func (x *ListActorsResponse) Reset() { *x = ListActorsResponse{} - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1562,7 +1692,7 @@ func (x *ListActorsResponse) String() string { func (*ListActorsResponse) ProtoMessage() {} func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1575,7 +1705,7 @@ func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsResponse.ProtoReflect.Descriptor instead. func (*ListActorsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{27} + return file_ateapi_proto_rawDescGZIP(), []int{28} } func (x *ListActorsResponse) GetActors() []*Actor { @@ -1610,7 +1740,7 @@ type Worker struct { func (x *Worker) Reset() { *x = Worker{} - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1622,7 +1752,7 @@ func (x *Worker) String() string { func (*Worker) ProtoMessage() {} func (x *Worker) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1635,7 +1765,7 @@ func (x *Worker) ProtoReflect() protoreflect.Message { // Deprecated: Use Worker.ProtoReflect.Descriptor instead. func (*Worker) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{28} + return file_ateapi_proto_rawDescGZIP(), []int{29} } func (x *Worker) GetWorkerNamespace() string { @@ -1718,7 +1848,7 @@ type Assignment struct { func (x *Assignment) Reset() { *x = Assignment{} - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1730,7 +1860,7 @@ func (x *Assignment) String() string { func (*Assignment) ProtoMessage() {} func (x *Assignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1743,7 +1873,7 @@ func (x *Assignment) ProtoReflect() protoreflect.Message { // Deprecated: Use Assignment.ProtoReflect.Descriptor instead. func (*Assignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{29} + return file_ateapi_proto_rawDescGZIP(), []int{30} } func (x *Assignment) GetActorTemplate() *KubeNamespacedObjectRef { @@ -1770,7 +1900,7 @@ type KubeNamespacedObjectRef struct { func (x *KubeNamespacedObjectRef) Reset() { *x = KubeNamespacedObjectRef{} - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1782,7 +1912,7 @@ func (x *KubeNamespacedObjectRef) String() string { func (*KubeNamespacedObjectRef) ProtoMessage() {} func (x *KubeNamespacedObjectRef) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1795,7 +1925,7 @@ func (x *KubeNamespacedObjectRef) ProtoReflect() protoreflect.Message { // Deprecated: Use KubeNamespacedObjectRef.ProtoReflect.Descriptor instead. func (*KubeNamespacedObjectRef) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{30} + return file_ateapi_proto_rawDescGZIP(), []int{31} } func (x *KubeNamespacedObjectRef) GetNamespace() string { @@ -1820,7 +1950,7 @@ type DebugClearRequest struct { func (x *DebugClearRequest) Reset() { *x = DebugClearRequest{} - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1832,7 +1962,7 @@ func (x *DebugClearRequest) String() string { func (*DebugClearRequest) ProtoMessage() {} func (x *DebugClearRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1845,7 +1975,7 @@ func (x *DebugClearRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugClearRequest.ProtoReflect.Descriptor instead. func (*DebugClearRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{31} + return file_ateapi_proto_rawDescGZIP(), []int{32} } type DebugClearResponse struct { @@ -1856,7 +1986,7 @@ type DebugClearResponse struct { func (x *DebugClearResponse) Reset() { *x = DebugClearResponse{} - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1868,7 +1998,7 @@ func (x *DebugClearResponse) String() string { func (*DebugClearResponse) ProtoMessage() {} func (x *DebugClearResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1881,7 +2011,7 @@ func (x *DebugClearResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DebugClearResponse.ProtoReflect.Descriptor instead. func (*DebugClearResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{32} + return file_ateapi_proto_rawDescGZIP(), []int{33} } type MintJWTRequest struct { @@ -1896,7 +2026,7 @@ type MintJWTRequest struct { func (x *MintJWTRequest) Reset() { *x = MintJWTRequest{} - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1908,7 +2038,7 @@ func (x *MintJWTRequest) String() string { func (*MintJWTRequest) ProtoMessage() {} func (x *MintJWTRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1921,7 +2051,7 @@ func (x *MintJWTRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintJWTRequest.ProtoReflect.Descriptor instead. func (*MintJWTRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{33} + return file_ateapi_proto_rawDescGZIP(), []int{34} } func (x *MintJWTRequest) GetAudience() []string { @@ -1981,7 +2111,7 @@ type MintJWTResponse struct { func (x *MintJWTResponse) Reset() { *x = MintJWTResponse{} - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1993,7 +2123,7 @@ func (x *MintJWTResponse) String() string { func (*MintJWTResponse) ProtoMessage() {} func (x *MintJWTResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2006,7 +2136,7 @@ func (x *MintJWTResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintJWTResponse.ProtoReflect.Descriptor instead. func (*MintJWTResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{34} + return file_ateapi_proto_rawDescGZIP(), []int{35} } func (x *MintJWTResponse) GetSessionJwt() string { @@ -2031,7 +2161,7 @@ type MintCertRequest struct { func (x *MintCertRequest) Reset() { *x = MintCertRequest{} - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2043,7 +2173,7 @@ func (x *MintCertRequest) String() string { func (*MintCertRequest) ProtoMessage() {} func (x *MintCertRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2056,7 +2186,7 @@ func (x *MintCertRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintCertRequest.ProtoReflect.Descriptor instead. func (*MintCertRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{35} + return file_ateapi_proto_rawDescGZIP(), []int{36} } func (x *MintCertRequest) GetAppId() string { @@ -2099,7 +2229,7 @@ type MintCertResponse struct { func (x *MintCertResponse) Reset() { *x = MintCertResponse{} - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2111,7 +2241,7 @@ func (x *MintCertResponse) String() string { func (*MintCertResponse) ProtoMessage() {} func (x *MintCertResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2124,7 +2254,7 @@ func (x *MintCertResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintCertResponse.ProtoReflect.Descriptor instead. func (*MintCertResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{36} + return file_ateapi_proto_rawDescGZIP(), []int{37} } func (x *MintCertResponse) GetSessionCertificates() [][]byte { @@ -2161,7 +2291,17 @@ const file_ateapi_proto_rawDesc = "" + "\vcreate_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + "createTime\x12;\n" + "\vupdate_time\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "updateTime\"\x84\x06\n" + + "updateTime\"\xf3\x01\n" + + "\x0eExternalVolume\x12&\n" + + "\x0factor_volume_id\x18\x01 \x01(\tR\ractorVolumeId\x12*\n" + + "\x11storage_volume_id\x18\x02 \x01(\tR\x0fstorageVolumeId\x12\x1f\n" + + "\vvolume_type\x18\x03 \x01(\tR\n" + + "volumeType\x125\n" + + "\x06status\x18\x04 \x01(\x0e2\x1d.ateapi.ExternalVolume.StatusR\x06status\"5\n" + + "\x06Status\x12\x10\n" + + "\fPROVISIONING\x10\x00\x12\v\n" + + "\aCREATED\x10\x01\x12\f\n" + + "\bDELETING\x10\x02\"\xc1\x06\n" + "\x05Actor\x124\n" + "\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x128\n" + "\x18actor_template_namespace\x18\x02 \x01(\tR\x16actorTemplateNamespace\x12.\n" + @@ -2176,7 +2316,8 @@ const file_ateapi_proto_rawDesc = "" + "\x14latest_snapshot_info\x18\n" + " \x01(\v2\x14.ateapi.SnapshotInfoR\x12latestSnapshotInfo\x129\n" + "\x0fworker_selector\x18\v \x01(\v2\x10.ateapi.SelectorR\x0eworkerSelector\x12(\n" + - "\x10worker_pool_name\x18\f \x01(\tR\x0eworkerPoolName\"\xb1\x01\n" + + "\x10worker_pool_name\x18\f \x01(\tR\x0eworkerPoolName\x12;\n" + + "\ractor_volumes\x18\x10 \x03(\v2\x16.ateapi.ExternalVolumeR\factorVolumes\"\xb1\x01\n" + "\x06Status\x12\x16\n" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x13\n" + "\x0fSTATUS_RESUMING\x10\x01\x12\x12\n" + @@ -2314,121 +2455,125 @@ func file_ateapi_proto_rawDescGZIP() []byte { return file_ateapi_proto_rawDescData } -var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 39) +var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 40) var file_ateapi_proto_goTypes = []any{ - (Actor_Status)(0), // 0: ateapi.Actor.Status - (*ExternalSnapshotInfo)(nil), // 1: ateapi.ExternalSnapshotInfo - (*LocalSnapshotInfo)(nil), // 2: ateapi.LocalSnapshotInfo - (*SnapshotInfo)(nil), // 3: ateapi.SnapshotInfo - (*Selector)(nil), // 4: ateapi.Selector - (*ResourceMetadata)(nil), // 5: ateapi.ResourceMetadata - (*Actor)(nil), // 6: ateapi.Actor - (*Atespace)(nil), // 7: ateapi.Atespace - (*ObjectRef)(nil), // 8: ateapi.ObjectRef - (*CreateAtespaceRequest)(nil), // 9: ateapi.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 10: ateapi.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 11: ateapi.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 12: ateapi.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 13: ateapi.DeleteAtespaceRequest - (*GetActorRequest)(nil), // 14: ateapi.GetActorRequest - (*CreateActorRequest)(nil), // 15: ateapi.CreateActorRequest - (*UpdateActorRequest)(nil), // 16: ateapi.UpdateActorRequest - (*UpdateActorResponse)(nil), // 17: ateapi.UpdateActorResponse - (*SuspendActorRequest)(nil), // 18: ateapi.SuspendActorRequest - (*SuspendActorResponse)(nil), // 19: ateapi.SuspendActorResponse - (*PauseActorRequest)(nil), // 20: ateapi.PauseActorRequest - (*PauseActorResponse)(nil), // 21: ateapi.PauseActorResponse - (*ResumeActorRequest)(nil), // 22: ateapi.ResumeActorRequest - (*ResumeActorResponse)(nil), // 23: ateapi.ResumeActorResponse - (*DeleteActorRequest)(nil), // 24: ateapi.DeleteActorRequest - (*ListWorkersRequest)(nil), // 25: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 26: ateapi.ListWorkersResponse - (*ListActorsRequest)(nil), // 27: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 28: ateapi.ListActorsResponse - (*Worker)(nil), // 29: ateapi.Worker - (*Assignment)(nil), // 30: ateapi.Assignment - (*KubeNamespacedObjectRef)(nil), // 31: ateapi.KubeNamespacedObjectRef - (*DebugClearRequest)(nil), // 32: ateapi.DebugClearRequest - (*DebugClearResponse)(nil), // 33: ateapi.DebugClearResponse - (*MintJWTRequest)(nil), // 34: ateapi.MintJWTRequest - (*MintJWTResponse)(nil), // 35: ateapi.MintJWTResponse - (*MintCertRequest)(nil), // 36: ateapi.MintCertRequest - (*MintCertResponse)(nil), // 37: ateapi.MintCertResponse - nil, // 38: ateapi.Selector.MatchLabelsEntry - nil, // 39: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 40: google.protobuf.Timestamp + (ExternalVolume_Status)(0), // 0: ateapi.ExternalVolume.Status + (Actor_Status)(0), // 1: ateapi.Actor.Status + (*ExternalSnapshotInfo)(nil), // 2: ateapi.ExternalSnapshotInfo + (*LocalSnapshotInfo)(nil), // 3: ateapi.LocalSnapshotInfo + (*SnapshotInfo)(nil), // 4: ateapi.SnapshotInfo + (*Selector)(nil), // 5: ateapi.Selector + (*ResourceMetadata)(nil), // 6: ateapi.ResourceMetadata + (*ExternalVolume)(nil), // 7: ateapi.ExternalVolume + (*Actor)(nil), // 8: ateapi.Actor + (*Atespace)(nil), // 9: ateapi.Atespace + (*ObjectRef)(nil), // 10: ateapi.ObjectRef + (*CreateAtespaceRequest)(nil), // 11: ateapi.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 12: ateapi.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 13: ateapi.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 14: ateapi.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 15: ateapi.DeleteAtespaceRequest + (*GetActorRequest)(nil), // 16: ateapi.GetActorRequest + (*CreateActorRequest)(nil), // 17: ateapi.CreateActorRequest + (*UpdateActorRequest)(nil), // 18: ateapi.UpdateActorRequest + (*UpdateActorResponse)(nil), // 19: ateapi.UpdateActorResponse + (*SuspendActorRequest)(nil), // 20: ateapi.SuspendActorRequest + (*SuspendActorResponse)(nil), // 21: ateapi.SuspendActorResponse + (*PauseActorRequest)(nil), // 22: ateapi.PauseActorRequest + (*PauseActorResponse)(nil), // 23: ateapi.PauseActorResponse + (*ResumeActorRequest)(nil), // 24: ateapi.ResumeActorRequest + (*ResumeActorResponse)(nil), // 25: ateapi.ResumeActorResponse + (*DeleteActorRequest)(nil), // 26: ateapi.DeleteActorRequest + (*ListWorkersRequest)(nil), // 27: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 28: ateapi.ListWorkersResponse + (*ListActorsRequest)(nil), // 29: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 30: ateapi.ListActorsResponse + (*Worker)(nil), // 31: ateapi.Worker + (*Assignment)(nil), // 32: ateapi.Assignment + (*KubeNamespacedObjectRef)(nil), // 33: ateapi.KubeNamespacedObjectRef + (*DebugClearRequest)(nil), // 34: ateapi.DebugClearRequest + (*DebugClearResponse)(nil), // 35: ateapi.DebugClearResponse + (*MintJWTRequest)(nil), // 36: ateapi.MintJWTRequest + (*MintJWTResponse)(nil), // 37: ateapi.MintJWTResponse + (*MintCertRequest)(nil), // 38: ateapi.MintCertRequest + (*MintCertResponse)(nil), // 39: ateapi.MintCertResponse + nil, // 40: ateapi.Selector.MatchLabelsEntry + nil, // 41: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp } var file_ateapi_proto_depIdxs = []int32{ - 1, // 0: ateapi.SnapshotInfo.external:type_name -> ateapi.ExternalSnapshotInfo - 2, // 1: ateapi.SnapshotInfo.local:type_name -> ateapi.LocalSnapshotInfo - 38, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 40, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 40, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 5, // 5: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata - 0, // 6: ateapi.Actor.status:type_name -> ateapi.Actor.Status - 3, // 7: ateapi.Actor.latest_snapshot_info:type_name -> ateapi.SnapshotInfo - 4, // 8: ateapi.Actor.worker_selector:type_name -> ateapi.Selector - 5, // 9: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 7, // 10: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 8, // 11: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 7, // 12: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 8, // 13: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 8, // 14: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 6, // 15: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 8, // 16: ateapi.UpdateActorRequest.actor:type_name -> ateapi.ObjectRef - 4, // 17: ateapi.UpdateActorRequest.worker_selector:type_name -> ateapi.Selector - 6, // 18: ateapi.UpdateActorResponse.actor:type_name -> ateapi.Actor - 8, // 19: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 6, // 20: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 8, // 21: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 6, // 22: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 8, // 23: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 6, // 24: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 8, // 25: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 29, // 26: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 6, // 27: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 30, // 28: ateapi.Worker.assignment:type_name -> ateapi.Assignment - 39, // 29: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 31, // 30: ateapi.Assignment.actor_template:type_name -> ateapi.KubeNamespacedObjectRef - 8, // 31: ateapi.Assignment.actor:type_name -> ateapi.ObjectRef - 14, // 32: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 15, // 33: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 16, // 34: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 18, // 35: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 20, // 36: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 22, // 37: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 24, // 38: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 25, // 39: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 27, // 40: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 9, // 41: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 10, // 42: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 11, // 43: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 13, // 44: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 32, // 45: ateapi.Control.DebugClear:input_type -> ateapi.DebugClearRequest - 34, // 46: ateapi.SessionIdentity.MintJWT:input_type -> ateapi.MintJWTRequest - 36, // 47: ateapi.SessionIdentity.MintCert:input_type -> ateapi.MintCertRequest - 6, // 48: ateapi.Control.GetActor:output_type -> ateapi.Actor - 6, // 49: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 17, // 50: ateapi.Control.UpdateActor:output_type -> ateapi.UpdateActorResponse - 19, // 51: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 21, // 52: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 23, // 53: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 6, // 54: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 26, // 55: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 28, // 56: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 7, // 57: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 7, // 58: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 12, // 59: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 7, // 60: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 33, // 61: ateapi.Control.DebugClear:output_type -> ateapi.DebugClearResponse - 35, // 62: ateapi.SessionIdentity.MintJWT:output_type -> ateapi.MintJWTResponse - 37, // 63: ateapi.SessionIdentity.MintCert:output_type -> ateapi.MintCertResponse - 48, // [48:64] is the sub-list for method output_type - 32, // [32:48] is the sub-list for method input_type - 32, // [32:32] is the sub-list for extension type_name - 32, // [32:32] is the sub-list for extension extendee - 0, // [0:32] is the sub-list for field type_name + 2, // 0: ateapi.SnapshotInfo.external:type_name -> ateapi.ExternalSnapshotInfo + 3, // 1: ateapi.SnapshotInfo.local:type_name -> ateapi.LocalSnapshotInfo + 40, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 42, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 42, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 0, // 5: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status + 6, // 6: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata + 1, // 7: ateapi.Actor.status:type_name -> ateapi.Actor.Status + 4, // 8: ateapi.Actor.latest_snapshot_info:type_name -> ateapi.SnapshotInfo + 5, // 9: ateapi.Actor.worker_selector:type_name -> ateapi.Selector + 7, // 10: ateapi.Actor.actor_volumes:type_name -> ateapi.ExternalVolume + 6, // 11: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 9, // 12: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 10, // 13: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 9, // 14: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 10, // 15: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 10, // 16: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 8, // 17: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 10, // 18: ateapi.UpdateActorRequest.actor:type_name -> ateapi.ObjectRef + 5, // 19: ateapi.UpdateActorRequest.worker_selector:type_name -> ateapi.Selector + 8, // 20: ateapi.UpdateActorResponse.actor:type_name -> ateapi.Actor + 10, // 21: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 8, // 22: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 10, // 23: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 8, // 24: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 10, // 25: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 8, // 26: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 10, // 27: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 31, // 28: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 8, // 29: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 32, // 30: ateapi.Worker.assignment:type_name -> ateapi.Assignment + 41, // 31: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 33, // 32: ateapi.Assignment.actor_template:type_name -> ateapi.KubeNamespacedObjectRef + 10, // 33: ateapi.Assignment.actor:type_name -> ateapi.ObjectRef + 16, // 34: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 17, // 35: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 18, // 36: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 20, // 37: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 22, // 38: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 24, // 39: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 26, // 40: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 27, // 41: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 29, // 42: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 11, // 43: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 12, // 44: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 13, // 45: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 15, // 46: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 34, // 47: ateapi.Control.DebugClear:input_type -> ateapi.DebugClearRequest + 36, // 48: ateapi.SessionIdentity.MintJWT:input_type -> ateapi.MintJWTRequest + 38, // 49: ateapi.SessionIdentity.MintCert:input_type -> ateapi.MintCertRequest + 8, // 50: ateapi.Control.GetActor:output_type -> ateapi.Actor + 8, // 51: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 19, // 52: ateapi.Control.UpdateActor:output_type -> ateapi.UpdateActorResponse + 21, // 53: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 23, // 54: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 25, // 55: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 8, // 56: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 28, // 57: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 30, // 58: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 9, // 59: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 9, // 60: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 14, // 61: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 9, // 62: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 35, // 63: ateapi.Control.DebugClear:output_type -> ateapi.DebugClearResponse + 37, // 64: ateapi.SessionIdentity.MintJWT:output_type -> ateapi.MintJWTResponse + 39, // 65: ateapi.SessionIdentity.MintCert:output_type -> ateapi.MintCertResponse + 50, // [50:66] is the sub-list for method output_type + 34, // [34:50] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -2445,8 +2590,8 @@ func file_ateapi_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateapi_proto_rawDesc), len(file_ateapi_proto_rawDesc)), - NumEnums: 1, - NumMessages: 39, + NumEnums: 2, + NumMessages: 40, NumExtensions: 0, NumServices: 2, }, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 7a8af2280..02d055cb0 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -118,6 +118,24 @@ message ResourceMetadata { google.protobuf.Timestamp update_time = 6; } +message ExternalVolume { + // actor_id + the volume name specified in the actor template. + string actor_volume_id = 1; + + // The volume_id returned from the storage system. + string storage_volume_id = 2; + + // Internal volume plugin name or CSI driver name. + string volume_type = 3; + + enum Status { + PROVISIONING = 0; + CREATED = 1; + DELETING = 2; + } + Status status = 4; +} + message Actor { // Common resource metadata: atespace, name, uid, version, timestamps. ResourceMetadata metadata = 1; @@ -158,6 +176,10 @@ message Actor { // suspend/pause since eligibility is no longer a single fixed pool // reference on the ActorTemplate. string worker_pool_name = 12; + + // Volumes attached to the actor. These volumes only live as long as the actor. + // They are deleted when the actor is deleted. + repeated ExternalVolume actor_volumes = 16; } // Atespace is the isolation boundary an Actor is created into. Global-scoped: