knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/configmap/informer/informed_watcher.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 informer 18 19 import ( 20 "errors" 21 "fmt" 22 23 corev1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/equality" 25 k8serrors "k8s.io/apimachinery/pkg/api/errors" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/selection" 29 "k8s.io/client-go/informers" 30 corev1informers "k8s.io/client-go/informers/core/v1" 31 "k8s.io/client-go/informers/internalinterfaces" 32 "k8s.io/client-go/kubernetes" 33 "k8s.io/client-go/tools/cache" 34 "knative.dev/pkg/configmap" 35 ) 36 37 // NewInformedWatcherFromFactory watches a Kubernetes namespace for ConfigMap changes. 38 func NewInformedWatcherFromFactory(sif informers.SharedInformerFactory, namespace string) *InformedWatcher { 39 return &InformedWatcher{ 40 sif: sif, 41 informer: sif.Core().V1().ConfigMaps(), 42 ManualWatcher: configmap.ManualWatcher{ 43 Namespace: namespace, 44 }, 45 defaults: make(map[string]*corev1.ConfigMap), 46 } 47 } 48 49 // NewInformedWatcher watches a Kubernetes namespace for ConfigMap changes. 50 // Optional label requirements allow restricting the list of ConfigMap objects 51 // that is tracked by the underlying Informer. 52 func NewInformedWatcher(kc kubernetes.Interface, namespace string, lr ...labels.Requirement) *InformedWatcher { 53 return NewInformedWatcherFromFactory(informers.NewSharedInformerFactoryWithOptions( 54 kc, 55 // We noticed that we're getting updates all the time anyway, due to the 56 // watches being terminated and re-spawned. 57 0, 58 informers.WithNamespace(namespace), 59 informers.WithTweakListOptions(addLabelRequirementsToListOptions(lr)), 60 ), namespace) 61 } 62 63 // addLabelRequirementsToListOptions returns a function which injects label 64 // requirements to existing metav1.ListOptions. 65 func addLabelRequirementsToListOptions(lr []labels.Requirement) internalinterfaces.TweakListOptionsFunc { 66 if len(lr) == 0 { 67 return nil 68 } 69 70 return func(lo *metav1.ListOptions) { 71 sel, err := labels.Parse(lo.LabelSelector) 72 if err != nil { 73 panic(fmt.Errorf("could not parse label selector %q: %w", lo.LabelSelector, err)) 74 } 75 lo.LabelSelector = sel.Add(lr...).String() 76 } 77 } 78 79 // FilterConfigByLabelExists returns an "exists" label requirement for the 80 // given label key. 81 func FilterConfigByLabelExists(labelKey string) (*labels.Requirement, error) { 82 req, err := labels.NewRequirement(labelKey, selection.Exists, nil) 83 if err != nil { 84 return nil, fmt.Errorf("could not construct label requirement: %w", err) 85 } 86 return req, nil 87 } 88 89 // InformedWatcher provides an informer-based implementation of Watcher. 90 type InformedWatcher struct { 91 sif informers.SharedInformerFactory 92 informer corev1informers.ConfigMapInformer 93 started bool 94 95 // defaults are the default ConfigMaps to use if the real ones do not exist or are deleted. 96 defaults map[string]*corev1.ConfigMap 97 98 // Embedding this struct allows us to reuse the logic 99 // of registering and notifying observers. This simplifies the 100 // InformedWatcher to just setting up the Kubernetes informer. 101 configmap.ManualWatcher 102 } 103 104 // Asserts that InformedWatcher implements Watcher. 105 var _ configmap.Watcher = (*InformedWatcher)(nil) 106 107 // Asserts that InformedWatcher implements DefaultingWatcher. 108 var _ configmap.DefaultingWatcher = (*InformedWatcher)(nil) 109 110 // WatchWithDefault implements DefaultingWatcher. Adding a default for the configMap being watched means that when 111 // Start is called, Start will not wait for the add event from the API server. 112 func (i *InformedWatcher) WatchWithDefault(cm corev1.ConfigMap, o ...configmap.Observer) { 113 i.defaults[cm.Name] = &cm 114 115 i.Lock() 116 started := i.started 117 i.Unlock() 118 if started { 119 // TODO make both Watch and WatchWithDefault work after the InformedWatcher has started. 120 // This likely entails changing this to `o(&cm)` and having Watch check started, if it has 121 // started, then ensuring i.informer.Lister().ConfigMaps(i.Namespace).Get(cmName) exists and 122 // calling this observer on it. It may require changing Watch and WatchWithDefault to return 123 // an error. 124 panic("cannot WatchWithDefault after the InformedWatcher has started") 125 } 126 127 i.Watch(cm.Name, o...) 128 } 129 130 func (i *InformedWatcher) triggerAddEventForDefaultedConfigMaps(addConfigMapEvent func(obj interface{})) { 131 i.ForEach(func(k string, _ []configmap.Observer) error { 132 if def, ok := i.defaults[k]; ok { 133 addConfigMapEvent(def) 134 } 135 return nil 136 }) 137 } 138 139 func (i *InformedWatcher) getConfigMapNames() []string { 140 var configMaps []string 141 i.ForEach(func(k string, _ []configmap.Observer) error { 142 configMaps = append(configMaps, k) 143 return nil 144 }) 145 return configMaps 146 } 147 148 // Start implements Watcher. Start will wait for all watched resources to exist and for the add event handler to be 149 // invoked at least once for each before continuing or for the stopCh to be signalled, whichever happens first. If 150 // the watched resource is defaulted, Start will invoke the add event handler directly and will not wait for a further 151 // add event from the API server. 152 func (i *InformedWatcher) Start(stopCh <-chan struct{}) error { 153 // using the synced callback wrapper around the add event handler will allow the caller 154 // to wait for the add event to be processed for all configmaps 155 s := newSyncedCallback(i.getConfigMapNames(), i.addConfigMapEvent) 156 addConfigMapEvent := func(obj interface{}) { 157 configMap := obj.(*corev1.ConfigMap) 158 s.Call(obj, configMap.Name) 159 } 160 // Pretend that all the defaulted ConfigMaps were just created. This is done before we start 161 // the informer to ensure that if a defaulted ConfigMap does exist, then the real value is 162 // processed after the default one. 163 i.triggerAddEventForDefaultedConfigMaps(addConfigMapEvent) 164 165 if err := i.registerCallbackAndStartInformer(addConfigMapEvent, stopCh); err != nil { 166 return err 167 } 168 169 // Wait until the shared informer has been synced (WITHOUT holing the mutex, so callbacks happen) 170 if ok := cache.WaitForCacheSync(stopCh, i.informer.Informer().HasSynced); !ok { 171 return errors.New("error waiting for ConfigMap informer to sync") 172 } 173 174 if err := i.checkObservedResourcesExist(); err != nil { 175 return err 176 } 177 178 // Wait until all config maps have been at least initially processed 179 return s.WaitForAllKeys(stopCh) 180 } 181 182 func (i *InformedWatcher) registerCallbackAndStartInformer(addConfigMapEvent func(obj interface{}), stopCh <-chan struct{}) error { 183 i.Lock() 184 defer i.Unlock() 185 if i.started { 186 return errors.New("watcher already started") 187 } 188 i.started = true 189 190 i.informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 191 AddFunc: addConfigMapEvent, 192 UpdateFunc: i.updateConfigMapEvent, 193 DeleteFunc: i.deleteConfigMapEvent, 194 }) 195 196 // Start the shared informer factory (non-blocking). 197 i.sif.Start(stopCh) 198 199 return nil 200 } 201 202 func (i *InformedWatcher) checkObservedResourcesExist() error { 203 i.RLock() 204 defer i.RUnlock() 205 // Check that all objects with Observers exist in our informers. 206 return i.ForEach(func(k string, _ []configmap.Observer) error { 207 if _, err := i.informer.Lister().ConfigMaps(i.Namespace).Get(k); err != nil { 208 if _, ok := i.defaults[k]; ok && k8serrors.IsNotFound(err) { 209 // It is defaulted, so it is OK that it doesn't exist. 210 return nil 211 } 212 return err 213 } 214 return nil 215 }) 216 } 217 218 func (i *InformedWatcher) addConfigMapEvent(obj interface{}) { 219 configMap := obj.(*corev1.ConfigMap) 220 i.OnChange(configMap) 221 } 222 223 func (i *InformedWatcher) updateConfigMapEvent(o, n interface{}) { 224 // Ignore updates that are idempotent. We are seeing those 225 // periodically. 226 if equality.Semantic.DeepEqual(o, n) { 227 return 228 } 229 configMap := n.(*corev1.ConfigMap) 230 i.OnChange(configMap) 231 } 232 233 func (i *InformedWatcher) deleteConfigMapEvent(obj interface{}) { 234 // Handle DeletedFinalStateUnknown which can occur when the final state 235 // of the deleted object is not known. 236 tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 237 if ok { 238 obj = tombstone.Obj 239 } 240 241 // Safely extract the ConfigMap from the object. 242 configMap, ok := obj.(*corev1.ConfigMap) 243 if !ok { 244 // If the object is not a ConfigMap, gracefully return. 245 // This can happen if the tombstone contains an invalid object. 246 return 247 } 248 249 if def, ok := i.defaults[configMap.Name]; ok { 250 i.OnChange(def) 251 } 252 // If there is no default value, then don't do anything. 253 }