istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/kclient/client.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kclient
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"sync"
    21  	"sync/atomic"
    22  
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	klabels "k8s.io/apimachinery/pkg/labels"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	apitypes "k8s.io/apimachinery/pkg/types"
    27  	"k8s.io/client-go/tools/cache"
    28  
    29  	"istio.io/istio/pilot/pkg/features"
    30  	istiogvr "istio.io/istio/pkg/config/schema/gvr"
    31  	"istio.io/istio/pkg/config/schema/kubeclient"
    32  	types "istio.io/istio/pkg/config/schema/kubetypes"
    33  	"istio.io/istio/pkg/kube"
    34  	"istio.io/istio/pkg/kube/controllers"
    35  	"istio.io/istio/pkg/kube/informerfactory"
    36  	"istio.io/istio/pkg/kube/kubetypes"
    37  	"istio.io/istio/pkg/log"
    38  	"istio.io/istio/pkg/ptr"
    39  	"istio.io/istio/pkg/util/sets"
    40  )
    41  
    42  type fullClient[T controllers.Object] struct {
    43  	writeClient[T]
    44  	Informer[T]
    45  }
    46  
    47  type writeClient[T controllers.Object] struct {
    48  	client kube.Client
    49  }
    50  
    51  // handlerRegistration stores a handler, with the registration so it can be de-registered
    52  type handlerRegistration struct {
    53  	registration cache.ResourceEventHandlerRegistration
    54  	// handler is the actual handler. Note this does NOT have the filtering applied.
    55  	handler cache.ResourceEventHandler
    56  }
    57  
    58  type informerClient[T controllers.Object] struct {
    59  	informer      cache.SharedIndexInformer
    60  	startInformer func(stopCh <-chan struct{})
    61  	filter        func(t any) bool
    62  
    63  	handlerMu          sync.RWMutex
    64  	registeredHandlers []handlerRegistration
    65  }
    66  
    67  func (n *informerClient[T]) Get(name, namespace string) T {
    68  	obj, exists, err := n.informer.GetIndexer().GetByKey(keyFunc(name, namespace))
    69  	if err != nil {
    70  		return ptr.Empty[T]()
    71  	}
    72  	if !exists {
    73  		return ptr.Empty[T]()
    74  	}
    75  	cast := obj.(T)
    76  	if !n.applyFilter(cast) {
    77  		return ptr.Empty[T]()
    78  	}
    79  	return cast
    80  }
    81  
    82  func (n *informerClient[T]) applyFilter(t T) bool {
    83  	if n.filter == nil {
    84  		return true
    85  	}
    86  	return n.filter(t)
    87  }
    88  
    89  func (n *informerClient[T]) Start(stopCh <-chan struct{}) {
    90  	n.startInformer(stopCh)
    91  }
    92  
    93  func (n *writeClient[T]) Create(object T) (T, error) {
    94  	api := kubeclient.GetWriteClient[T](n.client, object.GetNamespace())
    95  	return api.Create(context.Background(), object, metav1.CreateOptions{})
    96  }
    97  
    98  func (n *writeClient[T]) Update(object T) (T, error) {
    99  	api := kubeclient.GetWriteClient[T](n.client, object.GetNamespace())
   100  	return api.Update(context.Background(), object, metav1.UpdateOptions{})
   101  }
   102  
   103  func (n *writeClient[T]) Patch(name, namespace string, pt apitypes.PatchType, data []byte) (T, error) {
   104  	api := kubeclient.GetWriteClient[T](n.client, namespace)
   105  	return api.Patch(context.Background(), name, pt, data, metav1.PatchOptions{})
   106  }
   107  
   108  func (n *writeClient[T]) UpdateStatus(object T) (T, error) {
   109  	api, ok := kubeclient.GetWriteClient[T](n.client, object.GetNamespace()).(kubetypes.WriteStatusAPI[T])
   110  	if !ok {
   111  		return ptr.Empty[T](), fmt.Errorf("%T does not support UpdateStatus", object)
   112  	}
   113  	return api.UpdateStatus(context.Background(), object, metav1.UpdateOptions{})
   114  }
   115  
   116  func (n *writeClient[T]) Delete(name, namespace string) error {
   117  	api := kubeclient.GetWriteClient[T](n.client, namespace)
   118  	return api.Delete(context.Background(), name, metav1.DeleteOptions{})
   119  }
   120  
   121  func (n *informerClient[T]) ShutdownHandlers() {
   122  	n.handlerMu.Lock()
   123  	defer n.handlerMu.Unlock()
   124  	for _, c := range n.registeredHandlers {
   125  		_ = n.informer.RemoveEventHandler(c.registration)
   126  	}
   127  }
   128  
   129  func (n *informerClient[T]) AddEventHandler(h cache.ResourceEventHandler) {
   130  	fh := cache.FilteringResourceEventHandler{
   131  		FilterFunc: func(obj interface{}) bool {
   132  			if n.filter == nil {
   133  				return true
   134  			}
   135  			return n.filter(obj)
   136  		},
   137  		Handler: h,
   138  	}
   139  	n.handlerMu.Lock()
   140  	defer n.handlerMu.Unlock()
   141  	// AddEventHandler is safe to call under the lock. This will *enqueue* all existing items, but not block on processing them,
   142  	// so the timing is quick.
   143  	// If we do this outside the lock, we can hit a subtle race condition where we have started processing items before they
   144  	// are registered (in n.registeredHandlers); this can cause the dynamic filtering to miss events
   145  	reg, err := n.informer.AddEventHandler(fh)
   146  	if err != nil {
   147  		// Should only happen if its already stopped. We should exit early.
   148  		return
   149  	}
   150  	n.registeredHandlers = append(n.registeredHandlers, handlerRegistration{registration: reg, handler: h})
   151  }
   152  
   153  func (n *informerClient[T]) HasSynced() bool {
   154  	if !n.informer.HasSynced() {
   155  		return false
   156  	}
   157  	n.handlerMu.RLock()
   158  	defer n.handlerMu.RUnlock()
   159  	// HasSynced is fast, so doing it under the lock is okay
   160  	for _, g := range n.registeredHandlers {
   161  		if !g.registration.HasSynced() {
   162  			return false
   163  		}
   164  	}
   165  	return true
   166  }
   167  
   168  func (n *informerClient[T]) List(namespace string, selector klabels.Selector) []T {
   169  	var res []T
   170  	err := cache.ListAllByNamespace(n.informer.GetIndexer(), namespace, selector, func(i any) {
   171  		cast := i.(T)
   172  		if n.applyFilter(cast) {
   173  			res = append(res, cast)
   174  		}
   175  	})
   176  
   177  	// Should never happen
   178  	if err != nil && features.EnableUnsafeAssertions {
   179  		log.Fatalf("lister returned err for %v: %v", namespace, err)
   180  	}
   181  	return res
   182  }
   183  
   184  func (n *informerClient[T]) ListUnfiltered(namespace string, selector klabels.Selector) []T {
   185  	var res []T
   186  	err := cache.ListAllByNamespace(n.informer.GetIndexer(), namespace, selector, func(i any) {
   187  		cast := i.(T)
   188  		res = append(res, cast)
   189  	})
   190  
   191  	// Should never happen
   192  	if err != nil && features.EnableUnsafeAssertions {
   193  		log.Fatalf("lister returned err for %v: %v", namespace, err)
   194  	}
   195  	return res
   196  }
   197  
   198  // Filter allows filtering read operations.
   199  // This is aliased to allow easier access when constructing clients.
   200  type Filter = kubetypes.Filter
   201  
   202  // New returns a Client for the given type.
   203  // Internally, this uses a shared informer, so calling this multiple times will share the same internals.
   204  func New[T controllers.ComparableObject](c kube.Client) Client[T] {
   205  	return NewFiltered[T](c, Filter{})
   206  }
   207  
   208  // NewFiltered returns a Client with some filter applied.
   209  // Internally, this uses a shared informer, so calling this multiple times will share the same internals. This is keyed on
   210  // unique {Type,LabelSelector,FieldSelector}.
   211  //
   212  // Warning: if conflicting filter.ObjectTransform are used for the same key, the first one registered wins.
   213  // This means there must only be one filter configuration for a given type using the same kube.Client.
   214  // Use with caution.
   215  func NewFiltered[T controllers.ComparableObject](c kube.Client, filter Filter) Client[T] {
   216  	gvr := types.MustToGVR[T](types.MustGVKFromType[T]())
   217  	inf := kubeclient.GetInformerFiltered[T](c, ToOpts(c, gvr, filter))
   218  	return &fullClient[T]{
   219  		writeClient: writeClient[T]{client: c},
   220  		Informer:    newInformerClient[T](gvr, inf, filter),
   221  	}
   222  }
   223  
   224  // NewDelayedInformer returns a "delayed" client for the given GVR. This is read-only.
   225  // A delayed client is used for CRD watches when the CRD may or may not exist. When the CRD is not present, the client will return
   226  // empty results for all operations and watch for the CRD creation. Once created, watchers will be started and read operations will
   227  // begin returning results.
   228  // HasSynced will only return true if the CRD was not present upon creation OR the watch is fully synced. This ensures the creation
   229  // is fully consistent if the CRD was present during creation; otherwise it is eventually consistent.
   230  func NewDelayedInformer[T controllers.ComparableObject](
   231  	c kube.Client,
   232  	gvr schema.GroupVersionResource,
   233  	informerType kubetypes.InformerType,
   234  	filter Filter,
   235  ) Informer[T] {
   236  	watcher := c.CrdWatcher()
   237  	if watcher == nil {
   238  		log.Fatalf("NewDelayedInformer called without a CrdWatcher enabled")
   239  	}
   240  	delay := newDelayedFilter(gvr, watcher)
   241  	inf := func() informerfactory.StartableInformer {
   242  		opts := ToOpts(c, gvr, filter)
   243  		opts.InformerType = informerType
   244  		return kubeclient.GetInformerFilteredFromGVR(c, opts, gvr)
   245  	}
   246  	return newDelayedInformer[T](gvr, inf, delay, filter)
   247  }
   248  
   249  // NewUntypedInformer returns an untyped client for a given GVR. This is read-only.
   250  func NewUntypedInformer(c kube.Client, gvr schema.GroupVersionResource, filter Filter) Untyped {
   251  	inf := kubeclient.GetInformerFilteredFromGVR(c, ToOpts(c, gvr, filter), gvr)
   252  	return newInformerClient[controllers.Object](gvr, inf, filter)
   253  }
   254  
   255  // NewDynamic returns a dynamic client for a given GVR. This is read-only.
   256  func NewDynamic(c kube.Client, gvr schema.GroupVersionResource, filter Filter) Untyped {
   257  	opts := ToOpts(c, gvr, filter)
   258  	opts.InformerType = kubetypes.DynamicInformer
   259  	inf := kubeclient.GetInformerFilteredFromGVR(c, opts, gvr)
   260  	return newInformerClient[controllers.Object](gvr, inf, filter)
   261  }
   262  
   263  // NewMetadata returns a metadata client for a given GVR. This is read-only.
   264  func NewMetadata(c kube.Client, gvr schema.GroupVersionResource, filter Filter) Informer[*metav1.PartialObjectMetadata] {
   265  	opts := ToOpts(c, gvr, filter)
   266  	opts.InformerType = kubetypes.MetadataInformer
   267  	inf := kubeclient.GetInformerFilteredFromGVR(c, opts, gvr)
   268  	return newInformerClient[*metav1.PartialObjectMetadata](gvr, inf, filter)
   269  }
   270  
   271  // NewWriteClient is exposed for testing.
   272  func NewWriteClient[T controllers.ComparableObject](c kube.Client) Writer[T] {
   273  	return &writeClient[T]{client: c}
   274  }
   275  
   276  func newDelayedInformer[T controllers.ComparableObject](
   277  	gvr schema.GroupVersionResource,
   278  	getInf func() informerfactory.StartableInformer,
   279  	delay kubetypes.DelayedFilter,
   280  	filter Filter,
   281  ) Informer[T] {
   282  	delayedClient := &delayedClient[T]{
   283  		inf:     new(atomic.Pointer[Informer[T]]),
   284  		delayed: delay,
   285  	}
   286  
   287  	// If resource is not yet known, we will use the delayedClient.
   288  	// When the resource is later loaded, the callback will trigger and swap our dummy delayedClient
   289  	// with a full client
   290  	readyNow := delay.KnownOrCallback(func(stop <-chan struct{}) {
   291  		// The inf() call is responsible for starting the informer
   292  		inf := getInf()
   293  		fc := &informerClient[T]{
   294  			informer:      inf.Informer,
   295  			startInformer: inf.Start,
   296  		}
   297  		applyDynamicFilter(filter, gvr, fc)
   298  		inf.Start(stop)
   299  		log.Infof("%v is now ready, building client", gvr.GroupResource())
   300  		// Swap out the dummy client with the full one
   301  		delayedClient.set(fc)
   302  	})
   303  	if !readyNow {
   304  		log.Debugf("%v is not ready now, building delayed client", gvr.GroupResource())
   305  		return delayedClient
   306  	}
   307  	log.Debugf("%v ready now, building client", gvr.GroupResource())
   308  	return newInformerClient[T](gvr, getInf(), filter)
   309  }
   310  
   311  func newInformerClient[T controllers.ComparableObject](
   312  	gvr schema.GroupVersionResource,
   313  	inf informerfactory.StartableInformer,
   314  	filter Filter,
   315  ) Informer[T] {
   316  	ic := &informerClient[T]{
   317  		informer:      inf.Informer,
   318  		startInformer: inf.Start,
   319  	}
   320  	if filter.ObjectFilter != nil {
   321  		applyDynamicFilter(filter, gvr, ic)
   322  	}
   323  	return ic
   324  }
   325  
   326  func applyDynamicFilter[T controllers.ComparableObject](filter Filter, gvr schema.GroupVersionResource, ic *informerClient[T]) {
   327  	if filter.ObjectFilter != nil {
   328  		ic.filter = filter.ObjectFilter.Filter
   329  		filter.ObjectFilter.AddHandler(func(added, removed sets.String) {
   330  			ic.handlerMu.RLock()
   331  			defer ic.handlerMu.RUnlock()
   332  			if gvr == istiogvr.Namespace {
   333  				// Namespace is special; we query all namespaces
   334  				// Note: other cluster-scoped resources should just not use the filter
   335  				for _, item := range ic.ListUnfiltered(metav1.NamespaceAll, klabels.Everything()) {
   336  					if !added.Contains(item.GetName()) {
   337  						continue
   338  					}
   339  					for _, c := range ic.registeredHandlers {
   340  						c.handler.OnAdd(item, false)
   341  					}
   342  				}
   343  				// Removes are currently NOT handled. We only have the namespace name here. We would need to have the object
   344  				// filter passthrough the entire namespace object, so we can pass the last known state to OnDelete.
   345  				// Fortunately, missing a namespace delete event usually doesn't matter since everything in the namespace gets torn down.
   346  			} else {
   347  				for ns := range added {
   348  					for _, item := range ic.ListUnfiltered(ns, klabels.Everything()) {
   349  						for _, c := range ic.registeredHandlers {
   350  							c.handler.OnAdd(item, false)
   351  						}
   352  					}
   353  				}
   354  				for ns := range removed {
   355  					for _, item := range ic.ListUnfiltered(ns, klabels.Everything()) {
   356  						for _, c := range ic.registeredHandlers {
   357  							c.handler.OnDelete(item)
   358  						}
   359  					}
   360  				}
   361  			}
   362  		})
   363  	}
   364  }
   365  
   366  // keyFunc is the internal API key function that returns "namespace"/"name" or
   367  // "name" if "namespace" is empty
   368  func keyFunc(name, namespace string) string {
   369  	if len(namespace) == 0 {
   370  		return name
   371  	}
   372  	return namespace + "/" + name
   373  }
   374  
   375  func ToOpts(c kube.Client, gvr schema.GroupVersionResource, filter Filter) kubetypes.InformerOptions {
   376  	ns := filter.Namespace
   377  	if !istiogvr.IsClusterScoped(gvr) && ns == "" {
   378  		ns = features.InformerWatchNamespace
   379  	}
   380  	return kubetypes.InformerOptions{
   381  		LabelSelector:   filter.LabelSelector,
   382  		FieldSelector:   filter.FieldSelector,
   383  		Namespace:       ns,
   384  		ObjectTransform: filter.ObjectTransform,
   385  		Cluster:         c.ClusterID(),
   386  	}
   387  }