~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
172 lines · 4.8 KB Raw History
package compose

import (
	"cmp"
	"context"
	"net/url"
	"slices"
	"strings"
	"sync"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	eventsv1alpha1 "github.com/johnandersen777/socialweb-computer-kcp/api/events/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	ctlruntime "github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/controllers/runtime/kube"
)

const (
	RequesterStreamRole = "requester"

	BidderStreamRole = "bidder"

	retiredBidderSourceLabel = "socialweb.computer/bidder-source"

	requesterSourcePrefix = "requesters-"

	requesterSourceValue = "requesters-"

	MaxRequesterStreamDIDs = 100
)

func RequesterSourceName(kind, raw string) string {
	host := raw
	if u, err := url.Parse(strings.TrimSpace(raw)); err == nil && u.Host != "" {
		host = u.Host
	}
	return ObjectName(requesterSourcePrefix+kind, host)
}

func liveComputeVM(vm *v1alpha1.ComputeVM) bool {
	if vm.DeletionTimestamp != nil || strings.TrimSpace(vm.Spec.Requester.Did) == "" {
		return false
	}
	return vm.Status.Phase != v1alpha1.ComputeVMPhaseLost && vm.Status.Phase != v1alpha1.ComputeVMPhaseFailed
}

func (c *Composer) liveRequesters(ctx context.Context, ws string) ([]string, error) {
	ri, err := c.Client(ws, v1alpha1.ComputeVMGVR)
	if err != nil {
		return nil, err
	}
	vms, err := kube.List[v1alpha1.ComputeVM](ctx, ri, metav1.ListOptions{})
	if apierrors.IsNotFound(err) || apierrors.IsForbidden(err) {
		return nil, nil
	}
	if err != nil {
		return nil, err
	}
	var out []string
	for i := range vms {
		if did := strings.TrimSpace(vms[i].Spec.Requester.Did); liveComputeVM(&vms[i]) && !slices.Contains(out, did) {
			out = append(out, did)
		}
	}
	slices.Sort(out)
	if len(out) > MaxRequesterStreamDIDs {
		out = out[:MaxRequesterStreamDIDs]
	}
	return out, nil
}

func requesterSources(comms *v1alpha1.MarketComms, network *v1alpha1.MarketNetworkValue, requesters []string) []rootSource {
	if len(requesters) == 0 {
		return nil
	}
	relays := comms.FirehoseRelays(network)
	if relay := network.RelayURL(); relay != "" && (comms == nil || comms.RelayURLs == nil) {
		relays = []string{relay}
	}
	var jetstreams []string
	switch {
	case comms != nil && comms.JetstreamURLs != nil:
		jetstreams = comms.FirehoseJetstreams()
	case network.RelayURL() == "":
		jetstreams = []string{PublicJetstreamEastURL}
	}
	var out []rootSource
	for _, u := range relays {
		out = append(out, rootSource{Name: RequesterSourceName("relay", u), Relay: &eventsv1alpha1.RelayEventSourceSpec{URL: u}})
	}
	for _, u := range jetstreams {
		out = append(out, rootSource{Name: RequesterSourceName("jetstream", u), Jetstream: &eventsv1alpha1.JetstreamEventSourceSpec{URL: u}})
	}
	return out
}

func (c *Composer) pruneRetiredBidderSources(ctx context.Context, ws, role string) error {
	selector := retiredBidderSourceLabel + "=" + role
	if err := c.Prune(ctx, ws, eventsv1alpha1.EventSourceGVR, selector, nil); err != nil {
		return err
	}
	return c.Prune(ctx, ws, eventsv1alpha1.RelayEventSourceGVR, selector, nil)
}

func (c *Composer) logRequesterStreams(ws, role string, dids []string, names []string) {
	set := strings.Join(names, ",") + "[" + strings.Join(dids, ",") + "]"
	if len(names) == 0 {
		set = ""
	}
	if last, ok := c.requesterSets.Load(ws + "|" + role); ok && last == set {
		return
	}
	c.requesterSets.Store(ws+"|"+role, set)
	c.log.Info("compose_requester_streams", "workspace", ws, "role", role, "streams", len(names), "requesters", len(dids), "set", cmp.Or(set, "none"))
}

type requesterIndex struct {
	mu sync.Mutex

	byWorkspace map[string]map[string]string
}

func (x *requesterIndex) note(workspace, name, did string) bool {
	x.mu.Lock()
	defer x.mu.Unlock()
	if x.byWorkspace == nil {
		x.byWorkspace = map[string]map[string]string{}
	}
	vms := x.byWorkspace[workspace]
	if vms == nil {
		vms = map[string]string{}
		x.byWorkspace[workspace] = vms
	}
	prior, known := vms[name]
	if prior == did && (known || did == "") {
		return false
	}
	count := func(d string) int {
		n := 0
		for _, v := range vms {
			if v == d {
				n++
			}
		}
		return n
	}
	changed := (prior != "" && count(prior) == 1) || (did != "" && count(did) == 0)
	if did == "" {
		delete(vms, name)
	} else {
		vms[name] = did
	}
	return changed
}

func ComputeVMWatch(c *Composer, export, rootKind string) ctlruntime.Option {
	return ctlruntime.WatchesIn(export, func(_ context.Context, src ctlruntime.Request, vm *v1alpha1.ComputeVM) []ctlruntime.Request {
		out := c.deletingIn(rootKind, src.Workspace)
		did := ""
		if liveComputeVM(vm) {
			did = strings.TrimSpace(vm.Spec.Requester.Did)
		}
		if c.requesters.note(src.Cluster+"|"+src.Workspace, vm.Name, did) {
			for _, name := range c.rootsIn(src.Cluster, rootKind) {
				out = append(out, ctlruntime.Request{Cluster: src.Cluster, Name: name})
			}
		}
		return out
	})
}