Files
substrate/cmd/ateapi/internal/controlapi/service.go
T
Tim Hockin e2f2b1ebe9 Use more specific interfaces than store.Interface (#986)
This makes it clear(er) which methods of store.Interface are used in
which paths.

No functional changes

@juli4n @laoj2 as a first step towards splitting the validation logic
out of the RPC layer and out of the storage layer. This at least gives
us targets for what to pay attention to first.
2026-08-16 19:03:03 -04:00

124 lines
5.5 KiB
Go

// 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"
"sync"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
"github.com/agent-substrate/substrate/cmd/ateapi/internal/workercache"
"github.com/agent-substrate/substrate/internal/resources"
"github.com/agent-substrate/substrate/internal/volume"
"github.com/agent-substrate/substrate/internal/volume/csi"
listersv1alpha1 "github.com/agent-substrate/substrate/pkg/client/listers/api/v1alpha1"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
storagev1listers "k8s.io/client-go/listers/storage/v1"
)
// Service implements ateapipb.Control
type Service struct {
ateapipb.UnimplementedControlServer
persistence serviceStore
workerCache *workercache.Cache
dialer *AteletDialer
actorTemplateLister listersv1alpha1.ActorTemplateLister
workerPoolLister listersv1alpha1.WorkerPoolLister
csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister
storageClassLister storagev1listers.StorageClassLister
actorWorkflow *ActorWorkflow
instruments *Instruments
mu sync.RWMutex
volumePlugins map[string]volume.VolumePluginControlPlane
}
var _ ateapipb.ControlServer = (*Service)(nil)
// VolumePluginRegistry defines the interface for dynamic CSI plugin resolution.
type VolumePluginRegistry interface {
GetPlugin(ctx context.Context, name string) (volume.VolumePluginControlPlane, error)
}
// NewService creates a service. instruments may be nil; the record helpers no-op.
func NewService(
persistence store.Interface,
workerCache *workercache.Cache,
actorTemplateLister listersv1alpha1.ActorTemplateLister,
workerPoolLister listersv1alpha1.WorkerPoolLister,
sandboxConfigLister listersv1alpha1.SandboxConfigLister,
csiDriverConfigLister listersv1alpha1.CSIDriverConfigLister,
storageClassLister storagev1listers.StorageClassLister,
dialer *AteletDialer,
instruments *Instruments,
egressGatewayAddress string,
volumePlugins map[string]volume.VolumePluginControlPlane,
) *Service {
s := &Service{
persistence: persistence,
workerCache: workerCache,
actorTemplateLister: actorTemplateLister,
workerPoolLister: workerPoolLister,
csiDriverConfigLister: csiDriverConfigLister,
storageClassLister: storageClassLister,
dialer: dialer,
instruments: instruments,
volumePlugins: volumePlugins,
}
s.actorWorkflow = NewActorWorkflow(persistence, workerCache, dialer, actorTemplateLister, workerPoolLister, sandboxConfigLister, storageClassLister, instruments, egressGatewayAddress, s)
return s
}
// serviceStore enumerates the exact storage methods needed by
// the control API and nothing more.
type serviceStore interface {
CreateActor(ctx context.Context, actor *ateapipb.Actor) (*ateapipb.Actor, error)
GetActor(ctx context.Context, actorRef resources.ActorRef) (*ateapipb.Actor, error)
UpdateActor(ctx context.Context, actorRef resources.ActorRef, mutate func(toUpdate *ateapipb.Actor) error) (*ateapipb.Actor, error)
ListActors(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.Actor], error)
GetActorSnapshot(ctx context.Context, atespace, name string) (*ateapipb.ActorSnapshot, error)
ListActorSnapshots(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.ActorSnapshot], error)
CreateActorSnapshotTag(ctx context.Context, atespace, name string, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error)
GetActorSnapshotTag(ctx context.Context, atespace, name string) (*ateapipb.ActorSnapshotTag, error)
UpdateActorSnapshotTag(ctx context.Context, atespace, name string, mutate func(toUpdate *ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error)
DeleteActorSnapshotTag(ctx context.Context, atespace, name string) (*ateapipb.ActorSnapshotTag, error)
AtespaceExists(ctx context.Context, name string) (bool, error)
CreateAtespace(ctx context.Context, atespace *ateapipb.Atespace) (*ateapipb.Atespace, error)
GetAtespace(ctx context.Context, name string) (*ateapipb.Atespace, error)
ListAtespaces(ctx context.Context, opts store.ListOptions) (store.ListResponse[*ateapipb.Atespace], error)
DeleteAtespace(ctx context.Context, name string) (*ateapipb.Atespace, error)
ListWorkers(ctx context.Context, opts store.ListOptions) (store.ListResponse[*ateapipb.Worker], error)
AcquireLock(ctx context.Context, key string) (*store.Lock, error)
}
// GetPlugin retrieves a CSI volume plugin by driver name, dynamically discovering it if not present.
func (s *Service) GetPlugin(ctx context.Context, driverName string) (volume.VolumePluginControlPlane, error) {
s.mu.RLock()
plugin, ok := s.volumePlugins[driverName]
s.mu.RUnlock()
if ok {
return plugin, nil
}
csiPlugin, err := csi.NewCSIPlugin(ctx, s.csiDriverConfigLister, driverName, true /*isController*/)
if err != nil {
return nil, err
}
s.mu.Lock()
s.volumePlugins[driverName] = csiPlugin
s.mu.Unlock()
return csiPlugin, nil
}