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  }