api: remove the legacy actor_template fields and resolve templates from the store only

Remove actor_template_namespace/actor_template_name and
ActorAssignment.actor_template from the public API now that every
caller addresses templates by the actor_template ref. controlapi
resolves actor templates from the store only, dropping the CRD
conversion fallback, and its test fixtures are built as substrate
protos. Update the remaining readers of the removed fields (atenet
router, kubectl-ate, demos UI, benchmarking, docs, apitool exemptions)
and fix the affected functional tests and the Create actor script.
This commit is contained in:
zoezhao
2026-08-31 17:45:53 -07:00
parent 1d5d8f066f
commit e502cf60e3
49 changed files with 1000 additions and 1952 deletions
File diff suppressed because one or more lines are too long
@@ -402,10 +402,9 @@ func seedActor(t *testing.T, ctx context.Context, st store.Interface, f actorFix
actorRef := resources.ActorRef{Atespace: testAtespace, Name: testActorName}
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name},
Status: &ateapipb.ActorStatus{State: f.state},
ActorTemplateNamespace: "ate-demo",
ActorTemplateName: "counter",
Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: actorRef.Name},
Status: &ateapipb.ActorStatus{State: f.state},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ate-demo", Name: "counter"},
}
if !f.noPlacement {
workerName := testWorkerName
+1 -1
View File
@@ -71,7 +71,7 @@ func (s *ServiceImpl) CreateActor(ctx context.Context, inActor *ateapipb.Actor)
// will still exist later. Checking it here produces a nice error UX, but
// we still have to handle the template not existing later, which makes the
// UX inconsistent, at best. Is it actually worth checking at all?
template, err := resolveActorTemplate(ctx, s.store, s.actorTemplateLister, inActor)
template, err := resolveActorTemplate(ctx, s.store, inActor)
if err != nil {
return nil, err
}
@@ -17,9 +17,7 @@ package controlapi
import (
"testing"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
)
// TestActorResourceLimits covers the actor-side extraction: the CPU/memory limits
@@ -27,50 +25,37 @@ import (
func TestActorResourceLimits(t *testing.T) {
tests := []struct {
name string
res *corev1.ResourceRequirements
resources *ateapipb.Resources
wantCPU int64
wantMemory int64
}{
{
name: "nil resources yields zero",
res: nil,
resources: nil,
wantCPU: 0,
wantMemory: 0,
},
{
name: "cpu and memory limits are read",
res: &corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
corev1.ResourceMemory: resource.MustParse("4Gi"),
},
},
resources: &ateapipb.Resources{Limits: []*ateapipb.Limits{
{Name: "cpu", Quantity: "2"},
{Name: "memory", Quantity: "4Gi"},
}},
wantCPU: 2000,
wantMemory: 4 << 30,
},
{
name: "millicpu is preserved",
res: &corev1.ResourceRequirements{
Limits: corev1.ResourceList{corev1.ResourceCPU: resource.MustParse("1500m")},
},
resources: &ateapipb.Resources{Limits: []*ateapipb.Limits{
{Name: "cpu", Quantity: "1500m"},
}},
wantCPU: 1500,
wantMemory: 0,
},
{
name: "requests are ignored; only limits size the actor",
res: &corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1"),
corev1.ResourceMemory: resource.MustParse("1Gi"),
},
},
wantCPU: 0,
wantMemory: 0,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{Resources: tc.res}})
tmpl := &ateapipb.ActorTemplate{Resources: tc.resources}
cpu, mem, err := actorResourceLimits(tmpl)
if err != nil {
t.Fatalf("actorResourceLimits() error: %v", err)
@@ -279,3 +279,39 @@ func (s *ServiceImpl) UpdateActorTemplate(ctx context.Context, templateRef resou
// TODO: implement this
return s.store.UpdateActorTemplate(ctx, templateRef, precondition, mutate)
}
// actorTemplateGetter is the storage subset template resolution needs.
type actorTemplateGetter interface {
GetActorTemplate(ctx context.Context, templateRef resources.ActorTemplateRef) (*ateapipb.ActorTemplate, error)
}
// errActorTemplateNotFound matches (via errors.Is) resolution failures where
// the actor names a template that does not exist. Most callers return the
// error as is — it already carries FailedPrecondition — while delete
// tolerates it and cleans up without the template.
var errActorTemplateNotFound = status.New(codes.FailedPrecondition, "actor template not found").Err()
// resolveActorTemplate resolves the substrate ActorTemplate the actor's
// actor_template ref names. A missing template surfaces as
// errActorTemplateNotFound.
func resolveActorTemplate(ctx context.Context, st actorTemplateGetter, actor *ateapipb.Actor) (*ateapipb.ActorTemplate, error) {
templateRef := resources.ActorTemplateRefFromObjectRef(actor.GetActorTemplate())
template, err := st.GetActorTemplate(ctx, templateRef)
if errors.Is(err, store.ErrNotFound) {
return nil, fmt.Errorf("%w; ObjectRef: %s ", errActorTemplateNotFound, templateRef)
}
if err != nil {
return nil, fmt.Errorf("while getting ActorTemplate: %w", err)
}
return template, nil
}
// actorTemplateObjectRef returns a fresh copy of the actor's template
// reference — fresh so records built from it never alias the actor message.
func actorTemplateObjectRef(actor *ateapipb.Actor) *ateapipb.ObjectRef {
ref := actor.GetActorTemplate()
if ref == nil {
return nil
}
return &ateapipb.ObjectRef{Atespace: ref.GetAtespace(), Name: ref.GetName()}
}
@@ -16,8 +16,10 @@ package controlapi
import (
"context"
"errors"
"testing"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/codes"
@@ -334,3 +336,101 @@ func TestValidateDeleteActorTemplateRequest(t *testing.T) {
})
}
}
// seedSubstrateTemplate stores a minimal substrate ActorTemplate in team-a.
func seedSubstrateTemplate(t *testing.T, ctx context.Context, persistence store.Interface, name string) *ateapipb.ActorTemplate {
t.Helper()
stored, err := persistence.CreateActorTemplate(ctx, &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: name},
SnapshotsConfig: &ateapipb.SnapshotsConfig{
StorageLocation: "gs://ate-snapshots/team-a/",
},
SandboxConfig: &ateapipb.SandboxConfig{
SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR,
ConfigName: "gvisor",
},
})
if err != nil {
t.Fatalf("CreateActorTemplate: %v", err)
}
return stored
}
// TestResolveActorTemplate verifies the resolver reads the substrate resource
// the actor's actor_template reference names.
func TestResolveActorTemplate(t *testing.T) {
ctx := context.Background()
persistence := newTestPersistence(t)
stored := seedSubstrateTemplate(t, ctx, persistence, "sub-tmpl")
t.Run("ref reads the store", func(t *testing.T) {
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "sub-tmpl"}}
got, err := resolveActorTemplate(ctx, persistence, actor)
if err != nil {
t.Fatalf("resolveActorTemplate: %v", err)
}
if got.GetMetadata().GetUid() != stored.GetMetadata().GetUid() {
t.Errorf("template uid = %q, want the stored substrate template %q", got.GetMetadata().GetUid(), stored.GetMetadata().GetUid())
}
})
t.Run("ref to a missing template is FailedPrecondition", func(t *testing.T) {
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "absent"}}
_, err := resolveActorTemplate(ctx, persistence, actor)
if got := status.Code(err); got != codes.FailedPrecondition {
t.Fatalf("status.Code = %v, want FailedPrecondition (err: %v)", got, err)
}
})
}
// TestResolveActorTemplate_NotFound verifies a vanished template and an actor
// naming no template at all surface errActorTemplateNotFound, so callers like
// delete can tolerate them.
func TestResolveActorTemplate_NotFound(t *testing.T) {
ctx := context.Background()
persistence := newTestPersistence(t)
stored := seedSubstrateTemplate(t, ctx, persistence, "sub-tmpl")
tests := []struct {
name string
actor *ateapipb.Actor
wantNotFound bool
}{
{"ref resolves", &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "sub-tmpl"}}, false},
{"ref to deleted template", &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "gone"}}, true},
{"no template named at all", &ateapipb.Actor{}, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := resolveActorTemplate(ctx, persistence, tc.actor)
if tc.wantNotFound {
if !errors.Is(err, errActorTemplateNotFound) {
t.Fatalf("resolveActorTemplate err = %v, want errActorTemplateNotFound", err)
}
return
}
if err != nil {
t.Fatalf("resolveActorTemplate: %v", err)
}
if got.GetMetadata().GetUid() != stored.GetMetadata().GetUid() {
t.Errorf("template uid = %q, want %q", got.GetMetadata().GetUid(), stored.GetMetadata().GetUid())
}
})
}
}
// TestActorTemplateObjectRef pins that snapshot and assignment records get a
// fresh copy of the reference, never the actor's own message.
func TestActorTemplateObjectRef(t *testing.T) {
if got := actorTemplateObjectRef(&ateapipb.Actor{}); got != nil {
t.Errorf("actorTemplateObjectRef(no ref) = %v, want nil", got)
}
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "tmpl1"}}
got := actorTemplateObjectRef(actor)
if got == actor.GetActorTemplate() {
t.Error("actorTemplateObjectRef aliases the actor's reference")
}
if got.GetAtespace() != "team-a" || got.GetName() != "tmpl1" {
t.Errorf("actorTemplateObjectRef = %v, want team-a/tmpl1", got)
}
}
+10 -6
View File
@@ -89,6 +89,10 @@ func TestValidateCreateActorRequest(t *testing.T) {
"valid actor.actor_template",
validReq(validActor(withActorTemplate("as", "tmpl"))),
nil,
}, {
"missing actor.actor_template",
validReq(validActor(func(a *ateapipb.Actor) { a.ActorTemplate = nil })),
field.ErrorList{field.Required(field.NewPath("actor", "actor_template"), "")},
}, {
"missing actor.actor_template.atespace",
validReq(validActor(withActorTemplate("", "tmpl"))),
@@ -221,15 +225,15 @@ func TestValidateActorUpdate(t *testing.T) {
validOutput(withMetadata(func(m *ateapipb.ResourceMetadata) { m.Name = "invalid value" })),
field.ErrorList{field.Invalid(field.NewPath("metadata", "name"), nil, "").WithOrigin("immutable")},
}, {
"change actor.actor_template",
"change actor.actor_template is allowed",
validInput(withActorTemplate("as1", "nm1")),
validOutput(withActorTemplate("as2", "nm2")),
field.ErrorList{field.Invalid(field.NewPath("actor_template"), nil, "").WithOrigin("immutable")},
nil,
}, {
"clear actor.actor_template",
validInput(withActorTemplate("as", "nm")),
validOutput(func(a *ateapipb.Actor) { a.ActorTemplate = nil }),
field.ErrorList{field.Invalid(field.NewPath("actor_template"), nil, "").WithOrigin("immutable")},
field.ErrorList{field.Required(field.NewPath("actor_template"), "")},
}, {
"add actor.source_snapshot_tag",
validInput(),
@@ -711,7 +715,7 @@ func TestUpdateActor_DeleteRecreateRace(t *testing.T) {
}
},
}
svc := &RPCService{impl: newServiceImpl(racing, nil, nil)}
svc := &RPCService{impl: newServiceImpl(racing, nil)}
// The client asserts "only update the actor with uid A".
original.WorkerSelector = &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}}
@@ -772,7 +776,7 @@ func TestUpdateActor_ConcurrentDisjointUpdates(t *testing.T) {
}
},
}
svc := &RPCService{impl: newServiceImpl(racing, nil, nil)}
svc := &RPCService{impl: newServiceImpl(racing, nil)}
// Update operation is changing the worker_selector field, not the actor's state (like the concurrent op)
// This update must fail: the racing update bumped the version.
@@ -874,7 +878,7 @@ func rpcServiceWithActor(t *testing.T, actor *ateapipb.Actor) (*RPCService, *ate
t.Cleanup(cleanup)
created := storetest.MustCreateActor(t, context.Background(), persistence, actor)
return &RPCService{impl: newServiceImpl(persistence, nil, nil)}, created
return &RPCService{impl: newServiceImpl(persistence, nil)}, created
}
func TestValidateDeleteActorRequest(t *testing.T) {
@@ -20,13 +20,6 @@ import (
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
)
func toActorSnapshotContentScope(in atev1alpha1.SnapshotScope) ateapipb.SnapshotContentScope {
if in == atev1alpha1.SnapshotScopeData {
return ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA
}
return ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL
}
func actorSnapshotContentScopeToAtelet(in ateapipb.SnapshotContentScope) ateletpb.SnapshotScope {
if in == ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA {
return ateletpb.SnapshotScope_SNAPSHOT_SCOPE_DATA
@@ -18,46 +18,39 @@ import (
"testing"
"github.com/agent-substrate/substrate/internal/proto/ateletpb"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
)
// TestSnapshotScopeToAtelet covers the wire scope derivation for CRD scopes
// converted to proto content scopes: unknown and empty CRD scopes fall back
// to Full, matching the CRD's default.
// TestSnapshotScopeToAtelet covers the wire scope derivation for template
// content scopes: an unset scope falls back to Full.
func TestSnapshotScopeToAtelet(t *testing.T) {
tests := []struct {
name string
in atev1alpha1.SnapshotScope
in ateapipb.SnapshotContentScope
expected ateletpb.SnapshotScope
}{
{
name: "Full scope",
in: atev1alpha1.SnapshotScopeFull,
in: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
expected: ateletpb.SnapshotScope_SNAPSHOT_SCOPE_FULL,
},
{
name: "Data scope",
in: atev1alpha1.SnapshotScopeData,
in: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
expected: ateletpb.SnapshotScope_SNAPSHOT_SCOPE_DATA,
},
{
name: "Default scope (empty)",
in: "",
expected: ateletpb.SnapshotScope_SNAPSHOT_SCOPE_FULL,
},
{
name: "Default scope (unknown)",
in: "unknown",
name: "Default scope (unspecified)",
in: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED,
expected: ateletpb.SnapshotScope_SNAPSHOT_SCOPE_FULL,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := actorSnapshotContentScopeToAtelet(toActorSnapshotContentScope(tt.in))
result := actorSnapshotContentScopeToAtelet(tt.in)
if result != tt.expected {
t.Errorf("actorSnapshotContentScopeToAtelet(toActorSnapshotContentScope(%q)) = %v, want %v", tt.in, result, tt.expected)
t.Errorf("actorSnapshotContentScopeToAtelet(%v) = %v, want %v", tt.in, result, tt.expected)
}
})
}
@@ -669,14 +669,14 @@ func TestUpdateActor_Success(t *testing.T) {
// TestUpdateActor verifies a typical RMW UpdateActor flow: a
// client reads an actor, modifies it and send an UpdateActor request.
// Output-only fields it sets are ignored, and changes to immutable fields are
// rejected.
// Output-only fields it sets are ignored, and the mutable actor_template ref
// can repoint the actor at another template.
func TestUpdateActor(t *testing.T) {
ns := namespaceForTest("ns-update-replace")
tc := setupTest(t, ns)
defer tc.cleanup()
createTemplate(t, tc, ns)
tmpl := createTemplate(t, tc, ns)
created, err := tc.client.CreateActor(context.Background(), &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "id1"},
@@ -710,12 +710,24 @@ func TestUpdateActor(t *testing.T) {
t.Errorf("UpdateActor response mismatch (-want +got):\n%s", diff)
}
// Immutable field
updatedActor.ActorTemplate = &ateapipb.ObjectRef{Name: "new-templace", Atespace: "atespace"}
_, err = tc.client.UpdateActor(context.Background(), &ateapipb.UpdateActorRequest{
Actor: updatedActor,
})
assertGrpcErrorRegex(t, err, codes.InvalidArgument, "actor.actor_template: Invalid value: null: field is immutable")
// Mutable template ref: repoint the actor at a second template.
tmpl2 := proto.Clone(tmpl).(*ateapipb.ActorTemplate)
tmpl2.Metadata = &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "tmpl2"}
tmpl2.Status = nil
if _, err := tc.client.CreateActorTemplate(context.Background(), &ateapipb.CreateActorTemplateRequest{ActorTemplate: tmpl2}); err != nil {
t.Fatalf("CreateActorTemplate failed: %v", err)
}
updatedActor.ActorTemplate = &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl2"}
repointed, err := tc.client.UpdateActor(context.Background(), &ateapipb.UpdateActorRequest{Actor: updatedActor})
if err != nil {
t.Fatalf("UpdateActor failed: %v", err)
}
wantActor.Metadata.Version = 3
wantActor.ActorTemplate = &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl2"}
if diff := cmp.Diff(wantActor, repointed, protocmp.Transform(), ignoreUID, ignoreTimestamps); diff != "" {
t.Errorf("UpdateActor response mismatch after template repoint (-want +got):\n%s", diff)
}
}
// TestUpdateActor_Preconditions verifies the required version and uid guards
@@ -127,7 +127,6 @@ func setupTestWithVolumePlugins(t *testing.T, ns string, plugins map[string]volu
scLister := scFactory.Storage().V1().StorageClasses().Lister()
substrateInformerFactory := externalversions.NewSharedInformerFactory(substrateClient, 0)
actorTemplateLister := substrateInformerFactory.Api().V1alpha1().ActorTemplates().Lister()
workerPoolLister := substrateInformerFactory.Api().V1alpha1().WorkerPools().Lister()
sandboxConfigLister := substrateInformerFactory.Api().V1alpha1().SandboxConfigs().Lister()
csiDriverConfigLister := substrateInformerFactory.Api().V1alpha1().CSIDriverConfigs().Lister()
@@ -177,7 +176,7 @@ func setupTestWithVolumePlugins(t *testing.T, ns string, plugins map[string]volu
mockDriverName: mockPlugin,
}
}
service := controlapi.NewRPCService(persistence, wc, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, scLister, dialer, instruments, "", volPlugins)
service := controlapi.NewRPCService(persistence, wc, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, scLister, dialer, instruments, "", volPlugins)
// 5. Start REAL gRPC Server for ATE API
grpcServer := grpc.NewServer(grpc.ChainUnaryInterceptor(
@@ -213,9 +213,9 @@ func TestLifecycleOpDurationShape(t *testing.T) {
WorkerAssignment: &ateapipb.WorkerAssignment{WorkerNamespace: "ate-workers", WorkerPool: "pool-a"},
},
}
template := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor},
})
template := &ateapipb.ActorTemplate{
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR},
}
inst.recordLifecycleOp(context.Background(), ateattr.OperationResume, time.Now(), nil,
lifecycleOpAttrs(actor, template, ateattr.SnapshotKindLatest, ateattr.SnapshotScopeDataOnGolden)...)
+5 -9
View File
@@ -62,7 +62,6 @@ type VolumePluginRegistry interface {
func NewRPCService(
persistence store.Interface,
workerCache *workercache.Cache,
actorTemplateLister listersv1alpha1.ActorTemplateLister,
workerPoolLister listersv1alpha1.WorkerPoolLister,
sandboxConfigLister listersv1alpha1.SandboxConfigLister,
csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister,
@@ -72,7 +71,7 @@ func NewRPCService(
egressGatewayAddress string,
volumePlugins map[string]volume.VolumePluginControlPlane,
) *RPCService {
impl := newServiceImpl(persistence, actorTemplateLister, storageClassLister)
impl := newServiceImpl(persistence, storageClassLister)
s := &RPCService{
impl: impl,
persistence: persistence,
@@ -83,7 +82,7 @@ func NewRPCService(
instruments: instruments,
volumePlugins: volumePlugins,
}
s.actorWorkflow = NewActorWorkflow(impl, workerCache, dialer, actorTemplateLister, workerPoolLister, sandboxConfigLister, storageClassLister, instruments, egressGatewayAddress, s)
s.actorWorkflow = NewActorWorkflow(impl, workerCache, dialer, workerPoolLister, sandboxConfigLister, storageClassLister, instruments, egressGatewayAddress, s)
s.workerWorkflow = NewWorkerWorkflow(impl)
return s
}
@@ -150,8 +149,7 @@ type ServiceImpl struct {
// methods we need to trap.
store store.Interface
actorTemplateLister listersv1alpha1.ActorTemplateLister
storageClassLister storagev1listers.StorageClassLister
storageClassLister storagev1listers.StorageClassLister
}
var _ store.Interface = (*ServiceImpl)(nil)
@@ -160,13 +158,11 @@ var _ store.Interface = (*ServiceImpl)(nil)
// implementation layer.
func newServiceImpl(
persistence store.Interface,
actorTemplateLister listersv1alpha1.ActorTemplateLister,
storageClassLister storagev1listers.StorageClassLister,
) *ServiceImpl {
s := &ServiceImpl{
store: persistence,
actorTemplateLister: actorTemplateLister,
storageClassLister: storageClassLister,
store: persistence,
storageClassLister: storageClassLister,
}
return s
}
@@ -1,309 +0,0 @@
// 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"
"errors"
"fmt"
"maps"
"sort"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/internal/resources"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// actorTemplateGetter is the storage subset template resolution needs.
type actorTemplateGetter interface {
GetActorTemplate(ctx context.Context, templateRef resources.ActorTemplateRef) (*ateapipb.ActorTemplate, error)
}
// errActorTemplateNotFound matches (via errors.Is) resolution failures where
// the actor names a template that does not exist. Most callers return the
// error as is — it already carries FailedPrecondition — while delete
// tolerates it and cleans up without the template.
var errActorTemplateNotFound = status.New(codes.FailedPrecondition, "actor template not found").Err()
// resolveActorTemplate resolves an actor's template from whichever reference
// form it carries: the substrate ActorTemplate resource when actor_template
// is set, the ActorTemplate CRD otherwise. A missing template surfaces as a
// templateNotFoundError either way.
func resolveActorTemplate(ctx context.Context, st actorTemplateGetter, lister listersv1alpha1.ActorTemplateLister, actor *ateapipb.Actor) (*ateapipb.ActorTemplate, error) {
if ref := actor.GetActorTemplate(); ref != nil {
templateRef := resources.ActorTemplateRefFromObjectRef(ref)
template, err := st.GetActorTemplate(ctx, templateRef)
if errors.Is(err, store.ErrNotFound) {
return nil, fmt.Errorf("%w; ObjectRef: %s ", errActorTemplateNotFound, templateRef)
}
if err != nil {
return nil, fmt.Errorf("while getting ActorTemplate: %w", err)
}
return template, nil
}
// TODO: remove this fallback when we cut over to substrate resources.
crd, err := lister.ActorTemplates(actor.GetActorTemplateNamespace()).Get(actor.GetActorTemplateName())
if err != nil {
if k8serrors.IsNotFound(err) {
return nil, fmt.Errorf("%w; CRD %s/%s ", errActorTemplateNotFound, actor.GetActorTemplateNamespace(), actor.GetActorTemplateName())
}
return nil, fmt.Errorf("while getting ActorTemplate: %w", err)
}
return actorTemplateFromCRD(crd)
}
// actorTemplateObjectRef returns a fresh copy of the actor's substrate
// template reference, or nil for CRD-backed actors — fresh so records built
// from it never alias the actor message.
func actorTemplateObjectRef(actor *ateapipb.Actor) *ateapipb.ObjectRef {
ref := actor.GetActorTemplate()
if ref == nil {
return nil
}
return &ateapipb.ObjectRef{Atespace: ref.GetAtespace(), Name: ref.GetName()}
}
// actorTemplateFromCRD projects an ActorTemplate CRD onto the substrate
// ActorTemplate proto, so the workflows handle a single template type while
// both the CRD and the substrate resource exist during the migration.
// It refuses a template whose workerSelector uses matchExpressions: the
// substrate Selector supports equality matching only, and dropping the
// expressions would schedule actors onto a wider pool set than declared.
func actorTemplateFromCRD(t *atev1alpha1.ActorTemplate) (*ateapipb.ActorTemplate, error) {
if t == nil {
return nil, status.Error(codes.Internal, "nil ActorTemplate")
}
if sel := t.Spec.WorkerSelector; sel != nil && len(sel.MatchExpressions) > 0 {
return nil, status.Errorf(codes.FailedPrecondition,
"ActorTemplate %s/%s workerSelector uses matchExpressions; only matchLabels is supported",
t.Namespace, t.Name)
}
out := &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{
// Using the t.Namespace as Atespace is ok because this is used in memory only,
// never persists to store.
Atespace: t.Namespace,
Name: t.Name,
Uid: string(t.GetUID()),
},
WorkerSelector: selectorFromLabelSelector(t.Spec.WorkerSelector),
SnapshotsConfig: &ateapipb.SnapshotsConfig{
StorageLocation: t.Spec.SnapshotsConfig.Location,
OnPause: toActorSnapshotContentScope(t.Spec.SnapshotsConfig.OnPause),
OnCommit: toActorSnapshotContentScope(t.Spec.SnapshotsConfig.OnCommit),
OnResume: &ateapipb.OnResumeConfig{
FromData: resumeSourceFromCRD(t.Spec.SnapshotsConfig.OnResume.FromData),
},
},
// The CRD names no SandboxConfig object: CRD-backed actors resolve
// sandbox assets from their worker pool instead.
SandboxConfig: &ateapipb.SandboxConfig{
SandboxClass: sandboxClassFromCRD(t.Spec.SandboxClass),
},
Resources: resourcesFromCRD(t.Spec.Resources),
Status: templateStatusFromCRD(t.Status),
}
for i := range t.Spec.Containers {
out.Containers = append(out.Containers, containerFromCRD(&t.Spec.Containers[i]))
}
for i := range t.Spec.Volumes {
out.Volumes = append(out.Volumes, volumeFromCRD(&t.Spec.Volumes[i]))
}
return out, nil
}
// selectorFromLabelSelector converts the equality half of the CRD selector;
// actorTemplateFromCRD has already rejected any matchExpressions.
func selectorFromLabelSelector(sel *metav1.LabelSelector) *ateapipb.Selector {
if sel == nil {
return nil
}
out := &ateapipb.Selector{}
if len(sel.MatchLabels) > 0 {
out.MatchLabels = maps.Clone(sel.MatchLabels)
}
return out
}
func resumeSourceFromCRD(in atev1alpha1.ResumeSource) ateapipb.ResumeSource {
// Unset defaults to ColdBoot, mirroring the CRD's kubebuilder default.
if in == atev1alpha1.ResumeSourceGolden {
return ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN
}
return ateapipb.ResumeSource_RESUME_SOURCE_COLD_BOOT
}
func sandboxClassFromCRD(in atev1alpha1.SandboxClass) ateapipb.SandboxClass {
switch in {
case atev1alpha1.SandboxClassGvisor:
return ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR
case atev1alpha1.SandboxClassMicroVM:
return ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM
default:
return ateapipb.SandboxClass_SANDBOX_CLASS_UNSPECIFIED
}
}
func resourcesFromCRD(in *corev1.ResourceRequirements) *ateapipb.Resources {
if in == nil {
return nil
}
return limitsFromCRD(in.Limits)
}
func limitsFromCRD(limits map[corev1.ResourceName]resource.Quantity) *ateapipb.Resources {
if len(limits) == 0 {
return nil
}
names := make([]string, 0, len(limits))
for name := range limits {
names = append(names, string(name))
}
// Map iteration is unordered; sort for a deterministic proto.
sort.Strings(names)
out := &ateapipb.Resources{}
for _, name := range names {
q := limits[corev1.ResourceName(name)]
out.Limits = append(out.Limits, &ateapipb.Limits{Name: name, Quantity: q.String()})
}
return out
}
func containerFromCRD(c *atev1alpha1.Container) *ateapipb.Container {
out := &ateapipb.Container{
Name: c.Name,
Image: c.Image,
Command: append([]string(nil), c.Command...),
Args: append([]string(nil), c.Args...),
}
for _, env := range c.Env {
out.Env = append(out.Env, &ateapipb.EnvVar{Name: env.Name, Value: env.Value})
}
if r := c.Readyz; r != nil {
out.Readyz = &ateapipb.ContainerReadyz{TimeoutSeconds: r.TimeoutSeconds}
if r.HTTPGet != nil {
out.Readyz.HttpGet = &ateapipb.HTTPGetAction{Path: r.HTTPGet.Path, Port: r.HTTPGet.Port}
}
}
for _, m := range c.VolumeMounts {
out.VolumeMounts = append(out.VolumeMounts, &ateapipb.VolumeMount{Name: m.Name, MountPath: m.MountPath})
}
if sc := c.SecurityContext; sc != nil && sc.Capabilities != nil {
out.SecurityContext = &ateapipb.SecurityContext{
Capabilities: &ateapipb.Capabilities{
Add: capabilityNames(sc.Capabilities.Add),
Drop: capabilityNames(sc.Capabilities.Drop),
},
}
}
if r := c.Resources; r != nil {
out.Resources = limitsFromCRD(r.Limits)
}
return out
}
func capabilityNames(in []atev1alpha1.Capability) []string {
if len(in) == 0 {
return nil
}
out := make([]string, 0, len(in))
for _, c := range in {
out = append(out, string(c))
}
return out
}
func volumeFromCRD(v *atev1alpha1.Volume) *ateapipb.Volume {
out := &ateapipb.Volume{Name: v.Name}
switch {
case v.DurableDir != nil:
out.Type = "DurableDir"
out.DurableDir = &ateapipb.DurableDirVolumeSource{}
case v.ExternalVolumeTemplate != nil:
out.Type = "ExternalVolumeTemplate"
out.ExternalVolumeTemplate = &ateapipb.ExternalVolumeTemplate{
Capacity: v.ExternalVolumeTemplate.Capacity.String(),
StorageClassName: v.ExternalVolumeTemplate.StorageClassName,
}
case v.Image != nil:
out.Type = "Image"
out.Image = &ateapipb.ImageVolumeSource{Reference: v.Image.Reference}
case v.SystemInfo != nil:
out.Type = "SystemInfo"
out.SystemInfo = systemInfoFromCRD(v.SystemInfo)
}
return out
}
func systemInfoFromCRD(in *atev1alpha1.SystemInfoVolumeSource) *ateapipb.SystemInfoVolumeSource {
out := &ateapipb.SystemInfoVolumeSource{}
for _, ds := range in.DataSources {
switch {
case ds.ActorMetadata != nil:
meta := &ateapipb.ActorMetadataDataSource{}
for _, item := range ds.ActorMetadata.Items {
meta.Items = append(meta.Items, &ateapipb.ActorMetadataItem{
Field: actorMetadataFieldFromCRD(item.Field),
Path: item.Path,
})
}
out.DataSources = append(out.DataSources, &ateapipb.SystemInfoDataSource{ActorMetadata: meta})
case ds.TrustBundle != nil:
out.DataSources = append(out.DataSources, &ateapipb.SystemInfoDataSource{
TrustBundle: &ateapipb.TrustBundleDataSource{Name: ds.TrustBundle.Name, Path: ds.TrustBundle.Path},
})
}
}
return out
}
func actorMetadataFieldFromCRD(in atev1alpha1.ActorMetadataField) ateapipb.ActorMetadataField {
switch in {
case atev1alpha1.ActorMetadataFieldName:
return ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_NAME
case atev1alpha1.ActorMetadataFieldAtespace:
return ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_ATESPACE
case atev1alpha1.ActorMetadataFieldUID:
return ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_UID
default:
return ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_UNSPECIFIED
}
}
// templateStatusFromCRD maps only what the substrate proto models: the CRD's
// phase machinery belongs to the CRD controller, while converted templates
// are consumed by the actor workflows, which need the golden snapshot ref.
func templateStatusFromCRD(in atev1alpha1.ActorTemplateStatus) *ateapipb.ActorTemplateStatus {
out := &ateapipb.ActorTemplateStatus{}
if in.GoldenSnapshot != "" {
// The CRD stores only the snapshot name; golden snapshots always live
// in the reserved golden atespace.
out.GoldenSnapshotStatus = &ateapipb.GoldenSnapshotStatus{
GoldenSnapshot: &ateapipb.ObjectRef{
Atespace: resources.GoldenActorAtespace,
Name: in.GoldenSnapshot,
},
}
}
return out
}
@@ -1,361 +0,0 @@
// 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"
"errors"
"testing"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/internal/resources"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"github.com/google/go-cmp/cmp"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/testing/protocmp"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
)
// TestActorTemplateFromCRD pins the full CRD-to-proto projection: every field
// the workflows consume must survive the conversion, since both template
// sources share the proto code path during the migration.
func TestActorTemplateFromCRD(t *testing.T) {
crd := &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ate-demo-counter-microvm-csi",
Name: "counter-microvm-csi",
UID: types.UID("9a1b6f9e-6a3f-4a3e-9a51-0c2f3a34d001"),
},
Spec: atev1alpha1.ActorTemplateSpec{
SandboxClass: atev1alpha1.SandboxClassMicroVM,
WorkerSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{"workload": "counter-microvm-csi"},
},
Containers: []atev1alpha1.Container{{
Name: "counter",
Image: "ko://github.com/agent-substrate/substrate/demos/counter",
Command: []string{"/ko-app/counter"},
Args: []string{"--port=80"},
Env: []atev1alpha1.EnvVar{{Name: "COUNTER_DATA_DIR", Value: "/home/counter"}},
Readyz: &atev1alpha1.ContainerReadyz{
HTTPGet: &atev1alpha1.HTTPGetAction{Path: "/readyz", Port: 80},
TimeoutSeconds: 30,
},
VolumeMounts: []atev1alpha1.VolumeMount{{Name: "data", MountPath: "/home/counter"}},
SecurityContext: &atev1alpha1.SecurityContext{
Capabilities: &atev1alpha1.Capabilities{
Add: []atev1alpha1.Capability{"NET_BIND_SERVICE"},
Drop: []atev1alpha1.Capability{"ALL"},
},
},
}},
Volumes: []atev1alpha1.Volume{
{Name: "durable", VolumeSource: atev1alpha1.VolumeSource{DurableDir: &atev1alpha1.DurableDirVolumeSource{}}},
{Name: "data", VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
Capacity: resource.MustParse("1Gi"),
StorageClassName: "csi-hostpath-sc",
},
}},
{Name: "model", VolumeSource: atev1alpha1.VolumeSource{
Image: &atev1alpha1.ImageVolumeSource{Reference: "ko://github.com/agent-substrate/substrate/demos/counter"},
}},
{Name: "system", VolumeSource: atev1alpha1.VolumeSource{
SystemInfo: &atev1alpha1.SystemInfoVolumeSource{
DataSources: []atev1alpha1.SystemInfoDataSource{
{ActorMetadata: &atev1alpha1.ActorMetadataDataSource{Items: []atev1alpha1.ActorMetadataItem{
{Field: atev1alpha1.ActorMetadataFieldName, Path: "actor/name"},
{Field: atev1alpha1.ActorMetadataFieldAtespace, Path: "actor/atespace"},
{Field: atev1alpha1.ActorMetadataFieldUID, Path: "actor/uid"},
}}},
{TrustBundle: &atev1alpha1.TrustBundleDataSource{Name: "egress-mitm.ate.dev", Path: "tls/egress-ca.pem"}},
},
},
}},
},
SnapshotsConfig: atev1alpha1.SnapshotsConfig{
Location: "gs://ate-snapshots/ate-demo-counter-microvm-csi/",
OnPause: atev1alpha1.SnapshotScopeFull,
OnCommit: atev1alpha1.SnapshotScopeData,
OnResume: atev1alpha1.OnResumeConfig{FromData: atev1alpha1.ResumeSourceGolden},
},
Resources: &corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1500m"),
corev1.ResourceMemory: resource.MustParse("512Mi"),
},
},
},
Status: atev1alpha1.ActorTemplateStatus{
Phase: atev1alpha1.PhaseReady,
GoldenSnapshot: "2026-01-01t00-00-00z-abc",
},
}
want := &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{
Atespace: "ate-demo-counter-microvm-csi",
Name: "counter-microvm-csi",
Uid: "9a1b6f9e-6a3f-4a3e-9a51-0c2f3a34d001",
},
WorkerSelector: &ateapipb.Selector{
MatchLabels: map[string]string{"workload": "counter-microvm-csi"},
},
Containers: []*ateapipb.Container{{
Name: "counter",
Image: "ko://github.com/agent-substrate/substrate/demos/counter",
Command: []string{"/ko-app/counter"},
Args: []string{"--port=80"},
Env: []*ateapipb.EnvVar{{Name: "COUNTER_DATA_DIR", Value: "/home/counter"}},
Readyz: &ateapipb.ContainerReadyz{
HttpGet: &ateapipb.HTTPGetAction{Path: "/readyz", Port: 80},
TimeoutSeconds: 30,
},
VolumeMounts: []*ateapipb.VolumeMount{{Name: "data", MountPath: "/home/counter"}},
SecurityContext: &ateapipb.SecurityContext{
Capabilities: &ateapipb.Capabilities{Add: []string{"NET_BIND_SERVICE"}, Drop: []string{"ALL"}},
},
}},
Volumes: []*ateapipb.Volume{
{Name: "durable", Type: "DurableDir", DurableDir: &ateapipb.DurableDirVolumeSource{}},
{Name: "data", Type: "ExternalVolumeTemplate", ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
Capacity: "1Gi",
StorageClassName: "csi-hostpath-sc",
}},
{Name: "model", Type: "Image", Image: &ateapipb.ImageVolumeSource{Reference: "ko://github.com/agent-substrate/substrate/demos/counter"}},
{Name: "system", Type: "SystemInfo", SystemInfo: &ateapipb.SystemInfoVolumeSource{
DataSources: []*ateapipb.SystemInfoDataSource{
{ActorMetadata: &ateapipb.ActorMetadataDataSource{Items: []*ateapipb.ActorMetadataItem{
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_NAME, Path: "actor/name"},
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_ATESPACE, Path: "actor/atespace"},
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_UID, Path: "actor/uid"},
}}},
{TrustBundle: &ateapipb.TrustBundleDataSource{Name: "egress-mitm.ate.dev", Path: "tls/egress-ca.pem"}},
},
}},
},
SnapshotsConfig: &ateapipb.SnapshotsConfig{
StorageLocation: "gs://ate-snapshots/ate-demo-counter-microvm-csi/",
OnPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
OnCommit: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
OnResume: &ateapipb.OnResumeConfig{FromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN},
},
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM},
Resources: &ateapipb.Resources{Limits: []*ateapipb.Limits{
{Name: "cpu", Quantity: "1500m"},
{Name: "memory", Quantity: "512Mi"},
}},
Status: &ateapipb.ActorTemplateStatus{
GoldenSnapshotStatus: &ateapipb.GoldenSnapshotStatus{
GoldenSnapshot: &ateapipb.ObjectRef{
Atespace: resources.GoldenActorAtespace,
Name: "2026-01-01t00-00-00z-abc",
},
},
},
}
got := mustTemplateFromCRD(crd)
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("actorTemplateFromCRD mismatch (-want +got):\n%s", diff)
}
}
// TestActorTemplateFromCRD_Defaults pins the conversion of a minimal CRD:
// unset scopes normalize to the CRD defaults (Full / ColdBoot), and no
// golden snapshot yields an empty status.
func TestActorTemplateFromCRD_Defaults(t *testing.T) {
got := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1", Name: "tmpl1"},
})
want := &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "ns1", Name: "tmpl1"},
SnapshotsConfig: &ateapipb.SnapshotsConfig{
OnPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
OnCommit: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
OnResume: &ateapipb.OnResumeConfig{FromData: ateapipb.ResumeSource_RESUME_SOURCE_COLD_BOOT},
},
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_UNSPECIFIED},
Status: &ateapipb.ActorTemplateStatus{},
}
if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
t.Errorf("actorTemplateFromCRD mismatch (-want +got):\n%s", diff)
}
if _, err := actorTemplateFromCRD(nil); status.Code(err) != codes.Internal {
t.Errorf("actorTemplateFromCRD(nil) error = %v, want Internal", err)
}
}
// TestActorTemplateFromCRD_RejectsMatchExpressions pins the equality-only
// selector contract: conversion refuses a template with matchExpressions
// rather than dropping them and scheduling onto a wider pool set.
func TestActorTemplateFromCRD_RejectsMatchExpressions(t *testing.T) {
_, err := actorTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns1", Name: "tmpl1"},
Spec: atev1alpha1.ActorTemplateSpec{
WorkerSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{Key: "tier", Operator: metav1.LabelSelectorOpIn, Values: []string{"1"}},
},
},
},
})
if status.Code(err) != codes.FailedPrecondition {
t.Fatalf("actorTemplateFromCRD error = %v, want FailedPrecondition", err)
}
}
// mustTemplateFromCRD converts a fixture that must be convertible; rejection
// paths call actorTemplateFromCRD directly.
func mustTemplateFromCRD(crd *atev1alpha1.ActorTemplate) *ateapipb.ActorTemplate {
out, err := actorTemplateFromCRD(crd)
if err != nil {
panic(err)
}
return out
}
// seedSubstrateTemplate stores a minimal substrate ActorTemplate in team-a.
func seedSubstrateTemplate(t *testing.T, ctx context.Context, persistence store.Interface, name string) *ateapipb.ActorTemplate {
t.Helper()
stored, err := persistence.CreateActorTemplate(ctx, &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: name},
SnapshotsConfig: &ateapipb.SnapshotsConfig{
StorageLocation: "gs://ate-snapshots/team-a/",
},
SandboxConfig: &ateapipb.SandboxConfig{
SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR,
ConfigName: "gvisor",
},
})
if err != nil {
t.Fatalf("CreateActorTemplate: %v", err)
}
return stored
}
// TestResolveActorTemplate_PrefersSubstrateRef verifies the resolver reads
// the substrate resource when the actor carries an actor_template reference,
// and falls back to the converted CRD for legacy actors.
func TestResolveActorTemplate_PrefersSubstrateRef(t *testing.T) {
ctx := context.Background()
persistence := newTestPersistence(t)
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
if err := indexer.Add(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "crd-tmpl", UID: types.UID("crd-uid-1")},
}); err != nil {
t.Fatalf("add template to indexer: %v", err)
}
lister := listersv1alpha1.NewActorTemplateLister(indexer)
stored := seedSubstrateTemplate(t, ctx, persistence, "sub-tmpl")
t.Run("ref mode reads the store", func(t *testing.T) {
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "sub-tmpl"}}
got, err := resolveActorTemplate(ctx, persistence, lister, actor)
if err != nil {
t.Fatalf("resolveActorTemplate: %v", err)
}
if got.GetMetadata().GetUid() != stored.GetMetadata().GetUid() {
t.Errorf("template uid = %q, want the stored substrate template %q", got.GetMetadata().GetUid(), stored.GetMetadata().GetUid())
}
})
t.Run("ref to a missing template is FailedPrecondition", func(t *testing.T) {
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "absent"}}
_, err := resolveActorTemplate(ctx, persistence, lister, actor)
if got := status.Code(err); got != codes.FailedPrecondition {
t.Fatalf("status.Code = %v, want FailedPrecondition (err: %v)", got, err)
}
})
t.Run("legacy mode converts the CRD", func(t *testing.T) {
actor := &ateapipb.Actor{ActorTemplateNamespace: "ns", ActorTemplateName: "crd-tmpl"}
got, err := resolveActorTemplate(ctx, persistence, lister, actor)
if err != nil {
t.Fatalf("resolveActorTemplate: %v", err)
}
if got.GetMetadata().GetUid() != "crd-uid-1" {
t.Errorf("template uid = %q, want the CRD uid", got.GetMetadata().GetUid())
}
if got.GetMetadata().GetAtespace() != "ns" || got.GetMetadata().GetName() != "crd-tmpl" {
t.Errorf("template identity = %s/%s, want ns/crd-tmpl", got.GetMetadata().GetAtespace(), got.GetMetadata().GetName())
}
})
}
// TestResolveActorTemplate_NotFound verifies a vanished template (either
// form) and an actor naming no template at all surface
// errActorTemplateNotFound, so callers like delete can tolerate them.
func TestResolveActorTemplate_NotFound(t *testing.T) {
ctx := context.Background()
persistence := newTestPersistence(t)
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
lister := listersv1alpha1.NewActorTemplateLister(indexer)
stored := seedSubstrateTemplate(t, ctx, persistence, "sub-tmpl")
tests := []struct {
name string
actor *ateapipb.Actor
wantNotFound bool
}{
{"ref mode resolves", &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "sub-tmpl"}}, false},
{"ref to deleted template", &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "gone"}}, true},
{"legacy CRD gone", &ateapipb.Actor{ActorTemplateNamespace: "ns", ActorTemplateName: "gone"}, true},
{"no template named at all", &ateapipb.Actor{}, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got, err := resolveActorTemplate(ctx, persistence, lister, tc.actor)
if tc.wantNotFound {
if !errors.Is(err, errActorTemplateNotFound) {
t.Fatalf("resolveActorTemplate err = %v, want errActorTemplateNotFound", err)
}
return
}
if err != nil {
t.Fatalf("resolveActorTemplate: %v", err)
}
if got.GetMetadata().GetUid() != stored.GetMetadata().GetUid() {
t.Errorf("template uid = %q, want %q", got.GetMetadata().GetUid(), stored.GetMetadata().GetUid())
}
})
}
}
// TestActorTemplateObjectRef pins that snapshot and assignment records get a
// fresh copy of the reference, never the actor's own message.
func TestActorTemplateObjectRef(t *testing.T) {
if got := actorTemplateObjectRef(&ateapipb.Actor{}); got != nil {
t.Errorf("actorTemplateObjectRef(no ref) = %v, want nil", got)
}
actor := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "tmpl1"}}
got := actorTemplateObjectRef(actor)
if got == actor.GetActorTemplate() {
t.Error("actorTemplateObjectRef aliases the actor's reference")
}
if got.GetAtespace() != "team-a" || got.GetName() != "tmpl1" {
t.Errorf("actorTemplateObjectRef = %v, want team-a/tmpl1", got)
}
}
+51 -69
View File
@@ -23,7 +23,6 @@ import (
"google.golang.org/protobuf/testing/protocmp"
"github.com/agent-substrate/substrate/internal/volume"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
storagev1 "k8s.io/api/storage/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -51,37 +50,32 @@ func (f *fakeStorageClassLister) Get(name string) (*storagev1.StorageClass, erro
var _ storagev1listers.StorageClassLister = (*fakeStorageClassLister)(nil)
func TestInitialActorVolumes_PendingState(t *testing.T) {
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{
Name: "data-vol-1",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
tmpl := &ateapipb.ActorTemplate{
Volumes: []*ateapipb.Volume{
{
Name: "data-vol-1",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "standard",
},
{
Name: "scratch-vol",
},
{
Name: "durable-vol",
VolumeSource: atev1alpha1.VolumeSource{
DurableDir: &atev1alpha1.DurableDirVolumeSource{},
},
},
{
Name: "data-vol-2",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "fast",
},
},
},
{
Name: "scratch-vol",
},
{
Name: "durable-vol",
Type: "DurableDir",
DurableDir: &ateapipb.DurableDirVolumeSource{},
},
{
Name: "data-vol-2",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "fast",
},
},
},
})
}
want := []*ateapipb.ExternalVolume{
{
@@ -120,51 +114,43 @@ func TestInitialActorVolumes_PendingState(t *testing.T) {
func TestCreateActorVolumes(t *testing.T) {
ctx := context.Background()
standardTmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{
Name: "data-vol",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
standardTmpl := &ateapipb.ActorTemplate{
Volumes: []*ateapipb.Volume{
{
Name: "data-vol",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
},
})
}
multiVolTmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{
Name: "vol1",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
multiVolTmpl := &ateapipb.ActorTemplate{
Volumes: []*ateapipb.Volume{
{
Name: "vol1",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "standard",
},
{
Name: "vol2",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
},
{
Name: "vol2",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "standard",
},
{
Name: "vol3",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
},
{
Name: "vol3",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "standard",
},
},
},
})
}
tests := []struct {
name string
@@ -247,11 +233,7 @@ func TestCreateActorVolumes(t *testing.T) {
},
{
name: "volume not found in template returns error",
tmpl: mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{},
},
}),
tmpl: &ateapipb.ActorTemplate{},
inputVolumes: []*ateapipb.ExternalVolume{
{
VolumeName: "missing-vol",
@@ -54,9 +54,9 @@ func newAPIWorker(name string) *ateapipb.Worker {
func newAPIAssignment(actorUID string) *ateapipb.ActorAssignment {
return &ateapipb.ActorAssignment{
ActorTemplate: &ateapipb.KubeNamespacedObjectRef{Namespace: "ate-system", Name: "tmpl"},
Actor: &ateapipb.ObjectRef{Atespace: "team-a", Name: "actor-1"},
ActorUid: actorUID,
ActorTemplateRef: &ateapipb.ObjectRef{Atespace: "ate-system", Name: "tmpl"},
Actor: &ateapipb.ObjectRef{Atespace: "team-a", Name: "actor-1"},
ActorUid: actorUID,
}
}
@@ -72,7 +72,6 @@ type ActorWorkflow struct {
workerCache *workercache.Cache
scheduler scheduling.Scheduler
dialer *AteletDialer
actorTemplateLister listersv1alpha1.ActorTemplateLister
workerPoolLister listersv1alpha1.WorkerPoolLister
sandboxConfigLister listersv1alpha1.SandboxConfigLister
storageClassLister storagev1listers.StorageClassLister
@@ -86,7 +85,6 @@ func NewActorWorkflow(
store actorWorkflowStore,
workerCache *workercache.Cache,
dialer *AteletDialer,
actorTemplateLister listersv1alpha1.ActorTemplateLister,
workerPoolLister listersv1alpha1.WorkerPoolLister,
sandboxConfigLister listersv1alpha1.SandboxConfigLister,
storageClassLister storagev1listers.StorageClassLister,
@@ -99,7 +97,6 @@ func NewActorWorkflow(
workerCache: workerCache,
scheduler: scheduling.New(workerCache, scheduling.WithMeter(otel.Meter("ateapi"))),
dialer: dialer,
actorTemplateLister: actorTemplateLister,
workerPoolLister: workerPoolLister,
sandboxConfigLister: sandboxConfigLister,
storageClassLister: storageClassLister,
@@ -52,7 +52,7 @@ func (w *ActorWorkflow) DeleteActor(ctx context.Context, actorRef resources.Acto
var errs []error
// Cleanup stays best-effort: an unresolvable template is recorded and
// the remaining steps run without it, like a missing one.
actorTemplate, err := resolveActorTemplate(ctx, w.store, w.actorTemplateLister, actor)
actorTemplate, err := resolveActorTemplate(ctx, w.store, actor)
if errors.Is(err, errActorTemplateNotFound) {
actorTemplate, err = nil, nil
}
@@ -162,8 +162,8 @@ func (w *ActorWorkflow) ensureAteletTerminated(ctx context.Context, actorRef res
// all external volumes recorded on the actor so atelet can unmount them on the node.
slog.WarnContext(ctx, "actor template not found, constructing fallback workload spec for atelet terminate",
slog.String("actor", actorRef.Name),
slog.String("templateNamespace", actor.GetActorTemplateNamespace()),
slog.String("templateName", actor.GetActorTemplateName()))
slog.String("templateAtespace", actor.GetActorTemplate().GetAtespace()),
slog.String("templateName", actor.GetActorTemplate().GetName()))
workloadSpec = &ateletpb.WorkloadSpec{}
for _, vol := range actor.GetStatus().GetActorVolumes() {
// StorageVolumeId is only populated once the volume is provisioned.
@@ -188,8 +188,8 @@ func (w *ActorWorkflow) ensureAteletTerminated(ctx context.Context, actorRef res
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorUid: actor.GetMetadata().GetUid(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
Spec: workloadSpec,
}
@@ -102,7 +102,7 @@ func (w *ActorWorkflow) loadActorForPause(ctx context.Context, actorRef resource
if err != nil {
return nil, nil, err
}
actorTemplate, err := resolveActorTemplate(ctx, w.store, w.actorTemplateLister, actor)
actorTemplate, err := resolveActorTemplate(ctx, w.store, actor)
if err != nil {
return nil, nil, err
}
@@ -190,8 +190,8 @@ func (w *ActorWorkflow) ensureAteletPaused(ctx context.Context, actorRef resourc
TargetAteomUid: assignment.GetWorkerPodUid(),
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
Spec: workloadSpec,
Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_LOCAL,
Config: &ateletpb.CheckpointRequest_LocalConfig{
@@ -20,7 +20,6 @@ import (
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
"github.com/agent-substrate/substrate/internal/resources"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -60,7 +59,7 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) {
// Intentionally NOT creating the worker in store, simulates worker already gone.
w := &ActorWorkflow{store: st}
finalized, err := w.ensurePausedFinalized(ctx, actorRef, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{}))
finalized, err := w.ensurePausedFinalized(ctx, actorRef, &ateapipb.ActorTemplate{})
if err != nil {
t.Fatalf("ensurePausedFinalized: %v", err)
}
@@ -95,12 +94,12 @@ func TestEnsurePausedFinalized_WorkerGone(t *testing.T) {
func TestEnsurePausedFinalized_RecordsContentScope(t *testing.T) {
tests := []struct {
name string
onPause atev1alpha1.SnapshotScope
onPause ateapipb.SnapshotContentScope
want ateapipb.SnapshotContentScope
}{
{"data", atev1alpha1.SnapshotScopeData, ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA},
{"full", atev1alpha1.SnapshotScopeFull, ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
{"unset defaults to full", "", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
{"data", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA, ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA},
{"full", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL, ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
{"unset defaults to full", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED, ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
@@ -137,9 +136,9 @@ func TestEnsurePausedFinalized_RecordsContentScope(t *testing.T) {
}
w := &ActorWorkflow{store: st}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{OnPause: tc.onPause}},
})
tmpl := &ateapipb.ActorTemplate{
SnapshotsConfig: &ateapipb.SnapshotsConfig{OnPause: tc.onPause},
}
got, err := w.ensurePausedFinalized(ctx, actorRef, tmpl)
if err != nil {
t.Fatalf("ensurePausedFinalized: %v", err)
@@ -284,7 +283,7 @@ func TestEnsureAteletPaused_DanglingWorkerDoesNotRecordPhantomSnapshot(t *testin
created := storetest.MustCreateActor(t, ctx, persistence, actor)
w := &ActorWorkflow{store: persistence, dialer: newDanglingDialer()}
if _, err := w.ensureAteletPaused(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{})); err == nil {
if _, err := w.ensureAteletPaused(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, &ateapipb.ActorTemplate{}); err == nil {
t.Fatal("ensureAteletPaused: want error for dangling worker, got nil")
}
@@ -172,7 +172,7 @@ func (w *ActorWorkflow) loadActorForResume(ctx context.Context, actorRef resourc
return actor, nil, src, nil
}
actorTemplate, err := resolveActorTemplate(ctx, w.store, w.actorTemplateLister, actor)
actorTemplate, err := resolveActorTemplate(ctx, w.store, actor)
if err != nil {
return nil, nil, src, err
}
@@ -516,14 +516,7 @@ func (w *ActorWorkflow) assignWorkerAttempt(ctx context.Context, actorRef resour
},
ActorUid: actor.GetMetadata().GetUid(),
}
if ref := actorTemplateObjectRef(actor); ref != nil {
assignment.ActorTemplateRef = ref
} else {
assignment.ActorTemplate = &ateapipb.KubeNamespacedObjectRef{
Namespace: actor.GetActorTemplateNamespace(),
Name: actor.GetActorTemplateName(),
}
}
assignment.ActorTemplateRef = actorTemplateObjectRef(actor)
// Workers() returns pointers directly from the cache, so the claim is written
// by mutating the store's own copy; the cached one is only read, for the
@@ -686,8 +679,8 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou
TargetAteomUid: assignment.GetWorkerPodUid(),
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
Spec: workloadSpec,
ActorUid: actor.GetMetadata().Uid,
EgressGateway: egressGateway,
@@ -734,8 +727,8 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou
TargetAteomUid: assignment.GetWorkerPodUid(),
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
Spec: workloadSpec,
Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL,
Config: &ateletpb.RestoreRequest_ExternalConfig{
@@ -769,8 +762,8 @@ func (w *ActorWorkflow) ensureAteletRestored(ctx context.Context, actorRef resou
TargetAteomUid: assignment.GetWorkerPodUid(),
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
SandboxAssets: sandboxAssets,
Spec: workloadSpec,
ActorUid: actor.GetMetadata().Uid,
@@ -28,12 +28,10 @@ import (
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/resources"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// TestSchedulerRecordable guards the retry-dedup rule: the assignment loop
@@ -110,7 +108,7 @@ func TestAssignWorkerAttempt_MissingSelectedWorkerIsRetried(t *testing.T) {
actor, wc := seedAssignFixture(t, ctx, persistence)
st := &updateWorkerErrorStore{Interface: persistence, err: store.ErrNotFound}
w := &ActorWorkflow{store: st, workerCache: wc, scheduler: scheduling.New(wc)}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor}})
tmpl := &ateapipb.ActorTemplate{SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR}}
_, _, err := w.assignWorkerAttempt(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}, actor, tmpl)
if !errors.Is(err, store.ErrVersionConflict) {
@@ -131,7 +129,7 @@ func TestEnsureWorkerAssigned_ConflictExhaustionIsRetryable(t *testing.T) {
actor, wc := seedAssignFixture(t, ctx, persistence)
st := &updateWorkerErrorStore{Interface: persistence, err: store.ErrVersionConflict}
w := &ActorWorkflow{store: st, workerCache: wc, scheduler: scheduling.New(wc)}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor}})
tmpl := &ateapipb.ActorTemplate{SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR}}
_, _, err := w.ensureWorkerAssigned(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}, actor, tmpl)
if !errors.Is(err, store.ErrVersionConflict) {
@@ -190,9 +188,6 @@ func TestAssignWorkerAttempt_StampsSubstrateTemplateRef(t *testing.T) {
if assignment.GetActorTemplateRef().GetAtespace() != "team-a" || assignment.GetActorTemplateRef().GetName() != "sub-tmpl" {
t.Errorf("assignment ActorTemplateRef = %v, want team-a/sub-tmpl", assignment.GetActorTemplateRef())
}
if assignment.GetActorTemplate() != nil {
t.Errorf("assignment legacy ActorTemplate = %v, want nil for a ref-mode actor", assignment.GetActorTemplate())
}
}
func TestAssignWorkerAttempt_SkipsWorkerAssignedInOtherAtespace(t *testing.T) {
@@ -231,9 +226,9 @@ func TestAssignWorkerAttempt_SkipsWorkerAssignedInOtherAtespace(t *testing.T) {
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "shared", Uid: "actor-uid"},
}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor},
})
tmpl := &ateapipb.ActorTemplate{
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR},
}
_, _, err := w.assignWorkerAttempt(ctx, resources.ActorRef{Atespace: "team-a", Name: "shared"}, actor, tmpl)
if status.Code(err) != codes.ResourceExhausted {
t.Fatalf("assignWorkerAttempt() error = %v, want ResourceExhausted (no free workers)", err)
@@ -306,9 +301,9 @@ func TestAssignWorkerAttempt_ReleasesIneligibleStaleWorkerInBackground(t *testin
}
w := &ActorWorkflow{store: persistence, workerCache: wc, scheduler: scheduling.New(wc)}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor},
})
tmpl := &ateapipb.ActorTemplate{
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR},
}
_, worker, err := w.assignWorkerAttempt(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}, actor, tmpl)
if err != nil {
t.Fatalf("assignWorkerAttempt() error = %v, want nil (release must not fail the resume)", err)
@@ -404,9 +399,9 @@ func TestAssignWorkerAttempt_RetryAfterConflictPicksFreshWorker(t *testing.T) {
}
w := &ActorWorkflow{store: persistence, workerCache: wc, scheduler: scheduling.New(wc)}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor},
})
tmpl := &ateapipb.ActorTemplate{
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR},
}
_, worker, err := w.assignWorkerAttempt(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}, actor, tmpl)
if err != nil {
t.Fatalf("assignWorkerAttempt() on retry = %v, want nil (must re-pick a free worker)", err)
@@ -550,9 +545,9 @@ func TestAssignWorkerAttempt_ConflictRefreshesActor(t *testing.T) {
}}
w := &ActorWorkflow{store: st, workerCache: wc, scheduler: scheduling.New(wc)}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor},
})
tmpl := &ateapipb.ActorTemplate{
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR},
}
refreshed, _, err := w.assignWorkerAttempt(ctx, resources.ActorRef{Atespace: "team-a", Name: "id1"}, actor, tmpl)
if tc.wantRetry {
@@ -678,7 +673,7 @@ func TestEnsureWorkerAssigned_RejectsNonResumableStates(t *testing.T) {
continue
}
actor := &ateapipb.Actor{Status: &ateapipb.ActorStatus{State: st}, Metadata: &ateapipb.ResourceMetadata{Name: "id1", Uid: "actor-uid-1"}}
_, _, err := w.ensureWorkerAssigned(ctx, resources.ActorRef{Name: "id1"}, actor, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{}))
_, _, err := w.ensureWorkerAssigned(ctx, resources.ActorRef{Name: "id1"}, actor, &ateapipb.ActorTemplate{})
assertPrerequisiteResult(t, st, err, false)
}
}
@@ -877,7 +872,7 @@ func TestValidateAssignedWorker_WorkerOwnership(t *testing.T) {
},
},
}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor}})
tmpl := &ateapipb.ActorTemplate{SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_GVISOR}}
_, err = w.validateAssignedWorker(ctx, resources.ActorRef{Atespace: "team-a", Name: "shared"}, resumingActor, tmpl)
if got := status.Code(err); got != tt.wantCode {
t.Fatalf("status.Code(err) = %v, want %v (err: %v)", got, tt.wantCode, err)
@@ -915,17 +910,17 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
tests := []struct {
name string
fromData atev1alpha1.ResumeSource
fromData ateapipb.ResumeSource
// paused seeds the actor with LocalSnapshotInfo (a pause checkpoint)
// instead of a durable snapshot; onPause is the template's pause
// scope, contentScope the durable snapshot's recorded content.
paused bool
onPause atev1alpha1.SnapshotScope
onPause ateapipb.SnapshotContentScope
contentScope ateapipb.SnapshotContentScope
// goldenSnapshot is ActorTemplate.Status.GoldenSnapshot; seedGolden
// controls whether the golden ActorSnapshot row it names exists, and
// goldenScope the scope it records (zero value UNSPECIFIED is treated
// as Full for legacy snapshots).
// goldenSnapshot names the template status's golden snapshot;
// seedGolden controls whether the golden ActorSnapshot row it names
// exists, and goldenScope the scope it records (zero value UNSPECIFIED
// is treated as Full for legacy snapshots).
goldenSnapshot string
seedGolden bool
goldenScope ateapipb.SnapshotContentScope
@@ -934,7 +929,7 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
}{
{
name: "resolves golden location for Data durable snapshot",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
goldenSnapshot: "golden-1",
seedGolden: true,
@@ -944,9 +939,9 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
},
{
name: "resolves golden location for paused actor with Data onPause",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
paused: true,
onPause: atev1alpha1.SnapshotScopeData,
onPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
goldenSnapshot: "golden-1",
seedGolden: true,
goldenScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
@@ -957,9 +952,9 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
// A Full pause snapshot restores from its own content; the policy
// only governs data-only restores.
name: "leaves golden location empty for paused actor with Full onPause",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
paused: true,
onPause: atev1alpha1.SnapshotScopeFull,
onPause: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
goldenSnapshot: "golden-1",
seedGolden: true,
goldenScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
@@ -968,7 +963,7 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
},
{
name: "fails when golden snapshot is not Full",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
goldenSnapshot: "golden-1",
seedGolden: true,
@@ -977,13 +972,13 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
},
{
name: "fails when template has no golden snapshot",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
wantCode: codes.FailedPrecondition,
},
{
name: "fails when golden snapshot data is missing",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
goldenSnapshot: "golden-1",
wantCode: codes.DataLoss,
@@ -992,7 +987,7 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
// A Full snapshot restores from its own content even under
// Golden fromData (e.g. taken before the template switched).
name: "leaves golden location empty for Full snapshot",
fromData: atev1alpha1.ResumeSourceGolden,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_GOLDEN,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
goldenSnapshot: "golden-1",
seedGolden: true,
@@ -1002,7 +997,7 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
},
{
name: "leaves golden location empty under ColdBoot fromData",
fromData: atev1alpha1.ResumeSourceColdBoot,
fromData: ateapipb.ResumeSource_RESUME_SOURCE_COLD_BOOT,
contentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA,
goldenSnapshot: "golden-1",
seedGolden: true,
@@ -1052,16 +1047,21 @@ func TestLoadActorForResume_OnGoldenDataResume(t *testing.T) {
seedWorkflowActor(t, ctx, persistence, actorRef, "ns", "tmpl1", actorState, seedOpts...)
storetest.MustCreateAtespace(t, ctx, persistence, "ns")
if _, err := persistence.CreateActorTemplate(ctx, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "tmpl1"},
Spec: atev1alpha1.ActorTemplateSpec{
SnapshotsConfig: atev1alpha1.SnapshotsConfig{
OnPause: tt.onPause,
OnResume: atev1alpha1.OnResumeConfig{FromData: tt.fromData},
},
tmpl := &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "ns", Name: "tmpl1"},
SnapshotsConfig: &ateapipb.SnapshotsConfig{
OnPause: tt.onPause,
OnResume: &ateapipb.OnResumeConfig{FromData: tt.fromData},
},
Status: atev1alpha1.ActorTemplateStatus{GoldenSnapshot: tt.goldenSnapshot},
})); err != nil {
}
if tt.goldenSnapshot != "" {
tmpl.Status = &ateapipb.ActorTemplateStatus{
GoldenSnapshotStatus: &ateapipb.GoldenSnapshotStatus{
GoldenSnapshot: &ateapipb.ObjectRef{Atespace: resources.GoldenActorAtespace, Name: tt.goldenSnapshot},
},
}
}
if _, err := persistence.CreateActorTemplate(ctx, tmpl); err != nil {
t.Fatalf("create template: %v", err)
}
@@ -1104,10 +1104,14 @@ func TestLoadActorForResume_GoldenFallbackRejectsNonFullGolden(t *testing.T) {
seedWorkflowActor(t, ctx, persistence, actorRef, "ns", "tmpl1", ateapipb.ActorState_ACTOR_STATE_SUSPENDED)
storetest.MustCreateAtespace(t, ctx, persistence, "ns")
if _, err := persistence.CreateActorTemplate(ctx, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "tmpl1"},
Status: atev1alpha1.ActorTemplateStatus{GoldenSnapshot: "golden-1"},
})); err != nil {
if _, err := persistence.CreateActorTemplate(ctx, &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "ns", Name: "tmpl1"},
Status: &ateapipb.ActorTemplateStatus{
GoldenSnapshotStatus: &ateapipb.GoldenSnapshotStatus{
GoldenSnapshot: &ateapipb.ObjectRef{Atespace: resources.GoldenActorAtespace, Name: "golden-1"},
},
},
}); err != nil {
t.Fatalf("create template: %v", err)
}
@@ -109,7 +109,7 @@ func (w *ActorWorkflow) loadActorForSuspend(ctx context.Context, actorRef resour
if err != nil {
return nil, nil, err
}
actorTemplate, err := resolveActorTemplate(ctx, w.store, w.actorTemplateLister, actor)
actorTemplate, err := resolveActorTemplate(ctx, w.store, actor)
if err != nil {
return nil, nil, err
}
@@ -247,8 +247,8 @@ func (w *ActorWorkflow) ensureAteletSuspended(ctx context.Context, actorRef reso
TargetAteomUid: assignment.GetWorkerPodUid(),
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
Spec: workloadSpec,
Type: ateletpb.CheckpointType_CHECKPOINT_TYPE_EXTERNAL,
Config: &ateletpb.CheckpointRequest_ExternalConfig{
@@ -303,8 +303,8 @@ func (w *ActorWorkflow) ensurePausedSnapshotUploaded(ctx context.Context, actorR
Atespace: actor.GetMetadata().GetAtespace(),
ActorName: actor.GetMetadata().GetName(),
ActorUid: actor.GetMetadata().GetUid(),
ActorTemplateAtespace: actorTemplate.GetMetadata().GetAtespace(),
ActorTemplateName: actorTemplate.GetMetadata().GetName(),
ActorTemplateAtespace: actor.GetActorTemplate().GetAtespace(),
ActorTemplateName: actor.GetActorTemplate().GetName(),
LocalSnapshotName: local.GetSnapshotName(),
DestinationSnapshotUri: snapshotURI.String(),
// The commit scope, like a running-origin suspend; atelet converts
@@ -379,15 +379,13 @@ func (w *ActorWorkflow) ensureSuspendedFinalized(ctx context.Context, actorRef r
snapshot := &ateapipb.ActorSnapshot{
Metadata: &ateapipb.ResourceMetadata{Atespace: actorRef.Atespace, Name: snapshotName},
Status: &ateapipb.ActorSnapshotStatus{
SourceActor: actorRef.ToObjectRef(),
SourceActorUid: latestActor.GetMetadata().GetUid(),
SourceActorVersion: latestActor.GetStatus().GetInProgressSnapshotSourceActorVersion(),
ActorTemplateNamespace: latestActor.GetActorTemplateNamespace(),
ActorTemplateName: latestActor.GetActorTemplateName(),
ActorTemplate: actorTemplateObjectRef(latestActor),
ActorTemplateUid: actorTemplate.GetMetadata().GetUid(),
ContentScope: commitSnapshotScope(actorRef.Atespace, actorTemplate),
SnapshotUri: snapshotURI.String(),
SourceActor: actorRef.ToObjectRef(),
SourceActorUid: latestActor.GetMetadata().GetUid(),
SourceActorVersion: latestActor.GetStatus().GetInProgressSnapshotSourceActorVersion(),
ActorTemplate: actorTemplateObjectRef(latestActor),
ActorTemplateUid: actorTemplate.GetMetadata().GetUid(),
ContentScope: commitSnapshotScope(actorRef.Atespace, actorTemplate),
SnapshotUri: snapshotURI.String(),
},
}
// ErrAlreadyExists means a previous attempt crashed after creating
@@ -22,7 +22,6 @@ import (
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/storetest"
"github.com/agent-substrate/substrate/internal/resources"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -36,9 +35,9 @@ func TestEnsureMarkedSuspending_SnapshotName(t *testing.T) {
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING},
})
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{
SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://bucket/root/"},
}})
tmpl := &ateapipb.ActorTemplate{
SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://bucket/root/"},
}
w := &ActorWorkflow{store: persistence}
marked, err := w.ensureMarkedSuspending(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, actor, tmpl)
if err != nil {
@@ -78,7 +77,7 @@ func TestEnsureMarkedSuspending_ReentryKeepsPersistedSnapshotLocation(t *testing
},
})
w := &ActorWorkflow{store: persistence}
marked, err := w.ensureMarkedSuspending(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, actor, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{}))
marked, err := w.ensureMarkedSuspending(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, actor, &ateapipb.ActorTemplate{})
if err != nil {
t.Fatalf("ensureMarkedSuspending: %v", err)
}
@@ -169,9 +168,9 @@ func TestEnsureMarkedSuspending_StateMatrix(t *testing.T) {
Status: &ateapipb.ActorStatus{State: seedState},
})
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{
SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://snapshots"},
}})
tmpl := &ateapipb.ActorTemplate{
SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://snapshots"},
}
marked, err := w.ensureMarkedSuspending(ctx, actorRef, actor, tmpl)
assertPrerequisiteResult(t, seedState, err, allowed[seedState])
if err == nil && marked.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDING {
@@ -257,7 +256,7 @@ func TestEnsureAteletSuspended_DanglingWorkerDoesNotRecordPhantomSnapshot(t *tes
created := storetest.MustCreateActor(t, ctx, persistence, actor)
w := &ActorWorkflow{store: persistence, dialer: newDanglingDialer()}
if _, err := w.ensureAteletSuspended(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{})); err == nil {
if _, err := w.ensureAteletSuspended(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, &ateapipb.ActorTemplate{}); err == nil {
t.Fatal("ensureAteletSuspended: want error for dangling worker, got nil")
}
@@ -308,7 +307,7 @@ func TestEnsureSuspendedFinalized_NoAssignment(t *testing.T) {
created := storetest.MustCreateActor(t, ctx, persistence, actor)
w := &ActorWorkflow{store: persistence}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://snapshots"}}})
tmpl := &ateapipb.ActorTemplate{SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://snapshots"}}
stored, err := w.ensureSuspendedFinalized(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, tmpl)
if err != nil {
t.Fatalf("ensureSuspendedFinalized: %v", err)
@@ -449,7 +448,7 @@ func TestEnsureSuspendedFinalized_ReleasesOnlyOwnWorker(t *testing.T) {
}
w := &ActorWorkflow{store: persistence}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://bucket/root"}}})
tmpl := &ateapipb.ActorTemplate{SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://bucket/root"}}
if _, err := w.ensureSuspendedFinalized(ctx, resources.ActorRef{Atespace: "team-a", Name: "shared"}, tmpl); err != nil {
t.Fatalf("ensureSuspendedFinalized: %v", err)
}
@@ -490,7 +489,7 @@ func TestEnsureSuspendedFinalized_SnapshotSourceActorVersion(t *testing.T) {
})
w := &ActorWorkflow{store: persistence}
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://snapshots"}}})
tmpl := &ateapipb.ActorTemplate{SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://snapshots"}}
final, err := w.ensureSuspendedFinalized(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, tmpl)
if err != nil {
t.Fatalf("ensureSuspendedFinalized: %v", err)
@@ -515,23 +514,23 @@ func TestEnsureSuspendedFinalized_SnapshotSourceActorVersion(t *testing.T) {
// golden snapshot is the base an OnGolden data resume combines into, so the
// template's onCommit must not thin it down to a data-only capture.
func TestCommitSnapshotScope(t *testing.T) {
tmpl := func(onCommit atev1alpha1.SnapshotScope) *ateapipb.ActorTemplate {
return mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{
SnapshotsConfig: atev1alpha1.SnapshotsConfig{OnCommit: onCommit},
}})
tmpl := func(onCommit ateapipb.SnapshotContentScope) *ateapipb.ActorTemplate {
return &ateapipb.ActorTemplate{
SnapshotsConfig: &ateapipb.SnapshotsConfig{OnCommit: onCommit},
}
}
fullScope := ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL
dataScope := ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA
tests := []struct {
name string
atespace string
onCommit atev1alpha1.SnapshotScope
onCommit ateapipb.SnapshotContentScope
want ateapipb.SnapshotContentScope
}{
{"golden actor ignores Data onCommit", resources.GoldenActorAtespace, atev1alpha1.SnapshotScopeData, fullScope},
{"golden actor keeps Full onCommit", resources.GoldenActorAtespace, atev1alpha1.SnapshotScopeFull, fullScope},
{"regular actor uses Data onCommit", "team-a", atev1alpha1.SnapshotScopeData, dataScope},
{"regular actor uses Full onCommit", "team-a", atev1alpha1.SnapshotScopeFull, fullScope},
{"golden actor ignores Data onCommit", resources.GoldenActorAtespace, dataScope, fullScope},
{"golden actor keeps Full onCommit", resources.GoldenActorAtespace, fullScope, fullScope},
{"regular actor uses Data onCommit", "team-a", dataScope, dataScope},
{"regular actor uses Full onCommit", "team-a", fullScope, fullScope},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
@@ -572,24 +571,26 @@ func TestIsPausedOriginSuspend(t *testing.T) {
// suspend is rejected before the actor leaves PAUSED when the pause captured
// Data but the template commits Full: an upload cannot fabricate memory.
func TestEnsureMarkedSuspending_PausedScopeRejection(t *testing.T) {
tmpl := func(onPause, onCommit atev1alpha1.SnapshotScope) *ateapipb.ActorTemplate {
return mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{
SnapshotsConfig: atev1alpha1.SnapshotsConfig{OnPause: onPause, OnCommit: onCommit, Location: "gs://snapshots"},
}})
tmpl := func(onPause, onCommit ateapipb.SnapshotContentScope) *ateapipb.ActorTemplate {
return &ateapipb.ActorTemplate{
SnapshotsConfig: &ateapipb.SnapshotsConfig{OnPause: onPause, OnCommit: onCommit, StorageLocation: "gs://snapshots"},
}
}
fullScope := ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL
dataScope := ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA
tests := []struct {
name string
captured ateapipb.SnapshotContentScope
tmpl *ateapipb.ActorTemplate
wantErr bool
}{
{"data capture cannot commit full", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA, tmpl(atev1alpha1.SnapshotScopeData, atev1alpha1.SnapshotScopeFull), true},
{"data capture commits data", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA, tmpl(atev1alpha1.SnapshotScopeData, atev1alpha1.SnapshotScopeData), false},
{"full capture commits full", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL, tmpl(atev1alpha1.SnapshotScopeFull, atev1alpha1.SnapshotScopeFull), false},
{"full capture commits data via conversion", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL, tmpl(atev1alpha1.SnapshotScopeFull, atev1alpha1.SnapshotScopeData), false},
{"data capture cannot commit full", dataScope, tmpl(dataScope, fullScope), true},
{"data capture commits data", dataScope, tmpl(dataScope, dataScope), false},
{"full capture commits full", fullScope, tmpl(fullScope, fullScope), false},
{"full capture commits data via conversion", fullScope, tmpl(fullScope, dataScope), false},
// Actors paused before content_scope existed fall back to the
// template's onPause.
{"unset capture falls back to onPause", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED, tmpl(atev1alpha1.SnapshotScopeData, atev1alpha1.SnapshotScopeFull), true},
{"unset capture falls back to onPause", ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED, tmpl(dataScope, fullScope), true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
@@ -637,7 +638,7 @@ func TestEnsurePausedSnapshotUploaded_Preconditions(t *testing.T) {
},
})
if _, err := w.ensurePausedSnapshotUploaded(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, mustTemplateFromCRD(&atev1alpha1.ActorTemplate{})); err == nil {
if _, err := w.ensurePausedSnapshotUploaded(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, &ateapipb.ActorTemplate{}); err == nil {
t.Fatal("ensurePausedSnapshotUploaded = nil, want error for missing node record")
}
@@ -664,7 +665,7 @@ func TestEnsurePausedSnapshotUploaded_Preconditions(t *testing.T) {
},
})
tmpl := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://snapshots"}}})
tmpl := &ateapipb.ActorTemplate{SnapshotsConfig: &ateapipb.SnapshotsConfig{StorageLocation: "gs://snapshots"}}
_, err := w.ensurePausedSnapshotUploaded(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, created, tmpl)
if !errors.Is(err, ErrNoAteletOnNode) {
t.Fatalf("ensurePausedSnapshotUploaded = %v, want ErrNoAteletOnNode", err)
@@ -54,7 +54,7 @@ func newTestActorWorkflow(t *testing.T, st store.Interface, tmplAtespace, tmplNa
}); err != nil && !errors.Is(err, store.ErrAlreadyExists) {
t.Fatalf("create test ActorTemplate: %v", err)
}
return NewActorWorkflow(st, nil, nil, nil, nil, nil, nil, nil, "", nil)
return NewActorWorkflow(st, nil, nil, nil, nil, nil, nil, "", nil)
}
// seedWorkflowActor stores an actor with the given state, bound to the given
@@ -18,37 +18,31 @@ import (
"testing"
"github.com/agent-substrate/substrate/internal/proto/ateletpb"
atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/testing/protocmp"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestWorkloadSpecFromActorTemplate(t *testing.T) {
tests := []struct {
name string
template *atev1alpha1.ActorTemplate
template *ateapipb.ActorTemplate
want *ateletpb.WorkloadSpec
}{
{
name: "converts DurableDir volume and mounts",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{Name: "home", VolumeSource: atev1alpha1.VolumeSource{DurableDir: &atev1alpha1.DurableDirVolumeSource{}}},
},
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "home", MountPath: "/home/user"},
{Name: "home", MountPath: "/workspace"},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{Name: "home", Type: "DurableDir", DurableDir: &ateapipb.DurableDirVolumeSource{}},
},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "home", MountPath: "/home/user"},
{Name: "home", MountPath: "/workspace"},
},
},
},
@@ -74,34 +68,31 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "converts SystemInfo volume with actorMetadata items",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{
Name: "system-info",
VolumeSource: atev1alpha1.VolumeSource{
SystemInfo: &atev1alpha1.SystemInfoVolumeSource{
DataSources: []atev1alpha1.SystemInfoDataSource{
{ActorMetadata: &atev1alpha1.ActorMetadataDataSource{
Items: []atev1alpha1.ActorMetadataItem{
{Field: atev1alpha1.ActorMetadataFieldName, Path: "actor-name"},
{Field: atev1alpha1.ActorMetadataFieldAtespace, Path: "atespace"},
{Field: atev1alpha1.ActorMetadataFieldUID, Path: "identity/actor-uid"},
},
}},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{
Name: "system-info",
Type: "SystemInfo",
SystemInfo: &ateapipb.SystemInfoVolumeSource{
DataSources: []*ateapipb.SystemInfoDataSource{
{ActorMetadata: &ateapipb.ActorMetadataDataSource{
Items: []*ateapipb.ActorMetadataItem{
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_NAME, Path: "actor-name"},
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_ATESPACE, Path: "atespace"},
{Field: ateapipb.ActorMetadataField_ACTOR_METADATA_FIELD_UID, Path: "identity/actor-uid"},
},
},
}},
},
},
},
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "system-info", MountPath: "/run/ate"},
},
},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "system-info", MountPath: "/run/ate"},
},
},
},
@@ -140,21 +131,19 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "converts Image volume and mounts",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{Name: "home", VolumeSource: atev1alpha1.VolumeSource{DurableDir: &atev1alpha1.DurableDirVolumeSource{}}},
{Name: "agent", VolumeSource: atev1alpha1.VolumeSource{Image: &atev1alpha1.ImageVolumeSource{Reference: "example.com/agent@sha256:abc"}}},
},
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "home", MountPath: "/home/user"},
{Name: "agent", MountPath: "/ate"},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{Name: "home", Type: "DurableDir", DurableDir: &ateapipb.DurableDirVolumeSource{}},
{Name: "agent", Type: "Image", Image: &ateapipb.ImageVolumeSource{Reference: "example.com/agent@sha256:abc"}},
},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "home", MountPath: "/home/user"},
{Name: "agent", MountPath: "/ate"},
},
},
},
@@ -184,28 +173,25 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "converts trustBundle data sources carrying the name for node-side resolution",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{
Name: "system-info",
VolumeSource: atev1alpha1.VolumeSource{
SystemInfo: &atev1alpha1.SystemInfoVolumeSource{
DataSources: []atev1alpha1.SystemInfoDataSource{
{TrustBundle: &atev1alpha1.TrustBundleDataSource{Name: "egress-trust", Path: "trust/ca.pem"}},
},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{
Name: "system-info",
Type: "SystemInfo",
SystemInfo: &ateapipb.SystemInfoVolumeSource{
DataSources: []*ateapipb.SystemInfoDataSource{
{TrustBundle: &ateapipb.TrustBundleDataSource{Name: "egress-trust", Path: "trust/ca.pem"}},
},
},
},
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "system-info", MountPath: "/run/substrate/certs"},
},
},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "system-info", MountPath: "/run/substrate/certs"},
},
},
},
@@ -240,20 +226,18 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "skips non-DurableDir volumes",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{Name: "unsupported", VolumeSource: atev1alpha1.VolumeSource{}},
{Name: "home", VolumeSource: atev1alpha1.VolumeSource{DurableDir: &atev1alpha1.DurableDirVolumeSource{}}},
},
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "home", MountPath: "/workspace"},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{Name: "unsupported"},
{Name: "home", Type: "DurableDir", DurableDir: &ateapipb.DurableDirVolumeSource{}},
},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "home", MountPath: "/workspace"},
},
},
},
@@ -278,15 +262,13 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "container without volume mounts has none",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Volumes: []atev1alpha1.Volume{
{Name: "home", VolumeSource: atev1alpha1.VolumeSource{DurableDir: &atev1alpha1.DurableDirVolumeSource{}}},
},
Containers: []atev1alpha1.Container{
{Name: "main", Image: "main"},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Volumes: []*ateapipb.Volume{
{Name: "home", Type: "DurableDir", DurableDir: &ateapipb.DurableDirVolumeSource{}},
},
Containers: []*ateapipb.Container{
{Name: "main", Image: "main"},
},
},
want: &ateletpb.WorkloadSpec{
@@ -301,17 +283,15 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "maps literal env",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
Env: []atev1alpha1.EnvVar{
{Name: "LITERAL", Value: "plain"},
{Name: "EMPTY", Value: ""},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
Env: []*ateapipb.EnvVar{
{Name: "LITERAL", Value: "plain"},
{Name: "EMPTY", Value: ""},
},
},
},
@@ -329,16 +309,14 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
},
{
name: "maps command and args",
template: &atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl1", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Containers: []atev1alpha1.Container{
{
Name: "main",
Image: "main",
Command: []string{"/entrypoint"},
Args: []string{"--foo", "--bar"},
},
template: &ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl1"},
Containers: []*ateapipb.Container{
{
Name: "main",
Image: "main",
Command: []string{"/entrypoint"},
Args: []string{"--foo", "--bar"},
},
},
},
@@ -355,7 +333,7 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := workloadSpecFromActorTemplate(mustTemplateFromCRD(tt.template), nil)
got, err := workloadSpecFromActorTemplate(tt.template, nil)
if err != nil {
t.Fatalf("workloadSpecFromActorTemplate failed: %v", err)
}
@@ -367,25 +345,23 @@ func TestWorkloadSpecFromActorTemplate(t *testing.T) {
}
func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) {
got, err := workloadSpecFromActorTemplate(mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl-readyz", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Containers: []atev1alpha1.Container{
{
Name: "with-probe",
Image: "main",
Readyz: &atev1alpha1.ContainerReadyz{
HTTPGet: &atev1alpha1.HTTPGetAction{Path: "/health", Port: 8080},
TimeoutSeconds: 45,
},
},
{
Name: "without-probe",
Image: "side",
got, err := workloadSpecFromActorTemplate(&ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl-readyz"},
Containers: []*ateapipb.Container{
{
Name: "with-probe",
Image: "main",
Readyz: &ateapipb.ContainerReadyz{
HttpGet: &ateapipb.HTTPGetAction{Path: "/health", Port: 8080},
TimeoutSeconds: 45,
},
},
{
Name: "without-probe",
Image: "side",
},
},
}), nil)
}, nil)
if err != nil {
t.Fatalf("workloadSpecFromActorTemplate failed: %v", err)
}
@@ -412,42 +388,37 @@ func TestWorkloadSpecFromActorTemplatePropagatesReadyz(t *testing.T) {
}
func TestAppendExternalVolumes(t *testing.T) {
template := mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
Spec: atev1alpha1.ActorTemplateSpec{
Containers: []atev1alpha1.Container{
{
Name: "main",
VolumeMounts: []atev1alpha1.VolumeMount{
{Name: "vol-1", MountPath: "/mnt/vol1"},
},
},
},
Volumes: []atev1alpha1.Volume{
{
Name: "vol-1",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "pd-standard",
},
},
},
{
Name: "vol-2",
VolumeSource: atev1alpha1.VolumeSource{
DurableDir: &atev1alpha1.DurableDirVolumeSource{},
},
},
{
Name: "unmounted-vol",
VolumeSource: atev1alpha1.VolumeSource{
ExternalVolumeTemplate: &atev1alpha1.ExternalVolumeTemplate{
StorageClassName: "pd-standard",
},
},
template := &ateapipb.ActorTemplate{
Containers: []*ateapipb.Container{
{
Name: "main",
VolumeMounts: []*ateapipb.VolumeMount{
{Name: "vol-1", MountPath: "/mnt/vol1"},
},
},
},
})
Volumes: []*ateapipb.Volume{
{
Name: "vol-1",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "pd-standard",
},
},
{
Name: "vol-2",
Type: "DurableDir",
DurableDir: &ateapipb.DurableDirVolumeSource{},
},
{
Name: "unmounted-vol",
Type: "ExternalVolumeTemplate",
ExternalVolumeTemplate: &ateapipb.ExternalVolumeTemplate{
StorageClassName: "pd-standard",
},
},
},
}
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{
@@ -504,34 +475,32 @@ func TestAppendExternalVolumes(t *testing.T) {
}
func TestWorkloadSpecFromActorTemplatePropagatesSecurityContext(t *testing.T) {
got, err := workloadSpecFromActorTemplate(mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl-caps", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
Containers: []atev1alpha1.Container{
{
Name: "adjusted",
Image: "main",
SecurityContext: &atev1alpha1.SecurityContext{
Capabilities: &atev1alpha1.Capabilities{
Add: []atev1alpha1.Capability{"NET_ADMIN"},
Drop: []atev1alpha1.Capability{"ALL"},
},
got, err := workloadSpecFromActorTemplate(&ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl-caps"},
Containers: []*ateapipb.Container{
{
Name: "adjusted",
Image: "main",
SecurityContext: &ateapipb.SecurityContext{
Capabilities: &ateapipb.Capabilities{
Add: []string{"NET_ADMIN"},
Drop: []string{"ALL"},
},
},
{
Name: "unset",
Image: "side",
},
{
// An empty capabilities block asks for no adjustment, so
// nothing is put on the wire for it.
Name: "empty",
Image: "third",
SecurityContext: &atev1alpha1.SecurityContext{Capabilities: &atev1alpha1.Capabilities{}},
},
},
{
Name: "unset",
Image: "side",
},
{
// An empty capabilities block asks for no adjustment, so
// nothing is put on the wire for it.
Name: "empty",
Image: "third",
SecurityContext: &ateapipb.SecurityContext{Capabilities: &ateapipb.Capabilities{}},
},
},
}), nil)
}, nil)
if err != nil {
t.Fatalf("workloadSpecFromActorTemplate failed: %v", err)
}
@@ -634,25 +603,21 @@ func TestToAteletResources(t *testing.T) {
// literal leaves every test in the repository green while limits never leave
// ate-api-server.
func TestWorkloadSpecFromActorTemplatePropagatesResources(t *testing.T) {
got, err := workloadSpecFromActorTemplate(mustTemplateFromCRD(&atev1alpha1.ActorTemplate{
ObjectMeta: metav1.ObjectMeta{Name: "tmpl-limits", Namespace: "agent-ns"},
Spec: atev1alpha1.ActorTemplateSpec{
SandboxClass: atev1alpha1.SandboxClassMicroVM,
Containers: []atev1alpha1.Container{
{
Name: "limited",
Image: "main",
Resources: &atev1alpha1.ContainerResources{
Limits: atev1alpha1.ContainerResourceList{
corev1.ResourceMemory: resource.MustParse("256Mi"),
corev1.ResourceCPU: resource.MustParse("200m"),
},
},
},
{Name: "unlimited", Image: "main"},
got, err := workloadSpecFromActorTemplate(&ateapipb.ActorTemplate{
Metadata: &ateapipb.ResourceMetadata{Atespace: "agent-ns", Name: "tmpl-limits"},
SandboxConfig: &ateapipb.SandboxConfig{SandboxClass: ateapipb.SandboxClass_SANDBOX_CLASS_MICROVM},
Containers: []*ateapipb.Container{
{
Name: "limited",
Image: "main",
Resources: &ateapipb.Resources{Limits: []*ateapipb.Limits{
{Name: "cpu", Quantity: "200m"},
{Name: "memory", Quantity: "256Mi"},
}},
},
{Name: "unlimited", Image: "main"},
},
}), nil)
}, nil)
if err != nil {
t.Fatalf("workloadSpecFromActorTemplate failed: %v", err)
}
@@ -87,76 +87,6 @@ func Validate_Actor(
errs = append(errs, fn(fldPath.Child("metadata"), obj.Metadata, oldVal, oldObj != nil)...)
}
{ // field ateapipb.Actor.ActorTemplateNamespace
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.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
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.Actor) *string {
return &oldObj.ActorTemplateNamespace
})
errs = append(errs, fn(fldPath.Child("actor_template_namespace"), &obj.ActorTemplateNamespace, oldVal, oldObj != nil)...)
}
{ // field ateapipb.Actor.ActorTemplateName
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.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
if e := validate.LongName(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
errs = append(errs, e...)
}
return
}
oldVal := safe.Field(oldObj,
func(oldObj *ateapipb.Actor) *string {
return &oldObj.ActorTemplateName
})
errs = append(errs, fn(fldPath.Child("actor_template_name"), &obj.ActorTemplateName, oldVal, oldObj != nil)...)
}
{ // field ateapipb.Actor.ActorTemplate
fn := func(
fldPath *field.Path,
@@ -170,13 +100,10 @@ func Validate_Actor(
}
// call field-attached validations
earlyReturn := false
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
@@ -158,10 +158,9 @@ func TestUpdateActor_ConcurrentWriteReturnsConflict(t *testing.T) {
ctx := context.Background()
createTestAtespace(t, s, "team-a")
created, err := s.CreateActor(ctx, &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-a"},
ActorTemplateNamespace: "default",
ActorTemplateName: "template-a",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-a"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "template-a"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
})
if err != nil {
t.Fatalf("CreateActor failed: %v", err)
@@ -353,10 +352,9 @@ func TestCreateActor_MissingAtespace_FailedPrecondition(t *testing.T) {
ctx := context.Background()
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "id1", Atespace: "no-such-atespace"},
ActorTemplateNamespace: "ns1",
ActorTemplateName: "tmpl1",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "id1", Atespace: "no-such-atespace"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns1", Name: "tmpl1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
if _, err := s.CreateActor(ctx, actor); !errors.Is(err, store.ErrFailedPrecondition) {
t.Errorf("CreateActor with missing atespace = %v, want ErrFailedPrecondition", err)
@@ -336,10 +336,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
created, err := s.CreateActor(ctx, actor)
@@ -382,10 +381,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
if _, err := s.CreateActor(ctx, actor); err != nil {
@@ -402,10 +400,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
created, err := s.CreateActor(ctx, actor)
@@ -459,10 +456,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
if _, err := s.CreateActor(ctx, actor); err != nil {
@@ -502,10 +498,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
newActor := func() *ateapipb.Actor {
return &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
}
@@ -557,10 +552,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
created, err := s.CreateActor(ctx, &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
})
if err != nil {
t.Fatalf("CreateActor failed: %v", err)
@@ -589,10 +583,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
created, err := s.CreateActor(ctx, &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
})
if err != nil {
t.Fatalf("CreateActor failed: %v", err)
@@ -654,10 +647,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplateNamespace: "default",
ActorTemplateName: "test-template",
Status: &ateapipb.ActorStatus{State: tt.state},
Metadata: &ateapipb.ResourceMetadata{Name: "session-1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Status: &ateapipb.ActorStatus{State: tt.state},
}
if _, err := s.CreateActor(ctx, actor); err != nil {
t.Fatalf("CreateActor failed: %v", err)
@@ -718,18 +710,16 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mustCreateAtespace(t, s, testAtespace)
actor1 := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "id1", Atespace: testAtespace},
ActorTemplateNamespace: "ns1",
ActorTemplateName: "tmpl1",
Metadata: &ateapipb.ResourceMetadata{Name: "id1", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns1", Name: "tmpl1"},
Status: &ateapipb.ActorStatus{
State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED,
LatestSnapshot: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "snapshot-1"},
},
}
actor2 := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: "id2", Atespace: testAtespace},
ActorTemplateNamespace: "ns1",
ActorTemplateName: "tmpl1",
Metadata: &ateapipb.ResourceMetadata{Name: "id2", Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns1", Name: "tmpl1"},
Status: &ateapipb.ActorStatus{
State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED,
LatestSnapshot: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "snapshot-2"},
@@ -762,10 +752,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
for i := 0; i < 5; i++ {
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: fmt.Sprintf("name%d", i), Atespace: testAtespace},
ActorTemplateNamespace: "ns1",
ActorTemplateName: "tmpl1",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: fmt.Sprintf("name%d", i), Atespace: testAtespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns1", Name: "tmpl1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
if _, err := s.CreateActor(ctx, actor); err != nil {
t.Fatalf("failed to create actor %d: %v", i, err)
@@ -806,10 +795,9 @@ func runActorContractTests(t *testing.T, setup func(t *testing.T) store.Interfac
mkActor := func(atespace, name string) *ateapipb.Actor {
return &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Name: name, Atespace: atespace},
ActorTemplateNamespace: "ns1",
ActorTemplateName: "tmpl1",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
Metadata: &ateapipb.ResourceMetadata{Name: name, Atespace: atespace},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "ns1", Name: "tmpl1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
}
}
for _, a := range []*ateapipb.Actor{mkActor("team-a", "a1"), mkActor("team-a", "a2"), mkActor("team-b", "b1")} {
@@ -1256,8 +1244,8 @@ func runWorkerContractTests(t *testing.T, setup func(t *testing.T) store.Interfa
defer watch.Close()
assignment := &ateapipb.ActorAssignment{
ActorTemplate: &ateapipb.KubeNamespacedObjectRef{Namespace: "default", Name: "test-template"},
Actor: &ateapipb.ObjectRef{Name: "session-1"},
ActorTemplateRef: &ateapipb.ObjectRef{Atespace: "default", Name: "test-template"},
Actor: &ateapipb.ObjectRef{Name: "session-1"},
}
updated, err := s.UpdateWorker(ctx, testWorkerName, store.PreconditionFrom(created), func(toUpdate *ateapipb.Worker) error {
toUpdate.Status.Assignment = assignment
+1 -2
View File
@@ -151,7 +151,6 @@ func main() {
}
ateFactory := externalversions.NewSharedInformerFactory(ateClient, 0)
actorTemplateLister := ateFactory.Api().V1alpha1().ActorTemplates().Lister()
workerPoolLister := ateFactory.Api().V1alpha1().WorkerPools().Lister()
sandboxConfigLister := ateFactory.Api().V1alpha1().SandboxConfigs().Lister()
csiDriverConfigLister := ateFactory.Api().V1alpha1().CSIDriverConfigs().Lister()
@@ -187,7 +186,7 @@ func main() {
volPlugins := make(map[string]volume.VolumePluginControlPlane)
ateletDialer := controlapi.NewAteletDialer(workerPodInformer.GetIndexer(), ateletPodInformer.GetIndexer(), *ateletClientCredBundle, *podIdentityCACerts)
controlSrv := controlapi.NewRPCService(persistence, workerCache, actorTemplateLister, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, storageClassLister, ateletDialer, instruments, *egressGatewayAddress, volPlugins)
controlSrv := controlapi.NewRPCService(persistence, workerCache, workerPoolLister, sandboxConfigLister, csiDriverConfigLister, storageClassLister, ateletDialer, instruments, *egressGatewayAddress, volPlugins)
// Drive stored ActorTemplates through the golden actor flow.
templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv, sandboxConfigLister)
@@ -100,8 +100,10 @@ func (r *ActorTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Reques
Atespace: resources.GoldenActorAtespace,
Name: actorName,
},
ActorTemplateNamespace: at.ObjectMeta.Namespace,
ActorTemplateName: at.ObjectMeta.Name,
ActorTemplate: &ateapipb.ObjectRef{
Atespace: at.ObjectMeta.Namespace,
Name: at.ObjectMeta.Name,
},
},
}
_, err = r.AteClient.CreateActor(ctx, createReq)
@@ -155,7 +155,7 @@ func (s *Server) processRequestHeaders(
}
elapsed := time.Since(start)
s.recordRouteDuration(ctx, elapsed, res.TemplateNamespace, res.TemplateName, classifyOutcome(err), res.resume())
s.recordRouteDuration(ctx, elapsed, res.TemplateAtespace, res.TemplateName, classifyOutcome(err), res.resume())
if err != nil {
slog.ErrorContext(ctx, "Error during ext_proc RequestHeaders processing",
@@ -52,12 +52,12 @@ type Result struct {
// /statusz page. Empty for handlers that do not pick an upstream.
Target string
// TemplateNamespace and TemplateName identify the actor template the
// TemplateAtespace and TemplateName identify the actor template the
// request resolved to. They are the low-cardinality attributes on the
// route-duration metric, and are empty when the direction has no template
// (or the request failed before resolving one).
TemplateNamespace string
TemplateName string
TemplateAtespace string
TemplateName string
// Resume is the actor-resume outcome, as one of the ateattr.RouterResume*
// values. Empty means "none" — the direction never resumes an actor, or the
@@ -136,9 +136,9 @@ func (h *Handler) HandleRequestHeaders(ctx context.Context, md *extproc.RequestM
// Actor template identity, used as low-cardinality route-latency metric
// attributes.
res := extproc.Result{
TemplateNamespace: actor.GetActorTemplateNamespace(),
TemplateName: actor.GetActorTemplateName(),
Resume: string(resumeOutcome),
TemplateAtespace: actor.GetActorTemplate().GetAtespace(),
TemplateName: actor.GetActorTemplate().GetName(),
Resume: string(resumeOutcome),
}
workerIP := actor.GetStatus().GetWorkerAssignment().GetWorkerPodIp()
+10 -25
View File
@@ -16,7 +16,6 @@ package cmd
import (
"fmt"
"strings"
"github.com/agent-substrate/substrate/cmd/kubectl-ate/internal/printer"
"github.com/agent-substrate/substrate/internal/ateclient"
@@ -24,7 +23,6 @@ import (
"github.com/spf13/cobra"
)
var templateFlag string
var templateRefFlag string
var atespaceFlag string
var sourceSnapshotTagFlag string
@@ -34,7 +32,7 @@ var createActorCmd = &cobra.Command{
Short: "Create an actor",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
request, err := buildCreateActorRequest(args[0], atespaceFlag, templateFlag, templateRefFlag, sourceSnapshotTagFlag)
request, err := buildCreateActorRequest(args[0], atespaceFlag, templateRefFlag, sourceSnapshotTagFlag)
if err != nil {
return err
}
@@ -56,30 +54,19 @@ var createActorCmd = &cobra.Command{
}
// buildCreateActorRequest assembles the CreateActor request from the command
// flags. Exactly one of template (legacy CRD reference) and templateRef
// (substrate ActorTemplate resource) is set; cobra enforces that.
func buildCreateActorRequest(actorName, atespace, template, templateRef, snapshotTag string) (*ateapipb.CreateActorRequest, error) {
// flags.
func buildCreateActorRequest(actorName, atespace, templateName, snapshotTag string) (*ateapipb.CreateActorRequest, error) {
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{
Atespace: atespace,
Name: actorName,
},
ActorTemplate: &ateapipb.ObjectRef{
Atespace: atespace,
Name: templateName,
},
}
if templateRef != "" {
// The template name is resolved in the actor's atespace; cross-atespace
// references are intentionally not expressible here.
if strings.Contains(templateRef, "/") {
return nil, fmt.Errorf("malformed --template-ref: %s (expected a bare template name, resolved in the actor's atespace)", templateRef)
}
actor.ActorTemplate = &ateapipb.ObjectRef{Atespace: atespace, Name: templateRef}
} else {
parts := strings.Split(template, "/")
if len(parts) != 2 {
return nil, fmt.Errorf("malformed --template: %s (expected <namespace>/<name>)", template)
}
actor.ActorTemplateNamespace = parts[0]
actor.ActorTemplateName = parts[1]
}
if snapshotTag != "" {
ref, err := parseNamespacedName(snapshotTag)
if err != nil {
@@ -91,11 +78,9 @@ func buildCreateActorRequest(actorName, atespace, template, templateRef, snapsho
}
func init() {
createActorCmd.Flags().StringVarP(&templateFlag, "template", "t", "", "Legacy ActorTemplate CRD to derive the actor from, in <namespace>/<name> format")
// TODO: rename "template-ref" to "template" when we fully cutover.
// TODO: rename "template-ref" to "template" now that the legacy CRD flag is gone.
createActorCmd.Flags().StringVar(&templateRefFlag, "template-ref", "", "Name of the substrate ActorTemplate resource to derive the actor from, resolved in the actor's atespace (--atespace)")
createActorCmd.MarkFlagsMutuallyExclusive("template", "template-ref")
createActorCmd.MarkFlagsOneRequired("template", "template-ref")
_ = createActorCmd.MarkFlagRequired("template-ref")
createActorCmd.Flags().StringVarP(&atespaceFlag, "atespace", "a", "", "Atespace to create the actor in (required)")
_ = createActorCmd.MarkFlagRequired("atespace")
createActorCmd.Flags().StringVar(&sourceSnapshotTagFlag, "snapshot-tag", "", "Initialize from an ActorSnapshot tag in <atespace>/<name> format")
@@ -25,7 +25,6 @@ import (
func TestBuildCreateActorRequest(t *testing.T) {
tests := []struct {
name string
template string
templateRef string
snapshotTag string
want *ateapipb.Actor
@@ -49,13 +48,12 @@ func TestBuildCreateActorRequest(t *testing.T) {
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: "demo", Name: "before-upgrade"},
},
},
{name: "malformed template ref", templateRef: "ate-demo-counter-substrate/counter", wantErr: true},
{name: "malformed snapshot tag", templateRef: "counter", snapshotTag: "before-upgrade", wantErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := buildCreateActorRequest("my-counter", "demo", test.template, test.templateRef, test.snapshotTag)
got, err := buildCreateActorRequest("my-counter", "demo", test.templateRef, test.snapshotTag)
if (err != nil) != test.wantErr {
t.Fatalf("buildCreateActorRequest error = %v, wantErr %t", err, test.wantErr)
}
@@ -33,8 +33,8 @@ func TestGetWorkersRunner_Filters(t *testing.T) {
Labels: map[string]string{"ate.dev/worker-pool": "counter"},
Status: &ateapipb.WorkerStatus{
Assignment: &ateapipb.ActorAssignment{
ActorTemplate: &ateapipb.KubeNamespacedObjectRef{Namespace: "ns-1", Name: "counter"},
Actor: &ateapipb.ObjectRef{Atespace: "space-a", Name: "actor-a"},
ActorTemplateRef: &ateapipb.ObjectRef{Atespace: "ns-1", Name: "counter"},
Actor: &ateapipb.ObjectRef{Atespace: "space-a", Name: "actor-a"},
},
},
},
@@ -53,8 +53,8 @@ func TestGetWorkersRunner_Filters(t *testing.T) {
Labels: map[string]string{"ate.dev/worker-pool": "counter"},
Status: &ateapipb.WorkerStatus{
Assignment: &ateapipb.ActorAssignment{
ActorTemplate: &ateapipb.KubeNamespacedObjectRef{Namespace: "ns-2", Name: "counter"},
Actor: &ateapipb.ObjectRef{Atespace: "space-b", Name: "actor-b"},
ActorTemplateRef: &ateapipb.ObjectRef{Atespace: "ns-2", Name: "counter"},
Actor: &ateapipb.ObjectRef{Atespace: "space-b", Name: "actor-b"},
},
},
},
@@ -125,13 +125,6 @@ func (r *TopWorkersRunner) Run(ctx context.Context) error {
wass.GetActor().GetAtespace(),
wass.GetActor().GetName(),
)
} else if tpl := wass.GetActorTemplate(); tpl != nil && tpl.GetNamespace() != "" {
assignedActor = fmt.Sprintf("%s/%s/%s/%s",
tpl.GetNamespace(),
tpl.GetName(),
wass.GetActor().GetAtespace(),
wass.GetActor().GetName(),
)
} else {
assignedActor = fmt.Sprintf("%s/%s",
wass.GetActor().GetAtespace(),
+6 -14
View File
@@ -60,13 +60,10 @@ func sortActors(actors []*ateapipb.Actor) {
}
// actorTemplateDisplay renders the template an actor was created from, in
// "<atespace>/<name>" form for substrate-resource references and
// "<namespace>/<name>" form for legacy CRD references.
// "<atespace>/<name>" form.
func actorTemplateDisplay(a *ateapipb.Actor) string {
if ref := a.GetActorTemplate(); ref != nil {
return ref.GetAtespace() + "/" + ref.GetName()
}
return a.GetActorTemplateNamespace() + "/" + a.GetActorTemplateName()
ref := a.GetActorTemplate()
return ref.GetAtespace() + "/" + ref.GetName()
}
// PrintActorsTo prints a slice of actors to the provided writer.
@@ -136,14 +133,9 @@ func PrintWorkersTo(out io.Writer, workers []*ateapipb.Worker, format string) er
assignedActor := "<none>"
if wass := worker.GetStatus().GetAssignment(); wass != nil {
status = "ASSIGNED"
// The assignment names the template either as a substrate
// resource ref or as a legacy CRD ref; exactly one is set.
template := wass.GetActorTemplate().GetNamespace() + "/" + wass.GetActorTemplate().GetName()
if ref := wass.GetActorTemplateRef(); ref != nil {
template = ref.GetAtespace() + "/" + ref.GetName()
}
assignedActor = fmt.Sprintf("%s/%s/%s",
template, wass.GetActor().GetAtespace(), wass.GetActor().GetName())
ref := wass.GetActorTemplateRef()
assignedActor = fmt.Sprintf("%s/%s/%s/%s",
ref.GetAtespace(), ref.GetName(), wass.GetActor().GetAtespace(), wass.GetActor().GetName())
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n", ns, pool, class, pod, status, assignedActor)
@@ -66,8 +66,7 @@ func TestPrintActorsTo_Table(t *testing.T) {
Version: 2,
CreateTime: timestamppb.New(now.Add(-5 * time.Minute)),
},
ActorTemplateNamespace: "default",
ActorTemplateName: "template-1",
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "template-1"},
Status: &ateapipb.ActorStatus{
State: ateapipb.ActorState_ACTOR_STATE_RUNNING,
WorkerAssignment: &ateapipb.WorkerAssignment{
@@ -156,9 +155,8 @@ func TestPrintActorsTo_Table_Sorted(t *testing.T) {
Atespace: "team-b",
CreateTime: timestamppb.New(now.Add(-72 * time.Hour)),
},
ActorTemplateNamespace: "default",
ActorTemplateName: "template-1",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "template-1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
},
{
Metadata: &ateapipb.ResourceMetadata{
@@ -166,9 +164,8 @@ func TestPrintActorsTo_Table_Sorted(t *testing.T) {
Atespace: "team-a",
CreateTime: timestamppb.New(now.Add(-5 * time.Minute)),
},
ActorTemplateNamespace: "default",
ActorTemplateName: "template-1",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "default", Name: "template-1"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_RUNNING},
},
{
Metadata: &ateapipb.ResourceMetadata{
@@ -176,9 +173,8 @@ func TestPrintActorsTo_Table_Sorted(t *testing.T) {
Atespace: "team-a",
CreateTime: timestamppb.New(now.Add(-5 * time.Hour)),
},
ActorTemplateNamespace: "other",
ActorTemplateName: "template-2",
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "other", Name: "template-2"},
Status: &ateapipb.ActorStatus{State: ateapipb.ActorState_ACTOR_STATE_SUSPENDED},
},
}
@@ -247,9 +243,9 @@ func TestPrintWorkersTo_Table(t *testing.T) {
SandboxClass: "gvisor",
Status: &ateapipb.WorkerStatus{
Assignment: &ateapipb.ActorAssignment{
ActorTemplate: &ateapipb.KubeNamespacedObjectRef{
Namespace: "default",
Name: "template-1",
ActorTemplateRef: &ateapipb.ObjectRef{
Atespace: "default",
Name: "template-1",
},
Actor: &ateapipb.ObjectRef{
Atespace: "space-1",
@@ -273,44 +269,6 @@ default pool-1 gvisor pod-1 ASSIGNED default/template-1/space-1/id-1
}
}
// A worker assigned to an actor created from a substrate ActorTemplate
// carries only ActorTemplateRef; the printer must not dereference the legacy
// CRD ref (regression test for a nil-pointer panic).
func TestPrintWorkersTo_Table_TemplateRef(t *testing.T) {
var buf bytes.Buffer
workers := []*ateapipb.Worker{
{
WorkerNamespace: "default",
WorkerPool: "pool-1",
WorkerPod: "pod-1",
SandboxClass: "gvisor",
Status: &ateapipb.WorkerStatus{
Assignment: &ateapipb.ActorAssignment{
ActorTemplateRef: &ateapipb.ObjectRef{
Atespace: "ate-demo-counter-substrate",
Name: "counter",
},
Actor: &ateapipb.ObjectRef{
Atespace: "space-1",
Name: "id-1",
},
},
},
},
}
if err := PrintWorkersTo(&buf, workers, "table"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := `NAMESPACE POOL CLASS POD STATUS ASSIGNED ACTOR
default pool-1 gvisor pod-1 ASSIGNED ate-demo-counter-substrate/counter/space-1/id-1
`
if diff := cmp.Diff(expected, buf.String()); diff != "" {
t.Errorf("output mismatch (-want +got):\n%s", diff)
}
}
func TestPrintWorkersTo_Table_Free(t *testing.T) {
var buf bytes.Buffer
workers := []*ateapipb.Worker{
+4 -4
View File
@@ -375,8 +375,8 @@ func handlePods(w http.ResponseWriter, r *http.Request) {
// Filter to the demo namespace when set — workers may live
// in their own pool namespace (worker_namespace) so we
// compare against actor_namespace too.
if tpl := wk.GetStatus().GetAssignment().GetActorTemplate(); tpl != nil {
if ns, wkns := namespace, tpl.GetNamespace(); ns != "" && wkns != "" && wkns != ns {
if ref := wk.GetStatus().GetAssignment().GetActorTemplateRef(); ref != nil {
if ns, wkns := namespace, ref.GetAtespace(); ns != "" && wkns != "" && wkns != ns {
continue
}
}
@@ -413,7 +413,7 @@ func handleActors(w http.ResponseWriter, r *http.Request) {
}
actors := make([]actorSummary, 0, len(resp.GetActors()))
for _, a := range resp.GetActors() {
if namespace != "" && a.GetActorTemplateNamespace() != "" && a.GetActorTemplateNamespace() != namespace {
if namespace != "" && a.GetActorTemplate().GetAtespace() != "" && a.GetActorTemplate().GetAtespace() != namespace {
continue
}
// Carry the template name as the meta message so the UI's
@@ -422,7 +422,7 @@ func handleActors(w http.ResponseWriter, r *http.Request) {
// substrate Actors there's no equivalent, so the template
// name is the closest semantic match).
msg := ""
if t := a.GetActorTemplateName(); t != "" {
if t := a.GetActorTemplate().GetName(); t != "" {
msg = "template: " + t
}
actors = append(actors, actorSummary{
+2 -2
View File
@@ -473,13 +473,13 @@ The Substrate Control Plane (`ate-api-server`) exposes a gRPC interface for mana
#### `CreateActor`
Registers a new logical actor in the system.
* **Request:** `CreateActorRequest`
* `actor`: `Actor` — the actor to create. Its `metadata` carries the atespace and name (name must be a DNS-1123 label); `actor_template_namespace` and `actor_template_name` select the `ActorTemplate`.
* `actor`: `Actor` — the actor to create. Its `metadata` carries the atespace and name (name must be a DNS-1123 label); the `actor_template` ref (atespace + name) selects the `ActorTemplate`.
* **Response:** the initialized `Actor`.
#### `UpdateActor`
Replaces the mutable fields of an existing actor with the ones in the request.
* **Request:** `UpdateActorRequest`
* `actor`: `Actor` — the complete replacement actor. `metadata.atespace` and `metadata.name` identify the resource; `metadata.uid` and `metadata.version` are **required** preconditions. `metadata` and `status` are server-owned and whatever the request carries in them is ignored. `actor_template_namespace`, `actor_template_name`, `actor_template` and `source_snapshot_tag` are immutable.
* `actor`: `Actor` — the complete replacement actor. `metadata.atespace` and `metadata.name` identify the resource; `metadata.uid` and `metadata.version` are **required** preconditions. `metadata` and `status` are server-owned and whatever the request carries in them is ignored. `source_snapshot_tag` is immutable.
* **Response:** the updated `Actor`.
* **Errors:** `INVALID_ARGUMENT` if `uid` or `version` is unset, or if the request changes an immutable field — including by leaving one unset; `ABORTED` if either guard no longer matches the stored resource.
@@ -54,9 +54,9 @@ func TestFindUnknownFields(t *testing.T) {
in: func() proto.Message {
return &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"},
ActorTemplateName: "tmpl1",
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "team-a", Name: "tmpl1"},
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
},
}
},
@@ -106,7 +106,7 @@ func TestFindUnknownFields(t *testing.T) {
{
name: "several unknown fields on one message collapse to a single error",
in: func() proto.Message {
m := &ateapipb.Actor{ActorTemplateName: "tmpl1"}
m := &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Name: "tmpl1"}}
m.ProtoReflect().SetUnknown(append(unknownField(9998), unknownField(9999)...))
return m
},
@@ -117,8 +117,8 @@ func TestFindUnknownFields(t *testing.T) {
in: func() proto.Message {
return &ateapipb.ListActorsResponse{
Actors: []*ateapipb.Actor{
{ActorTemplateName: "tmpl1"},
withUnknown(&ateapipb.Actor{ActorTemplateName: "tmpl2"}, 9999),
{ActorTemplate: &ateapipb.ObjectRef{Name: "tmpl1"}},
withUnknown(&ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Name: "tmpl2"}}, 9999),
},
}
},
@@ -159,7 +159,7 @@ func TestRejectUnknownFieldsUnaryInterceptor(t *testing.T) {
t.Run("clean request reaches the handler", func(t *testing.T) {
resp, err := RejectUnknownFieldsUnaryInterceptor(context.Background(),
&ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ActorTemplateName: "tmpl1"}}, info, handler)
&ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ActorTemplate: &ateapipb.ObjectRef{Name: "tmpl1"}}}, info, handler)
if err != nil {
t.Fatalf("interceptor error = %v, want nil", err)
}
@@ -191,9 +191,8 @@ func (u *durDirUser) create(ctx context.Context) error {
return u.tracedCall(ctx, "CreateActor", func(callCtx context.Context, tr *metadata.MD) error {
_, err := u.cfg.APIStub.CreateActor(callCtx, &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: u.cfg.Atespace, Name: u.actorName},
ActorTemplateNamespace: templateNS,
ActorTemplateName: u.templateName,
Metadata: &ateapipb.ResourceMetadata{Atespace: u.cfg.Atespace, Name: u.actorName},
ActorTemplate: &ateapipb.ObjectRef{Atespace: templateNS, Name: u.templateName},
},
}, grpc.Trailer(tr))
return err
@@ -204,9 +204,8 @@ func (u *gluttonUser) create(ctx context.Context) error {
return u.tracedCall(ctx, "CreateActor", func(callCtx context.Context, tr *metadata.MD) error {
_, err := u.cfg.APIStub.CreateActor(callCtx, &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: u.cfg.Atespace, Name: u.actorName},
ActorTemplateNamespace: templateNS,
ActorTemplateName: templateName,
Metadata: &ateapipb.ResourceMetadata{Atespace: u.cfg.Atespace, Name: u.actorName},
ActorTemplate: &ateapipb.ObjectRef{Atespace: templateNS, Name: templateName},
},
}, grpc.Trailer(tr))
return err
+133 -248
View File
@@ -859,23 +859,10 @@ type Actor struct {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
Metadata *ResourceMetadata `protobuf:"bytes,1,opt,name=metadata,proto3" json:"metadata,omitempty"`
// The template is named by the legacy CRD namespace/name pair below or by
// the substrate actor_template ref; actor_template takes precedence when
// set.
//
// TODO: delete both fields once we start using actor_template below.
// +k8s:optional
// +k8s:format=k8s-short-name
// +k8s:immutable
ActorTemplateNamespace string `protobuf:"bytes,2,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"`
// +k8s:optional
// +k8s:format=k8s-long-name
// +k8s:immutable
ActorTemplateName string `protobuf:"bytes,3,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"`
// TODO: replace with full actor_template spec if we decide to make each Actor self-contained.
// +k8s:optional # TODO: this should be required?
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable # TODO: is this mutable?
// +k8s:mutable
ActorTemplate *ObjectRef `protobuf:"bytes,4,opt,name=actor_template,json=actorTemplate,proto3" json:"actor_template,omitempty"`
// worker_selector is the per-actor placement constraint. The scheduler
// evaluates the AND of this selector and the template's workerSelector to
@@ -938,20 +925,6 @@ func (x *Actor) GetMetadata() *ResourceMetadata {
return nil
}
func (x *Actor) GetActorTemplateNamespace() string {
if x != nil {
return x.ActorTemplateNamespace
}
return ""
}
func (x *Actor) GetActorTemplateName() string {
if x != nil {
return x.ActorTemplateName
}
return ""
}
func (x *Actor) GetActorTemplate() *ObjectRef {
if x != nil {
return x.ActorTemplate
@@ -1755,14 +1728,12 @@ func (x *ActorSnapshot) GetStatus() *ActorSnapshotStatus {
type ActorSnapshotStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// +k8s:opaqueType
SourceActor *ObjectRef `protobuf:"bytes,1,opt,name=source_actor,json=sourceActor,proto3" json:"source_actor,omitempty"`
SourceActorUid string `protobuf:"bytes,2,opt,name=source_actor_uid,json=sourceActorUid,proto3" json:"source_actor_uid,omitempty"`
SourceActorVersion int64 `protobuf:"varint,3,opt,name=source_actor_version,json=sourceActorVersion,proto3" json:"source_actor_version,omitempty"`
ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"`
ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"`
ActorTemplateUid string `protobuf:"bytes,6,opt,name=actor_template_uid,json=actorTemplateUid,proto3" json:"actor_template_uid,omitempty"`
ContentScope SnapshotContentScope `protobuf:"varint,7,opt,name=content_scope,json=contentScope,proto3,enum=ateapi.SnapshotContentScope" json:"content_scope,omitempty"`
SnapshotUri string `protobuf:"bytes,8,opt,name=snapshot_uri,json=snapshotUri,proto3" json:"snapshot_uri,omitempty"`
SourceActor *ObjectRef `protobuf:"bytes,1,opt,name=source_actor,json=sourceActor,proto3" json:"source_actor,omitempty"`
SourceActorUid string `protobuf:"bytes,2,opt,name=source_actor_uid,json=sourceActorUid,proto3" json:"source_actor_uid,omitempty"`
SourceActorVersion int64 `protobuf:"varint,3,opt,name=source_actor_version,json=sourceActorVersion,proto3" json:"source_actor_version,omitempty"`
ActorTemplateUid string `protobuf:"bytes,6,opt,name=actor_template_uid,json=actorTemplateUid,proto3" json:"actor_template_uid,omitempty"`
ContentScope SnapshotContentScope `protobuf:"varint,7,opt,name=content_scope,json=contentScope,proto3,enum=ateapi.SnapshotContentScope" json:"content_scope,omitempty"`
SnapshotUri string `protobuf:"bytes,8,opt,name=snapshot_uri,json=snapshotUri,proto3" json:"snapshot_uri,omitempty"`
// Immutable reference to the actor_template where the snapshot was created from.
// +k8s:opaqueType
ActorTemplate *ObjectRef `protobuf:"bytes,9,opt,name=actor_template,json=actorTemplate,proto3" json:"actor_template,omitempty"`
@@ -1821,20 +1792,6 @@ func (x *ActorSnapshotStatus) GetSourceActorVersion() int64 {
return 0
}
func (x *ActorSnapshotStatus) GetActorTemplateNamespace() string {
if x != nil {
return x.ActorTemplateNamespace
}
return ""
}
func (x *ActorSnapshotStatus) GetActorTemplateName() string {
if x != nil {
return x.ActorTemplateName
}
return ""
}
func (x *ActorSnapshotStatus) GetActorTemplateUid() string {
if x != nil {
return x.ActorTemplateUid
@@ -5878,14 +5835,11 @@ func (x *WorkerCapacity) GetMemoryBytes() int64 {
// WorkerAssignment.
type ActorAssignment struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The KubeNamespacedObjectRef actor_template names the CRD-backed template
// of an actor created with the legacy CRD path
// actor_template_ref names the substrate ActorTemplate resource.
// Exactly one is set.
ActorTemplate *KubeNamespacedObjectRef `protobuf:"bytes,1,opt,name=actor_template,json=actorTemplate,proto3" json:"actor_template,omitempty"`
// +k8s:opaqueType
Actor *ObjectRef `protobuf:"bytes,2,opt,name=actor,proto3" json:"actor,omitempty"`
ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"`
// actor_template_ref names the substrate ActorTemplate resource the
// assigned actor was created from.
// +k8s:opaqueType
ActorTemplateRef *ObjectRef `protobuf:"bytes,4,opt,name=actor_template_ref,json=actorTemplateRef,proto3" json:"actor_template_ref,omitempty"`
unknownFields protoimpl.UnknownFields
@@ -5922,13 +5876,6 @@ func (*ActorAssignment) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{87}
}
func (x *ActorAssignment) GetActorTemplate() *KubeNamespacedObjectRef {
if x != nil {
return x.ActorTemplate
}
return nil
}
func (x *ActorAssignment) GetActor() *ObjectRef {
if x != nil {
return x.Actor
@@ -5950,58 +5897,6 @@ func (x *ActorAssignment) GetActorTemplateRef() *ObjectRef {
return nil
}
type KubeNamespacedObjectRef struct {
state protoimpl.MessageState `protogen:"open.v1"`
Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *KubeNamespacedObjectRef) Reset() {
*x = KubeNamespacedObjectRef{}
mi := &file_ateapi_proto_msgTypes[88]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KubeNamespacedObjectRef) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KubeNamespacedObjectRef) ProtoMessage() {}
func (x *KubeNamespacedObjectRef) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[88]
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 KubeNamespacedObjectRef.ProtoReflect.Descriptor instead.
func (*KubeNamespacedObjectRef) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{88}
}
func (x *KubeNamespacedObjectRef) GetNamespace() string {
if x != nil {
return x.Namespace
}
return ""
}
func (x *KubeNamespacedObjectRef) GetName() string {
if x != nil {
return x.Name
}
return ""
}
type DebugClearRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
@@ -6010,7 +5905,7 @@ type DebugClearRequest struct {
func (x *DebugClearRequest) Reset() {
*x = DebugClearRequest{}
mi := &file_ateapi_proto_msgTypes[89]
mi := &file_ateapi_proto_msgTypes[88]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6022,7 +5917,7 @@ func (x *DebugClearRequest) String() string {
func (*DebugClearRequest) ProtoMessage() {}
func (x *DebugClearRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[89]
mi := &file_ateapi_proto_msgTypes[88]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6035,7 +5930,7 @@ func (x *DebugClearRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use DebugClearRequest.ProtoReflect.Descriptor instead.
func (*DebugClearRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{89}
return file_ateapi_proto_rawDescGZIP(), []int{88}
}
type DebugClearResponse struct {
@@ -6046,7 +5941,7 @@ type DebugClearResponse struct {
func (x *DebugClearResponse) Reset() {
*x = DebugClearResponse{}
mi := &file_ateapi_proto_msgTypes[90]
mi := &file_ateapi_proto_msgTypes[89]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6058,7 +5953,7 @@ func (x *DebugClearResponse) String() string {
func (*DebugClearResponse) ProtoMessage() {}
func (x *DebugClearResponse) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[90]
mi := &file_ateapi_proto_msgTypes[89]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6071,7 +5966,7 @@ func (x *DebugClearResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use DebugClearResponse.ProtoReflect.Descriptor instead.
func (*DebugClearResponse) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{90}
return file_ateapi_proto_rawDescGZIP(), []int{89}
}
type MintJWTRequest struct {
@@ -6086,7 +5981,7 @@ type MintJWTRequest struct {
func (x *MintJWTRequest) Reset() {
*x = MintJWTRequest{}
mi := &file_ateapi_proto_msgTypes[91]
mi := &file_ateapi_proto_msgTypes[90]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6098,7 +5993,7 @@ func (x *MintJWTRequest) String() string {
func (*MintJWTRequest) ProtoMessage() {}
func (x *MintJWTRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[91]
mi := &file_ateapi_proto_msgTypes[90]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6111,7 +6006,7 @@ func (x *MintJWTRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use MintJWTRequest.ProtoReflect.Descriptor instead.
func (*MintJWTRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{91}
return file_ateapi_proto_rawDescGZIP(), []int{90}
}
func (x *MintJWTRequest) GetAudience() []string {
@@ -6170,7 +6065,7 @@ type MintJWTResponse struct {
func (x *MintJWTResponse) Reset() {
*x = MintJWTResponse{}
mi := &file_ateapi_proto_msgTypes[92]
mi := &file_ateapi_proto_msgTypes[91]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6182,7 +6077,7 @@ func (x *MintJWTResponse) String() string {
func (*MintJWTResponse) ProtoMessage() {}
func (x *MintJWTResponse) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[92]
mi := &file_ateapi_proto_msgTypes[91]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6195,7 +6090,7 @@ func (x *MintJWTResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use MintJWTResponse.ProtoReflect.Descriptor instead.
func (*MintJWTResponse) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{92}
return file_ateapi_proto_rawDescGZIP(), []int{91}
}
func (x *MintJWTResponse) GetActorJwt() string {
@@ -6231,7 +6126,7 @@ type MintCertRequest struct {
func (x *MintCertRequest) Reset() {
*x = MintCertRequest{}
mi := &file_ateapi_proto_msgTypes[93]
mi := &file_ateapi_proto_msgTypes[92]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6243,7 +6138,7 @@ func (x *MintCertRequest) String() string {
func (*MintCertRequest) ProtoMessage() {}
func (x *MintCertRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[93]
mi := &file_ateapi_proto_msgTypes[92]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6256,7 +6151,7 @@ func (x *MintCertRequest) ProtoReflect() protoreflect.Message {
// Deprecated: Use MintCertRequest.ProtoReflect.Descriptor instead.
func (*MintCertRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{93}
return file_ateapi_proto_rawDescGZIP(), []int{92}
}
func (x *MintCertRequest) GetWorker() *ObjectRef {
@@ -6299,7 +6194,7 @@ type MintCertResponse struct {
func (x *MintCertResponse) Reset() {
*x = MintCertResponse{}
mi := &file_ateapi_proto_msgTypes[94]
mi := &file_ateapi_proto_msgTypes[93]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@@ -6311,7 +6206,7 @@ func (x *MintCertResponse) String() string {
func (*MintCertResponse) ProtoMessage() {}
func (x *MintCertResponse) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[94]
mi := &file_ateapi_proto_msgTypes[93]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -6324,7 +6219,7 @@ func (x *MintCertResponse) ProtoReflect() protoreflect.Message {
// Deprecated: Use MintCertResponse.ProtoReflect.Descriptor instead.
func (*MintCertResponse) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{94}
return file_ateapi_proto_rawDescGZIP(), []int{93}
}
func (x *MintCertResponse) GetActorCertificates() [][]byte {
@@ -6372,11 +6267,9 @@ 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\"\x8c\x03\n" +
"\x0fSTATUS_DELETING\x10\x03\"\xa2\x02\n" +
"\x05Actor\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x128\n" +
"\x18actor_template_namespace\x18\x02 \x01(\tR\x16actorTemplateNamespace\x12.\n" +
"\x13actor_template_name\x18\x03 \x01(\tR\x11actorTemplateName\x128\n" +
"\x0eactor_template\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\ractorTemplate\x129\n" +
"\x0fworker_selector\x18\x05 \x01(\v2\x10.ateapi.SelectorR\x0eworkerSelector\x12A\n" +
"\x13source_snapshot_tag\x18\x06 \x01(\v2\x11.ateapi.ObjectRefR\x11sourceSnapshotTag\x12+\n" +
@@ -6424,13 +6317,11 @@ const file_ateapi_proto_rawDesc = "" +
"\rworker_pod_ip\x18\x05 \x01(\tR\vworkerPodIp\"z\n" +
"\rActorSnapshot\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x123\n" +
"\x06status\x18\x02 \x01(\v2\x1b.ateapi.ActorSnapshotStatusR\x06status\"\xdf\x03\n" +
"\x06status\x18\x02 \x01(\v2\x1b.ateapi.ActorSnapshotStatusR\x06status\"\xf5\x02\n" +
"\x13ActorSnapshotStatus\x124\n" +
"\fsource_actor\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\vsourceActor\x12(\n" +
"\x10source_actor_uid\x18\x02 \x01(\tR\x0esourceActorUid\x120\n" +
"\x14source_actor_version\x18\x03 \x01(\x03R\x12sourceActorVersion\x128\n" +
"\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" +
"\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12,\n" +
"\x14source_actor_version\x18\x03 \x01(\x03R\x12sourceActorVersion\x12,\n" +
"\x12actor_template_uid\x18\x06 \x01(\tR\x10actorTemplateUid\x12A\n" +
"\rcontent_scope\x18\a \x01(\x0e2\x1c.ateapi.SnapshotContentScopeR\fcontentScope\x12!\n" +
"\fsnapshot_uri\x18\b \x01(\tR\vsnapshotUri\x128\n" +
@@ -6665,15 +6556,11 @@ const file_ateapi_proto_rawDesc = "" +
"assignment\"P\n" +
"\x0eWorkerCapacity\x12\x1b\n" +
"\tcpu_milli\x18\x01 \x01(\x03R\bcpuMilli\x12!\n" +
"\fmemory_bytes\x18\x02 \x01(\x03R\vmemoryBytes\"\xe0\x01\n" +
"\x0fActorAssignment\x12F\n" +
"\x0eactor_template\x18\x01 \x01(\v2\x1f.ateapi.KubeNamespacedObjectRefR\ractorTemplate\x12'\n" +
"\fmemory_bytes\x18\x02 \x01(\x03R\vmemoryBytes\"\x98\x01\n" +
"\x0fActorAssignment\x12'\n" +
"\x05actor\x18\x02 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\x12\x1b\n" +
"\tactor_uid\x18\x03 \x01(\tR\bactorUid\x12?\n" +
"\x12actor_template_ref\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\x10actorTemplateRef\"K\n" +
"\x17KubeNamespacedObjectRef\x12\x1c\n" +
"\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\"\x13\n" +
"\x12actor_template_ref\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\x10actorTemplateRef\"\x13\n" +
"\x11DebugClearRequest\"\x14\n" +
"\x12DebugClearResponse\"\x84\x01\n" +
"\x0eMintJWTRequest\x12\x1a\n" +
@@ -6785,7 +6672,7 @@ func file_ateapi_proto_rawDescGZIP() []byte {
}
var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 9)
var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 98)
var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 97)
var file_ateapi_proto_goTypes = []any{
(SnapshotContentScope)(0), // 0: ateapi.SnapshotContentScope
(ActorSnapshotTagScope)(0), // 1: ateapi.ActorSnapshotTagScope
@@ -6884,26 +6771,25 @@ var file_ateapi_proto_goTypes = []any{
(*WorkerStatus)(nil), // 94: ateapi.WorkerStatus
(*WorkerCapacity)(nil), // 95: ateapi.WorkerCapacity
(*ActorAssignment)(nil), // 96: ateapi.ActorAssignment
(*KubeNamespacedObjectRef)(nil), // 97: ateapi.KubeNamespacedObjectRef
(*DebugClearRequest)(nil), // 98: ateapi.DebugClearRequest
(*DebugClearResponse)(nil), // 99: ateapi.DebugClearResponse
(*MintJWTRequest)(nil), // 100: ateapi.MintJWTRequest
(*MintJWTResponse)(nil), // 101: ateapi.MintJWTResponse
(*MintCertRequest)(nil), // 102: ateapi.MintCertRequest
(*MintCertResponse)(nil), // 103: ateapi.MintCertResponse
nil, // 104: ateapi.Selector.MatchLabelsEntry
nil, // 105: ateapi.ExternalVolume.VolumeContextEntry
nil, // 106: ateapi.Worker.LabelsEntry
(*timestamppb.Timestamp)(nil), // 107: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 108: google.protobuf.Empty
(*DebugClearRequest)(nil), // 97: ateapi.DebugClearRequest
(*DebugClearResponse)(nil), // 98: ateapi.DebugClearResponse
(*MintJWTRequest)(nil), // 99: ateapi.MintJWTRequest
(*MintJWTResponse)(nil), // 100: ateapi.MintJWTResponse
(*MintCertRequest)(nil), // 101: ateapi.MintCertRequest
(*MintCertResponse)(nil), // 102: ateapi.MintCertResponse
nil, // 103: ateapi.Selector.MatchLabelsEntry
nil, // 104: ateapi.ExternalVolume.VolumeContextEntry
nil, // 105: ateapi.Worker.LabelsEntry
(*timestamppb.Timestamp)(nil), // 106: google.protobuf.Timestamp
(*emptypb.Empty)(nil), // 107: google.protobuf.Empty
}
var file_ateapi_proto_depIdxs = []int32{
0, // 0: ateapi.LocalSnapshotInfo.content_scope:type_name -> ateapi.SnapshotContentScope
104, // 1: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry
107, // 2: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp
107, // 3: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp
103, // 1: ateapi.Selector.match_labels:type_name -> ateapi.Selector.MatchLabelsEntry
106, // 2: ateapi.ResourceMetadata.create_time:type_name -> google.protobuf.Timestamp
106, // 3: ateapi.ResourceMetadata.update_time:type_name -> google.protobuf.Timestamp
8, // 4: ateapi.ExternalVolume.status:type_name -> ateapi.ExternalVolume.Status
105, // 5: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry
104, // 5: ateapi.ExternalVolume.volume_context:type_name -> ateapi.ExternalVolume.VolumeContextEntry
11, // 6: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata
27, // 7: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef
10, // 8: ateapi.Actor.worker_selector:type_name -> ateapi.Selector
@@ -6913,7 +6799,7 @@ var file_ateapi_proto_depIdxs = []int32{
15, // 12: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule
16, // 13: ateapi.EgressRule.hostnames:type_name -> ateapi.HostnameRule
17, // 14: ateapi.EgressRule.ip_blocks:type_name -> ateapi.IPBlockRule
108, // 15: ateapi.EgressRule.all:type_name -> google.protobuf.Empty
107, // 15: ateapi.EgressRule.all:type_name -> google.protobuf.Empty
18, // 16: ateapi.HostnameRule.effects:type_name -> ateapi.EgressRuleEffects
19, // 17: ateapi.EgressRuleEffects.inject_static_headers:type_name -> ateapi.CredentialHeaderInjection
2, // 18: ateapi.ActorStatus.state:type_name -> ateapi.ActorState
@@ -6943,7 +6829,7 @@ var file_ateapi_proto_depIdxs = []int32{
32, // 42: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus
30, // 43: ateapi.Resources.limits:type_name -> ateapi.Limits
27, // 44: ateapi.GoldenSnapshotStatus.golden_snapshot:type_name -> ateapi.ObjectRef
107, // 45: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp
106, // 45: ateapi.GoldenSnapshotStatus.take_golden_snapshot_at:type_name -> google.protobuf.Timestamp
31, // 46: ateapi.ActorTemplateStatus.golden_snapshot_status:type_name -> ateapi.GoldenSnapshotStatus
3, // 47: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass
0, // 48: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope
@@ -7005,91 +6891,90 @@ var file_ateapi_proto_depIdxs = []int32{
27, // 104: ateapi.DrainWorkerRequest.worker:type_name -> ateapi.ObjectRef
13, // 105: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor
11, // 106: ateapi.Worker.metadata:type_name -> ateapi.ResourceMetadata
106, // 107: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry
105, // 107: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry
95, // 108: ateapi.Worker.capacity:type_name -> ateapi.WorkerCapacity
94, // 109: ateapi.Worker.status:type_name -> ateapi.WorkerStatus
6, // 110: ateapi.WorkerStatus.state:type_name -> ateapi.WorkerState
96, // 111: ateapi.WorkerStatus.assignment:type_name -> ateapi.ActorAssignment
97, // 112: ateapi.ActorAssignment.actor_template:type_name -> ateapi.KubeNamespacedObjectRef
27, // 113: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef
27, // 114: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef
27, // 115: ateapi.MintCertRequest.worker:type_name -> ateapi.ObjectRef
7, // 116: ateapi.MintCertRequest.purpose:type_name -> ateapi.ActorCertificatePurpose
62, // 117: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest
63, // 118: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest
64, // 119: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest
65, // 120: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest
67, // 121: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest
69, // 122: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest
71, // 123: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest
72, // 124: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest
73, // 125: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest
74, // 126: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest
75, // 127: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest
76, // 128: ateapi.Control.GetActorSnapshot:input_type -> ateapi.GetActorSnapshotRequest
77, // 129: ateapi.Control.GetActorSnapshotTag:input_type -> ateapi.GetActorSnapshotTagRequest
78, // 130: ateapi.Control.ListActorSnapshots:input_type -> ateapi.ListActorSnapshotsRequest
80, // 131: ateapi.Control.CreateActorSnapshotTag:input_type -> ateapi.CreateActorSnapshotTagRequest
81, // 132: ateapi.Control.UpdateActorSnapshotTag:input_type -> ateapi.UpdateActorSnapshotTagRequest
82, // 133: ateapi.Control.DeleteActorSnapshotTag:input_type -> ateapi.DeleteActorSnapshotTagRequest
84, // 134: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest
86, // 135: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest
87, // 136: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest
88, // 137: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest
89, // 138: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest
90, // 139: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest
91, // 140: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest
52, // 141: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest
53, // 142: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest
54, // 143: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest
56, // 144: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest
57, // 145: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest
58, // 146: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest
59, // 147: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest
61, // 148: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest
98, // 149: ateapi.Debug.DebugClear:input_type -> ateapi.DebugClearRequest
100, // 150: ateapi.ActorIdentity.MintJWT:input_type -> ateapi.MintJWTRequest
102, // 151: ateapi.ActorIdentity.MintCert:input_type -> ateapi.MintCertRequest
13, // 152: ateapi.Control.GetActor:output_type -> ateapi.Actor
13, // 153: ateapi.Control.CreateActor:output_type -> ateapi.Actor
13, // 154: ateapi.Control.UpdateActor:output_type -> ateapi.Actor
66, // 155: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse
68, // 156: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse
70, // 157: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse
13, // 158: ateapi.Control.DeleteActor:output_type -> ateapi.Actor
14, // 159: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 160: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 161: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 162: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy
23, // 163: ateapi.Control.GetActorSnapshot:output_type -> ateapi.ActorSnapshot
25, // 164: ateapi.Control.GetActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
79, // 165: ateapi.Control.ListActorSnapshots:output_type -> ateapi.ListActorSnapshotsResponse
25, // 166: ateapi.Control.CreateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
25, // 167: ateapi.Control.UpdateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
25, // 168: ateapi.Control.DeleteActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
85, // 169: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse
93, // 170: ateapi.Control.GetWorker:output_type -> ateapi.Worker
93, // 171: ateapi.Control.CreateWorker:output_type -> ateapi.Worker
93, // 172: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker
93, // 173: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker
93, // 174: ateapi.Control.DrainWorker:output_type -> ateapi.Worker
92, // 175: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse
26, // 176: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace
26, // 177: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace
55, // 178: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse
26, // 179: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace
28, // 180: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate
28, // 181: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate
60, // 182: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse
28, // 183: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate
99, // 184: ateapi.Debug.DebugClear:output_type -> ateapi.DebugClearResponse
101, // 185: ateapi.ActorIdentity.MintJWT:output_type -> ateapi.MintJWTResponse
103, // 186: ateapi.ActorIdentity.MintCert:output_type -> ateapi.MintCertResponse
152, // [152:187] is the sub-list for method output_type
117, // [117:152] is the sub-list for method input_type
117, // [117:117] is the sub-list for extension type_name
117, // [117:117] is the sub-list for extension extendee
0, // [0:117] is the sub-list for field type_name
27, // 112: ateapi.ActorAssignment.actor:type_name -> ateapi.ObjectRef
27, // 113: ateapi.ActorAssignment.actor_template_ref:type_name -> ateapi.ObjectRef
27, // 114: ateapi.MintCertRequest.worker:type_name -> ateapi.ObjectRef
7, // 115: ateapi.MintCertRequest.purpose:type_name -> ateapi.ActorCertificatePurpose
62, // 116: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest
63, // 117: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest
64, // 118: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest
65, // 119: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest
67, // 120: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest
69, // 121: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest
71, // 122: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest
72, // 123: ateapi.Control.GetActorEgressPolicy:input_type -> ateapi.GetActorEgressPolicyRequest
73, // 124: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest
74, // 125: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest
75, // 126: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest
76, // 127: ateapi.Control.GetActorSnapshot:input_type -> ateapi.GetActorSnapshotRequest
77, // 128: ateapi.Control.GetActorSnapshotTag:input_type -> ateapi.GetActorSnapshotTagRequest
78, // 129: ateapi.Control.ListActorSnapshots:input_type -> ateapi.ListActorSnapshotsRequest
80, // 130: ateapi.Control.CreateActorSnapshotTag:input_type -> ateapi.CreateActorSnapshotTagRequest
81, // 131: ateapi.Control.UpdateActorSnapshotTag:input_type -> ateapi.UpdateActorSnapshotTagRequest
82, // 132: ateapi.Control.DeleteActorSnapshotTag:input_type -> ateapi.DeleteActorSnapshotTagRequest
84, // 133: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest
86, // 134: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest
87, // 135: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest
88, // 136: ateapi.Control.UpdateWorker:input_type -> ateapi.UpdateWorkerRequest
89, // 137: ateapi.Control.DeleteWorker:input_type -> ateapi.DeleteWorkerRequest
90, // 138: ateapi.Control.DrainWorker:input_type -> ateapi.DrainWorkerRequest
91, // 139: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest
52, // 140: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest
53, // 141: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest
54, // 142: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest
56, // 143: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest
57, // 144: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest
58, // 145: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest
59, // 146: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest
61, // 147: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest
97, // 148: ateapi.Debug.DebugClear:input_type -> ateapi.DebugClearRequest
99, // 149: ateapi.ActorIdentity.MintJWT:input_type -> ateapi.MintJWTRequest
101, // 150: ateapi.ActorIdentity.MintCert:input_type -> ateapi.MintCertRequest
13, // 151: ateapi.Control.GetActor:output_type -> ateapi.Actor
13, // 152: ateapi.Control.CreateActor:output_type -> ateapi.Actor
13, // 153: ateapi.Control.UpdateActor:output_type -> ateapi.Actor
66, // 154: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse
68, // 155: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse
70, // 156: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse
13, // 157: ateapi.Control.DeleteActor:output_type -> ateapi.Actor
14, // 158: ateapi.Control.GetActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 159: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 160: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy
14, // 161: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy
23, // 162: ateapi.Control.GetActorSnapshot:output_type -> ateapi.ActorSnapshot
25, // 163: ateapi.Control.GetActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
79, // 164: ateapi.Control.ListActorSnapshots:output_type -> ateapi.ListActorSnapshotsResponse
25, // 165: ateapi.Control.CreateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
25, // 166: ateapi.Control.UpdateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
25, // 167: ateapi.Control.DeleteActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
85, // 168: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse
93, // 169: ateapi.Control.GetWorker:output_type -> ateapi.Worker
93, // 170: ateapi.Control.CreateWorker:output_type -> ateapi.Worker
93, // 171: ateapi.Control.UpdateWorker:output_type -> ateapi.Worker
93, // 172: ateapi.Control.DeleteWorker:output_type -> ateapi.Worker
93, // 173: ateapi.Control.DrainWorker:output_type -> ateapi.Worker
92, // 174: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse
26, // 175: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace
26, // 176: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace
55, // 177: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse
26, // 178: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace
28, // 179: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate
28, // 180: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate
60, // 181: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse
28, // 182: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate
98, // 183: ateapi.Debug.DebugClear:output_type -> ateapi.DebugClearResponse
100, // 184: ateapi.ActorIdentity.MintJWT:output_type -> ateapi.MintJWTResponse
102, // 185: ateapi.ActorIdentity.MintCert:output_type -> ateapi.MintCertResponse
151, // [151:186] is the sub-list for method output_type
116, // [116:151] is the sub-list for method input_type
116, // [116:116] is the sub-list for extension type_name
116, // [116:116] is the sub-list for extension extendee
0, // [0:116] is the sub-list for field type_name
}
func init() { file_ateapi_proto_init() }
@@ -7103,7 +6988,7 @@ func file_ateapi_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_ateapi_proto_rawDesc), len(file_ateapi_proto_rawDesc)),
NumEnums: 9,
NumMessages: 98,
NumMessages: 97,
NumExtensions: 0,
NumServices: 3,
},
+4 -28
View File
@@ -263,24 +263,10 @@ message Actor {
// +k8s:subfield(atespace)=+k8s:required
ResourceMetadata metadata = 1;
// The template is named by the legacy CRD namespace/name pair below or by
// the substrate actor_template ref; actor_template takes precedence when
// set.
//
// TODO: delete both fields once we start using actor_template below.
// +k8s:optional
// +k8s:format=k8s-short-name
// +k8s:immutable
string actor_template_namespace = 2;
// +k8s:optional
// +k8s:format=k8s-long-name
// +k8s:immutable
string actor_template_name = 3;
// TODO: replace with full actor_template spec if we decide to make each Actor self-contained.
// +k8s:optional # TODO: this should be required?
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable # TODO: is this mutable?
// +k8s:mutable
ObjectRef actor_template = 4;
// worker_selector is the per-actor placement constraint. The scheduler
@@ -571,8 +557,6 @@ message ActorSnapshotStatus {
ObjectRef source_actor = 1;
string source_actor_uid = 2;
int64 source_actor_version = 3;
string actor_template_namespace = 4;
string actor_template_name = 5;
string actor_template_uid = 6;
SnapshotContentScope content_scope = 7;
string snapshot_uri = 8;
@@ -1360,23 +1344,15 @@ message WorkerCapacity {
// ActorAssignment names the Actor currently bound to a Worker — the inverse of
// WorkerAssignment.
message ActorAssignment {
// The KubeNamespacedObjectRef actor_template names the CRD-backed template
// of an actor created with the legacy CRD path
// actor_template_ref names the substrate ActorTemplate resource.
// Exactly one is set.
KubeNamespacedObjectRef actor_template = 1;
// +k8s:opaqueType
ObjectRef actor = 2;
string actor_uid = 3;
// actor_template_ref names the substrate ActorTemplate resource the
// assigned actor was created from.
// +k8s:opaqueType
ObjectRef actor_template_ref = 4;
}
message KubeNamespacedObjectRef {
string namespace = 1;
string name = 2;
}
// Debug is the RPC interface for administrative and debugging operations
// (such as wiping state during development).
service Debug {
-35
View File
@@ -9,21 +9,11 @@
"subject": "ateapi.Actor",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.Actor.actor_template_name",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorAssignment.actor",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorAssignment.actor_template_ref",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorAssignment.actor_uid",
@@ -54,16 +44,6 @@
"subject": "ateapi.ActorSnapshotStatus",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorSnapshotStatus.actor_template_name",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorSnapshotStatus.actor_template_namespace",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ActorSnapshotStatus.actor_template_uid",
@@ -349,21 +329,6 @@
"subject": "ateapi.HTTPGetAction.port",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.KubeNamespacedObjectRef",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.KubeNamespacedObjectRef.name",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.KubeNamespacedObjectRef.namespace",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.Limits",