knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/tracker/enqueue.go (about)

     1  /*
     2  Copyright 2018 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tracker
    18  
    19  import (
    20  	"fmt"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/apimachinery/pkg/util/validation"
    31  
    32  	"knative.dev/pkg/kmeta"
    33  )
    34  
    35  // New returns an implementation of Interface that lets a Reconciler
    36  // register a particular resource as watching an ObjectReference for
    37  // a particular lease duration.  This watch must be refreshed
    38  // periodically (e.g. by a controller resync) or it will expire.
    39  //
    40  // When OnChanged is called by the informer for a particular
    41  // GroupVersionKind, the provided callback is called with the "key"
    42  // of each object actively watching the changed object.
    43  func New(callback func(types.NamespacedName), lease time.Duration) Interface {
    44  	return &impl{
    45  		leaseDuration: lease,
    46  		cb:            callback,
    47  	}
    48  }
    49  
    50  type impl struct {
    51  	m sync.Mutex
    52  	// exact maps from an object reference to the set of
    53  	// keys for objects watching it.
    54  	exact map[Reference]set
    55  	// inexact maps from a partial object reference (no name/selector) to
    56  	// a map from watcher keys to the compiled selector and expiry.
    57  	inexact map[Reference]matchers
    58  
    59  	// The amount of time that an object may watch another
    60  	// before having to renew the lease.
    61  	leaseDuration time.Duration
    62  
    63  	cb func(types.NamespacedName)
    64  }
    65  
    66  // Check that impl implements Interface.
    67  var _ Interface = (*impl)(nil)
    68  
    69  // set is a map from keys to expirations
    70  type set map[types.NamespacedName]time.Time
    71  
    72  // matchers maps the tracker's key to the matcher.
    73  type matchers map[types.NamespacedName]matcher
    74  
    75  // matcher holds the selector and expiry for matching tracked objects.
    76  type matcher struct {
    77  	// The selector to complete the match.
    78  	selector labels.Selector
    79  
    80  	// When this lease expires.
    81  	expiry time.Time
    82  }
    83  
    84  // Track implements Interface.
    85  func (i *impl) Track(ref corev1.ObjectReference, obj interface{}) error {
    86  	return i.TrackReference(Reference{
    87  		APIVersion: ref.APIVersion,
    88  		Kind:       ref.Kind,
    89  		Namespace:  ref.Namespace,
    90  		Name:       ref.Name,
    91  	}, obj)
    92  }
    93  
    94  func (i *impl) TrackReference(ref Reference, obj interface{}) error {
    95  	invalidFields := map[string][]string{
    96  		"APIVersion": validation.IsQualifiedName(ref.APIVersion),
    97  		"Kind":       validation.IsCIdentifier(ref.Kind),
    98  	}
    99  	// Allow namespace to be empty for cluster-scoped references.
   100  	if ref.Namespace != "" {
   101  		invalidFields["Namespace"] = validation.IsDNS1123Label(ref.Namespace)
   102  	}
   103  	var selector labels.Selector
   104  	fieldErrors := []string{}
   105  	switch {
   106  	case ref.Selector != nil && ref.Name != "":
   107  		fieldErrors = append(fieldErrors, "cannot provide both Name and Selector")
   108  	case ref.Name != "":
   109  		invalidFields["Name"] = validation.IsDNS1123Subdomain(ref.Name)
   110  	case ref.Selector != nil:
   111  		ls, err := metav1.LabelSelectorAsSelector(ref.Selector)
   112  		if err != nil {
   113  			invalidFields["Selector"] = []string{err.Error()}
   114  		}
   115  		selector = ls
   116  	default:
   117  		fieldErrors = append(fieldErrors, "must provide either Name or Selector")
   118  	}
   119  	for k, v := range invalidFields {
   120  		for _, msg := range v {
   121  			fieldErrors = append(fieldErrors, fmt.Sprintf("%s: %s", k, msg))
   122  		}
   123  	}
   124  	if len(fieldErrors) > 0 {
   125  		sort.Strings(fieldErrors)
   126  		return fmt.Errorf("invalid Reference:\n%s", strings.Join(fieldErrors, "\n"))
   127  	}
   128  
   129  	// Determine the key of the object tracking this reference.
   130  	object, err := kmeta.DeletionHandlingAccessor(obj)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	key := types.NamespacedName{Namespace: object.GetNamespace(), Name: object.GetName()}
   135  
   136  	i.m.Lock()
   137  	// Call the callback without the lock held.
   138  	var keys []types.NamespacedName
   139  	defer func(cb func(types.NamespacedName)) {
   140  		for _, key := range keys {
   141  			cb(key)
   142  		}
   143  	}(i.cb) // read i.cb with the lock held
   144  	defer i.m.Unlock()
   145  	if i.exact == nil {
   146  		i.exact = make(map[Reference]set)
   147  	}
   148  	if i.inexact == nil {
   149  		i.inexact = make(map[Reference]matchers)
   150  	}
   151  
   152  	// If the reference uses Name then it is an exact match.
   153  	if selector == nil {
   154  		l, ok := i.exact[ref]
   155  		if !ok {
   156  			l = set{}
   157  		}
   158  
   159  		if expiry, ok := l[key]; !ok || isExpired(expiry) {
   160  			// When covering an uncovered key, immediately call the
   161  			// registered callback to ensure that the following pattern
   162  			// doesn't create problems:
   163  			//    foo, err := lister.Get(key)
   164  			//    // Later...
   165  			//    err := tracker.TrackReference(fooRef, parent)
   166  			// In this example, "Later" represents a window where "foo" may
   167  			// have changed or been created while the Track is not active.
   168  			// The simplest way of eliminating such a window is to call the
   169  			// callback to "catch up" immediately following new
   170  			// registrations.
   171  			keys = append(keys, key)
   172  		}
   173  		// Overwrite the key with a new expiration.
   174  		l[key] = time.Now().Add(i.leaseDuration)
   175  
   176  		i.exact[ref] = l
   177  		return nil
   178  	}
   179  
   180  	// Otherwise, it is an inexact match by selector.
   181  	partialRef := Reference{
   182  		APIVersion: ref.APIVersion,
   183  		Kind:       ref.Kind,
   184  		Namespace:  ref.Namespace,
   185  		// Exclude the selector.
   186  	}
   187  	l, ok := i.inexact[partialRef]
   188  	if !ok {
   189  		l = matchers{}
   190  	}
   191  
   192  	if m, ok := l[key]; !ok || isExpired(m.expiry) {
   193  		// When covering an uncovered key, immediately call the
   194  		// registered callback to ensure that the following pattern
   195  		// doesn't create problems:
   196  		//    foo, err := lister.Get(key)
   197  		//    // Later...
   198  		//    err := tracker.TrackReference(fooRef, parent)
   199  		// In this example, "Later" represents a window where "foo" may
   200  		// have changed or been created while the Track is not active.
   201  		// The simplest way of eliminating such a window is to call the
   202  		// callback to "catch up" immediately following new
   203  		// registrations.
   204  		keys = append(keys, key)
   205  	}
   206  	// Overwrite the key with a new expiration.
   207  	l[key] = matcher{
   208  		selector: selector,
   209  		expiry:   time.Now().Add(i.leaseDuration),
   210  	}
   211  
   212  	i.inexact[partialRef] = l
   213  	return nil
   214  }
   215  
   216  func isExpired(expiry time.Time) bool {
   217  	return time.Now().After(expiry)
   218  }
   219  
   220  // OnChanged implements Interface.
   221  func (i *impl) OnChanged(obj interface{}) {
   222  	observers := i.GetObservers(obj)
   223  
   224  	for _, observer := range observers {
   225  		i.cb(observer)
   226  	}
   227  }
   228  
   229  // GetObservers implements Interface.
   230  func (i *impl) GetObservers(obj interface{}) []types.NamespacedName {
   231  	item, err := kmeta.DeletionHandlingAccessor(obj)
   232  	if err != nil {
   233  		return nil
   234  	}
   235  
   236  	or := kmeta.ObjectReference(item)
   237  	ref := Reference{
   238  		APIVersion: or.APIVersion,
   239  		Kind:       or.Kind,
   240  		Namespace:  or.Namespace,
   241  		Name:       or.Name,
   242  	}
   243  
   244  	var keys []types.NamespacedName
   245  
   246  	i.m.Lock()
   247  	defer i.m.Unlock()
   248  
   249  	// Handle exact matches.
   250  	s, ok := i.exact[ref]
   251  	if ok {
   252  		for key, expiry := range s {
   253  			// If the expiration has lapsed, then delete the key.
   254  			if isExpired(expiry) {
   255  				delete(s, key)
   256  				continue
   257  			}
   258  			keys = append(keys, key)
   259  		}
   260  		if len(s) == 0 {
   261  			delete(i.exact, ref)
   262  		}
   263  	}
   264  
   265  	// Handle inexact matches.
   266  	ref.Name = ""
   267  	ms, ok := i.inexact[ref]
   268  	if ok {
   269  		ls := labels.Set(item.GetLabels())
   270  		for key, m := range ms {
   271  			// If the expiration has lapsed, then delete the key.
   272  			if isExpired(m.expiry) {
   273  				delete(ms, key)
   274  				continue
   275  			}
   276  			if m.selector.Matches(ls) {
   277  				keys = append(keys, key)
   278  			}
   279  		}
   280  		if len(ms) == 0 {
   281  			delete(i.inexact, ref)
   282  		}
   283  	}
   284  
   285  	return keys
   286  }
   287  
   288  // OnChanged implements Interface.
   289  func (i *impl) OnDeletedObserver(obj interface{}) {
   290  	item, err := kmeta.DeletionHandlingAccessor(obj)
   291  	if err != nil {
   292  		return
   293  	}
   294  
   295  	key := types.NamespacedName{Namespace: item.GetNamespace(), Name: item.GetName()}
   296  
   297  	i.m.Lock()
   298  	defer i.m.Unlock()
   299  
   300  	// Remove exact matches.
   301  	for ref, matchers := range i.exact {
   302  		delete(matchers, key)
   303  		if len(matchers) == 0 {
   304  			delete(i.exact, ref)
   305  		}
   306  	}
   307  
   308  	// Remove inexact matches.
   309  	for ref, matchers := range i.inexact {
   310  		delete(matchers, key)
   311  		if len(matchers) == 0 {
   312  			delete(i.inexact, ref)
   313  		}
   314  	}
   315  }