From 7862487b90485c45b5139a65b10243752ef9dbd4 Mon Sep 17 00:00:00 2001 From: Timo Derstappen Date: Fri, 9 Oct 2026 14:25:33 +0200 Subject: [PATCH 1/5] fix(resources): compare repeated message fields with proto.Equal DeepEqual handed a slice of proto messages to reflect.DeepEqual, which compares each message's internal state as well as its fields. The RPC logger's marshal fills the size cache of a request's messages, so a repeated message field and its clone compared unequal, and declarative validation's unchanged-value shortcut then ran the field's update checks: an immutable repeated field failed every create, because the create validates the stored object as an update of the request. A slice of messages is now compared element by element with proto.Equal, a nil and an empty one being the same field value. Signed-off-by: Timo Derstappen --- internal/resources/validate.go | 20 +++++++++++++++++++- internal/resources/validate_test.go | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/internal/resources/validate.go b/internal/resources/validate.go index f39e6dbdf9..d9cdace12d 100644 --- a/internal/resources/validate.go +++ b/internal/resources/validate.go @@ -39,7 +39,10 @@ func ToGRPCStatusError(errs field.ErrorList) error { } // DeepEqual compares two values of any type, using proto.Equal if both are -// proto messages, and reflect.DeepEqual otherwise. Declarative validation's +// proto messages or slices of them (a repeated message field), and +// reflect.DeepEqual otherwise. reflect.DeepEqual must not see a message: it +// compares the message's internal state, such as the size cache a marshal +// fills, so a message and its clone would differ. Declarative validation's // generated code reaches it through each generating package's ateDeepEqual. func DeepEqual[T any](a, b T) bool { asProto := func(x any) proto.Message { @@ -53,9 +56,24 @@ func DeepEqual[T any](a, b T) bool { if pa, pb := asProto(a), asProto(b); pa != nil && pb != nil { return proto.Equal(pa, pb) } + if va, vb := reflect.ValueOf(a), reflect.ValueOf(b); va.Kind() == reflect.Slice && vb.Kind() == reflect.Slice && + va.Type().Elem().Implements(protoMessageType) { + // A nil and an empty repeated field are the same field value. + if va.Len() != vb.Len() { + return false + } + for i := range va.Len() { + if !proto.Equal(va.Index(i).Interface().(proto.Message), vb.Index(i).Interface().(proto.Message)) { + return false + } + } + return true + } return reflect.DeepEqual(a, b) } +var protoMessageType = reflect.TypeFor[proto.Message]() + // ValidateResourceName checks that a string conforms to Agent Substrate's // rules for a resource name, which is a subset of the rules for an RFC-1123 // DNS label. This does not check for zero-length strings, which callers may diff --git a/internal/resources/validate_test.go b/internal/resources/validate_test.go index 01f9f1c0bf..1b98650020 100644 --- a/internal/resources/validate_test.go +++ b/internal/resources/validate_test.go @@ -23,6 +23,7 @@ import ( "github.com/agent-substrate/substrate/pkg/proto/ateapipb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -42,6 +43,9 @@ func TestDeepEqual(t *testing.T) { a := &ateapipb.ObjectRef{Atespace: "a", Name: "x"} b := &ateapipb.ObjectRef{Atespace: "a", Name: "x"} _ = a.String() // populates a's internal state, not b's + // A marshal fills the size cache of every element of a repeated field. + sized := []*ateapipb.ObjectRef{{Atespace: "a", Name: "x"}} + _ = proto.Size(sized[0]) tests := []struct { name string @@ -53,6 +57,10 @@ func TestDeepEqual(t *testing.T) { {name: "nil protos", got: DeepEqual[*ateapipb.ObjectRef](nil, nil), want: true}, {name: "equal non-protos", got: DeepEqual([]string{"a"}, []string{"a"}), want: true}, {name: "different non-protos", got: DeepEqual(1, 2), want: false}, + {name: "equal repeated protos", got: DeepEqual(sized, []*ateapipb.ObjectRef{{Atespace: "a", Name: "x"}}), want: true}, + {name: "different repeated protos", got: DeepEqual(sized, []*ateapipb.ObjectRef{{Atespace: "a", Name: "y"}}), want: false}, + {name: "repeated protos of different lengths", got: DeepEqual(sized, nil), want: false}, + {name: "nil and empty repeated protos", got: DeepEqual([]*ateapipb.ObjectRef{}, nil), want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From cb9ef06e45c4afbf3e68907f33a79fc0bffc209e Mon Sep 17 00:00:00 2001 From: Timo Derstappen Date: Fri, 9 Oct 2026 14:27:49 +0200 Subject: [PATCH 2/5] docs(fork): row for the repeated-message equality fix Signed-off-by: Timo Derstappen --- FORK.md | 1 + 1 file changed, 1 insertion(+) diff --git a/FORK.md b/FORK.md index 8c9bb561ca..fdf36b61e4 100644 --- a/FORK.md +++ b/FORK.md @@ -101,6 +101,7 @@ Everything on `giantswarm` that is not in the pin (`git log v0.4.0-alpha1..giant | The egress gateway's ext-proc names its telemetry `atenet-egress` (`OTEL_SERVICE_NAME` on the `atenet-egress` `ext-proc` container; a chart unit test) | the `atenet` binary hardcodes the service name `atenet-router` for both modes and the container set none, so egress spans and logs carried `service.name=atenet-router` | `6627a636` ([#217](https://github.com/giantswarm/substrate/pull/217), rebase-merged 2026-10-09; first release 1.6.2) | fork-only chart change; upstream's `atenet-egress.yaml` has the same gap, not queued | | The gVisor SandboxConfig's pause image is a chart value (`images.pause`, default upstream's `registry.k8s.io/pause:3.10.2@sha256:f548e0e8…`, rendered with `required`; the unit-test suite `charts/substrate/tests/sandboxconfig_gvisor_test.yaml` covers the default, an override and the empty value; the README row; the preserved kubectl-apply manifest keeps upstream's default) | the one image reference the chart hardcoded, and the one an operator could not move: `registry.k8s.io` redirects every pull to a Google Artifact Registry host, so a cluster whose egress admits only its own registry failed every golden boot while creating the pause OCI bundle, before any snapshot existed ([#224](https://github.com/giantswarm/substrate/issues/224)) | `f844d726` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | to file (prepared): branch [`upstream/sandbox-pause-image-value`](https://github.com/giantswarm/substrate/tree/upstream/sandbox-pause-image-value) here (`1591d20d`, the same commit on kagent-dev `main` @ `9c4b1fb5` of 2026-10-08, DCO signed), the shape of [kagent-dev/substrate#23](https://github.com/kagent-dev/substrate/pull/23) (2026-08-20, closed unmerged 2026-10-06 on a stale base); [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 155 | | The pause image defaults to its gsoci copy: `images.pause` = `gsoci.azurecr.io/giantswarm/pause:3.10.2@sha256:f548e0e8…`, upstream's digest | every Giant Swarm installation pulls from gsoci.azurecr.io, and one whose egress admits only that registry cannot reach `registry.k8s.io`; the same digest, so the snapshots that record it restore unchanged ([#224](https://github.com/giantswarm/substrate/issues/224)) | `dd957c2e` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | **ours to keep**: a Giant Swarm registry is not upstream's default; the upstream-shaped change is the row above | +| `resources.DeepEqual`, the unchanged-value check of the generated declarative validation, compares a slice of proto messages (a repeated message field) element by element with `proto.Equal`, a nil and an empty one being equal | it handed the slice to `reflect.DeepEqual`, which also compares each message's internal state; the RPC logger's marshal fills the size cache, so a repeated message field and its clone differed and an `+k8s:immutable` repeated field (`Actor.shared_volumes`, row below) failed every `CreateActor` with "field is immutable" | pending (`fix(resources): compare repeated message fields with proto.Equal`) | none: upstream `main` has the same `DeepEqual`; to file, queued in the upstream engagement list | Twenty-three patches change Substrate ahead of upstream — egress for an actor while it resumes, without which no skill-carrying agent of the platform boots, the atelet scheduling knobs, the keep policy on the CRD chart's From 55b7fa04912bf4811c7bc5200ce824615e26f59a Mon Sep 17 00:00:00 2001 From: Timo Derstappen Date: Fri, 9 Oct 2026 18:04:37 +0200 Subject: [PATCH 3/5] feat: mount an existing volume per actor at a sub-path Actors that work in one shared workspace need part of a volume that exists outside Substrate and outlives them: their own directory read-write and another directory of the same volume read-only. External volumes were created per actor, attached single-node-writer and deleted with the actor, and a mount had no sub-path or read-only flag. An ActorTemplate now declares an existing volume by name (Volume.existing_volume), and CreateActor supplies it per actor (Actor.existing_volumes: name, CSI driver, volume handle, access mode READ_WRITE_MANY or READ_ONLY_MANY, and the sub-path this actor sees as the volume's root). A VolumeMount gains sub_path and read_only, so one volume may be mounted at several paths. CreateActor refuses a reference to a volume the template does not declare as existing, a driver without a CSIDriverConfig, a handle no PersistentVolume of the driver holds and an access mode the PersistentVolume does not permit; the PersistentVolume's volume attributes are passed to the driver at resume. An existing volume the actor does not supply contributes neither the volume nor its mounts. Substrate never creates, deletes or detaches an existing volume: pause, resume and delete only unmount and mount it. A volume of a multi-node mode is staged once per target, so one actor's unmount never unstages it under another actor on the node. ateom, which sees the published volume and may mount, binds each sub-path before the sandbox starts and releases it when the sandbox stops, on both runtimes: from a descriptor opened beneath the volume's root without following symbolic links, so a link another actor wrote on the volume fails the mount rather than redirecting it, and a missing directory fails it too. An actor with existing volumes boots from its image instead of the template's golden snapshot, which was captured without their mounts, and cannot be created from a tag. The existingvolumes e2e suite runs on kind with the CSI NFS driver. Signed-off-by: Timo Derstappen --- .github/workflows/pr-workflow.yaml | 3 + .../substrate/templates/ate-api-server.yaml | 5 + cmd/ateapi/internal/apivalidation/actor.go | 18 + .../internal/apivalidation/actor_template.go | 47 +- .../apivalidation/actor_template_test.go | 62 + .../internal/apivalidation/actor_test.go | 60 + .../apivalidation/zz_generated.validation.go | 332 ++- .../internal/controlapi/access_policy_test.go | 2 +- cmd/ateapi/internal/controlapi/actor.go | 17 +- .../internal/controlapi/existing_volumes.go | 220 ++ .../controlapi/existing_volumes_test.go | 287 +++ .../controlapi/functionaltest/actor_test.go | 10 +- .../controlapi/functionaltest/common_test.go | 2 + cmd/ateapi/internal/controlapi/service.go | 9 + cmd/ateapi/internal/controlapi/workflow.go | 8 +- .../internal/controlapi/workflow_resume.go | 8 +- .../internal/controlapi/workload_spec.go | 8 + cmd/ateapi/main.go | 4 + cmd/atelet/main.go | 10 +- cmd/atelet/volumes.go | 24 +- cmd/atelet/volumes_test.go | 32 +- cmd/ateom-gvisor/main.go | 12 + cmd/ateom-microvm/checkpoint.go | 10 +- cmd/ateom-microvm/run.go | 6 + docs/csi-volumes.md | 44 + .../existingvolumes/existingvolumes_test.go | 441 ++++ .../suites/existingvolumes/testmain_test.go | 24 + internal/ocispec/ocispec.go | 6 +- internal/ocispec/ocispec_test.go | 5 + internal/proto/ateletpb/atelet.pb.go | 370 ++-- internal/proto/ateletpb/atelet.proto | 15 + internal/proto/ateompb/ateom.pb.go | 32 +- internal/proto/ateompb/ateom.proto | 6 + internal/volume/csi/plugin.go | 47 +- internal/volume/csi/plugin_test.go | 79 +- internal/volume/mock.go | 6 +- internal/volume/plugin.go | 22 +- internal/volumebind/volumebind.go | 159 ++ internal/volumebind/volumebind_test.go | 119 ++ manifests/ate-install/ate-api-server.yaml | 5 + pkg/proto/ateapipb/ateapi.pb.go | 1782 ++++++++++------- pkg/proto/ateapipb/ateapi.proto | 110 +- 42 files changed, 3509 insertions(+), 959 deletions(-) create mode 100644 cmd/ateapi/internal/controlapi/existing_volumes.go create mode 100644 cmd/ateapi/internal/controlapi/existing_volumes_test.go create mode 100644 internal/e2e/suites/existingvolumes/existingvolumes_test.go create mode 100644 internal/e2e/suites/existingvolumes/testmain_test.go create mode 100644 internal/volumebind/volumebind.go create mode 100644 internal/volumebind/volumebind_test.go diff --git a/.github/workflows/pr-workflow.yaml b/.github/workflows/pr-workflow.yaml index 6467be8d44..e9e167014e 100644 --- a/.github/workflows/pr-workflow.yaml +++ b/.github/workflows/pr-workflow.yaml @@ -146,6 +146,9 @@ jobs: # E2E_JUNIT_FILE is set per step, not here: the lanes below share this # job, so one job-level path would have each overwrite the last. ARTIFACTS: ${{ github.workspace }}/_artifacts + # The NFS driver is installed below, so the existingvolumes suite fails + # rather than skips without it. + E2E_CSI_NFS: "1" steps: - name: Checkout uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 diff --git a/charts/substrate/templates/ate-api-server.yaml b/charts/substrate/templates/ate-api-server.yaml index 3b4fdd7119..a132e76807 100644 --- a/charts/substrate/templates/ate-api-server.yaml +++ b/charts/substrate/templates/ate-api-server.yaml @@ -34,6 +34,11 @@ rules: - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "watch", "list"] +# PersistentVolumes: an actor's existing volumes are checked against them at +# CreateActor, and their volume attributes are read at resume. +- apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "watch", "list"] # Secret reads for env source resolution are intentionally NOT granted # cluster-wide here. Each demo / tenant is responsible for granting # ate-api-server read access only to the specific Secrets referenced by its diff --git a/cmd/ateapi/internal/apivalidation/actor.go b/cmd/ateapi/internal/apivalidation/actor.go index 77c0d27a5b..32e8341654 100644 --- a/cmd/ateapi/internal/apivalidation/actor.go +++ b/cmd/ateapi/internal/apivalidation/actor.go @@ -165,3 +165,21 @@ func ValidateCustom_ExternalVolume_StorageVolumeId(_ context.Context, _ operatio } return nil } + +// ValidateCustom_ExistingVolume_Driver checks an existing volume's driver +// with the syntax of ExternalVolume.volume_type. +func ValidateCustom_ExistingVolume_Driver(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList { + return ValidateCustom_ExternalVolume_VolumeType(ctx, op, fldPath, value, oldValue) +} + +// ValidateCustom_ExistingVolume_VolumeHandle checks that an existing +// volume's handle, like a storage volume ID, contains no control characters. +func ValidateCustom_ExistingVolume_VolumeHandle(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList { + return ValidateCustom_ExternalVolume_StorageVolumeId(ctx, op, fldPath, value, oldValue) +} + +// ValidateCustom_ExistingVolume_SubPath checks an existing volume's root +// with the shape of VolumeMount.sub_path. +func ValidateCustom_ExistingVolume_SubPath(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList { + return ValidateCustom_VolumeMount_SubPath(ctx, op, fldPath, value, oldValue) +} diff --git a/cmd/ateapi/internal/apivalidation/actor_template.go b/cmd/ateapi/internal/apivalidation/actor_template.go index 93925735f1..5108667893 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template.go +++ b/cmd/ateapi/internal/apivalidation/actor_template.go @@ -62,11 +62,12 @@ func ValidateActorTemplateUpdate(ctx context.Context, fldPath *field.Path, newVa } // ValidateCustom_CreateActorTemplateRequest_ActorTemplate rejects container -// volume mounts that reference volumes the template does not declare. +// volume mounts that reference volumes the template does not declare, and a +// sub_path or read_only on a mount of any volume but an existing one. func ValidateCustom_CreateActorTemplateRequest_ActorTemplate(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *ateapipb.ActorTemplate) field.ErrorList { - declared := make(map[string]bool, len(value.GetVolumes())) + declared := make(map[string]*ateapipb.Volume, len(value.GetVolumes())) for _, vol := range value.GetVolumes() { - declared[vol.GetName()] = true + declared[vol.GetName()] = vol } var errs field.ErrorList for i, ctr := range value.GetContainers() { @@ -75,10 +76,20 @@ func ValidateCustom_CreateActorTemplateRequest_ActorTemplate(_ context.Context, if name == "" { continue // required is enforced by tags } - if !declared[name] { - errs = append(errs, field.Invalid( - fldPath.Child("containers").Index(i).Child("volume_mounts").Index(j).Child("name"), - name, "must reference a volume declared in the template")) + mountPath := fldPath.Child("containers").Index(i).Child("volume_mounts").Index(j) + vol, ok := declared[name] + if !ok { + errs = append(errs, field.Invalid(mountPath.Child("name"), name, "must reference a volume declared in the template")) + continue + } + if vol.GetExistingVolume() != nil { + continue + } + if mount.GetSubPath() != "" { + errs = append(errs, field.Invalid(mountPath.Child("sub_path"), mount.GetSubPath(), "may be set only on a mount of an existing volume")) + } + if mount.GetReadOnly() { + errs = append(errs, field.Invalid(mountPath.Child("read_only"), true, "may be set only on a mount of an existing volume")) } } } @@ -288,3 +299,25 @@ func ValidateCustom_Capabilities_Add(_ context.Context, _ operation.Operation, f func ValidateCustom_Capabilities_Drop(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []string) field.ErrorList { return validateCapabilities(fldPath, value, true) } + +// ValidateCustom_VolumeMount_SubPath requires a clean relative Unix path: no +// leading '/', no '.' or '..' segments, '//', trailing '/', or control +// characters. +func ValidateCustom_VolumeMount_SubPath(_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList { + p := *value + if p == "" { + return nil + } + bad := strings.HasPrefix(p, "/") || strings.HasSuffix(p, "/") || + strings.Contains(p, "//") || mountPathBadSegmentRE.MatchString(p) + for _, r := range p { + if r < 0x20 || r == 0x7f { + bad = true + break + } + } + if bad { + return field.ErrorList{field.Invalid(fldPath, p, "must be a clean relative Unix path: must not start or end with '/', and contain no '..', '.', '//', or control characters")} + } + return nil +} diff --git a/cmd/ateapi/internal/apivalidation/actor_template_test.go b/cmd/ateapi/internal/apivalidation/actor_template_test.go index aa55942e18..53fb950df1 100644 --- a/cmd/ateapi/internal/apivalidation/actor_template_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_template_test.go @@ -112,6 +112,68 @@ func TestValidateCreateActorTemplateRequest(t *testing.T) { tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "ghost-vol", MountPath: "/var/data"}} })}, field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("name"), "ghost-vol", "")}, + }, { + "existing volume mounted twice, at a sub-path and read-only", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{ + {Name: "workspace", MountPath: "/workspace", SubPath: "sessions/a"}, + {Name: "workspace", MountPath: "/mirrors", SubPath: "mirrors", ReadOnly: true}, + } + })}, + nil, + }, { + "sub_path and read_only on a mount of another kind of volume", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "data", DurableDir: &ateapipb.DurableDirVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "data", MountPath: "/var/data", SubPath: "a", ReadOnly: true}} + })}, + field.ErrorList{ + field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), "a", ""), + field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("read_only"), true, ""), + }, + }, { + "sub_path '/abs'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "/abs"}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, + }, { + "sub_path 'a/'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "a/"}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, + }, { + "sub_path 'a//b'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "a//b"}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, + }, { + "sub_path 'a/../b'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "a/../b"}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, + }, { + "sub_path '.'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "."}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, + }, { + "sub_path 'a\\x01b'", + &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { + tmpl.Volumes = []*ateapipb.Volume{{Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}} + tmpl.Containers[0].VolumeMounts = []*ateapipb.VolumeMount{{Name: "workspace", MountPath: "/workspace", SubPath: "a\x01b"}} + })}, + field.ErrorList{field.Invalid(field.NewPath("actor_template", "containers").Index(0).Child("volume_mounts").Index(0).Child("sub_path"), nil, "")}, }, { "missing snapshot_config", &ateapipb.CreateActorTemplateRequest{ActorTemplate: validActorTemplate(func(tmpl *ateapipb.ActorTemplate) { diff --git a/cmd/ateapi/internal/apivalidation/actor_test.go b/cmd/ateapi/internal/apivalidation/actor_test.go index b886db97f6..cd3ca5f8d4 100644 --- a/cmd/ateapi/internal/apivalidation/actor_test.go +++ b/cmd/ateapi/internal/apivalidation/actor_test.go @@ -152,6 +152,48 @@ func TestValidateCreateActorRequest(t *testing.T) { "invalid actor.source_tag.name", validReq(validActor(withSourceTag("as", "invalid value"))), field.ErrorList{field.Invalid(field.NewPath("actor", "source_tag", "name"), nil, "").WithOrigin("format=k8s-short-name")}, + }, { + "valid existing volume", + validReq(validActor(withExistingVolume(nil))), + nil, + }, { + "existing volume without a driver", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { ev.Driver = "" }))), + field.ErrorList{field.Required(field.NewPath("actor", "existing_volumes").Index(0).Child("driver"), "")}, + }, { + "existing volume with an invalid driver", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { ev.Driver = "Not A Driver" }))), + field.ErrorList{field.Invalid(field.NewPath("actor", "existing_volumes").Index(0).Child("driver"), "Not A Driver", "")}, + }, { + "existing volume without a handle", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { ev.VolumeHandle = "" }))), + field.ErrorList{field.Required(field.NewPath("actor", "existing_volumes").Index(0).Child("volume_handle"), "")}, + }, { + "existing volume handle with a control character", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { ev.VolumeHandle = "a\x01b" }))), + field.ErrorList{field.Invalid(field.NewPath("actor", "existing_volumes").Index(0).Child("volume_handle"), "a\x01b", "")}, + }, { + "existing volume read-only many", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { + ev.AccessMode = ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY + }))), + nil, + }, { + "existing volume single-node", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { + ev.AccessMode = ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE + }))), + field.ErrorList{field.Invalid(field.NewPath("actor", "existing_volumes").Index(0).Child("access_mode"), nil, "").WithOrigin("minimum")}, + }, { + "existing volume without an access mode", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { + ev.AccessMode = ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED + }))), + field.ErrorList{field.Required(field.NewPath("actor", "existing_volumes").Index(0).Child("access_mode"), "")}, + }, { + "existing volume escaping its root", + validReq(validActor(withExistingVolume(func(ev *ateapipb.ExistingVolume) { ev.SubPath = "../other" }))), + field.ErrorList{field.Invalid(field.NewPath("actor", "existing_volumes").Index(0).Child("sub_path"), "../other", "")}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1004,6 +1046,24 @@ func withActorSourceTag(atespace, name string) func(*ateapipb.Actor) { return func(a *ateapipb.Actor) { a.SourceTag = &ateapipb.ObjectRef{Atespace: atespace, Name: name} } } +// withExistingVolume returns a modifier func (see validActor) which gives the +// actor one valid existing volume, changed by mod when it is not nil. +func withExistingVolume(mod func(*ateapipb.ExistingVolume)) func(*ateapipb.Actor) { + return func(a *ateapipb.Actor) { + ev := &ateapipb.ExistingVolume{ + Name: "workspace", + Driver: "nfs.csi.k8s.io", + VolumeHandle: "nfs-server#share#workspace-1", + AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY, + SubPath: "sessions/a", + } + if mod != nil { + mod(ev) + } + a.ExistingVolumes = []*ateapipb.ExistingVolume{ev} + } +} + // withActorWorkerAssignment returns a modifier func (see validActor) which sets // the actor's worker_assignment to a valid value. func withActorWorkerAssignment(mods ...func(*ateapipb.WorkerAssignment)) func(*ateapipb.ActorStatus) { diff --git a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go index a4fac889b6..1dc8e7ba2b 100644 --- a/cmd/ateapi/internal/apivalidation/zz_generated.validation.go +++ b/cmd/ateapi/internal/apivalidation/zz_generated.validation.go @@ -325,6 +325,56 @@ func Validate_Actor( errs = append(errs, fn(fldPath.Child("status"), obj.Status, oldVal, oldObj != nil)...) } + { // field ateapipb.Actor.ExistingVolumes + fn := func( + fldPath *field.Path, + obj, oldObj []*ateapipb.ExistingVolume, + 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.PtrSliceNoNils[ateapipb.ExistingVolume](ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.MaxItems(ctx, op, fldPath, obj, oldObj, 8).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // lists with map semantics require unique keys + if e := validate.PtrSliceUnique(ctx, op, fldPath, obj, oldObj, + func(a *ateapipb.ExistingVolume, b *ateapipb.ExistingVolume) bool { return a.Name == b.Name }); len(e) != 0 { + errs = append(errs, e...) + } + // iterate the list and call the type's validation function + if e := validate.EachPtrSliceVal(ctx, op, fldPath, obj, oldObj, + func(a *ateapipb.ExistingVolume, b *ateapipb.ExistingVolume) bool { return a.Name == b.Name }, ateDeepEqual, Validate_ExistingVolume); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.Actor) []*ateapipb.ExistingVolume { + return oldObj.ExistingVolumes + }) + errs = append(errs, fn(fldPath.Child("existing_volumes"), obj.ExistingVolumes, oldVal, oldObj != nil)...) + } + return errs } @@ -3669,6 +3719,189 @@ func Validate_EnvVar( return errs } +// Validate_ExistingVolume validates an instance of ExistingVolume according +// to declarative validation rules in the API schema. +func Validate_ExistingVolume( + ctx context.Context, op operation.Operation, fldPath *field.Path, + obj, oldObj *ateapipb.ExistingVolume) (errs field.ErrorList) { + + { // field ateapipb.ExistingVolume.Name + 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.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + if e := validate.ShortName(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExistingVolume) *string { + return &oldObj.Name + }) + errs = append(errs, fn(fldPath.Child("name"), &obj.Name, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExistingVolume.Driver + 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.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // custom validation + if e := ValidateCustom_ExistingVolume_Driver(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + 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.ExistingVolume) *string { + return &oldObj.Driver + }) + errs = append(errs, fn(fldPath.Child("driver"), &obj.Driver, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExistingVolume.VolumeHandle + 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.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + // custom validation + if e := ValidateCustom_ExistingVolume_VolumeHandle(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 256); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExistingVolume) *string { + return &oldObj.VolumeHandle + }) + errs = append(errs, fn(fldPath.Child("volume_handle"), &obj.VolumeHandle, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExistingVolume.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.RequiredValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + errs = append(errs, e...) + 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...) + } + if e := validate.Minimum(ctx, op, fldPath, obj, oldObj, 2); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExistingVolume) *ateapipb.VolumeAccessMode { + return &oldObj.AccessMode + }) + errs = append(errs, fn(fldPath.Child("access_mode"), &obj.AccessMode, oldVal, oldObj != nil)...) + } + + { // field ateapipb.ExistingVolume.SubPath + 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 + } + // custom validation + if e := ValidateCustom_ExistingVolume_SubPath(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 4096); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.ExistingVolume) *string { + return &oldObj.SubPath + }) + errs = append(errs, fn(fldPath.Child("sub_path"), &obj.SubPath, oldVal, oldObj != nil)...) + } + + return errs +} + // Validate_ExternalSnapshot validates an instance of ExternalSnapshot according // to declarative validation rules in the API schema. func Validate_ExternalSnapshot( @@ -8500,7 +8733,7 @@ func Validate_UpdateWorkerRequest( return errs } -var unionMembershipFor_github_com_agent_substrate_substrate_pkg_proto_ateapipb_Volume_ = validate.NewUnionMembership(validate.NewUnionMember("durable_dir"), validate.NewUnionMember("external_volume_template"), validate.NewUnionMember("system_info"), validate.NewUnionMember("image")) +var unionMembershipFor_github_com_agent_substrate_substrate_pkg_proto_ateapipb_Volume_ = validate.NewUnionMembership(validate.NewUnionMember("durable_dir"), validate.NewUnionMember("external_volume_template"), validate.NewUnionMember("system_info"), validate.NewUnionMember("image"), validate.NewUnionMember("existing_volume")) // Validate_Volume validates an instance of Volume according // to declarative validation rules in the API schema. @@ -8532,6 +8765,12 @@ func Validate_Volume( return false } return obj.Image != nil + }, + func(obj *ateapipb.Volume) bool { + if obj == nil { + return false + } + return obj.ExistingVolume != nil }); len(e) != 0 { errs = append(errs, e...) } @@ -8686,6 +8925,34 @@ func Validate_Volume( errs = append(errs, fn(fldPath.Child("image"), obj.Image, oldVal, oldObj != nil)...) } + { // field ateapipb.Volume.ExistingVolume + fn := func( + fldPath *field.Path, + obj, oldObj *ateapipb.ExistingVolumeSource, + 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.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 { + earlyReturn = true + } + if earlyReturn { + return // do not proceed + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.Volume) *ateapipb.ExistingVolumeSource { + return oldObj.ExistingVolume + }) + errs = append(errs, fn(fldPath.Child("existing_volume"), obj.ExistingVolume, oldVal, oldObj != nil)...) + } + return errs } @@ -8763,6 +9030,69 @@ func Validate_VolumeMount( errs = append(errs, fn(fldPath.Child("mount_path"), &obj.MountPath, oldVal, oldObj != nil)...) } + { // field ateapipb.VolumeMount.SubPath + 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 + } + // custom validation + if e := ValidateCustom_VolumeMount_SubPath(ctx, op, fldPath, obj, oldObj); len(e) != 0 { + errs = append(errs, e...) + } + if e := validate.MaxLength(ctx, op, fldPath, obj, oldObj, 4096); len(e) != 0 { + errs = append(errs, e...) + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.VolumeMount) *string { + return &oldObj.SubPath + }) + errs = append(errs, fn(fldPath.Child("sub_path"), &obj.SubPath, oldVal, oldObj != nil)...) + } + + { // field ateapipb.VolumeMount.ReadOnly + fn := func( + fldPath *field.Path, + obj, oldObj *bool, + 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 + } + return + } + oldVal := safe.Field(oldObj, + func(oldObj *ateapipb.VolumeMount) *bool { + return &oldObj.ReadOnly + }) + errs = append(errs, fn(fldPath.Child("read_only"), &obj.ReadOnly, oldVal, oldObj != nil)...) + } + return errs } diff --git a/cmd/ateapi/internal/controlapi/access_policy_test.go b/cmd/ateapi/internal/controlapi/access_policy_test.go index 8ac514ff14..83bdbd3861 100644 --- a/cmd/ateapi/internal/controlapi/access_policy_test.go +++ b/cmd/ateapi/internal/controlapi/access_policy_test.go @@ -47,7 +47,7 @@ func TestAccessPolicy_GlobalAndAtespaceGovernance(t *testing.T) { } persistence.SetPolicyManager(policyManager) - svc := NewRPCService(persistence, nil, nil, nil, nil, nil, nil, nil, "", 0, 0, nil, nil, "", nil, nil) + svc := NewRPCService(persistence, nil, nil, nil, nil, nil, nil, nil, nil, "", 0, 0, nil, nil, "", nil, nil) interceptor := authz.UnaryServerInterceptor(authorizer, true) userCtx := func(id string) context.Context { diff --git a/cmd/ateapi/internal/controlapi/actor.go b/cmd/ateapi/internal/controlapi/actor.go index c9ce6bb394..53fc664e93 100644 --- a/cmd/ateapi/internal/controlapi/actor.go +++ b/cmd/ateapi/internal/controlapi/actor.go @@ -88,14 +88,27 @@ func (s *ServiceImpl) CreateActor(ctx context.Context, inActor *ateapipb.Actor) return nil, err } + if err := validateExistingVolumes(ctx, s.pluginRegistry, s.persistentVolumeLister, template, inActor.GetExistingVolumes()); err != nil { + return nil, err + } + // Resolve the explicit tag, or freeze the template's current golden default. + // The golden actor supplies no existing volumes, so its snapshot was + // captured without their mounts: an actor that supplies any boots fresh + // instead of restoring guest state built under another mount set. tagRef := inActor.GetSourceTag() - if tagRef == nil { + switch { + case tagRef == nil && len(inActor.GetExistingVolumes()) > 0: + case tagRef == nil: if legacyGoldenSnapshot(template.GetStatus().GetGoldenSnapshotStatus()) { return nil, goldenSnapshotAwaitingMigration(template) } tagRef = template.GetStatus().GetGoldenSnapshotStatus().GetGoldenTag() - } else { + case len(inActor.GetExistingVolumes()) > 0: + // A tag does not record which existing volumes its guest state was + // captured with. + return nil, status.Error(codes.FailedPrecondition, "Tag cloning does not support actors with existing volumes") + default: for _, volume := range template.GetVolumes() { if volume.GetExternalVolumeTemplate() != nil { // TODO: Permit cloning after CSI volume snapshots are supported. diff --git a/cmd/ateapi/internal/controlapi/existing_volumes.go b/cmd/ateapi/internal/controlapi/existing_volumes.go new file mode 100644 index 0000000000..255520b4a0 --- /dev/null +++ b/cmd/ateapi/internal/controlapi/existing_volumes.go @@ -0,0 +1,220 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlapi + +import ( + "context" + "fmt" + "path" + + "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "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" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + corev1listers "k8s.io/client-go/listers/core/v1" +) + +// existingVolume returns the actor's reference for the template's existing +// volume volumeName, or nil. +func existingVolume(actor *ateapipb.Actor, volumeName string) *ateapipb.ExistingVolume { + for _, ev := range actor.GetExistingVolumes() { + if ev.GetName() == volumeName { + return ev + } + } + return nil +} + +// unsuppliedVolume reports whether volumeName is an existing volume of the +// template that the actor does not supply: such a volume contributes neither +// itself nor its mounts. +func unsuppliedVolume(template *ateapipb.ActorTemplate, actor *ateapipb.Actor, volumeName string) bool { + for _, vol := range template.GetVolumes() { + if vol.GetName() == volumeName { + return vol.GetExistingVolume() != nil && existingVolume(actor, volumeName) == nil + } + } + return false +} + +// accessModeToVolume maps the API's access mode to the volume plugins'. +func accessModeToVolume(mode ateapipb.VolumeAccessMode) volume.AccessMode { + switch mode { + case ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY: + return volume.ReadOnlyMany + case ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY: + return volume.ReadWriteMany + default: + return volume.ReadWriteOnce + } +} + +// accessModeToAtelet maps the API's access mode to atelet's. +func accessModeToAtelet(mode ateapipb.VolumeAccessMode) ateletpb.VolumeAccessMode { + return ateletpb.VolumeAccessMode(mode) +} + +// pvAccessModes are the PersistentVolume access modes that permit each mode: +// a read-write-many volume may also be mounted read-only on many nodes. +var pvAccessModes = map[ateapipb.VolumeAccessMode][]corev1.PersistentVolumeAccessMode{ + ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY: {corev1.ReadOnlyMany, corev1.ReadWriteMany}, + ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY: {corev1.ReadWriteMany}, +} + +// findPersistentVolume returns the PersistentVolume that holds the CSI +// volume handle of driver, or nil. +func findPersistentVolume(pvLister corev1listers.PersistentVolumeLister, driver, handle string) (*corev1.PersistentVolume, error) { + pvs, err := pvLister.List(labels.Everything()) + if err != nil { + return nil, err + } + for _, pv := range pvs { + if csi := pv.Spec.CSI; csi != nil && csi.Driver == driver && csi.VolumeHandle == handle { + return pv, nil + } + } + return nil, nil +} + +// resolveExistingVolume finds the PersistentVolume of an actor's existing +// volume and checks that it permits the volume's access mode. +func resolveExistingVolume(pvLister corev1listers.PersistentVolumeLister, ev *ateapipb.ExistingVolume) (*corev1.PersistentVolume, error) { + name := ev.GetName() + pv, err := findPersistentVolume(pvLister, ev.GetDriver(), ev.GetVolumeHandle()) + if err != nil { + return nil, status.Errorf(codes.Internal, "existing volume %q: listing PersistentVolumes: %v", name, err) + } + if pv == nil { + return nil, status.Errorf(codes.FailedPrecondition, "existing volume %q: no PersistentVolume of driver %q holds volume %q", name, ev.GetDriver(), ev.GetVolumeHandle()) + } + for _, want := range pvAccessModes[ev.GetAccessMode()] { + for _, got := range pv.Spec.AccessModes { + if got == want { + return pv, nil + } + } + } + return nil, status.Errorf(codes.FailedPrecondition, "existing volume %q: PersistentVolume %q permits %v, not %s", name, pv.Name, pv.Spec.AccessModes, ev.GetAccessMode()) +} + +// validateExistingVolumes checks a CreateActor's existing volumes against the +// template and the cluster, so that a volume that cannot be mounted is +// refused at create rather than at the actor's first resume: each must +// supply an existing volume of the template, its driver must be registered, +// and a PersistentVolume of the driver must hold its handle and permit its +// access mode. +func validateExistingVolumes(ctx context.Context, registry VolumePluginRegistry, pvLister corev1listers.PersistentVolumeLister, template *ateapipb.ActorTemplate, evs []*ateapipb.ExistingVolume) error { + for _, ev := range evs { + name := ev.GetName() + declared := false + for _, vol := range template.GetVolumes() { + if vol.GetName() == name && vol.GetExistingVolume() != nil { + declared = true + break + } + } + if !declared { + return status.Errorf(codes.InvalidArgument, "existing volume %q: ActorTemplate declares no existing volume %q", name, name) + } + if _, err := registry.GetPlugin(ctx, ev.GetDriver()); err != nil { + return status.Errorf(codes.FailedPrecondition, "existing volume %q: unknown driver %q: %v", name, ev.GetDriver(), err) + } + if _, err := resolveExistingVolume(pvLister, ev); err != nil { + return err + } + } + return nil +} + +// appendExistingVolumes adds the actor's mounted existing volumes to +// workloadSpec. Their volume context comes from the PersistentVolume at +// resume (fillExistingVolumeContext): unmounting needs none. +func appendExistingVolumes(workloadSpec *ateletpb.WorkloadSpec, template *ateapipb.ActorTemplate, actor *ateapipb.Actor) { + for _, vol := range template.GetVolumes() { + if vol.GetExistingVolume() == nil || !isVolumeMounted(vol.GetName(), template) { + continue + } + ev := existingVolume(actor, vol.GetName()) + if ev == nil { + continue + } + workloadSpec.Volumes = append(workloadSpec.Volumes, &ateletpb.Volume{ + Name: vol.GetName(), + Source: &ateletpb.Volume_External{ + External: &ateletpb.ExternalVolumeSource{ + StorageVolumeId: ev.GetVolumeHandle(), + VolumeType: ev.GetDriver(), + AccessMode: accessModeToAtelet(ev.GetAccessMode()), + }, + }, + }) + } +} + +// existingVolumeMount returns the atelet mount of an existing volume: its +// sub_path is under the actor's root of the volume, and a read-only volume +// mounts read-only. +func existingVolumeMount(mount *ateapipb.VolumeMount, ev *ateapipb.ExistingVolume) *ateletpb.VolumeMount { + subPath := mount.GetSubPath() + if root := ev.GetSubPath(); root != "" { + subPath = path.Join(root, subPath) + } + return &ateletpb.VolumeMount{ + Name: mount.GetName(), + MountPath: mount.GetMountPath(), + SubPath: subPath, + ReadOnly: mount.GetReadOnly() || ev.GetAccessMode() == ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, + } +} + +// fillExistingVolumeContext sets each existing volume's volume context in +// workloadSpec from its PersistentVolume, which the driver needs to mount it. +func fillExistingVolumeContext(workloadSpec *ateletpb.WorkloadSpec, pvLister corev1listers.PersistentVolumeLister, actor *ateapipb.Actor) error { + for _, vol := range workloadSpec.GetVolumes() { + ev := existingVolume(actor, vol.GetName()) + ext := vol.GetExternal() + if ev == nil || ext == nil { + continue + } + pv, err := resolveExistingVolume(pvLister, ev) + if err != nil { + return err + } + ext.VolumeContext = pv.Spec.CSI.VolumeAttributes + } + return nil +} + +// attachExistingVolumes attaches the actor's mounted existing volumes to +// node. They are never detached by the actor: other actors on the node may +// be using them. +func attachExistingVolumes(ctx context.Context, registry VolumePluginRegistry, actor *ateapipb.Actor, template *ateapipb.ActorTemplate, node string) error { + for _, ev := range actor.GetExistingVolumes() { + if !isVolumeMounted(ev.GetName(), template) { + continue + } + plugin, err := registry.GetPlugin(ctx, ev.GetDriver()) + if err != nil { + return fmt.Errorf("failed to get volume plugin for %q: %w", ev.GetDriver(), err) + } + if err := plugin.AttachVolume(ctx, ev.GetVolumeHandle(), node, accessModeToVolume(ev.GetAccessMode())); err != nil { + return fmt.Errorf("failed to attach existing volume %q to node %q: %w", ev.GetName(), node, err) + } + } + return nil +} diff --git a/cmd/ateapi/internal/controlapi/existing_volumes_test.go b/cmd/ateapi/internal/controlapi/existing_volumes_test.go new file mode 100644 index 0000000000..81e0a75900 --- /dev/null +++ b/cmd/ateapi/internal/controlapi/existing_volumes_test.go @@ -0,0 +1,287 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controlapi + +import ( + "context" + "strings" + "testing" + + "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "github.com/agent-substrate/substrate/internal/volume" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "github.com/google/go-cmp/cmp" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" +) + +const ( + nfsDriver = "nfs.csi.k8s.io" + nfsHandle = "nfs-server#share#workspace-1" +) + +// workspaceTemplate mounts the existing volume "workspace" at /workspace and +// a second existing volume "mirrors" read-only at /mirrors, beside an +// image volume. +func workspaceTemplate() *ateapipb.ActorTemplate { + return &ateapipb.ActorTemplate{ + Containers: []*ateapipb.Container{{ + Name: "agent", + Image: "agent@sha256:0000000000000000000000000000000000000000000000000000000000000000", + VolumeMounts: []*ateapipb.VolumeMount{ + {Name: "tools", MountPath: "/tools"}, + {Name: "workspace", MountPath: "/workspace"}, + {Name: "mirrors", MountPath: "/mirrors", ReadOnly: true}, + }, + }}, + Volumes: []*ateapipb.Volume{ + {Name: "tools", Image: &ateapipb.ImageVolumeSource{Reference: "tools@sha256:0000000000000000000000000000000000000000000000000000000000000000"}}, + {Name: "workspace", ExistingVolume: &ateapipb.ExistingVolumeSource{}}, + {Name: "mirrors", ExistingVolume: &ateapipb.ExistingVolumeSource{}}, + }, + } +} + +// sessionVolumes supply both existing volumes from one handle: the +// session's own directory read-write and the mirrors read-only. +func sessionVolumes(session string) []*ateapipb.ExistingVolume { + return []*ateapipb.ExistingVolume{ + {Name: "workspace", Driver: nfsDriver, VolumeHandle: nfsHandle, AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY, SubPath: "sessions/" + session}, + {Name: "mirrors", Driver: nfsDriver, VolumeHandle: nfsHandle, AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, SubPath: "mirrors"}, + } +} + +func pvLister(t *testing.T, pvs ...*corev1.PersistentVolume) corev1listers.PersistentVolumeLister { + t.Helper() + indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + for _, pv := range pvs { + if err := indexer.Add(pv); err != nil { + t.Fatalf("adding PersistentVolume: %v", err) + } + } + return corev1listers.NewPersistentVolumeLister(indexer) +} + +func workspacePV(modes ...corev1.PersistentVolumeAccessMode) *corev1.PersistentVolume { + return &corev1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{Name: "pv-workspace-1"}, + Spec: corev1.PersistentVolumeSpec{ + AccessModes: modes, + PersistentVolumeSource: corev1.PersistentVolumeSource{CSI: &corev1.CSIPersistentVolumeSource{ + Driver: nfsDriver, + VolumeHandle: nfsHandle, + VolumeAttributes: map[string]string{"server": "nfs-server", "share": "/"}, + }}, + }, + } +} + +func TestValidateExistingVolumes(t *testing.T) { + rwx := workspacePV(corev1.ReadWriteMany) + registry := &mockPluginRegistry{plugins: map[string]volume.VolumePluginControlPlane{nfsDriver: &volume.MockVolumePlugin{}}} + with := func(mod func(ev *ateapipb.ExistingVolume)) []*ateapipb.ExistingVolume { + evs := sessionVolumes("a") + mod(evs[0]) + return evs + } + for _, tc := range []struct { + name string + evs []*ateapipb.ExistingVolume + pvs []*corev1.PersistentVolume + wantCode codes.Code + wantMsg string + }{ + {name: "none", wantCode: codes.OK}, + {name: "read-write-many volume, mounted read-write and read-only", evs: sessionVolumes("a"), pvs: []*corev1.PersistentVolume{rwx}, wantCode: codes.OK}, + { + name: "volume the template does not declare as existing", + evs: with(func(ev *ateapipb.ExistingVolume) { ev.Name = "tools" }), + pvs: []*corev1.PersistentVolume{rwx}, + wantCode: codes.InvalidArgument, + wantMsg: `ActorTemplate declares no existing volume "tools"`, + }, + { + name: "unknown driver", + evs: with(func(ev *ateapipb.ExistingVolume) { ev.Driver = "unknown.csi.example.com" }), + pvs: []*corev1.PersistentVolume{rwx}, + wantCode: codes.FailedPrecondition, + wantMsg: `existing volume "workspace": unknown driver "unknown.csi.example.com"`, + }, + { + name: "missing volume", + evs: with(func(ev *ateapipb.ExistingVolume) { ev.VolumeHandle = "no-such-volume" }), + pvs: []*corev1.PersistentVolume{rwx}, + wantCode: codes.FailedPrecondition, + wantMsg: `no PersistentVolume of driver "nfs.csi.k8s.io" holds volume "no-such-volume"`, + }, + { + name: "volume of another driver", + evs: sessionVolumes("a"), + pvs: []*corev1.PersistentVolume{func() *corev1.PersistentVolume { + pv := workspacePV(corev1.ReadWriteMany) + pv.Spec.CSI.Driver = "other" + return pv + }()}, + wantCode: codes.FailedPrecondition, + wantMsg: `no PersistentVolume of driver "nfs.csi.k8s.io" holds volume`, + }, + { + name: "single-node volume", + evs: sessionVolumes("a"), + pvs: []*corev1.PersistentVolume{workspacePV(corev1.ReadWriteOnce)}, + wantCode: codes.FailedPrecondition, + wantMsg: `PersistentVolume "pv-workspace-1" permits [ReadWriteOnce], not VOLUME_ACCESS_MODE_READ_WRITE_MANY`, + }, + { + name: "read-only-many volume mounted read-write", + evs: sessionVolumes("a"), + pvs: []*corev1.PersistentVolume{workspacePV(corev1.ReadOnlyMany)}, + wantCode: codes.FailedPrecondition, + wantMsg: `not VOLUME_ACCESS_MODE_READ_WRITE_MANY`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + err := validateExistingVolumes(context.Background(), registry, pvLister(t, tc.pvs...), workspaceTemplate(), tc.evs) + if got := status.Code(err); got != tc.wantCode { + t.Fatalf("validateExistingVolumes code = %v (%v), want %v", got, err, tc.wantCode) + } + if tc.wantMsg != "" && !strings.Contains(err.Error(), tc.wantMsg) { + t.Errorf("validateExistingVolumes error = %q, want it to contain %q", err, tc.wantMsg) + } + }) + } +} + +func TestWorkloadSpecExistingVolumes(t *testing.T) { + t.Run("supplied volumes mount at their sub-paths", func(t *testing.T) { + actor := &ateapipb.Actor{ExistingVolumes: sessionVolumes("a")} + spec, err := workloadSpecFromActorTemplate(workspaceTemplate(), actor) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate: %v", err) + } + wantVolumes := []*ateletpb.Volume{ + {Name: "tools", Source: &ateletpb.Volume_Image{Image: &ateletpb.ImageVolumeSource{Reference: "tools@sha256:0000000000000000000000000000000000000000000000000000000000000000"}}}, + {Name: "workspace", Source: &ateletpb.Volume_External{External: &ateletpb.ExternalVolumeSource{StorageVolumeId: nfsHandle, VolumeType: nfsDriver, AccessMode: ateletpb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY}}}, + {Name: "mirrors", Source: &ateletpb.Volume_External{External: &ateletpb.ExternalVolumeSource{StorageVolumeId: nfsHandle, VolumeType: nfsDriver, AccessMode: ateletpb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY}}}, + } + if diff := cmp.Diff(wantVolumes, spec.GetVolumes(), protocmp.Transform()); diff != "" { + t.Errorf("volumes mismatch (-want +got):\n%s", diff) + } + wantMounts := []*ateletpb.VolumeMount{ + {Name: "tools", MountPath: "/tools"}, + {Name: "workspace", MountPath: "/workspace", SubPath: "sessions/a"}, + {Name: "mirrors", MountPath: "/mirrors", SubPath: "mirrors", ReadOnly: true}, + } + if diff := cmp.Diff(wantMounts, spec.GetContainers()[0].GetVolumeMounts(), protocmp.Transform()); diff != "" { + t.Errorf("mounts mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("a mount's sub_path is under the actor's root, and a read-only volume mounts read-only", func(t *testing.T) { + tmpl := workspaceTemplate() + tmpl.Containers[0].VolumeMounts[1].SubPath = "src" + evs := sessionVolumes("a") + evs[1].SubPath = "" + spec, err := workloadSpecFromActorTemplate(tmpl, &ateapipb.Actor{ExistingVolumes: evs}) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate: %v", err) + } + mounts := spec.GetContainers()[0].GetVolumeMounts() + if got := mounts[1].GetSubPath(); got != "sessions/a/src" { + t.Errorf("workspace sub_path = %q, want sessions/a/src", got) + } + tmpl.Containers[0].VolumeMounts[2].ReadOnly = false + spec, _ = workloadSpecFromActorTemplate(tmpl, &ateapipb.Actor{ExistingVolumes: evs}) + if m := spec.GetContainers()[0].GetVolumeMounts()[2]; !m.GetReadOnly() || m.GetSubPath() != "" { + t.Errorf("mirrors mount = %v, want read-only at the volume root", m) + } + }) + + t.Run("without a reference the request is unchanged", func(t *testing.T) { + // The same template without its existing volumes and their mounts. + plain := workspaceTemplate() + plain.Containers[0].VolumeMounts = plain.Containers[0].VolumeMounts[:1] + plain.Volumes = plain.Volumes[:1] + want, err := workloadSpecFromActorTemplate(plain, &ateapipb.Actor{}) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate: %v", err) + } + for _, actor := range []*ateapipb.Actor{{}, nil} { + got, err := workloadSpecFromActorTemplate(workspaceTemplate(), actor) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate: %v", err) + } + if !proto.Equal(want, got) { + t.Errorf("spec without references = %v, want %v", got, want) + } + } + }) +} + +func TestFillExistingVolumeContext(t *testing.T) { + actor := &ateapipb.Actor{ExistingVolumes: sessionVolumes("a")} + spec, err := workloadSpecFromActorTemplate(workspaceTemplate(), actor) + if err != nil { + t.Fatalf("workloadSpecFromActorTemplate: %v", err) + } + if err := fillExistingVolumeContext(spec, pvLister(t, workspacePV(corev1.ReadWriteMany)), actor); err != nil { + t.Fatalf("fillExistingVolumeContext: %v", err) + } + for _, vol := range spec.GetVolumes()[1:] { + if diff := cmp.Diff(map[string]string{"server": "nfs-server", "share": "/"}, vol.GetExternal().GetVolumeContext()); diff != "" { + t.Errorf("%s volume context mismatch (-want +got):\n%s", vol.GetName(), diff) + } + } + + // A PersistentVolume gone since the create fails the resume with the reason. + err = fillExistingVolumeContext(spec, pvLister(t), actor) + if status.Code(err) != codes.FailedPrecondition || !strings.Contains(err.Error(), "no PersistentVolume") { + t.Errorf("fillExistingVolumeContext without the PersistentVolume = %v, want FailedPrecondition", err) + } +} + +// attachRecorder records AttachVolume calls. +type attachRecorder struct { + volume.MockVolumePlugin + attached []string + modes []volume.AccessMode +} + +func (a *attachRecorder) AttachVolume(_ context.Context, volumeID, node string, mode volume.AccessMode) error { + a.attached = append(a.attached, volumeID+"@"+node) + a.modes = append(a.modes, mode) + return nil +} + +func TestAttachExistingVolumes(t *testing.T) { + plugin := &attachRecorder{} + registry := &mockPluginRegistry{plugins: map[string]volume.VolumePluginControlPlane{nfsDriver: plugin}} + actor := &ateapipb.Actor{ExistingVolumes: sessionVolumes("a")} + if err := attachExistingVolumes(context.Background(), registry, actor, workspaceTemplate(), "node-1"); err != nil { + t.Fatalf("attachExistingVolumes: %v", err) + } + if diff := cmp.Diff([]string{nfsHandle + "@node-1", nfsHandle + "@node-1"}, plugin.attached); diff != "" { + t.Errorf("attached mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff([]volume.AccessMode{volume.ReadWriteMany, volume.ReadOnlyMany}, plugin.modes); diff != "" { + t.Errorf("modes mismatch (-want +got):\n%s", diff) + } +} diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index 88daec6ac9..defdfb9953 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -1597,7 +1597,7 @@ 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 { +func (f *partialFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, _ volume.AccessMode) error { return nil } @@ -1738,7 +1738,7 @@ 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 { +func (r *retrySuccessVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, _ volume.AccessMode) error { return nil } @@ -1880,7 +1880,7 @@ func (a *attachFailVolumePlugin) CreateVolume(ctx context.Context, name, capacit return "storage-" + name, parameters, nil } -func (a *attachFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string) error { +func (a *attachFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, _ volume.AccessMode) error { a.mu.Lock() defer a.mu.Unlock() a.attachAttempts++ @@ -2125,7 +2125,7 @@ func (m *multiVolAttachPlugin) CreateVolume(ctx context.Context, name, capacity, return "storage-" + name, parameters, nil } -func (m *multiVolAttachPlugin) AttachVolume(ctx context.Context, volumeID, node string) error { +func (m *multiVolAttachPlugin) AttachVolume(ctx context.Context, volumeID, node string, _ volume.AccessMode) error { m.mu.Lock() defer m.mu.Unlock() m.attachAttempts[volumeID]++ @@ -2296,7 +2296,7 @@ func (d *detachFailVolumePlugin) CreateVolume(ctx context.Context, name, capacit return "storage-" + name, parameters, nil } -func (d *detachFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string) error { +func (d *detachFailVolumePlugin) AttachVolume(ctx context.Context, volumeID, node string, _ volume.AccessMode) error { d.mu.Lock() defer d.mu.Unlock() d.attachedNodes = append(d.attachedNodes, node) diff --git a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go index ae130a6218..22337ce952 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go @@ -135,6 +135,7 @@ func setupTestWithVolumePlugins(t *testing.T, ns string, plugins map[string]volu scFactory := informers.NewSharedInformerFactory(k8sClient, 0) scLister := scFactory.Storage().V1().StorageClasses().Lister() nodeLister := scFactory.Core().V1().Nodes().Lister() + pvLister := scFactory.Core().V1().PersistentVolumes().Lister() substrateInformerFactory := externalversions.NewSharedInformerFactory(substrateClient, 0) workerPoolLister := substrateInformerFactory.Api().V1alpha1().WorkerPools().Lister() @@ -217,6 +218,7 @@ func setupTestWithVolumePlugins(t *testing.T, ns string, plugins map[string]volu sandboxConfigLister, csiDriverConfigLister, scLister, + pvLister, nodeLister, dialer, instruments, diff --git a/cmd/ateapi/internal/controlapi/service.go b/cmd/ateapi/internal/controlapi/service.go index 0efaf8cf83..484a7feaa8 100644 --- a/cmd/ateapi/internal/controlapi/service.go +++ b/cmd/ateapi/internal/controlapi/service.go @@ -82,6 +82,7 @@ func NewRPCService( sandboxConfigLister listersv1alpha1.SandboxConfigLister, csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister, storageClassLister storagev1listers.StorageClassLister, + persistentVolumeLister corev1listers.PersistentVolumeLister, nodeLister corev1listers.NodeLister, dialer *AteletDialer, instruments *Instruments, @@ -108,7 +109,10 @@ func NewRPCService( actorIDJWTPool: actorIDJWTPool, actorIDCAPool: actorIDCAPool, } + impl.pluginRegistry = s + impl.persistentVolumeLister = persistentVolumeLister s.actorWorkflow = NewActorWorkflow(impl, workerCache, dialer, sandboxConfigLister, storageClassLister, nodeLister, instruments, egressGatewayAddress, s, actorWorkflowDeadline, actorRestoreBudget, objectStore) + s.actorWorkflow.persistentVolumeLister = persistentVolumeLister s.workerWorkflow = NewWorkerWorkflow(impl) return s } @@ -188,6 +192,11 @@ type ServiceImpl struct { store store.Interface storageClassLister storagev1listers.StorageClassLister + + // pluginRegistry and persistentVolumeLister resolve what CreateActor + // checks an actor's existing volumes against. + pluginRegistry VolumePluginRegistry + persistentVolumeLister corev1listers.PersistentVolumeLister } // newServiceImpl creates an instance of the service's middleware diff --git a/cmd/ateapi/internal/controlapi/workflow.go b/cmd/ateapi/internal/controlapi/workflow.go index 8cee739565..5f62072f51 100644 --- a/cmd/ateapi/internal/controlapi/workflow.go +++ b/cmd/ateapi/internal/controlapi/workflow.go @@ -116,9 +116,11 @@ type ActorWorkflow struct { instruments *Instruments egressGatewayAddress string pluginRegistry VolumePluginRegistry - workflowDeadline time.Duration - restoreBudget time.Duration - objectStore objectstore.Store + // persistentVolumeLister resolves an actor's existing volumes at resume. + persistentVolumeLister corev1listers.PersistentVolumeLister + workflowDeadline time.Duration + restoreBudget time.Duration + objectStore objectstore.Store // pauseUploads makes every pause durable in the background; nil in a // workflow built without one, which then pauses node-locally only. pauseUploads *pauseUploader diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 0e3bb3d80c..db7b8f4210 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -27,6 +27,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" @@ -624,11 +625,11 @@ func (w *ActorWorkflow) ensureVolumesAttached(ctx context.Context, actor *ateapi if err != nil { return fmt.Errorf("failed to get volume plugin for %q: %w", vol.GetVolumeType(), err) } - if err := plugin.AttachVolume(ctx, vol.GetStorageVolumeId(), node); err != nil { + if err := plugin.AttachVolume(ctx, vol.GetStorageVolumeId(), node, volume.ReadWriteOnce); err != nil { return fmt.Errorf("failed to attach volume %q to node %q: %w", vol.GetStorageVolumeId(), node, err) } } - return nil + return attachExistingVolumes(ctx, w.pluginRegistry, actor, actorTemplate, node) } // ensureAteletRestored brings the workload up on the assigned worker: @@ -665,6 +666,9 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou if err != nil { return tele, err } + if err := fillExistingVolumeContext(workloadSpec, w.persistentVolumeLister, actor); err != nil { + return tele, err + } egressGateway := w.egressGateway() // The actor's declared limits ride the RPC down to the sandbox so it is sized diff --git a/cmd/ateapi/internal/controlapi/workload_spec.go b/cmd/ateapi/internal/controlapi/workload_spec.go index 8bcc167458..16642756d5 100644 --- a/cmd/ateapi/internal/controlapi/workload_spec.go +++ b/cmd/ateapi/internal/controlapi/workload_spec.go @@ -122,6 +122,7 @@ func workloadSpecFromActorTemplate(actorTemplate *ateapipb.ActorTemplate, actor if err := appendExternalVolumes(workloadSpec, actorTemplate, actor); err != nil { return nil, err } + appendExistingVolumes(workloadSpec, actorTemplate, actor) for _, ctr := range actorTemplate.GetContainers() { ctrResources, err := toAteletResources(ctr.GetResources()) @@ -144,6 +145,13 @@ func workloadSpecFromActorTemplate(actorTemplate *ateapipb.ActorTemplate, actor }) } for _, mount := range ctr.GetVolumeMounts() { + if unsuppliedVolume(actorTemplate, actor, mount.GetName()) { + continue + } + if ev := existingVolume(actor, mount.GetName()); ev != nil { + ateletCtr.VolumeMounts = append(ateletCtr.VolumeMounts, existingVolumeMount(mount, ev)) + continue + } ateletCtr.VolumeMounts = append(ateletCtr.VolumeMounts, &ateletpb.VolumeMount{ Name: mount.GetName(), MountPath: mount.GetMountPath(), diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index bfe19d0ce4..d913cf02fd 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -248,6 +248,9 @@ func main() { // exists, the one signal that tells a node gone from one whose workers are // momentarily absent. nodeLister := scInformerFactory.Core().V1().Nodes().Lister() + // PersistentVolumes: the cluster's record of the existing volumes actors + // mount, which CreateActor checks and resume reads the volume context from. + persistentVolumeLister := scInformerFactory.Core().V1().PersistentVolumes().Lister() stopCh := make(chan struct{}) defer close(stopCh) @@ -299,6 +302,7 @@ func main() { sandboxConfigLister, csiDriverConfigLister, storageClassLister, + persistentVolumeLister, nodeLister, ateletDialer, instruments, diff --git a/cmd/atelet/main.go b/cmd/atelet/main.go index bcaa3bf302..ea96f49115 100644 --- a/cmd/atelet/main.go +++ b/cmd/atelet/main.go @@ -521,7 +521,7 @@ func (s *AteomHerder) Run(ctx context.Context, req *ateletpb.RunRequest) (resp * return nil, fmt.Errorf("while resetting actor dirs: %w", err) } - if err := s.mountExternalVolumes(ctx, actorUID, req.GetSpec().GetVolumes()); err != nil { + if err := s.mountExternalVolumes(ctx, actorUID, req.GetSpec()); err != nil { return nil, err } @@ -759,7 +759,7 @@ func (s *AteomHerder) Checkpoint(ctx context.Context, req *ateletpb.CheckpointRe } dPersist = time.Since(tPersist) - if err := s.unmountExternalVolumes(ctx, actorUID, req.GetSpec().GetVolumes()); err != nil { + if err := s.unmountExternalVolumes(ctx, actorUID, req.GetSpec()); err != nil { return nil, fmt.Errorf("while unmounting external volumes: %w", err) } @@ -1133,7 +1133,7 @@ func (s *AteomHerder) Restore(ctx context.Context, req *ateletpb.RestoreRequest) } tMount := time.Now() - mountErr := s.mountExternalVolumes(ctx, actorUID, req.GetSpec().GetVolumes()) + mountErr := s.mountExternalVolumes(ctx, actorUID, req.GetSpec()) dMount = time.Since(tMount) if mountErr != nil { return nil, mountErr @@ -1364,7 +1364,7 @@ func (s *AteomHerder) Terminate(ctx context.Context, req *ateletpb.TerminateRequ s.systemInfoVolumes.Deregister(actorUID) // Unmount external volumes - if err := s.unmountExternalVolumes(ctx, actorUID, req.GetSpec().GetVolumes()); err != nil { + if err := s.unmountExternalVolumes(ctx, actorUID, req.GetSpec()); err != nil { return nil, fmt.Errorf("failed to unmount external volumes during terminate (actor: %s, actorUID: %s): %w", actorRef, actorUID, err) } @@ -1664,6 +1664,8 @@ func buildAteomWorkloadSpec(spec *ateletpb.WorkloadSpec) (*ateompb.WorkloadSpec, csiMounts = append(csiMounts, &ateompb.VolumeMount{ VolumeName: volName, MountPath: vm.GetMountPath(), + SubPath: vm.GetSubPath(), + ReadOnly: vm.GetReadOnly(), }) case *ateletpb.Volume_SystemInfo: siMounts = append(siMounts, &ateompb.SystemInfoVolumeMount{ diff --git a/cmd/atelet/volumes.go b/cmd/atelet/volumes.go index b2698d7004..08f19812e0 100644 --- a/cmd/atelet/volumes.go +++ b/cmd/atelet/volumes.go @@ -29,8 +29,8 @@ import ( "google.golang.org/grpc/status" ) -func (s *AteomHerder) mountExternalVolumes(ctx context.Context, actorUID string, volumes []*ateletpb.Volume) error { - for _, vol := range volumes { +func (s *AteomHerder) mountExternalVolumes(ctx context.Context, actorUID string, spec *ateletpb.WorkloadSpec) error { + for _, vol := range spec.GetVolumes() { ext := vol.GetExternal() if ext == nil { continue @@ -44,16 +44,16 @@ func (s *AteomHerder) mountExternalVolumes(ctx context.Context, actorUID string, if err != nil { return fmt.Errorf("failed to get volume plugin for %q: %w", ext.GetVolumeType(), err) } - if err := plugin.MountVolume(ctx, ext.GetStorageVolumeId(), hostPath, ext.GetVolumeContext()); err != nil { + if err := plugin.MountVolume(ctx, ext.GetStorageVolumeId(), hostPath, ext.GetVolumeContext(), accessMode(ext)); err != nil { return fmt.Errorf("failed to mount volume %q to %q: %w", ext.GetStorageVolumeId(), hostPath, err) } } return nil } -func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, actorUID string, volumes []*ateletpb.Volume) error { +func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, actorUID string, spec *ateletpb.WorkloadSpec) error { var errs []error - for _, vol := range volumes { + for _, vol := range spec.GetVolumes() { ext := vol.GetExternal() if ext == nil { continue @@ -67,7 +67,7 @@ func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, actorUID strin errs = append(errs, fmt.Errorf("failed to get volume plugin for %q (volume %q): %w", ext.GetVolumeType(), ext.GetStorageVolumeId(), err)) continue } - if err := plugin.UnmountVolume(ctx, ext.GetStorageVolumeId(), hostPath); err != nil { + if err := plugin.UnmountVolume(ctx, ext.GetStorageVolumeId(), hostPath, accessMode(ext)); err != nil { if status.Code(err) == codes.NotFound || errors.Is(err, os.ErrNotExist) { slog.WarnContext(ctx, "Volume not found during unmount, assuming already unmounted", slog.String("volume_id", ext.GetStorageVolumeId()), slog.Any("error", err)) } else { @@ -78,6 +78,18 @@ func (s *AteomHerder) unmountExternalVolumes(ctx context.Context, actorUID strin return errors.Join(errs...) } +// accessMode maps an external volume's access mode to the volume plugins'. +func accessMode(ext *ateletpb.ExternalVolumeSource) volume.AccessMode { + switch ext.GetAccessMode() { + case ateletpb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY: + return volume.ReadOnlyMany + case ateletpb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY: + return volume.ReadWriteMany + default: + return volume.ReadWriteOnce + } +} + func (s *AteomHerder) getPlugin(ctx context.Context, driverName string) (volume.VolumePluginWorkerPlane, error) { s.mu.RLock() plugin, ok := s.volumePlugins[driverName] diff --git a/cmd/atelet/volumes_test.go b/cmd/atelet/volumes_test.go index dcd36ecea6..55a5777a83 100644 --- a/cmd/atelet/volumes_test.go +++ b/cmd/atelet/volumes_test.go @@ -45,7 +45,7 @@ type fakeWorkerPlugin struct { mountCalls []mountCall } -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, _ volume.AccessMode) error { f.mountCalls = append(f.mountCalls, mountCall{ volumeID: volumeID, targetPath: targetPath, @@ -59,7 +59,7 @@ func (f *fakeWorkerPlugin) MountVolume(ctx context.Context, volumeID string, tar return f.mountErr } -func (f *fakeWorkerPlugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string) error { +func (f *fakeWorkerPlugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string, _ volume.AccessMode) error { f.unmounted = append(f.unmounted, volumeID) if f.unmountErrs != nil { if err, ok := f.unmountErrs[volumeID]; ok { @@ -119,7 +119,7 @@ func TestUnmountExternalVolumes(t *testing.T) { }, } - err := s.unmountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, durableVol, extVol2}) + err := s.unmountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, durableVol, extVol2}}) if err != nil { t.Fatalf("unmountExternalVolumes failed unexpectedly: %v", err) } @@ -138,7 +138,7 @@ func TestUnmountExternalVolumes(t *testing.T) { }, } - err := s.unmountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1}) + err := s.unmountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1}}) if err == nil { t.Fatal("unmountExternalVolumes returned nil, want blocking error") } @@ -157,7 +157,7 @@ func TestUnmountExternalVolumes(t *testing.T) { }, } - err := s.unmountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}) + err := s.unmountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}) if err == nil { t.Fatal("unmountExternalVolumes returned nil, want blocking error") } @@ -209,7 +209,7 @@ func TestMountExternalVolumes(t *testing.T) { }, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, durableVol, extVol2}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, durableVol, extVol2}}) if err != nil { t.Fatalf("mountExternalVolumes failed unexpectedly: %v", err) } @@ -258,7 +258,7 @@ func TestMountExternalVolumes(t *testing.T) { }, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1}}) if err != nil { t.Fatalf("mountExternalVolumes with existing directory failed: %v", err) } @@ -284,7 +284,7 @@ func TestMountExternalVolumes(t *testing.T) { volumePlugins: map[string]volume.VolumePluginWorkerPlane{}, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{unknownVol}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{unknownVol}}) if err == nil { t.Fatal("expected mountExternalVolumes to fail with unknown plugin, got nil") } @@ -305,7 +305,7 @@ func TestMountExternalVolumes(t *testing.T) { }, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1}}) if err == nil { t.Fatal("expected mountExternalVolumes to fail, got nil") } @@ -326,7 +326,7 @@ func TestMountExternalVolumes(t *testing.T) { }, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}) if err == nil { t.Fatal("expected mountExternalVolumes to fail, got nil") } @@ -352,7 +352,7 @@ func TestMountExternalVolumes(t *testing.T) { }, } - err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}) + err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}) if err == nil { t.Fatal("expected mountExternalVolumes to fail when vol-2 fails, got nil") } @@ -418,7 +418,7 @@ func TestVolumeHostDirectoryCleanup(t *testing.T) { }, } - if err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}); err != nil { + if err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}); err != nil { t.Fatalf("mountExternalVolumes failed: %v", err) } @@ -430,7 +430,7 @@ func TestVolumeHostDirectoryCleanup(t *testing.T) { } } - if err := s.unmountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}); err != nil { + if err := s.unmountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}); err != nil { t.Fatalf("unmountExternalVolumes failed: %v", err) } @@ -453,10 +453,10 @@ func TestVolumeHostDirectoryCleanup(t *testing.T) { } // Mount and then unmount external volumes. - if err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}); err != nil { + if err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}); err != nil { t.Fatalf("mountExternalVolumes failed: %v", err) } - if err := s.unmountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1, extVol2}); err != nil { + if err := s.unmountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1, extVol2}}); err != nil { t.Fatalf("unmountExternalVolumes failed: %v", err) } @@ -500,7 +500,7 @@ func TestVolumeHostDirectoryCleanup(t *testing.T) { }, } - if err := s.mountExternalVolumes(ctx, actorUID, []*ateletpb.Volume{extVol1}); err != nil { + if err := s.mountExternalVolumes(ctx, actorUID, &ateletpb.WorkloadSpec{Volumes: []*ateletpb.Volume{extVol1}}); err != nil { t.Fatalf("mountExternalVolumes failed: %v", err) } diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index 0324458442..b603177562 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -53,6 +53,7 @@ import ( "github.com/agent-substrate/substrate/internal/serverboot" "github.com/agent-substrate/substrate/internal/sizing" "github.com/agent-substrate/substrate/internal/version" + "github.com/agent-substrate/substrate/internal/volumebind" "github.com/agent-substrate/substrate/internal/wakeupprobe" "github.com/spf13/pflag" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" @@ -583,6 +584,11 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload if err := s.tunnel.ActivateEgress(ateomstats.ActorAttributionFromRequest(req), egress); err != nil { return nil, err } + // Bind the directories of external volumes the mounts name a sub-path of + // or mount read-only, here for the same reason as the rootfs below. + if err := volumebind.Prepare(req.GetActorDirs().GetVolumesDir(), req.GetSpec().GetContainers()); err != nil { + return nil, fmt.Errorf("while binding volume directories: %w", err) + } // Create and start pause container. The bundle rootfs is composed here — // an overlay of the node's cached image layers plus the bundle's private // upper — because mounting is ateom's job (atelet runs with no @@ -890,6 +896,9 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore return nil, fmt.Errorf("while restoring durable-dir volumes: %w", err) } } + if err := volumebind.Prepare(req.GetActorDirs().GetVolumesDir(), req.GetSpec().GetContainers()); err != nil { + return nil, fmt.Errorf("while binding volume directories: %w", err) + } // Compose the pause rootfs before create (see RunWorkload). runsc restore // only needs the rootfs to hold the correct content; whether it came from // an untar or an overlay of cached layers is transparent to it. @@ -1027,6 +1036,9 @@ func (s *AteomService) terminateWorkload(ctx context.Context, actorRef resources if err := imagecache.UnmountAllUnder(actorDirs.GetOciBundleDir()); err != nil { errs = append(errs, fmt.Errorf("while unmounting bundle rootfs overlays: %w", err)) } + if err := volumebind.Release(actorDirs.GetVolumesDir(), containers); err != nil { + errs = append(errs, fmt.Errorf("while unbinding volume directories: %w", err)) + } if err := s.unhostActor(ctx, actorUID); err != nil { errs = append(errs, fmt.Errorf("while cleaning up actor network: %w", err)) diff --git a/cmd/ateom-microvm/checkpoint.go b/cmd/ateom-microvm/checkpoint.go index c5fa0beefc..f1963d24cd 100644 --- a/cmd/ateom-microvm/checkpoint.go +++ b/cmd/ateom-microvm/checkpoint.go @@ -28,6 +28,7 @@ import ( "github.com/agent-substrate/substrate/internal/apierror" "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/internal/volumebind" "github.com/agent-substrate/substrate/cmd/ateom-microvm/internal/ch" "github.com/agent-substrate/substrate/cmd/ateom-microvm/internal/kata" @@ -212,7 +213,7 @@ func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.Chec // Tear down: the actor returns to "available". Best-effort; the snapshot is // already on disk for atelet to ship. tTeardown := time.Now() - if err := s.terminateWorkload(ctx, attribution, actorDirs); err != nil { + if err := s.terminateWorkload(ctx, attribution, actorDirs, req.GetSpec().GetContainers()); err != nil { slog.WarnContext(ctx, "failed to terminate workload after checkpoint", slog.String("actor", attribution.Ref.String()), slog.String("actorUID", actorUID), @@ -399,7 +400,7 @@ func (s *AteomService) TerminateWorkload(ctx context.Context, req *ateompb.Termi attribution := ateomstats.ActorAttributionFromRequest(req) - if err := s.terminateWorkload(ctx, attribution, req.GetActorDirs()); err != nil { + if err := s.terminateWorkload(ctx, attribution, req.GetActorDirs(), req.GetSpec().GetContainers()); err != nil { return nil, fmt.Errorf("failed to terminate workload: %w", err) } @@ -418,7 +419,7 @@ func (s *AteomService) stopActorVM(ctx context.Context, actorUID string, actorDi return s.teardownActor(ctx, actorUID, actorDirs, ra, ch.NewClient(chSocket)) } -func (s *AteomService) terminateWorkload(ctx context.Context, actor resources.ActorAttribution, actorDirs *ateompb.ActorDirs) error { +func (s *AteomService) terminateWorkload(ctx context.Context, actor resources.ActorAttribution, actorDirs *ateompb.ActorDirs, containers []*ateompb.Container) error { var errs []error if err := s.tunnel.Deactivate(ctx, actor); err != nil { errs = append(errs, fmt.Errorf("while deactivating actor networking: %w", err)) @@ -428,6 +429,9 @@ func (s *AteomService) terminateWorkload(ctx context.Context, actor resources.Ac if err := s.stopActorVM(ctx, actorUID, actorDirs); err != nil { errs = append(errs, fmt.Errorf("while tearing down actor: %w", err)) } + if err := volumebind.Release(actorDirs.GetVolumesDir(), containers); err != nil { + errs = append(errs, fmt.Errorf("while unbinding volume directories: %w", err)) + } // Remove attribution after teardown; a failed checkpoint may leave the VM running. if err := s.unhostActor(ctx, actorUID); err != nil { errs = append(errs, fmt.Errorf("while cleaning up actor network: %w", err)) diff --git a/cmd/ateom-microvm/run.go b/cmd/ateom-microvm/run.go index 490e712eb0..d8706670c0 100644 --- a/cmd/ateom-microvm/run.go +++ b/cmd/ateom-microvm/run.go @@ -43,6 +43,7 @@ import ( "github.com/agent-substrate/substrate/internal/proto/ateompb" "github.com/agent-substrate/substrate/internal/resources" "github.com/agent-substrate/substrate/internal/sizing" + "github.com/agent-substrate/substrate/internal/volumebind" "github.com/agent-substrate/substrate/internal/wakeupprobe" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/sys/unix" @@ -697,6 +698,11 @@ func (s *AteomService) stageMergedRootfs(ctx context.Context, rr resolvedRuntime } } if hasCsiVolumes(containers) { + // The directories mounts name a sub-path of, or mount read-only, are + // bound beside their volumes first, so the share carries them too. + if err := volumebind.Prepare(actorDirs.GetVolumesDir(), containers); err != nil { + return nil, fmt.Errorf("while binding volume directories: %w", err) + } if err := s.stageCsiVolumes(ctx, id, actorDirs.GetVolumesDir()); err != nil { return nil, fmt.Errorf("while staging CSI volumes: %w", err) } diff --git a/docs/csi-volumes.md b/docs/csi-volumes.md index d33bb83c08..6eda80ba22 100644 --- a/docs/csi-volumes.md +++ b/docs/csi-volumes.md @@ -99,6 +99,50 @@ volumeMounts: --- +### Existing Volumes: One Volume, Many Actors + +A volume that exists outside Substrate and outlives its actors, such as a read-write-many workspace several actors work on, is an **existing volume**. The template declares it by name and mounts it; each actor supplies it at `CreateActor`: + +```yaml +volumes: +- name: workspace + existingVolume: {} +- name: mirrors + existingVolume: {} +containers: +- name: agent + volumeMounts: + - name: workspace + mountPath: /workspace + - name: mirrors + mountPath: /mirrors + readOnly: true +``` + +```yaml +existingVolumes: +- name: workspace + driver: nfs.csi.k8s.io + volumeHandle: + accessMode: VOLUME_ACCESS_MODE_READ_WRITE_MANY + subPath: sessions/a +- name: mirrors + driver: nfs.csi.k8s.io + volumeHandle: + accessMode: VOLUME_ACCESS_MODE_READ_ONLY_MANY + subPath: mirrors +``` + +* An existing volume's `subPath` is the directory this actor sees as the volume's root; a mount's own `subPath` is relative to it. So one template serves every actor, each in a directory of its own. One volume may be mounted at several paths. +* The directory must exist on the volume: Substrate never creates one. It is resolved beneath the volume's root without following any symbolic link, so a link that another actor wrote on the volume fails the mount rather than redirecting it. +* `readOnly` mounts read-only, and so does every mount of a `READ_ONLY_MANY` volume. +* `CreateActor` refuses a reference that names no existing volume of the template, a driver without a `CSIDriverConfig`, a handle that no PersistentVolume of the driver holds, and an access mode the PersistentVolume does not permit. The PersistentVolume's `spec.csi.volumeAttributes` are passed to the driver when the volume is mounted, so it must still exist when the actor resumes. +* An existing volume of the template that the actor does not supply contributes neither the volume nor its mounts. +* Substrate never creates, deletes or changes an existing volume, and never detaches one from a node, where other actors may be using it. Pause, resume (on another node too) and delete only unmount and mount it. +* An actor with existing volumes boots from its image instead of the template's golden snapshot, which was captured without their mounts, and cannot be created from a tag. `existingVolumes` is immutable. + +--- + ## 4. End-to-End Example The following example demonstrates setting up an NFS CSI driver with Substrate and deploying an `ActorTemplate` that mounts an external NFS volume. diff --git a/internal/e2e/suites/existingvolumes/existingvolumes_test.go b/internal/e2e/suites/existingvolumes/existingvolumes_test.go new file mode 100644 index 0000000000..494e9c8798 --- /dev/null +++ b/internal/e2e/suites/existingvolumes/existingvolumes_test.go @@ -0,0 +1,441 @@ +// 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 existingvolumes exercises existing volumes against a live Kind +// cluster with the CSI NFS driver (`ate-setup setup csi nfs`): a +// read-write-many volume is provisioned and filled outside Substrate, as a +// Kubernetes PVC a pod prepares, and actors mount directories of it. +package existingvolumes + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/agent-substrate/substrate/internal/e2e" + "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + atespace = "existingvolumes" + driver = "nfs.csi.k8s.io" + + workspaceVolume = "workspace" + mirrorsVolume = "mirrors" + workspacePath = "/workspace" + mirrorsPath = "/mirrors" + + mirrorFile = "mirror.txt" + mirrorContent = "a file of the mirrors" + + // probeWrittenContent is the fixed string the probe's /writefile writes. + probeWrittenContent = "written by probe" + + busybox = "busybox@sha256:1487d0af5f52b4ba31c7e465126ee2123fe3f2305d638e7827681e7cf6c83d5e" +) + +// requireNFS skips unless the cluster has the NFS StorageClass. A lane that +// installed it sets E2E_CSI_NFS=1, and there a missing one fails the suite +// instead: a skip there would pass having run nothing. +func requireNFS(ctx context.Context, t *testing.T, clients *e2e.Clients) { + t.Helper() + missing := t.Skipf + if os.Getenv("E2E_CSI_NFS") == "1" { + missing = t.Fatalf + } + if _, err := clients.K8s.StorageV1().StorageClasses().Get(ctx, e2e.StorageClass, metav1.GetOptions{}); err != nil { + missing("StorageClass %q not found (%v); install it with `ate-setup setup csi nfs`", e2e.StorageClass, err) + } +} + +// eventually polls check until it returns true or the timeout passes. +func eventually(t *testing.T, timeout time.Duration, what string, check func() (bool, error)) { + t.Helper() + deadline := time.Now().Add(timeout) + for { + ok, err := check() + if ok { + return + } + if time.Now().After(deadline) { + t.Fatalf("timed out after %v waiting for %s (last error: %v)", timeout, what, err) + } + time.Sleep(2 * time.Second) + } +} + +// runOnVolume runs script in a pod that mounts the claim at /data and +// returns its output. +func runOnVolume(ctx context.Context, t *testing.T, clients *e2e.Clients, ns, claim, name, script string) string { + t.Helper() + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{{ + Name: "sh", + Image: busybox, + Command: []string{"sh", "-c", script}, + VolumeMounts: []corev1.VolumeMount{{Name: "data", MountPath: "/data"}}, + }}, + Volumes: []corev1.Volume{{ + Name: "data", + VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: claim}}, + }}, + }, + } + if _, err := clients.K8s.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}); err != nil { + t.Fatalf("creating pod %s: %v", name, err) + } + eventually(t, 3*time.Minute, "pod "+name+" to succeed", func() (bool, error) { + got, err := clients.K8s.CoreV1().Pods(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, err + } + if got.Status.Phase == corev1.PodFailed { + t.Fatalf("pod %s failed: %+v", name, got.Status) + } + return got.Status.Phase == corev1.PodSucceeded, fmt.Errorf("phase %s", got.Status.Phase) + }) + out, err := clients.K8s.CoreV1().Pods(ns).GetLogs(name, &corev1.PodLogOptions{}).Do(ctx).Raw() + if err != nil { + t.Fatalf("reading the logs of pod %s: %v", name, err) + } + return string(out) +} + +// prepareVolume provisions a read-write-many PVC, lays out a session +// directory per actor the probe may write and a mirrors directory with +// mirrorFile, and returns the claim and its PersistentVolume's name and handle. +func prepareVolume(ctx context.Context, t *testing.T, clients *e2e.Clients, ns string) (claim, pvName, handle string) { + t.Helper() + pvc := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{Name: "workspace", Namespace: ns}, + Spec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}, + StorageClassName: &e2e.StorageClass, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{corev1.ResourceStorage: resource.MustParse("1Gi")}, + }, + }, + } + if _, err := clients.K8s.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{}); err != nil { + t.Fatalf("creating PVC: %v", err) + } + runOnVolume(ctx, t, clients, ns, pvc.Name, "prepare", fmt.Sprintf( + "mkdir -m 0777 -p /data/sessions/a /data/sessions/b && mkdir -p /data/mirrors && printf %%s %q > /data/mirrors/%s && sync", + mirrorContent, mirrorFile)) + + bound, err := clients.K8s.CoreV1().PersistentVolumeClaims(ns).Get(ctx, pvc.Name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("reading PVC: %v", err) + } + pv, err := clients.K8s.CoreV1().PersistentVolumes().Get(ctx, bound.Spec.VolumeName, metav1.GetOptions{}) + if err != nil { + t.Fatalf("reading PersistentVolume %q: %v", bound.Spec.VolumeName, err) + } + if pv.Spec.CSI == nil || pv.Spec.CSI.Driver != driver { + t.Fatalf("PersistentVolume %q is not a volume of %s: %+v", pv.Name, driver, pv.Spec.PersistentVolumeSource) + } + return pvc.Name, pv.Name, pv.Spec.CSI.VolumeHandle +} + +// createTemplate builds a probe ActorTemplate that mounts the existing +// volume "workspace" at workspacePath and "mirrors" read-only at mirrorsPath. +func createTemplate(ctx context.Context, t *testing.T, clients *e2e.Clients, ns *e2e.Namespace) *ateapipb.ActorTemplate { + t.Helper() + env, err := e2e.CheckEnv("BUCKET_NAME") + if err != nil { + t.Fatalf("CheckEnv: %v", err) + } + probeAtespace, _ := e2e.DeployProbe(t, env["BUCKET_NAME"], "existingvolumes") + src := e2e.SubstrateFixture{ + Atespace: probeAtespace, + Name: e2e.ProbeName, + PoolNamespace: probeAtespace, + PoolName: e2e.ProbeName, + DeployWith: "the existingvolumes suite's own DeployProbe", + } + return e2e.CreateSubstrateTemplateFrom(ctx, t, clients, ns.Name, src, e2e.SubstrateTemplateOptions{ + Atespace: atespace, + Name: "probe-" + ns.Name, + PoolName: e2e.ProbeName, + PoolReplicas: 3, + Labels: map[string]string{"existingvolumes": ns.Name}, + SnapshotConfig: &ateapipb.SnapshotConfig{ + StorageLocation: fmt.Sprintf("gs://%s/%s/", env["BUCKET_NAME"], ns.Name), + }, + Modify: func(tmpl *ateapipb.ActorTemplate) { + tmpl.Containers[0].VolumeMounts = append(tmpl.Containers[0].VolumeMounts, + &ateapipb.VolumeMount{Name: workspaceVolume, MountPath: workspacePath}, + &ateapipb.VolumeMount{Name: mirrorsVolume, MountPath: mirrorsPath, ReadOnly: true}) + tmpl.Volumes = append(tmpl.Volumes, + &ateapipb.Volume{Name: workspaceVolume, ExistingVolume: &ateapipb.ExistingVolumeSource{}}, + &ateapipb.Volume{Name: mirrorsVolume, ExistingVolume: &ateapipb.ExistingVolumeSource{}}) + }, + }) +} + +// sessionVolumes supply both existing volumes from one handle: the +// session's own directory read-write and the mirrors read-only. +func sessionVolumes(handle, session string) []*ateapipb.ExistingVolume { + return []*ateapipb.ExistingVolume{ + {Name: workspaceVolume, Driver: driver, VolumeHandle: handle, AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_MANY, SubPath: "sessions/" + session}, + {Name: mirrorsVolume, Driver: driver, VolumeHandle: handle, AccessMode: ateapipb.VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY, SubPath: "mirrors"}, + } +} + +func createActor(ctx context.Context, clients *e2e.Clients, tmpl *ateapipb.ActorTemplate, ref resources.ActorRef, evs []*ateapipb.ExistingVolume) (*ateapipb.Actor, error) { + return clients.SubstrateAPI.CreateActor(ctx, &ateapipb.CreateActorRequest{ + Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: ref.Atespace, Name: ref.Name}, + ActorTemplate: e2e.TemplateRef(tmpl), + ExistingVolumes: evs, + }, + }) +} + +// deleteActorAtEnd registers the deletion of an actor when the whole suite +// ends: a later subtest uses an actor an earlier one started. +func deleteActorAtEnd(t *testing.T, clients *e2e.Clients, ref resources.ActorRef) { + t.Cleanup(func() { + cleanupCtx := context.Background() + _, _ = clients.SubstrateAPI.SuspendActor(cleanupCtx, &ateapipb.SuspendActorRequest{Actor: ref.ToObjectRef()}) + _, _ = clients.SubstrateAPI.DeleteActor(cleanupCtx, &ateapipb.DeleteActorRequest{Actor: ref.ToObjectRef()}) + }) +} + +// startActor creates and resumes an actor. +func startActor(ctx context.Context, t *testing.T, clients *e2e.Clients, tmpl *ateapipb.ActorTemplate, ref resources.ActorRef, evs []*ateapipb.ExistingVolume) { + t.Helper() + if _, err := createActor(ctx, clients, tmpl, ref, evs); err != nil { + t.Fatalf("CreateActor %s: %v", ref, err) + } + if _, err := e2e.ResumeActorAwaitCapacity(t, ctx, clients, &ateapipb.ResumeActorRequest{Actor: ref.ToObjectRef()}); err != nil { + t.Fatalf("ResumeActor %s: %v", ref, err) + } +} + +func probeJSON(ctx context.Context, t *testing.T, router *e2e.RouterClient, ref resources.ActorRef, path string) map[string]string { + t.Helper() + resp, err := router.Get(ctx, ref, path) + if err != nil { + t.Fatalf("GET %s on %s: %v", path, ref, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + t.Fatalf("GET %s on %s: status %d: %s", path, ref, resp.StatusCode, body) + } + var out map[string]string + if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { + t.Fatalf("decoding %s: %v", path, err) + } + return out +} + +func requireContent(ctx context.Context, t *testing.T, router *e2e.RouterClient, ref resources.ActorRef, path, want string) { + t.Helper() + got := probeJSON(ctx, t, router, ref, "/readfile?path="+path) + if got["error"] != "" { + t.Fatalf("%s: reading %s: %s", ref, path, got["error"]) + } + if got["content"] != want { + t.Errorf("%s: content at %s = %q, want %q", ref, path, got["content"], want) + } +} + +func requireAbsent(ctx context.Context, t *testing.T, router *e2e.RouterClient, ref resources.ActorRef, path string) { + t.Helper() + if got := probeJSON(ctx, t, router, ref, "/readfile?path="+path); got["error"] == "" { + t.Errorf("%s: %s is readable (%q), want it absent", ref, path, got["content"]) + } +} + +func requireWrite(ctx context.Context, t *testing.T, router *e2e.RouterClient, ref resources.ActorRef, path string) { + t.Helper() + if got := probeJSON(ctx, t, router, ref, "/writefile?path="+path); got["error"] != "" { + t.Fatalf("%s: writing %s: %s", ref, path, got["error"]) + } +} + +func requireWriteRefused(ctx context.Context, t *testing.T, router *e2e.RouterClient, ref resources.ActorRef, path string) { + t.Helper() + got := probeJSON(ctx, t, router, ref, "/writefile?path="+path) + if got["error"] == "" { + t.Fatalf("%s: writing %s succeeded, want it refused", ref, path) + } + t.Logf("%s: write to %s refused as expected: %s", ref, path, got["error"]) +} + +// volumeFiles lists the regular files on the volume, relative to its root. +func volumeFiles(ctx context.Context, t *testing.T, clients *e2e.Clients, ns, claim, name string) []string { + t.Helper() + out := runOnVolume(ctx, t, clients, ns, claim, name, "cd /data && find . -type f | sort") + return strings.Fields(out) +} + +func requireFiles(t *testing.T, got []string, want ...string) { + t.Helper() + for _, w := range want { + found := false + for _, g := range got { + if g == w { + found = true + } + } + if !found { + t.Errorf("volume files %v lack %s", got, w) + } + } +} + +func TestExistingVolumes(t *testing.T) { + ctx := context.Background() + clients := e2e.GetClients() + requireNFS(ctx, t, clients) + + ns := e2e.CreateNamespace(t) + claim, pvName, handle := prepareVolume(ctx, t, clients, ns.Name) + t.Logf("existing volume: PersistentVolume %s, handle %s", pvName, handle) + tmpl := createTemplate(ctx, t, clients, ns) + + router, err := e2e.NewRouterClient(ctx) + if err != nil { + t.Fatalf("NewRouterClient: %v", err) + } + defer router.Close() + + actorA := resources.ActorRef{Atespace: atespace, Name: "session-a-" + ns.Name} + actorB := resources.ActorRef{Atespace: atespace, Name: "session-b-" + ns.Name} + plain := resources.ActorRef{Atespace: atespace, Name: "plain-" + ns.Name} + for _, ref := range []resources.ActorRef{actorA, actorB, plain} { + deleteActorAtEnd(t, clients, ref) + } + mirrorPath := mirrorsPath + "/" + mirrorFile + + t.Run("MountsSubPathsAtDeclaredPaths", func(t *testing.T) { + startActor(ctx, t, clients, tmpl, actorA, sessionVolumes(handle, "a")) + requireContent(ctx, t, router, actorA, mirrorPath, mirrorContent) + requireWrite(ctx, t, router, actorA, workspacePath+"/a.txt") + requireContent(ctx, t, router, actorA, workspacePath+"/a.txt", probeWrittenContent) + requireFiles(t, volumeFiles(ctx, t, clients, ns.Name, claim, "inspect-a"), "./sessions/a/a.txt", "./mirrors/"+mirrorFile) + }) + + t.Run("ActorsWriteOnlyTheirOwnSubPath", func(t *testing.T) { + startActor(ctx, t, clients, tmpl, actorB, sessionVolumes(handle, "b")) + requireContent(ctx, t, router, actorB, mirrorPath, mirrorContent) + requireAbsent(ctx, t, router, actorB, workspacePath+"/a.txt") + requireWrite(ctx, t, router, actorB, workspacePath+"/b.txt") + requireWriteRefused(ctx, t, router, actorB, mirrorsPath+"/b.txt") + requireWriteRefused(ctx, t, router, actorA, mirrorsPath+"/a.txt") + files := volumeFiles(ctx, t, clients, ns.Name, claim, "inspect-b") + requireFiles(t, files, "./sessions/a/a.txt", "./sessions/b/b.txt") + for _, f := range files { + if strings.HasPrefix(f, "./mirrors/") && f != "./mirrors/"+mirrorFile { + t.Errorf("the read-only mount wrote %s", f) + } + } + }) + + t.Run("NoReferenceNoMounts", func(t *testing.T) { + startActor(ctx, t, clients, tmpl, plain, nil) + requireAbsent(ctx, t, router, plain, mirrorPath) + if got := probeJSON(ctx, t, router, plain, "/writefile?path="+workspacePath+"/plain.txt"); got["error"] == "" { + t.Errorf("%s wrote %s/plain.txt: the unsupplied volume is mounted", plain, workspacePath) + } + }) + + t.Run("PauseResumeDeleteLeaveTheVolume", func(t *testing.T) { + if _, err := clients.SubstrateAPI.PauseActor(ctx, &ateapipb.PauseActorRequest{Actor: actorA.ToObjectRef()}); err != nil { + t.Fatalf("PauseActor: %v", err) + } + eventually(t, 2*time.Minute, "actor A to pause", func() (bool, error) { + actor, err := clients.SubstrateAPI.GetActor(ctx, &ateapipb.GetActorRequest{Actor: actorA.ToObjectRef()}) + if err != nil { + return false, err + } + state := actor.GetStatus().GetState() + return state == ateapipb.ActorState_ACTOR_STATE_PAUSED, fmt.Errorf("state %v", state) + }) + if _, err := e2e.ResumeActorAwaitCapacity(t, ctx, clients, &ateapipb.ResumeActorRequest{Actor: actorA.ToObjectRef()}); err != nil { + t.Fatalf("ResumeActor: %v", err) + } + requireContent(ctx, t, router, actorA, workspacePath+"/a.txt", probeWrittenContent) + requireContent(ctx, t, router, actorA, mirrorPath, mirrorContent) + + // A running actor is not deletable; the suspend unmounts its volumes. + if _, err := clients.SubstrateAPI.SuspendActor(ctx, &ateapipb.SuspendActorRequest{Actor: actorA.ToObjectRef()}); err != nil { + t.Fatalf("SuspendActor: %v", err) + } + if _, err := clients.SubstrateAPI.DeleteActor(ctx, &ateapipb.DeleteActorRequest{Actor: actorA.ToObjectRef()}); err != nil { + t.Fatalf("DeleteActor: %v", err) + } + eventually(t, 2*time.Minute, "actor A to be gone", func() (bool, error) { + _, err := clients.SubstrateAPI.GetActor(ctx, &ateapipb.GetActorRequest{Actor: actorA.ToObjectRef()}) + return status.Code(err) == codes.NotFound, fmt.Errorf("GetActor: %v", err) + }) + if _, err := clients.K8s.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}); err != nil { + t.Fatalf("PersistentVolume %s after the delete: %v", pvName, err) + } + requireFiles(t, volumeFiles(ctx, t, clients, ns.Name, claim, "inspect-after-delete"), "./sessions/a/a.txt", "./sessions/b/b.txt", "./mirrors/"+mirrorFile) + // The other actor still works on the volume. + requireContent(ctx, t, router, actorB, workspacePath+"/b.txt", probeWrittenContent) + }) + + t.Run("RefusesBadReferencesAtCreate", func(t *testing.T) { + for _, tc := range []struct { + name string + mod func(ev *ateapipb.ExistingVolume) + wantCode codes.Code + wantMsg string + }{ + { + name: "unknown driver", + mod: func(ev *ateapipb.ExistingVolume) { ev.Driver = "unknown.csi.example.com" }, + wantCode: codes.FailedPrecondition, + wantMsg: `unknown driver "unknown.csi.example.com"`, + }, + { + name: "missing volume", + mod: func(ev *ateapipb.ExistingVolume) { ev.VolumeHandle = "no-such-volume" }, + wantCode: codes.FailedPrecondition, + wantMsg: `no PersistentVolume of driver "nfs.csi.k8s.io" holds volume "no-such-volume"`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + evs := sessionVolumes(handle, "refused") + tc.mod(evs[0]) + ref := resources.ActorRef{Atespace: atespace, Name: "refused-" + ns.Name} + _, err := createActor(ctx, clients, tmpl, ref, evs) + if status.Code(err) != tc.wantCode || !strings.Contains(err.Error(), tc.wantMsg) { + t.Fatalf("CreateActor = %v, want %v containing %q", err, tc.wantCode, tc.wantMsg) + } + t.Logf("refused as expected: %v", err) + }) + } + }) +} diff --git a/internal/e2e/suites/existingvolumes/testmain_test.go b/internal/e2e/suites/existingvolumes/testmain_test.go new file mode 100644 index 0000000000..ff99ca8e29 --- /dev/null +++ b/internal/e2e/suites/existingvolumes/testmain_test.go @@ -0,0 +1,24 @@ +// 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 existingvolumes + +import ( + "os" + "testing" + + "github.com/agent-substrate/substrate/internal/e2e" +) + +func TestMain(m *testing.M) { os.Exit(e2e.RunTestMain(m)) } diff --git a/internal/ocispec/ocispec.go b/internal/ocispec/ocispec.go index 09a8a79b8f..23d1feae1f 100644 --- a/internal/ocispec/ocispec.go +++ b/internal/ocispec/ocispec.go @@ -26,6 +26,7 @@ import ( "github.com/agent-substrate/substrate/internal/imagecache" "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "github.com/agent-substrate/substrate/internal/volumebind" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -182,7 +183,10 @@ func Build(o Options) *specs.Spec { case *ateletpb.Volume_DurableDir: srcPath = filepath.Join(o.DurableDirVolumeMountsDir, vm.GetName()) case *ateletpb.Volume_External: - srcPath = filepath.Join(o.VolumesDir, vm.GetName()) + srcPath = filepath.Join(o.VolumesDir, volumebind.MountDir(vm.GetName(), vm.GetSubPath(), vm.GetReadOnly())) + if vm.GetReadOnly() { + options = []string{"bind", "ro"} + } case *ateletpb.Volume_SystemInfo: srcPath = filepath.Join(o.SystemInfoVolumeRootsDir, vm.GetName()) options = []string{"bind", "ro"} diff --git a/internal/ocispec/ocispec_test.go b/internal/ocispec/ocispec_test.go index f6cb3878ae..98951e2eb2 100644 --- a/internal/ocispec/ocispec_test.go +++ b/internal/ocispec/ocispec_test.go @@ -21,6 +21,7 @@ import ( "github.com/agent-substrate/substrate/internal/imagecache" "github.com/agent-substrate/substrate/internal/proto/ateletpb" + "github.com/agent-substrate/substrate/internal/volumebind" "github.com/opencontainers/runtime-spec/specs-go" ) @@ -62,6 +63,8 @@ func TestBuild_VolumeMounts(t *testing.T) { {Name: "data", MountPath: "/home/counter"}, {Name: "sysinfo", MountPath: "/run/ate"}, {Name: "csi", MountPath: "/mnt/csi"}, + {Name: "csi", MountPath: "/workspace", SubPath: "sessions/a"}, + {Name: "csi", MountPath: "/mirrors", SubPath: "mirrors", ReadOnly: true}, {Name: "agent", MountPath: "/ate"}, }, DurableDirVolumeMountsDir: durableDir, @@ -79,6 +82,8 @@ func TestBuild_VolumeMounts(t *testing.T) { {"/home/counter", durableDir + "/data", []string{"bind", "rw"}}, {"/run/ate", sysInfoDir + "/sysinfo", []string{"bind", "ro"}}, {"/mnt/csi", volumesDir + "/csi", []string{"bind", "rw"}}, + {"/workspace", volumesDir + "/" + volumebind.MountDir("csi", "sessions/a", false), []string{"bind", "rw"}}, + {"/mirrors", volumesDir + "/" + volumebind.MountDir("csi", "mirrors", true), []string{"bind", "ro"}}, {"/ate", imagecache.ImageVolumeMountPath(bundle, "agent"), []string{"bind", "ro"}}, } { m := mountFor(t, spec, tc.dest) diff --git a/internal/proto/ateletpb/atelet.pb.go b/internal/proto/ateletpb/atelet.pb.go index e2e3123b24..456ba9976a 100644 --- a/internal/proto/ateletpb/atelet.pb.go +++ b/internal/proto/ateletpb/atelet.pb.go @@ -35,6 +35,59 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// VolumeAccessMode mirrors ateapi.VolumeAccessMode. +type VolumeAccessMode int32 + +const ( + VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED VolumeAccessMode = 0 + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE VolumeAccessMode = 1 + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY VolumeAccessMode = 2 + 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_atelet_proto_enumTypes[0].Descriptor() +} + +func (VolumeAccessMode) Type() protoreflect.EnumType { + return &file_atelet_proto_enumTypes[0] +} + +func (x VolumeAccessMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use VolumeAccessMode.Descriptor instead. +func (VolumeAccessMode) EnumDescriptor() ([]byte, []int) { + return file_atelet_proto_rawDescGZIP(), []int{0} +} + // ActorMetadataField selects one identity field of the actor. type ActorMetadataField int32 @@ -72,11 +125,11 @@ func (x ActorMetadataField) String() string { } func (ActorMetadataField) Descriptor() protoreflect.EnumDescriptor { - return file_atelet_proto_enumTypes[0].Descriptor() + return file_atelet_proto_enumTypes[1].Descriptor() } func (ActorMetadataField) Type() protoreflect.EnumType { - return &file_atelet_proto_enumTypes[0] + return &file_atelet_proto_enumTypes[1] } func (x ActorMetadataField) Number() protoreflect.EnumNumber { @@ -85,7 +138,7 @@ func (x ActorMetadataField) Number() protoreflect.EnumNumber { // Deprecated: Use ActorMetadataField.Descriptor instead. func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{0} + return file_atelet_proto_rawDescGZIP(), []int{1} } type CheckpointType int32 @@ -124,11 +177,11 @@ func (x CheckpointType) String() string { } func (CheckpointType) Descriptor() protoreflect.EnumDescriptor { - return file_atelet_proto_enumTypes[1].Descriptor() + return file_atelet_proto_enumTypes[2].Descriptor() } func (CheckpointType) Type() protoreflect.EnumType { - return &file_atelet_proto_enumTypes[1] + return &file_atelet_proto_enumTypes[2] } func (x CheckpointType) Number() protoreflect.EnumNumber { @@ -137,7 +190,7 @@ func (x CheckpointType) Number() protoreflect.EnumNumber { // Deprecated: Use CheckpointType.Descriptor instead. func (CheckpointType) EnumDescriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{1} + return file_atelet_proto_rawDescGZIP(), []int{2} } type SnapshotScope int32 @@ -179,11 +232,11 @@ func (x SnapshotScope) String() string { } func (SnapshotScope) Descriptor() protoreflect.EnumDescriptor { - return file_atelet_proto_enumTypes[2].Descriptor() + return file_atelet_proto_enumTypes[3].Descriptor() } func (SnapshotScope) Type() protoreflect.EnumType { - return &file_atelet_proto_enumTypes[2] + return &file_atelet_proto_enumTypes[3] } func (x SnapshotScope) Number() protoreflect.EnumNumber { @@ -192,7 +245,7 @@ func (x SnapshotScope) Number() protoreflect.EnumNumber { // Deprecated: Use SnapshotScope.Descriptor instead. func (SnapshotScope) EnumDescriptor() ([]byte, []int) { - return file_atelet_proto_rawDescGZIP(), []int{2} + return file_atelet_proto_rawDescGZIP(), []int{3} } type SetWorkerCapacityRequest struct { @@ -1263,8 +1316,10 @@ type ExternalVolumeSource struct { 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"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // access_mode is how the volume is published; unset is READ_WRITE_ONCE. + AccessMode VolumeAccessMode `protobuf:"varint,4,opt,name=access_mode,json=accessMode,proto3,enum=atelet.VolumeAccessMode" json:"access_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ExternalVolumeSource) Reset() { @@ -1318,6 +1373,13 @@ func (x *ExternalVolumeSource) GetVolumeContext() map[string]string { return nil } +func (x *ExternalVolumeSource) GetAccessMode() VolumeAccessMode { + if x != nil { + return x.AccessMode + } + return VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED +} + type ImageVolumeSource struct { state protoimpl.MessageState `protogen:"open.v1"` Reference string `protobuf:"bytes,1,opt,name=reference,proto3" json:"reference,omitempty"` @@ -1767,9 +1829,14 @@ func (*Volume_SystemInfo) isVolume_Source() {} func (*Volume_Image) isVolume_Source() {} type VolumeMount struct { - state protoimpl.MessageState `protogen:"open.v1"` - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + // sub_path is the directory of an external volume to mount, relative to + // its root; empty mounts the root. + SubPath string `protobuf:"bytes,3,opt,name=sub_path,json=subPath,proto3" json:"sub_path,omitempty"` + // read_only mounts an external volume read-only. + ReadOnly bool `protobuf:"varint,4,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1818,6 +1885,20 @@ func (x *VolumeMount) GetMountPath() string { return "" } +func (x *VolumeMount) GetSubPath() string { + if x != nil { + return x.SubPath + } + return "" +} + +func (x *VolumeMount) GetReadOnly() bool { + if x != nil { + return x.ReadOnly + } + return false +} + type Container struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -3188,12 +3269,14 @@ const file_atelet_proto_rawDesc = "" + "containers\x18\x01 \x03(\v2\x11.atelet.ContainerR\n" + "containers\x12(\n" + "\avolumes\x18\x02 \x03(\v2\x0e.atelet.VolumeR\avolumes\"\x12\n" + - "\x10DurableDirVolume\"\xfd\x01\n" + + "\x10DurableDirVolume\"\xb8\x02\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\x129\n" + + "\vaccess_mode\x18\x04 \x01(\x0e2\x18.atelet.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\"1\n" + @@ -3221,11 +3304,13 @@ const file_atelet_proto_rawDesc = "" + "\vsystem_info\x18\x04 \x01(\v2\x18.atelet.SystemInfoVolumeH\x00R\n" + "systemInfo\x121\n" + "\x05image\x18\x05 \x01(\v2\x19.atelet.ImageVolumeSourceH\x00R\x05imageB\b\n" + - "\x06source\"@\n" + + "\x06source\"x\n" + "\vVolumeMount\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + "\n" + - "mount_path\x18\x02 \x01(\tR\tmountPath\"\xf3\x02\n" + + "mount_path\x18\x02 \x01(\tR\tmountPath\x12\x19\n" + + "\bsub_path\x18\x03 \x01(\tR\asubPath\x12\x1b\n" + + "\tread_only\x18\x04 \x01(\bR\breadOnly\"\xf3\x02\n" + "\tContainer\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05image\x18\x02 \x01(\tR\x05image\x12\x18\n" + @@ -3316,7 +3401,12 @@ const file_atelet_proto_rawDesc = "" + "\x0esandbox_assets\x18\x10 \x01(\v2\x15.atelet.SandboxAssetsR\rsandboxAssetsB\b\n" + "\x06configB\x11\n" + "\x0f_egress_gateway\"\x11\n" + - "\x0fRestoreResponse*\x9a\x01\n" + + "\x0fRestoreResponse*\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*\x9a\x01\n" + "\x12ActorMetadataField\x12$\n" + " ACTOR_METADATA_FIELD_UNSPECIFIED\x10\x00\x12\x1d\n" + "\x19ACTOR_METADATA_FIELD_NAME\x10\x01\x12!\n" + @@ -3355,129 +3445,131 @@ func file_atelet_proto_rawDescGZIP() []byte { return file_atelet_proto_rawDescData } -var file_atelet_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_atelet_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_atelet_proto_msgTypes = make([]protoimpl.MessageInfo, 49) var file_atelet_proto_goTypes = []any{ - (ActorMetadataField)(0), // 0: atelet.ActorMetadataField - (CheckpointType)(0), // 1: atelet.CheckpointType - (SnapshotScope)(0), // 2: atelet.SnapshotScope - (*SetWorkerCapacityRequest)(nil), // 3: atelet.SetWorkerCapacityRequest - (*WorkerResources)(nil), // 4: atelet.WorkerResources - (*Resources)(nil), // 5: atelet.Resources - (*Limits)(nil), // 6: atelet.Limits - (*SetWorkerCapacityResponse)(nil), // 7: atelet.SetWorkerCapacityResponse - (*RequestActorSuspendRequest)(nil), // 8: atelet.RequestActorSuspendRequest - (*RequestActorSuspendResponse)(nil), // 9: atelet.RequestActorSuspendResponse - (*MintActorCertificateRequest)(nil), // 10: atelet.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 11: atelet.MintActorCertificateResponse - (*TerminateRequest)(nil), // 12: atelet.TerminateRequest - (*TerminateResponse)(nil), // 13: atelet.TerminateResponse - (*RunRequest)(nil), // 14: atelet.RunRequest - (*EgressGateway)(nil), // 15: atelet.EgressGateway - (*AssetFile)(nil), // 16: atelet.AssetFile - (*ArchAssets)(nil), // 17: atelet.ArchAssets - (*SandboxAssets)(nil), // 18: atelet.SandboxAssets - (*WorkloadSpec)(nil), // 19: atelet.WorkloadSpec - (*DurableDirVolume)(nil), // 20: atelet.DurableDirVolume - (*ExternalVolumeSource)(nil), // 21: atelet.ExternalVolumeSource - (*ImageVolumeSource)(nil), // 22: atelet.ImageVolumeSource - (*ActorMetadataItem)(nil), // 23: atelet.ActorMetadataItem - (*ActorMetadataDataSource)(nil), // 24: atelet.ActorMetadataDataSource - (*TrustBundleDataSource)(nil), // 25: atelet.TrustBundleDataSource - (*SystemInfoDataSource)(nil), // 26: atelet.SystemInfoDataSource - (*SystemInfoVolume)(nil), // 27: atelet.SystemInfoVolume - (*Volume)(nil), // 28: atelet.Volume - (*VolumeMount)(nil), // 29: atelet.VolumeMount - (*Container)(nil), // 30: atelet.Container - (*SecurityContext)(nil), // 31: atelet.SecurityContext - (*Capabilities)(nil), // 32: atelet.Capabilities - (*ResourceLimits)(nil), // 33: atelet.ResourceLimits - (*EnvEntry)(nil), // 34: atelet.EnvEntry - (*WakeupProbe)(nil), // 35: atelet.WakeupProbe - (*HTTPGetAction)(nil), // 36: atelet.HTTPGetAction - (*RunResponse)(nil), // 37: atelet.RunResponse - (*LocalCheckpointConfiguration)(nil), // 38: atelet.LocalCheckpointConfiguration - (*ExternalCheckpointConfiguration)(nil), // 39: atelet.ExternalCheckpointConfiguration - (*ExternalRestoreConfiguration)(nil), // 40: atelet.ExternalRestoreConfiguration - (*CheckpointRequest)(nil), // 41: atelet.CheckpointRequest - (*CheckpointResponse)(nil), // 42: atelet.CheckpointResponse - (*UploadPausedCheckpointRequest)(nil), // 43: atelet.UploadPausedCheckpointRequest - (*UploadPausedCheckpointResponse)(nil), // 44: atelet.UploadPausedCheckpointResponse - (*PruneLocalCheckpointsRequest)(nil), // 45: atelet.PruneLocalCheckpointsRequest - (*PruneLocalCheckpointsResponse)(nil), // 46: atelet.PruneLocalCheckpointsResponse - (*RestoreRequest)(nil), // 47: atelet.RestoreRequest - (*RestoreResponse)(nil), // 48: atelet.RestoreResponse - nil, // 49: atelet.ArchAssets.FilesEntry - nil, // 50: atelet.SandboxAssets.AssetsEntry - nil, // 51: atelet.ExternalVolumeSource.VolumeContextEntry + (VolumeAccessMode)(0), // 0: atelet.VolumeAccessMode + (ActorMetadataField)(0), // 1: atelet.ActorMetadataField + (CheckpointType)(0), // 2: atelet.CheckpointType + (SnapshotScope)(0), // 3: atelet.SnapshotScope + (*SetWorkerCapacityRequest)(nil), // 4: atelet.SetWorkerCapacityRequest + (*WorkerResources)(nil), // 5: atelet.WorkerResources + (*Resources)(nil), // 6: atelet.Resources + (*Limits)(nil), // 7: atelet.Limits + (*SetWorkerCapacityResponse)(nil), // 8: atelet.SetWorkerCapacityResponse + (*RequestActorSuspendRequest)(nil), // 9: atelet.RequestActorSuspendRequest + (*RequestActorSuspendResponse)(nil), // 10: atelet.RequestActorSuspendResponse + (*MintActorCertificateRequest)(nil), // 11: atelet.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 12: atelet.MintActorCertificateResponse + (*TerminateRequest)(nil), // 13: atelet.TerminateRequest + (*TerminateResponse)(nil), // 14: atelet.TerminateResponse + (*RunRequest)(nil), // 15: atelet.RunRequest + (*EgressGateway)(nil), // 16: atelet.EgressGateway + (*AssetFile)(nil), // 17: atelet.AssetFile + (*ArchAssets)(nil), // 18: atelet.ArchAssets + (*SandboxAssets)(nil), // 19: atelet.SandboxAssets + (*WorkloadSpec)(nil), // 20: atelet.WorkloadSpec + (*DurableDirVolume)(nil), // 21: atelet.DurableDirVolume + (*ExternalVolumeSource)(nil), // 22: atelet.ExternalVolumeSource + (*ImageVolumeSource)(nil), // 23: atelet.ImageVolumeSource + (*ActorMetadataItem)(nil), // 24: atelet.ActorMetadataItem + (*ActorMetadataDataSource)(nil), // 25: atelet.ActorMetadataDataSource + (*TrustBundleDataSource)(nil), // 26: atelet.TrustBundleDataSource + (*SystemInfoDataSource)(nil), // 27: atelet.SystemInfoDataSource + (*SystemInfoVolume)(nil), // 28: atelet.SystemInfoVolume + (*Volume)(nil), // 29: atelet.Volume + (*VolumeMount)(nil), // 30: atelet.VolumeMount + (*Container)(nil), // 31: atelet.Container + (*SecurityContext)(nil), // 32: atelet.SecurityContext + (*Capabilities)(nil), // 33: atelet.Capabilities + (*ResourceLimits)(nil), // 34: atelet.ResourceLimits + (*EnvEntry)(nil), // 35: atelet.EnvEntry + (*WakeupProbe)(nil), // 36: atelet.WakeupProbe + (*HTTPGetAction)(nil), // 37: atelet.HTTPGetAction + (*RunResponse)(nil), // 38: atelet.RunResponse + (*LocalCheckpointConfiguration)(nil), // 39: atelet.LocalCheckpointConfiguration + (*ExternalCheckpointConfiguration)(nil), // 40: atelet.ExternalCheckpointConfiguration + (*ExternalRestoreConfiguration)(nil), // 41: atelet.ExternalRestoreConfiguration + (*CheckpointRequest)(nil), // 42: atelet.CheckpointRequest + (*CheckpointResponse)(nil), // 43: atelet.CheckpointResponse + (*UploadPausedCheckpointRequest)(nil), // 44: atelet.UploadPausedCheckpointRequest + (*UploadPausedCheckpointResponse)(nil), // 45: atelet.UploadPausedCheckpointResponse + (*PruneLocalCheckpointsRequest)(nil), // 46: atelet.PruneLocalCheckpointsRequest + (*PruneLocalCheckpointsResponse)(nil), // 47: atelet.PruneLocalCheckpointsResponse + (*RestoreRequest)(nil), // 48: atelet.RestoreRequest + (*RestoreResponse)(nil), // 49: atelet.RestoreResponse + nil, // 50: atelet.ArchAssets.FilesEntry + nil, // 51: atelet.SandboxAssets.AssetsEntry + nil, // 52: atelet.ExternalVolumeSource.VolumeContextEntry } var file_atelet_proto_depIdxs = []int32{ - 4, // 0: atelet.SetWorkerCapacityRequest.capacity:type_name -> atelet.WorkerResources - 5, // 1: atelet.WorkerResources.resources:type_name -> atelet.Resources - 6, // 2: atelet.Resources.limits:type_name -> atelet.Limits - 19, // 3: atelet.TerminateRequest.spec:type_name -> atelet.WorkloadSpec - 19, // 4: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec - 18, // 5: atelet.RunRequest.sandbox_assets:type_name -> atelet.SandboxAssets - 15, // 6: atelet.RunRequest.egress_gateway:type_name -> atelet.EgressGateway - 49, // 7: atelet.ArchAssets.files:type_name -> atelet.ArchAssets.FilesEntry - 50, // 8: atelet.SandboxAssets.assets:type_name -> atelet.SandboxAssets.AssetsEntry - 30, // 9: atelet.WorkloadSpec.containers:type_name -> atelet.Container - 28, // 10: atelet.WorkloadSpec.volumes:type_name -> atelet.Volume - 51, // 11: atelet.ExternalVolumeSource.volume_context:type_name -> atelet.ExternalVolumeSource.VolumeContextEntry - 0, // 12: atelet.ActorMetadataItem.field:type_name -> atelet.ActorMetadataField - 23, // 13: atelet.ActorMetadataDataSource.items:type_name -> atelet.ActorMetadataItem - 24, // 14: atelet.SystemInfoDataSource.actor_metadata:type_name -> atelet.ActorMetadataDataSource - 25, // 15: atelet.SystemInfoDataSource.trust_bundle:type_name -> atelet.TrustBundleDataSource - 26, // 16: atelet.SystemInfoVolume.data_sources:type_name -> atelet.SystemInfoDataSource - 20, // 17: atelet.Volume.durable_dir:type_name -> atelet.DurableDirVolume - 21, // 18: atelet.Volume.external:type_name -> atelet.ExternalVolumeSource - 27, // 19: atelet.Volume.system_info:type_name -> atelet.SystemInfoVolume - 22, // 20: atelet.Volume.image:type_name -> atelet.ImageVolumeSource - 34, // 21: atelet.Container.env:type_name -> atelet.EnvEntry - 35, // 22: atelet.Container.wakeup_probe:type_name -> atelet.WakeupProbe - 29, // 23: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount - 31, // 24: atelet.Container.security_context:type_name -> atelet.SecurityContext - 33, // 25: atelet.Container.resources:type_name -> atelet.ResourceLimits - 32, // 26: atelet.SecurityContext.capabilities:type_name -> atelet.Capabilities - 36, // 27: atelet.WakeupProbe.http_get:type_name -> atelet.HTTPGetAction - 19, // 28: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 29: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType - 38, // 30: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 39, // 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 - 19, // 34: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec - 1, // 35: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType - 38, // 36: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration - 40, // 37: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalRestoreConfiguration - 2, // 38: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope - 15, // 39: atelet.RestoreRequest.egress_gateway:type_name -> atelet.EgressGateway - 18, // 40: atelet.RestoreRequest.sandbox_assets:type_name -> atelet.SandboxAssets - 16, // 41: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile - 17, // 42: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets - 10, // 43: atelet.AteomSupport.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest - 3, // 44: atelet.AteomSupport.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest - 8, // 45: atelet.AteomSupport.RequestActorSuspend:input_type -> atelet.RequestActorSuspendRequest - 14, // 46: atelet.AteomHerder.Run:input_type -> atelet.RunRequest - 41, // 47: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest - 47, // 48: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest - 43, // 49: atelet.AteomHerder.UploadPausedCheckpoint:input_type -> atelet.UploadPausedCheckpointRequest - 45, // 50: atelet.AteomHerder.PruneLocalCheckpoints:input_type -> atelet.PruneLocalCheckpointsRequest - 12, // 51: atelet.AteomHerder.Terminate:input_type -> atelet.TerminateRequest - 11, // 52: atelet.AteomSupport.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse - 7, // 53: atelet.AteomSupport.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse - 9, // 54: atelet.AteomSupport.RequestActorSuspend:output_type -> atelet.RequestActorSuspendResponse - 37, // 55: atelet.AteomHerder.Run:output_type -> atelet.RunResponse - 42, // 56: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse - 48, // 57: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse - 44, // 58: atelet.AteomHerder.UploadPausedCheckpoint:output_type -> atelet.UploadPausedCheckpointResponse - 46, // 59: atelet.AteomHerder.PruneLocalCheckpoints:output_type -> atelet.PruneLocalCheckpointsResponse - 13, // 60: atelet.AteomHerder.Terminate:output_type -> atelet.TerminateResponse - 52, // [52:61] is the sub-list for method output_type - 43, // [43:52] is the sub-list for method input_type - 43, // [43:43] is the sub-list for extension type_name - 43, // [43:43] is the sub-list for extension extendee - 0, // [0:43] is the sub-list for field type_name + 5, // 0: atelet.SetWorkerCapacityRequest.capacity:type_name -> atelet.WorkerResources + 6, // 1: atelet.WorkerResources.resources:type_name -> atelet.Resources + 7, // 2: atelet.Resources.limits:type_name -> atelet.Limits + 20, // 3: atelet.TerminateRequest.spec:type_name -> atelet.WorkloadSpec + 20, // 4: atelet.RunRequest.spec:type_name -> atelet.WorkloadSpec + 19, // 5: atelet.RunRequest.sandbox_assets:type_name -> atelet.SandboxAssets + 16, // 6: atelet.RunRequest.egress_gateway:type_name -> atelet.EgressGateway + 50, // 7: atelet.ArchAssets.files:type_name -> atelet.ArchAssets.FilesEntry + 51, // 8: atelet.SandboxAssets.assets:type_name -> atelet.SandboxAssets.AssetsEntry + 31, // 9: atelet.WorkloadSpec.containers:type_name -> atelet.Container + 29, // 10: atelet.WorkloadSpec.volumes:type_name -> atelet.Volume + 52, // 11: atelet.ExternalVolumeSource.volume_context:type_name -> atelet.ExternalVolumeSource.VolumeContextEntry + 0, // 12: atelet.ExternalVolumeSource.access_mode:type_name -> atelet.VolumeAccessMode + 1, // 13: atelet.ActorMetadataItem.field:type_name -> atelet.ActorMetadataField + 24, // 14: atelet.ActorMetadataDataSource.items:type_name -> atelet.ActorMetadataItem + 25, // 15: atelet.SystemInfoDataSource.actor_metadata:type_name -> atelet.ActorMetadataDataSource + 26, // 16: atelet.SystemInfoDataSource.trust_bundle:type_name -> atelet.TrustBundleDataSource + 27, // 17: atelet.SystemInfoVolume.data_sources:type_name -> atelet.SystemInfoDataSource + 21, // 18: atelet.Volume.durable_dir:type_name -> atelet.DurableDirVolume + 22, // 19: atelet.Volume.external:type_name -> atelet.ExternalVolumeSource + 28, // 20: atelet.Volume.system_info:type_name -> atelet.SystemInfoVolume + 23, // 21: atelet.Volume.image:type_name -> atelet.ImageVolumeSource + 35, // 22: atelet.Container.env:type_name -> atelet.EnvEntry + 36, // 23: atelet.Container.wakeup_probe:type_name -> atelet.WakeupProbe + 30, // 24: atelet.Container.volume_mounts:type_name -> atelet.VolumeMount + 32, // 25: atelet.Container.security_context:type_name -> atelet.SecurityContext + 34, // 26: atelet.Container.resources:type_name -> atelet.ResourceLimits + 33, // 27: atelet.SecurityContext.capabilities:type_name -> atelet.Capabilities + 37, // 28: atelet.WakeupProbe.http_get:type_name -> atelet.HTTPGetAction + 20, // 29: atelet.CheckpointRequest.spec:type_name -> atelet.WorkloadSpec + 2, // 30: atelet.CheckpointRequest.type:type_name -> atelet.CheckpointType + 39, // 31: atelet.CheckpointRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 40, // 32: atelet.CheckpointRequest.external_config:type_name -> atelet.ExternalCheckpointConfiguration + 3, // 33: atelet.CheckpointRequest.scope:type_name -> atelet.SnapshotScope + 3, // 34: atelet.UploadPausedCheckpointRequest.desired_scope:type_name -> atelet.SnapshotScope + 20, // 35: atelet.RestoreRequest.spec:type_name -> atelet.WorkloadSpec + 2, // 36: atelet.RestoreRequest.type:type_name -> atelet.CheckpointType + 39, // 37: atelet.RestoreRequest.local_config:type_name -> atelet.LocalCheckpointConfiguration + 41, // 38: atelet.RestoreRequest.external_config:type_name -> atelet.ExternalRestoreConfiguration + 3, // 39: atelet.RestoreRequest.scope:type_name -> atelet.SnapshotScope + 16, // 40: atelet.RestoreRequest.egress_gateway:type_name -> atelet.EgressGateway + 19, // 41: atelet.RestoreRequest.sandbox_assets:type_name -> atelet.SandboxAssets + 17, // 42: atelet.ArchAssets.FilesEntry.value:type_name -> atelet.AssetFile + 18, // 43: atelet.SandboxAssets.AssetsEntry.value:type_name -> atelet.ArchAssets + 11, // 44: atelet.AteomSupport.MintActorCertificate:input_type -> atelet.MintActorCertificateRequest + 4, // 45: atelet.AteomSupport.SetWorkerCapacity:input_type -> atelet.SetWorkerCapacityRequest + 9, // 46: atelet.AteomSupport.RequestActorSuspend:input_type -> atelet.RequestActorSuspendRequest + 15, // 47: atelet.AteomHerder.Run:input_type -> atelet.RunRequest + 42, // 48: atelet.AteomHerder.Checkpoint:input_type -> atelet.CheckpointRequest + 48, // 49: atelet.AteomHerder.Restore:input_type -> atelet.RestoreRequest + 44, // 50: atelet.AteomHerder.UploadPausedCheckpoint:input_type -> atelet.UploadPausedCheckpointRequest + 46, // 51: atelet.AteomHerder.PruneLocalCheckpoints:input_type -> atelet.PruneLocalCheckpointsRequest + 13, // 52: atelet.AteomHerder.Terminate:input_type -> atelet.TerminateRequest + 12, // 53: atelet.AteomSupport.MintActorCertificate:output_type -> atelet.MintActorCertificateResponse + 8, // 54: atelet.AteomSupport.SetWorkerCapacity:output_type -> atelet.SetWorkerCapacityResponse + 10, // 55: atelet.AteomSupport.RequestActorSuspend:output_type -> atelet.RequestActorSuspendResponse + 38, // 56: atelet.AteomHerder.Run:output_type -> atelet.RunResponse + 43, // 57: atelet.AteomHerder.Checkpoint:output_type -> atelet.CheckpointResponse + 49, // 58: atelet.AteomHerder.Restore:output_type -> atelet.RestoreResponse + 45, // 59: atelet.AteomHerder.UploadPausedCheckpoint:output_type -> atelet.UploadPausedCheckpointResponse + 47, // 60: atelet.AteomHerder.PruneLocalCheckpoints:output_type -> atelet.PruneLocalCheckpointsResponse + 14, // 61: atelet.AteomHerder.Terminate:output_type -> atelet.TerminateResponse + 53, // [53:62] is the sub-list for method output_type + 44, // [44:53] is the sub-list for method input_type + 44, // [44:44] is the sub-list for extension type_name + 44, // [44:44] is the sub-list for extension extendee + 0, // [0:44] is the sub-list for field type_name } func init() { file_atelet_proto_init() } @@ -3509,7 +3601,7 @@ func file_atelet_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_atelet_proto_rawDesc), len(file_atelet_proto_rawDesc)), - NumEnums: 3, + NumEnums: 4, NumMessages: 49, NumExtensions: 0, NumServices: 2, diff --git a/internal/proto/ateletpb/atelet.proto b/internal/proto/ateletpb/atelet.proto index d3559bad8b..1b271de846 100644 --- a/internal/proto/ateletpb/atelet.proto +++ b/internal/proto/ateletpb/atelet.proto @@ -282,6 +282,16 @@ message ExternalVolumeSource { string storage_volume_id = 1; string volume_type = 2; map volume_context = 3; + // access_mode is how the volume is published; unset is READ_WRITE_ONCE. + VolumeAccessMode access_mode = 4; +} + +// VolumeAccessMode mirrors ateapi.VolumeAccessMode. +enum VolumeAccessMode { + 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; } message ImageVolumeSource { @@ -345,6 +355,11 @@ message Volume { message VolumeMount { string name = 1; string mount_path = 2; + // sub_path is the directory of an external volume to mount, relative to + // its root; empty mounts the root. + string sub_path = 3; + // read_only mounts an external volume read-only. + bool read_only = 4; } message Container { diff --git a/internal/proto/ateompb/ateom.pb.go b/internal/proto/ateompb/ateom.pb.go index 5ed1349db9..cfaab00801 100644 --- a/internal/proto/ateompb/ateom.pb.go +++ b/internal/proto/ateompb/ateom.pb.go @@ -769,9 +769,15 @@ func (x *Container) GetImageVolumeMounts() []*ImageVolumeMount { // VolumeMount is one volume mounted into a container. type VolumeMount struct { - state protoimpl.MessageState `protogen:"open.v1"` - VolumeName string `protobuf:"bytes,1,opt,name=volume_name,json=volumeName,proto3" json:"volume_name,omitempty"` - MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + VolumeName string `protobuf:"bytes,1,opt,name=volume_name,json=volumeName,proto3" json:"volume_name,omitempty"` + MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + // sub_path is the directory of the volume to mount, relative to its root; + // empty mounts the root. ateom binds it beside the volume's mount point + // before the sandbox starts (internal/volumebind). + SubPath string `protobuf:"bytes,3,opt,name=sub_path,json=subPath,proto3" json:"sub_path,omitempty"` + // read_only mounts it read-only. + ReadOnly bool `protobuf:"varint,4,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -820,6 +826,20 @@ func (x *VolumeMount) GetMountPath() string { return "" } +func (x *VolumeMount) GetSubPath() string { + if x != nil { + return x.SubPath + } + return "" +} + +func (x *VolumeMount) GetReadOnly() bool { + if x != nil { + return x.ReadOnly + } + return false +} + // DurableDirVolumeMount is one durable-dir volume mounted into a container. type DurableDirVolumeMount struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1949,12 +1969,14 @@ const file_ateom_proto_rawDesc = "" + "\x19durable_dir_volume_mounts\x18\x03 \x03(\v2\x1c.ateom.DurableDirVolumeMountR\x16durableDirVolumeMounts\x12>\n" + "\x11csi_volume_mounts\x18\x04 \x03(\v2\x12.ateom.VolumeMountR\x0fcsiVolumeMounts\x12W\n" + "\x19system_info_volume_mounts\x18\x05 \x03(\v2\x1c.ateom.SystemInfoVolumeMountR\x16systemInfoVolumeMounts\x12G\n" + - "\x13image_volume_mounts\x18\x06 \x03(\v2\x17.ateom.ImageVolumeMountR\x11imageVolumeMounts\"M\n" + + "\x13image_volume_mounts\x18\x06 \x03(\v2\x17.ateom.ImageVolumeMountR\x11imageVolumeMounts\"\x85\x01\n" + "\vVolumeMount\x12\x1f\n" + "\vvolume_name\x18\x01 \x01(\tR\n" + "volumeName\x12\x1d\n" + "\n" + - "mount_path\x18\x02 \x01(\tR\tmountPath\"W\n" + + "mount_path\x18\x02 \x01(\tR\tmountPath\x12\x19\n" + + "\bsub_path\x18\x03 \x01(\tR\asubPath\x12\x1b\n" + + "\tread_only\x18\x04 \x01(\bR\breadOnly\"W\n" + "\x15DurableDirVolumeMount\x12\x1f\n" + "\vvolume_name\x18\x01 \x01(\tR\n" + "volumeName\x12\x1d\n" + diff --git a/internal/proto/ateompb/ateom.proto b/internal/proto/ateompb/ateom.proto index 7f234c0be5..5b5a18b180 100644 --- a/internal/proto/ateompb/ateom.proto +++ b/internal/proto/ateompb/ateom.proto @@ -206,6 +206,12 @@ message Container { message VolumeMount { string volume_name = 1; string mount_path = 2; + // sub_path is the directory of the volume to mount, relative to its root; + // empty mounts the root. ateom binds it beside the volume's mount point + // before the sandbox starts (internal/volumebind). + string sub_path = 3; + // read_only mounts it read-only. + bool read_only = 4; } // DurableDirVolumeMount is one durable-dir volume mounted into a container. diff --git a/internal/volume/csi/plugin.go b/internal/volume/csi/plugin.go index d1cede7777..149126ec13 100644 --- a/internal/volume/csi/plugin.go +++ b/internal/volume/csi/plugin.go @@ -16,8 +16,10 @@ package csi import ( "context" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/hex" "fmt" "log/slog" "os" @@ -122,11 +124,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 { +func (p *Plugin) AttachVolume(ctx context.Context, volumeID string, node string, mode volume.AccessMode) error { req := &csi.ControllerPublishVolumeRequest{ VolumeId: volumeID, NodeId: node, - VolumeCapability: getStandardCapabilities()[0], // Use primary capability + VolumeCapability: mountCapability(mode), Readonly: false, } @@ -173,9 +175,9 @@ 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, mode volume.AccessMode) error { // 1. Stage the volume - stagingPath := filepath.Join(p.stagingDirPrefix, volumeID) + stagingPath := p.stagingPath(volumeID, targetPath, mode) if err := os.MkdirAll(stagingPath, 0750); err != nil { return fmt.Errorf("failed to create staging directory %q: %w", stagingPath, err) } @@ -183,7 +185,7 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st stageReq := &csi.NodeStageVolumeRequest{ VolumeId: volumeID, StagingTargetPath: stagingPath, - VolumeCapability: getStandardCapabilities()[0], // Use primary capability + VolumeCapability: mountCapability(mode), VolumeContext: volumeContext, } @@ -201,8 +203,8 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st req := &csi.NodePublishVolumeRequest{ VolumeId: volumeID, TargetPath: targetPath, - VolumeCapability: getStandardCapabilities()[0], - Readonly: false, + VolumeCapability: mountCapability(mode), + Readonly: mode == volume.ReadOnlyMany, VolumeContext: volumeContext, } if stagingPath != "" { @@ -218,7 +220,7 @@ func (p *Plugin) MountVolume(ctx context.Context, volumeID string, targetPath st // UnmountVolume maps to CSI Node NodeUnpublishVolume. // It also handles NodeUnstageVolume if staging was used. -func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string) error { +func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string, mode volume.AccessMode) error { // 1. Unpublish (Unmount) the volume req := &csi.NodeUnpublishVolumeRequest{ VolumeId: volumeID, @@ -231,7 +233,7 @@ func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath } // 2. Unstage the volume - stagingPath := filepath.Join(p.stagingDirPrefix, volumeID) + stagingPath := p.stagingPath(volumeID, targetPath, mode) unstageReq := &csi.NodeUnstageVolumeRequest{ VolumeId: volumeID, StagingTargetPath: stagingPath, @@ -250,10 +252,37 @@ func (p *Plugin) UnmountVolume(ctx context.Context, volumeID string, targetPath 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)) } + if mode.MultiNode() { + // The volume's directory of per-target stagings goes with its last one. + _ = os.Remove(filepath.Dir(stagingPath)) + } return nil } +// stagingPath is where a volume is staged on the node. A volume of a +// multi-node mode is staged once per target, so that one actor unmounting it +// never unstages it under another actor on the same node. +func (p *Plugin) stagingPath(volumeID, targetPath string, mode volume.AccessMode) string { + if !mode.MultiNode() { + return filepath.Join(p.stagingDirPrefix, volumeID) + } + sum := sha256.Sum256([]byte(targetPath)) + return filepath.Join(p.stagingDirPrefix, volumeID, hex.EncodeToString(sum[:8])) +} + +// mountCapability is the mount capability of an access mode. +func mountCapability(mode volume.AccessMode) *csi.VolumeCapability { + capability := getStandardCapabilities()[0] + switch mode { + case volume.ReadOnlyMany: + capability.AccessMode.Mode = csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY + case volume.ReadWriteMany: + capability.AccessMode.Mode = csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER + } + return capability +} + // 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. diff --git a/internal/volume/csi/plugin_test.go b/internal/volume/csi/plugin_test.go index 79b5a75d9d..055dc2b069 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/internal/volume" "github.com/container-storage-interface/spec/lib/go/csi" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -228,7 +229,7 @@ func TestPlugin_AttachVolume(t *testing.T) { plugin := NewPlugin(client) ctx := context.Background() - err = plugin.AttachVolume(ctx, "test-vol", "node-1") + err = plugin.AttachVolume(ctx, "test-vol", "node-1", volume.ReadWriteOnce) if err != nil { t.Fatalf("AttachVolume failed: %v", err) } @@ -237,7 +238,7 @@ func TestPlugin_AttachVolume(t *testing.T) { 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") + err = plugin.AttachVolume(ctx, "test-vol", "node-1", volume.ReadWriteOnce) if err != nil { t.Errorf("AttachVolume should have ignored Unimplemented error, got: %v", err) } @@ -294,7 +295,7 @@ func TestPlugin_MountVolume(t *testing.T) { targetPath := filepath.Join(tmpDir, "target") ctx := context.Background() - err = plugin.MountVolume(ctx, "test-vol", targetPath, nil) + err = plugin.MountVolume(ctx, "test-vol", targetPath, nil, volume.ReadWriteOnce) if err != nil { t.Fatalf("MountVolume failed: %v", err) } @@ -313,7 +314,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, volume.ReadWriteOnce) if err != nil { t.Errorf("MountVolume should have succeeded when NodeStageVolume is unimplemented, got: %v", err) } @@ -347,7 +348,7 @@ func TestPlugin_UnmountVolume(t *testing.T) { } ctx := context.Background() - err = plugin.UnmountVolume(ctx, "test-vol", targetPath) + err = plugin.UnmountVolume(ctx, "test-vol", targetPath, volume.ReadWriteOnce) if err != nil { t.Fatalf("UnmountVolume failed: %v", err) } @@ -365,7 +366,7 @@ func TestPlugin_UnmountVolume(t *testing.T) { if err := os.MkdirAll(stagingPath, 0750); err != nil { t.Fatalf("failed to create staging path: %v", err) } - err = plugin.UnmountVolume(ctx, "test-vol", targetPath) + err = plugin.UnmountVolume(ctx, "test-vol", targetPath, volume.ReadWriteOnce) if err != nil { t.Errorf("UnmountVolume should have succeeded when NodeUnstageVolume is unimplemented, got: %v", err) } @@ -408,3 +409,69 @@ func TestClient_Identity(t *testing.T) { t.Fatalf("Probe failed: %v", err) } } + +func TestPlugin_MountVolumeMultiNode(t *testing.T) { + var staged, unstaged []string + var published []*csi.NodePublishVolumeRequest + driver := &mockCSIDriver{ + nodeStageVolumeFunc: func(_ context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + staged = append(staged, req.GetStagingTargetPath()) + return &csi.NodeStageVolumeResponse{}, nil + }, + nodePublishVolumeFunc: func(_ context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + published = append(published, req) + return &csi.NodePublishVolumeResponse{}, nil + }, + nodeUnstageVolumeFunc: func(_ context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { + unstaged = append(unstaged, req.GetStagingTargetPath()) + 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) + tmpDir := t.TempDir() + plugin.stagingDirPrefix = filepath.Join(tmpDir, "staging") + ctx := context.Background() + + // Two actors on one node mount the same read-write-many volume. + targetA, targetB := filepath.Join(tmpDir, "a"), filepath.Join(tmpDir, "b") + for _, target := range []string{targetA, targetB} { + if err := plugin.MountVolume(ctx, "shared", target, nil, volume.ReadWriteMany); err != nil { + t.Fatalf("MountVolume %s: %v", target, err) + } + } + if len(staged) != 2 || staged[0] == staged[1] { + t.Fatalf("staging paths = %v, want one per target", staged) + } + for _, req := range published { + if got := req.GetVolumeCapability().GetAccessMode().GetMode(); got != csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { + t.Errorf("publish access mode = %v, want MULTI_NODE_MULTI_WRITER", got) + } + if req.GetReadonly() { + t.Errorf("read-write-many volume published read-only") + } + } + + // One actor's unmount unstages only its own staging. + if err := plugin.UnmountVolume(ctx, "shared", targetA, volume.ReadWriteMany); err != nil { + t.Fatalf("UnmountVolume: %v", err) + } + if len(unstaged) != 1 || unstaged[0] != staged[0] { + t.Errorf("unstaged = %v, want only %q", unstaged, staged[0]) + } + + // A read-only-many volume is published read-only. + published = nil + if err := plugin.MountVolume(ctx, "mirrors", targetA, nil, volume.ReadOnlyMany); err != nil { + t.Fatalf("MountVolume: %v", err) + } + if req := published[0]; !req.GetReadonly() || req.GetVolumeCapability().GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY { + t.Errorf("read-only-many publish = %v, want read-only MULTI_NODE_READER_ONLY", req) + } +} diff --git a/internal/volume/mock.go b/internal/volume/mock.go index e6c9c99589..32c2a2b524 100644 --- a/internal/volume/mock.go +++ b/internal/volume/mock.go @@ -75,7 +75,7 @@ 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 { +func (p *MockVolumePlugin) AttachVolume(ctx context.Context, volumeID string, node string, _ AccessMode) error { slog.InfoContext(ctx, "MockVolumePlugin.AttachVolume", slog.String("volumeID", volumeID), slog.String("node", node)) return nil } @@ -87,7 +87,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, _ AccessMode) error { slog.InfoContext(ctx, "MockVolumePlugin.MountVolume", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath)) volumeDir := filepath.Join(mockVolumeDirectories, volumeID) @@ -111,7 +111,7 @@ func (p *MockVolumePlugin) MountVolume(ctx context.Context, volumeID string, tar } // UnmountVolume simulates unmounting volume from the host. -func (p *MockVolumePlugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string) error { +func (p *MockVolumePlugin) UnmountVolume(ctx context.Context, volumeID string, targetPath string, _ AccessMode) error { slog.InfoContext(ctx, "MockVolumePlugin.UnmountVolume", slog.String("volumeID", volumeID), slog.String("targetPath", targetPath)) if err := os.Remove(targetPath); err != nil && !os.IsNotExist(err) { diff --git a/internal/volume/plugin.go b/internal/volume/plugin.go index ee54799c2b..7547fcbcf1 100644 --- a/internal/volume/plugin.go +++ b/internal/volume/plugin.go @@ -18,17 +18,33 @@ import ( "context" ) +// AccessMode is how a volume is attached and mounted across nodes. +type AccessMode int + +const ( + // ReadWriteOnce is read-write on a single node. + ReadWriteOnce AccessMode = iota + // ReadOnlyMany is read-only on many nodes. + ReadOnlyMany + // ReadWriteMany is read-write on many nodes. + ReadWriteMany +) + +// MultiNode reports whether a volume of the mode may be in use by several +// actors at once, on one node or many. +func (m AccessMode) MultiNode() bool { return m != ReadWriteOnce } + // VolumePluginControlPlane abstracts storage operations performed on the control plane. 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 AccessMode) 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 - UnmountVolume(ctx context.Context, volumeID string, targetPath string) error + MountVolume(ctx context.Context, volumeID string, targetPath string, volumeContext map[string]string, mode AccessMode) error + UnmountVolume(ctx context.Context, volumeID string, targetPath string, mode AccessMode) error } diff --git a/internal/volumebind/volumebind.go b/internal/volumebind/volumebind.go new file mode 100644 index 0000000000..5cba2a6cd8 --- /dev/null +++ b/internal/volumebind/volumebind.go @@ -0,0 +1,159 @@ +// 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 volumebind binds directories of an actor's mounted external +// volumes beside their mount points, read-only where asked, for the mounts +// that name a sub-path or are read-only. ateom runs it: it sees the volumes +// the CSI node plugin published on the host and may mount, which atelet may +// not. +package volumebind + +import ( + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/agent-substrate/substrate/internal/proto/ateompb" + "golang.org/x/sys/unix" +) + +// MountDir is the directory under an actor's volumes directory that a mount +// of the external volume name binds from: the volume's own mount point, or, +// for a mount of a sub-path or a read-only mount, a bind of its own beside +// it. A volume name is a DNS label, so the suffix never names a volume. +func MountDir(name, subPath string, readOnly bool) string { + if subPath == "" && !readOnly { + return name + } + sum := sha256.Sum256(fmt.Appendf(nil, "%s\x00%t", subPath, readOnly)) + return name + "." + hex.EncodeToString(sum[:6]) +} + +// boundMounts returns the containers' CSI volume mounts that bind a +// directory of their own (MountDir), once per directory. +func boundMounts(containers []*ateompb.Container) []*ateompb.VolumeMount { + var out []*ateompb.VolumeMount + seen := map[string]bool{} + for _, ctr := range containers { + for _, vm := range ctr.GetCsiVolumeMounts() { + dir := MountDir(vm.GetVolumeName(), vm.GetSubPath(), vm.GetReadOnly()) + if dir == vm.GetVolumeName() || seen[dir] { + continue + } + seen[dir] = true + out = append(out, vm) + } + } + return out +} + +// Prepare binds every directory the containers' mounts bind from (MountDir) +// under volumesDir, replacing a bind left by an earlier attempt. It runs +// before the sandbox starts. +func Prepare(volumesDir string, containers []*ateompb.Container) error { + for _, vm := range boundMounts(containers) { + root := filepath.Join(volumesDir, vm.GetVolumeName()) + target := filepath.Join(volumesDir, MountDir(vm.GetVolumeName(), vm.GetSubPath(), vm.GetReadOnly())) + if err := bindVolumeDir(root, vm.GetSubPath(), target, vm.GetReadOnly()); err != nil { + return fmt.Errorf("volume %q: %w", vm.GetVolumeName(), err) + } + } + return nil +} + +// Release unmounts the binds of Prepare. It runs once the sandbox has +// stopped, before the volumes themselves are unmounted. +func Release(volumesDir string, containers []*ateompb.Container) error { + var errs []error + for _, vm := range boundMounts(containers) { + if err := unbindVolumeDir(filepath.Join(volumesDir, MountDir(vm.GetVolumeName(), vm.GetSubPath(), vm.GetReadOnly()))); err != nil { + errs = append(errs, fmt.Errorf("volume %q: %w", vm.GetVolumeName(), err)) + } + } + return errors.Join(errs...) +} + +// openVolumeDir opens the directory subPath of the volume mounted at root +// (root itself when subPath is empty) as an O_PATH descriptor. The path is +// resolved beneath root without following any symbolic link or crossing a +// mount: other actors write the volume, so a link they planted must not turn +// a mount into one of a directory outside it. +func openVolumeDir(root, subPath string) (int, error) { + if subPath == "" { + subPath = "." + } + rootFd, err := unix.Open(root, unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0) + if err != nil { + return -1, fmt.Errorf("opening volume root %q: %w", root, err) + } + defer unix.Close(rootFd) + fd, err := unix.Openat2(rootFd, subPath, &unix.OpenHow{ + Flags: unix.O_PATH | unix.O_DIRECTORY | unix.O_CLOEXEC, + Resolve: unix.RESOLVE_BENEATH | unix.RESOLVE_NO_SYMLINKS | unix.RESOLVE_NO_MAGICLINKS | unix.RESOLVE_NO_XDEV, + }) + switch { + case errors.Is(err, unix.ENOENT): + return -1, fmt.Errorf("sub_path %q does not exist on the volume", subPath) + case errors.Is(err, unix.ELOOP): + return -1, fmt.Errorf("sub_path %q crosses a symbolic link on the volume", subPath) + case errors.Is(err, unix.ENOTDIR): + return -1, fmt.Errorf("sub_path %q is not a directory on the volume", subPath) + case err != nil: + return -1, fmt.Errorf("opening sub_path %q of the volume: %w", subPath, err) + } + return fd, nil +} + +// bindVolumeDir bind-mounts the directory subPath of the volume mounted at +// root onto target, read-only when readOnly. A bind left by an earlier +// attempt is replaced. +func bindVolumeDir(root, subPath, target string, readOnly bool) error { + fd, err := openVolumeDir(root, subPath) + if err != nil { + return err + } + defer unix.Close(fd) + if err := unbindVolumeDir(target); err != nil { + return err + } + if err := os.MkdirAll(target, 0o750); err != nil { + return fmt.Errorf("creating mount point %q: %w", target, err) + } + // Binding from the descriptor mounts exactly the directory resolved above. + if err := unix.Mount(fmt.Sprintf("/proc/self/fd/%d", fd), target, "", unix.MS_BIND, ""); err != nil { + return fmt.Errorf("bind-mounting sub_path %q at %q: %w", subPath, target, err) + } + if readOnly { + if err := unix.Mount("", target, "", unix.MS_BIND|unix.MS_REMOUNT|unix.MS_RDONLY, ""); err != nil { + _ = unix.Unmount(target, unix.MNT_DETACH) + return fmt.Errorf("making %q read-only: %w", target, err) + } + } + return nil +} + +// unbindVolumeDir unmounts a bind of bindVolumeDir and removes its mount +// point; a target that is absent or not mounted is left as it is. +func unbindVolumeDir(target string) error { + if err := unix.Unmount(target, 0); err != nil && !errors.Is(err, unix.EINVAL) && !errors.Is(err, unix.ENOENT) { + return fmt.Errorf("unmounting %q: %w", target, err) + } + if err := os.Remove(target); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("removing mount point %q: %w", target, err) + } + return nil +} diff --git a/internal/volumebind/volumebind_test.go b/internal/volumebind/volumebind_test.go new file mode 100644 index 0000000000..bc6a007adc --- /dev/null +++ b/internal/volumebind/volumebind_test.go @@ -0,0 +1,119 @@ +// 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 volumebind + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/agent-substrate/substrate/internal/proto/ateompb" + "golang.org/x/sys/unix" +) + +func TestOpenVolumeDir(t *testing.T) { + root := t.TempDir() + outside := t.TempDir() + for _, dir := range []string{"sessions/a", "mirrors"} { + if err := os.MkdirAll(filepath.Join(root, dir), 0o755); err != nil { + t.Fatal(err) + } + } + if err := os.WriteFile(filepath.Join(root, "file"), nil, 0o644); err != nil { + t.Fatal(err) + } + // Links another actor could plant on the shared volume. + if err := os.Symlink(outside, filepath.Join(root, "sessions", "b")); err != nil { + t.Fatal(err) + } + if err := os.Symlink("../mirrors", filepath.Join(root, "sessions", "c")); err != nil { + t.Fatal(err) + } + + for _, tc := range []struct { + subPath string + wantErr string + }{ + {subPath: ""}, + {subPath: "sessions/a"}, + {subPath: "mirrors"}, + {subPath: "sessions/missing", wantErr: "does not exist"}, + {subPath: "sessions/b", wantErr: "crosses a symbolic link"}, + {subPath: "sessions/c", wantErr: "crosses a symbolic link"}, + {subPath: "file", wantErr: "is not a directory"}, + {subPath: "../" + filepath.Base(outside), wantErr: "opening sub_path"}, + } { + t.Run(tc.subPath, func(t *testing.T) { + fd, err := openVolumeDir(root, tc.subPath) + if tc.wantErr == "" { + if err != nil { + t.Fatalf("openVolumeDir(%q): %v", tc.subPath, err) + } + unix.Close(fd) + return + } + if err == nil { + unix.Close(fd) + t.Fatalf("openVolumeDir(%q) succeeded, want %q", tc.subPath, tc.wantErr) + } + if !strings.Contains(err.Error(), tc.wantErr) { + t.Errorf("openVolumeDir(%q) = %v, want %q", tc.subPath, err, tc.wantErr) + } + }) + } +} + +func TestBoundMounts(t *testing.T) { + containers := []*ateompb.Container{ + {CsiVolumeMounts: []*ateompb.VolumeMount{ + {VolumeName: "ws", MountPath: "/root"}, + {VolumeName: "ws", MountPath: "/workspace", SubPath: "sessions/a"}, + {VolumeName: "ws", MountPath: "/mirrors", SubPath: "mirrors", ReadOnly: true}, + }}, + {CsiVolumeMounts: []*ateompb.VolumeMount{ + {VolumeName: "ws", MountPath: "/also", SubPath: "sessions/a"}, + }}, + } + var got []string + for _, vm := range boundMounts(containers) { + got = append(got, vm.GetMountPath()) + } + if want := []string{"/workspace", "/mirrors"}; strings.Join(got, ",") != strings.Join(want, ",") { + t.Errorf("boundMounts = %v, want %v (one per directory, none for the root mount)", got, want) + } +} + +// A mount of a sub-path or a read-only mount binds a directory of its own, +// one per sub-path and read-only flag, which never names a volume. +func TestMountDir(t *testing.T) { + plain := MountDir("ws", "", false) + sub := MountDir("ws", "sessions/a", false) + subRO := MountDir("ws", "sessions/a", true) + other := MountDir("ws", "sessions/b", false) + if plain != "ws" { + t.Errorf("plain mount dir = %q, want the volume's own", plain) + } + seen := map[string]bool{} + for _, d := range []string{plain, sub, subRO, other} { + if seen[d] { + t.Errorf("mount dir %q is not distinct", d) + } + seen[d] = true + } + if !strings.HasPrefix(sub, "ws.") { + t.Errorf("sub-path mount dir = %q, want ws.", sub) + } +} diff --git a/manifests/ate-install/ate-api-server.yaml b/manifests/ate-install/ate-api-server.yaml index 0a861c7d90..2587b8402a 100644 --- a/manifests/ate-install/ate-api-server.yaml +++ b/manifests/ate-install/ate-api-server.yaml @@ -34,6 +34,11 @@ rules: - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "watch", "list"] +# PersistentVolumes: an actor's existing volumes are checked against them at +# CreateActor, and their volume attributes are read at resume. +- apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "watch", "list"] --- # Create Service Account for Workload Identity apiVersion: v1 diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index a3ec8fd9c4..0ce4d2f11d 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -141,6 +141,62 @@ func (TagScope) EnumDescriptor() ([]byte, []int) { return file_ateapi_proto_rawDescGZIP(), []int{1} } +// VolumeAccessMode is how a volume may be attached and mounted across nodes. +type VolumeAccessMode int32 + +const ( + VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED VolumeAccessMode = 0 + // Read-write on a single node (CSI SINGLE_NODE_WRITER). + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_WRITE_ONCE VolumeAccessMode = 1 + // Read-only on many nodes (CSI MULTI_NODE_READER_ONLY). + VolumeAccessMode_VOLUME_ACCESS_MODE_READ_ONLY_MANY VolumeAccessMode = 2 + // Read-write on many nodes (CSI MULTI_NODE_MULTI_WRITER). + 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 ( @@ -195,11 +251,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 { @@ -208,7 +264,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 @@ -246,11 +302,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 { @@ -259,7 +315,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} } // ActorMetadataField selects one identity field of the actor. @@ -299,11 +355,11 @@ func (x ActorMetadataField) String() string { } func (ActorMetadataField) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[4].Descriptor() + return file_ateapi_proto_enumTypes[5].Descriptor() } func (ActorMetadataField) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[4] + return &file_ateapi_proto_enumTypes[5] } func (x ActorMetadataField) Number() protoreflect.EnumNumber { @@ -312,7 +368,7 @@ func (x ActorMetadataField) Number() protoreflect.EnumNumber { // Deprecated: Use ActorMetadataField.Descriptor instead. func (ActorMetadataField) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{4} + return file_ateapi_proto_rawDescGZIP(), []int{5} } type WorkerState int32 @@ -350,11 +406,11 @@ func (x WorkerState) String() string { } func (WorkerState) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[5].Descriptor() + return file_ateapi_proto_enumTypes[6].Descriptor() } func (WorkerState) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[5] + return &file_ateapi_proto_enumTypes[6] } func (x WorkerState) Number() protoreflect.EnumNumber { @@ -363,7 +419,7 @@ func (x WorkerState) Number() protoreflect.EnumNumber { // Deprecated: Use WorkerState.Descriptor instead. func (WorkerState) EnumDescriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{5} + return file_ateapi_proto_rawDescGZIP(), []int{6} } type ExternalVolume_Status int32 @@ -405,11 +461,11 @@ func (x ExternalVolume_Status) String() string { } func (ExternalVolume_Status) Descriptor() protoreflect.EnumDescriptor { - return file_ateapi_proto_enumTypes[6].Descriptor() + return file_ateapi_proto_enumTypes[7].Descriptor() } func (ExternalVolume_Status) Type() protoreflect.EnumType { - return &file_ateapi_proto_enumTypes[6] + return &file_ateapi_proto_enumTypes[7] } func (x ExternalVolume_Status) Number() protoreflect.EnumNumber { @@ -951,9 +1007,26 @@ type Actor struct { // server and ignored on input. It is always present in output. // // +k8s:optional - Status *ActorStatus `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Status *ActorStatus `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + // existing_volumes supply the template's existing volumes + // (Volume.existing_volume) for this actor: each names one and references a + // CSI volume that exists outside Substrate and outlives the actor, such as + // a read-write-many volume several actors work on. Substrate attaches and + // mounts it for this actor, and never creates or deletes it nor changes its + // content. An existing volume of the template that this list does not name + // has neither the volume nor its mounts. An actor that names any boots from + // its image instead of restoring the template's golden snapshot, which was + // captured without these mounts. Set once at creation and immutable + // afterward. + // + // +k8s:optional + // +k8s:maxItems=8 + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:immutable + ExistingVolumes []*ExistingVolume `protobuf:"bytes,10001,rep,name=existing_volumes,json=existingVolumes,proto3" json:"existing_volumes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Actor) Reset() { @@ -1021,6 +1094,126 @@ func (x *Actor) GetStatus() *ActorStatus { return nil } +func (x *Actor) GetExistingVolumes() []*ExistingVolume { + if x != nil { + return x.ExistingVolumes + } + return nil +} + +// ExistingVolume references a CSI volume for one of the template's existing +// volumes. +type ExistingVolume struct { + state protoimpl.MessageState `protogen:"open.v1"` + // name of the template's existing volume this supplies. + // + // +k8s:required + // +k8s:format=k8s-short-name + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // driver is the CSI driver that serves the volume. It must have a + // CSIDriverConfig. + // + // +k8s:required + // +k8s:maxLength=253 + // +k8s:customValidation # same syntax as ExternalVolume.volume_type + Driver string `protobuf:"bytes,2,opt,name=driver,proto3" json:"driver,omitempty"` + // volume_handle is the driver's ID of the volume, as a Kubernetes + // PersistentVolume names it in spec.csi.volumeHandle. A PersistentVolume of + // the driver with this handle must exist; its spec.csi.volumeAttributes are + // passed to the driver when the volume is mounted. Several existing volumes + // of one actor may reference the same handle. + // + // +k8s:required + // +k8s:maxLength=256 + // +k8s:customValidation # no control characters + VolumeHandle string `protobuf:"bytes,3,opt,name=volume_handle,json=volumeHandle,proto3" json:"volume_handle,omitempty"` + // access_mode is READ_WRITE_MANY or READ_ONLY_MANY, and must be one of the + // PersistentVolume's access modes. With READ_ONLY_MANY every mount of the + // volume is read-only. + // + // +k8s:required + // +k8s:minimum=2 + // +k8s:maximum=3 # keep in sync with VolumeAccessMode + AccessMode VolumeAccessMode `protobuf:"varint,4,opt,name=access_mode,json=accessMode,proto3,enum=ateapi.VolumeAccessMode" json:"access_mode,omitempty"` + // sub_path is the directory of the volume that this actor sees as the + // volume's root, in the form of VolumeMount.sub_path; empty is the root. + // The template's mounts of the volume, and their own sub_path, are + // relative to it, so actors of one template can each work in a directory + // of their own. + // + // +k8s:optional + // +k8s:maxLength=4096 + // +k8s:customValidation # clean relative path + SubPath string `protobuf:"bytes,5,opt,name=sub_path,json=subPath,proto3" json:"sub_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExistingVolume) Reset() { + *x = ExistingVolume{} + mi := &file_ateapi_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExistingVolume) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExistingVolume) ProtoMessage() {} + +func (x *ExistingVolume) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExistingVolume.ProtoReflect.Descriptor instead. +func (*ExistingVolume) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{6} +} + +func (x *ExistingVolume) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ExistingVolume) GetDriver() string { + if x != nil { + return x.Driver + } + return "" +} + +func (x *ExistingVolume) GetVolumeHandle() string { + if x != nil { + return x.VolumeHandle + } + return "" +} + +func (x *ExistingVolume) GetAccessMode() VolumeAccessMode { + if x != nil { + return x.AccessMode + } + return VolumeAccessMode_VOLUME_ACCESS_MODE_UNSPECIFIED +} + +func (x *ExistingVolume) GetSubPath() string { + if x != nil { + return x.SubPath + } + return "" +} + // EgressPolicy is an egress policy resource nested under an Actor. An Actor has // at most one egress policy resource, named "default". type EgressPolicy struct { @@ -1061,7 +1254,7 @@ type EgressPolicy struct { func (x *EgressPolicy) Reset() { *x = EgressPolicy{} - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1073,7 +1266,7 @@ func (x *EgressPolicy) String() string { func (*EgressPolicy) ProtoMessage() {} func (x *EgressPolicy) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[6] + mi := &file_ateapi_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1086,7 +1279,7 @@ func (x *EgressPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressPolicy.ProtoReflect.Descriptor instead. func (*EgressPolicy) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{6} + return file_ateapi_proto_rawDescGZIP(), []int{7} } func (x *EgressPolicy) GetMetadata() *ResourceMetadata { @@ -1121,7 +1314,7 @@ type EgressPolicyTemplate struct { func (x *EgressPolicyTemplate) Reset() { *x = EgressPolicyTemplate{} - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1133,7 +1326,7 @@ func (x *EgressPolicyTemplate) String() string { func (*EgressPolicyTemplate) ProtoMessage() {} func (x *EgressPolicyTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[7] + mi := &file_ateapi_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1146,7 +1339,7 @@ func (x *EgressPolicyTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressPolicyTemplate.ProtoReflect.Descriptor instead. func (*EgressPolicyTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{7} + return file_ateapi_proto_rawDescGZIP(), []int{8} } func (x *EgressPolicyTemplate) GetRules() []*EgressRule { @@ -1189,7 +1382,7 @@ type EgressRule struct { func (x *EgressRule) Reset() { *x = EgressRule{} - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1201,7 +1394,7 @@ func (x *EgressRule) String() string { func (*EgressRule) ProtoMessage() {} func (x *EgressRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[8] + mi := &file_ateapi_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1214,7 +1407,7 @@ func (x *EgressRule) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressRule.ProtoReflect.Descriptor instead. func (*EgressRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{8} + return file_ateapi_proto_rawDescGZIP(), []int{9} } func (x *EgressRule) GetHttp() *HTTPRule { @@ -1282,7 +1475,7 @@ type HTTPRule struct { func (x *HTTPRule) Reset() { *x = HTTPRule{} - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1294,7 +1487,7 @@ func (x *HTTPRule) String() string { func (*HTTPRule) ProtoMessage() {} func (x *HTTPRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[9] + mi := &file_ateapi_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1307,7 +1500,7 @@ func (x *HTTPRule) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPRule.ProtoReflect.Descriptor instead. func (*HTTPRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{9} + return file_ateapi_proto_rawDescGZIP(), []int{10} } func (x *HTTPRule) GetHostnames() []string { @@ -1367,7 +1560,7 @@ type HTTPSRule struct { func (x *HTTPSRule) Reset() { *x = HTTPSRule{} - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1379,7 +1572,7 @@ func (x *HTTPSRule) String() string { func (*HTTPSRule) ProtoMessage() {} func (x *HTTPSRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[10] + mi := &file_ateapi_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1392,7 +1585,7 @@ func (x *HTTPSRule) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPSRule.ProtoReflect.Descriptor instead. func (*HTTPSRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{10} + return file_ateapi_proto_rawDescGZIP(), []int{11} } func (x *HTTPSRule) GetHostnames() []string { @@ -1443,7 +1636,7 @@ type TLSPassthroughRule struct { func (x *TLSPassthroughRule) Reset() { *x = TLSPassthroughRule{} - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1455,7 +1648,7 @@ func (x *TLSPassthroughRule) String() string { func (*TLSPassthroughRule) ProtoMessage() {} func (x *TLSPassthroughRule) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[11] + mi := &file_ateapi_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1468,7 +1661,7 @@ func (x *TLSPassthroughRule) ProtoReflect() protoreflect.Message { // Deprecated: Use TLSPassthroughRule.ProtoReflect.Descriptor instead. func (*TLSPassthroughRule) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{11} + return file_ateapi_proto_rawDescGZIP(), []int{12} } func (x *TLSPassthroughRule) GetHostnames() []string { @@ -1509,7 +1702,7 @@ type Ports struct { func (x *Ports) Reset() { *x = Ports{} - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1521,7 +1714,7 @@ func (x *Ports) String() string { func (*Ports) ProtoMessage() {} func (x *Ports) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[12] + mi := &file_ateapi_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1534,7 +1727,7 @@ func (x *Ports) ProtoReflect() protoreflect.Message { // Deprecated: Use Ports.ProtoReflect.Descriptor instead. func (*Ports) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{12} + return file_ateapi_proto_rawDescGZIP(), []int{13} } func (x *Ports) GetAll() *AllPorts { @@ -1560,7 +1753,7 @@ type AllPorts struct { func (x *AllPorts) Reset() { *x = AllPorts{} - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1572,7 +1765,7 @@ func (x *AllPorts) String() string { func (*AllPorts) ProtoMessage() {} func (x *AllPorts) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[13] + mi := &file_ateapi_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1585,7 +1778,7 @@ func (x *AllPorts) ProtoReflect() protoreflect.Message { // Deprecated: Use AllPorts.ProtoReflect.Descriptor instead. func (*AllPorts) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{13} + return file_ateapi_proto_rawDescGZIP(), []int{14} } // HttpRuleEffects contains effects applied by a matching HTTP or HTTPS rule. @@ -1612,7 +1805,7 @@ type HttpRuleEffects struct { func (x *HttpRuleEffects) Reset() { *x = HttpRuleEffects{} - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1624,7 +1817,7 @@ func (x *HttpRuleEffects) String() string { func (*HttpRuleEffects) ProtoMessage() {} func (x *HttpRuleEffects) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[14] + mi := &file_ateapi_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1637,7 +1830,7 @@ func (x *HttpRuleEffects) ProtoReflect() protoreflect.Message { // Deprecated: Use HttpRuleEffects.ProtoReflect.Descriptor instead. func (*HttpRuleEffects) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{14} + return file_ateapi_proto_rawDescGZIP(), []int{15} } func (x *HttpRuleEffects) GetReplaceHeaders() []*CredentialHeader { @@ -1687,7 +1880,7 @@ type CredentialHeader struct { func (x *CredentialHeader) Reset() { *x = CredentialHeader{} - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1699,7 +1892,7 @@ func (x *CredentialHeader) String() string { func (*CredentialHeader) ProtoMessage() {} func (x *CredentialHeader) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[15] + mi := &file_ateapi_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1712,7 +1905,7 @@ func (x *CredentialHeader) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialHeader.ProtoReflect.Descriptor instead. func (*CredentialHeader) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{15} + return file_ateapi_proto_rawDescGZIP(), []int{16} } func (x *CredentialHeader) GetHeader() string { @@ -1769,7 +1962,7 @@ type ActorJWTSource struct { func (x *ActorJWTSource) Reset() { *x = ActorJWTSource{} - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1781,7 +1974,7 @@ func (x *ActorJWTSource) String() string { func (*ActorJWTSource) ProtoMessage() {} func (x *ActorJWTSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[16] + mi := &file_ateapi_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1794,7 +1987,7 @@ func (x *ActorJWTSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorJWTSource.ProtoReflect.Descriptor instead. func (*ActorJWTSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{16} + return file_ateapi_proto_rawDescGZIP(), []int{17} } func (x *ActorJWTSource) GetAudiences() []string { @@ -1875,7 +2068,7 @@ type ActorStatus struct { func (x *ActorStatus) Reset() { *x = ActorStatus{} - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1887,7 +2080,7 @@ func (x *ActorStatus) String() string { func (*ActorStatus) ProtoMessage() {} func (x *ActorStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[17] + mi := &file_ateapi_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1900,7 +2093,7 @@ func (x *ActorStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorStatus.ProtoReflect.Descriptor instead. func (*ActorStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{17} + return file_ateapi_proto_rawDescGZIP(), []int{18} } func (x *ActorStatus) GetState() ActorState { @@ -1984,7 +2177,7 @@ type ActorCrash struct { func (x *ActorCrash) Reset() { *x = ActorCrash{} - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1996,7 +2189,7 @@ func (x *ActorCrash) String() string { func (*ActorCrash) ProtoMessage() {} func (x *ActorCrash) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[18] + mi := &file_ateapi_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2009,7 +2202,7 @@ func (x *ActorCrash) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorCrash.ProtoReflect.Descriptor instead. func (*ActorCrash) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{18} + return file_ateapi_proto_rawDescGZIP(), []int{19} } func (x *ActorCrash) GetMessage() string { @@ -2090,7 +2283,7 @@ type WorkerAssignment struct { func (x *WorkerAssignment) Reset() { *x = WorkerAssignment{} - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2102,7 +2295,7 @@ func (x *WorkerAssignment) String() string { func (*WorkerAssignment) ProtoMessage() {} func (x *WorkerAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[19] + mi := &file_ateapi_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2115,7 +2308,7 @@ func (x *WorkerAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerAssignment.ProtoReflect.Descriptor instead. func (*WorkerAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{19} + return file_ateapi_proto_rawDescGZIP(), []int{20} } func (x *WorkerAssignment) GetWorker() *ObjectRef { @@ -2205,7 +2398,7 @@ type TagStatus struct { func (x *TagStatus) Reset() { *x = TagStatus{} - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2217,7 +2410,7 @@ func (x *TagStatus) String() string { func (*TagStatus) ProtoMessage() {} func (x *TagStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[20] + mi := &file_ateapi_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2230,7 +2423,7 @@ func (x *TagStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use TagStatus.ProtoReflect.Descriptor instead. func (*TagStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{20} + return file_ateapi_proto_rawDescGZIP(), []int{21} } func (x *TagStatus) GetSnapshot() *ExternalSnapshot { @@ -2289,7 +2482,7 @@ type Tag struct { func (x *Tag) Reset() { *x = Tag{} - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2301,7 +2494,7 @@ func (x *Tag) String() string { func (*Tag) ProtoMessage() {} func (x *Tag) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[21] + mi := &file_ateapi_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2314,7 +2507,7 @@ func (x *Tag) ProtoReflect() protoreflect.Message { // Deprecated: Use Tag.ProtoReflect.Descriptor instead. func (*Tag) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{21} + return file_ateapi_proto_rawDescGZIP(), []int{22} } func (x *Tag) GetMetadata() *ResourceMetadata { @@ -2360,7 +2553,7 @@ type Atespace struct { func (x *Atespace) Reset() { *x = Atespace{} - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2372,7 +2565,7 @@ func (x *Atespace) String() string { func (*Atespace) ProtoMessage() {} func (x *Atespace) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[22] + mi := &file_ateapi_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2385,7 +2578,7 @@ func (x *Atespace) ProtoReflect() protoreflect.Message { // Deprecated: Use Atespace.ProtoReflect.Descriptor instead. func (*Atespace) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{22} + return file_ateapi_proto_rawDescGZIP(), []int{23} } func (x *Atespace) GetMetadata() *ResourceMetadata { @@ -2418,7 +2611,7 @@ type ObjectRef struct { func (x *ObjectRef) Reset() { *x = ObjectRef{} - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2430,7 +2623,7 @@ func (x *ObjectRef) String() string { func (*ObjectRef) ProtoMessage() {} func (x *ObjectRef) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[23] + mi := &file_ateapi_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2443,7 +2636,7 @@ func (x *ObjectRef) ProtoReflect() protoreflect.Message { // Deprecated: Use ObjectRef.ProtoReflect.Descriptor instead. func (*ObjectRef) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{23} + return file_ateapi_proto_rawDescGZIP(), []int{24} } func (x *ObjectRef) GetAtespace() string { @@ -2507,7 +2700,7 @@ type ActorTemplate struct { func (x *ActorTemplate) Reset() { *x = ActorTemplate{} - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2519,7 +2712,7 @@ func (x *ActorTemplate) String() string { func (*ActorTemplate) ProtoMessage() {} func (x *ActorTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[24] + mi := &file_ateapi_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2532,7 +2725,7 @@ func (x *ActorTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorTemplate.ProtoReflect.Descriptor instead. func (*ActorTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{24} + return file_ateapi_proto_rawDescGZIP(), []int{25} } func (x *ActorTemplate) GetMetadata() *ResourceMetadata { @@ -2615,7 +2808,7 @@ type Resources struct { func (x *Resources) Reset() { *x = Resources{} - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2627,7 +2820,7 @@ func (x *Resources) String() string { func (*Resources) ProtoMessage() {} func (x *Resources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[25] + mi := &file_ateapi_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2640,7 +2833,7 @@ func (x *Resources) ProtoReflect() protoreflect.Message { // Deprecated: Use Resources.ProtoReflect.Descriptor instead. func (*Resources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{25} + return file_ateapi_proto_rawDescGZIP(), []int{26} } func (x *Resources) GetLimits() []*Limits { @@ -2668,7 +2861,7 @@ type Limits struct { func (x *Limits) Reset() { *x = Limits{} - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2680,7 +2873,7 @@ func (x *Limits) String() string { func (*Limits) ProtoMessage() {} func (x *Limits) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[26] + mi := &file_ateapi_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2693,7 +2886,7 @@ func (x *Limits) ProtoReflect() protoreflect.Message { // Deprecated: Use Limits.ProtoReflect.Descriptor instead. func (*Limits) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{26} + return file_ateapi_proto_rawDescGZIP(), []int{27} } func (x *Limits) GetName() string { @@ -2734,7 +2927,7 @@ type GoldenSnapshotStatus struct { func (x *GoldenSnapshotStatus) Reset() { *x = GoldenSnapshotStatus{} - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2746,7 +2939,7 @@ func (x *GoldenSnapshotStatus) String() string { func (*GoldenSnapshotStatus) ProtoMessage() {} func (x *GoldenSnapshotStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[27] + mi := &file_ateapi_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2759,7 +2952,7 @@ func (x *GoldenSnapshotStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use GoldenSnapshotStatus.ProtoReflect.Descriptor instead. func (*GoldenSnapshotStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{27} + return file_ateapi_proto_rawDescGZIP(), []int{28} } func (x *GoldenSnapshotStatus) GetGoldenTag() *ObjectRef { @@ -2793,7 +2986,7 @@ type ActorTemplateStatus struct { func (x *ActorTemplateStatus) Reset() { *x = ActorTemplateStatus{} - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2805,7 +2998,7 @@ func (x *ActorTemplateStatus) String() string { func (*ActorTemplateStatus) ProtoMessage() {} func (x *ActorTemplateStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[28] + mi := &file_ateapi_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2818,7 +3011,7 @@ func (x *ActorTemplateStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorTemplateStatus.ProtoReflect.Descriptor instead. func (*ActorTemplateStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{28} + return file_ateapi_proto_rawDescGZIP(), []int{29} } func (x *ActorTemplateStatus) GetGoldenSnapshotStatus() *GoldenSnapshotStatus { @@ -2849,7 +3042,7 @@ type SandboxConfig struct { func (x *SandboxConfig) Reset() { *x = SandboxConfig{} - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2861,7 +3054,7 @@ func (x *SandboxConfig) String() string { func (*SandboxConfig) ProtoMessage() {} func (x *SandboxConfig) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[29] + mi := &file_ateapi_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2874,7 +3067,7 @@ func (x *SandboxConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SandboxConfig.ProtoReflect.Descriptor instead. func (*SandboxConfig) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{29} + return file_ateapi_proto_rawDescGZIP(), []int{30} } func (x *SandboxConfig) GetSandboxClass() SandboxClass { @@ -2928,7 +3121,7 @@ type SnapshotConfig struct { func (x *SnapshotConfig) Reset() { *x = SnapshotConfig{} - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2940,7 +3133,7 @@ func (x *SnapshotConfig) String() string { func (*SnapshotConfig) ProtoMessage() {} func (x *SnapshotConfig) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[30] + mi := &file_ateapi_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2953,7 +3146,7 @@ func (x *SnapshotConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotConfig.ProtoReflect.Descriptor instead. func (*SnapshotConfig) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{30} + return file_ateapi_proto_rawDescGZIP(), []int{31} } func (x *SnapshotConfig) GetOnPause() SnapshotContentScope { @@ -3047,7 +3240,7 @@ type Container struct { func (x *Container) Reset() { *x = Container{} - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3059,7 +3252,7 @@ func (x *Container) String() string { func (*Container) ProtoMessage() {} func (x *Container) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[31] + mi := &file_ateapi_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3072,7 +3265,7 @@ func (x *Container) ProtoReflect() protoreflect.Message { // Deprecated: Use Container.ProtoReflect.Descriptor instead. func (*Container) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{31} + return file_ateapi_proto_rawDescGZIP(), []int{32} } func (x *Container) GetName() string { @@ -3152,7 +3345,7 @@ type SecurityContext struct { func (x *SecurityContext) Reset() { *x = SecurityContext{} - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3164,7 +3357,7 @@ func (x *SecurityContext) String() string { func (*SecurityContext) ProtoMessage() {} func (x *SecurityContext) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[32] + mi := &file_ateapi_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3177,7 +3370,7 @@ func (x *SecurityContext) ProtoReflect() protoreflect.Message { // Deprecated: Use SecurityContext.ProtoReflect.Descriptor instead. func (*SecurityContext) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{32} + return file_ateapi_proto_rawDescGZIP(), []int{33} } func (x *SecurityContext) GetCapabilities() *Capabilities { @@ -3215,7 +3408,7 @@ type Capabilities struct { func (x *Capabilities) Reset() { *x = Capabilities{} - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3227,7 +3420,7 @@ func (x *Capabilities) String() string { func (*Capabilities) ProtoMessage() {} func (x *Capabilities) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[33] + mi := &file_ateapi_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3240,7 +3433,7 @@ func (x *Capabilities) ProtoReflect() protoreflect.Message { // Deprecated: Use Capabilities.ProtoReflect.Descriptor instead. func (*Capabilities) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{33} + return file_ateapi_proto_rawDescGZIP(), []int{34} } func (x *Capabilities) GetAdd() []string { @@ -3278,7 +3471,7 @@ type EnvVar struct { func (x *EnvVar) Reset() { *x = EnvVar{} - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3290,7 +3483,7 @@ func (x *EnvVar) String() string { func (*EnvVar) ProtoMessage() {} func (x *EnvVar) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[34] + mi := &file_ateapi_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3303,7 +3496,7 @@ func (x *EnvVar) ProtoReflect() protoreflect.Message { // Deprecated: Use EnvVar.ProtoReflect.Descriptor instead. func (*EnvVar) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{34} + return file_ateapi_proto_rawDescGZIP(), []int{35} } func (x *EnvVar) GetName() string { @@ -3343,7 +3536,7 @@ type ContainerWakeupProbe struct { func (x *ContainerWakeupProbe) Reset() { *x = ContainerWakeupProbe{} - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3355,7 +3548,7 @@ func (x *ContainerWakeupProbe) String() string { func (*ContainerWakeupProbe) ProtoMessage() {} func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[35] + mi := &file_ateapi_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3368,7 +3561,7 @@ func (x *ContainerWakeupProbe) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerWakeupProbe.ProtoReflect.Descriptor instead. func (*ContainerWakeupProbe) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{35} + return file_ateapi_proto_rawDescGZIP(), []int{36} } func (x *ContainerWakeupProbe) GetHttpGet() *HTTPGetAction { @@ -3408,7 +3601,7 @@ type HTTPGetAction struct { func (x *HTTPGetAction) Reset() { *x = HTTPGetAction{} - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3420,7 +3613,7 @@ func (x *HTTPGetAction) String() string { func (*HTTPGetAction) ProtoMessage() {} func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[36] + mi := &file_ateapi_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3433,7 +3626,7 @@ func (x *HTTPGetAction) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPGetAction.ProtoReflect.Descriptor instead. func (*HTTPGetAction) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{36} + return file_ateapi_proto_rawDescGZIP(), []int{37} } func (x *HTTPGetAction) GetPath() string { @@ -3458,7 +3651,7 @@ type Volume struct { // +k8s:format=k8s-short-name Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // Exactly one of durable_dir / external_volume_template / image / - // system_info must be set. + // system_info / existing_volume must be set. // // +k8s:optional // +k8s:unionMember @@ -3473,14 +3666,20 @@ type Volume struct { // // +k8s:optional // +k8s:unionMember - Image *ImageVolumeSource `protobuf:"bytes,6,opt,name=image,proto3" json:"image,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Image *ImageVolumeSource `protobuf:"bytes,6,opt,name=image,proto3" json:"image,omitempty"` + // existing_volume declares a volume each actor supplies at CreateActor + // (Actor.existing_volumes). + // + // +k8s:optional + // +k8s:unionMember + ExistingVolume *ExistingVolumeSource `protobuf:"bytes,10001,opt,name=existing_volume,json=existingVolume,proto3" json:"existing_volume,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Volume) Reset() { *x = Volume{} - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3492,7 +3691,7 @@ func (x *Volume) String() string { func (*Volume) ProtoMessage() {} func (x *Volume) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[37] + mi := &file_ateapi_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3505,7 +3704,7 @@ func (x *Volume) ProtoReflect() protoreflect.Message { // Deprecated: Use Volume.ProtoReflect.Descriptor instead. func (*Volume) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{37} + return file_ateapi_proto_rawDescGZIP(), []int{38} } func (x *Volume) GetName() string { @@ -3543,6 +3742,52 @@ func (x *Volume) GetImage() *ImageVolumeSource { return nil } +func (x *Volume) GetExistingVolume() *ExistingVolumeSource { + if x != nil { + return x.ExistingVolume + } + return nil +} + +// ExistingVolumeSource declares a volume that exists outside Substrate and +// that each actor supplies at CreateActor in Actor.existing_volumes. An actor +// that supplies none has neither the volume nor its mounts. +type ExistingVolumeSource struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExistingVolumeSource) Reset() { + *x = ExistingVolumeSource{} + mi := &file_ateapi_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExistingVolumeSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExistingVolumeSource) ProtoMessage() {} + +func (x *ExistingVolumeSource) ProtoReflect() protoreflect.Message { + mi := &file_ateapi_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExistingVolumeSource.ProtoReflect.Descriptor instead. +func (*ExistingVolumeSource) Descriptor() ([]byte, []int) { + return file_ateapi_proto_rawDescGZIP(), []int{39} +} + // ImageVolumeSource mounts the contents of an OCI image, read-only. The // reference must include a digest: changing the image invalidates // snapshots. @@ -3561,7 +3806,7 @@ type ImageVolumeSource struct { func (x *ImageVolumeSource) Reset() { *x = ImageVolumeSource{} - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3573,7 +3818,7 @@ func (x *ImageVolumeSource) String() string { func (*ImageVolumeSource) ProtoMessage() {} func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[38] + mi := &file_ateapi_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3586,7 +3831,7 @@ func (x *ImageVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ImageVolumeSource.ProtoReflect.Descriptor instead. func (*ImageVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{38} + return file_ateapi_proto_rawDescGZIP(), []int{40} } func (x *ImageVolumeSource) GetReference() string { @@ -3606,7 +3851,7 @@ type DurableDirVolumeSource struct { func (x *DurableDirVolumeSource) Reset() { *x = DurableDirVolumeSource{} - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3618,7 +3863,7 @@ func (x *DurableDirVolumeSource) String() string { func (*DurableDirVolumeSource) ProtoMessage() {} func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[39] + mi := &file_ateapi_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3631,7 +3876,7 @@ func (x *DurableDirVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use DurableDirVolumeSource.ProtoReflect.Descriptor instead. func (*DurableDirVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{39} + return file_ateapi_proto_rawDescGZIP(), []int{41} } // ExternalVolumeTemplate provisions an external volume per actor; the volume @@ -3657,7 +3902,7 @@ type ExternalVolumeTemplate struct { func (x *ExternalVolumeTemplate) Reset() { *x = ExternalVolumeTemplate{} - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3669,7 +3914,7 @@ func (x *ExternalVolumeTemplate) String() string { func (*ExternalVolumeTemplate) ProtoMessage() {} func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[40] + mi := &file_ateapi_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3682,7 +3927,7 @@ func (x *ExternalVolumeTemplate) ProtoReflect() protoreflect.Message { // Deprecated: Use ExternalVolumeTemplate.ProtoReflect.Descriptor instead. func (*ExternalVolumeTemplate) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{40} + return file_ateapi_proto_rawDescGZIP(), []int{42} } func (x *ExternalVolumeTemplate) GetCapacity() string { @@ -3719,7 +3964,7 @@ type SystemInfoVolumeSource struct { func (x *SystemInfoVolumeSource) Reset() { *x = SystemInfoVolumeSource{} - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3731,7 +3976,7 @@ func (x *SystemInfoVolumeSource) String() string { func (*SystemInfoVolumeSource) ProtoMessage() {} func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[41] + mi := &file_ateapi_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3744,7 +3989,7 @@ func (x *SystemInfoVolumeSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoVolumeSource.ProtoReflect.Descriptor instead. func (*SystemInfoVolumeSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{41} + return file_ateapi_proto_rawDescGZIP(), []int{43} } func (x *SystemInfoVolumeSource) GetDataSources() []*SystemInfoDataSource { @@ -3770,7 +4015,7 @@ type SystemInfoDataSource struct { func (x *SystemInfoDataSource) Reset() { *x = SystemInfoDataSource{} - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3782,7 +4027,7 @@ func (x *SystemInfoDataSource) String() string { func (*SystemInfoDataSource) ProtoMessage() {} func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[42] + mi := &file_ateapi_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3795,7 +4040,7 @@ func (x *SystemInfoDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use SystemInfoDataSource.ProtoReflect.Descriptor instead. func (*SystemInfoDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{42} + return file_ateapi_proto_rawDescGZIP(), []int{44} } func (x *SystemInfoDataSource) GetActorMetadata() *ActorMetadataDataSource { @@ -3832,7 +4077,7 @@ type ActorMetadataDataSource struct { func (x *ActorMetadataDataSource) Reset() { *x = ActorMetadataDataSource{} - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3844,7 +4089,7 @@ func (x *ActorMetadataDataSource) String() string { func (*ActorMetadataDataSource) ProtoMessage() {} func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[43] + mi := &file_ateapi_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3857,7 +4102,7 @@ func (x *ActorMetadataDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataDataSource.ProtoReflect.Descriptor instead. func (*ActorMetadataDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{43} + return file_ateapi_proto_rawDescGZIP(), []int{45} } func (x *ActorMetadataDataSource) GetItems() []*ActorMetadataItem { @@ -3889,7 +4134,7 @@ type ActorMetadataItem struct { func (x *ActorMetadataItem) Reset() { *x = ActorMetadataItem{} - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3901,7 +4146,7 @@ func (x *ActorMetadataItem) String() string { func (*ActorMetadataItem) ProtoMessage() {} func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[44] + mi := &file_ateapi_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3914,7 +4159,7 @@ func (x *ActorMetadataItem) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorMetadataItem.ProtoReflect.Descriptor instead. func (*ActorMetadataItem) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{44} + return file_ateapi_proto_rawDescGZIP(), []int{46} } func (x *ActorMetadataItem) GetField() ActorMetadataField { @@ -3962,7 +4207,7 @@ type TrustBundleDataSource struct { func (x *TrustBundleDataSource) Reset() { *x = TrustBundleDataSource{} - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3974,7 +4219,7 @@ func (x *TrustBundleDataSource) String() string { func (*TrustBundleDataSource) ProtoMessage() {} func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[45] + mi := &file_ateapi_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3987,7 +4232,7 @@ func (x *TrustBundleDataSource) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleDataSource.ProtoReflect.Descriptor instead. func (*TrustBundleDataSource) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{45} + return file_ateapi_proto_rawDescGZIP(), []int{47} } func (x *TrustBundleDataSource) GetNames() []string { @@ -4019,14 +4264,30 @@ type VolumeMount struct { // +k8s:required // +k8s:maxLength=4096 // +k8s:customValidation # clean-absolute-path shape; no regex/pattern tag exists - MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + MountPath string `protobuf:"bytes,2,opt,name=mount_path,json=mountPath,proto3" json:"mount_path,omitempty"` + // sub_path is the directory of the volume to mount, relative to its root; + // empty mounts the root. It must be a clean relative path: no leading '/', + // no '.' or '..' element, no '//', no trailing '/' and no control + // characters. Only an existing volume's mount may set it. The directory + // must exist, and no element of it may be a symbolic link: a mount that + // would need either fails. + // + // +k8s:optional + // +k8s:maxLength=4096 + // +k8s:customValidation # clean relative path + SubPath string `protobuf:"bytes,10001,opt,name=sub_path,json=subPath,proto3" json:"sub_path,omitempty"` + // read_only mounts the volume read-only. Only an existing volume's mount + // may set it. + // + // +k8s:optional + ReadOnly bool `protobuf:"varint,10002,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *VolumeMount) Reset() { *x = VolumeMount{} - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4038,7 +4299,7 @@ func (x *VolumeMount) String() string { func (*VolumeMount) ProtoMessage() {} func (x *VolumeMount) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[46] + mi := &file_ateapi_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4051,7 +4312,7 @@ func (x *VolumeMount) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeMount.ProtoReflect.Descriptor instead. func (*VolumeMount) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{46} + return file_ateapi_proto_rawDescGZIP(), []int{48} } func (x *VolumeMount) GetName() string { @@ -4068,6 +4329,20 @@ func (x *VolumeMount) GetMountPath() string { return "" } +func (x *VolumeMount) GetSubPath() string { + if x != nil { + return x.SubPath + } + return "" +} + +func (x *VolumeMount) GetReadOnly() bool { + if x != nil { + return x.ReadOnly + } + return false +} + type CreateAtespaceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // The atespace to create. @@ -4080,7 +4355,7 @@ type CreateAtespaceRequest struct { func (x *CreateAtespaceRequest) Reset() { *x = CreateAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4092,7 +4367,7 @@ func (x *CreateAtespaceRequest) String() string { func (*CreateAtespaceRequest) ProtoMessage() {} func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[47] + mi := &file_ateapi_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4105,7 +4380,7 @@ func (x *CreateAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateAtespaceRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{47} + return file_ateapi_proto_rawDescGZIP(), []int{49} } func (x *CreateAtespaceRequest) GetAtespace() *Atespace { @@ -4126,7 +4401,7 @@ type GetAtespaceRequest struct { func (x *GetAtespaceRequest) Reset() { *x = GetAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4138,7 +4413,7 @@ func (x *GetAtespaceRequest) String() string { func (*GetAtespaceRequest) ProtoMessage() {} func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[48] + mi := &file_ateapi_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4151,7 +4426,7 @@ func (x *GetAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{48} + return file_ateapi_proto_rawDescGZIP(), []int{50} } func (x *GetAtespaceRequest) GetAtespace() *ObjectRef { @@ -4182,7 +4457,7 @@ type ListAtespacesRequest struct { func (x *ListAtespacesRequest) Reset() { *x = ListAtespacesRequest{} - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4194,7 +4469,7 @@ func (x *ListAtespacesRequest) String() string { func (*ListAtespacesRequest) ProtoMessage() {} func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[49] + mi := &file_ateapi_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4207,7 +4482,7 @@ func (x *ListAtespacesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesRequest.ProtoReflect.Descriptor instead. func (*ListAtespacesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{49} + return file_ateapi_proto_rawDescGZIP(), []int{51} } func (x *ListAtespacesRequest) GetPageSize() int32 { @@ -4236,7 +4511,7 @@ type ListAtespacesResponse struct { func (x *ListAtespacesResponse) Reset() { *x = ListAtespacesResponse{} - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4248,7 +4523,7 @@ func (x *ListAtespacesResponse) String() string { func (*ListAtespacesResponse) ProtoMessage() {} func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[50] + mi := &file_ateapi_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4261,7 +4536,7 @@ func (x *ListAtespacesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAtespacesResponse.ProtoReflect.Descriptor instead. func (*ListAtespacesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{50} + return file_ateapi_proto_rawDescGZIP(), []int{52} } func (x *ListAtespacesResponse) GetAtespaces() []*Atespace { @@ -4293,7 +4568,7 @@ type DeleteAtespaceRequest struct { func (x *DeleteAtespaceRequest) Reset() { *x = DeleteAtespaceRequest{} - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4305,7 +4580,7 @@ func (x *DeleteAtespaceRequest) String() string { func (*DeleteAtespaceRequest) ProtoMessage() {} func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[51] + mi := &file_ateapi_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4318,7 +4593,7 @@ func (x *DeleteAtespaceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteAtespaceRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{51} + return file_ateapi_proto_rawDescGZIP(), []int{53} } func (x *DeleteAtespaceRequest) GetAtespace() *ObjectRef { @@ -4349,7 +4624,7 @@ type CreateActorTemplateRequest struct { func (x *CreateActorTemplateRequest) Reset() { *x = CreateActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4361,7 +4636,7 @@ func (x *CreateActorTemplateRequest) String() string { func (*CreateActorTemplateRequest) ProtoMessage() {} func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[52] + mi := &file_ateapi_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4374,7 +4649,7 @@ func (x *CreateActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorTemplateRequest.ProtoReflect.Descriptor instead. func (*CreateActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{52} + return file_ateapi_proto_rawDescGZIP(), []int{54} } func (x *CreateActorTemplateRequest) GetActorTemplate() *ActorTemplate { @@ -4395,7 +4670,7 @@ type GetActorTemplateRequest struct { func (x *GetActorTemplateRequest) Reset() { *x = GetActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4407,7 +4682,7 @@ func (x *GetActorTemplateRequest) String() string { func (*GetActorTemplateRequest) ProtoMessage() {} func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[53] + mi := &file_ateapi_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4420,7 +4695,7 @@ func (x *GetActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorTemplateRequest.ProtoReflect.Descriptor instead. func (*GetActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{53} + return file_ateapi_proto_rawDescGZIP(), []int{55} } func (x *GetActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4457,7 +4732,7 @@ type ListActorTemplatesRequest struct { func (x *ListActorTemplatesRequest) Reset() { *x = ListActorTemplatesRequest{} - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4469,7 +4744,7 @@ func (x *ListActorTemplatesRequest) String() string { func (*ListActorTemplatesRequest) ProtoMessage() {} func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[54] + mi := &file_ateapi_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4482,7 +4757,7 @@ func (x *ListActorTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListActorTemplatesRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{54} + return file_ateapi_proto_rawDescGZIP(), []int{56} } func (x *ListActorTemplatesRequest) GetAtespace() string { @@ -4519,7 +4794,7 @@ type ListActorTemplatesResponse struct { func (x *ListActorTemplatesResponse) Reset() { *x = ListActorTemplatesResponse{} - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4531,7 +4806,7 @@ func (x *ListActorTemplatesResponse) String() string { func (*ListActorTemplatesResponse) ProtoMessage() {} func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[55] + mi := &file_ateapi_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4544,7 +4819,7 @@ func (x *ListActorTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListActorTemplatesResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{55} + return file_ateapi_proto_rawDescGZIP(), []int{57} } func (x *ListActorTemplatesResponse) GetActorTemplates() []*ActorTemplate { @@ -4576,7 +4851,7 @@ type DeleteActorTemplateRequest struct { func (x *DeleteActorTemplateRequest) Reset() { *x = DeleteActorTemplateRequest{} - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4588,7 +4863,7 @@ func (x *DeleteActorTemplateRequest) String() string { func (*DeleteActorTemplateRequest) ProtoMessage() {} func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[56] + mi := &file_ateapi_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4601,7 +4876,7 @@ func (x *DeleteActorTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorTemplateRequest.ProtoReflect.Descriptor instead. func (*DeleteActorTemplateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{56} + return file_ateapi_proto_rawDescGZIP(), []int{58} } func (x *DeleteActorTemplateRequest) GetActorTemplate() *ObjectRef { @@ -4629,7 +4904,7 @@ type GetActorRequest struct { func (x *GetActorRequest) Reset() { *x = GetActorRequest{} - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4641,7 +4916,7 @@ func (x *GetActorRequest) String() string { func (*GetActorRequest) ProtoMessage() {} func (x *GetActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[57] + mi := &file_ateapi_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4654,7 +4929,7 @@ func (x *GetActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorRequest.ProtoReflect.Descriptor instead. func (*GetActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{57} + return file_ateapi_proto_rawDescGZIP(), []int{59} } func (x *GetActorRequest) GetActor() *ObjectRef { @@ -4677,7 +4952,7 @@ type CreateActorRequest struct { func (x *CreateActorRequest) Reset() { *x = CreateActorRequest{} - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4689,7 +4964,7 @@ func (x *CreateActorRequest) String() string { func (*CreateActorRequest) ProtoMessage() {} func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[58] + mi := &file_ateapi_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4702,7 +4977,7 @@ func (x *CreateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorRequest.ProtoReflect.Descriptor instead. func (*CreateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{58} + return file_ateapi_proto_rawDescGZIP(), []int{60} } func (x *CreateActorRequest) GetActor() *Actor { @@ -4733,7 +5008,7 @@ type UpdateActorRequest struct { func (x *UpdateActorRequest) Reset() { *x = UpdateActorRequest{} - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4745,7 +5020,7 @@ func (x *UpdateActorRequest) String() string { func (*UpdateActorRequest) ProtoMessage() {} func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[59] + mi := &file_ateapi_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4758,7 +5033,7 @@ func (x *UpdateActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorRequest.ProtoReflect.Descriptor instead. func (*UpdateActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{59} + return file_ateapi_proto_rawDescGZIP(), []int{61} } func (x *UpdateActorRequest) GetActor() *Actor { @@ -4792,7 +5067,7 @@ type FencingToken struct { func (x *FencingToken) Reset() { *x = FencingToken{} - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4804,7 +5079,7 @@ func (x *FencingToken) String() string { func (*FencingToken) ProtoMessage() {} func (x *FencingToken) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[60] + mi := &file_ateapi_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4817,7 +5092,7 @@ func (x *FencingToken) ProtoReflect() protoreflect.Message { // Deprecated: Use FencingToken.ProtoReflect.Descriptor instead. func (*FencingToken) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{60} + return file_ateapi_proto_rawDescGZIP(), []int{62} } func (x *FencingToken) GetHolder() string { @@ -4852,7 +5127,7 @@ type SuspendActorRequest struct { func (x *SuspendActorRequest) Reset() { *x = SuspendActorRequest{} - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4864,7 +5139,7 @@ func (x *SuspendActorRequest) String() string { func (*SuspendActorRequest) ProtoMessage() {} func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[61] + mi := &file_ateapi_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4877,7 +5152,7 @@ func (x *SuspendActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorRequest.ProtoReflect.Descriptor instead. func (*SuspendActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{61} + return file_ateapi_proto_rawDescGZIP(), []int{63} } func (x *SuspendActorRequest) GetActor() *ObjectRef { @@ -4903,7 +5178,7 @@ type SuspendActorResponse struct { func (x *SuspendActorResponse) Reset() { *x = SuspendActorResponse{} - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4915,7 +5190,7 @@ func (x *SuspendActorResponse) String() string { func (*SuspendActorResponse) ProtoMessage() {} func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[62] + mi := &file_ateapi_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4928,7 +5203,7 @@ func (x *SuspendActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SuspendActorResponse.ProtoReflect.Descriptor instead. func (*SuspendActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{62} + return file_ateapi_proto_rawDescGZIP(), []int{64} } func (x *SuspendActorResponse) GetActor() *Actor { @@ -4956,7 +5231,7 @@ type PauseActorRequest struct { func (x *PauseActorRequest) Reset() { *x = PauseActorRequest{} - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4968,7 +5243,7 @@ func (x *PauseActorRequest) String() string { func (*PauseActorRequest) ProtoMessage() {} func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[63] + mi := &file_ateapi_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4981,7 +5256,7 @@ func (x *PauseActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorRequest.ProtoReflect.Descriptor instead. func (*PauseActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{63} + return file_ateapi_proto_rawDescGZIP(), []int{65} } func (x *PauseActorRequest) GetActor() *ObjectRef { @@ -5007,7 +5282,7 @@ type PauseActorResponse struct { func (x *PauseActorResponse) Reset() { *x = PauseActorResponse{} - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5019,7 +5294,7 @@ func (x *PauseActorResponse) String() string { func (*PauseActorResponse) ProtoMessage() {} func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[64] + mi := &file_ateapi_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5032,7 +5307,7 @@ func (x *PauseActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PauseActorResponse.ProtoReflect.Descriptor instead. func (*PauseActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{64} + return file_ateapi_proto_rawDescGZIP(), []int{66} } func (x *PauseActorResponse) GetActor() *Actor { @@ -5060,7 +5335,7 @@ type ResumeActorRequest struct { func (x *ResumeActorRequest) Reset() { *x = ResumeActorRequest{} - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5072,7 +5347,7 @@ func (x *ResumeActorRequest) String() string { func (*ResumeActorRequest) ProtoMessage() {} func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[65] + mi := &file_ateapi_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5085,7 +5360,7 @@ func (x *ResumeActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorRequest.ProtoReflect.Descriptor instead. func (*ResumeActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{65} + return file_ateapi_proto_rawDescGZIP(), []int{67} } func (x *ResumeActorRequest) GetActor() *ObjectRef { @@ -5114,7 +5389,7 @@ type ResumeActorResponse struct { func (x *ResumeActorResponse) Reset() { *x = ResumeActorResponse{} - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5126,7 +5401,7 @@ func (x *ResumeActorResponse) String() string { func (*ResumeActorResponse) ProtoMessage() {} func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[66] + mi := &file_ateapi_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5139,7 +5414,7 @@ func (x *ResumeActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ResumeActorResponse.ProtoReflect.Descriptor instead. func (*ResumeActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{66} + return file_ateapi_proto_rawDescGZIP(), []int{68} } func (x *ResumeActorResponse) GetActor() *Actor { @@ -5169,7 +5444,7 @@ type RevertActorRequest struct { func (x *RevertActorRequest) Reset() { *x = RevertActorRequest{} - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5181,7 +5456,7 @@ func (x *RevertActorRequest) String() string { func (*RevertActorRequest) ProtoMessage() {} func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[67] + mi := &file_ateapi_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5194,7 +5469,7 @@ func (x *RevertActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorRequest.ProtoReflect.Descriptor instead. func (*RevertActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{67} + return file_ateapi_proto_rawDescGZIP(), []int{69} } func (x *RevertActorRequest) GetActor() *ObjectRef { @@ -5216,7 +5491,7 @@ type RevertActorResponse struct { func (x *RevertActorResponse) Reset() { *x = RevertActorResponse{} - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5228,7 +5503,7 @@ func (x *RevertActorResponse) String() string { func (*RevertActorResponse) ProtoMessage() {} func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[68] + mi := &file_ateapi_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5241,7 +5516,7 @@ func (x *RevertActorResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevertActorResponse.ProtoReflect.Descriptor instead. func (*RevertActorResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{68} + return file_ateapi_proto_rawDescGZIP(), []int{70} } func (x *RevertActorResponse) GetActor() *Actor { @@ -5270,7 +5545,7 @@ type DeleteActorRequest struct { func (x *DeleteActorRequest) Reset() { *x = DeleteActorRequest{} - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5282,7 +5557,7 @@ func (x *DeleteActorRequest) String() string { func (*DeleteActorRequest) ProtoMessage() {} func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[69] + mi := &file_ateapi_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5295,7 +5570,7 @@ func (x *DeleteActorRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorRequest.ProtoReflect.Descriptor instead. func (*DeleteActorRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{69} + return file_ateapi_proto_rawDescGZIP(), []int{71} } func (x *DeleteActorRequest) GetActor() *ObjectRef { @@ -5333,7 +5608,7 @@ type GetActorEgressPolicyRequest struct { func (x *GetActorEgressPolicyRequest) Reset() { *x = GetActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5345,7 +5620,7 @@ func (x *GetActorEgressPolicyRequest) String() string { func (*GetActorEgressPolicyRequest) ProtoMessage() {} func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[70] + mi := &file_ateapi_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5358,7 +5633,7 @@ func (x *GetActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*GetActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{70} + return file_ateapi_proto_rawDescGZIP(), []int{72} } func (x *GetActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5389,7 +5664,7 @@ type CreateActorEgressPolicyRequest struct { func (x *CreateActorEgressPolicyRequest) Reset() { *x = CreateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5401,7 +5676,7 @@ func (x *CreateActorEgressPolicyRequest) String() string { func (*CreateActorEgressPolicyRequest) ProtoMessage() {} func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[71] + mi := &file_ateapi_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5414,7 +5689,7 @@ func (x *CreateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{71} + return file_ateapi_proto_rawDescGZIP(), []int{73} } func (x *CreateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5452,7 +5727,7 @@ type UpdateActorEgressPolicyRequest struct { func (x *UpdateActorEgressPolicyRequest) Reset() { *x = UpdateActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5464,7 +5739,7 @@ func (x *UpdateActorEgressPolicyRequest) String() string { func (*UpdateActorEgressPolicyRequest) ProtoMessage() {} func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[72] + mi := &file_ateapi_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5477,7 +5752,7 @@ func (x *UpdateActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{72} + return file_ateapi_proto_rawDescGZIP(), []int{74} } func (x *UpdateActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5512,7 +5787,7 @@ type DeleteActorEgressPolicyRequest struct { func (x *DeleteActorEgressPolicyRequest) Reset() { *x = DeleteActorEgressPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5524,7 +5799,7 @@ func (x *DeleteActorEgressPolicyRequest) String() string { func (*DeleteActorEgressPolicyRequest) ProtoMessage() {} func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[73] + mi := &file_ateapi_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5537,7 +5812,7 @@ func (x *DeleteActorEgressPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteActorEgressPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteActorEgressPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{73} + return file_ateapi_proto_rawDescGZIP(), []int{75} } func (x *DeleteActorEgressPolicyRequest) GetActor() *ObjectRef { @@ -5564,7 +5839,7 @@ type GetEgressPolicyContractRequest struct { func (x *GetEgressPolicyContractRequest) Reset() { *x = GetEgressPolicyContractRequest{} - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5576,7 +5851,7 @@ func (x *GetEgressPolicyContractRequest) String() string { func (*GetEgressPolicyContractRequest) ProtoMessage() {} func (x *GetEgressPolicyContractRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[74] + mi := &file_ateapi_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5589,7 +5864,7 @@ func (x *GetEgressPolicyContractRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEgressPolicyContractRequest.ProtoReflect.Descriptor instead. func (*GetEgressPolicyContractRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{74} + return file_ateapi_proto_rawDescGZIP(), []int{76} } // EgressPolicyContract names the egress policy contract a server speaks. @@ -5605,7 +5880,7 @@ type EgressPolicyContract struct { func (x *EgressPolicyContract) Reset() { *x = EgressPolicyContract{} - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5617,7 +5892,7 @@ func (x *EgressPolicyContract) String() string { func (*EgressPolicyContract) ProtoMessage() {} func (x *EgressPolicyContract) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[75] + mi := &file_ateapi_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5630,7 +5905,7 @@ func (x *EgressPolicyContract) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressPolicyContract.ProtoReflect.Descriptor instead. func (*EgressPolicyContract) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{75} + return file_ateapi_proto_rawDescGZIP(), []int{77} } func (x *EgressPolicyContract) GetVersion() string { @@ -5651,7 +5926,7 @@ type GetTagRequest struct { func (x *GetTagRequest) Reset() { *x = GetTagRequest{} - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5663,7 +5938,7 @@ func (x *GetTagRequest) String() string { func (*GetTagRequest) ProtoMessage() {} func (x *GetTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[76] + mi := &file_ateapi_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5676,7 +5951,7 @@ func (x *GetTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTagRequest.ProtoReflect.Descriptor instead. func (*GetTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{76} + return file_ateapi_proto_rawDescGZIP(), []int{78} } func (x *GetTagRequest) GetTag() *ObjectRef { @@ -5721,7 +5996,7 @@ type MintActorJWTRequest struct { func (x *MintActorJWTRequest) Reset() { *x = MintActorJWTRequest{} - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5733,7 +6008,7 @@ func (x *MintActorJWTRequest) String() string { func (*MintActorJWTRequest) ProtoMessage() {} func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[77] + mi := &file_ateapi_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5746,7 +6021,7 @@ func (x *MintActorJWTRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTRequest.ProtoReflect.Descriptor instead. func (*MintActorJWTRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{77} + return file_ateapi_proto_rawDescGZIP(), []int{79} } func (x *MintActorJWTRequest) GetActor() *ObjectRef { @@ -5803,7 +6078,7 @@ type MintActorJWTResponse struct { func (x *MintActorJWTResponse) Reset() { *x = MintActorJWTResponse{} - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5815,7 +6090,7 @@ func (x *MintActorJWTResponse) String() string { func (*MintActorJWTResponse) ProtoMessage() {} func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[78] + mi := &file_ateapi_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5828,7 +6103,7 @@ func (x *MintActorJWTResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorJWTResponse.ProtoReflect.Descriptor instead. func (*MintActorJWTResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{78} + return file_ateapi_proto_rawDescGZIP(), []int{80} } func (x *MintActorJWTResponse) GetActorJwt() string { @@ -5873,7 +6148,7 @@ type MintActorCertificateRequest struct { func (x *MintActorCertificateRequest) Reset() { *x = MintActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5885,7 +6160,7 @@ func (x *MintActorCertificateRequest) String() string { func (*MintActorCertificateRequest) ProtoMessage() {} func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[79] + mi := &file_ateapi_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5898,7 +6173,7 @@ func (x *MintActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{79} + return file_ateapi_proto_rawDescGZIP(), []int{81} } func (x *MintActorCertificateRequest) GetActor() *ObjectRef { @@ -5935,7 +6210,7 @@ type MintActorCertificateResponse struct { func (x *MintActorCertificateResponse) Reset() { *x = MintActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5947,7 +6222,7 @@ func (x *MintActorCertificateResponse) String() string { func (*MintActorCertificateResponse) ProtoMessage() {} func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[80] + mi := &file_ateapi_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5960,7 +6235,7 @@ func (x *MintActorCertificateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MintActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{80} + return file_ateapi_proto_rawDescGZIP(), []int{82} } func (x *MintActorCertificateResponse) GetActorCertificates() [][]byte { @@ -5996,7 +6271,7 @@ type ListTagsRequest struct { func (x *ListTagsRequest) Reset() { *x = ListTagsRequest{} - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6008,7 +6283,7 @@ func (x *ListTagsRequest) String() string { func (*ListTagsRequest) ProtoMessage() {} func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[81] + mi := &file_ateapi_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6021,7 +6296,7 @@ func (x *ListTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead. func (*ListTagsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{81} + return file_ateapi_proto_rawDescGZIP(), []int{83} } func (x *ListTagsRequest) GetAtespace() string { @@ -6055,7 +6330,7 @@ type ListTagsResponse struct { func (x *ListTagsResponse) Reset() { *x = ListTagsResponse{} - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6067,7 +6342,7 @@ func (x *ListTagsResponse) String() string { func (*ListTagsResponse) ProtoMessage() {} func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[82] + mi := &file_ateapi_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6080,7 +6355,7 @@ func (x *ListTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead. func (*ListTagsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{82} + return file_ateapi_proto_rawDescGZIP(), []int{84} } func (x *ListTagsResponse) GetTags() []*Tag { @@ -6125,7 +6400,7 @@ type CreateTagRequest struct { func (x *CreateTagRequest) Reset() { *x = CreateTagRequest{} - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6137,7 +6412,7 @@ func (x *CreateTagRequest) String() string { func (*CreateTagRequest) ProtoMessage() {} func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[83] + mi := &file_ateapi_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6150,7 +6425,7 @@ func (x *CreateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead. func (*CreateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{83} + return file_ateapi_proto_rawDescGZIP(), []int{85} } func (x *CreateTagRequest) GetTag() *Tag { @@ -6180,7 +6455,7 @@ type UpdateTagRequest struct { func (x *UpdateTagRequest) Reset() { *x = UpdateTagRequest{} - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6192,7 +6467,7 @@ func (x *UpdateTagRequest) String() string { func (*UpdateTagRequest) ProtoMessage() {} func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[84] + mi := &file_ateapi_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6205,7 +6480,7 @@ func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateTagRequest.ProtoReflect.Descriptor instead. func (*UpdateTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{84} + return file_ateapi_proto_rawDescGZIP(), []int{86} } func (x *UpdateTagRequest) GetTag() *Tag { @@ -6230,7 +6505,7 @@ type DeleteTagRequest struct { func (x *DeleteTagRequest) Reset() { *x = DeleteTagRequest{} - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6242,7 +6517,7 @@ func (x *DeleteTagRequest) String() string { func (*DeleteTagRequest) ProtoMessage() {} func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[85] + mi := &file_ateapi_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6255,7 +6530,7 @@ func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteTagRequest.ProtoReflect.Descriptor instead. func (*DeleteTagRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{85} + return file_ateapi_proto_rawDescGZIP(), []int{87} } func (x *DeleteTagRequest) GetTag() *ObjectRef { @@ -6294,7 +6569,7 @@ type DeleteOptions struct { func (x *DeleteOptions) Reset() { *x = DeleteOptions{} - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6306,7 +6581,7 @@ func (x *DeleteOptions) String() string { func (*DeleteOptions) ProtoMessage() {} func (x *DeleteOptions) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[86] + mi := &file_ateapi_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6319,7 +6594,7 @@ func (x *DeleteOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteOptions.ProtoReflect.Descriptor instead. func (*DeleteOptions) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{86} + return file_ateapi_proto_rawDescGZIP(), []int{88} } func (x *DeleteOptions) GetVersion() int64 { @@ -6363,7 +6638,7 @@ type ListWorkerActorAssignmentsRequest struct { func (x *ListWorkerActorAssignmentsRequest) Reset() { *x = ListWorkerActorAssignmentsRequest{} - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6375,7 +6650,7 @@ func (x *ListWorkerActorAssignmentsRequest) String() string { func (*ListWorkerActorAssignmentsRequest) ProtoMessage() {} func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[87] + mi := &file_ateapi_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6388,7 +6663,7 @@ func (x *ListWorkerActorAssignmentsRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsRequest.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{87} + return file_ateapi_proto_rawDescGZIP(), []int{89} } func (x *ListWorkerActorAssignmentsRequest) GetWorker() *ObjectRef { @@ -6425,7 +6700,7 @@ type ListWorkerActorAssignmentsResponse struct { func (x *ListWorkerActorAssignmentsResponse) Reset() { *x = ListWorkerActorAssignmentsResponse{} - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6437,7 +6712,7 @@ func (x *ListWorkerActorAssignmentsResponse) String() string { func (*ListWorkerActorAssignmentsResponse) ProtoMessage() {} func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[88] + mi := &file_ateapi_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6450,7 +6725,7 @@ func (x *ListWorkerActorAssignmentsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListWorkerActorAssignmentsResponse.ProtoReflect.Descriptor instead. func (*ListWorkerActorAssignmentsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{88} + return file_ateapi_proto_rawDescGZIP(), []int{90} } func (x *ListWorkerActorAssignmentsResponse) GetActorAssignments() []*ActorAssignment { @@ -6488,7 +6763,7 @@ type ListWorkersRequest struct { func (x *ListWorkersRequest) Reset() { *x = ListWorkersRequest{} - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6500,7 +6775,7 @@ func (x *ListWorkersRequest) String() string { func (*ListWorkersRequest) ProtoMessage() {} func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[89] + mi := &file_ateapi_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6513,7 +6788,7 @@ func (x *ListWorkersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersRequest.ProtoReflect.Descriptor instead. func (*ListWorkersRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{89} + return file_ateapi_proto_rawDescGZIP(), []int{91} } func (x *ListWorkersRequest) GetPageSize() int32 { @@ -6542,7 +6817,7 @@ type ListWorkersResponse struct { func (x *ListWorkersResponse) Reset() { *x = ListWorkersResponse{} - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6554,7 +6829,7 @@ func (x *ListWorkersResponse) String() string { func (*ListWorkersResponse) ProtoMessage() {} func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[90] + mi := &file_ateapi_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6567,7 +6842,7 @@ func (x *ListWorkersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListWorkersResponse.ProtoReflect.Descriptor instead. func (*ListWorkersResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{90} + return file_ateapi_proto_rawDescGZIP(), []int{92} } func (x *ListWorkersResponse) GetWorkers() []*Worker { @@ -6597,7 +6872,7 @@ type GetWorkerRequest struct { func (x *GetWorkerRequest) Reset() { *x = GetWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6609,7 +6884,7 @@ func (x *GetWorkerRequest) String() string { func (*GetWorkerRequest) ProtoMessage() {} func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[91] + mi := &file_ateapi_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6622,7 +6897,7 @@ func (x *GetWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetWorkerRequest.ProtoReflect.Descriptor instead. func (*GetWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{91} + return file_ateapi_proto_rawDescGZIP(), []int{93} } func (x *GetWorkerRequest) GetWorker() *ObjectRef { @@ -6644,7 +6919,7 @@ type CreateWorkerRequest struct { func (x *CreateWorkerRequest) Reset() { *x = CreateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6656,7 +6931,7 @@ func (x *CreateWorkerRequest) String() string { func (*CreateWorkerRequest) ProtoMessage() {} func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[92] + mi := &file_ateapi_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6669,7 +6944,7 @@ func (x *CreateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateWorkerRequest.ProtoReflect.Descriptor instead. func (*CreateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{92} + return file_ateapi_proto_rawDescGZIP(), []int{94} } func (x *CreateWorkerRequest) GetWorker() *Worker { @@ -6704,7 +6979,7 @@ type UpdateWorkerRequest struct { func (x *UpdateWorkerRequest) Reset() { *x = UpdateWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6716,7 +6991,7 @@ func (x *UpdateWorkerRequest) String() string { func (*UpdateWorkerRequest) ProtoMessage() {} func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[93] + mi := &file_ateapi_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6729,7 +7004,7 @@ func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{93} + return file_ateapi_proto_rawDescGZIP(), []int{95} } func (x *UpdateWorkerRequest) GetWorker() *Worker { @@ -6756,7 +7031,7 @@ type DeleteWorkerRequest struct { func (x *DeleteWorkerRequest) Reset() { *x = DeleteWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6768,7 +7043,7 @@ func (x *DeleteWorkerRequest) String() string { func (*DeleteWorkerRequest) ProtoMessage() {} func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[94] + mi := &file_ateapi_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6781,7 +7056,7 @@ func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{94} + return file_ateapi_proto_rawDescGZIP(), []int{96} } func (x *DeleteWorkerRequest) GetWorker() *ObjectRef { @@ -6811,7 +7086,7 @@ type DrainWorkerRequest struct { func (x *DrainWorkerRequest) Reset() { *x = DrainWorkerRequest{} - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6823,7 +7098,7 @@ func (x *DrainWorkerRequest) String() string { func (*DrainWorkerRequest) ProtoMessage() {} func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[95] + mi := &file_ateapi_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6836,7 +7111,7 @@ func (x *DrainWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DrainWorkerRequest.ProtoReflect.Descriptor instead. func (*DrainWorkerRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{95} + return file_ateapi_proto_rawDescGZIP(), []int{97} } func (x *DrainWorkerRequest) GetWorker() *ObjectRef { @@ -6874,7 +7149,7 @@ type ListActorsRequest struct { func (x *ListActorsRequest) Reset() { *x = ListActorsRequest{} - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6886,7 +7161,7 @@ func (x *ListActorsRequest) String() string { func (*ListActorsRequest) ProtoMessage() {} func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[96] + mi := &file_ateapi_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6899,7 +7174,7 @@ func (x *ListActorsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsRequest.ProtoReflect.Descriptor instead. func (*ListActorsRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{96} + return file_ateapi_proto_rawDescGZIP(), []int{98} } func (x *ListActorsRequest) GetAtespace() string { @@ -6935,7 +7210,7 @@ type ListActorsResponse struct { func (x *ListActorsResponse) Reset() { *x = ListActorsResponse{} - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6947,7 +7222,7 @@ func (x *ListActorsResponse) String() string { func (*ListActorsResponse) ProtoMessage() {} func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[97] + mi := &file_ateapi_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6960,7 +7235,7 @@ func (x *ListActorsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActorsResponse.ProtoReflect.Descriptor instead. func (*ListActorsResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{97} + return file_ateapi_proto_rawDescGZIP(), []int{99} } func (x *ListActorsResponse) GetActors() []*Actor { @@ -7068,7 +7343,7 @@ type Worker struct { func (x *Worker) Reset() { *x = Worker{} - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7080,7 +7355,7 @@ func (x *Worker) String() string { func (*Worker) ProtoMessage() {} func (x *Worker) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[98] + mi := &file_ateapi_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7093,7 +7368,7 @@ func (x *Worker) ProtoReflect() protoreflect.Message { // Deprecated: Use Worker.ProtoReflect.Descriptor instead. func (*Worker) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{98} + return file_ateapi_proto_rawDescGZIP(), []int{100} } func (x *Worker) GetMetadata() *ResourceMetadata { @@ -7209,7 +7484,7 @@ type WorkerStatus struct { func (x *WorkerStatus) Reset() { *x = WorkerStatus{} - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7221,7 +7496,7 @@ func (x *WorkerStatus) String() string { func (*WorkerStatus) ProtoMessage() {} func (x *WorkerStatus) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[99] + mi := &file_ateapi_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7234,7 +7509,7 @@ func (x *WorkerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerStatus.ProtoReflect.Descriptor instead. func (*WorkerStatus) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{99} + return file_ateapi_proto_rawDescGZIP(), []int{101} } func (x *WorkerStatus) GetState() WorkerState { @@ -7287,7 +7562,7 @@ type WorkerResources struct { func (x *WorkerResources) Reset() { *x = WorkerResources{} - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7299,7 +7574,7 @@ func (x *WorkerResources) String() string { func (*WorkerResources) ProtoMessage() {} func (x *WorkerResources) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[100] + mi := &file_ateapi_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7312,7 +7587,7 @@ func (x *WorkerResources) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerResources.ProtoReflect.Descriptor instead. func (*WorkerResources) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{100} + return file_ateapi_proto_rawDescGZIP(), []int{102} } func (x *WorkerResources) GetResources() *Resources { @@ -7371,7 +7646,7 @@ type ActorAssignment struct { func (x *ActorAssignment) Reset() { *x = ActorAssignment{} - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7383,7 +7658,7 @@ func (x *ActorAssignment) String() string { func (*ActorAssignment) ProtoMessage() {} func (x *ActorAssignment) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[101] + mi := &file_ateapi_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7396,7 +7671,7 @@ func (x *ActorAssignment) ProtoReflect() protoreflect.Message { // Deprecated: Use ActorAssignment.ProtoReflect.Descriptor instead. func (*ActorAssignment) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{101} + return file_ateapi_proto_rawDescGZIP(), []int{103} } func (x *ActorAssignment) GetMetadata() *ResourceMetadata { @@ -7461,7 +7736,7 @@ type SetWorkerCapacityRequest struct { func (x *SetWorkerCapacityRequest) Reset() { *x = SetWorkerCapacityRequest{} - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7473,7 +7748,7 @@ func (x *SetWorkerCapacityRequest) String() string { func (*SetWorkerCapacityRequest) ProtoMessage() {} func (x *SetWorkerCapacityRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[102] + mi := &file_ateapi_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7486,7 +7761,7 @@ func (x *SetWorkerCapacityRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerCapacityRequest.ProtoReflect.Descriptor instead. func (*SetWorkerCapacityRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{102} + return file_ateapi_proto_rawDescGZIP(), []int{104} } func (x *SetWorkerCapacityRequest) GetWorker() *ObjectRef { @@ -7513,7 +7788,7 @@ type SetWorkerCapacityResponse struct { func (x *SetWorkerCapacityResponse) Reset() { *x = SetWorkerCapacityResponse{} - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7525,7 +7800,7 @@ func (x *SetWorkerCapacityResponse) String() string { func (*SetWorkerCapacityResponse) ProtoMessage() {} func (x *SetWorkerCapacityResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[103] + mi := &file_ateapi_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7538,7 +7813,7 @@ func (x *SetWorkerCapacityResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerCapacityResponse.ProtoReflect.Descriptor instead. func (*SetWorkerCapacityResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{103} + return file_ateapi_proto_rawDescGZIP(), []int{105} } func (x *SetWorkerCapacityResponse) GetWorker() *Worker { @@ -7577,7 +7852,7 @@ type MintAteomActorCertificateRequest struct { func (x *MintAteomActorCertificateRequest) Reset() { *x = MintAteomActorCertificateRequest{} - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7589,7 +7864,7 @@ func (x *MintAteomActorCertificateRequest) String() string { func (*MintAteomActorCertificateRequest) ProtoMessage() {} func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[104] + mi := &file_ateapi_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7602,7 +7877,7 @@ func (x *MintAteomActorCertificateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MintAteomActorCertificateRequest.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{104} + return file_ateapi_proto_rawDescGZIP(), []int{106} } func (x *MintAteomActorCertificateRequest) GetActor() *ObjectRef { @@ -7639,7 +7914,7 @@ type MintAteomActorCertificateResponse struct { func (x *MintAteomActorCertificateResponse) Reset() { *x = MintAteomActorCertificateResponse{} - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7651,7 +7926,7 @@ func (x *MintAteomActorCertificateResponse) String() string { func (*MintAteomActorCertificateResponse) ProtoMessage() {} func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[105] + mi := &file_ateapi_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7664,7 +7939,7 @@ func (x *MintAteomActorCertificateResponse) ProtoReflect() protoreflect.Message // Deprecated: Use MintAteomActorCertificateResponse.ProtoReflect.Descriptor instead. func (*MintAteomActorCertificateResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{105} + return file_ateapi_proto_rawDescGZIP(), []int{107} } func (x *MintAteomActorCertificateResponse) GetActorCertificates() [][]byte { @@ -7702,7 +7977,7 @@ type RequestActorSuspendRequest struct { func (x *RequestActorSuspendRequest) Reset() { *x = RequestActorSuspendRequest{} - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7714,7 +7989,7 @@ func (x *RequestActorSuspendRequest) String() string { func (*RequestActorSuspendRequest) ProtoMessage() {} func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[106] + mi := &file_ateapi_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7727,7 +8002,7 @@ func (x *RequestActorSuspendRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendRequest.ProtoReflect.Descriptor instead. func (*RequestActorSuspendRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{106} + return file_ateapi_proto_rawDescGZIP(), []int{108} } func (x *RequestActorSuspendRequest) GetWorker() *ObjectRef { @@ -7761,7 +8036,7 @@ type RequestActorSuspendResponse struct { func (x *RequestActorSuspendResponse) Reset() { *x = RequestActorSuspendResponse{} - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7773,7 +8048,7 @@ func (x *RequestActorSuspendResponse) String() string { func (*RequestActorSuspendResponse) ProtoMessage() {} func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[107] + mi := &file_ateapi_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7786,7 +8061,7 @@ func (x *RequestActorSuspendResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RequestActorSuspendResponse.ProtoReflect.Descriptor instead. func (*RequestActorSuspendResponse) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{107} + return file_ateapi_proto_rawDescGZIP(), []int{109} } func (x *RequestActorSuspendResponse) GetActor() *Actor { @@ -7821,7 +8096,7 @@ type AccessPolicy struct { func (x *AccessPolicy) Reset() { *x = AccessPolicy{} - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7833,7 +8108,7 @@ func (x *AccessPolicy) String() string { func (*AccessPolicy) ProtoMessage() {} func (x *AccessPolicy) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[108] + mi := &file_ateapi_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7846,7 +8121,7 @@ func (x *AccessPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use AccessPolicy.ProtoReflect.Descriptor instead. func (*AccessPolicy) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{108} + return file_ateapi_proto_rawDescGZIP(), []int{110} } func (x *AccessPolicy) GetMetadata() *ResourceMetadata { @@ -7888,7 +8163,7 @@ type Binding struct { func (x *Binding) Reset() { *x = Binding{} - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7900,7 +8175,7 @@ func (x *Binding) String() string { func (*Binding) ProtoMessage() {} func (x *Binding) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[109] + mi := &file_ateapi_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7913,7 +8188,7 @@ func (x *Binding) ProtoReflect() protoreflect.Message { // Deprecated: Use Binding.ProtoReflect.Descriptor instead. func (*Binding) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{109} + return file_ateapi_proto_rawDescGZIP(), []int{111} } func (x *Binding) GetRole() string { @@ -7939,7 +8214,7 @@ type GetGlobalAccessPolicyRequest struct { func (x *GetGlobalAccessPolicyRequest) Reset() { *x = GetGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7951,7 +8226,7 @@ func (x *GetGlobalAccessPolicyRequest) String() string { func (*GetGlobalAccessPolicyRequest) ProtoMessage() {} func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[110] + mi := &file_ateapi_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7964,7 +8239,7 @@ func (x *GetGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{110} + return file_ateapi_proto_rawDescGZIP(), []int{112} } // CreateGlobalAccessPolicyRequest creates the deployment-wide global access policy singleton. @@ -7981,7 +8256,7 @@ type CreateGlobalAccessPolicyRequest struct { func (x *CreateGlobalAccessPolicyRequest) Reset() { *x = CreateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7993,7 +8268,7 @@ func (x *CreateGlobalAccessPolicyRequest) String() string { func (*CreateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[111] + mi := &file_ateapi_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8006,7 +8281,7 @@ func (x *CreateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{111} + return file_ateapi_proto_rawDescGZIP(), []int{113} } func (x *CreateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8030,7 +8305,7 @@ type UpdateGlobalAccessPolicyRequest struct { func (x *UpdateGlobalAccessPolicyRequest) Reset() { *x = UpdateGlobalAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8042,7 +8317,7 @@ func (x *UpdateGlobalAccessPolicyRequest) String() string { func (*UpdateGlobalAccessPolicyRequest) ProtoMessage() {} func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[112] + mi := &file_ateapi_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8055,7 +8330,7 @@ func (x *UpdateGlobalAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateGlobalAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateGlobalAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{112} + return file_ateapi_proto_rawDescGZIP(), []int{114} } func (x *UpdateGlobalAccessPolicyRequest) GetAccessPolicy() *AccessPolicy { @@ -8079,7 +8354,7 @@ type GetAtespaceAccessPolicyRequest struct { func (x *GetAtespaceAccessPolicyRequest) Reset() { *x = GetAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8091,7 +8366,7 @@ func (x *GetAtespaceAccessPolicyRequest) String() string { func (*GetAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[113] + mi := &file_ateapi_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8104,7 +8379,7 @@ func (x *GetAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*GetAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{113} + return file_ateapi_proto_rawDescGZIP(), []int{115} } func (x *GetAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8133,7 +8408,7 @@ type CreateAtespaceAccessPolicyRequest struct { func (x *CreateAtespaceAccessPolicyRequest) Reset() { *x = CreateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8145,7 +8420,7 @@ func (x *CreateAtespaceAccessPolicyRequest) String() string { func (*CreateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[114] + mi := &file_ateapi_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8158,7 +8433,7 @@ func (x *CreateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use CreateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*CreateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{114} + return file_ateapi_proto_rawDescGZIP(), []int{116} } func (x *CreateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8194,7 +8469,7 @@ type UpdateAtespaceAccessPolicyRequest struct { func (x *UpdateAtespaceAccessPolicyRequest) Reset() { *x = UpdateAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8206,7 +8481,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) String() string { func (*UpdateAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[115] + mi := &file_ateapi_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8219,7 +8494,7 @@ func (x *UpdateAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpdateAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*UpdateAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{115} + return file_ateapi_proto_rawDescGZIP(), []int{117} } func (x *UpdateAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8254,7 +8529,7 @@ type DeleteAtespaceAccessPolicyRequest struct { func (x *DeleteAtespaceAccessPolicyRequest) Reset() { *x = DeleteAtespaceAccessPolicyRequest{} - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8266,7 +8541,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) String() string { func (*DeleteAtespaceAccessPolicyRequest) ProtoMessage() {} func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message { - mi := &file_ateapi_proto_msgTypes[116] + mi := &file_ateapi_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8279,7 +8554,7 @@ func (x *DeleteAtespaceAccessPolicyRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteAtespaceAccessPolicyRequest.ProtoReflect.Descriptor instead. func (*DeleteAtespaceAccessPolicyRequest) Descriptor() ([]byte, []int) { - return file_ateapi_proto_rawDescGZIP(), []int{116} + return file_ateapi_proto_rawDescGZIP(), []int{118} } func (x *DeleteAtespaceAccessPolicyRequest) GetAtespace() *ObjectRef { @@ -8340,14 +8615,22 @@ const file_ateapi_proto_rawDesc = "" + "\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" + "\x0eSTATUS_PENDING\x10\x01\x12\x12\n" + "\x0eSTATUS_CREATED\x10\x02\x12\x13\n" + - "\x0fSTATUS_DELETING\x10\x03\"\x91\x02\n" + + "\x0fSTATUS_DELETING\x10\x03\"\xd5\x02\n" + "\x05Actor\x124\n" + "\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x128\n" + "\x0eactor_template\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\ractorTemplate\x129\n" + "\x0fworker_selector\x18\x05 \x01(\v2\x10.ateapi.SelectorR\x0eworkerSelector\x120\n" + "\n" + "source_tag\x18\x06 \x01(\v2\x11.ateapi.ObjectRefR\tsourceTag\x12+\n" + - "\x06status\x18\a \x01(\v2\x13.ateapi.ActorStatusR\x06status\"n\n" + + "\x06status\x18\a \x01(\v2\x13.ateapi.ActorStatusR\x06status\x12B\n" + + "\x10existing_volumes\x18\x91N \x03(\v2\x16.ateapi.ExistingVolumeR\x0fexistingVolumes\"\xb7\x01\n" + + "\x0eExistingVolume\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06driver\x18\x02 \x01(\tR\x06driver\x12#\n" + + "\rvolume_handle\x18\x03 \x01(\tR\fvolumeHandle\x129\n" + + "\vaccess_mode\x18\x04 \x01(\x0e2\x18.ateapi.VolumeAccessModeR\n" + + "accessMode\x12\x19\n" + + "\bsub_path\x18\x05 \x01(\tR\asubPath\"n\n" + "\fEgressPolicy\x124\n" + "\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x12(\n" + "\x05rules\x18\x02 \x03(\v2\x12.ateapi.EgressRuleR\x05rules\"@\n" + @@ -8479,7 +8762,7 @@ const file_ateapi_proto_rawDesc = "" + "\x0ftimeout_seconds\x18\x02 \x01(\x05R\x0etimeoutSeconds\"7\n" + "\rHTTPGetAction\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12\x12\n" + - "\x04port\x18\x02 \x01(\x05R\x04port\"\xa9\x02\n" + + "\x04port\x18\x02 \x01(\x05R\x04port\"\xf1\x02\n" + "\x06Volume\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12?\n" + "\vdurable_dir\x18\x02 \x01(\v2\x1e.ateapi.DurableDirVolumeSourceR\n" + @@ -8487,7 +8770,9 @@ const file_ateapi_proto_rawDesc = "" + "\x18external_volume_template\x18\x03 \x01(\v2\x1e.ateapi.ExternalVolumeTemplateR\x16externalVolumeTemplate\x12?\n" + "\vsystem_info\x18\x05 \x01(\v2\x1e.ateapi.SystemInfoVolumeSourceR\n" + "systemInfo\x12/\n" + - "\x05image\x18\x06 \x01(\v2\x19.ateapi.ImageVolumeSourceR\x05image\"1\n" + + "\x05image\x18\x06 \x01(\v2\x19.ateapi.ImageVolumeSourceR\x05image\x12F\n" + + "\x0fexisting_volume\x18\x91N \x01(\v2\x1c.ateapi.ExistingVolumeSourceR\x0eexistingVolume\"\x16\n" + + "\x14ExistingVolumeSource\"1\n" + "\x11ImageVolumeSource\x12\x1c\n" + "\treference\x18\x01 \x01(\tR\treference\"\x18\n" + "\x16DurableDirVolumeSource\"b\n" + @@ -8506,11 +8791,13 @@ const file_ateapi_proto_rawDesc = "" + "\x04path\x18\x02 \x01(\tR\x04path\"M\n" + "\x15TrustBundleDataSource\x12\x14\n" + "\x05names\x18\x03 \x03(\tR\x05names\x12\x12\n" + - "\x04path\x18\x02 \x01(\tR\x04pathJ\x04\b\x01\x10\x02R\x04name\"@\n" + + "\x04path\x18\x02 \x01(\tR\x04pathJ\x04\b\x01\x10\x02R\x04name\"z\n" + "\vVolumeMount\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n" + "\n" + - "mount_path\x18\x02 \x01(\tR\tmountPath\"E\n" + + "mount_path\x18\x02 \x01(\tR\tmountPath\x12\x1a\n" + + "\bsub_path\x18\x91N \x01(\tR\asubPath\x12\x1c\n" + + "\tread_only\x18\x92N \x01(\bR\breadOnly\"E\n" + "\x15CreateAtespaceRequest\x12,\n" + "\batespace\x18\x01 \x01(\v2\x10.ateapi.AtespaceR\batespace\"C\n" + "\x12GetAtespaceRequest\x12-\n" + @@ -8736,7 +9023,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*\x92\x02\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*\x92\x02\n" + "\n" + "ActorState\x12\x1b\n" + "\x17ACTOR_STATE_UNSPECIFIED\x10\x00\x12\x18\n" + @@ -8825,391 +9117,397 @@ func file_ateapi_proto_rawDescGZIP() []byte { return file_ateapi_proto_rawDescData } -var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 120) +var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 8) +var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 122) 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 - (ActorMetadataField)(0), // 4: ateapi.ActorMetadataField - (WorkerState)(0), // 5: ateapi.WorkerState - (ExternalVolume_Status)(0), // 6: ateapi.ExternalVolume.Status - (*ExternalSnapshot)(nil), // 7: ateapi.ExternalSnapshot - (*LocalSnapshot)(nil), // 8: ateapi.LocalSnapshot - (*Selector)(nil), // 9: ateapi.Selector - (*ResourceMetadata)(nil), // 10: ateapi.ResourceMetadata - (*ExternalVolume)(nil), // 11: ateapi.ExternalVolume - (*Actor)(nil), // 12: ateapi.Actor - (*EgressPolicy)(nil), // 13: ateapi.EgressPolicy - (*EgressPolicyTemplate)(nil), // 14: ateapi.EgressPolicyTemplate - (*EgressRule)(nil), // 15: ateapi.EgressRule - (*HTTPRule)(nil), // 16: ateapi.HTTPRule - (*HTTPSRule)(nil), // 17: ateapi.HTTPSRule - (*TLSPassthroughRule)(nil), // 18: ateapi.TLSPassthroughRule - (*Ports)(nil), // 19: ateapi.Ports - (*AllPorts)(nil), // 20: ateapi.AllPorts - (*HttpRuleEffects)(nil), // 21: ateapi.HttpRuleEffects - (*CredentialHeader)(nil), // 22: ateapi.CredentialHeader - (*ActorJWTSource)(nil), // 23: ateapi.ActorJWTSource - (*ActorStatus)(nil), // 24: ateapi.ActorStatus - (*ActorCrash)(nil), // 25: ateapi.ActorCrash - (*WorkerAssignment)(nil), // 26: ateapi.WorkerAssignment - (*TagStatus)(nil), // 27: ateapi.TagStatus - (*Tag)(nil), // 28: ateapi.Tag - (*Atespace)(nil), // 29: ateapi.Atespace - (*ObjectRef)(nil), // 30: ateapi.ObjectRef - (*ActorTemplate)(nil), // 31: ateapi.ActorTemplate - (*Resources)(nil), // 32: ateapi.Resources - (*Limits)(nil), // 33: ateapi.Limits - (*GoldenSnapshotStatus)(nil), // 34: ateapi.GoldenSnapshotStatus - (*ActorTemplateStatus)(nil), // 35: ateapi.ActorTemplateStatus - (*SandboxConfig)(nil), // 36: ateapi.SandboxConfig - (*SnapshotConfig)(nil), // 37: ateapi.SnapshotConfig - (*Container)(nil), // 38: ateapi.Container - (*SecurityContext)(nil), // 39: ateapi.SecurityContext - (*Capabilities)(nil), // 40: ateapi.Capabilities - (*EnvVar)(nil), // 41: ateapi.EnvVar - (*ContainerWakeupProbe)(nil), // 42: ateapi.ContainerWakeupProbe - (*HTTPGetAction)(nil), // 43: ateapi.HTTPGetAction - (*Volume)(nil), // 44: ateapi.Volume - (*ImageVolumeSource)(nil), // 45: ateapi.ImageVolumeSource - (*DurableDirVolumeSource)(nil), // 46: ateapi.DurableDirVolumeSource - (*ExternalVolumeTemplate)(nil), // 47: ateapi.ExternalVolumeTemplate - (*SystemInfoVolumeSource)(nil), // 48: ateapi.SystemInfoVolumeSource - (*SystemInfoDataSource)(nil), // 49: ateapi.SystemInfoDataSource - (*ActorMetadataDataSource)(nil), // 50: ateapi.ActorMetadataDataSource - (*ActorMetadataItem)(nil), // 51: ateapi.ActorMetadataItem - (*TrustBundleDataSource)(nil), // 52: ateapi.TrustBundleDataSource - (*VolumeMount)(nil), // 53: ateapi.VolumeMount - (*CreateAtespaceRequest)(nil), // 54: ateapi.CreateAtespaceRequest - (*GetAtespaceRequest)(nil), // 55: ateapi.GetAtespaceRequest - (*ListAtespacesRequest)(nil), // 56: ateapi.ListAtespacesRequest - (*ListAtespacesResponse)(nil), // 57: ateapi.ListAtespacesResponse - (*DeleteAtespaceRequest)(nil), // 58: ateapi.DeleteAtespaceRequest - (*CreateActorTemplateRequest)(nil), // 59: ateapi.CreateActorTemplateRequest - (*GetActorTemplateRequest)(nil), // 60: ateapi.GetActorTemplateRequest - (*ListActorTemplatesRequest)(nil), // 61: ateapi.ListActorTemplatesRequest - (*ListActorTemplatesResponse)(nil), // 62: ateapi.ListActorTemplatesResponse - (*DeleteActorTemplateRequest)(nil), // 63: ateapi.DeleteActorTemplateRequest - (*GetActorRequest)(nil), // 64: ateapi.GetActorRequest - (*CreateActorRequest)(nil), // 65: ateapi.CreateActorRequest - (*UpdateActorRequest)(nil), // 66: ateapi.UpdateActorRequest - (*FencingToken)(nil), // 67: ateapi.FencingToken - (*SuspendActorRequest)(nil), // 68: ateapi.SuspendActorRequest - (*SuspendActorResponse)(nil), // 69: ateapi.SuspendActorResponse - (*PauseActorRequest)(nil), // 70: ateapi.PauseActorRequest - (*PauseActorResponse)(nil), // 71: ateapi.PauseActorResponse - (*ResumeActorRequest)(nil), // 72: ateapi.ResumeActorRequest - (*ResumeActorResponse)(nil), // 73: ateapi.ResumeActorResponse - (*RevertActorRequest)(nil), // 74: ateapi.RevertActorRequest - (*RevertActorResponse)(nil), // 75: ateapi.RevertActorResponse - (*DeleteActorRequest)(nil), // 76: ateapi.DeleteActorRequest - (*GetActorEgressPolicyRequest)(nil), // 77: ateapi.GetActorEgressPolicyRequest - (*CreateActorEgressPolicyRequest)(nil), // 78: ateapi.CreateActorEgressPolicyRequest - (*UpdateActorEgressPolicyRequest)(nil), // 79: ateapi.UpdateActorEgressPolicyRequest - (*DeleteActorEgressPolicyRequest)(nil), // 80: ateapi.DeleteActorEgressPolicyRequest - (*GetEgressPolicyContractRequest)(nil), // 81: ateapi.GetEgressPolicyContractRequest - (*EgressPolicyContract)(nil), // 82: ateapi.EgressPolicyContract - (*GetTagRequest)(nil), // 83: ateapi.GetTagRequest - (*MintActorJWTRequest)(nil), // 84: ateapi.MintActorJWTRequest - (*MintActorJWTResponse)(nil), // 85: ateapi.MintActorJWTResponse - (*MintActorCertificateRequest)(nil), // 86: ateapi.MintActorCertificateRequest - (*MintActorCertificateResponse)(nil), // 87: ateapi.MintActorCertificateResponse - (*ListTagsRequest)(nil), // 88: ateapi.ListTagsRequest - (*ListTagsResponse)(nil), // 89: ateapi.ListTagsResponse - (*CreateTagRequest)(nil), // 90: ateapi.CreateTagRequest - (*UpdateTagRequest)(nil), // 91: ateapi.UpdateTagRequest - (*DeleteTagRequest)(nil), // 92: ateapi.DeleteTagRequest - (*DeleteOptions)(nil), // 93: ateapi.DeleteOptions - (*ListWorkerActorAssignmentsRequest)(nil), // 94: ateapi.ListWorkerActorAssignmentsRequest - (*ListWorkerActorAssignmentsResponse)(nil), // 95: ateapi.ListWorkerActorAssignmentsResponse - (*ListWorkersRequest)(nil), // 96: ateapi.ListWorkersRequest - (*ListWorkersResponse)(nil), // 97: ateapi.ListWorkersResponse - (*GetWorkerRequest)(nil), // 98: ateapi.GetWorkerRequest - (*CreateWorkerRequest)(nil), // 99: ateapi.CreateWorkerRequest - (*UpdateWorkerRequest)(nil), // 100: ateapi.UpdateWorkerRequest - (*DeleteWorkerRequest)(nil), // 101: ateapi.DeleteWorkerRequest - (*DrainWorkerRequest)(nil), // 102: ateapi.DrainWorkerRequest - (*ListActorsRequest)(nil), // 103: ateapi.ListActorsRequest - (*ListActorsResponse)(nil), // 104: ateapi.ListActorsResponse - (*Worker)(nil), // 105: ateapi.Worker - (*WorkerStatus)(nil), // 106: ateapi.WorkerStatus - (*WorkerResources)(nil), // 107: ateapi.WorkerResources - (*ActorAssignment)(nil), // 108: ateapi.ActorAssignment - (*SetWorkerCapacityRequest)(nil), // 109: ateapi.SetWorkerCapacityRequest - (*SetWorkerCapacityResponse)(nil), // 110: ateapi.SetWorkerCapacityResponse - (*MintAteomActorCertificateRequest)(nil), // 111: ateapi.MintAteomActorCertificateRequest - (*MintAteomActorCertificateResponse)(nil), // 112: ateapi.MintAteomActorCertificateResponse - (*RequestActorSuspendRequest)(nil), // 113: ateapi.RequestActorSuspendRequest - (*RequestActorSuspendResponse)(nil), // 114: ateapi.RequestActorSuspendResponse - (*AccessPolicy)(nil), // 115: ateapi.AccessPolicy - (*Binding)(nil), // 116: ateapi.Binding - (*GetGlobalAccessPolicyRequest)(nil), // 117: ateapi.GetGlobalAccessPolicyRequest - (*CreateGlobalAccessPolicyRequest)(nil), // 118: ateapi.CreateGlobalAccessPolicyRequest - (*UpdateGlobalAccessPolicyRequest)(nil), // 119: ateapi.UpdateGlobalAccessPolicyRequest - (*GetAtespaceAccessPolicyRequest)(nil), // 120: ateapi.GetAtespaceAccessPolicyRequest - (*CreateAtespaceAccessPolicyRequest)(nil), // 121: ateapi.CreateAtespaceAccessPolicyRequest - (*UpdateAtespaceAccessPolicyRequest)(nil), // 122: ateapi.UpdateAtespaceAccessPolicyRequest - (*DeleteAtespaceAccessPolicyRequest)(nil), // 123: ateapi.DeleteAtespaceAccessPolicyRequest - nil, // 124: ateapi.Selector.MatchLabelsEntry - nil, // 125: ateapi.ExternalVolume.VolumeContextEntry - nil, // 126: ateapi.Worker.LabelsEntry - (*timestamppb.Timestamp)(nil), // 127: google.protobuf.Timestamp + (VolumeAccessMode)(0), // 2: ateapi.VolumeAccessMode + (ActorState)(0), // 3: ateapi.ActorState + (SandboxClass)(0), // 4: ateapi.SandboxClass + (ActorMetadataField)(0), // 5: ateapi.ActorMetadataField + (WorkerState)(0), // 6: ateapi.WorkerState + (ExternalVolume_Status)(0), // 7: ateapi.ExternalVolume.Status + (*ExternalSnapshot)(nil), // 8: ateapi.ExternalSnapshot + (*LocalSnapshot)(nil), // 9: ateapi.LocalSnapshot + (*Selector)(nil), // 10: ateapi.Selector + (*ResourceMetadata)(nil), // 11: ateapi.ResourceMetadata + (*ExternalVolume)(nil), // 12: ateapi.ExternalVolume + (*Actor)(nil), // 13: ateapi.Actor + (*ExistingVolume)(nil), // 14: ateapi.ExistingVolume + (*EgressPolicy)(nil), // 15: ateapi.EgressPolicy + (*EgressPolicyTemplate)(nil), // 16: ateapi.EgressPolicyTemplate + (*EgressRule)(nil), // 17: ateapi.EgressRule + (*HTTPRule)(nil), // 18: ateapi.HTTPRule + (*HTTPSRule)(nil), // 19: ateapi.HTTPSRule + (*TLSPassthroughRule)(nil), // 20: ateapi.TLSPassthroughRule + (*Ports)(nil), // 21: ateapi.Ports + (*AllPorts)(nil), // 22: ateapi.AllPorts + (*HttpRuleEffects)(nil), // 23: ateapi.HttpRuleEffects + (*CredentialHeader)(nil), // 24: ateapi.CredentialHeader + (*ActorJWTSource)(nil), // 25: ateapi.ActorJWTSource + (*ActorStatus)(nil), // 26: ateapi.ActorStatus + (*ActorCrash)(nil), // 27: ateapi.ActorCrash + (*WorkerAssignment)(nil), // 28: ateapi.WorkerAssignment + (*TagStatus)(nil), // 29: ateapi.TagStatus + (*Tag)(nil), // 30: ateapi.Tag + (*Atespace)(nil), // 31: ateapi.Atespace + (*ObjectRef)(nil), // 32: ateapi.ObjectRef + (*ActorTemplate)(nil), // 33: ateapi.ActorTemplate + (*Resources)(nil), // 34: ateapi.Resources + (*Limits)(nil), // 35: ateapi.Limits + (*GoldenSnapshotStatus)(nil), // 36: ateapi.GoldenSnapshotStatus + (*ActorTemplateStatus)(nil), // 37: ateapi.ActorTemplateStatus + (*SandboxConfig)(nil), // 38: ateapi.SandboxConfig + (*SnapshotConfig)(nil), // 39: ateapi.SnapshotConfig + (*Container)(nil), // 40: ateapi.Container + (*SecurityContext)(nil), // 41: ateapi.SecurityContext + (*Capabilities)(nil), // 42: ateapi.Capabilities + (*EnvVar)(nil), // 43: ateapi.EnvVar + (*ContainerWakeupProbe)(nil), // 44: ateapi.ContainerWakeupProbe + (*HTTPGetAction)(nil), // 45: ateapi.HTTPGetAction + (*Volume)(nil), // 46: ateapi.Volume + (*ExistingVolumeSource)(nil), // 47: ateapi.ExistingVolumeSource + (*ImageVolumeSource)(nil), // 48: ateapi.ImageVolumeSource + (*DurableDirVolumeSource)(nil), // 49: ateapi.DurableDirVolumeSource + (*ExternalVolumeTemplate)(nil), // 50: ateapi.ExternalVolumeTemplate + (*SystemInfoVolumeSource)(nil), // 51: ateapi.SystemInfoVolumeSource + (*SystemInfoDataSource)(nil), // 52: ateapi.SystemInfoDataSource + (*ActorMetadataDataSource)(nil), // 53: ateapi.ActorMetadataDataSource + (*ActorMetadataItem)(nil), // 54: ateapi.ActorMetadataItem + (*TrustBundleDataSource)(nil), // 55: ateapi.TrustBundleDataSource + (*VolumeMount)(nil), // 56: ateapi.VolumeMount + (*CreateAtespaceRequest)(nil), // 57: ateapi.CreateAtespaceRequest + (*GetAtespaceRequest)(nil), // 58: ateapi.GetAtespaceRequest + (*ListAtespacesRequest)(nil), // 59: ateapi.ListAtespacesRequest + (*ListAtespacesResponse)(nil), // 60: ateapi.ListAtespacesResponse + (*DeleteAtespaceRequest)(nil), // 61: ateapi.DeleteAtespaceRequest + (*CreateActorTemplateRequest)(nil), // 62: ateapi.CreateActorTemplateRequest + (*GetActorTemplateRequest)(nil), // 63: ateapi.GetActorTemplateRequest + (*ListActorTemplatesRequest)(nil), // 64: ateapi.ListActorTemplatesRequest + (*ListActorTemplatesResponse)(nil), // 65: ateapi.ListActorTemplatesResponse + (*DeleteActorTemplateRequest)(nil), // 66: ateapi.DeleteActorTemplateRequest + (*GetActorRequest)(nil), // 67: ateapi.GetActorRequest + (*CreateActorRequest)(nil), // 68: ateapi.CreateActorRequest + (*UpdateActorRequest)(nil), // 69: ateapi.UpdateActorRequest + (*FencingToken)(nil), // 70: ateapi.FencingToken + (*SuspendActorRequest)(nil), // 71: ateapi.SuspendActorRequest + (*SuspendActorResponse)(nil), // 72: ateapi.SuspendActorResponse + (*PauseActorRequest)(nil), // 73: ateapi.PauseActorRequest + (*PauseActorResponse)(nil), // 74: ateapi.PauseActorResponse + (*ResumeActorRequest)(nil), // 75: ateapi.ResumeActorRequest + (*ResumeActorResponse)(nil), // 76: ateapi.ResumeActorResponse + (*RevertActorRequest)(nil), // 77: ateapi.RevertActorRequest + (*RevertActorResponse)(nil), // 78: ateapi.RevertActorResponse + (*DeleteActorRequest)(nil), // 79: ateapi.DeleteActorRequest + (*GetActorEgressPolicyRequest)(nil), // 80: ateapi.GetActorEgressPolicyRequest + (*CreateActorEgressPolicyRequest)(nil), // 81: ateapi.CreateActorEgressPolicyRequest + (*UpdateActorEgressPolicyRequest)(nil), // 82: ateapi.UpdateActorEgressPolicyRequest + (*DeleteActorEgressPolicyRequest)(nil), // 83: ateapi.DeleteActorEgressPolicyRequest + (*GetEgressPolicyContractRequest)(nil), // 84: ateapi.GetEgressPolicyContractRequest + (*EgressPolicyContract)(nil), // 85: ateapi.EgressPolicyContract + (*GetTagRequest)(nil), // 86: ateapi.GetTagRequest + (*MintActorJWTRequest)(nil), // 87: ateapi.MintActorJWTRequest + (*MintActorJWTResponse)(nil), // 88: ateapi.MintActorJWTResponse + (*MintActorCertificateRequest)(nil), // 89: ateapi.MintActorCertificateRequest + (*MintActorCertificateResponse)(nil), // 90: ateapi.MintActorCertificateResponse + (*ListTagsRequest)(nil), // 91: ateapi.ListTagsRequest + (*ListTagsResponse)(nil), // 92: ateapi.ListTagsResponse + (*CreateTagRequest)(nil), // 93: ateapi.CreateTagRequest + (*UpdateTagRequest)(nil), // 94: ateapi.UpdateTagRequest + (*DeleteTagRequest)(nil), // 95: ateapi.DeleteTagRequest + (*DeleteOptions)(nil), // 96: ateapi.DeleteOptions + (*ListWorkerActorAssignmentsRequest)(nil), // 97: ateapi.ListWorkerActorAssignmentsRequest + (*ListWorkerActorAssignmentsResponse)(nil), // 98: ateapi.ListWorkerActorAssignmentsResponse + (*ListWorkersRequest)(nil), // 99: ateapi.ListWorkersRequest + (*ListWorkersResponse)(nil), // 100: ateapi.ListWorkersResponse + (*GetWorkerRequest)(nil), // 101: ateapi.GetWorkerRequest + (*CreateWorkerRequest)(nil), // 102: ateapi.CreateWorkerRequest + (*UpdateWorkerRequest)(nil), // 103: ateapi.UpdateWorkerRequest + (*DeleteWorkerRequest)(nil), // 104: ateapi.DeleteWorkerRequest + (*DrainWorkerRequest)(nil), // 105: ateapi.DrainWorkerRequest + (*ListActorsRequest)(nil), // 106: ateapi.ListActorsRequest + (*ListActorsResponse)(nil), // 107: ateapi.ListActorsResponse + (*Worker)(nil), // 108: ateapi.Worker + (*WorkerStatus)(nil), // 109: ateapi.WorkerStatus + (*WorkerResources)(nil), // 110: ateapi.WorkerResources + (*ActorAssignment)(nil), // 111: ateapi.ActorAssignment + (*SetWorkerCapacityRequest)(nil), // 112: ateapi.SetWorkerCapacityRequest + (*SetWorkerCapacityResponse)(nil), // 113: ateapi.SetWorkerCapacityResponse + (*MintAteomActorCertificateRequest)(nil), // 114: ateapi.MintAteomActorCertificateRequest + (*MintAteomActorCertificateResponse)(nil), // 115: ateapi.MintAteomActorCertificateResponse + (*RequestActorSuspendRequest)(nil), // 116: ateapi.RequestActorSuspendRequest + (*RequestActorSuspendResponse)(nil), // 117: ateapi.RequestActorSuspendResponse + (*AccessPolicy)(nil), // 118: ateapi.AccessPolicy + (*Binding)(nil), // 119: ateapi.Binding + (*GetGlobalAccessPolicyRequest)(nil), // 120: ateapi.GetGlobalAccessPolicyRequest + (*CreateGlobalAccessPolicyRequest)(nil), // 121: ateapi.CreateGlobalAccessPolicyRequest + (*UpdateGlobalAccessPolicyRequest)(nil), // 122: ateapi.UpdateGlobalAccessPolicyRequest + (*GetAtespaceAccessPolicyRequest)(nil), // 123: ateapi.GetAtespaceAccessPolicyRequest + (*CreateAtespaceAccessPolicyRequest)(nil), // 124: ateapi.CreateAtespaceAccessPolicyRequest + (*UpdateAtespaceAccessPolicyRequest)(nil), // 125: ateapi.UpdateAtespaceAccessPolicyRequest + (*DeleteAtespaceAccessPolicyRequest)(nil), // 126: ateapi.DeleteAtespaceAccessPolicyRequest + nil, // 127: ateapi.Selector.MatchLabelsEntry + nil, // 128: ateapi.ExternalVolume.VolumeContextEntry + nil, // 129: ateapi.Worker.LabelsEntry + (*timestamppb.Timestamp)(nil), // 130: google.protobuf.Timestamp } var file_ateapi_proto_depIdxs = []int32{ 0, // 0: ateapi.ExternalSnapshot.content_scope:type_name -> ateapi.SnapshotContentScope 0, // 1: ateapi.LocalSnapshot.content_scope:type_name -> ateapi.SnapshotContentScope - 7, // 2: ateapi.LocalSnapshot.durable_copy:type_name -> ateapi.ExternalSnapshot - 124, // 3: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry - 127, // 4: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp - 127, // 5: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp - 6, // 6: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status - 125, // 7: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry - 10, // 8: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata - 30, // 9: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef - 9, // 10: ateapi.Actor.worker_selector:type_name -> ateapi.Selector - 30, // 11: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef - 24, // 12: ateapi.Actor.status:type_name -> ateapi.ActorStatus - 10, // 13: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata - 15, // 14: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule - 15, // 15: ateapi.EgressPolicyTemplate.rules:type_name -> ateapi.EgressRule - 16, // 16: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule - 17, // 17: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule - 18, // 18: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule - 19, // 19: ateapi.HTTPRule.ports:type_name -> ateapi.Ports - 21, // 20: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects - 19, // 21: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports - 21, // 22: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects - 19, // 23: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports - 20, // 24: ateapi.Ports.all:type_name -> ateapi.AllPorts - 22, // 25: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader - 23, // 26: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource - 2, // 27: ateapi.ActorStatus.state:type_name -> ateapi.ActorState - 26, // 28: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment - 7, // 29: ateapi.ActorStatus.external_snapshot:type_name -> ateapi.ExternalSnapshot - 8, // 30: ateapi.ActorStatus.local_snapshot:type_name -> ateapi.LocalSnapshot - 11, // 31: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume - 25, // 32: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash - 67, // 33: ateapi.ActorStatus.fencing_token:type_name -> ateapi.FencingToken - 127, // 34: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp - 30, // 35: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef - 7, // 36: ateapi.TagStatus.snapshot:type_name -> ateapi.ExternalSnapshot - 10, // 37: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata - 27, // 38: ateapi.Tag.status:type_name -> ateapi.TagStatus - 1, // 39: ateapi.Tag.scope:type_name -> ateapi.TagScope - 30, // 40: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef - 10, // 41: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 10, // 42: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata - 9, // 43: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector - 38, // 44: ateapi.ActorTemplate.containers:type_name -> ateapi.Container - 44, // 45: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume - 37, // 46: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig - 36, // 47: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig - 32, // 48: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources - 35, // 49: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus - 14, // 50: ateapi.ActorTemplate.default_egress_policy:type_name -> ateapi.EgressPolicyTemplate - 33, // 51: ateapi.Resources.limits:type_name -> ateapi.Limits - 30, // 52: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef - 127, // 53: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp - 34, // 54: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus - 3, // 55: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass - 0, // 56: ateapi.SnapshotConfig.on_pause:type_name -> ateapi.SnapshotContentScope - 0, // 57: ateapi.SnapshotConfig.on_commit:type_name -> ateapi.SnapshotContentScope - 41, // 58: ateapi.Container.env:type_name -> ateapi.EnvVar - 42, // 59: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe - 53, // 60: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount - 39, // 61: ateapi.Container.security_context:type_name -> ateapi.SecurityContext - 32, // 62: ateapi.Container.resources:type_name -> ateapi.Resources - 40, // 63: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities - 43, // 64: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction - 46, // 65: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource - 47, // 66: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate - 48, // 67: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource - 45, // 68: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource - 49, // 69: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource - 50, // 70: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource - 52, // 71: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource - 51, // 72: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem - 4, // 73: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField - 29, // 74: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 30, // 75: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 29, // 76: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 30, // 77: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 93, // 78: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions - 31, // 79: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate - 30, // 80: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 31, // 81: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate - 30, // 82: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 93, // 83: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions - 30, // 84: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 12, // 85: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 12, // 86: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor - 30, // 87: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 67, // 88: ateapi.SuspendActorRequest.fencing_token:type_name -> ateapi.FencingToken - 12, // 89: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 30, // 90: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 67, // 91: ateapi.PauseActorRequest.fencing_token:type_name -> ateapi.FencingToken - 12, // 92: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 30, // 93: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 67, // 94: ateapi.ResumeActorRequest.fencing_token:type_name -> ateapi.FencingToken - 12, // 95: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 30, // 96: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef - 12, // 97: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor - 30, // 98: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 93, // 99: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions - 30, // 100: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 30, // 101: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 13, // 102: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 30, // 103: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 13, // 104: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy - 30, // 105: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef - 93, // 106: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions - 30, // 107: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef - 30, // 108: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef - 127, // 109: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp - 30, // 110: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 28, // 111: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag - 28, // 112: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag - 28, // 113: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag - 30, // 114: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef - 93, // 115: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions - 30, // 116: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef - 108, // 117: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment - 105, // 118: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 30, // 119: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef - 105, // 120: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker - 105, // 121: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker - 30, // 122: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef - 93, // 123: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions - 30, // 124: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef - 12, // 125: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 10, // 126: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata - 126, // 127: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 106, // 128: ateapi.Worker.status:type_name -> ateapi.WorkerStatus - 5, // 129: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState - 107, // 130: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources - 107, // 131: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources - 32, // 132: ateapi.WorkerResources.resources:type_name -> ateapi.Resources - 10, // 133: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata - 30, // 134: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef - 30, // 135: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef - 32, // 136: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources - 30, // 137: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef - 107, // 138: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources - 105, // 139: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker - 30, // 140: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef - 30, // 141: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef - 30, // 142: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef - 12, // 143: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor - 10, // 144: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata - 116, // 145: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding - 115, // 146: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 115, // 147: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 30, // 148: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 30, // 149: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 115, // 150: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 30, // 151: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 115, // 152: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy - 30, // 153: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef - 93, // 154: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions - 64, // 155: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 65, // 156: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 66, // 157: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 68, // 158: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 70, // 159: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 72, // 160: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 74, // 161: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest - 76, // 162: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 77, // 163: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest - 78, // 164: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest - 79, // 165: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest - 80, // 166: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest - 81, // 167: ateapi.Control.GetEgressPolicyContract:input_type -> ateapi.GetEgressPolicyContractRequest - 84, // 168: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest - 86, // 169: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest - 90, // 170: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest - 83, // 171: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest - 88, // 172: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest - 91, // 173: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest - 92, // 174: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest - 96, // 175: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 98, // 176: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest - 99, // 177: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest - 100, // 178: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest - 101, // 179: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest - 102, // 180: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest - 94, // 181: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest - 103, // 182: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 54, // 183: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 55, // 184: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 56, // 185: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 58, // 186: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 59, // 187: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 60, // 188: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 61, // 189: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 63, // 190: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 117, // 191: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest - 118, // 192: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest - 119, // 193: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest - 120, // 194: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest - 121, // 195: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest - 122, // 196: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest - 123, // 197: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest - 109, // 198: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest - 111, // 199: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest - 113, // 200: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest - 12, // 201: ateapi.Control.GetActor:output_type -> ateapi.Actor - 12, // 202: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 12, // 203: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 69, // 204: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 71, // 205: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 73, // 206: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 75, // 207: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse - 12, // 208: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 13, // 209: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy - 13, // 210: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 13, // 211: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy - 13, // 212: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy - 82, // 213: ateapi.Control.GetEgressPolicyContract:output_type -> ateapi.EgressPolicyContract - 85, // 214: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse - 87, // 215: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse - 28, // 216: ateapi.Control.CreateTag:output_type -> ateapi.Tag - 28, // 217: ateapi.Control.GetTag:output_type -> ateapi.Tag - 89, // 218: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse - 28, // 219: ateapi.Control.UpdateTag:output_type -> ateapi.Tag - 28, // 220: ateapi.Control.DeleteTag:output_type -> ateapi.Tag - 97, // 221: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 105, // 222: ateapi.Control.GetWorker:output_type -> ateapi.Worker - 105, // 223: ateapi.Control.CreateWorker:output_type -> ateapi.Worker - 105, // 224: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker - 105, // 225: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker - 105, // 226: ateapi.Control.DrainWorker:output_type -> ateapi.Worker - 95, // 227: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse - 104, // 228: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 29, // 229: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 29, // 230: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 57, // 231: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 29, // 232: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 31, // 233: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 31, // 234: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 62, // 235: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 31, // 236: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 115, // 237: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 238: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 239: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 240: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 241: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 242: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 115, // 243: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy - 110, // 244: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse - 112, // 245: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse - 114, // 246: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse - 201, // [201:247] is the sub-list for method output_type - 155, // [155:201] is the sub-list for method input_type - 155, // [155:155] is the sub-list for extension type_name - 155, // [155:155] is the sub-list for extension extendee - 0, // [0:155] is the sub-list for field type_name + 8, // 2: ateapi.LocalSnapshot.durable_copy:type_name -> ateapi.ExternalSnapshot + 127, // 3: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry + 130, // 4: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp + 130, // 5: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp + 7, // 6: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status + 128, // 7: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry + 11, // 8: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata + 32, // 9: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef + 10, // 10: ateapi.Actor.worker_selector:type_name -> ateapi.Selector + 32, // 11: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef + 26, // 12: ateapi.Actor.status:type_name -> ateapi.ActorStatus + 14, // 13: ateapi.Actor.existing_volumes:type_name -> ateapi.ExistingVolume + 2, // 14: ateapi.ExistingVolume.access_mode:type_name -> ateapi.VolumeAccessMode + 11, // 15: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata + 17, // 16: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule + 17, // 17: ateapi.EgressPolicyTemplate.rules:type_name -> ateapi.EgressRule + 18, // 18: ateapi.EgressRule.http:type_name -> ateapi.HTTPRule + 19, // 19: ateapi.EgressRule.https:type_name -> ateapi.HTTPSRule + 20, // 20: ateapi.EgressRule.tls_passthrough:type_name -> ateapi.TLSPassthroughRule + 21, // 21: ateapi.HTTPRule.ports:type_name -> ateapi.Ports + 23, // 22: ateapi.HTTPRule.effects:type_name -> ateapi.HttpRuleEffects + 21, // 23: ateapi.HTTPSRule.ports:type_name -> ateapi.Ports + 23, // 24: ateapi.HTTPSRule.effects:type_name -> ateapi.HttpRuleEffects + 21, // 25: ateapi.TLSPassthroughRule.ports:type_name -> ateapi.Ports + 22, // 26: ateapi.Ports.all:type_name -> ateapi.AllPorts + 24, // 27: ateapi.HttpRuleEffects.replace_headers:type_name -> ateapi.CredentialHeader + 25, // 28: ateapi.CredentialHeader.actor_jwt:type_name -> ateapi.ActorJWTSource + 3, // 29: ateapi.ActorStatus.state:type_name -> ateapi.ActorState + 28, // 30: ateapi.ActorStatus.worker_assignment:type_name -> ateapi.WorkerAssignment + 8, // 31: ateapi.ActorStatus.external_snapshot:type_name -> ateapi.ExternalSnapshot + 9, // 32: ateapi.ActorStatus.local_snapshot:type_name -> ateapi.LocalSnapshot + 12, // 33: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume + 27, // 34: ateapi.ActorStatus.crash:type_name -> ateapi.ActorCrash + 70, // 35: ateapi.ActorStatus.fencing_token:type_name -> ateapi.FencingToken + 130, // 36: ateapi.ActorCrash.crash_time:type_name -> google.protobuf.Timestamp + 32, // 37: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef + 8, // 38: ateapi.TagStatus.snapshot:type_name -> ateapi.ExternalSnapshot + 11, // 39: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata + 29, // 40: ateapi.Tag.status:type_name -> ateapi.TagStatus + 1, // 41: ateapi.Tag.scope:type_name -> ateapi.TagScope + 32, // 42: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef + 11, // 43: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 11, // 44: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata + 10, // 45: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector + 40, // 46: ateapi.ActorTemplate.containers:type_name -> ateapi.Container + 46, // 47: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume + 39, // 48: ateapi.ActorTemplate.snapshot_config:type_name -> ateapi.SnapshotConfig + 38, // 49: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig + 34, // 50: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources + 37, // 51: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus + 16, // 52: ateapi.ActorTemplate.default_egress_policy:type_name -> ateapi.EgressPolicyTemplate + 35, // 53: ateapi.Resources.limits:type_name -> ateapi.Limits + 32, // 54: ateapi.GoldenSnapshotStatus.golden_tag:type_name -> ateapi.ObjectRef + 130, // 55: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp + 36, // 56: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus + 4, // 57: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass + 0, // 58: ateapi.SnapshotConfig.on_pause:type_name -> ateapi.SnapshotContentScope + 0, // 59: ateapi.SnapshotConfig.on_commit:type_name -> ateapi.SnapshotContentScope + 43, // 60: ateapi.Container.env:type_name -> ateapi.EnvVar + 44, // 61: ateapi.Container.wakeup_probe:type_name -> ateapi.ContainerWakeupProbe + 56, // 62: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount + 41, // 63: ateapi.Container.security_context:type_name -> ateapi.SecurityContext + 34, // 64: ateapi.Container.resources:type_name -> ateapi.Resources + 42, // 65: ateapi.SecurityContext.capabilities:type_name -> ateapi.Capabilities + 45, // 66: ateapi.ContainerWakeupProbe.http_get:type_name -> ateapi.HTTPGetAction + 49, // 67: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource + 50, // 68: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate + 51, // 69: ateapi.Volume.system_info:type_name -> ateapi.SystemInfoVolumeSource + 48, // 70: ateapi.Volume.image:type_name -> ateapi.ImageVolumeSource + 47, // 71: ateapi.Volume.existing_volume:type_name -> ateapi.ExistingVolumeSource + 52, // 72: ateapi.SystemInfoVolumeSource.data_sources:type_name -> ateapi.SystemInfoDataSource + 53, // 73: ateapi.SystemInfoDataSource.actor_metadata:type_name -> ateapi.ActorMetadataDataSource + 55, // 74: ateapi.SystemInfoDataSource.trust_bundle:type_name -> ateapi.TrustBundleDataSource + 54, // 75: ateapi.ActorMetadataDataSource.items:type_name -> ateapi.ActorMetadataItem + 5, // 76: ateapi.ActorMetadataItem.field:type_name -> ateapi.ActorMetadataField + 31, // 77: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 32, // 78: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 31, // 79: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 32, // 80: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 96, // 81: ateapi.DeleteAtespaceRequest.options:type_name -> ateapi.DeleteOptions + 33, // 82: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate + 32, // 83: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 33, // 84: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate + 32, // 85: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 96, // 86: ateapi.DeleteActorTemplateRequest.options:type_name -> ateapi.DeleteOptions + 32, // 87: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 88: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 13, // 89: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor + 32, // 90: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 70, // 91: ateapi.SuspendActorRequest.fencing_token:type_name -> ateapi.FencingToken + 13, // 92: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 32, // 93: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 70, // 94: ateapi.PauseActorRequest.fencing_token:type_name -> ateapi.FencingToken + 13, // 95: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 32, // 96: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 70, // 97: ateapi.ResumeActorRequest.fencing_token:type_name -> ateapi.FencingToken + 13, // 98: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 32, // 99: ateapi.RevertActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 100: ateapi.RevertActorResponse.actor:type_name -> ateapi.Actor + 32, // 101: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 96, // 102: ateapi.DeleteActorRequest.options:type_name -> ateapi.DeleteOptions + 32, // 103: ateapi.GetActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 32, // 104: ateapi.CreateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 15, // 105: ateapi.CreateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 32, // 106: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 15, // 107: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy + 32, // 108: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef + 96, // 109: ateapi.DeleteActorEgressPolicyRequest.options:type_name -> ateapi.DeleteOptions + 32, // 110: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef + 32, // 111: ateapi.MintActorJWTRequest.actor:type_name -> ateapi.ObjectRef + 130, // 112: ateapi.MintActorJWTResponse.expires_at:type_name -> google.protobuf.Timestamp + 32, // 113: ateapi.MintActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 30, // 114: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag + 30, // 115: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag + 30, // 116: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag + 32, // 117: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef + 96, // 118: ateapi.DeleteTagRequest.options:type_name -> ateapi.DeleteOptions + 32, // 119: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef + 111, // 120: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment + 108, // 121: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 32, // 122: ateapi.GetWorkerRequest.worker:type_name -> ateapi.ObjectRef + 108, // 123: ateapi.CreateWorkerRequest.worker:type_name -> ateapi.Worker + 108, // 124: ateapi.UpdateWorkerRequest.worker:type_name -> ateapi.Worker + 32, // 125: ateapi.DeleteWorkerRequest.worker:type_name -> ateapi.ObjectRef + 96, // 126: ateapi.DeleteWorkerRequest.options:type_name -> ateapi.DeleteOptions + 32, // 127: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef + 13, // 128: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 11, // 129: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata + 129, // 130: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 109, // 131: ateapi.Worker.status:type_name -> ateapi.WorkerStatus + 6, // 132: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState + 110, // 133: ateapi.WorkerStatus.capacity:type_name -> ateapi.WorkerResources + 110, // 134: ateapi.WorkerStatus.allocated:type_name -> ateapi.WorkerResources + 34, // 135: ateapi.WorkerResources.resources:type_name -> ateapi.Resources + 11, // 136: ateapi.ActorAssignment.metadata:type_name -> ateapi.ResourceMetadata + 32, // 137: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef + 32, // 138: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef + 34, // 139: ateapi.ActorAssignment.resources:type_name -> ateapi.Resources + 32, // 140: ateapi.SetWorkerCapacityRequest.worker:type_name -> ateapi.ObjectRef + 110, // 141: ateapi.SetWorkerCapacityRequest.capacity:type_name -> ateapi.WorkerResources + 108, // 142: ateapi.SetWorkerCapacityResponse.worker:type_name -> ateapi.Worker + 32, // 143: ateapi.MintAteomActorCertificateRequest.actor:type_name -> ateapi.ObjectRef + 32, // 144: ateapi.RequestActorSuspendRequest.worker:type_name -> ateapi.ObjectRef + 32, // 145: ateapi.RequestActorSuspendRequest.actor:type_name -> ateapi.ObjectRef + 13, // 146: ateapi.RequestActorSuspendResponse.actor:type_name -> ateapi.Actor + 11, // 147: ateapi.AccessPolicy.metadata:type_name -> ateapi.ResourceMetadata + 119, // 148: ateapi.AccessPolicy.bindings:type_name -> ateapi.Binding + 118, // 149: ateapi.CreateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 118, // 150: ateapi.UpdateGlobalAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 32, // 151: ateapi.GetAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 32, // 152: ateapi.CreateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 118, // 153: ateapi.CreateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 32, // 154: ateapi.UpdateAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 118, // 155: ateapi.UpdateAtespaceAccessPolicyRequest.access_policy:type_name -> ateapi.AccessPolicy + 32, // 156: ateapi.DeleteAtespaceAccessPolicyRequest.atespace:type_name -> ateapi.ObjectRef + 96, // 157: ateapi.DeleteAtespaceAccessPolicyRequest.options:type_name -> ateapi.DeleteOptions + 67, // 158: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 68, // 159: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 69, // 160: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 71, // 161: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 73, // 162: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 75, // 163: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 77, // 164: ateapi.Control.RevertActor:input_type -> ateapi.RevertActorRequest + 79, // 165: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 80, // 166: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest + 81, // 167: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest + 82, // 168: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest + 83, // 169: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest + 84, // 170: ateapi.Control.GetEgressPolicyContract:input_type -> ateapi.GetEgressPolicyContractRequest + 87, // 171: ateapi.Control.MintActorJWT:input_type -> ateapi.MintActorJWTRequest + 89, // 172: ateapi.Control.MintActorCertificate:input_type -> ateapi.MintActorCertificateRequest + 93, // 173: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest + 86, // 174: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest + 91, // 175: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest + 94, // 176: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest + 95, // 177: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest + 99, // 178: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 101, // 179: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest + 102, // 180: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest + 103, // 181: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest + 104, // 182: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest + 105, // 183: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest + 97, // 184: ateapi.Control.ListWorkerActorAssignments:input_type -> ateapi.ListWorkerActorAssignmentsRequest + 106, // 185: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 57, // 186: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 58, // 187: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 59, // 188: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 61, // 189: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 62, // 190: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 63, // 191: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 64, // 192: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 66, // 193: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 120, // 194: ateapi.Control.GetGlobalAccessPolicy:input_type -> ateapi.GetGlobalAccessPolicyRequest + 121, // 195: ateapi.Control.CreateGlobalAccessPolicy:input_type -> ateapi.CreateGlobalAccessPolicyRequest + 122, // 196: ateapi.Control.UpdateGlobalAccessPolicy:input_type -> ateapi.UpdateGlobalAccessPolicyRequest + 123, // 197: ateapi.Control.GetAtespaceAccessPolicy:input_type -> ateapi.GetAtespaceAccessPolicyRequest + 124, // 198: ateapi.Control.CreateAtespaceAccessPolicy:input_type -> ateapi.CreateAtespaceAccessPolicyRequest + 125, // 199: ateapi.Control.UpdateAtespaceAccessPolicy:input_type -> ateapi.UpdateAtespaceAccessPolicyRequest + 126, // 200: ateapi.Control.DeleteAtespaceAccessPolicy:input_type -> ateapi.DeleteAtespaceAccessPolicyRequest + 112, // 201: ateapi.WorkerService.SetWorkerCapacity:input_type -> ateapi.SetWorkerCapacityRequest + 114, // 202: ateapi.WorkerService.MintAteomActorCertificate:input_type -> ateapi.MintAteomActorCertificateRequest + 116, // 203: ateapi.WorkerService.RequestActorSuspend:input_type -> ateapi.RequestActorSuspendRequest + 13, // 204: ateapi.Control.GetActor:output_type -> ateapi.Actor + 13, // 205: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 13, // 206: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 72, // 207: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 74, // 208: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 76, // 209: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 78, // 210: ateapi.Control.RevertActor:output_type -> ateapi.RevertActorResponse + 13, // 211: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 15, // 212: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 213: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 214: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy + 15, // 215: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy + 85, // 216: ateapi.Control.GetEgressPolicyContract:output_type -> ateapi.EgressPolicyContract + 88, // 217: ateapi.Control.MintActorJWT:output_type -> ateapi.MintActorJWTResponse + 90, // 218: ateapi.Control.MintActorCertificate:output_type -> ateapi.MintActorCertificateResponse + 30, // 219: ateapi.Control.CreateTag:output_type -> ateapi.Tag + 30, // 220: ateapi.Control.GetTag:output_type -> ateapi.Tag + 92, // 221: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse + 30, // 222: ateapi.Control.UpdateTag:output_type -> ateapi.Tag + 30, // 223: ateapi.Control.DeleteTag:output_type -> ateapi.Tag + 100, // 224: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 108, // 225: ateapi.Control.GetWorker:output_type -> ateapi.Worker + 108, // 226: ateapi.Control.CreateWorker:output_type -> ateapi.Worker + 108, // 227: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker + 108, // 228: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker + 108, // 229: ateapi.Control.DrainWorker:output_type -> ateapi.Worker + 98, // 230: ateapi.Control.ListWorkerActorAssignments:output_type -> ateapi.ListWorkerActorAssignmentsResponse + 107, // 231: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 31, // 232: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 31, // 233: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 60, // 234: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 31, // 235: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 33, // 236: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 33, // 237: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 65, // 238: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 33, // 239: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 118, // 240: ateapi.Control.GetGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 241: ateapi.Control.CreateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 242: ateapi.Control.UpdateGlobalAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 243: ateapi.Control.GetAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 244: ateapi.Control.CreateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 245: ateapi.Control.UpdateAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 118, // 246: ateapi.Control.DeleteAtespaceAccessPolicy:output_type -> ateapi.AccessPolicy + 113, // 247: ateapi.WorkerService.SetWorkerCapacity:output_type -> ateapi.SetWorkerCapacityResponse + 115, // 248: ateapi.WorkerService.MintAteomActorCertificate:output_type -> ateapi.MintAteomActorCertificateResponse + 117, // 249: ateapi.WorkerService.RequestActorSuspend:output_type -> ateapi.RequestActorSuspendResponse + 204, // [204:250] is the sub-list for method output_type + 158, // [158:204] is the sub-list for method input_type + 158, // [158:158] is the sub-list for extension type_name + 158, // [158:158] is the sub-list for extension extendee + 0, // [0:158] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } @@ -9222,8 +9520,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: 7, - NumMessages: 120, + NumEnums: 8, + NumMessages: 122, NumExtensions: 0, NumServices: 2, }, diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 27ab20e3f4..ffe4a58da2 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -460,6 +460,84 @@ message Actor { // // +k8s:optional ActorStatus status = 7; + + // existing_volumes supply the template's existing volumes + // (Volume.existing_volume) for this actor: each names one and references a + // CSI volume that exists outside Substrate and outlives the actor, such as + // a read-write-many volume several actors work on. Substrate attaches and + // mounts it for this actor, and never creates or deletes it nor changes its + // content. An existing volume of the template that this list does not name + // has neither the volume nor its mounts. An actor that names any boots from + // its image instead of restoring the template's golden snapshot, which was + // captured without these mounts. Set once at creation and immutable + // afterward. + // + // +k8s:optional + // +k8s:maxItems=8 + // +k8s:listType=map + // +k8s:listMapKey=name + // +k8s:immutable + repeated ExistingVolume existing_volumes = 10001; +} + +// VolumeAccessMode is how a volume may be attached and mounted across nodes. +enum VolumeAccessMode { + VOLUME_ACCESS_MODE_UNSPECIFIED = 0; + // Read-write on a single node (CSI SINGLE_NODE_WRITER). + VOLUME_ACCESS_MODE_READ_WRITE_ONCE = 1; + // Read-only on many nodes (CSI MULTI_NODE_READER_ONLY). + VOLUME_ACCESS_MODE_READ_ONLY_MANY = 2; + // Read-write on many nodes (CSI MULTI_NODE_MULTI_WRITER). + VOLUME_ACCESS_MODE_READ_WRITE_MANY = 3; +} + +// ExistingVolume references a CSI volume for one of the template's existing +// volumes. +message ExistingVolume { + // name of the template's existing volume this supplies. + // + // +k8s:required + // +k8s:format=k8s-short-name + string name = 1; + + // driver is the CSI driver that serves the volume. It must have a + // CSIDriverConfig. + // + // +k8s:required + // +k8s:maxLength=253 + // +k8s:customValidation # same syntax as ExternalVolume.volume_type + string driver = 2; + + // volume_handle is the driver's ID of the volume, as a Kubernetes + // PersistentVolume names it in spec.csi.volumeHandle. A PersistentVolume of + // the driver with this handle must exist; its spec.csi.volumeAttributes are + // passed to the driver when the volume is mounted. Several existing volumes + // of one actor may reference the same handle. + // + // +k8s:required + // +k8s:maxLength=256 + // +k8s:customValidation # no control characters + string volume_handle = 3; + + // access_mode is READ_WRITE_MANY or READ_ONLY_MANY, and must be one of the + // PersistentVolume's access modes. With READ_ONLY_MANY every mount of the + // volume is read-only. + // + // +k8s:required + // +k8s:minimum=2 + // +k8s:maximum=3 # keep in sync with VolumeAccessMode + VolumeAccessMode access_mode = 4; + + // sub_path is the directory of the volume that this actor sees as the + // volume's root, in the form of VolumeMount.sub_path; empty is the root. + // The template's mounts of the volume, and their own sub_path, are + // relative to it, so actors of one template can each work in a directory + // of their own. + // + // +k8s:optional + // +k8s:maxLength=4096 + // +k8s:customValidation # clean relative path + string sub_path = 5; } // EgressPolicy is an egress policy resource nested under an Actor. An Actor has @@ -1323,7 +1401,7 @@ message Volume { string name = 1; // Exactly one of durable_dir / external_volume_template / image / - // system_info must be set. + // system_info / existing_volume must be set. // // +k8s:optional // +k8s:unionMember @@ -1342,8 +1420,20 @@ message Volume { // +k8s:optional // +k8s:unionMember ImageVolumeSource image = 6; + + // existing_volume declares a volume each actor supplies at CreateActor + // (Actor.existing_volumes). + // + // +k8s:optional + // +k8s:unionMember + ExistingVolumeSource existing_volume = 10001; } +// ExistingVolumeSource declares a volume that exists outside Substrate and +// that each actor supplies at CreateActor in Actor.existing_volumes. An actor +// that supplies none has neither the volume nor its mounts. +message ExistingVolumeSource {} + // ImageVolumeSource mounts the contents of an OCI image, read-only. The // reference must include a digest: changing the image invalidates // snapshots. @@ -1495,6 +1585,24 @@ message VolumeMount { // +k8s:maxLength=4096 // +k8s:customValidation # clean-absolute-path shape; no regex/pattern tag exists string mount_path = 2; + + // sub_path is the directory of the volume to mount, relative to its root; + // empty mounts the root. It must be a clean relative path: no leading '/', + // no '.' or '..' element, no '//', no trailing '/' and no control + // characters. Only an existing volume's mount may set it. The directory + // must exist, and no element of it may be a symbolic link: a mount that + // would need either fails. + // + // +k8s:optional + // +k8s:maxLength=4096 + // +k8s:customValidation # clean relative path + string sub_path = 10001; + + // read_only mounts the volume read-only. Only an existing volume's mount + // may set it. + // + // +k8s:optional + bool read_only = 10002; } message CreateAtespaceRequest { From 5421c8ed4475a45cc5a1f8a5a3bbd01e64fb334b Mon Sep 17 00:00:00 2001 From: Timo Derstappen Date: Fri, 9 Oct 2026 18:04:38 +0200 Subject: [PATCH 4/5] docs(fork): row for the existing volume mounted at a sub-path Records the existing-volume change, the upstream issue it follows and when it leaves the line. Signed-off-by: Timo Derstappen --- FORK.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FORK.md b/FORK.md index fdf36b61e4..e5686bad10 100644 --- a/FORK.md +++ b/FORK.md @@ -101,7 +101,8 @@ Everything on `giantswarm` that is not in the pin (`git log v0.4.0-alpha1..giant | The egress gateway's ext-proc names its telemetry `atenet-egress` (`OTEL_SERVICE_NAME` on the `atenet-egress` `ext-proc` container; a chart unit test) | the `atenet` binary hardcodes the service name `atenet-router` for both modes and the container set none, so egress spans and logs carried `service.name=atenet-router` | `6627a636` ([#217](https://github.com/giantswarm/substrate/pull/217), rebase-merged 2026-10-09; first release 1.6.2) | fork-only chart change; upstream's `atenet-egress.yaml` has the same gap, not queued | | The gVisor SandboxConfig's pause image is a chart value (`images.pause`, default upstream's `registry.k8s.io/pause:3.10.2@sha256:f548e0e8…`, rendered with `required`; the unit-test suite `charts/substrate/tests/sandboxconfig_gvisor_test.yaml` covers the default, an override and the empty value; the README row; the preserved kubectl-apply manifest keeps upstream's default) | the one image reference the chart hardcoded, and the one an operator could not move: `registry.k8s.io` redirects every pull to a Google Artifact Registry host, so a cluster whose egress admits only its own registry failed every golden boot while creating the pause OCI bundle, before any snapshot existed ([#224](https://github.com/giantswarm/substrate/issues/224)) | `f844d726` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | to file (prepared): branch [`upstream/sandbox-pause-image-value`](https://github.com/giantswarm/substrate/tree/upstream/sandbox-pause-image-value) here (`1591d20d`, the same commit on kagent-dev `main` @ `9c4b1fb5` of 2026-10-08, DCO signed), the shape of [kagent-dev/substrate#23](https://github.com/kagent-dev/substrate/pull/23) (2026-08-20, closed unmerged 2026-10-06 on a stale base); [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 155 | | The pause image defaults to its gsoci copy: `images.pause` = `gsoci.azurecr.io/giantswarm/pause:3.10.2@sha256:f548e0e8…`, upstream's digest | every Giant Swarm installation pulls from gsoci.azurecr.io, and one whose egress admits only that registry cannot reach `registry.k8s.io`; the same digest, so the snapshots that record it restore unchanged ([#224](https://github.com/giantswarm/substrate/issues/224)) | `dd957c2e` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | **ours to keep**: a Giant Swarm registry is not upstream's default; the upstream-shaped change is the row above | -| `resources.DeepEqual`, the unchanged-value check of the generated declarative validation, compares a slice of proto messages (a repeated message field) element by element with `proto.Equal`, a nil and an empty one being equal | it handed the slice to `reflect.DeepEqual`, which also compares each message's internal state; the RPC logger's marshal fills the size cache, so a repeated message field and its clone differed and an `+k8s:immutable` repeated field (`Actor.shared_volumes`, row below) failed every `CreateActor` with "field is immutable" | pending (`fix(resources): compare repeated message fields with proto.Equal`) | none: upstream `main` has the same `DeepEqual`; to file, queued in the upstream engagement list | +| `resources.DeepEqual`, the unchanged-value check of the generated declarative validation, compares a slice of proto messages (a repeated message field) element by element with `proto.Equal`, a nil and an empty one being equal | it handed the slice to `reflect.DeepEqual`, which also compares each message's internal state; the RPC logger's marshal fills the size cache, so a repeated message field and its clone differed and an `+k8s:immutable` repeated field (`Actor.existing_volumes`, row below) failed every `CreateActor` with "field is immutable" | pending (`fix(resources): compare repeated message fields with proto.Equal`) | none: upstream `main` has the same `DeepEqual`; to file, queued in the upstream engagement list | +| An existing volume mounted per actor at a sub-path: an ActorTemplate declares it (`Volume.existing_volume`, field 10001, an empty marker), `CreateActor` supplies it (`Actor.existing_volumes`, field 10001, immutable; `ExistingVolume` = name, CSI driver, volume handle, access mode `READ_WRITE_MANY`/`READ_ONLY_MANY` in the shape of upstream's `VolumeAccessMode`, and the `sub_path` the actor sees as the volume's root), and `VolumeMount` gains `sub_path` and `read_only` (fields 10001, 10002), so one volume mounts at several paths. `CreateActor` refuses a reference to a volume the template does not declare as existing, a driver without a `CSIDriverConfig`, a handle no PersistentVolume of the driver holds and an access mode the PersistentVolume does not permit (ate-api-server reads PersistentVolumes: a new ClusterRole rule); the PersistentVolume's `volumeAttributes` reach the driver at resume. An unsupplied existing volume contributes neither itself nor its mounts. Substrate never creates, deletes or detaches one: pause, resume and delete only unmount and mount it; a multi-node volume is staged per target, and atelet binds each sub-path from a descriptor opened beneath the volume's root without following symbolic links (`openat2` `RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`), so a missing directory or a planted link fails the mount. An actor with existing volumes boots from its image instead of the golden snapshot and cannot be cloned from a tag. Tests: unit (validation, workload spec, CSI staging, the sub-path resolver), the `existingvolumes` e2e on kind with the CSI NFS driver (`E2E_CSI_NFS=1` in pr-workflow) | Sessions on one workspace each need their own directory of one read-write-many volume read-write and its git mirrors read-only, hundreds a day; a per-actor volume or snapshot per Session does not scale, and an external volume was created per actor, attached single-node-writer and deleted with it, with no sub-path or read-only mount | pending (`feat: mount an existing volume per actor at a sub-path`) | follows agent-substrate/substrate#1637 (shared volumes across actors, open), in the shape proposed there (https://github.com/agent-substrate/substrate/issues/1637#issuecomment-6083442678), on top of #1988's access modes (open); upstream puts it behind the Preview gate of #1994 (open), which this line does not carry, so it is ungated here. [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 157. Exit: drops at the re-pin that carries #1637's API | Twenty-three patches change Substrate ahead of upstream — egress for an actor while it resumes, without which no skill-carrying agent of the platform boots, the atelet scheduling knobs, the keep policy on the CRD chart's From 6572232ce5eafa9cbd95fec345ca291bffe34730 Mon Sep 17 00:00:00 2001 From: Timo Derstappen Date: Fri, 9 Oct 2026 18:04:55 +0200 Subject: [PATCH 5/5] docs(fork): name the pull request of the existing-volume rows Signed-off-by: Timo Derstappen --- FORK.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FORK.md b/FORK.md index e5686bad10..5aaaf5d2e0 100644 --- a/FORK.md +++ b/FORK.md @@ -101,8 +101,8 @@ Everything on `giantswarm` that is not in the pin (`git log v0.4.0-alpha1..giant | The egress gateway's ext-proc names its telemetry `atenet-egress` (`OTEL_SERVICE_NAME` on the `atenet-egress` `ext-proc` container; a chart unit test) | the `atenet` binary hardcodes the service name `atenet-router` for both modes and the container set none, so egress spans and logs carried `service.name=atenet-router` | `6627a636` ([#217](https://github.com/giantswarm/substrate/pull/217), rebase-merged 2026-10-09; first release 1.6.2) | fork-only chart change; upstream's `atenet-egress.yaml` has the same gap, not queued | | The gVisor SandboxConfig's pause image is a chart value (`images.pause`, default upstream's `registry.k8s.io/pause:3.10.2@sha256:f548e0e8…`, rendered with `required`; the unit-test suite `charts/substrate/tests/sandboxconfig_gvisor_test.yaml` covers the default, an override and the empty value; the README row; the preserved kubectl-apply manifest keeps upstream's default) | the one image reference the chart hardcoded, and the one an operator could not move: `registry.k8s.io` redirects every pull to a Google Artifact Registry host, so a cluster whose egress admits only its own registry failed every golden boot while creating the pause OCI bundle, before any snapshot existed ([#224](https://github.com/giantswarm/substrate/issues/224)) | `f844d726` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | to file (prepared): branch [`upstream/sandbox-pause-image-value`](https://github.com/giantswarm/substrate/tree/upstream/sandbox-pause-image-value) here (`1591d20d`, the same commit on kagent-dev `main` @ `9c4b1fb5` of 2026-10-08, DCO signed), the shape of [kagent-dev/substrate#23](https://github.com/kagent-dev/substrate/pull/23) (2026-08-20, closed unmerged 2026-10-06 on a stale base); [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 155 | | The pause image defaults to its gsoci copy: `images.pause` = `gsoci.azurecr.io/giantswarm/pause:3.10.2@sha256:f548e0e8…`, upstream's digest | every Giant Swarm installation pulls from gsoci.azurecr.io, and one whose egress admits only that registry cannot reach `registry.k8s.io`; the same digest, so the snapshots that record it restore unchanged ([#224](https://github.com/giantswarm/substrate/issues/224)) | `dd957c2e` ([#225](https://github.com/giantswarm/substrate/pull/225), rebase-merged 2026-10-09; first release 1.6.3) | **ours to keep**: a Giant Swarm registry is not upstream's default; the upstream-shaped change is the row above | -| `resources.DeepEqual`, the unchanged-value check of the generated declarative validation, compares a slice of proto messages (a repeated message field) element by element with `proto.Equal`, a nil and an empty one being equal | it handed the slice to `reflect.DeepEqual`, which also compares each message's internal state; the RPC logger's marshal fills the size cache, so a repeated message field and its clone differed and an `+k8s:immutable` repeated field (`Actor.existing_volumes`, row below) failed every `CreateActor` with "field is immutable" | pending (`fix(resources): compare repeated message fields with proto.Equal`) | none: upstream `main` has the same `DeepEqual`; to file, queued in the upstream engagement list | -| An existing volume mounted per actor at a sub-path: an ActorTemplate declares it (`Volume.existing_volume`, field 10001, an empty marker), `CreateActor` supplies it (`Actor.existing_volumes`, field 10001, immutable; `ExistingVolume` = name, CSI driver, volume handle, access mode `READ_WRITE_MANY`/`READ_ONLY_MANY` in the shape of upstream's `VolumeAccessMode`, and the `sub_path` the actor sees as the volume's root), and `VolumeMount` gains `sub_path` and `read_only` (fields 10001, 10002), so one volume mounts at several paths. `CreateActor` refuses a reference to a volume the template does not declare as existing, a driver without a `CSIDriverConfig`, a handle no PersistentVolume of the driver holds and an access mode the PersistentVolume does not permit (ate-api-server reads PersistentVolumes: a new ClusterRole rule); the PersistentVolume's `volumeAttributes` reach the driver at resume. An unsupplied existing volume contributes neither itself nor its mounts. Substrate never creates, deletes or detaches one: pause, resume and delete only unmount and mount it; a multi-node volume is staged per target, and atelet binds each sub-path from a descriptor opened beneath the volume's root without following symbolic links (`openat2` `RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`), so a missing directory or a planted link fails the mount. An actor with existing volumes boots from its image instead of the golden snapshot and cannot be cloned from a tag. Tests: unit (validation, workload spec, CSI staging, the sub-path resolver), the `existingvolumes` e2e on kind with the CSI NFS driver (`E2E_CSI_NFS=1` in pr-workflow) | Sessions on one workspace each need their own directory of one read-write-many volume read-write and its git mirrors read-only, hundreds a day; a per-actor volume or snapshot per Session does not scale, and an external volume was created per actor, attached single-node-writer and deleted with it, with no sub-path or read-only mount | pending (`feat: mount an existing volume per actor at a sub-path`) | follows agent-substrate/substrate#1637 (shared volumes across actors, open), in the shape proposed there (https://github.com/agent-substrate/substrate/issues/1637#issuecomment-6083442678), on top of #1988's access modes (open); upstream puts it behind the Preview gate of #1994 (open), which this line does not carry, so it is ungated here. [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 157. Exit: drops at the re-pin that carries #1637's API | +| `resources.DeepEqual`, the unchanged-value check of the generated declarative validation, compares a slice of proto messages (a repeated message field) element by element with `proto.Equal`, a nil and an empty one being equal | it handed the slice to `reflect.DeepEqual`, which also compares each message's internal state; the RPC logger's marshal fills the size cache, so a repeated message field and its clone differed and an `+k8s:immutable` repeated field (`Actor.existing_volumes`, row below) failed every `CreateActor` with "field is immutable" | [#234](https://github.com/giantswarm/substrate/pull/234) (`fix(resources): compare repeated message fields with proto.Equal`) | none: upstream `main` has the same `DeepEqual`; to file, queued in the upstream engagement list | +| An existing volume mounted per actor at a sub-path: an ActorTemplate declares it (`Volume.existing_volume`, field 10001, an empty marker), `CreateActor` supplies it (`Actor.existing_volumes`, field 10001, immutable; `ExistingVolume` = name, CSI driver, volume handle, access mode `READ_WRITE_MANY`/`READ_ONLY_MANY` in the shape of upstream's `VolumeAccessMode`, and the `sub_path` the actor sees as the volume's root), and `VolumeMount` gains `sub_path` and `read_only` (fields 10001, 10002), so one volume mounts at several paths. `CreateActor` refuses a reference to a volume the template does not declare as existing, a driver without a `CSIDriverConfig`, a handle no PersistentVolume of the driver holds and an access mode the PersistentVolume does not permit (ate-api-server reads PersistentVolumes: a new ClusterRole rule); the PersistentVolume's `volumeAttributes` reach the driver at resume. An unsupplied existing volume contributes neither itself nor its mounts. Substrate never creates, deletes or detaches one: pause, resume and delete only unmount and mount it; a multi-node volume is staged per target, and atelet binds each sub-path from a descriptor opened beneath the volume's root without following symbolic links (`openat2` `RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`), so a missing directory or a planted link fails the mount. An actor with existing volumes boots from its image instead of the golden snapshot and cannot be cloned from a tag. Tests: unit (validation, workload spec, CSI staging, the sub-path resolver), the `existingvolumes` e2e on kind with the CSI NFS driver (`E2E_CSI_NFS=1` in pr-workflow) | Sessions on one workspace each need their own directory of one read-write-many volume read-write and its git mirrors read-only, hundreds a day; a per-actor volume or snapshot per Session does not scale, and an external volume was created per actor, attached single-node-writer and deleted with it, with no sub-path or read-only mount | [#234](https://github.com/giantswarm/substrate/pull/234) (`feat: mount an existing volume per actor at a sub-path`) | follows agent-substrate/substrate#1637 (shared volumes across actors, open), in the shape proposed there (https://github.com/agent-substrate/substrate/issues/1637#issuecomment-6083442678), on top of #1988's access modes (open); upstream puts it behind the Preview gate of #1994 (open), which this line does not carry, so it is ungated here. [#37742](https://github.com/giantswarm/giantswarm/issues/37742) row 157. Exit: drops at the re-pin that carries #1637's API | Twenty-three patches change Substrate ahead of upstream — egress for an actor while it resumes, without which no skill-carrying agent of the platform boots, the atelet scheduling knobs, the keep policy on the CRD chart's