From dfc2625ea61df35f3ec84df86fd711f3579b486c Mon Sep 17 00:00:00 2001 From: Anish Gangu Date: Thu, 17 Sep 2026 23:38:46 +0000 Subject: [PATCH 1/5] volume: factor out generic LookupPlugin helper Deduplicate volume plugin lookup logic across cmd/ateapi and cmd/atelet by introducing internal/volume.LookupPlugin. Preserves gRPC NotFound status codes when wrapping lookup failures. --- cmd/ateapi/internal/controlapi/volumes.go | 13 ++- cmd/atelet/volumes.go | 10 +- internal/volume/resolve.go | 39 +++++++ internal/volume/resolve_test.go | 121 ++++++++++++++++++++++ 4 files changed, 170 insertions(+), 13 deletions(-) create mode 100644 internal/volume/resolve.go create mode 100644 internal/volume/resolve_test.go diff --git a/cmd/ateapi/internal/controlapi/volumes.go b/cmd/ateapi/internal/controlapi/volumes.go index 14680245b1..954e225264 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" @@ -109,7 +110,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) } @@ -144,11 +145,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,9 +234,9 @@ 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 { diff --git a/cmd/atelet/volumes.go b/cmd/atelet/volumes.go index 2a1e54cefb..d8ae645b96 100644 --- a/cmd/atelet/volumes.go +++ b/cmd/atelet/volumes.go @@ -40,9 +40,9 @@ 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 { 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 { 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) + } + }) + } +} From 20d76d8da537276c40b527fa4d35c044aad2092d Mon Sep 17 00:00:00 2001 From: Anish Gangu Date: Thu, 17 Sep 2026 23:39:11 +0000 Subject: [PATCH 2/5] csi: harden CSIDriverConfig ControllerEndpoint validation Add OpenAPI regex and length constraints on ControllerEndpoint in the CSIDriverConfig CRD and add runtime URI and port range validation in the CSI client package. --- internal/volume/csi/client.go | 32 ++++++++++++++++ internal/volume/csi/client_test.go | 37 ++++++++++++++++--- .../generated/ate.dev_csidriverconfigs.yaml | 4 +- pkg/api/v1alpha1/csidriverconfig_types.go | 5 ++- 4 files changed, 69 insertions(+), 9 deletions(-) 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/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 From 795a6d911cad8e43026576aff5abceeecf00fbd7 Mon Sep 17 00:00:00 2001 From: Anish Gangu Date: Thu, 17 Sep 2026 23:41:05 +0000 Subject: [PATCH 3/5] volume/csi: query and cache driver capabilities at plugin init Query ControllerGetCapabilities and NodeGetCapabilities once during plugin initialization and cache the results. Guard attach, detach, and staging operations against missing driver capabilities to avoid unnecessary RPC overhead and Unimplemented errors. --- internal/volume/csi/controller.go | 5 + internal/volume/csi/node.go | 5 + internal/volume/csi/plugin.go | 162 ++++++++++++++++++++-------- internal/volume/csi/plugin_test.go | 164 ++++++++++++++++++++++++++++- 4 files changed, 292 insertions(+), 44 deletions(-) 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..9f8aa24de2 100644 --- a/internal/volume/csi/plugin.go +++ b/internal/volume/csi/plugin.go @@ -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,9 +71,63 @@ 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), } } +// SupportsControllerPublish returns true if the CSI driver supports ControllerPublishVolume. +func (p *Plugin) SupportsControllerPublish() bool { + return p.controllerCaps[csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME] +} + +// SupportsNodeStage returns true if the CSI driver supports NodeStageVolume/NodeUnstageVolume. +func (p *Plugin) SupportsNodeStage() bool { + return p.nodeCaps[csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME] +} + +// InitControllerCapabilities queries and caches ControllerGetCapabilities from the 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 _, c := range resp.GetCapabilities() { + if rpc := c.GetRpc(); rpc != nil { + p.controllerCaps[rpc.GetType()] = true + } + } + p.controllerCapsInitialized = true + return nil +} + +// InitNodeCapabilities queries and caches NodeGetCapabilities from the 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 _, c := range resp.GetCapabilities() { + if rpc := c.GetRpc(); rpc != nil { + p.nodeCaps[rpc.GetType()] = true + } + } + p.nodeCapsInitialized = true + return nil +} + // DriverName returns the driver name obtained from the CSI plugin. func (p *Plugin) DriverName(ctx context.Context) (string, error) { resp, err := p.client.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) @@ -123,6 +181,11 @@ func (p *Plugin) DeleteVolume(ctx context.Context, volumeID string) error { // AttachVolume maps to CSI Controller ControllerPublishVolume. func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string) error { + if p.controllerCapsInitialized && !p.SupportsControllerPublish() { + slog.DebugContext(ctx, "Driver does not support ControllerPublishVolume; skipping attach", slog.String("volume_id", volumeID), slog.String("node", node)) + return nil + } + req := &csi.ControllerPublishVolumeRequest{ VolumeId: volumeID, NodeId: node, @@ -132,8 +195,6 @@ func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string) 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 @@ -141,11 +202,6 @@ func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string) return 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() } @@ -155,6 +211,11 @@ func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string) // 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, "Driver does not support ControllerPublishVolume; skipping detach", slog.String("volume_id", volumeID), slog.String("node", node)) + return nil + } + req := &csi.ControllerUnpublishVolumeRequest{ VolumeId: volumeID, NodeId: node, @@ -175,25 +236,28 @@ func (p *Plugin) DetachVolume(ctx context.Context, volumeID string, node string) // 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 { // 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, + StagingTargetPath: stagingPath, + VolumeCapability: getStandardCapabilities()[0], // Use primary capability + 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) + } } } @@ -209,7 +273,7 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st 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,24 +295,26 @@ 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 @@ -322,6 +388,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..6c0077d0ba 100644 --- a/internal/volume/csi/plugin_test.go +++ b/internal/volume/csi/plugin_test.go @@ -41,8 +41,10 @@ type mockCSIDriver struct { nodePublishVolumeFunc func(context.Context, *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) nodeUnpublishVolumeFunc func(context.Context, *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) - getPluginCapabilitiesFunc func(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) - probeFunc func(context.Context, *csi.ProbeRequest) (*csi.ProbeResponse, 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) } func (m *mockCSIDriver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { @@ -109,6 +111,40 @@ func (m *mockCSIDriver) ControllerUnpublishVolume(ctx context.Context, req *csi. return &csi.ControllerUnpublishVolumeResponse{}, 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 (m *mockCSIDriver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { if m.nodeStageVolumeFunc != nil { return m.nodeStageVolumeFunc(ctx, req) @@ -408,3 +444,127 @@ 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"); 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); 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) + } +} From 3d5b4d29fce6ac57ca6856c183836ccb2ca97b85 Mon Sep 17 00:00:00 2001 From: Anish Gangu Date: Thu, 17 Sep 2026 23:43:20 +0000 Subject: [PATCH 4/5] atelet: replace CSIDriverConfig shared informer with direct client Remove the SharedInformerFactory from atelet which ran a persistent cluster-wide watch on CSIDriverConfig CRDs. Replace it with a direct client-go Get cached in memory on first use. --- cmd/atelet/main.go | 37 +++++++++++++++-------------------- cmd/atelet/volumes.go | 2 +- cmd/atelet/volumes_test.go | 32 ++++++++++++++++++++++++++++++ internal/volume/csi/plugin.go | 19 +++++++++++------- 4 files changed, 61 insertions(+), 29 deletions(-) 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 d8ae645b96..278cec71ba 100644 --- a/cmd/atelet/volumes.go +++ b/cmd/atelet/volumes.go @@ -84,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..3d47b75f73 100644 --- a/cmd/atelet/volumes_test.go +++ b/cmd/atelet/volumes_test.go @@ -22,6 +22,9 @@ 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" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type fakeWorkerPlugin struct { @@ -126,3 +129,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/volume/csi/plugin.go b/internal/volume/csi/plugin.go index 9f8aa24de2..e9d13bb63c 100644 --- a/internal/volume/csi/plugin.go +++ b/internal/volume/csi/plugin.go @@ -28,7 +28,6 @@ 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/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -336,17 +335,23 @@ func getStandardCapabilities() []*csi.VolumeCapability { } } +// 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) } From 41c57e98232087d18e0a5d7c008e1eff4665322d Mon Sep 17 00:00:00 2001 From: Anish Gangu Date: Thu, 17 Sep 2026 23:44:24 +0000 Subject: [PATCH 5/5] volume: support configurable access modes and propagate PublishContext Add VolumeAccessMode enum and PublishContext fields to external volume protos. Propagate PublishContext from ControllerPublishVolume through the control-plane actor record to worker node stage and mount operations, and configure CSI volume capabilities according to the requested access mode. --- .../controlapi/functionaltest/actor_test.go | 8 +- cmd/ateapi/internal/controlapi/volumes.go | 7 + .../internal/controlapi/volumes_test.go | 2 + .../internal/controlapi/workflow_resume.go | 56 +- .../internal/controlapi/workload_spec.go | 8 + .../internal/controlapi/workload_spec_test.go | 5 + .../controlapi/zz_generated.validation.go | 137 +++ cmd/atelet/volumes.go | 2 +- cmd/atelet/volumes_test.go | 3 +- internal/proto/ateletpb/atelet.pb.go | 153 ++-- internal/proto/ateletpb/atelet.proto | 3 + internal/volume/csi/plugin.go | 170 ++-- internal/volume/csi/plugin_test.go | 217 ++++- internal/volume/mock.go | 9 +- internal/volume/plugin.go | 6 +- pkg/proto/ateapipb/ateapi.pb.go | 796 ++++++++++-------- pkg/proto/ateapipb/ateapi.proto | 40 + 17 files changed, 1087 insertions(+), 535 deletions(-) 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 954e225264..9f7e59e550 100644 --- a/cmd/ateapi/internal/controlapi/volumes.go +++ b/cmd/ateapi/internal/controlapi/volumes.go @@ -50,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(), }) } } @@ -126,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 @@ -242,9 +244,14 @@ func detachActorVolumes(ctx context.Context, st detachActorVolumesStore, registr 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/volumes.go b/cmd/atelet/volumes.go index 278cec71ba..36b76fc2e6 100644 --- a/cmd/atelet/volumes.go +++ b/cmd/atelet/volumes.go @@ -44,7 +44,7 @@ func (s *AteomHerder) mountExternalVolumes(ctx context.Context, actorUID string, if err != nil { 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) } } diff --git a/cmd/atelet/volumes_test.go b/cmd/atelet/volumes_test.go index 3d47b75f73..3e1ba54658 100644 --- a/cmd/atelet/volumes_test.go +++ b/cmd/atelet/volumes_test.go @@ -24,6 +24,7 @@ import ( "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" ) @@ -33,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 } 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/plugin.go b/internal/volume/csi/plugin.go index e9d13bb63c..471db7fa10 100644 --- a/internal/volume/csi/plugin.go +++ b/internal/volume/csi/plugin.go @@ -28,6 +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" + "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" @@ -75,58 +76,6 @@ func NewPlugin(client *Client) *Plugin { } } -// SupportsControllerPublish returns true if the CSI driver supports ControllerPublishVolume. -func (p *Plugin) SupportsControllerPublish() bool { - return p.controllerCaps[csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME] -} - -// SupportsNodeStage returns true if the CSI driver supports NodeStageVolume/NodeUnstageVolume. -func (p *Plugin) SupportsNodeStage() bool { - return p.nodeCaps[csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME] -} - -// InitControllerCapabilities queries and caches ControllerGetCapabilities from the 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 _, c := range resp.GetCapabilities() { - if rpc := c.GetRpc(); rpc != nil { - p.controllerCaps[rpc.GetType()] = true - } - } - p.controllerCapsInitialized = true - return nil -} - -// InitNodeCapabilities queries and caches NodeGetCapabilities from the 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 _, c := range resp.GetCapabilities() { - if rpc := c.GetRpc(); rpc != nil { - p.nodeCaps[rpc.GetType()] = true - } - } - p.nodeCapsInitialized = true - return nil -} - // DriverName returns the driver name obtained from the CSI plugin. func (p *Plugin) DriverName(ctx context.Context) (string, error) { resp, err := p.client.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) @@ -178,40 +127,92 @@ 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, "Driver does not support ControllerPublishVolume; skipping attach", slog.String("volume_id", volumeID), slog.String("node", node)) - return nil + 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 { 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) } 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, "Driver does not support ControllerPublishVolume; skipping detach", slog.String("volume_id", volumeID), slog.String("node", node)) + slog.DebugContext(ctx, "CSI driver does not support PUBLISH_UNPUBLISH_VOLUME; skipping detach", slog.String("volume_id", volumeID), slog.String("node", node)) return nil } @@ -233,7 +234,10 @@ 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 := "" if !p.nodeCapsInitialized || p.SupportsNodeStage() { @@ -244,8 +248,9 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st stageReq := &csi.NodeStageVolumeRequest{ VolumeId: volumeID, + PublishContext: publishContext, StagingTargetPath: stagingPath, - VolumeCapability: getStandardCapabilities()[0], // Use primary capability + VolumeCapability: cap, VolumeContext: volumeContext, } @@ -263,9 +268,10 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st // 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 != "" { @@ -319,19 +325,29 @@ func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath 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), } } diff --git a/internal/volume/csi/plugin_test.go b/internal/volume/csi/plugin_test.go index 6c0077d0ba..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,11 +41,11 @@ 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) + + getPluginCapabilitiesFunc func(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) + probeFunc func(context.Context, *csi.ProbeRequest) (*csi.ProbeResponse, error) } func (m *mockCSIDriver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { @@ -111,6 +112,34 @@ func (m *mockCSIDriver) ControllerUnpublishVolume(ctx context.Context, req *csi. return &csi.ControllerUnpublishVolumeResponse{}, nil } +func (m *mockCSIDriver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + if m.nodeStageVolumeFunc != nil { + return m.nodeStageVolumeFunc(ctx, req) + } + return &csi.NodeStageVolumeResponse{}, nil +} + +func (m *mockCSIDriver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { + if m.nodeUnstageVolumeFunc != nil { + return m.nodeUnstageVolumeFunc(ctx, req) + } + return &csi.NodeUnstageVolumeResponse{}, nil +} + +func (m *mockCSIDriver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + if m.nodePublishVolumeFunc != nil { + return m.nodePublishVolumeFunc(ctx, req) + } + return &csi.NodePublishVolumeResponse{}, nil +} + +func (m *mockCSIDriver) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { + if m.nodeUnpublishVolumeFunc != nil { + return m.nodeUnpublishVolumeFunc(ctx, req) + } + 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) @@ -145,34 +174,6 @@ func (m *mockCSIDriver) NodeGetCapabilities(ctx context.Context, req *csi.NodeGe }, nil } -func (m *mockCSIDriver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { - if m.nodeStageVolumeFunc != nil { - return m.nodeStageVolumeFunc(ctx, req) - } - return &csi.NodeStageVolumeResponse{}, nil -} - -func (m *mockCSIDriver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { - if m.nodeUnstageVolumeFunc != nil { - return m.nodeUnstageVolumeFunc(ctx, req) - } - return &csi.NodeUnstageVolumeResponse{}, nil -} - -func (m *mockCSIDriver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { - if m.nodePublishVolumeFunc != nil { - return m.nodePublishVolumeFunc(ctx, req) - } - return &csi.NodePublishVolumeResponse{}, nil -} - -func (m *mockCSIDriver) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { - if m.nodeUnpublishVolumeFunc != nil { - return m.nodeUnpublishVolumeFunc(ctx, req) - } - return &csi.NodeUnpublishVolumeResponse{}, nil -} - func startMockCSIDriver(t *testing.T, driver *mockCSIDriver) (string, func()) { tmpDir, err := os.MkdirTemp("", "csi-test-*") if err != nil { @@ -251,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() @@ -264,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) { @@ -309,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() @@ -320,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) } @@ -341,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") @@ -349,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) } @@ -492,7 +540,7 @@ func TestPlugin_Capabilities_SkipAttachDetachWhenUnsupported(t *testing.T) { } // AttachVolume should skip calling ControllerPublishVolume entirely - if err := plugin.AttachVolume(ctx, "test-vol", "node-1"); err != nil { + 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 { @@ -553,7 +601,7 @@ func TestPlugin_Capabilities_SkipStageUnstageWhenUnsupported(t *testing.T) { } // MountVolume should skip NodeStageVolume entirely - if err := plugin.MountVolume(ctx, "test-vol", targetPath, nil); err != nil { + 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 { @@ -568,3 +616,84 @@ func TestPlugin_Capabilities_SkipStageUnstageWhenUnsupported(t *testing.T) { 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/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