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  }