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 }