Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions cmd/ateapi/internal/actoridentity/actoridentity.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (

"github.com/agent-substrate/substrate/cmd/ateapi/internal/actoridjwt"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/localca"
"github.com/agent-substrate/substrate/internal/localjwtauthority"
"github.com/agent-substrate/substrate/internal/principal"
Expand All @@ -54,19 +53,17 @@ type Server struct {

// store is the actor database. MintCert consults it to confirm the caller
// is entitled to the actor it is asking for a credential for.
store store.Interface
workers *workercache.Cache
store store.Interface
}

var _ ateapipb.ActorIdentityServer = (*Server)(nil)

func New(actorIdentityJWTIssuer, actorIDJWTPoolFile, actorIDCAPoolFile string, store store.Interface, workers *workercache.Cache) *Server {
func New(actorIdentityJWTIssuer, actorIDJWTPoolFile, actorIDCAPoolFile string, store store.Interface) *Server {
return &Server{
actorIdentityJWTIssuer: actorIdentityJWTIssuer,
actorIDJWTPoolFile: actorIDJWTPoolFile,
actorIDCAPoolFile: actorIDCAPoolFile,
store: store,
workers: workers,
}
}

Expand Down Expand Up @@ -312,7 +309,7 @@ func (s *Server) authorizeActor(ctx context.Context, caller *ateletCaller, req *
return status.Errorf(codes.PermissionDenied, "caller is not permitted to mint credentials for this actor")
}

worker, err := s.workers.Worker(req.GetWorkerNamespace(), req.GetWorkerPod())
worker, err := s.store.GetWorker(ctx, req.GetWorkerNamespace(), req.GetWorkerPod())
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return nil, resources.ActorRef{}, deny("worker not found")
Expand Down
67 changes: 46 additions & 21 deletions cmd/ateapi/internal/actoridentity/actoridentity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/localca"
"github.com/agent-substrate/substrate/internal/principal"
"github.com/agent-substrate/substrate/internal/resources"
Expand Down Expand Up @@ -131,9 +130,8 @@ func ctxWithCert(cert *x509.Certificate) context.Context {
})
}

// newTestServer returns a Server backed by st, with a freshly generated actor
// CA pool written to a temp file.
func newTestServer(t *testing.T, st store.Interface) *Server {
// newActorCAPoolFile writes a freshly generated actor CA pool to a temp file.
func newActorCAPoolFile(t *testing.T) string {
t.Helper()

ca, err := localca.GenerateED25519CA("test-actor-ca")
Expand All @@ -148,17 +146,15 @@ func newTestServer(t *testing.T, st store.Interface) *Server {
if err := os.WriteFile(poolFile, poolBytes, 0o600); err != nil {
t.Fatalf("write CA pool: %v", err)
}
return poolFile
}

var workers *workercache.Cache
if st != nil {
workers = workercache.New(st, time.Hour)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
if err := workers.Start(ctx); err != nil {
t.Fatalf("start worker cache: %v", err)
}
}
return New("issuer", "", poolFile, st, workers)
// newTestServer returns a Server backed by st, with a freshly generated actor
// CA pool written to a temp file.
func newTestServer(t *testing.T, st store.Interface) *Server {
t.Helper()

return New("issuer", "", newActorCAPoolFile(t), st)
}

func TestMintJWTRequiresConfiguredJWTProvider(t *testing.T) {
Expand Down Expand Up @@ -707,6 +703,41 @@ func TestMintCertDeniesUnassignedActorWhateverItsStatus(t *testing.T) {
}
}

// TestMintCertUsesAnAssignmentAsSoonAsItIsWritten guards the invariant the gate
// rests on: it reads the store, so an assignment is usable the instant resume
// writes it. Behind a replica-local cache, atelet mints inside the lag window.
func TestMintCertUsesAnAssignmentAsSoonAsItIsWritten(t *testing.T) {
ctx := context.Background()
st, cleanup := storetest.SetupTestStore(t)
defer cleanup()

// The state pause leaves behind: the worker exists but holds no assignment.
seedActor(t, ctx, st, actorFixture{
status: ateapipb.Actor_STATUS_RESUMING,
workerNode: testNode,
unassigned: true,
})
srv := newTestServer(t, st)

actorRef := resources.ActorRef{Atespace: testAtespace, Name: testActorName}
actor, err := st.GetActor(ctx, actorRef)
if err != nil {
t.Fatal(err)
}
worker, err := st.GetWorker(ctx, testPodNS, testWorkerPod)
if err != nil {
t.Fatal(err)
}
worker.Assignment = &ateapipb.Assignment{Actor: actorRef.ToObjectRef(), ActorUid: actor.GetMetadata().GetUid()}
if err := st.UpdateWorker(ctx, worker, worker.GetVersion()); err != nil {
t.Fatalf("assign worker: %v", err)
}

if _, err := srv.MintCert(ctxWithCert(ateletCertOn(t, testNode)), mintCertRequest(t, actor.GetMetadata().GetUid())); err != nil {
t.Errorf("MintCert() error = %v, want success from the freshly written assignment", err)
}
}

// TestMintCertAuthorizesBeforeSigning checks that the gate runs before any CSR
// parsing or CA material is touched. An unauthorized caller must be rejected
// with PermissionDenied even when the rest of the request is unusable, so that
Expand All @@ -720,13 +751,7 @@ func TestMintCertAuthorizesBeforeSigning(t *testing.T) {

// A server whose CA pool file does not exist: reaching the signing path at
// all would surface as Internal rather than PermissionDenied.
workers := workercache.New(st, time.Hour)
cacheCtx, cancel := context.WithCancel(ctx)
defer cancel()
if err := workers.Start(cacheCtx); err != nil {
t.Fatal(err)
}
srv := New("issuer", "", filepath.Join(t.TempDir(), "missing.json"), st, workers)
srv := New("issuer", "", filepath.Join(t.TempDir(), "missing.json"), st)

actor, err := st.GetActor(ctx, resources.ActorRef{Atespace: testAtespace, Name: testActorName})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/ateapi/internal/controlapi/crash.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func releaseWorker(ctx context.Context, st store.Interface, actor *ateapipb.Acto
}
podUid := assignment.GetWorkerPodUid()

worker, err := st.GetWorker(ctx, assignment.GetWorkerNamespace(), assignment.GetWorkerPool(), assignment.GetWorkerPod())
worker, err := st.GetWorker(ctx, assignment.GetWorkerNamespace(), assignment.GetWorkerPod())
if errors.Is(err, store.ErrNotFound) {
// No need to release if the worker is not found.
slog.WarnContext(ctx, "Worker already gone while crashing actor, skipping release", slog.String("worker", podUid))
Expand Down
8 changes: 4 additions & 4 deletions cmd/ateapi/internal/controlapi/crash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestCrashActor(t *testing.T) {
t.Fatalf("crashActor() = %v, want nil", err)
}
assertCrashed(t, ctx, st, actorRef)
worker, gerr := st.GetWorker(ctx, "ns", "pool", "pod")
worker, gerr := st.GetWorker(ctx, "ns", "pod")
if gerr != nil {
t.Fatalf("GetWorker() = %v, want nil", gerr)
}
Expand All @@ -165,7 +165,7 @@ func TestCrashActor(t *testing.T) {
t.Fatalf("crashActor() = %v, want nil", err)
}
assertCrashed(t, ctx, st, actorRef)
worker, gerr := st.GetWorker(ctx, "ns", "pool", "pod")
worker, gerr := st.GetWorker(ctx, "ns", "pod")
if gerr != nil {
t.Fatalf("GetWorker() = %v, want nil", gerr)
}
Expand Down Expand Up @@ -200,7 +200,7 @@ func TestCrashActor(t *testing.T) {
t.Fatalf("crashActor() = %v, want nil", err)
}
assertCrashed(t, ctx, st, actorRef)
worker, gerr := st.GetWorker(ctx, "ns", "pool", "pod")
worker, gerr := st.GetWorker(ctx, "ns", "pod")
if gerr != nil {
t.Fatalf("GetWorker() = %v, want nil", gerr)
}
Expand All @@ -227,7 +227,7 @@ func TestCrashActor(t *testing.T) {
// Without a binding the worker cannot be looked up, so its
// assignment must be left untouched even though it names
// the crashed actor.
worker, gerr := st.GetWorker(ctx, "ns", "pool", "pod")
worker, gerr := st.GetWorker(ctx, "ns", "pod")
if gerr != nil {
t.Fatalf("GetWorker() = %v, want nil", gerr)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/ateapi/internal/controlapi/functional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2725,7 +2725,7 @@ func TestResumeActor_CrashesIfAssignedWorkerIsDraining(t *testing.T) {
t.Fatalf("expected actor to be bound to a worker after the failed attempt")
}

assigned, err := tc.persistence.GetWorker(context.Background(), ns, "pool1", assignedPod)
assigned, err := tc.persistence.GetWorker(context.Background(), ns, assignedPod)
if err != nil {
t.Fatalf("GetWorker(%s) failed: %v", assignedPod, err)
}
Expand Down
Loading
Loading