github.com/oam-dev/kubevela@v1.9.11/pkg/cache/informer.go (about) 1 /* 2 Copyright 2023 The KubeVela 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 cache 18 19 import ( 20 "context" 21 "sync" 22 "time" 23 24 "github.com/kubevela/pkg/util/k8s" 25 "github.com/kubevela/pkg/util/runtime" 26 "github.com/kubevela/pkg/util/singleton" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/util/sets" 29 utilfeature "k8s.io/apiserver/pkg/util/feature" 30 kcache "k8s.io/client-go/tools/cache" 31 "k8s.io/klog/v2" 32 "sigs.k8s.io/controller-runtime/pkg/cache" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 35 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1" 36 ctrlutils "github.com/oam-dev/kubevela/pkg/controller/utils" 37 "github.com/oam-dev/kubevela/pkg/features" 38 ) 39 40 var ( 41 // ApplicationRevisionDefinitionCachePruneDuration the prune duration for application revision definition cache 42 ApplicationRevisionDefinitionCachePruneDuration = time.Hour 43 ) 44 45 // ObjectCacheEntry entry for object cache 46 type ObjectCacheEntry[T any] struct { 47 ptr *T 48 refs sets.Set[string] 49 lastAccessed time.Time 50 } 51 52 // ObjectCache cache for objects 53 type ObjectCache[T any] struct { 54 mu sync.RWMutex 55 objects map[string]*ObjectCacheEntry[T] 56 } 57 58 // NewObjectCache create an object cache 59 func NewObjectCache[T any]() *ObjectCache[T] { 60 return &ObjectCache[T]{ 61 objects: map[string]*ObjectCacheEntry[T]{}, 62 } 63 } 64 65 // Get retrieve the cache entry 66 func (in *ObjectCache[T]) Get(hash string) *T { 67 in.mu.RLock() 68 defer in.mu.RUnlock() 69 if entry, found := in.objects[hash]; found { 70 return entry.ptr 71 } 72 return nil 73 } 74 75 // Add insert cache entry with ref, return the ptr of the entry 76 func (in *ObjectCache[T]) Add(hash string, obj *T, ref string) *T { 77 in.mu.Lock() 78 defer in.mu.Unlock() 79 if entry, found := in.objects[hash]; found { 80 entry.refs.Insert(ref) 81 entry.lastAccessed = time.Now() 82 return entry.ptr 83 } 84 in.objects[hash] = &ObjectCacheEntry[T]{ 85 ptr: obj, 86 refs: sets.New[string](ref), 87 lastAccessed: time.Now(), 88 } 89 return obj 90 } 91 92 // DeleteRef delete ref for an obj 93 func (in *ObjectCache[T]) DeleteRef(hash string, ref string) { 94 in.mu.Lock() 95 defer in.mu.Unlock() 96 if entry, found := in.objects[hash]; found { 97 entry.refs.Delete(ref) 98 if entry.refs.Len() == 0 { 99 delete(in.objects, hash) 100 } 101 } 102 } 103 104 // Remap relocate the object ptr with given ref 105 func (in *ObjectCache[T]) Remap(m map[string]*T, ref string) { 106 for key, o := range m { 107 if hash, err := ctrlutils.ComputeSpecHash(o); err == nil { 108 m[key] = in.Add(hash, o, ref) 109 } 110 } 111 } 112 113 // Unmap drop all the hash object from the map 114 func (in *ObjectCache[T]) Unmap(m map[string]*T, ref string) { 115 for _, o := range m { 116 if hash, err := ctrlutils.ComputeSpecHash(o); err == nil { 117 in.DeleteRef(hash, ref) 118 } 119 } 120 } 121 122 // Size get the size of the cache 123 func (in *ObjectCache[T]) Size() int { 124 in.mu.RLock() 125 defer in.mu.RUnlock() 126 return len(in.objects) 127 } 128 129 // Prune remove outdated cache, return the pruned count 130 func (in *ObjectCache[T]) Prune(outdated time.Duration) int { 131 in.mu.Lock() 132 defer in.mu.Unlock() 133 cnt := 0 134 for key, entry := range in.objects { 135 if time.Now().After(entry.lastAccessed.Add(outdated)) { 136 delete(in.objects, key) 137 cnt++ 138 } 139 } 140 return cnt 141 } 142 143 // DefinitionCache cache for definitions 144 type DefinitionCache struct { 145 ComponentDefinitionCache *ObjectCache[v1beta1.ComponentDefinition] 146 TraitDefinitionCache *ObjectCache[v1beta1.TraitDefinition] 147 WorkflowStepDefinitionCache *ObjectCache[v1beta1.WorkflowStepDefinition] 148 } 149 150 // NewDefinitionCache create DefinitionCache 151 func NewDefinitionCache() *DefinitionCache { 152 return &DefinitionCache{ 153 ComponentDefinitionCache: NewObjectCache[v1beta1.ComponentDefinition](), 154 TraitDefinitionCache: NewObjectCache[v1beta1.TraitDefinition](), 155 WorkflowStepDefinitionCache: NewObjectCache[v1beta1.WorkflowStepDefinition](), 156 } 157 } 158 159 // RemapRevision remap all definitions in the given revision 160 func (in *DefinitionCache) RemapRevision(rev *v1beta1.ApplicationRevision) { 161 ref := client.ObjectKeyFromObject(rev).String() 162 in.ComponentDefinitionCache.Remap(rev.Spec.ComponentDefinitions, ref) 163 in.TraitDefinitionCache.Remap(rev.Spec.TraitDefinitions, ref) 164 in.WorkflowStepDefinitionCache.Remap(rev.Spec.WorkflowStepDefinitions, ref) 165 } 166 167 // UnmapRevision unmap definitions from the provided revision by the given ref 168 func (in *DefinitionCache) UnmapRevision(rev *v1beta1.ApplicationRevision) { 169 ref := client.ObjectKeyFromObject(rev).String() 170 in.ComponentDefinitionCache.Unmap(rev.Spec.ComponentDefinitions, ref) 171 in.TraitDefinitionCache.Unmap(rev.Spec.TraitDefinitions, ref) 172 in.WorkflowStepDefinitionCache.Unmap(rev.Spec.WorkflowStepDefinitions, ref) 173 } 174 175 // Start clear cache every duration 176 func (in *DefinitionCache) Start(ctx context.Context, store cache.Cache, duration time.Duration) { 177 informer := runtime.Must(store.GetInformer(ctx, &v1beta1.ApplicationRevision{})) 178 _, err := informer.AddEventHandler(kcache.ResourceEventHandlerFuncs{ 179 AddFunc: func(obj interface{}) { 180 if rev, ok := obj.(*v1beta1.ApplicationRevision); ok { 181 in.RemapRevision(rev) 182 } 183 }, 184 UpdateFunc: func(oldObj, newObj interface{}) { 185 if rev, ok := oldObj.(*v1beta1.ApplicationRevision); ok { 186 in.UnmapRevision(rev) 187 } 188 if rev, ok := newObj.(*v1beta1.ApplicationRevision); ok { 189 in.RemapRevision(rev) 190 } 191 }, 192 DeleteFunc: func(obj interface{}) { 193 if rev, ok := obj.(*v1beta1.ApplicationRevision); ok { 194 in.UnmapRevision(rev) 195 } 196 }, 197 }) 198 if err != nil { 199 klog.ErrorS(err, "failed to add event handler for definition cache") 200 } 201 for { 202 select { 203 case <-ctx.Done(): 204 return 205 default: 206 t0 := time.Now() 207 compDefPruned := in.ComponentDefinitionCache.Prune(duration) 208 traitDefPruned := in.TraitDefinitionCache.Prune(duration) 209 wsDefPruned := in.WorkflowStepDefinitionCache.Prune(duration) 210 klog.Infof("DefinitionCache prune finished. ComponentDefinition: %d(-%d), TraitDefinition: %d(-%d), WorkflowStepDefinition: %d(-%d). Time cost: %d ms.", 211 in.ComponentDefinitionCache.Size(), compDefPruned, 212 in.TraitDefinitionCache.Size(), traitDefPruned, 213 in.WorkflowStepDefinitionCache.Size(), wsDefPruned, 214 time.Since(t0).Microseconds()) 215 time.Sleep(duration) 216 } 217 } 218 } 219 220 // DefaultDefinitionCache the default definition cache 221 var DefaultDefinitionCache = singleton.NewSingleton(NewDefinitionCache) 222 223 func filterUnnecessaryField(o client.Object) { 224 _ = k8s.DeleteAnnotation(o, "kubectl.kubernetes.io/last-applied-configuration") 225 o.SetManagedFields(nil) 226 } 227 228 func wrapTransformFunc[T client.Object](fn func(T)) kcache.TransformFunc { 229 return func(i interface{}) (interface{}, error) { 230 if o, ok := i.(T); ok { 231 filterUnnecessaryField(o) 232 fn(o) 233 return o, nil 234 } 235 return i, nil 236 } 237 } 238 239 // AddInformerTransformFuncToCacheOption add informer transform func to cache option 240 // This will filter out the unnecessary fields for cached objects and use definition cache 241 // to reduce the duplicated storage of same definitions 242 func AddInformerTransformFuncToCacheOption(opts *cache.Options) { 243 if utilfeature.DefaultMutableFeatureGate.Enabled(features.InformerCacheFilterUnnecessaryFields) { 244 if opts.TransformByObject == nil { 245 opts.TransformByObject = map[client.Object]kcache.TransformFunc{} 246 } 247 opts.TransformByObject[&v1beta1.ApplicationRevision{}] = wrapTransformFunc(func(rev *v1beta1.ApplicationRevision) { 248 if utilfeature.DefaultMutableFeatureGate.Enabled(features.SharedDefinitionStorageForApplicationRevision) { 249 DefaultDefinitionCache.Get().RemapRevision(rev) 250 } 251 }) 252 opts.TransformByObject[&v1beta1.Application{}] = wrapTransformFunc(func(app *v1beta1.Application) {}) 253 opts.TransformByObject[&v1beta1.ResourceTracker{}] = wrapTransformFunc(func(rt *v1beta1.ResourceTracker) {}) 254 } 255 } 256 257 // NewResourcesToDisableCache get ClientDisableCacheFor for building controller 258 func NewResourcesToDisableCache() []client.Object { 259 var objs []client.Object 260 if utilfeature.DefaultMutableFeatureGate.Enabled(features.DisableWorkflowContextConfigMapCache) { 261 objs = append(objs, &corev1.ConfigMap{}) 262 } 263 return objs 264 }