Ctrl K
ComunidadGuías…

pkg/k8score/read_scope.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package k8score

import (
	"slices"

	apiruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/tools/cache"
)

// kindStore is one informer's store for a kind and that informer's own sync.
type kindStore struct {
	indexer cache.Indexer
	synced  func() bool
}

func (rc *ResourceCache) addKindStore(key, namespace string, inf cache.SharedIndexInformer) {
	if rc.kindStores[key] == nil {
		rc.kindStores[key] = map[string]kindStore{}
	}
	rc.kindStores[key][namespace] = kindStore{indexer: inf.GetIndexer(), synced: inf.HasSynced}
}

// storeFor returns the store holding namespace's objects: that namespace's own
// informer, or the one informer serving the kind's whole scope.
func (rc *ResourceCache) storeFor(key, namespace string) (kindStore, bool) {
	stores := rc.kindStores[key]
	if st, ok := stores[namespace]; ok {
		return st, true
	}
	st, ok := stores[""]
	return st, ok
}

// pastDeadline reports a tracked kind whose deferred sync deadline fired.
// Background kinds (Events) have no deadline, and a kind mid-Phase-1 is not in
// the tracking map, so neither is ever past it.
func (rc *ResourceCache) pastDeadline(key string) bool {
	if !rc.deferredFailed.Load() || rc.backgroundKeys[key] {
		return false
	}
	rc.deferredMu.RLock()
	_, tracked := rc.deferredSynced[key]
	rc.deferredMu.RUnlock()
	return tracked
}

func (rc *ResourceCache) storeReadiness(key string, st kindStore) KindReadiness {
	if st.synced() {
		return KindReady
	}
	if rc.pastDeadline(key) {
		return KindFailed
	}
	return KindPending
}

// KindReadAssessment contains full membership facts for one cache handle.
// Requested nil means all namespaces; an empty non-nil set means no target.
// Consumers must project namespaces to what the caller may see.
type KindReadAssessment struct {
	// Readiness is the whole kind's state; it is Ready only once every
	// informer for the kind has synced.
	Readiness KindReadiness
	Requested []string
	// AllNamespaces: one informer covers every namespace, so each namespace's
	// state is Readiness.
	AllNamespaces bool
	// Read, Loading, Failed and Uncovered partition the target: Requested, or
	// for an all-namespaces request on a namespace-scoped collector, the
	// namespaces it collects. A namespace is Read when the store holding it is
	// complete now; Failed when its load missed the sync deadline. Uncovered
	// is known from configuration before any sync. An all-namespaces request
	// on an all-namespaces informer has no partition: Readiness is its state
	// and Read is nil. So is a kind Radar doesn't watch (Readiness
	// Unavailable), which has nothing to assess. Otherwise Read is non-nil
	// even when empty, because readers take a nil namespace list to mean
	// every namespace.
	Read, Loading, Failed, Uncovered []string
	// Complete: every requested namespace is readable now; an empty target is
	// complete. An all-namespaces request (Requested nil) is complete only on
	// one informer over every namespace that has loaded: a namespace-scoped
	// cache never collects every namespace.
	Complete bool
}

// AssessKindRead assesses an already authorized namespace target without I/O.
// It describes the scope this cache can serve now, not a read that happened.
func (rc *ResourceCache) AssessKindRead(resource string, requested []string) KindReadAssessment {
	a := KindReadAssessment{Readiness: rc.KindReadinessFor(resource), Requested: slices.Clone(requested)}
	if a.Readiness == KindUnavailable {
		return a
	}
	covered := rc.KindNamespaces(resource)
	if covered == nil {
		a.AllNamespaces = true
		if requested == nil {
			a.Complete = a.Readiness == KindReady
			return a
		}
		a.Read = []string{}
		a.assign(a.Readiness, slices.Clone(requested)...)
		a.Complete = len(a.Loading) == 0 && len(a.Failed) == 0
		return a
	}
	a.Read = []string{}
	target := requested
	if target == nil {
		target = covered
	}
	isCovered := namespaceSet(covered)
	for _, namespace := range target {
		if !isCovered[namespace] {
			a.Uncovered = append(a.Uncovered, namespace)
			continue
		}
		state := a.Readiness
		if st, ok := rc.storeFor(resource, namespace); ok {
			state = rc.storeReadiness(resource, st)
		}
		a.assign(state, namespace)
	}
	a.Complete = requested != nil && len(a.Loading) == 0 && len(a.Failed) == 0 && len(a.Uncovered) == 0
	return a
}

// TargetReadiness is the state of the requested namespaces alone: Pending
// while any of them loads, Failed when one missed its load deadline and none
// is loading, Ready otherwise. Other namespaces of the kind don't count, so a
// target of loaded namespaces is Ready while another namespace still loads.
// Uncovered namespaces don't count either; Complete covers them. Without a
// partition (one informer for every namespace and no target), it is Readiness.
func (a KindReadAssessment) TargetReadiness() KindReadiness {
	if a.Read == nil {
		return a.Readiness
	}
	switch {
	case len(a.Loading) > 0:
		return KindPending
	case len(a.Failed) > 0:
		return KindFailed
	}
	return KindReady
}

// namespaceSet indexes namespaces for membership checks: a collector's list
// and a caller's target can each hold thousands.
func namespaceSet(namespaces []string) map[string]bool {
	set := make(map[string]bool, len(namespaces))
	for _, ns := range namespaces {
		set[ns] = true
	}
	return set
}

func (a *KindReadAssessment) assign(state KindReadiness, namespaces ...string) {
	switch state {
	case KindReady:
		a.Read = append(a.Read, namespaces...)
	case KindFailed:
		a.Failed = append(a.Failed, namespaces...)
	default:
		a.Loading = append(a.Loading, namespaces...)
	}
}

// readableIndexers returns one read per store a request needs. With nil
// namespaces each read is a whole store, for the kind's whole scope; otherwise
// each read selects one namespace's objects from the store holding them. It
// fails unless every store involved is complete now.
func (rc *ResourceCache) readableIndexers(resource string, namespaces []string) ([]func() []any, bool) {
	if rc == nil || !rc.isEnabled(resource) || rc.kindStores[resource] == nil {
		return nil, false
	}
	stores := rc.kindStores[resource]
	var reads []func() []any
	if namespaces == nil {
		for _, st := range stores {
			if !st.synced() {
				return nil, false
			}
			reads = append(reads, st.indexer.List)
		}
		return reads, true
	}
	covered := rc.KindNamespaces(resource)
	isCovered := namespaceSet(covered)
	for _, namespace := range namespaces {
		if covered != nil && !isCovered[namespace] {
			return nil, false
		}
		st, ok := rc.storeFor(resource, namespace)
		if !ok || !st.synced() {
			return nil, false
		}
		reads = append(reads, func() []any {
			items, _ := st.indexer.ByIndex(cache.NamespaceIndex, namespace)
			return items
		})
	}
	return reads, true
}

// ReadableObjects lists resource in namespaces straight from the informer
// stores holding them. ok is false unless each namespace's store is complete
// now, so a namespace whose own informer has synced can be read while another
// namespace of the kind is still loading. It never consults the kind's
// whole-kind readiness, so it is for readers that assessed the scope first.
func (rc *ResourceCache) ReadableObjects(resource string, namespaces []string) ([]apiruntime.Object, bool) {
	reads, ok := rc.readableIndexers(resource, namespaces)
	if !ok {
		return nil, false
	}
	var out []apiruntime.Object
	for _, read := range reads {
		for _, item := range read() {
			if obj, isObj := item.(apiruntime.Object); isObj {
				out = append(out, obj)
			}
		}
	}
	return out, true
}

// ReadableCount counts resource in namespaces under ReadableObjects' rules
// without copying the objects.
func (rc *ResourceCache) ReadableCount(resource string, namespaces []string) (int, bool) {
	reads, ok := rc.readableIndexers(resource, namespaces)
	if !ok {
		return 0, false
	}
	n := 0
	for _, read := range reads {
		n += len(read())
	}
	return n, true
}

// CachedObject looks up a collected object even before its informer syncs.
// A hit proves existence; a miss does not prove absence. The returned object
// is a copy. Nil or disabled caches return no hit.
func (rc *ResourceCache) CachedObject(resource, namespace, name string) (apiruntime.Object, bool) {
	if rc == nil || !rc.isEnabled(resource) {
		return nil, false
	}
	if covered := rc.KindNamespaces(resource); covered != nil && !slices.Contains(covered, namespace) {
		return nil, false
	}
	st, ok := rc.storeFor(resource, namespace)
	if !ok {
		return nil, false
	}
	key := name
	if namespace != "" {
		key = namespace + "/" + name
	}
	item, found, err := st.indexer.GetByKey(key)
	if err != nil || !found {
		return nil, false
	}
	obj, ok := item.(apiruntime.Object)
	if !ok {
		return nil, false
	}
	return obj.DeepCopyObject(), true
}

// ReadableObject gets one object under ReadableObjects' rules. found reports
// whether it exists; ok is false when its namespace is not readable now, in
// which case a miss proves nothing. namespace is "" for a cluster-scoped kind.
func (rc *ResourceCache) ReadableObject(resource, namespace, name string) (obj apiruntime.Object, found, ok bool) {
	if rc == nil || !rc.isEnabled(resource) || rc.kindStores[resource] == nil {
		return nil, false, false
	}
	if covered := rc.KindNamespaces(resource); covered != nil && !slices.Contains(covered, namespace) {
		return nil, false, false
	}
	st, exists := rc.storeFor(resource, namespace)
	if !exists || !st.synced() {
		return nil, false, false
	}
	key := name
	if namespace != "" {
		key = namespace + "/" + name
	}
	item, found, err := st.indexer.GetByKey(key)
	if err != nil || !found {
		return nil, false, err == nil
	}
	obj, isObj := item.(apiruntime.Object)
	return obj, isObj, isObj
}