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 }