~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
311 lines · 8.5 KB Raw History
package marketintents

import (
	"context"
	"errors"
	"fmt"
	"log/slog"
	"strings"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"

	exposurev1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/exposure/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bus"
	market "github.com/johnandersen777/socialweb-computer-kcp/internal/contract/market"
	ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
)

const (
	Name = "marketintents"

	ExportComputeVMs = "computevms"

	ExportApplications = "applications"

	ExportComputeContracts = "computecontracts"

	ExportExposures = "exposures"

	ExportMarketIdentities = "marketidentities"
)

type Options struct {
	Bus *bus.Bus

	Clients func(workspace string) (client.Client, error)

	Scope func(workspace string) bool

	Log *slog.Logger
}

type key struct {
	workspace string

	kind string

	name string
}

type subscriber struct {
	workspaces map[string]bool

	kinds map[string]bool

	observer market.Observer

	synced func()

	pending map[key]bool

	fired bool

	ctx context.Context
}

type Controller struct {
	clients func(workspace string) (client.Client, error)

	scope func(workspace string) bool

	log *slog.Logger

	mu sync.Mutex

	clusters map[key]string

	subs map[*subscriber]struct{}

	events map[string]*ctlruntime.Queue

	owners map[string]ownerUID

	locksMu sync.Mutex

	statusLocks map[string]*statusLock
}

type statusLock struct {
	mu sync.Mutex

	users int
}

func (c *Controller) lockStatus(key string) func() {
	c.locksMu.Lock()
	if c.statusLocks == nil {
		c.statusLocks = map[string]*statusLock{}
	}
	lock := c.statusLocks[key]
	if lock == nil {
		lock = &statusLock{}
		c.statusLocks[key] = lock
	}
	lock.users++
	c.locksMu.Unlock()
	lock.mu.Lock()
	return func() {
		lock.mu.Unlock()
		c.locksMu.Lock()
		if lock.users--; lock.users == 0 {
			delete(c.statusLocks, key)
		}
		c.locksMu.Unlock()
	}
}

type ownerUID struct {
	uid string

	at time.Time
}

func Register(m *ctlruntime.Manager, opts Options) (*Controller, error) {
	if opts.Bus == nil {
		return nil, errors.New("marketintents: the bus is required")
	}
	if opts.Clients == nil {
		return nil, errors.New("marketintents: the workspace clients are required")
	}
	log := opts.Log
	if log == nil {
		log = slog.New(slog.DiscardHandler)
	}
	c := &Controller{clients: opts.Clients, scope: opts.Scope, log: log.With("component", "marketintents"), clusters: map[key]string{},
		subs: map[*subscriber]struct{}{}, events: map[string]*ctlruntime.Queue{}, owners: map[string]ownerUID{}}
	err := errors.Join(
		forward(m, c, opts.Scope, market.KindBid, ExportComputeVMs, "", 4, func(ch *market.Change, o *v1alpha1.Bid) { ch.Bid = o }),
		forward(m, c, opts.Scope, market.KindComputeVM, ExportComputeVMs, "", 2, func(ch *market.Change, o *v1alpha1.ComputeVM) { ch.ComputeVM = o }),
		forward(m, c, opts.Scope, market.KindStateSync, ExportApplications, "", 4, func(ch *market.Change, o *v1alpha1.StateSync) { ch.StateSync = o }),
		forward(m, c, opts.Scope, market.KindComputeContract, ExportComputeContracts, v1alpha1.FinalizerComputeContract, 4,
			func(ch *market.Change, o *v1alpha1.ComputeContract) { ch.ComputeContract = o }),
		forward(m, c, opts.Scope, market.KindExposure, ExportExposures, "", 1, func(ch *market.Change, o *exposurev1alpha1.Exposure) { ch.Exposure = o }),
		forward(m, c, opts.Scope, market.KindMarketIdentity, ExportMarketIdentities, "", 1,
			func(ch *market.Change, o *v1alpha1.MarketIdentity) { ch.MarketIdentity = o }),
	)
	if err != nil {
		return nil, err
	}
	for _, kind := range []string{"Bidder", "Requester"} {
		bus.Provide[market.Intents](opts.Bus, schema.GroupKind{Group: v1alpha1.GroupName, Kind: kind}, market.CapabilityIntents, c.open)
	}
	bus.Provide[market.SessionOwnerIntents](opts.Bus, schema.GroupKind{Group: v1alpha1.GroupName, Kind: KindSshServer}, market.CapabilitySessionOwners, c.openSessionOwners)
	return c, nil
}

type object[T any] interface {
	ctlruntime.Object
	DeepCopy() T
}

type forwarder[T object[T]] struct {
	c *Controller

	kind string

	finalizer string

	fill func(*market.Change, T)
}

func forward[T object[T]](m *ctlruntime.Manager, c *Controller, scope func(string) bool, kind, export, finalizer string, workers int,
	fill func(*market.Change, T)) error {
	queue := ctlruntime.NewQueue()
	events, err := queue.Run(m, Name+"-"+strings.ToLower(kind)+": replay")
	if err != nil {
		return fmt.Errorf("marketintents: the %s replay: %w", kind, err)
	}
	c.mu.Lock()
	c.events[kind] = queue
	c.mu.Unlock()
	f := &forwarder[T]{c: c, kind: kind, finalizer: finalizer, fill: fill}
	opts := []ctlruntime.Option{ctlruntime.Export(export), ctlruntime.Events(events), ctlruntime.Gone(f.gone), ctlruntime.Workers(workers),
		ctlruntime.Watches(func(_ context.Context, src ctlruntime.Request, obj T) []ctlruntime.Request {
			return []ctlruntime.Request{{Cluster: src.Cluster, Namespace: obj.GetNamespace(), Name: obj.GetName()}}
		})}
	if finalizer != "" {
		opts = append(opts, ctlruntime.Finalizer(finalizer))
	}
	if scope != nil {
		opts = append(opts, ctlruntime.InWorkspaces(scope))
	}
	if err := ctlruntime.For[T](m, Name+"-"+strings.ToLower(kind), f, opts...); err != nil {
		return fmt.Errorf("marketintents: registering the %s forwarder: %w", kind, err)
	}
	return nil
}

func (f *forwarder[T]) Reconcile(ctx context.Context, req ctlruntime.Request, obj T) (ctlruntime.Result, error) {
	k := key{workspace: req.Workspace, kind: f.kind, name: req.Name}
	f.c.mu.Lock()
	f.c.clusters[k] = req.Cluster
	f.c.mu.Unlock()
	change := market.Change{Workspace: req.Workspace, Kind: f.kind, Name: req.Name}
	f.fill(&change, obj.DeepCopy())
	wait, claimed, err := f.c.deliver(ctx, change)
	waiting := f.finalizer != "" && !obj.GetDeletionTimestamp().IsZero() && (wait > 0 || (claimed == 0 && holdsGuest(obj)))
	return ctlruntime.Result{RequeueAfter: wait, Waiting: waiting}, err
}

func holdsGuest(obj any) bool {
	if h, ok := obj.(interface{ HoldsGuest() bool }); ok {
		return h.HoldsGuest()
	}
	return true
}

func (f *forwarder[T]) gone(ctx context.Context, req ctlruntime.Request) {
	k := key{workspace: req.Workspace, kind: f.kind, name: req.Name}
	f.c.mu.Lock()
	delete(f.c.clusters, k)
	f.c.mu.Unlock()
	if _, _, err := f.c.deliver(ctx, market.Change{Workspace: req.Workspace, Kind: f.kind, Name: req.Name, Gone: true}); err != nil {
		f.c.log.Warn("delivering a deletion failed", "kind", f.kind, "workspace", req.Workspace, "name", req.Name, "err", err.Error())
	}
}

func (c *Controller) deliver(ctx context.Context, change market.Change) (time.Duration, int, error) {
	k := key{workspace: change.Workspace, kind: change.Kind, name: change.Name}
	c.mu.Lock()
	var targets []*subscriber
	for s := range c.subs {
		if s.matches(change.Workspace, change.Kind) {
			targets = append(targets, s)
		}
	}
	c.mu.Unlock()
	var wait time.Duration
	var claimed int
	var errs []error
	for _, s := range targets {
		if s.kinds[change.Kind] {
			claimed++
		}
		d, err := s.observer.Observe(ctx, change)
		if err != nil {
			errs = append(errs, err)
		}
		if d > 0 && (wait == 0 || d < wait) {
			wait = d
		}
		c.observed(s, k)
	}
	return wait, claimed, errors.Join(errs...)
}

func (c *Controller) subscribe(ctx context.Context, home string, sub market.Subscription) (func(), error) {
	if sub.Observer == nil {
		return nil, errors.New("marketintents: a subscription needs an observer")
	}
	s := &subscriber{workspaces: map[string]bool{home: true}, kinds: map[string]bool{}, observer: sub.Observer, synced: sub.Synced, ctx: ctx}
	for _, ws := range sub.Workspaces {
		if ws = strings.TrimSpace(ws); ws != "" {
			s.workspaces[ws] = true
		}
	}
	for _, kind := range sub.Kinds {
		if kind = strings.TrimSpace(kind); kind != "" {
			s.kinds[kind] = true
		}
	}
	var listed map[key]string
	if s.synced != nil {
		var err error
		if listed, err = c.listed(ctx, s); err != nil {
			return nil, err
		}
	}
	c.mu.Lock()
	c.subs[s] = struct{}{}
	replay := c.replayLocked(s)
	if s.synced != nil {
		s.pending = map[key]bool{}
		for k, annotated := range listed {
			if _, known := c.clusters[k]; known {
				s.pending[k] = true
				continue
			}
			if cluster := c.clusterOfLocked(k, annotated); cluster != "" {
				s.pending[k] = true
				replay[k.kind] = append(replay[k.kind], ctlruntime.Request{Cluster: cluster, Name: k.name})
			}
		}
	}
	c.mu.Unlock()
	c.enqueue(replay)
	c.settle(s)
	var once sync.Once
	cancel := func() {
		once.Do(func() {
			c.mu.Lock()
			delete(c.subs, s)
			c.mu.Unlock()
		})
	}
	context.AfterFunc(ctx, cancel)
	return cancel, nil
}