istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/kube/krt/informer.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 krt 16 17 import ( 18 "fmt" 19 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 klabels "k8s.io/apimachinery/pkg/labels" 22 "k8s.io/client-go/tools/cache" 23 24 "istio.io/istio/pkg/kube" 25 "istio.io/istio/pkg/kube/controllers" 26 "istio.io/istio/pkg/kube/kclient" 27 "istio.io/istio/pkg/kube/kubetypes" 28 istiolog "istio.io/istio/pkg/log" 29 "istio.io/istio/pkg/ptr" 30 ) 31 32 type informer[I controllers.ComparableObject] struct { 33 inf kclient.Informer[I] 34 log *istiolog.Scope 35 collectionName string 36 id collectionUID 37 38 eventHandlers *handlers[I] 39 augmentation func(a any) any 40 synced chan struct{} 41 } 42 43 // nolint: unused // (not true, its to implement an interface) 44 func (i *informer[I]) augment(a any) any { 45 if i.augmentation != nil { 46 return i.augmentation(a) 47 } 48 return a 49 } 50 51 var _ internalCollection[controllers.Object] = &informer[controllers.Object]{} 52 53 func (i *informer[I]) _internalHandler() {} 54 55 func (i *informer[I]) Synced() Syncer { 56 return channelSyncer{ 57 name: i.collectionName, 58 synced: i.synced, 59 } 60 } 61 62 // nolint: unused // (not true, its to implement an interface) 63 func (i *informer[I]) dump() { 64 // TODO: implement some useful dump here 65 } 66 67 func (i *informer[I]) name() string { 68 return i.collectionName 69 } 70 71 // nolint: unused // (not true, its to implement an interface) 72 func (i *informer[I]) uid() collectionUID { 73 return i.id 74 } 75 76 func (i *informer[I]) List() []I { 77 res := i.inf.List(metav1.NamespaceAll, klabels.Everything()) 78 return res 79 } 80 81 func (i *informer[I]) GetKey(k Key[I]) *I { 82 // ns, n := splitKeyFunc(string(k)) 83 // Internal optimization: we know kclient will eventually lookup "ns/name" 84 // We also have a key in this format. 85 // Rather than split and rejoin it later, just pass it as the name 86 // This is depending on "unstable" implementation details, but we own both libraries and tests would catch any issues. 87 if got := i.inf.Get(string(k), ""); !controllers.IsNil(got) { 88 return &got 89 } 90 return nil 91 } 92 93 func (i *informer[I]) Register(f func(o Event[I])) Syncer { 94 return registerHandlerAsBatched[I](i, f) 95 } 96 97 func (i *informer[I]) RegisterBatch(f func(o []Event[I], initialSync bool), runExistingState bool) Syncer { 98 // Note: runExistingState is NOT respected here. 99 // Informer doesn't expose a way to do that. However, due to the runtime model of informers, this isn't a dealbreaker; 100 // the handlers are all called async, so we don't end up with the same deadlocks we would have in the other collection types. 101 // While this is quite kludgy, this is an internal interface so its not too bad. 102 if !i.eventHandlers.Insert(f) { 103 i.inf.AddEventHandler(informerEventHandler[I](func(o Event[I], initialSync bool) { 104 f([]Event[I]{o}, initialSync) 105 })) 106 } 107 return pollSyncer{ 108 name: fmt.Sprintf("%v handler", i.name()), 109 f: i.inf.HasSynced, 110 } 111 } 112 113 func informerEventHandler[I controllers.ComparableObject](handler func(o Event[I], initialSync bool)) cache.ResourceEventHandler { 114 return controllers.EventHandler[I]{ 115 AddExtendedFunc: func(obj I, initialSync bool) { 116 handler(Event[I]{ 117 New: &obj, 118 Event: controllers.EventAdd, 119 }, initialSync) 120 }, 121 UpdateFunc: func(oldObj, newObj I) { 122 handler(Event[I]{ 123 Old: &oldObj, 124 New: &newObj, 125 Event: controllers.EventUpdate, 126 }, false) 127 }, 128 DeleteFunc: func(obj I) { 129 handler(Event[I]{ 130 Old: &obj, 131 Event: controllers.EventDelete, 132 }, false) 133 }, 134 } 135 } 136 137 // WrapClient is the base entrypoint that enables the creation 138 // of a collection from an API Server client. 139 // 140 // Generic types can use kclient.NewDynamic to create an 141 // informer for a Collection of type controllers.Object 142 func WrapClient[I controllers.ComparableObject](c kclient.Informer[I], opts ...CollectionOption) Collection[I] { 143 o := buildCollectionOptions(opts...) 144 if o.name == "" { 145 o.name = fmt.Sprintf("NewInformer[%v]", ptr.TypeName[I]()) 146 } 147 h := &informer[I]{ 148 inf: c, 149 log: log.WithLabels("owner", o.name), 150 collectionName: o.name, 151 id: nextUID(), 152 eventHandlers: &handlers[I]{}, 153 augmentation: o.augmentation, 154 synced: make(chan struct{}), 155 } 156 157 go func() { 158 // First, wait for the informer to populate 159 if !kube.WaitForCacheSync(o.name, o.stop, c.HasSynced) { 160 return 161 } 162 // Now, take all our handlers we have built up and register them... 163 handlers := h.eventHandlers.MarkInitialized() 164 for _, h := range handlers { 165 c.AddEventHandler(informerEventHandler[I](func(o Event[I], initialSync bool) { 166 h([]Event[I]{o}, initialSync) 167 })) 168 } 169 // Now wait for handlers to sync 170 if !kube.WaitForCacheSync(o.name+" handlers", o.stop, c.HasSynced) { 171 c.ShutdownHandlers() 172 return 173 } 174 close(h.synced) 175 h.log.Infof("%v synced", h.name()) 176 }() 177 return h 178 } 179 180 // NewInformer creates a Collection[I] sourced from 181 // the results of kube.Client querying resources of type I 182 // from the API Server. 183 // 184 // Resources must have their GVR and GVK registered in the 185 // kube.Client before this method is called, otherwise 186 // NewInformer will panic. 187 func NewInformer[I controllers.ComparableObject](c kube.Client, opts ...CollectionOption) Collection[I] { 188 return NewInformerFiltered[I](c, kubetypes.Filter{}, opts...) 189 } 190 191 // NewInformerFiltered takes an argument that filters the 192 // results from the kube.Client. Otherwise, behaves 193 // the same as NewInformer 194 func NewInformerFiltered[I controllers.ComparableObject](c kube.Client, filter kubetypes.Filter, opts ...CollectionOption) Collection[I] { 195 return WrapClient[I](kclient.NewFiltered[I](c, filter), opts...) 196 }