diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index 07ce4cd75a..f3437131f1 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -1492,8 +1492,8 @@ func (f *partialFailVolumePlugin) CreateVolume(ctx context.Context, name, capaci return "storage-" + name, parameters, nil } -func (f *partialFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string) error { - return nil +func (f *partialFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, mode ateapipb.VolumeAccessMode) (map[string]string, error) { + return nil, nil } func (f *partialFailVolumePlugin) DetachVolume(ctx context.Context, volumeID, node string) error { @@ -1633,8 +1633,8 @@ func (r *retrySuccessVolumePlugin) CreateVolume(ctx context.Context, name, capac return "storage-" + name, parameters, nil } -func (r *retrySuccessVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string) error { - return nil +func (r *retrySuccessVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, mode ateapipb.VolumeAccessMode) (map[string]string, error) { + return nil, nil } func (r *retrySuccessVolumePlugin) DetachVolume(ctx context.Context, volumeID, node string) error { diff --git a/cmd/ateapi/internal/controlapi/volumes.go b/cmd/ateapi/internal/controlapi/volumes.go index 14680245b1..9f7e59e550 100644 --- a/cmd/ateapi/internal/controlapi/volumes.go +++ b/cmd/ateapi/internal/controlapi/volumes.go @@ -21,6 +21,7 @@ import ( "log/slog" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" + "github.com/agent-substrate/substrate/internal/volume" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -49,6 +50,7 @@ func initialActorVolumes(ctx context.Context, scLister storagev1listers.StorageC VolumeName: vol.GetName(), VolumeType: sc.Provisioner, Status: ateapipb.ExternalVolume_STATUS_PENDING, + AccessMode: vol.GetExternalVolumeTemplate().GetAccessMode(), }) } } @@ -109,7 +111,7 @@ func createActorVolumes(ctx context.Context, registry VolumePluginRegistry, scLi return resultVolumes, status.Errorf(codes.FailedPrecondition, "volume %q has mismatched type %q (expected %q from StorageClass %q)", volName, vol.GetVolumeType(), sc.Provisioner, scName) } - plugin, err := registry.GetPlugin(ctx, vol.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, registry.GetPlugin, vol.GetVolumeType()) if err != nil { return resultVolumes, status.Errorf(codes.FailedPrecondition, "failed to get volume plugin for driver %q (StorageClass %q): %v", sc.Provisioner, scName, err) } @@ -125,6 +127,7 @@ func createActorVolumes(ctx context.Context, registry VolumePluginRegistry, scLi VolumeType: sc.Provisioner, Status: ateapipb.ExternalVolume_STATUS_CREATED, VolumeContext: volCtx, + AccessMode: specVol.GetExternalVolumeTemplate().GetAccessMode(), }) } return resultVolumes, nil @@ -144,11 +147,9 @@ func deleteActorVolumes(ctx context.Context, registry VolumePluginRegistry, acto // to the original requested volID. volID = actorVolumeID(actorUID, vol.GetVolumeName()) } - // TODO: Standardize volume plugin lookup and error handling across control plane - // and worker plane (e.g. via a shared helper). - plugin, err := registry.GetPlugin(ctx, vol.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, registry.GetPlugin, vol.GetVolumeType()) if err != nil { - errs = append(errs, fmt.Errorf("failed to get volume plugin for %q: %w", vol.GetVolumeType(), err)) + errs = append(errs, err) continue } if err := plugin.DeleteVolume(ctx, volID); err != nil { @@ -235,17 +236,22 @@ func detachActorVolumes(ctx context.Context, st detachActorVolumesStore, registr continue } slog.InfoContext(ctx, "Detaching volume from node", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) - plugin, err := registry.GetPlugin(ctx, vol.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, registry.GetPlugin, vol.GetVolumeType()) if err != nil { - errs = append(errs, fmt.Errorf("failed to get volume plugin for %q: %w", vol.GetVolumeType(), err)) + errs = append(errs, err) continue } if err := plugin.DetachVolume(ctx, vol.GetStorageVolumeId(), node); err != nil { if status.Code(err) == codes.NotFound { slog.WarnContext(ctx, "Volume not found during detach, assuming already detached", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) + vol.PublishContext = nil + vol.PublishContextNode = "" continue } errs = append(errs, fmt.Errorf("failed to detach volume %q from node %q: %w", vol.GetStorageVolumeId(), node, err)) + } else { + vol.PublishContext = nil + vol.PublishContextNode = "" } } return errors.Join(errs...) diff --git a/cmd/ateapi/internal/controlapi/volumes_test.go b/cmd/ateapi/internal/controlapi/volumes_test.go index 7be9b02783..8a71dab30c 100644 --- a/cmd/ateapi/internal/controlapi/volumes_test.go +++ b/cmd/ateapi/internal/controlapi/volumes_test.go @@ -56,6 +56,7 @@ func TestInitialActorVolumes_PendingState(t *testing.T) { Name: "data-vol-1", ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{ StorageClassName: "standard", + AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, }, }, { @@ -79,6 +80,7 @@ func TestInitialActorVolumes_PendingState(t *testing.T) { VolumeName: "data-vol-1", VolumeType: "mock-standard", Status: ateapipb.ExternalVolume_STATUS_PENDING, + AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, }, { VolumeName: "data-vol-2", diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 7bb47fb4c7..75e225b312 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -26,6 +26,7 @@ import ( "github.com/agent-substrate/substrate/internal/ateattr" "github.com/agent-substrate/substrate/internal/proto/ateletpb" "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/internal/volume" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -121,9 +122,11 @@ func (w *ActorWorkflow) ResumeActor(ctx context.Context, actorRef resources.Acto return nil, false, err } actor = assigned - if err = w.ensureVolumesAttached(leaseCtx, actor, worker, actorTemplate); err != nil { + var attached *ateapipb.Actor + if attached, err = w.ensureVolumesAttached(leaseCtx, actorRef, actor, worker, actorTemplate); err != nil { return nil, false, err } + actor = attached if tele, err = w.ensureAteletRestored(leaseCtx, actorRef, actor, actorTemplate, src); err != nil { return nil, false, err } @@ -622,27 +625,62 @@ func schedulingConstraints(actor *ateapipb.Actor, tmpl *ateapipb.ActorTemplate) // assigned worker's node. Attachment is idempotent, so a re-entered workflow // safely runs it again. // TODO replace re-execution with a proper check on the volumes' attach state. -func (w *ActorWorkflow) ensureVolumesAttached(ctx context.Context, actor *ateapipb.Actor, worker *ateapipb.Worker, actorTemplate *ateapipb.ActorTemplate) (err error) { +func (w *ActorWorkflow) ensureVolumesAttached(ctx context.Context, actorRef resources.ActorRef, actor *ateapipb.Actor, worker *ateapipb.Worker, actorTemplate *ateapipb.ActorTemplate) (_ *ateapipb.Actor, err error) { ctx, done := stepSpan(ctx, "AttachVolumes") defer func() { err = done(err) }() node := worker.GetNodeName() if node == "" { - return fmt.Errorf("assigned worker has no node name") + return nil, fmt.Errorf("assigned worker has no node name") } ref := &ateapipb.ObjectRef{Atespace: actor.GetMetadata().GetAtespace(), Name: actor.GetMetadata().GetName()} - for _, vol := range getMountedActorVolumes(ctx, ref, actor.GetStatus().GetActorVolumes(), actorTemplate) { + mountedVols := getMountedActorVolumes(ctx, ref, actor.GetStatus().GetActorVolumes(), actorTemplate) + if len(mountedVols) == 0 { + return actor, nil + } + + updated := false + for _, vol := range mountedVols { slog.InfoContext(ctx, "Attaching volume to node", slog.String("volume_id", vol.GetStorageVolumeId()), slog.String("node", node)) - plugin, err := w.pluginRegistry.GetPlugin(ctx, vol.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, w.pluginRegistry.GetPlugin, vol.GetVolumeType()) if err != nil { - return fmt.Errorf("failed to get volume plugin for %q: %w", vol.GetVolumeType(), err) + return nil, err } - if err := plugin.AttachVolume(ctx, vol.GetStorageVolumeId(), node); err != nil { - return fmt.Errorf("failed to attach volume %q to node %q: %w", vol.GetStorageVolumeId(), node, err) + pubCtx, err := plugin.AttachVolume(ctx, vol.GetStorageVolumeId(), node, vol.GetAccessMode()) + if err != nil { + return nil, fmt.Errorf("failed to attach volume %q to node %q: %w", vol.GetStorageVolumeId(), node, err) + } + if vol.GetPublishContextNode() != node || len(pubCtx) > 0 { + vol.PublishContext = pubCtx + vol.PublishContextNode = node + updated = true } } - return nil + + if updated { + updatePrecondition := store.PreconditionFrom(actor) + storedActor, updateErr := w.store.UpdateActor(ctx, actorRef, updatePrecondition, func(toUpdate *ateapipb.Actor) error { + for _, mVol := range mountedVols { + for _, toVol := range toUpdate.GetStatus().GetActorVolumes() { + if toVol.GetVolumeName() == mVol.GetVolumeName() { + toVol.PublishContext = mVol.GetPublishContext() + toVol.PublishContextNode = mVol.GetPublishContextNode() + } + } + } + return nil + }) + if updateErr != nil { + if errors.Is(updateErr, store.ErrVersionConflict) { + return nil, status.Error(codes.Aborted, "concurrent update conflict, please retry") + } + return nil, fmt.Errorf("while updating actor after volume attachment: %w", updateErr) + } + return storedActor, nil + } + + return actor, nil } // ensureAteletRestored brings the workload up on the assigned worker: diff --git a/cmd/ateapi/internal/controlapi/workload_spec.go b/cmd/ateapi/internal/controlapi/workload_spec.go index f5048ece26..006428bbee 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec.go +++ b/cmd/ateapi/internal/controlapi/workload_spec.go @@ -173,17 +173,22 @@ func appendExternalVolumes(workloadSpec *ateletpb.WorkloadSpec, template *ateapi var storageVolID string var volType string var volCtx map[string]string + var pubCtx map[string]string + var accessMode ateapipb.VolumeAccessMode for _, dbVol := range actor.GetStatus().GetActorVolumes() { if dbVol.GetVolumeName() == vol.GetName() { storageVolID = dbVol.GetStorageVolumeId() volType = dbVol.GetVolumeType() volCtx = dbVol.GetVolumeContext() + pubCtx = dbVol.GetPublishContext() + accessMode = dbVol.GetAccessMode() break } } if storageVolID == "" { return fmt.Errorf("volume %s not found for actor %s", vol.GetName(), actor.GetMetadata().GetName()) } + isReadOnly := accessMode == ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY workloadSpec.Volumes = append(workloadSpec.Volumes, &ateletpb.Volume{ Name: vol.GetName(), Source: &ateletpb.Volume_External{ @@ -191,6 +196,9 @@ func appendExternalVolumes(workloadSpec *ateletpb.WorkloadSpec, template *ateapi StorageVolumeId: storageVolID, VolumeType: volType, VolumeContext: volCtx, + PublishContext: pubCtx, + AccessMode: accessMode, + Readonly: isReadOnly, }, }, }) diff --git a/cmd/ateapi/internal/controlapi/workload_spec_test.go b/cmd/ateapi/internal/controlapi/workload_spec_test.go index 8b5384b1a8..d4697ac5b0 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec_test.go +++ b/cmd/ateapi/internal/controlapi/workload_spec_test.go @@ -427,6 +427,8 @@ func TestAppendExternalVolumes(t *testing.T) { StorageVolumeId: "vol-gce-pd-123", VolumeType: "pd-standard", VolumeContext: map[string]string{"foo": "bar"}, + PublishContext: map[string]string{"devicePath": "/dev/sda"}, + AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, }, }, }, @@ -446,6 +448,9 @@ func TestAppendExternalVolumes(t *testing.T) { StorageVolumeId: "vol-gce-pd-123", VolumeType: "pd-standard", VolumeContext: map[string]string{"foo": "bar"}, + PublishContext: map[string]string{"devicePath": "/dev/sda"}, + AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, + Readonly: true, }, }, }, diff --git a/cmd/ateapi/internal/controlapi/zz_generated.validation.go b/cmd/ateapi/internal/controlapi/zz_generated.validation.go index dc7abf7379..3b0524aef9 100644 --- a/cmd/ateapi/internal/controlapi/zz_generated.validation.go +++ b/cmd/ateapi/internal/controlapi/zz_generated.validation.go @@ -3189,6 +3189,112 @@ func Validate_ExternalVolume( errs = append(errs, fn(fldPath.Child("volume_context"), obj.VolumeContext, oldVal, oldObj != nil)...) } + { // field ateapipb.ExternalVolume.PublishContext + fn := func( + fldPath *field.Path, + obj, oldObj map[string]string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if ateDeepEqual(obj, oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.MaxProperties(ctx, op, fldPath, obj, oldObj, 32).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.EachMapKey(ctx, op, fldPath, obj, oldObj, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 128) + }); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.EachMapVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, + func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList { + return validate.MaxLength(ctx, op, fldPath, obj, oldObj, 1024) + }); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExternalVolume) map[string]string { + return oldObj.PublishContext + }) + errs = append(errs, fn(fldPath.Child("publish_context"), obj.PublishContext, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExternalVolume.PublishContextNode + fn := func( + fldPath *field.Path, + obj, oldObj *string, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 253); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExternalVolume) *string { + return &oldObj.PublishContextNode + }) + errs = append(errs, fn(fldPath.Child("publish_context_node"), &obj.PublishContextNode, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExternalVolume.AccessMode + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.VolumeAccessMode, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExternalVolume) *ateapipb.VolumeAccessMode { + return &oldObj.AccessMode + }) + errs = append(errs, fn(fldPath.Child("access_mode"), &obj.AccessMode, oldVal, oldObj != nil)...) + } + return errs } @@ -3263,6 +3369,37 @@ func Validate_ExternalVolumeTemplate( errs = append(errs, fn(fldPath.Child("storage_class_name"), &obj.StorageClassName, oldVal, oldObj != nil)...) } + { // field ateapipb.ExternalVolumeTemplate.AccessMode + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.VolumeAccessMode, + oldValueCorrelated bool) (errs field.ErrorList) { + // don't revalidate unchanged data + if oldValueCorrelated && op.Type == operation.Update { + if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) { + return nil + } + } + // call field-attached validations + earlyReturn := false + if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.Maximum(ctx, op, fldPath, obj, oldObj, 3); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExternalVolumeTemplate) *ateapipb.VolumeAccessMode { + return &oldObj.AccessMode + }) + errs = append(errs, fn(fldPath.Child("access_mode"), &obj.AccessMode, oldVal, oldObj != nil)...) + } + return errs } diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index 6155e49aa0..dd4191c7f8 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -53,10 +53,10 @@ import ( "github.com/agent-substrate/substrate/internal/substratex509" "github.com/agent-substrate/substrate/internal/version" "github.com/agent-substrate/substrate/internal/volume" + "github.com/agent-substrate/substrate/internal/volume/csi" atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/client/clientset/versioned" "github.com/agent-substrate/substrate/pkg/client/informers/externalversions" - listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1" "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/s3" @@ -266,10 +266,7 @@ func main() { } } - // TODO: Revisit scalability implications of using a shared informer. This lister - // is unlikely to be used with frequency. - ateFactory := externalversions.NewSharedInformerFactory(ateClient, 0) - csiDriverConfigLister := ateFactory.Api().V1alpha1().CSIDriverConfigs().Lister() + csiDriverConfigGetter := &directCSIDriverConfigGetter{client: ateClient} clusterTrustBundleInformerFactory := informers.NewSharedInformerFactoryWithOptions(k8sClient, 24*time.Hour, informers.WithTweakListOptions(func(o *metav1.ListOptions) { @@ -280,9 +277,7 @@ func main() { stopCh := make(chan struct{}) defer close(stopCh) - ateFactory.Start(stopCh) clusterTrustBundleInformerFactory.Start(stopCh) - ateFactory.WaitForCacheSync(stopCh) clusterTrustBundleInformerFactory.WaitForCacheSync(stopCh) wmService := NewService( @@ -293,7 +288,7 @@ func main() { imageCache, instruments, volPlugins, - csiDriverConfigLister, + csiDriverConfigGetter, systemInfoVolumes, ) go systemInfoVolumes.run(ctx) @@ -301,20 +296,11 @@ func main() { // Pre-download sandbox assets as SandboxConfigs appear/change so the first // Run/Restore on this node hits the cache. Best-effort: on failure the // on-demand fetch in ensureSandboxAssets still covers correctness. - // - // The informer is requested only now, after the factory's blocking - // WaitForCacheSync above, so it cannot hold up atelet startup when its - // list/watch fails (e.g. Forbidden while the ClusterRole rollout lags the - // binary): the reflector retries in the background and prewarm stays cold - // until it recovers. + ateFactory := externalversions.NewSharedInformerFactory(ateClient, 0) sandboxConfigInformer := ateFactory.Api().V1alpha1().SandboxConfigs().Informer() if err := startSandboxAssetPrewarm(ctx, sandboxConfigInformer, wmService, imageCache, microvmNodeCapable(hostDevRoot)); err != nil { slog.ErrorContext(ctx, "Sandbox asset prewarm disabled", slog.Any("err", err)) } - // The factory only runs informers that exist when Start is called: the - // Start above predates the SandboxConfigs informer, so without this call - // it would never list or watch. Start is idempotent per informer — this - // launches the new one and leaves the already-running ones untouched. ateFactory.Start(stopCh) dialOpts, err := ateapiauth.DialOptions(ateapiauth.ClientConfig{ K8sClient: k8sClient, @@ -427,6 +413,15 @@ func drainOnShutdown(ctx context.Context, srv *grpc.Server, readiness *serverboo return done } +// directCSIDriverConfigGetter retrieves CSIDriverConfig via direct API call rather than a cluster-wide watch informer. +type directCSIDriverConfigGetter struct { + client versioned.Interface +} + +func (g *directCSIDriverConfigGetter) Get(name string) (*atev1alpha1.CSIDriverConfig, error) { + return g.client.ApiV1alpha1().CSIDriverConfigs().Get(context.Background(), name, metav1.GetOptions{}) +} + // AteomHerder is a service that allows controlling workloads on individual // ateoms. type AteomHerder struct { @@ -439,7 +434,7 @@ type AteomHerder struct { instruments *Instruments mu sync.RWMutex volumePlugins map[string]volume.VolumePluginWorkerPlane - csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister + csiDriverConfigGetter csi.CSIDriverConfigGetter systemInfoVolumes *systemInfoVolumeRefresher } @@ -454,7 +449,7 @@ func NewService( imageCache *imagecache.Store, instruments *Instruments, volumePlugins map[string]volume.VolumePluginWorkerPlane, - csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister, + csiDriverConfigGetter csi.CSIDriverConfigGetter, systemInfoVolumes *systemInfoVolumeRefresher, ) *AteomHerder { wms := &AteomHerder{ @@ -464,7 +459,7 @@ func NewService( gcsClient: gcsClient, instruments: instruments, volumePlugins: volumePlugins, - csiDriverConfigLister: csiDriverConfigLister, + csiDriverConfigGetter: csiDriverConfigGetter, systemInfoVolumes: systemInfoVolumes, } return wms diff --git a/cmd/atelet/volumes.go b/cmd/atelet/volumes.go index 2a1e54cefb..36b76fc2e6 100644 --- a/cmd/atelet/volumes.go +++ b/cmd/atelet/volumes.go @@ -40,11 +40,11 @@ func (s *AteomHerder) mountExternalVolumes(ctx context.Context, actorUID string, 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), slog.String("volume_type", ext.GetVolumeType())) - plugin, err := s.getPlugin(ctx, ext.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, s.getPlugin, ext.GetVolumeType()) if err != nil { - return fmt.Errorf("failed to get volume plugin for %q: %w", ext.GetVolumeType(), err) + return err } - if err := plugin.MountVolume(ctx, ext.GetStorageVolumeId(), hostPath, ext.GetVolumeContext()); err != nil { + if err := plugin.MountVolume(ctx, ext.GetStorageVolumeId(), hostPath, ext.GetVolumeContext(), ext.GetPublishContext(), ext.GetAccessMode(), ext.GetReadonly()); err != nil { return fmt.Errorf("failed to mount volume %q to %q: %w", ext.GetStorageVolumeId(), hostPath, err) } } @@ -60,11 +60,9 @@ func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, actorUID strin } hostPath := ateompath.VolumeHostPath(actorUID, vol.GetName()) slog.InfoContext(ctx, "Unmounting volume", slog.String("volume_id", ext.GetStorageVolumeId()), slog.String("host_path", hostPath), slog.String("volume_type", ext.GetVolumeType())) - // TODO: Standardize volume plugin lookup and error handling across control plane - // and worker plane (e.g. via a shared helper). - plugin, err := s.getPlugin(ctx, ext.GetVolumeType()) + plugin, err := volume.LookupPlugin(ctx, s.getPlugin, ext.GetVolumeType()) if err != nil { - errs = append(errs, fmt.Errorf("failed to get volume plugin for %q (volume %q): %w", ext.GetVolumeType(), ext.GetStorageVolumeId(), err)) + errs = append(errs, fmt.Errorf("%w (volume %q)", err, ext.GetStorageVolumeId())) continue } if err := plugin.UnmountVolume(ctx, ext.GetStorageVolumeId(), hostPath); err != nil { @@ -86,7 +84,7 @@ func (s *AteomHerder) getPlugin(ctx context.Context, driverName string) (volume. return plugin, nil } - csiPlugin, err := csi.NewCSIPlugin(ctx, s.csiDriverConfigLister, driverName, false /*isController*/) + csiPlugin, err := csi.NewCSIPlugin(ctx, s.csiDriverConfigGetter, driverName, false /*isController*/) if err != nil { return nil, err } diff --git a/cmd/atelet/volumes_test.go b/cmd/atelet/volumes_test.go index 588d1f637d..3e1ba54658 100644 --- a/cmd/atelet/volumes_test.go +++ b/cmd/atelet/volumes_test.go @@ -22,6 +22,10 @@ import ( "github.com/agent-substrate/substrate/internal/proto/ateletpb" "github.com/agent-substrate/substrate/internal/volume" + "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/client/clientset/versioned/fake" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type fakeWorkerPlugin struct { @@ -30,7 +34,7 @@ type fakeWorkerPlugin struct { unmounted []string } -func (f *fakeWorkerPlugin) MountVolume(ctx context.Context, volumeID string, targetPath string, attributes map[string]string) error { +func (f *fakeWorkerPlugin) MountVolume(ctx context.Context, volumeID string, targetPath string, attributes map[string]string, publishContext map[string]string, mode ateapipb.VolumeAccessMode, readonly bool) error { return f.mountErr } @@ -126,3 +130,32 @@ func TestUnmountExternalVolumes(t *testing.T) { } }) } + +func TestDirectCSIDriverConfigGetter(t *testing.T) { + fakeClient := fake.NewSimpleClientset(&v1alpha1.CSIDriverConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test.csi.k8s.io", + }, + Spec: v1alpha1.CSIDriverConfigSpec{ + DriverName: "test.csi.k8s.io", + ControllerEndpoint: "tcp://127.0.0.1:9000", + }, + }) + + getter := &directCSIDriverConfigGetter{client: fakeClient} + + // Existing config + cfg, err := getter.Get("test.csi.k8s.io") + if err != nil { + t.Fatalf("getter.Get failed: %v", err) + } + if cfg.Spec.DriverName != "test.csi.k8s.io" { + t.Errorf("expected driver name %q, got %q", "test.csi.k8s.io", cfg.Spec.DriverName) + } + + // Missing config + _, err = getter.Get("missing.csi.k8s.io") + if err == nil { + t.Fatalf("expected error for missing driver, got nil") + } +} diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index 4c4047ae8c..63e1e994f8 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -972,10 +972,13 @@ func (*DurableDirVolume) Descriptor() ([]byte, []int) { } 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"` - VolumeContext map[string]string `protobuf:"bytes,3,rep,name=volume_context,json=volumeContext,proto3" json:"volume_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + 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"` + VolumeContext map[string]string `protobuf:"bytes,3,rep,name=volume_context,json=volumeContext,proto3" json:"volume_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + PublishContext map[string]string `protobuf:"bytes,4,rep,name=publish_context,json=publishContext,proto3" json:"publish_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + AccessMode ateapipb.VolumeAccessMode `protobuf:"varint,5,opt,name=access_mode,json=accessMode,proto3,enum=ateapi.VolumeAccessMode" json:"access_mode,omitempty"` + Readonly bool `protobuf:"varint,6,opt,name=readonly,proto3" json:"readonly,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1031,6 +1034,27 @@ func (x *ExternalVolumeSource) GetVolumeContext() map[string]string { return nil } +func (x *ExternalVolumeSource) GetPublishContext() map[string]string { + if x != nil { + return x.PublishContext + } + return nil +} + +func (x *ExternalVolumeSource) GetAccessMode() ateapipb.VolumeAccessMode { + if x != nil { + return x.AccessMode + } + return ateapipb.VolumeAccessMode(0) +} + +func (x *ExternalVolumeSource) GetReadonly() bool { + if x != nil { + return x.Readonly + } + return false +} + type ImageVolumeSource struct { state protoimpl.MessageState `protogen:"open.v1"` Reference string `protobuf:"bytes,1,opt,name=reference,proto3" json:"reference,omitempty"` @@ -2741,14 +2765,21 @@ const file_atelet_proto_rawDesc = "" + "containers\x18\x01 \x03(\v2\x11.atelet.ContainerR\n" + "containers\x12(\n" + "\avolumes\x18\x03 \x03(\v2\x0e.atelet.VolumeR\avolumesJ\x04\b\x02\x10\x03R\vpause_image\"\x12\n" + - "\x10DurableDirVolume\"\xfd\x01\n" + + "\x10DurableDirVolume\"\xf2\x03\n" + "\x14ExternalVolumeSource\x12*\n" + "\x11storage_volume_id\x18\x01 \x01(\tR\x0fstorageVolumeId\x12\x1f\n" + "\vvolume_type\x18\x02 \x01(\tR\n" + "volumeType\x12V\n" + - "\x0evolume_context\x18\x03 \x03(\v2/.atelet.ExternalVolumeSource.VolumeContextEntryR\rvolumeContext\x1a@\n" + + "\x0evolume_context\x18\x03 \x03(\v2/.atelet.ExternalVolumeSource.VolumeContextEntryR\rvolumeContext\x12Y\n" + + "\x0fpublish_context\x18\x04 \x03(\v20.atelet.ExternalVolumeSource.PublishContextEntryR\x0epublishContext\x129\n" + + "\vaccess_mode\x18\x05 \x01(\x0e2\x18.ateapi.VolumeAccessModeR\n" + + "accessMode\x12\x1a\n" + + "\breadonly\x18\x06 \x01(\bR\breadonly\x1a@\n" + "\x12VolumeContextEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aA\n" + + "\x13PublishContextEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"1\n" + "\x11ImageVolumeSource\x12\x1c\n" + "\treference\x18\x01 \x01(\tR\treference\"Y\n" + @@ -2898,7 +2929,7 @@ func file_atelet_proto_rawDescGZIP() []byte { } var file_atelet_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 41) +var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 42) var file_atelet_proto_goTypes = []any{ (ActorMetadataField)(0), // 0: atelet.ActorMetadataField (CheckpointType)(0), // 1: atelet.CheckpointType @@ -2944,10 +2975,12 @@ var file_atelet_proto_goTypes = []any{ nil, // 41: atelet.ArchAssets.FilesEntry nil, // 42: atelet.SandboxAssets.AssetsEntry nil, // 43: atelet.ExternalVolumeSource.VolumeContextEntry - (*ateapipb.WorkerResources)(nil), // 44: ateapi.WorkerResources + nil, // 44: atelet.ExternalVolumeSource.PublishContextEntry + (*ateapipb.WorkerResources)(nil), // 45: ateapi.WorkerResources + (ateapipb.VolumeAccessMode)(0), // 46: ateapi.VolumeAccessMode } var file_atelet_proto_depIdxs = []int32{ - 44, // 0: atelet.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources + 45, // 0: atelet.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources 14, // 1: atelet.TerminateRequest.spec:type_name -> atelet.WorkloadSpec 14, // 2: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec 13, // 3: atelet.RunRequest.sandbox_assets:type_name -> atelet.SandboxAssets @@ -2957,55 +2990,57 @@ var file_atelet_proto_depIdxs = []int32{ 25, // 7: atelet.WorkloadSpec.containers:type_name -> atelet.Container 23, // 8: atelet.WorkloadSpec.volumes:type_name -> atelet.Volume 43, // 9: atelet.ExternalVolumeSource.volume_context:type_name -> atelet.ExternalVolumeSource.VolumeContextEntry - 0, // 10: atelet.ActorMetadataItem.field:type_name -> atelet.ActorMetadataField - 18, // 11: atelet.ActorMetadataDataSource.items:type_name -> atelet.ActorMetadataItem - 19, // 12: atelet.SystemInfoDataSource.actor_metadata:type_name -> atelet.ActorMetadataDataSource - 20, // 13: atelet.SystemInfoDataSource.trust_bundle:type_name -> atelet.TrustBundleDataSource - 21, // 14: atelet.SystemInfoVolume.data_sources:type_name -> atelet.SystemInfoDataSource - 15, // 15: atelet.Volume.durable_dir:type_name -> atelet.DurableDirVolume - 16, // 16: atelet.Volume.external:type_name -> atelet.ExternalVolumeSource - 22, // 17: atelet.Volume.system_info:type_name -> atelet.SystemInfoVolume - 17, // 18: atelet.Volume.image:type_name -> atelet.ImageVolumeSource - 29, // 19: atelet.Container.env:type_name -> atelet.EnvEntry - 30, // 20: atelet.Container.readyz:type_name -> atelet.Readyz - 24, // 21: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount - 26, // 22: atelet.Container.security_context:type_name -> atelet.SecurityContext - 28, // 23: atelet.Container.resources:type_name -> atelet.ResourceLimits - 27, // 24: atelet.SecurityContext.capabilities:type_name -> atelet.Capabilities - 31, // 25: atelet.Readyz.http_get:type_name -> atelet.HTTPGetAction - 14, // 26: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 27: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType - 33, // 28: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 34, // 29: atelet.CheckpointRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration - 2, // 30: atelet.CheckpointRequest.scope:type_name -> atelet.SnapshotScope - 2, // 31: atelet.UploadPausedCheckpointRequest.desired_scope:type_name -> atelet.SnapshotScope - 14, // 32: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 33: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType - 33, // 34: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 34, // 35: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration - 2, // 36: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope - 10, // 37: atelet.RestoreRequest.egress_gateway:type_name -> atelet.EgressGateway - 11, // 38: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile - 12, // 39: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets - 5, // 40: atelet.AteomSupport.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest - 3, // 41: atelet.AteomSupport.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest - 9, // 42: atelet.AteomHerder.Run:input_type -> atelet.RunRequest - 35, // 43: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest - 39, // 44: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest - 37, // 45: atelet.AteomHerder.UploadPausedCheckpoint:input_type -> atelet.UploadPausedCheckpointRequest - 7, // 46: atelet.AteomHerder.Terminate:input_type -> atelet.TerminateRequest - 6, // 47: atelet.AteomSupport.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse - 4, // 48: atelet.AteomSupport.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse - 32, // 49: atelet.AteomHerder.Run:output_type -> atelet.RunResponse - 36, // 50: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse - 40, // 51: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse - 38, // 52: atelet.AteomHerder.UploadPausedCheckpoint:output_type -> atelet.UploadPausedCheckpointResponse - 8, // 53: atelet.AteomHerder.Terminate:output_type -> atelet.TerminateResponse - 47, // [47:54] is the sub-list for method output_type - 40, // [40:47] is the sub-list for method input_type - 40, // [40:40] is the sub-list for extension type_name - 40, // [40:40] is the sub-list for extension extendee - 0, // [0:40] is the sub-list for field type_name + 44, // 10: atelet.ExternalVolumeSource.publish_context:type_name -> atelet.ExternalVolumeSource.PublishContextEntry + 46, // 11: atelet.ExternalVolumeSource.access_mode:type_name -> ateapi.VolumeAccessMode + 0, // 12: atelet.ActorMetadataItem.field:type_name -> atelet.ActorMetadataField + 18, // 13: atelet.ActorMetadataDataSource.items:type_name -> atelet.ActorMetadataItem + 19, // 14: atelet.SystemInfoDataSource.actor_metadata:type_name -> atelet.ActorMetadataDataSource + 20, // 15: atelet.SystemInfoDataSource.trust_bundle:type_name -> atelet.TrustBundleDataSource + 21, // 16: atelet.SystemInfoVolume.data_sources:type_name -> atelet.SystemInfoDataSource + 15, // 17: atelet.Volume.durable_dir:type_name -> atelet.DurableDirVolume + 16, // 18: atelet.Volume.external:type_name -> atelet.ExternalVolumeSource + 22, // 19: atelet.Volume.system_info:type_name -> atelet.SystemInfoVolume + 17, // 20: atelet.Volume.image:type_name -> atelet.ImageVolumeSource + 29, // 21: atelet.Container.env:type_name -> atelet.EnvEntry + 30, // 22: atelet.Container.readyz:type_name -> atelet.Readyz + 24, // 23: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount + 26, // 24: atelet.Container.security_context:type_name -> atelet.SecurityContext + 28, // 25: atelet.Container.resources:type_name -> atelet.ResourceLimits + 27, // 26: atelet.SecurityContext.capabilities:type_name -> atelet.Capabilities + 31, // 27: atelet.Readyz.http_get:type_name -> atelet.HTTPGetAction + 14, // 28: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec + 1, // 29: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType + 33, // 30: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 34, // 31: atelet.CheckpointRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration + 2, // 32: atelet.CheckpointRequest.scope:type_name -> atelet.SnapshotScope + 2, // 33: atelet.UploadPausedCheckpointRequest.desired_scope:type_name -> atelet.SnapshotScope + 14, // 34: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec + 1, // 35: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType + 33, // 36: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 34, // 37: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration + 2, // 38: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope + 10, // 39: atelet.RestoreRequest.egress_gateway:type_name -> atelet.EgressGateway + 11, // 40: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile + 12, // 41: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets + 5, // 42: atelet.AteomSupport.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest + 3, // 43: atelet.AteomSupport.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest + 9, // 44: atelet.AteomHerder.Run:input_type -> atelet.RunRequest + 35, // 45: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest + 39, // 46: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest + 37, // 47: atelet.AteomHerder.UploadPausedCheckpoint:input_type -> atelet.UploadPausedCheckpointRequest + 7, // 48: atelet.AteomHerder.Terminate:input_type -> atelet.TerminateRequest + 6, // 49: atelet.AteomSupport.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse + 4, // 50: atelet.AteomSupport.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse + 32, // 51: atelet.AteomHerder.Run:output_type -> atelet.RunResponse + 36, // 52: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse + 40, // 53: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse + 38, // 54: atelet.AteomHerder.UploadPausedCheckpoint:output_type -> atelet.UploadPausedCheckpointResponse + 8, // 55: atelet.AteomHerder.Terminate:output_type -> atelet.TerminateResponse + 49, // [49:56] is the sub-list for method output_type + 42, // [42:49] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name } func init() { file_atelet_proto_init() } @@ -3038,7 +3073,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: 41, + NumMessages: 42, NumExtensions: 0, NumServices: 2, }, diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index c4c79201dc..5b9a59c9b9 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -189,6 +189,9 @@ message ExternalVolumeSource { string storage_volume_id = 1; string volume_type = 2; map volume_context = 3; + map publish_context = 4; + ateapi.VolumeAccessMode access_mode = 5; + bool readonly = 6; } message ImageVolumeSource { diff --git a/internal/volume/csi/client.go b/internal/volume/csi/client.go index 19ff306279..8ac4c1cd76 100644 --- a/internal/volume/csi/client.go +++ b/internal/volume/csi/client.go @@ -17,7 +17,10 @@ package csi import ( "crypto/tls" "fmt" + "net" "net/url" + "strconv" + "strings" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" @@ -48,8 +51,37 @@ func parseEndpoint(endpoint string) (string, string, error) { if u.Host == "" { return "", "", fmt.Errorf("tcp endpoint missing host:port: %s", endpoint) } + host, portStr, err := net.SplitHostPort(u.Host) + if err != nil { + return "", "", fmt.Errorf("invalid tcp endpoint %q: %w", endpoint, err) + } + if host == "" { + return "", "", fmt.Errorf("tcp endpoint missing host in %q", endpoint) + } + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + return "", "", fmt.Errorf("tcp endpoint has invalid port %q in %q (must be 1-65535)", portStr, endpoint) + } return "tcp", u.Host, nil case "dns": + target := u.Path + if target == "" { + target = u.Host + } else { + target = strings.TrimPrefix(target, "/") + } + if target == "" { + return "", "", fmt.Errorf("dns endpoint missing host[:port]: %s", endpoint) + } + if host, portStr, err := net.SplitHostPort(target); err == nil { + if host == "" { + return "", "", fmt.Errorf("dns endpoint missing host in %q", endpoint) + } + port, err := strconv.Atoi(portStr) + if err != nil || port < 1 || port > 65535 { + return "", "", fmt.Errorf("dns endpoint has invalid port %q in %q (must be 1-65535)", portStr, endpoint) + } + } return "dns", endpoint, nil default: return "", "", fmt.Errorf("unsupported scheme %q, must be unix, tcp or dns", u.Scheme) diff --git a/internal/volume/csi/client_test.go b/internal/volume/csi/client_test.go index 1e69af2b6b..6bd0930383 100644 --- a/internal/volume/csi/client_test.go +++ b/internal/volume/csi/client_test.go @@ -59,6 +59,20 @@ func TestParseEndpoint(t *testing.T) { wantTgt: "dns:///csi-service:9000", wantErr: false, }, + { + name: "valid dns authority", + endpoint: "dns://8.8.8.8/csi-service:9000", + wantSrc: "dns", + wantTgt: "dns://8.8.8.8/csi-service:9000", + wantErr: false, + }, + { + name: "valid dns without port", + endpoint: "dns:///csi-service", + wantSrc: "dns", + wantTgt: "dns:///csi-service", + wantErr: false, + }, { name: "invalid scheme", endpoint: "http://localhost:50051", @@ -72,16 +86,27 @@ func TestParseEndpoint(t *testing.T) { { name: "tcp missing port", endpoint: "tcp://127.0.0.1", - wantSrc: "tcp", - wantTgt: "127.0.0.1", - wantErr: false, + wantErr: true, }, { name: "tcp missing host", endpoint: "tcp://:50051", - wantSrc: "tcp", - wantTgt: ":50051", - wantErr: false, + wantErr: true, + }, + { + name: "tcp invalid port out of range", + endpoint: "tcp://127.0.0.1:99999", + wantErr: true, + }, + { + name: "tcp non-numeric port", + endpoint: "tcp://127.0.0.1:abc", + wantErr: true, + }, + { + name: "dns invalid port out of range", + endpoint: "dns:///csi-service:99999", + wantErr: true, }, { name: "unix missing path", diff --git a/internal/volume/csi/controller.go b/internal/volume/csi/controller.go index 39f6090877..6fa1316226 100644 --- a/internal/volume/csi/controller.go +++ b/internal/volume/csi/controller.go @@ -39,3 +39,8 @@ func (c *Client) ControllerPublishVolume(ctx context.Context, req *csi.Controlle func (c *Client) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { return c.controller.ControllerUnpublishVolume(ctx, req) } + +// ControllerGetCapabilities returns the capabilities supported by the controller service. +func (c *Client) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + return c.controller.ControllerGetCapabilities(ctx, req) +} diff --git a/internal/volume/csi/node.go b/internal/volume/csi/node.go index 7a91b001e5..2fdf040592 100644 --- a/internal/volume/csi/node.go +++ b/internal/volume/csi/node.go @@ -39,3 +39,8 @@ func (c *Client) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolu func (c *Client) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { return c.node.NodeUnpublishVolume(ctx, req) } + +// NodeGetCapabilities returns the capabilities supported by the node service. +func (c *Client) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { + return c.node.NodeGetCapabilities(ctx, req) +} diff --git a/internal/volume/csi/plugin.go b/internal/volume/csi/plugin.go index ab1337efd9..471db7fa10 100644 --- a/internal/volume/csi/plugin.go +++ b/internal/volume/csi/plugin.go @@ -28,7 +28,7 @@ import ( "github.com/agent-substrate/substrate/internal/credbundle" "github.com/agent-substrate/substrate/internal/volume" v1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" - listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -54,8 +54,12 @@ var defaultTLSPaths = tlsPaths{ // Plugin implements volume.VolumePluginWorkerPlane using the CSI Client. type Plugin struct { - client *Client - stagingDirPrefix string + client *Client + stagingDirPrefix string + controllerCapsInitialized bool + controllerCaps map[csi.ControllerServiceCapability_RPC_Type]bool + nodeCapsInitialized bool + nodeCaps map[csi.NodeServiceCapability_RPC_Type]bool } // Ensure Plugin implements volume.VolumePluginControlPlane and VolumePluginWorkerPlane @@ -67,6 +71,8 @@ func NewPlugin(client *Client) *Plugin { return &Plugin{ client: client, stagingDirPrefix: ateompath.StagingDirPrefix(), + controllerCaps: make(map[csi.ControllerServiceCapability_RPC_Type]bool), + nodeCaps: make(map[csi.NodeServiceCapability_RPC_Type]bool), } } @@ -121,40 +127,95 @@ func (p *Plugin) DeleteVolume(ctx context.Context, volumeID string) error { return nil } +// InitControllerCapabilities queries and caches the controller capabilities of the CSI driver. +func (p *Plugin) InitControllerCapabilities(ctx context.Context) error { + resp, err := p.client.ControllerGetCapabilities(ctx, &csi.ControllerGetCapabilitiesRequest{}) + if err != nil { + if status.Code(err) == codes.Unimplemented { + slog.DebugContext(ctx, "CSI ControllerGetCapabilities unimplemented by driver", slog.Any("error", err)) + p.controllerCapsInitialized = true + return nil + } + return fmt.Errorf("CSI ControllerGetCapabilities failed: %w", err) + } + p.controllerCaps = make(map[csi.ControllerServiceCapability_RPC_Type]bool) + for _, cap := range resp.GetCapabilities() { + if rpc := cap.GetRpc(); rpc != nil { + p.controllerCaps[rpc.GetType()] = true + } + } + p.controllerCapsInitialized = true + return nil +} + +// InitNodeCapabilities queries and caches the node capabilities of the CSI driver. +func (p *Plugin) InitNodeCapabilities(ctx context.Context) error { + resp, err := p.client.NodeGetCapabilities(ctx, &csi.NodeGetCapabilitiesRequest{}) + if err != nil { + if status.Code(err) == codes.Unimplemented { + slog.DebugContext(ctx, "CSI NodeGetCapabilities unimplemented by driver", slog.Any("error", err)) + p.nodeCapsInitialized = true + return nil + } + return fmt.Errorf("CSI NodeGetCapabilities failed: %w", err) + } + p.nodeCaps = make(map[csi.NodeServiceCapability_RPC_Type]bool) + for _, cap := range resp.GetCapabilities() { + if rpc := cap.GetRpc(); rpc != nil { + p.nodeCaps[rpc.GetType()] = true + } + } + p.nodeCapsInitialized = true + return nil +} + +// SupportsControllerPublish reports whether the driver supports ControllerPublishVolume and ControllerUnpublishVolume. +func (p *Plugin) SupportsControllerPublish() bool { + return p.controllerCaps[csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME] +} + +// SupportsNodeStage reports whether the driver supports NodeStageVolume and NodeUnstageVolume. +func (p *Plugin) SupportsNodeStage() bool { + return p.nodeCaps[csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME] +} + // AttachVolume maps to CSI Controller ControllerPublishVolume. -func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string) error { +func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string, mode ateapipb.VolumeAccessMode) (map[string]string, error) { + if p.controllerCapsInitialized && !p.SupportsControllerPublish() { + slog.DebugContext(ctx, "CSI driver does not support PUBLISH_UNPUBLISH_VOLUME; skipping attach", slog.String("volume_id", volumeID), slog.String("node", node)) + return nil, nil + } + + readonly := mode == ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY req := &csi.ControllerPublishVolumeRequest{ VolumeId: volumeID, NodeId: node, - VolumeCapability: getStandardCapabilities()[0], // Use primary capability - Readonly: false, + VolumeCapability: VolumeCapability(mode), + Readonly: readonly, } resp, err := p.client.ControllerPublishVolume(ctx, req) if err != nil { - // TODO: Query CSI driver capabilities ahead of time (e.g. during plugin initialization) - // to avoid calling unimplemented methods and generating spammy logs. if status.Code(err) == codes.Unimplemented { slog.WarnContext(ctx, "CSI ControllerPublishVolume is unimplemented by driver; skipping attach", slog.String("volume_id", volumeID), slog.String("node", node)) - return nil + return nil, nil } - return fmt.Errorf("CSI ControllerPublishVolume failed: %w", err) + return nil, fmt.Errorf("CSI ControllerPublishVolume failed: %w", err) } - // NOTE: CSI ControllerPublishVolume returns PublishContext (metadata needed for mounting). - // Currently, Substrate VolumePlugin interface does not support returning PublishContext. - // We might need to store this context if the driver requires it (e.g. AWS EBS attachment info). - // TODO: Extend Substrate's VolumePlugin interface to return and propagate - // PublishContext if required by the driver for mounting. if resp != nil { - _ = resp.GetPublishContext() + return resp.GetPublishContext(), nil } - - return nil + return nil, nil } // DetachVolume maps to CSI Controller ControllerUnpublishVolume. func (p *Plugin) DetachVolume(ctx context.Context, volumeID string, node string) error { + if p.controllerCapsInitialized && !p.SupportsControllerPublish() { + slog.DebugContext(ctx, "CSI driver does not support PUBLISH_UNPUBLISH_VOLUME; skipping detach", slog.String("volume_id", volumeID), slog.String("node", node)) + return nil + } + req := &csi.ControllerUnpublishVolumeRequest{ VolumeId: volumeID, NodeId: node, @@ -173,43 +234,51 @@ func (p *Plugin) DetachVolume(ctx context.Context, volumeID string, node string) // MountVolume maps to CSI Node NodePublishVolume. // It also handles NodeStageVolume staging if required by the driver. -func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string) error { +func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string, publishContext map[string]string, mode ateapipb.VolumeAccessMode, readonly bool) error { + cap := VolumeCapability(mode) + isReadOnly := readonly || mode == ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY + // 1. Stage the volume - stagingPath := filepath.Join(p.stagingDirPrefix, volumeID) - if err := os.MkdirAll(stagingPath, 0750); err != nil { - return fmt.Errorf("failed to create staging directory %q: %w", stagingPath, err) - } + stagingPath := "" + if !p.nodeCapsInitialized || p.SupportsNodeStage() { + stagingPath = filepath.Join(p.stagingDirPrefix, volumeID) + if err := os.MkdirAll(stagingPath, 0750); err != nil { + return fmt.Errorf("failed to create staging directory %q: %w", stagingPath, err) + } - stageReq := &csi.NodeStageVolumeRequest{ - VolumeId: volumeID, - StagingTargetPath: stagingPath, - VolumeCapability: getStandardCapabilities()[0], // Use primary capability - VolumeContext: volumeContext, - } + stageReq := &csi.NodeStageVolumeRequest{ + VolumeId: volumeID, + PublishContext: publishContext, + StagingTargetPath: stagingPath, + VolumeCapability: cap, + VolumeContext: volumeContext, + } - _, err := p.client.NodeStageVolume(ctx, stageReq) - if err != nil { - if status.Code(err) == codes.Unimplemented { - slog.WarnContext(ctx, "CSI NodeStageVolume is unimplemented by driver; skipping staging", slog.String("volume_id", volumeID)) - stagingPath = "" - } else { - return fmt.Errorf("CSI NodeStageVolume failed: %w", err) + _, err := p.client.NodeStageVolume(ctx, stageReq) + if err != nil { + if status.Code(err) == codes.Unimplemented { + slog.WarnContext(ctx, "CSI NodeStageVolume is unimplemented by driver; skipping staging", slog.String("volume_id", volumeID)) + stagingPath = "" + } else { + return fmt.Errorf("CSI NodeStageVolume failed: %w", err) + } } } // 2. Publish (Mount) the volume req := &csi.NodePublishVolumeRequest{ VolumeId: volumeID, + PublishContext: publishContext, TargetPath: targetPath, - VolumeCapability: getStandardCapabilities()[0], - Readonly: false, + VolumeCapability: cap, + Readonly: isReadOnly, VolumeContext: volumeContext, } if stagingPath != "" { req.StagingTargetPath = stagingPath } - _, err = p.client.NodePublishVolume(ctx, req) + _, err := p.client.NodePublishVolume(ctx, req) if err != nil { return fmt.Errorf("CSI NodePublishVolume failed: %w", err) } @@ -231,56 +300,74 @@ func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath } // 2. Unstage the volume - stagingPath := filepath.Join(p.stagingDirPrefix, volumeID) - unstageReq := &csi.NodeUnstageVolumeRequest{ - VolumeId: volumeID, - StagingTargetPath: stagingPath, - } + if !p.nodeCapsInitialized || p.SupportsNodeStage() { + stagingPath := filepath.Join(p.stagingDirPrefix, volumeID) + unstageReq := &csi.NodeUnstageVolumeRequest{ + VolumeId: volumeID, + StagingTargetPath: stagingPath, + } - _, err = p.client.NodeUnstageVolume(ctx, unstageReq) - if err != nil { - if status.Code(err) == codes.Unimplemented { - slog.WarnContext(ctx, "CSI NodeUnstageVolume is unimplemented by driver; skipping unstaging", slog.String("volume_id", volumeID)) - } else { - return fmt.Errorf("CSI NodeUnstageVolume failed: %w", err) + _, err = p.client.NodeUnstageVolume(ctx, unstageReq) + if err != nil { + if status.Code(err) == codes.Unimplemented { + slog.WarnContext(ctx, "CSI NodeUnstageVolume is unimplemented by driver; skipping unstaging", slog.String("volume_id", volumeID)) + } else { + return fmt.Errorf("CSI NodeUnstageVolume failed: %w", err) + } } - } - // Clean up staging directory - if err := os.Remove(stagingPath); err != nil && !os.IsNotExist(err) { - slog.WarnContext(ctx, "failed to remove staging directory", slog.String("path", stagingPath), slog.Any("error", err)) + // Clean up staging directory + if err := os.Remove(stagingPath); err != nil && !os.IsNotExist(err) { + slog.WarnContext(ctx, "failed to remove staging directory", slog.String("path", stagingPath), slog.Any("error", err)) + } } return nil } +// VolumeCapability returns the CSI VolumeCapability corresponding to the given VolumeAccessMode. +func VolumeCapability(mode ateapipb.VolumeAccessMode) *csi.VolumeCapability { + csiMode := csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER + switch mode { + case ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY: + csiMode = csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY + case ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY: + csiMode = csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER + } + return &csi.VolumeCapability{ + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csiMode, + }, + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{}, + }, + } +} + // Helper to provide standard capabilities for general volume operations. -// TODO: Support and expose different volume access modes (e.g. ReadWriteMany, ReadOnlyMany) -// instead of hardcoding SingleNodeWriter. func getStandardCapabilities() []*csi.VolumeCapability { return []*csi.VolumeCapability{ - { - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{}, - }, - }, + VolumeCapability(ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE), } } +// CSIDriverConfigGetter provides access to retrieve a CSIDriverConfig by name. +// Both listersv1alpha1.CSIDriverConfigLister and direct client getters implement this interface. +type CSIDriverConfigGetter interface { + Get(name string) (*v1alpha1.CSIDriverConfig, error) +} + // NewCSIPlugin establishes a CSI client and returns a verified Plugin instance. -func NewCSIPlugin(ctx context.Context, lister listersv1alpha1.CSIDriverConfigLister, driverName string, isController bool) (*Plugin, error) { - return newCSIPlugin(ctx, lister, driverName, isController, defaultTLSPaths) +func NewCSIPlugin(ctx context.Context, getter CSIDriverConfigGetter, driverName string, isController bool) (*Plugin, error) { + return newCSIPlugin(ctx, getter, driverName, isController, defaultTLSPaths) } -func newCSIPlugin(ctx context.Context, lister listersv1alpha1.CSIDriverConfigLister, driverName string, isController bool, paths tlsPaths) (*Plugin, error) { - if lister == nil { - return nil, fmt.Errorf("missing csiDriverConfigLister") +func newCSIPlugin(ctx context.Context, getter CSIDriverConfigGetter, driverName string, isController bool, paths tlsPaths) (*Plugin, error) { + if getter == nil { + return nil, fmt.Errorf("missing csiDriverConfigGetter") } - cfg, err := lister.Get(driverName) + cfg, err := getter.Get(driverName) if err != nil { return nil, fmt.Errorf("failed to retrieve CSIDriverConfig for %q: %w", driverName, err) } @@ -322,6 +409,18 @@ func newCSIPlugin(ctx context.Context, lister listersv1alpha1.CSIDriverConfigLis return nil, fmt.Errorf("reported driver name %q does not match requested name %q", reportedName, driverName) } + if isController { + if err := csiPlugin.InitControllerCapabilities(ctx); err != nil { + csiClient.Close() + return nil, fmt.Errorf("failed to initialize controller capabilities for %q: %w", driverName, err) + } + } else { + if err := csiPlugin.InitNodeCapabilities(ctx); err != nil { + csiClient.Close() + return nil, fmt.Errorf("failed to initialize node capabilities for %q: %w", driverName, err) + } + } + return csiPlugin, nil } diff --git a/internal/volume/csi/plugin_test.go b/internal/volume/csi/plugin_test.go index 79b5a75d9d..059546591f 100644 --- a/internal/volume/csi/plugin_test.go +++ b/internal/volume/csi/plugin_test.go @@ -21,6 +21,7 @@ import ( "path/filepath" "testing" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -40,6 +41,8 @@ type mockCSIDriver struct { nodeUnstageVolumeFunc func(context.Context, *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) nodePublishVolumeFunc func(context.Context, *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) nodeUnpublishVolumeFunc func(context.Context, *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) + controllerGetCapabilitiesFunc func(context.Context, *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) + nodeGetCapabilitiesFunc func(context.Context, *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) getPluginCapabilitiesFunc func(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) probeFunc func(context.Context, *csi.ProbeRequest) (*csi.ProbeResponse, error) @@ -137,6 +140,40 @@ func (m *mockCSIDriver) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUn return &csi.NodeUnpublishVolumeResponse{}, nil } +func (m *mockCSIDriver) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + if m.controllerGetCapabilitiesFunc != nil { + return m.controllerGetCapabilitiesFunc(ctx, req) + } + return &csi.ControllerGetCapabilitiesResponse{ + Capabilities: []*csi.ControllerServiceCapability{ + { + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + }, + }, + }, + }, + }, nil +} + +func (m *mockCSIDriver) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { + if m.nodeGetCapabilitiesFunc != nil { + return m.nodeGetCapabilitiesFunc(ctx, req) + } + return &csi.NodeGetCapabilitiesResponse{ + Capabilities: []*csi.NodeServiceCapability{ + { + Type: &csi.NodeServiceCapability_Rpc{ + Rpc: &csi.NodeServiceCapability_RPC{ + Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, + }, + }, + }, + }, + }, nil +} + func startMockCSIDriver(t *testing.T, driver *mockCSIDriver) (string, func()) { tmpDir, err := os.MkdirTemp("", "csi-test-*") if err != nil { @@ -215,7 +252,15 @@ func TestPlugin_DeleteVolume(t *testing.T) { } func TestPlugin_AttachVolume(t *testing.T) { - driver := &mockCSIDriver{} + var receivedReq *csi.ControllerPublishVolumeRequest + driver := &mockCSIDriver{ + controllerPublishVolumeFunc: func(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + receivedReq = req + return &csi.ControllerPublishVolumeResponse{ + PublishContext: map[string]string{"device_path": "/dev/xvda"}, + }, nil + }, + } endpoint, cleanup := startMockCSIDriver(t, driver) defer cleanup() @@ -228,19 +273,31 @@ func TestPlugin_AttachVolume(t *testing.T) { plugin := NewPlugin(client) ctx := context.Background() - err = plugin.AttachVolume(ctx, "test-vol", "node-1") + pubCtx, err := plugin.AttachVolume(ctx, "test-vol", "node-1", ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY) if err != nil { t.Fatalf("AttachVolume failed: %v", err) } + if pubCtx["device_path"] != "/dev/xvda" { + t.Errorf("expected publish context device_path to be /dev/xvda, got %v", pubCtx) + } + if !receivedReq.GetReadonly() { + t.Errorf("expected Readonly to be true for ReadOnlyMany") + } + if receivedReq.GetVolumeCapability().GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { + t.Errorf("expected MULTI_NODE_READER_ONLY, got %v", receivedReq.GetVolumeCapability().GetAccessMode().GetMode()) + } // Test Unimplemented warning bypass driver.controllerPublishVolumeFunc = func(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { return nil, status.Error(codes.Unimplemented, "unimplemented") } - err = plugin.AttachVolume(ctx, "test-vol", "node-1") + pubCtx, err = plugin.AttachVolume(ctx, "test-vol", "node-1", ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE) if err != nil { t.Errorf("AttachVolume should have ignored Unimplemented error, got: %v", err) } + if pubCtx != nil { + t.Errorf("expected nil publish context on unimplemented, got %v", pubCtx) + } } func TestPlugin_DetachVolume(t *testing.T) { @@ -273,7 +330,18 @@ func TestPlugin_DetachVolume(t *testing.T) { } func TestPlugin_MountVolume(t *testing.T) { - driver := &mockCSIDriver{} + var receivedStageReq *csi.NodeStageVolumeRequest + var receivedPublishReq *csi.NodePublishVolumeRequest + driver := &mockCSIDriver{ + nodeStageVolumeFunc: func(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + receivedStageReq = req + return &csi.NodeStageVolumeResponse{}, nil + }, + nodePublishVolumeFunc: func(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + receivedPublishReq = req + return &csi.NodePublishVolumeResponse{}, nil + }, + } endpoint, cleanup := startMockCSIDriver(t, driver) defer cleanup() @@ -284,17 +352,14 @@ func TestPlugin_MountVolume(t *testing.T) { defer client.Close() plugin := NewPlugin(client) - tmpDir, err := os.MkdirTemp("", "csi-mount-test-*") - if err != nil { - t.Fatalf("failed to create temp dir: %v", err) - } - defer os.RemoveAll(tmpDir) + tmpDir := t.TempDir() plugin.stagingDirPrefix = filepath.Join(tmpDir, "staging") targetPath := filepath.Join(tmpDir, "target") ctx := context.Background() - err = plugin.MountVolume(ctx, "test-vol", targetPath, nil) + pubCtx := map[string]string{"device_path": "/dev/xvda"} + err = plugin.MountVolume(ctx, "test-vol", targetPath, nil, pubCtx, ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, false) if err != nil { t.Fatalf("MountVolume failed: %v", err) } @@ -305,6 +370,25 @@ func TestPlugin_MountVolume(t *testing.T) { t.Errorf("staging directory %q was not created", stagingPath) } + // Verify publishContext and access mode were propagated to NodeStageVolume + if receivedStageReq == nil || receivedStageReq.GetPublishContext()["device_path"] != "/dev/xvda" { + t.Errorf("expected publishContext to be propagated to NodeStageVolume, got %v", receivedStageReq) + } + if receivedStageReq.GetVolumeCapability().GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { + t.Errorf("expected MULTI_NODE_READER_ONLY in stage, got %v", receivedStageReq.GetVolumeCapability().GetAccessMode().GetMode()) + } + + // Verify publishContext, access mode, and readonly were propagated to NodePublishVolume + if receivedPublishReq == nil || receivedPublishReq.GetPublishContext()["device_path"] != "/dev/xvda" { + t.Errorf("expected publishContext to be propagated to NodePublishVolume, got %v", receivedPublishReq) + } + if !receivedPublishReq.GetReadonly() { + t.Errorf("expected Readonly to be true in NodePublishVolume for ReadOnlyMany") + } + if receivedPublishReq.GetVolumeCapability().GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { + t.Errorf("expected MULTI_NODE_READER_ONLY in publish, got %v", receivedPublishReq.GetVolumeCapability().GetAccessMode().GetMode()) + } + // Test NodeStageVolume Unimplemented bypass driver.nodeStageVolumeFunc = func(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { return nil, status.Error(codes.Unimplemented, "unimplemented") @@ -313,7 +397,7 @@ func TestPlugin_MountVolume(t *testing.T) { os.RemoveAll(tmpDir) os.MkdirAll(plugin.stagingDirPrefix, 0750) - err = plugin.MountVolume(ctx, "test-vol-2", targetPath, nil) + err = plugin.MountVolume(ctx, "test-vol-2", targetPath, nil, nil, ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE, false) if err != nil { t.Errorf("MountVolume should have succeeded when NodeStageVolume is unimplemented, got: %v", err) } @@ -408,3 +492,208 @@ func TestClient_Identity(t *testing.T) { t.Fatalf("Probe failed: %v", err) } } + +func TestPlugin_Capabilities_SkipAttachDetachWhenUnsupported(t *testing.T) { + var publishCalled, unpublishCalled int + driver := &mockCSIDriver{ + controllerGetCapabilitiesFunc: func(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + // Return capabilities WITHOUT PUBLISH_UNPUBLISH_VOLUME (like hostpath driver) + return &csi.ControllerGetCapabilitiesResponse{ + Capabilities: []*csi.ControllerServiceCapability{ + { + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + }, + }, + }, + }, + }, nil + }, + controllerPublishVolumeFunc: func(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + publishCalled++ + return &csi.ControllerPublishVolumeResponse{}, nil + }, + controllerUnpublishVolumeFunc: func(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { + unpublishCalled++ + return &csi.ControllerUnpublishVolumeResponse{}, nil + }, + } + endpoint, cleanup := startMockCSIDriver(t, driver) + defer cleanup() + + client, err := NewCSIClient(endpoint, nil) + if err != nil { + t.Fatalf("failed to create CSI client: %v", err) + } + defer client.Close() + + plugin := NewPlugin(client) + ctx := context.Background() + + if err := plugin.InitControllerCapabilities(ctx); err != nil { + t.Fatalf("InitControllerCapabilities failed: %v", err) + } + + if plugin.SupportsControllerPublish() { + t.Errorf("expected SupportsControllerPublish to be false") + } + + // AttachVolume should skip calling ControllerPublishVolume entirely + if _, err := plugin.AttachVolume(ctx, "test-vol", "node-1", ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE); err != nil { + t.Errorf("AttachVolume failed: %v", err) + } + if publishCalled != 0 { + t.Errorf("expected 0 calls to ControllerPublishVolume, got %d", publishCalled) + } + + // DetachVolume should skip calling ControllerUnpublishVolume entirely + if err := plugin.DetachVolume(ctx, "test-vol", "node-1"); err != nil { + t.Errorf("DetachVolume failed: %v", err) + } + if unpublishCalled != 0 { + t.Errorf("expected 0 calls to ControllerUnpublishVolume, got %d", unpublishCalled) + } +} + +func TestPlugin_Capabilities_SkipStageUnstageWhenUnsupported(t *testing.T) { + var stageCalled, unstageCalled int + driver := &mockCSIDriver{ + nodeGetCapabilitiesFunc: func(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { + // Return capabilities WITHOUT STAGE_UNSTAGE_VOLUME + return &csi.NodeGetCapabilitiesResponse{ + Capabilities: []*csi.NodeServiceCapability{}, + }, nil + }, + nodeStageVolumeFunc: func(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + stageCalled++ + return &csi.NodeStageVolumeResponse{}, nil + }, + nodeUnstageVolumeFunc: func(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { + unstageCalled++ + return &csi.NodeUnstageVolumeResponse{}, nil + }, + } + endpoint, cleanup := startMockCSIDriver(t, driver) + defer cleanup() + + client, err := NewCSIClient(endpoint, nil) + if err != nil { + t.Fatalf("failed to create CSI client: %v", err) + } + defer client.Close() + + plugin := NewPlugin(client) + ctx := context.Background() + + if err := plugin.InitNodeCapabilities(ctx); err != nil { + t.Fatalf("InitNodeCapabilities failed: %v", err) + } + + if plugin.SupportsNodeStage() { + t.Errorf("expected SupportsNodeStage to be false") + } + + tmpDir := t.TempDir() + targetPath := filepath.Join(tmpDir, "target") + if err := os.MkdirAll(targetPath, 0750); err != nil { + t.Fatalf("failed to create target path: %v", err) + } + + // MountVolume should skip NodeStageVolume entirely + if err := plugin.MountVolume(ctx, "test-vol", targetPath, nil, nil, ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE, false); err != nil { + t.Errorf("MountVolume failed: %v", err) + } + if stageCalled != 0 { + t.Errorf("expected 0 calls to NodeStageVolume, got %d", stageCalled) + } + + // UnmountVolume should skip NodeUnstageVolume entirely + if err := plugin.UnmountVolume(ctx, "test-vol", targetPath); err != nil { + t.Errorf("UnmountVolume failed: %v", err) + } + if unstageCalled != 0 { + t.Errorf("expected 0 calls to NodeUnstageVolume, got %d", unstageCalled) + } +} + +func TestPlugin_VolumeCapability(t *testing.T) { + tests := []struct { + name string + mode ateapipb.VolumeAccessMode + wantMode csi.VolumeCapability_AccessMode_Mode + }{ + { + name: "unspecified defaults to SINGLE_NODE_WRITER", + mode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED, + wantMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + { + name: "ReadWriteOnce maps to SINGLE_NODE_WRITER", + mode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE, + wantMode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, + }, + { + name: "ReadOnlyMany maps to MULTI_NODE_READER_ONLY", + mode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, + wantMode: csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, + }, + { + name: "ReadWriteMany maps to MULTI_NODE_MULTI_WRITER", + mode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY, + wantMode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cap := VolumeCapability(tt.mode) + if cap == nil { + t.Fatalf("VolumeCapability returned nil") + } + if cap.GetAccessMode().GetMode() != tt.wantMode { + t.Errorf("VolumeCapability(%v) mode = %v, want %v", tt.mode, cap.GetAccessMode().GetMode(), tt.wantMode) + } + if cap.GetMount() == nil { + t.Errorf("VolumeCapability(%v) mount is nil", tt.mode) + } + }) + } +} + +func TestPlugin_AttachVolume_ReadWriteMany(t *testing.T) { + var receivedReq *csi.ControllerPublishVolumeRequest + driver := &mockCSIDriver{ + controllerPublishVolumeFunc: func(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + receivedReq = req + return &csi.ControllerPublishVolumeResponse{ + PublishContext: map[string]string{"shared_disk": "true"}, + }, nil + }, + } + endpoint, cleanup := startMockCSIDriver(t, driver) + defer cleanup() + + client, err := NewCSIClient(endpoint, nil) + if err != nil { + t.Fatalf("failed to create CSI client: %v", err) + } + defer client.Close() + + plugin := NewPlugin(client) + ctx := context.Background() + + pubCtx, err := plugin.AttachVolume(ctx, "shared-vol", "node-1", ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY) + if err != nil { + t.Fatalf("AttachVolume failed: %v", err) + } + if pubCtx["shared_disk"] != "true" { + t.Errorf("expected publish context shared_disk=true, got %v", pubCtx) + } + if receivedReq.GetReadonly() { + t.Errorf("expected Readonly=false for ReadWriteMany") + } + if receivedReq.GetVolumeCapability().GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { + t.Errorf("expected MULTI_NODE_MULTI_WRITER, got %v", receivedReq.GetVolumeCapability().GetAccessMode().GetMode()) + } +} diff --git a/internal/volume/mock.go b/internal/volume/mock.go index 017d3ca11d..f2188fc83d 100644 --- a/internal/volume/mock.go +++ b/internal/volume/mock.go @@ -22,6 +22,7 @@ import ( "path/filepath" "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" ) // Use a directory that is shared between atelet and ateom but not cleaned up by atelet @@ -75,9 +76,9 @@ func (p *MockVolumePlugin) DeleteVolume(ctx context.Context, volumeID string) er } // AttachVolume simulates volume attachment to a node. -func (p *MockVolumePlugin) AttachVolume(ctx context.Context, volumeID string, node string) error { - slog.InfoContext(ctx, "MockVolumePlugin.AttachVolume", slog.String("volumeID", volumeID), slog.String("node", node)) - return nil +func (p *MockVolumePlugin) AttachVolume(ctx context.Context, volumeID string, node string, mode ateapipb.VolumeAccessMode) (map[string]string, error) { + slog.InfoContext(ctx, "MockVolumePlugin.AttachVolume", slog.String("volumeID", volumeID), slog.String("node", node), slog.String("mode", mode.String())) + return nil, nil } // DetachVolume simulates volume detachment from a node. @@ -87,7 +88,7 @@ func (p *MockVolumePlugin) DetachVolume(ctx context.Context, volumeID string, no } // MountVolume simulates mounting volume on the host. -func (p *MockVolumePlugin) MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string) error { +func (p *MockVolumePlugin) MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string, publishContext map[string]string, mode ateapipb.VolumeAccessMode, readonly bool) error { slog.InfoContext(ctx, "MockVolumePlugin.MountVolume", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath)) volumeDir := filepath.Join(mockVolumeDirectories, volumeID) diff --git a/internal/volume/plugin.go b/internal/volume/plugin.go index ee54799c2b..90a6435a51 100644 --- a/internal/volume/plugin.go +++ b/internal/volume/plugin.go @@ -16,6 +16,8 @@ package volume import ( "context" + + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" ) // VolumePluginControlPlane abstracts storage operations performed on the control plane. @@ -23,12 +25,12 @@ type VolumePluginControlPlane interface { DriverName(ctx context.Context) (string, error) CreateVolume(ctx context.Context, name string, capacity string, driverName string, parameters map[string]string) (volumeID string, volumeContext map[string]string, err error) DeleteVolume(ctx context.Context, volumeID string) error - AttachVolume(ctx context.Context, volumeID string, node string) error + AttachVolume(ctx context.Context, volumeID string, node string, mode ateapipb.VolumeAccessMode) (publishContext map[string]string, err error) DetachVolume(ctx context.Context, volumeID string, node string) error } // VolumePluginWorkerPlane abstracts storage operations performed on worker nodes. type VolumePluginWorkerPlane interface { - MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string) error + MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string, publishContext map[string]string, mode ateapipb.VolumeAccessMode, readonly bool) error UnmountVolume(ctx context.Context, volumeID string, targetPath string) error } diff --git a/internal/volume/resolve.go b/internal/volume/resolve.go new file mode 100644 index 0000000000..5bde23de35 --- /dev/null +++ b/internal/volume/resolve.go @@ -0,0 +1,39 @@ +// 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" + "errors" + "fmt" +) + +// LookupPlugin resolves a volume plugin for the given volume type using the provided resolver function. +// It returns an error wrapping the underlying failure if lookup fails, if resolver is nil, or if volumeType is empty. +// Wrapping with %w preserves underlying gRPC status codes (e.g. codes.NotFound). +func LookupPlugin[T any](ctx context.Context, resolver func(context.Context, string) (T, error), volumeType string) (T, error) { + var zero T + if resolver == nil { + return zero, errors.New("plugin resolver is required") + } + if volumeType == "" { + return zero, errors.New("volume type is required") + } + plugin, err := resolver(ctx, volumeType) + if err != nil { + return zero, fmt.Errorf("failed to get volume plugin for %q: %w", volumeType, err) + } + return plugin, nil +} diff --git a/internal/volume/resolve_test.go b/internal/volume/resolve_test.go new file mode 100644 index 0000000000..63788c6ef7 --- /dev/null +++ b/internal/volume/resolve_test.go @@ -0,0 +1,121 @@ +// 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" + "errors" + "strings" + "testing" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func TestLookupPlugin(t *testing.T) { + type dummyPlugin struct { + name string + } + + ctx := context.Background() + testErr := errors.New("driver not found") + grpcNotFoundErr := status.Error(codes.NotFound, "plugin not found in registry") + + tests := []struct { + name string + resolver func(context.Context, string) (*dummyPlugin, error) + volumeType string + wantPlugin *dummyPlugin + wantErr bool + errContains string + targetErr error + wantGRPCCode codes.Code + }{ + { + name: "success", + resolver: func(ctx context.Context, name string) (*dummyPlugin, error) { + if name == "csi.example.com" { + return &dummyPlugin{name: name}, nil + } + return nil, testErr + }, + volumeType: "csi.example.com", + wantPlugin: &dummyPlugin{name: "csi.example.com"}, + }, + { + name: "empty volume type", + resolver: func(ctx context.Context, name string) (*dummyPlugin, error) { + return &dummyPlugin{name: name}, nil + }, + volumeType: "", + wantErr: true, + errContains: "volume type is required", + }, + { + name: "nil resolver", + resolver: nil, + volumeType: "csi.example.com", + wantErr: true, + errContains: "plugin resolver is required", + }, + { + name: "resolver error wrapped", + resolver: func(ctx context.Context, name string) (*dummyPlugin, error) { + return nil, testErr + }, + volumeType: "missing.csi", + wantErr: true, + errContains: `failed to get volume plugin for "missing.csi"`, + targetErr: testErr, + }, + { + name: "grpc status code preserved through wrapping", + resolver: func(ctx context.Context, name string) (*dummyPlugin, error) { + return nil, grpcNotFoundErr + }, + volumeType: "notfound.csi", + wantErr: true, + errContains: `failed to get volume plugin for "notfound.csi"`, + targetErr: grpcNotFoundErr, + wantGRPCCode: codes.NotFound, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := LookupPlugin(ctx, tc.resolver, tc.volumeType) + if (err != nil) != tc.wantErr { + t.Fatalf("LookupPlugin() error = %v, wantErr %v", err, tc.wantErr) + } + if tc.wantErr { + if tc.errContains != "" && !strings.Contains(err.Error(), tc.errContains) { + t.Errorf("LookupPlugin() error %q does not contain %q", err.Error(), tc.errContains) + } + if tc.targetErr != nil && !errors.Is(err, tc.targetErr) { + t.Errorf("LookupPlugin() error %v does not wrap %v", err, tc.targetErr) + } + if tc.wantGRPCCode != codes.OK { + if code := status.Code(err); code != tc.wantGRPCCode { + t.Errorf("LookupPlugin() status code = %v, want %v", code, tc.wantGRPCCode) + } + } + return + } + if got == nil || got.name != tc.wantPlugin.name { + t.Errorf("LookupPlugin() = %v, want %v", got, tc.wantPlugin) + } + }) + } +} diff --git a/manifests/ate-install/generated/ate.dev_csidriverconfigs.yaml b/manifests/ate-install/generated/ate.dev_csidriverconfigs.yaml index 31bdc43f8d..7bc21e33c9 100644 --- a/manifests/ate-install/generated/ate.dev_csidriverconfigs.yaml +++ b/manifests/ate-install/generated/ate.dev_csidriverconfigs.yaml @@ -66,7 +66,9 @@ spec: description: |- ControllerEndpoint is the gRPC endpoint for the CSI Controller service. Must be a valid network URI (e.g. dns:///csi-service:9000 or tcp://127.0.0.1:9000). - pattern: ^(tcp|dns)://.+$ + maxLength: 253 + minLength: 1 + pattern: ^(tcp://([a-zA-Z0-9][-a-zA-Z0-9.]*|\[[a-fA-F0-9:]+\]):[0-9]+|dns://(/([^/]+/)?)?[a-zA-Z0-9][-a-zA-Z0-9.]*(:[0-9]+)?)$ type: string driverName: description: |- diff --git a/pkg/api/v1alpha1/csidriverconfig_types.go b/pkg/api/v1alpha1/csidriverconfig_types.go index 02d095245f..f27a2f8915 100644 --- a/pkg/api/v1alpha1/csidriverconfig_types.go +++ b/pkg/api/v1alpha1/csidriverconfig_types.go @@ -31,10 +31,11 @@ type CSIDriverConfigSpec struct { // ControllerEndpoint is the gRPC endpoint for the CSI Controller service. // Must be a valid network URI (e.g. dns:///csi-service:9000 or tcp://127.0.0.1:9000). - // TODO: Harden endpoint validation to prevent invalid or unsafe URI inputs. // // +required - // +kubebuilder:validation:Pattern=`^(tcp|dns)://.+$` + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:MaxLength=253 + // +kubebuilder:validation:Pattern=`^(tcp://([a-zA-Z0-9][-a-zA-Z0-9.]*|\[[a-fA-F0-9:]+\]):[0-9]+|dns://(/([^/]+/)?)?[a-zA-Z0-9][-a-zA-Z0-9.]*(:[0-9]+)?)$` ControllerEndpoint string `json:"controllerEndpoint"` // NodeSocketOverride is an optional override for the CSI Node service socket diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index ea3f295511..7a062354cb 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -142,6 +142,62 @@ func (TagScope) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{1} } +// VolumeAccessMode defines the access modes for external volumes. +type VolumeAccessMode int32 + +const ( + VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED VolumeAccessMode = 0 + // ReadWriteOnce can be mounted as read-write by a single node. + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE VolumeAccessMode = 1 + // ReadOnlyMany can be mounted as read-only by many nodes simultaneously. + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY VolumeAccessMode = 2 + // ReadWriteMany can be mounted as read-write by many nodes simultaneously. + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY VolumeAccessMode = 3 +) + +// Enum value maps for VolumeAccessMode. +var ( + VolumeAccessMode_name = map[int32]string{ + 0: "VOLUME_ACCESS_MODE_UNSPECIFIED", + 1: "VOLUME_ACCESS_MODE_READ_WRITE_ONCE", + 2: "VOLUME_ACCESS_MODE_READ_ONLY_MANY", + 3: "VOLUME_ACCESS_MODE_READ_WRITE_MANY", + } + VolumeAccessMode_value = map[string]int32{ + "VOLUME_ACCESS_MODE_UNSPECIFIED": 0, + "VOLUME_ACCESS_MODE_READ_WRITE_ONCE": 1, + "VOLUME_ACCESS_MODE_READ_ONLY_MANY": 2, + "VOLUME_ACCESS_MODE_READ_WRITE_MANY": 3, + } +) + +func (x VolumeAccessMode) Enum() *VolumeAccessMode { + p := new(VolumeAccessMode) + *p = x + return p +} + +func (x VolumeAccessMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (VolumeAccessMode) Descriptor() protoreflect.EnumDescriptor { + return file_ateapi_proto_enumTypes[2].Descriptor() +} + +func (VolumeAccessMode) Type() protoreflect.EnumType { + return &file_ateapi_proto_enumTypes[2] +} + +func (x VolumeAccessMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use VolumeAccessMode.Descriptor instead. +func (VolumeAccessMode) EnumDescriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{2} +} + type ActorState int32 const ( @@ -193,11 +249,11 @@ func (x ActorState) String() string { } func (ActorState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[2].Descriptor() + return file_ateapi_proto_enumTypes[3].Descriptor() } func (ActorState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[2] + return &file_ateapi_proto_enumTypes[3] } func (x ActorState) Number() protoreflect.EnumNumber { @@ -206,7 +262,7 @@ func (x ActorState) Number() protoreflect.EnumNumber { // Deprecated: Use ActorState.Descriptor instead. func (ActorState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{2} + return file_ateapi_proto_rawDescGZIP(), []int{3} } // SandboxClass selects the sandbox runtime family. Snapshots are not portable @@ -244,11 +300,11 @@ func (x SandboxClass) String() string { } func (SandboxClass) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[3].Descriptor() + return file_ateapi_proto_enumTypes[4].Descriptor() } func (SandboxClass) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[3] + return &file_ateapi_proto_enumTypes[4] } func (x SandboxClass) Number() protoreflect.EnumNumber { @@ -257,7 +313,7 @@ func (x SandboxClass) Number() protoreflect.EnumNumber { // Deprecated: Use SandboxClass.Descriptor instead. func (SandboxClass) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{3} + return file_ateapi_proto_rawDescGZIP(), []int{4} } // ResumeSource selects what supplies the guest state when an actor is resumed @@ -299,11 +355,11 @@ func (x ResumeSource) String() string { } func (ResumeSource) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[4].Descriptor() + return file_ateapi_proto_enumTypes[5].Descriptor() } func (ResumeSource) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[4] + return &file_ateapi_proto_enumTypes[5] } func (x ResumeSource) Number() protoreflect.EnumNumber { @@ -312,7 +368,7 @@ func (x ResumeSource) Number() protoreflect.EnumNumber { // Deprecated: Use ResumeSource.Descriptor instead. func (ResumeSource) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{4} + return file_ateapi_proto_rawDescGZIP(), []int{5} } // ActorMetadataField selects one identity field of the actor. @@ -352,11 +408,11 @@ func (x ActorMetadataField) String() string { } func (ActorMetadataField) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[5].Descriptor() + return file_ateapi_proto_enumTypes[6].Descriptor() } func (ActorMetadataField) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[5] + return &file_ateapi_proto_enumTypes[6] } func (x ActorMetadataField) Number() protoreflect.EnumNumber { @@ -365,7 +421,7 @@ func (x ActorMetadataField) Number() protoreflect.EnumNumber { // Deprecated: Use ActorMetadataField.Descriptor instead. func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5} + return file_ateapi_proto_rawDescGZIP(), []int{6} } // Purpose of the certificate. Used to distinguish between system components @@ -403,11 +459,11 @@ func (x ActorCertificatePurpose) String() string { } func (ActorCertificatePurpose) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[6].Descriptor() + return file_ateapi_proto_enumTypes[7].Descriptor() } func (ActorCertificatePurpose) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[6] + return &file_ateapi_proto_enumTypes[7] } func (x ActorCertificatePurpose) Number() protoreflect.EnumNumber { @@ -416,7 +472,7 @@ func (x ActorCertificatePurpose) Number() protoreflect.EnumNumber { // Deprecated: Use ActorCertificatePurpose.Descriptor instead. func (ActorCertificatePurpose) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6} + return file_ateapi_proto_rawDescGZIP(), []int{7} } type WorkerState int32 @@ -454,11 +510,11 @@ func (x WorkerState) String() string { } func (WorkerState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[7].Descriptor() + return file_ateapi_proto_enumTypes[8].Descriptor() } func (WorkerState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[7] + return &file_ateapi_proto_enumTypes[8] } func (x WorkerState) Number() protoreflect.EnumNumber { @@ -467,7 +523,7 @@ func (x WorkerState) Number() protoreflect.EnumNumber { // Deprecated: Use WorkerState.Descriptor instead. func (WorkerState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{8} } type ExternalVolume_Status int32 @@ -509,11 +565,11 @@ func (x ExternalVolume_Status) String() string { } func (ExternalVolume_Status) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[8].Descriptor() + return file_ateapi_proto_enumTypes[9].Descriptor() } func (ExternalVolume_Status) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[8] + return &file_ateapi_proto_enumTypes[9] } func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { @@ -900,6 +956,25 @@ type ExternalVolume struct { // +k8s:eachKey=+k8s:maxLength=128 // +k8s:eachVal=+k8s:maxLength=256 VolumeContext map[string]string `protobuf:"bytes,5,rep,name=volume_context,json=volumeContext,proto3" json:"volume_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // publish_context contains metadata returned by the CSI driver when the + // volume was attached to a node, needed by the node plugin to complete mounting. + // + // +k8s:optional + // +k8s:maxProperties=32 + // +k8s:eachKey=+k8s:maxLength=128 + // +k8s:eachVal=+k8s:maxLength=1024 + PublishContext map[string]string `protobuf:"bytes,6,rep,name=publish_context,json=publishContext,proto3" json:"publish_context,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // publish_context_node is the node name the volume was attached to. + // + // +k8s:optional + // +k8s:maxLength=253 + PublishContextNode string `protobuf:"bytes,7,opt,name=publish_context_node,json=publishContextNode,proto3" json:"publish_context_node,omitempty"` + // access_mode specifies how the volume should be accessed. + // If unspecified, defaults to VOLUME_ACCESS_MODE_READ_WRITE_ONCE. + // + // +k8s:optional + // +k8s:maximum=3 # keep this in sync with the VolumeAccessMode enum + AccessMode VolumeAccessMode `protobuf:"varint,8,opt,name=access_mode,json=accessMode,proto3,enum=ateapi.VolumeAccessMode" json:"access_mode,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -969,6 +1044,27 @@ func (x *ExternalVolume) GetVolumeContext() map[string]string { return nil } +func (x *ExternalVolume) GetPublishContext() map[string]string { + if x != nil { + return x.PublishContext + } + return nil +} + +func (x *ExternalVolume) GetPublishContextNode() string { + if x != nil { + return x.PublishContextNode + } + return "" +} + +func (x *ExternalVolume) GetAccessMode() VolumeAccessMode { + if x != nil { + return x.AccessMode + } + return VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED +} + type Actor struct { state protoimpl.MessageState `protogen:"open.v1"` // Common resource metadata: atespace, name, uid, version, timestamps. @@ -3258,8 +3354,14 @@ type ExternalVolumeTemplate struct { // +k8s:required // +k8s:format=k8s-long-name StorageClassName string `protobuf:"bytes,2,opt,name=storage_class_name,json=storageClassName,proto3" json:"storage_class_name,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // access_mode specifies the access mode for the volume. + // If unspecified, defaults to VOLUME_ACCESS_MODE_READ_WRITE_ONCE. + // + // +k8s:optional + // +k8s:maximum=3 # keep this in sync with the VolumeAccessMode enum + AccessMode VolumeAccessMode `protobuf:"varint,3,opt,name=access_mode,json=accessMode,proto3,enum=ateapi.VolumeAccessMode" json:"access_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ExternalVolumeTemplate) Reset() { @@ -3306,6 +3408,13 @@ func (x *ExternalVolumeTemplate) GetStorageClassName() string { return "" } +func (x *ExternalVolumeTemplate) GetAccessMode() VolumeAccessMode { + if x != nil { + return x.AccessMode + } + return VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED +} + // SystemInfoVolumeSource is a read-only volume of substrate-generated // per-actor files (identity fields, projected trust bundles), regenerated by // atelet on every Run/Restore. @@ -6869,7 +6978,7 @@ 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\"\xa8\x03\n" + + "updateTime\"\xad\x05\n" + "\x0eExternalVolume\x12\x1f\n" + "\vvolume_name\x18\x01 \x01(\tR\n" + "volumeName\x12*\n" + @@ -6877,9 +6986,16 @@ const file_ateapi_proto_rawDesc = "" + "\vvolume_type\x18\x03 \x01(\tR\n" + "volumeType\x125\n" + "\x06status\x18\x04 \x01(\x0e2\x1d.ateapi.ExternalVolume.StatusR\x06status\x12P\n" + - "\x0evolume_context\x18\x05 \x03(\v2).ateapi.ExternalVolume.VolumeContextEntryR\rvolumeContext\x1a@\n" + + "\x0evolume_context\x18\x05 \x03(\v2).ateapi.ExternalVolume.VolumeContextEntryR\rvolumeContext\x12S\n" + + "\x0fpublish_context\x18\x06 \x03(\v2*.ateapi.ExternalVolume.PublishContextEntryR\x0epublishContext\x120\n" + + "\x14publish_context_node\x18\a \x01(\tR\x12publishContextNode\x129\n" + + "\vaccess_mode\x18\b \x01(\x0e2\x18.ateapi.VolumeAccessModeR\n" + + "accessMode\x1a@\n" + "\x12VolumeContextEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aA\n" + + "\x13PublishContextEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"]\n" + "\x06Status\x12\x16\n" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" + @@ -7013,10 +7129,12 @@ const file_ateapi_proto_rawDesc = "" + "\x05image\x18\x06 \x01(\v2\x19.ateapi.ImageVolumeSourceR\x05image\"1\n" + "\x11ImageVolumeSource\x12\x1c\n" + "\treference\x18\x01 \x01(\tR\treference\"\x18\n" + - "\x16DurableDirVolumeSource\"b\n" + + "\x16DurableDirVolumeSource\"\x9d\x01\n" + "\x16ExternalVolumeTemplate\x12\x1a\n" + "\bcapacity\x18\x01 \x01(\tR\bcapacity\x12,\n" + - "\x12storage_class_name\x18\x02 \x01(\tR\x10storageClassName\"Y\n" + + "\x12storage_class_name\x18\x02 \x01(\tR\x10storageClassName\x129\n" + + "\vaccess_mode\x18\x03 \x01(\x0e2\x18.ateapi.VolumeAccessModeR\n" + + "accessMode\"Y\n" + "\x16SystemInfoVolumeSource\x12?\n" + "\fdata_sources\x18\x01 \x03(\v2\x1c.ateapi.SystemInfoDataSourceR\vdataSources\"\xa0\x01\n" + "\x14SystemInfoDataSource\x12F\n" + @@ -7204,7 +7322,12 @@ const file_ateapi_proto_rawDesc = "" + "\bTagScope\x12\x19\n" + "\x15TAG_SCOPE_UNSPECIFIED\x10\x00\x12\x16\n" + "\x12TAG_SCOPE_ATESPACE\x10\x01\x12\x17\n" + - "\x13TAG_SCOPE_PUBLISHED\x10\x02*\xf7\x01\n" + + "\x13TAG_SCOPE_PUBLISHED\x10\x02*\xad\x01\n" + + "\x10VolumeAccessMode\x12\"\n" + + "\x1eVOLUME_ACCESS_MODE_UNSPECIFIED\x10\x00\x12&\n" + + "\"VOLUME_ACCESS_MODE_READ_WRITE_ONCE\x10\x01\x12%\n" + + "!VOLUME_ACCESS_MODE_READ_ONLY_MANY\x10\x02\x12&\n" + + "\"VOLUME_ACCESS_MODE_READ_WRITE_MANY\x10\x03*\xf7\x01\n" + "\n" + "ActorState\x12\x1b\n" + "\x17ACTOR_STATE_UNSPECIFIED\x10\x00\x12\x18\n" + @@ -7288,318 +7411,323 @@ func file_ateapi_proto_rawDescGZIP() []byte { return file_ateapi_proto_rawDescData } -var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 9) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 99) +var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 10) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 100) var file_ateapi_proto_goTypes = []any{ (SnapshotContentScope)(0), // 0: ateapi.SnapshotContentScope (TagScope)(0), // 1: ateapi.TagScope - (ActorState)(0), // 2: ateapi.ActorState - (SandboxClass)(0), // 3: ateapi.SandboxClass - (ResumeSource)(0), // 4: ateapi.ResumeSource - (ActorMetadataField)(0), // 5: ateapi.ActorMetadataField - (ActorCertificatePurpose)(0), // 6: ateapi.ActorCertificatePurpose - (WorkerState)(0), // 7: ateapi.WorkerState - (ExternalVolume_Status)(0), // 8: ateapi.ExternalVolume.Status - (*ExternalSnapshot)(nil), // 9: ateapi.ExternalSnapshot - (*LocalSnapshotInfo)(nil), // 10: ateapi.LocalSnapshotInfo - (*Selector)(nil), // 11: ateapi.Selector - (*ResourceMetadata)(nil), // 12: ateapi.ResourceMetadata - (*ExternalVolume)(nil), // 13: ateapi.ExternalVolume - (*Actor)(nil), // 14: ateapi.Actor - (*EgressPolicy)(nil), // 15: ateapi.EgressPolicy - (*EgressRule)(nil), // 16: ateapi.EgressRule - (*HostnameRule)(nil), // 17: ateapi.HostnameRule - (*CIDRRule)(nil), // 18: ateapi.CIDRRule - (*EgressRuleEffects)(nil), // 19: ateapi.EgressRuleEffects - (*CredentialHeaderInjection)(nil), // 20: ateapi.CredentialHeaderInjection - (*ActorStatus)(nil), // 21: ateapi.ActorStatus - (*WorkerAssignment)(nil), // 22: ateapi.WorkerAssignment - (*TagStatus)(nil), // 23: ateapi.TagStatus - (*Tag)(nil), // 24: ateapi.Tag - (*Atespace)(nil), // 25: ateapi.Atespace - (*ObjectRef)(nil), // 26: ateapi.ObjectRef - (*ActorTemplate)(nil), // 27: ateapi.ActorTemplate - (*Resources)(nil), // 28: ateapi.Resources - (*Limits)(nil), // 29: ateapi.Limits - (*GoldenSnapshotStatus)(nil), // 30: ateapi.GoldenSnapshotStatus - (*ActorTemplateStatus)(nil), // 31: ateapi.ActorTemplateStatus - (*SandboxConfig)(nil), // 32: ateapi.SandboxConfig - (*SnapshotsConfig)(nil), // 33: ateapi.SnapshotsConfig - (*OnResumeConfig)(nil), // 34: ateapi.OnResumeConfig - (*Container)(nil), // 35: ateapi.Container - (*SecurityContext)(nil), // 36: ateapi.SecurityContext - (*Capabilities)(nil), // 37: ateapi.Capabilities - (*EnvVar)(nil), // 38: ateapi.EnvVar - (*ContainerReadyz)(nil), // 39: ateapi.ContainerReadyz - (*HTTPGetAction)(nil), // 40: ateapi.HTTPGetAction - (*Volume)(nil), // 41: ateapi.Volume - (*ImageVolumeSource)(nil), // 42: ateapi.ImageVolumeSource - (*DurableDirVolumeSource)(nil), // 43: ateapi.DurableDirVolumeSource - (*ExternalVolumeTemplate)(nil), // 44: ateapi.ExternalVolumeTemplate - (*SystemInfoVolumeSource)(nil), // 45: ateapi.SystemInfoVolumeSource - (*SystemInfoDataSource)(nil), // 46: ateapi.SystemInfoDataSource - (*ActorMetadataDataSource)(nil), // 47: ateapi.ActorMetadataDataSource - (*ActorMetadataItem)(nil), // 48: ateapi.ActorMetadataItem - (*TrustBundleDataSource)(nil), // 49: ateapi.TrustBundleDataSource - (*VolumeMount)(nil), // 50: ateapi.VolumeMount - (*CreateAtespaceRequest)(nil), // 51: ateapi.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 52: ateapi.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 53: ateapi.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 54: ateapi.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 55: ateapi.DeleteAtespaceRequest - (*CreateActorTemplateRequest)(nil), // 56: ateapi.CreateActorTemplateRequest - (*GetActorTemplateRequest)(nil), // 57: ateapi.GetActorTemplateRequest - (*ListActorTemplatesRequest)(nil), // 58: ateapi.ListActorTemplatesRequest - (*ListActorTemplatesResponse)(nil), // 59: ateapi.ListActorTemplatesResponse - (*DeleteActorTemplateRequest)(nil), // 60: ateapi.DeleteActorTemplateRequest - (*GetActorRequest)(nil), // 61: ateapi.GetActorRequest - (*CreateActorRequest)(nil), // 62: ateapi.CreateActorRequest - (*UpdateActorRequest)(nil), // 63: ateapi.UpdateActorRequest - (*SuspendActorRequest)(nil), // 64: ateapi.SuspendActorRequest - (*SuspendActorResponse)(nil), // 65: ateapi.SuspendActorResponse - (*PauseActorRequest)(nil), // 66: ateapi.PauseActorRequest - (*PauseActorResponse)(nil), // 67: ateapi.PauseActorResponse - (*ResumeActorRequest)(nil), // 68: ateapi.ResumeActorRequest - (*ResumeActorResponse)(nil), // 69: ateapi.ResumeActorResponse - (*DeleteActorRequest)(nil), // 70: ateapi.DeleteActorRequest - (*GetActorEgressPolicyRequest)(nil), // 71: ateapi.GetActorEgressPolicyRequest - (*CreateActorEgressPolicyRequest)(nil), // 72: ateapi.CreateActorEgressPolicyRequest - (*UpdateActorEgressPolicyRequest)(nil), // 73: ateapi.UpdateActorEgressPolicyRequest - (*DeleteActorEgressPolicyRequest)(nil), // 74: ateapi.DeleteActorEgressPolicyRequest - (*GetTagRequest)(nil), // 75: ateapi.GetTagRequest - (*MintActorJWTRequest)(nil), // 76: ateapi.MintActorJWTRequest - (*MintActorJWTResponse)(nil), // 77: ateapi.MintActorJWTResponse - (*MintActorCertificateRequest)(nil), // 78: ateapi.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 79: ateapi.MintActorCertificateResponse - (*GetActorSnapshotRequest)(nil), // 80: ateapi.GetActorSnapshotRequest - (*GetActorSnapshotTagRequest)(nil), // 81: ateapi.GetActorSnapshotTagRequest - (*ListTagsRequest)(nil), // 82: ateapi.ListTagsRequest - (*ListTagsResponse)(nil), // 83: ateapi.ListTagsResponse - (*CreateTagRequest)(nil), // 84: ateapi.CreateTagRequest - (*UpdateTagRequest)(nil), // 85: ateapi.UpdateTagRequest - (*DeleteTagRequest)(nil), // 86: ateapi.DeleteTagRequest - (*DeleteOptions)(nil), // 87: ateapi.DeleteOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 88: ateapi.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 89: ateapi.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 90: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 91: ateapi.ListWorkersResponse - (*GetWorkerRequest)(nil), // 92: ateapi.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 93: ateapi.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 94: ateapi.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 95: ateapi.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 96: ateapi.DrainWorkerRequest - (*ListActorsRequest)(nil), // 97: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 98: ateapi.ListActorsResponse - (*Worker)(nil), // 99: ateapi.Worker - (*WorkerStatus)(nil), // 100: ateapi.WorkerStatus - (*WorkerResources)(nil), // 101: ateapi.WorkerResources - (*ActorAssignment)(nil), // 102: ateapi.ActorAssignment - (*SetWorkerCapacityRequest)(nil), // 103: ateapi.SetWorkerCapacityRequest - (*SetWorkerCapacityResponse)(nil), // 104: ateapi.SetWorkerCapacityResponse - nil, // 105: ateapi.Selector.MatchLabelsEntry - nil, // 106: ateapi.ExternalVolume.VolumeContextEntry - nil, // 107: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 108: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 109: google.protobuf.Empty + (VolumeAccessMode)(0), // 2: ateapi.VolumeAccessMode + (ActorState)(0), // 3: ateapi.ActorState + (SandboxClass)(0), // 4: ateapi.SandboxClass + (ResumeSource)(0), // 5: ateapi.ResumeSource + (ActorMetadataField)(0), // 6: ateapi.ActorMetadataField + (ActorCertificatePurpose)(0), // 7: ateapi.ActorCertificatePurpose + (WorkerState)(0), // 8: ateapi.WorkerState + (ExternalVolume_Status)(0), // 9: ateapi.ExternalVolume.Status + (*ExternalSnapshot)(nil), // 10: ateapi.ExternalSnapshot + (*LocalSnapshotInfo)(nil), // 11: ateapi.LocalSnapshotInfo + (*Selector)(nil), // 12: ateapi.Selector + (*ResourceMetadata)(nil), // 13: ateapi.ResourceMetadata + (*ExternalVolume)(nil), // 14: ateapi.ExternalVolume + (*Actor)(nil), // 15: ateapi.Actor + (*EgressPolicy)(nil), // 16: ateapi.EgressPolicy + (*EgressRule)(nil), // 17: ateapi.EgressRule + (*HostnameRule)(nil), // 18: ateapi.HostnameRule + (*CIDRRule)(nil), // 19: ateapi.CIDRRule + (*EgressRuleEffects)(nil), // 20: ateapi.EgressRuleEffects + (*CredentialHeaderInjection)(nil), // 21: ateapi.CredentialHeaderInjection + (*ActorStatus)(nil), // 22: ateapi.ActorStatus + (*WorkerAssignment)(nil), // 23: ateapi.WorkerAssignment + (*TagStatus)(nil), // 24: ateapi.TagStatus + (*Tag)(nil), // 25: ateapi.Tag + (*Atespace)(nil), // 26: ateapi.Atespace + (*ObjectRef)(nil), // 27: ateapi.ObjectRef + (*ActorTemplate)(nil), // 28: ateapi.ActorTemplate + (*Resources)(nil), // 29: ateapi.Resources + (*Limits)(nil), // 30: ateapi.Limits + (*GoldenSnapshotStatus)(nil), // 31: ateapi.GoldenSnapshotStatus + (*ActorTemplateStatus)(nil), // 32: ateapi.ActorTemplateStatus + (*SandboxConfig)(nil), // 33: ateapi.SandboxConfig + (*SnapshotsConfig)(nil), // 34: ateapi.SnapshotsConfig + (*OnResumeConfig)(nil), // 35: ateapi.OnResumeConfig + (*Container)(nil), // 36: ateapi.Container + (*SecurityContext)(nil), // 37: ateapi.SecurityContext + (*Capabilities)(nil), // 38: ateapi.Capabilities + (*EnvVar)(nil), // 39: ateapi.EnvVar + (*ContainerReadyz)(nil), // 40: ateapi.ContainerReadyz + (*HTTPGetAction)(nil), // 41: ateapi.HTTPGetAction + (*Volume)(nil), // 42: ateapi.Volume + (*ImageVolumeSource)(nil), // 43: ateapi.ImageVolumeSource + (*DurableDirVolumeSource)(nil), // 44: ateapi.DurableDirVolumeSource + (*ExternalVolumeTemplate)(nil), // 45: ateapi.ExternalVolumeTemplate + (*SystemInfoVolumeSource)(nil), // 46: ateapi.SystemInfoVolumeSource + (*SystemInfoDataSource)(nil), // 47: ateapi.SystemInfoDataSource + (*ActorMetadataDataSource)(nil), // 48: ateapi.ActorMetadataDataSource + (*ActorMetadataItem)(nil), // 49: ateapi.ActorMetadataItem + (*TrustBundleDataSource)(nil), // 50: ateapi.TrustBundleDataSource + (*VolumeMount)(nil), // 51: ateapi.VolumeMount + (*CreateAtespaceRequest)(nil), // 52: ateapi.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 53: ateapi.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 54: ateapi.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 55: ateapi.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 56: ateapi.DeleteAtespaceRequest + (*CreateActorTemplateRequest)(nil), // 57: ateapi.CreateActorTemplateRequest + (*GetActorTemplateRequest)(nil), // 58: ateapi.GetActorTemplateRequest + (*ListActorTemplatesRequest)(nil), // 59: ateapi.ListActorTemplatesRequest + (*ListActorTemplatesResponse)(nil), // 60: ateapi.ListActorTemplatesResponse + (*DeleteActorTemplateRequest)(nil), // 61: ateapi.DeleteActorTemplateRequest + (*GetActorRequest)(nil), // 62: ateapi.GetActorRequest + (*CreateActorRequest)(nil), // 63: ateapi.CreateActorRequest + (*UpdateActorRequest)(nil), // 64: ateapi.UpdateActorRequest + (*SuspendActorRequest)(nil), // 65: ateapi.SuspendActorRequest + (*SuspendActorResponse)(nil), // 66: ateapi.SuspendActorResponse + (*PauseActorRequest)(nil), // 67: ateapi.PauseActorRequest + (*PauseActorResponse)(nil), // 68: ateapi.PauseActorResponse + (*ResumeActorRequest)(nil), // 69: ateapi.ResumeActorRequest + (*ResumeActorResponse)(nil), // 70: ateapi.ResumeActorResponse + (*DeleteActorRequest)(nil), // 71: ateapi.DeleteActorRequest + (*GetActorEgressPolicyRequest)(nil), // 72: ateapi.GetActorEgressPolicyRequest + (*CreateActorEgressPolicyRequest)(nil), // 73: ateapi.CreateActorEgressPolicyRequest + (*UpdateActorEgressPolicyRequest)(nil), // 74: ateapi.UpdateActorEgressPolicyRequest + (*DeleteActorEgressPolicyRequest)(nil), // 75: ateapi.DeleteActorEgressPolicyRequest + (*GetTagRequest)(nil), // 76: ateapi.GetTagRequest + (*MintActorJWTRequest)(nil), // 77: ateapi.MintActorJWTRequest + (*MintActorJWTResponse)(nil), // 78: ateapi.MintActorJWTResponse + (*MintActorCertificateRequest)(nil), // 79: ateapi.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 80: ateapi.MintActorCertificateResponse + (*GetActorSnapshotRequest)(nil), // 81: ateapi.GetActorSnapshotRequest + (*GetActorSnapshotTagRequest)(nil), // 82: ateapi.GetActorSnapshotTagRequest + (*ListTagsRequest)(nil), // 83: ateapi.ListTagsRequest + (*ListTagsResponse)(nil), // 84: ateapi.ListTagsResponse + (*CreateTagRequest)(nil), // 85: ateapi.CreateTagRequest + (*UpdateTagRequest)(nil), // 86: ateapi.UpdateTagRequest + (*DeleteTagRequest)(nil), // 87: ateapi.DeleteTagRequest + (*DeleteOptions)(nil), // 88: ateapi.DeleteOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 89: ateapi.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 90: ateapi.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 91: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 92: ateapi.ListWorkersResponse + (*GetWorkerRequest)(nil), // 93: ateapi.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 94: ateapi.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 95: ateapi.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 96: ateapi.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 97: ateapi.DrainWorkerRequest + (*ListActorsRequest)(nil), // 98: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 99: ateapi.ListActorsResponse + (*Worker)(nil), // 100: ateapi.Worker + (*WorkerStatus)(nil), // 101: ateapi.WorkerStatus + (*WorkerResources)(nil), // 102: ateapi.WorkerResources + (*ActorAssignment)(nil), // 103: ateapi.ActorAssignment + (*SetWorkerCapacityRequest)(nil), // 104: ateapi.SetWorkerCapacityRequest + (*SetWorkerCapacityResponse)(nil), // 105: ateapi.SetWorkerCapacityResponse + nil, // 106: ateapi.Selector.MatchLabelsEntry + nil, // 107: ateapi.ExternalVolume.VolumeContextEntry + nil, // 108: ateapi.ExternalVolume.PublishContextEntry + nil, // 109: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 110: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 111: google.protobuf.Empty } var file_ateapi_proto_depIdxs = []int32{ 0, // 0: ateapi.ExternalSnapshot.content_scope:type_name -> ateapi.SnapshotContentScope 0, // 1: ateapi.LocalSnapshotInfo.content_scope:type_name -> ateapi.SnapshotContentScope - 105, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 108, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 108, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 8, // 5: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status - 106, // 6: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry - 12, // 7: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata - 26, // 8: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef - 11, // 9: ateapi.Actor.worker_selector:type_name -> ateapi.Selector - 26, // 10: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef - 21, // 11: ateapi.Actor.status:type_name -> ateapi.ActorStatus - 12, // 12: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata - 16, // 13: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule - 17, // 14: ateapi.EgressRule.hostnames:type_name -> ateapi.HostnameRule - 18, // 15: ateapi.EgressRule.cidrs:type_name -> ateapi.CIDRRule - 109, // 16: ateapi.EgressRule.all:type_name -> google.protobuf.Empty - 19, // 17: ateapi.HostnameRule.effects:type_name -> ateapi.EgressRuleEffects - 20, // 18: ateapi.EgressRuleEffects.inject_static_headers:type_name -> ateapi.CredentialHeaderInjection - 2, // 19: ateapi.ActorStatus.state:type_name -> ateapi.ActorState - 22, // 20: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment - 9, // 21: ateapi.ActorStatus.external_snapshot:type_name -> ateapi.ExternalSnapshot - 10, // 22: ateapi.ActorStatus.local_snapshot_info:type_name -> ateapi.LocalSnapshotInfo - 13, // 23: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume - 26, // 24: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef - 9, // 25: ateapi.TagStatus.snapshot:type_name -> ateapi.ExternalSnapshot - 12, // 26: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata - 23, // 27: ateapi.Tag.status:type_name -> ateapi.TagStatus - 1, // 28: ateapi.Tag.scope:type_name -> ateapi.TagScope - 26, // 29: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef - 12, // 30: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 12, // 31: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata - 11, // 32: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector - 35, // 33: ateapi.ActorTemplate.containers:type_name -> ateapi.Container - 41, // 34: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume - 33, // 35: ateapi.ActorTemplate.snapshots_config:type_name -> ateapi.SnapshotsConfig - 32, // 36: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig - 28, // 37: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources - 31, // 38: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus - 29, // 39: ateapi.Resources.limits:type_name -> ateapi.Limits - 26, // 40: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef - 108, // 41: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp - 30, // 42: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus - 3, // 43: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass - 0, // 44: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope - 0, // 45: ateapi.SnapshotsConfig.on_commit:type_name -> ateapi.SnapshotContentScope - 34, // 46: ateapi.SnapshotsConfig.on_resume:type_name -> ateapi.OnResumeConfig - 4, // 47: ateapi.OnResumeConfig.from_data:type_name -> ateapi.ResumeSource - 38, // 48: ateapi.Container.env:type_name -> ateapi.EnvVar - 39, // 49: ateapi.Container.readyz:type_name -> ateapi.ContainerReadyz - 50, // 50: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount - 36, // 51: ateapi.Container.security_context:type_name -> ateapi.SecurityContext - 28, // 52: ateapi.Container.resources:type_name -> ateapi.Resources - 37, // 53: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities - 40, // 54: ateapi.ContainerReadyz.http_get:type_name -> ateapi.HTTPGetAction - 43, // 55: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource - 44, // 56: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate - 45, // 57: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource - 42, // 58: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource - 46, // 59: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource - 47, // 60: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource - 49, // 61: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource - 48, // 62: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem - 5, // 63: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField - 25, // 64: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 26, // 65: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 25, // 66: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 26, // 67: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 27, // 68: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate - 26, // 69: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 27, // 70: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate - 26, // 71: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 26, // 72: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 14, // 73: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 14, // 74: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor - 26, // 75: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 14, // 76: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 26, // 77: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 14, // 78: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 26, // 79: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 14, // 80: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 26, // 81: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 26, // 82: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 26, // 83: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 15, // 84: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 26, // 85: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 15, // 86: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 26, // 87: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 26, // 88: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef - 26, // 89: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef - 26, // 90: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 6, // 91: ateapi.MintActorCertificateRequest.purpose:type_name -> ateapi.ActorCertificatePurpose - 26, // 92: ateapi.GetActorSnapshotRequest.actor_snapshot:type_name -> ateapi.ObjectRef - 26, // 93: ateapi.GetActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ObjectRef - 24, // 94: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag - 24, // 95: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag - 24, // 96: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag - 26, // 97: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef - 26, // 98: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef - 102, // 99: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment - 99, // 100: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 26, // 101: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef - 99, // 102: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker - 99, // 103: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker - 26, // 104: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef - 87, // 105: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions - 26, // 106: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef - 14, // 107: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 12, // 108: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata - 107, // 109: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 100, // 110: ateapi.Worker.status:type_name -> ateapi.WorkerStatus - 7, // 111: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState - 101, // 112: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources - 101, // 113: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources - 28, // 114: ateapi.WorkerResources.resources:type_name -> ateapi.Resources - 12, // 115: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata - 26, // 116: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef - 26, // 117: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef - 28, // 118: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources - 26, // 119: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef - 101, // 120: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources - 99, // 121: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker - 61, // 122: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 62, // 123: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 63, // 124: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 64, // 125: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 66, // 126: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 68, // 127: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 70, // 128: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 71, // 129: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest - 72, // 130: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest - 73, // 131: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest - 74, // 132: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest - 76, // 133: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest - 78, // 134: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest - 84, // 135: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest - 75, // 136: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest - 82, // 137: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest - 85, // 138: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest - 86, // 139: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest - 90, // 140: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 92, // 141: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest - 93, // 142: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest - 94, // 143: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest - 95, // 144: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest - 96, // 145: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest - 88, // 146: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest - 97, // 147: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 51, // 148: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 52, // 149: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 53, // 150: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 55, // 151: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 56, // 152: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 57, // 153: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 58, // 154: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 60, // 155: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 103, // 156: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest - 14, // 157: ateapi.Control.GetActor:output_type -> ateapi.Actor - 14, // 158: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 14, // 159: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 65, // 160: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 67, // 161: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 69, // 162: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 14, // 163: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 15, // 164: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 165: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 166: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 15, // 167: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy - 77, // 168: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse - 79, // 169: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse - 24, // 170: ateapi.Control.CreateTag:output_type -> ateapi.Tag - 24, // 171: ateapi.Control.GetTag:output_type -> ateapi.Tag - 83, // 172: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse - 24, // 173: ateapi.Control.UpdateTag:output_type -> ateapi.Tag - 24, // 174: ateapi.Control.DeleteTag:output_type -> ateapi.Tag - 91, // 175: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 99, // 176: ateapi.Control.GetWorker:output_type -> ateapi.Worker - 99, // 177: ateapi.Control.CreateWorker:output_type -> ateapi.Worker - 99, // 178: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker - 99, // 179: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker - 99, // 180: ateapi.Control.DrainWorker:output_type -> ateapi.Worker - 89, // 181: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse - 98, // 182: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 25, // 183: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 25, // 184: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 54, // 185: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 25, // 186: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 27, // 187: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 27, // 188: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 59, // 189: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 27, // 190: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 104, // 191: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse - 157, // [157:192] is the sub-list for method output_type - 122, // [122:157] is the sub-list for method input_type - 122, // [122:122] is the sub-list for extension type_name - 122, // [122:122] is the sub-list for extension extendee - 0, // [0:122] is the sub-list for field type_name + 106, // 2: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 110, // 3: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 110, // 4: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 9, // 5: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status + 107, // 6: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry + 108, // 7: ateapi.ExternalVolume.publish_context:type_name -> ateapi.ExternalVolume.PublishContextEntry + 2, // 8: ateapi.ExternalVolume.access_mode:type_name -> ateapi.VolumeAccessMode + 13, // 9: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata + 27, // 10: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef + 12, // 11: ateapi.Actor.worker_selector:type_name -> ateapi.Selector + 27, // 12: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef + 22, // 13: ateapi.Actor.status:type_name -> ateapi.ActorStatus + 13, // 14: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata + 17, // 15: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule + 18, // 16: ateapi.EgressRule.hostnames:type_name -> ateapi.HostnameRule + 19, // 17: ateapi.EgressRule.cidrs:type_name -> ateapi.CIDRRule + 111, // 18: ateapi.EgressRule.all:type_name -> google.protobuf.Empty + 20, // 19: ateapi.HostnameRule.effects:type_name -> ateapi.EgressRuleEffects + 21, // 20: ateapi.EgressRuleEffects.inject_static_headers:type_name -> ateapi.CredentialHeaderInjection + 3, // 21: ateapi.ActorStatus.state:type_name -> ateapi.ActorState + 23, // 22: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment + 10, // 23: ateapi.ActorStatus.external_snapshot:type_name -> ateapi.ExternalSnapshot + 11, // 24: ateapi.ActorStatus.local_snapshot_info:type_name -> ateapi.LocalSnapshotInfo + 14, // 25: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume + 27, // 26: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef + 10, // 27: ateapi.TagStatus.snapshot:type_name -> ateapi.ExternalSnapshot + 13, // 28: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata + 24, // 29: ateapi.Tag.status:type_name -> ateapi.TagStatus + 1, // 30: ateapi.Tag.scope:type_name -> ateapi.TagScope + 27, // 31: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef + 13, // 32: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 13, // 33: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata + 12, // 34: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector + 36, // 35: ateapi.ActorTemplate.containers:type_name -> ateapi.Container + 42, // 36: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume + 34, // 37: ateapi.ActorTemplate.snapshots_config:type_name -> ateapi.SnapshotsConfig + 33, // 38: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig + 29, // 39: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources + 32, // 40: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus + 30, // 41: ateapi.Resources.limits:type_name -> ateapi.Limits + 27, // 42: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef + 110, // 43: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp + 31, // 44: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus + 4, // 45: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass + 0, // 46: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope + 0, // 47: ateapi.SnapshotsConfig.on_commit:type_name -> ateapi.SnapshotContentScope + 35, // 48: ateapi.SnapshotsConfig.on_resume:type_name -> ateapi.OnResumeConfig + 5, // 49: ateapi.OnResumeConfig.from_data:type_name -> ateapi.ResumeSource + 39, // 50: ateapi.Container.env:type_name -> ateapi.EnvVar + 40, // 51: ateapi.Container.readyz:type_name -> ateapi.ContainerReadyz + 51, // 52: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount + 37, // 53: ateapi.Container.security_context:type_name -> ateapi.SecurityContext + 29, // 54: ateapi.Container.resources:type_name -> ateapi.Resources + 38, // 55: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities + 41, // 56: ateapi.ContainerReadyz.http_get:type_name -> ateapi.HTTPGetAction + 44, // 57: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource + 45, // 58: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate + 46, // 59: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource + 43, // 60: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource + 2, // 61: ateapi.ExternalVolumeTemplate.access_mode:type_name -> ateapi.VolumeAccessMode + 47, // 62: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource + 48, // 63: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource + 50, // 64: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource + 49, // 65: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem + 6, // 66: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField + 26, // 67: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 27, // 68: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 26, // 69: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 27, // 70: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 28, // 71: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate + 27, // 72: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 28, // 73: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate + 27, // 74: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 27, // 75: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 15, // 76: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 15, // 77: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor + 27, // 78: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 15, // 79: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 27, // 80: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 15, // 81: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 27, // 82: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 15, // 83: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 27, // 84: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 27, // 85: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 27, // 86: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 16, // 87: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 27, // 88: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 16, // 89: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 27, // 90: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 27, // 91: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef + 27, // 92: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef + 27, // 93: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 7, // 94: ateapi.MintActorCertificateRequest.purpose:type_name -> ateapi.ActorCertificatePurpose + 27, // 95: ateapi.GetActorSnapshotRequest.actor_snapshot:type_name -> ateapi.ObjectRef + 27, // 96: ateapi.GetActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ObjectRef + 25, // 97: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag + 25, // 98: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag + 25, // 99: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag + 27, // 100: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef + 27, // 101: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef + 103, // 102: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment + 100, // 103: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 27, // 104: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef + 100, // 105: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker + 100, // 106: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker + 27, // 107: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef + 88, // 108: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions + 27, // 109: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef + 15, // 110: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 13, // 111: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata + 109, // 112: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 101, // 113: ateapi.Worker.status:type_name -> ateapi.WorkerStatus + 8, // 114: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState + 102, // 115: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources + 102, // 116: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources + 29, // 117: ateapi.WorkerResources.resources:type_name -> ateapi.Resources + 13, // 118: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata + 27, // 119: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef + 27, // 120: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef + 29, // 121: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources + 27, // 122: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef + 102, // 123: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources + 100, // 124: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker + 62, // 125: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 63, // 126: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 64, // 127: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 65, // 128: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 67, // 129: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 69, // 130: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 71, // 131: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 72, // 132: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest + 73, // 133: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest + 74, // 134: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest + 75, // 135: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest + 77, // 136: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest + 79, // 137: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest + 85, // 138: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest + 76, // 139: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest + 83, // 140: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest + 86, // 141: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest + 87, // 142: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest + 91, // 143: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 93, // 144: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest + 94, // 145: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest + 95, // 146: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest + 96, // 147: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest + 97, // 148: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest + 89, // 149: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest + 98, // 150: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 52, // 151: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 53, // 152: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 54, // 153: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 56, // 154: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 57, // 155: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 58, // 156: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 59, // 157: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 61, // 158: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 104, // 159: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest + 15, // 160: ateapi.Control.GetActor:output_type -> ateapi.Actor + 15, // 161: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 15, // 162: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 66, // 163: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 68, // 164: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 70, // 165: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 15, // 166: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 16, // 167: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy + 16, // 168: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 16, // 169: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 16, // 170: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy + 78, // 171: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse + 80, // 172: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse + 25, // 173: ateapi.Control.CreateTag:output_type -> ateapi.Tag + 25, // 174: ateapi.Control.GetTag:output_type -> ateapi.Tag + 84, // 175: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse + 25, // 176: ateapi.Control.UpdateTag:output_type -> ateapi.Tag + 25, // 177: ateapi.Control.DeleteTag:output_type -> ateapi.Tag + 92, // 178: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 100, // 179: ateapi.Control.GetWorker:output_type -> ateapi.Worker + 100, // 180: ateapi.Control.CreateWorker:output_type -> ateapi.Worker + 100, // 181: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker + 100, // 182: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker + 100, // 183: ateapi.Control.DrainWorker:output_type -> ateapi.Worker + 90, // 184: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse + 99, // 185: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 26, // 186: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 26, // 187: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 55, // 188: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 26, // 189: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 28, // 190: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 28, // 191: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 60, // 192: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 28, // 193: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 105, // 194: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse + 160, // [160:195] is the sub-list for method output_type + 125, // [125:160] is the sub-list for method input_type + 125, // [125:125] is the sub-list for extension type_name + 125, // [125:125] is the sub-list for extension extendee + 0, // [0:125] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -7612,8 +7740,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: 9, - NumMessages: 99, + NumEnums: 10, + NumMessages: 100, NumExtensions: 0, NumServices: 2, }, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index fea0a203ac..2a15b665f2 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -206,6 +206,17 @@ enum TagScope { TAG_SCOPE_PUBLISHED = 2; } +// VolumeAccessMode defines the access modes for external volumes. +enum VolumeAccessMode { + VOLUME_ACCESS_MODE_UNSPECIFIED = 0; + // ReadWriteOnce can be mounted as read-write by a single node. + VOLUME_ACCESS_MODE_READ_WRITE_ONCE = 1; + // ReadOnlyMany can be mounted as read-only by many nodes simultaneously. + VOLUME_ACCESS_MODE_READ_ONLY_MANY = 2; + // ReadWriteMany can be mounted as read-write by many nodes simultaneously. + VOLUME_ACCESS_MODE_READ_WRITE_MANY = 3; +} + // Selector matches worker pools by label. // Only equality-based matching is supported. message Selector { @@ -343,6 +354,28 @@ message ExternalVolume { // +k8s:eachKey=+k8s:maxLength=128 // +k8s:eachVal=+k8s:maxLength=256 map volume_context = 5; + + // publish_context contains metadata returned by the CSI driver when the + // volume was attached to a node, needed by the node plugin to complete mounting. + // + // +k8s:optional + // +k8s:maxProperties=32 + // +k8s:eachKey=+k8s:maxLength=128 + // +k8s:eachVal=+k8s:maxLength=1024 + map publish_context = 6; + + // publish_context_node is the node name the volume was attached to. + // + // +k8s:optional + // +k8s:maxLength=253 + string publish_context_node = 7; + + // access_mode specifies how the volume should be accessed. + // If unspecified, defaults to VOLUME_ACCESS_MODE_READ_WRITE_ONCE. + // + // +k8s:optional + // +k8s:maximum=3 # keep this in sync with the VolumeAccessMode enum + VolumeAccessMode access_mode = 8; } message Actor { @@ -1122,6 +1155,13 @@ message ExternalVolumeTemplate { // +k8s:required // +k8s:format=k8s-long-name string storage_class_name = 2; + + // access_mode specifies the access mode for the volume. + // If unspecified, defaults to VOLUME_ACCESS_MODE_READ_WRITE_ONCE. + // + // +k8s:optional + // +k8s:maximum=3 # keep this in sync with the VolumeAccessMode enum + VolumeAccessMode access_mode = 3; } // SystemInfoVolumeSource is a read-only volume of substrate-generated