github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/kcc/target/target_accessor.go (about)

     1  /*
     2  Copyright 2022 The Katalyst 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 target
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    29  	"k8s.io/apimachinery/pkg/util/wait"
    30  	"k8s.io/client-go/dynamic"
    31  	"k8s.io/client-go/dynamic/dynamicinformer"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/client-go/util/workqueue"
    34  	"k8s.io/klog/v2"
    35  
    36  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    37  )
    38  
    39  const (
    40  	targetWorkerCount = 1
    41  )
    42  
    43  // KatalystCustomConfigTargetAccessor is to handle creation/update/delete event of target unstructured obj,
    44  // and it can trigger obj re-sync by calling Enqueue function
    45  type KatalystCustomConfigTargetAccessor interface {
    46  	// Start to reconcile obj of kcc target type
    47  	Start()
    48  
    49  	// Stop reconcile obj of kcc target type
    50  	Stop()
    51  
    52  	// Enqueue obj of kcc target type to the work queue
    53  	Enqueue(name string, obj *unstructured.Unstructured)
    54  
    55  	// List all obj (with DeepCopy) of kcc target type according selector
    56  	List(selector labels.Selector) ([]*unstructured.Unstructured, error)
    57  
    58  	// Get obj (with DeepCopy) of kcc target type by namespace and name
    59  	Get(namespace, name string) (*unstructured.Unstructured, error)
    60  }
    61  
    62  type DummyKatalystCustomConfigTargetAccessor struct{}
    63  
    64  func (d DummyKatalystCustomConfigTargetAccessor) Start()                               {}
    65  func (d DummyKatalystCustomConfigTargetAccessor) Stop()                                {}
    66  func (d DummyKatalystCustomConfigTargetAccessor) Enqueue(_ *unstructured.Unstructured) {}
    67  func (d DummyKatalystCustomConfigTargetAccessor) List(_ labels.Selector) ([]*unstructured.Unstructured, error) {
    68  	return nil, nil
    69  }
    70  
    71  func (d DummyKatalystCustomConfigTargetAccessor) Get(_, _ string) (*unstructured.Unstructured, error) {
    72  	return nil, nil
    73  }
    74  
    75  // KatalystCustomConfigTargetHandlerFunc func to process the obj in the work queue
    76  type KatalystCustomConfigTargetHandlerFunc func(gvr metav1.GroupVersionResource, target *unstructured.Unstructured) error
    77  
    78  // targetHandlerFuncWithSyncQueue is used to store the handler and
    79  // syncing queue for each kcc-target
    80  type targetHandlerFuncWithSyncQueue struct {
    81  	targetHandlerFunc KatalystCustomConfigTargetHandlerFunc
    82  	syncQueue         workqueue.RateLimitingInterface
    83  }
    84  
    85  type RealKatalystCustomConfigTargetAccessor struct {
    86  	stopCh chan struct{}
    87  	ctx    context.Context
    88  
    89  	gvr metav1.GroupVersionResource
    90  
    91  	// targetLister can list/get target resource from the targetInformer's store
    92  	targetLister   cache.GenericLister
    93  	targetInformer cache.SharedIndexInformer
    94  
    95  	// targetHandlerFuncWithSyncQueueMap is used to store the handler and syncing
    96  	// queue for each kcc-target
    97  	targetHandlerFuncWithSyncQueueMap map[string]targetHandlerFuncWithSyncQueue
    98  }
    99  
   100  // NewRealKatalystCustomConfigTargetAccessor returns a new KatalystCustomConfigTargetAccessor
   101  // which is used to handle creation/update/delete event of target unstructured obj, and it can
   102  // trigger obj re-sync by calling Enqueue function of the returned accessor.
   103  func NewRealKatalystCustomConfigTargetAccessor(
   104  	gvr metav1.GroupVersionResource,
   105  	client dynamic.Interface,
   106  	handlerInfos map[string]KatalystCustomConfigTargetHandlerFunc,
   107  ) (*RealKatalystCustomConfigTargetAccessor, error) {
   108  	dynamicInformer := dynamicinformer.NewFilteredDynamicInformer(client,
   109  		native.ToSchemaGVR(gvr.Group, gvr.Version, gvr.Resource),
   110  		metav1.NamespaceAll,
   111  		time.Hour*24,
   112  		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   113  		nil)
   114  
   115  	k := &RealKatalystCustomConfigTargetAccessor{
   116  		stopCh:                            make(chan struct{}),
   117  		gvr:                               gvr,
   118  		targetLister:                      dynamicInformer.Lister(),
   119  		targetInformer:                    dynamicInformer.Informer(),
   120  		targetHandlerFuncWithSyncQueueMap: make(map[string]targetHandlerFuncWithSyncQueue),
   121  	}
   122  
   123  	k.targetInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
   124  		AddFunc:    k.addTargetEventHandle,
   125  		UpdateFunc: k.updateTargetEventHandle,
   126  		DeleteFunc: k.deleteTargetEventHandle,
   127  	})
   128  
   129  	for name, info := range handlerInfos {
   130  		k.targetHandlerFuncWithSyncQueueMap[name] = targetHandlerFuncWithSyncQueue{
   131  			targetHandlerFunc: info,
   132  			syncQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(),
   133  				name+"-"+gvr.Resource),
   134  		}
   135  	}
   136  
   137  	return k, nil
   138  }
   139  
   140  func (k *RealKatalystCustomConfigTargetAccessor) Start() {
   141  	// run target informer
   142  	go k.targetInformer.Run(k.stopCh)
   143  
   144  	for _, info := range k.targetHandlerFuncWithSyncQueueMap {
   145  		for i := 0; i < targetWorkerCount; i++ {
   146  			go wait.Until(k.generateWorker(info), time.Second, k.stopCh)
   147  		}
   148  	}
   149  
   150  	klog.Infof("target accessor of %s has been started", k.gvr.String())
   151  }
   152  
   153  func (k *RealKatalystCustomConfigTargetAccessor) Stop() {
   154  	klog.Infof("target accessor of %s is stopping", k.gvr.String())
   155  
   156  	for _, info := range k.targetHandlerFuncWithSyncQueueMap {
   157  		info.syncQueue.ShutDown()
   158  	}
   159  
   160  	close(k.stopCh)
   161  }
   162  
   163  // Enqueue will add the obj to the work queue of the target handler, if name is empty,
   164  // it will add the obj to all the work queue of the target handler
   165  func (k *RealKatalystCustomConfigTargetAccessor) Enqueue(name string, obj *unstructured.Unstructured) {
   166  	if len(name) == 0 {
   167  		k.enqueueTarget(obj)
   168  		return
   169  	}
   170  
   171  	if obj == nil {
   172  		klog.Warning("trying to enqueue a nil kcc target")
   173  		return
   174  	}
   175  
   176  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
   177  	if err != nil {
   178  		utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", obj, err))
   179  		return
   180  	}
   181  
   182  	info, ok := k.targetHandlerFuncWithSyncQueueMap[name]
   183  	if ok {
   184  		info.syncQueue.Add(key)
   185  	} else {
   186  		klog.Warningf("target handler %s not found", name)
   187  	}
   188  }
   189  
   190  func (k *RealKatalystCustomConfigTargetAccessor) Get(namespace, name string) (*unstructured.Unstructured, error) {
   191  	if !k.targetInformer.HasSynced() {
   192  		return nil, fmt.Errorf("target targetInformer for %s not synced", k.gvr)
   193  	}
   194  
   195  	if namespace == "" {
   196  		obj, err := k.targetLister.Get(name)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  		return obj.(*unstructured.Unstructured), nil
   201  	}
   202  
   203  	obj, err := k.targetLister.ByNamespace(namespace).Get(name)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	return obj.(*unstructured.Unstructured).DeepCopy(), nil
   208  }
   209  
   210  func (k *RealKatalystCustomConfigTargetAccessor) List(selector labels.Selector) ([]*unstructured.Unstructured, error) {
   211  	if !k.targetInformer.HasSynced() {
   212  		return nil, fmt.Errorf("target targetInformer for %s not synced", k.gvr)
   213  	}
   214  
   215  	list, err := k.targetLister.List(selector)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	ret := make([]*unstructured.Unstructured, 0, len(list))
   221  	for _, o := range list {
   222  		ret = append(ret, o.(*unstructured.Unstructured).DeepCopy())
   223  	}
   224  	return ret, nil
   225  }
   226  
   227  func (k *RealKatalystCustomConfigTargetAccessor) addTargetEventHandle(obj interface{}) {
   228  	t, ok := obj.(*unstructured.Unstructured)
   229  	if !ok {
   230  		klog.Errorf("cannot convert obj to *unstructured.Unstructured: %v", obj)
   231  		return
   232  	}
   233  
   234  	klog.V(4).Infof("notice addition of %s, %s", k.gvr, native.GenerateUniqObjectNameKey(t))
   235  	k.enqueueTarget(t)
   236  }
   237  
   238  func (k *RealKatalystCustomConfigTargetAccessor) updateTargetEventHandle(_, new interface{}) {
   239  	t, ok := new.(*unstructured.Unstructured)
   240  	if !ok {
   241  		klog.Errorf("cannot convert obj to *unstructured.Unstructured: %v", new)
   242  		return
   243  	}
   244  
   245  	klog.V(4).Infof("notice update of %s, %s", k.gvr, native.GenerateUniqObjectNameKey(t))
   246  	k.enqueueTarget(t)
   247  }
   248  
   249  func (k *RealKatalystCustomConfigTargetAccessor) deleteTargetEventHandle(obj interface{}) {
   250  	t, ok := obj.(*unstructured.Unstructured)
   251  	if !ok {
   252  		klog.Errorf("cannot convert obj to *unstructured.Unstructured: %v", obj)
   253  		return
   254  	}
   255  
   256  	klog.V(4).Infof("notice delete of %s, %s", k.gvr, native.GenerateUniqObjectNameKey(t))
   257  	k.enqueueTarget(t)
   258  }
   259  
   260  func (k *RealKatalystCustomConfigTargetAccessor) enqueueTarget(obj *unstructured.Unstructured) {
   261  	if obj == nil {
   262  		klog.Warning("trying to enqueue a nil kcc target")
   263  		return
   264  	}
   265  
   266  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
   267  	if err != nil {
   268  		utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", obj, err))
   269  		return
   270  	}
   271  
   272  	for _, info := range k.targetHandlerFuncWithSyncQueueMap {
   273  		info.syncQueue.Add(key)
   274  	}
   275  }
   276  
   277  func (k *RealKatalystCustomConfigTargetAccessor) generateWorker(queue targetHandlerFuncWithSyncQueue) func() {
   278  	return func() {
   279  		for k.processNextKatalystCustomConfigTargetItem(queue.syncQueue, queue.targetHandlerFunc) {
   280  		}
   281  	}
   282  }
   283  
   284  func (k *RealKatalystCustomConfigTargetAccessor) processNextKatalystCustomConfigTargetItem(queue workqueue.RateLimitingInterface, handler KatalystCustomConfigTargetHandlerFunc) bool {
   285  	key, quit := queue.Get()
   286  	if quit {
   287  		return false
   288  	}
   289  	defer queue.Done(key)
   290  
   291  	err := k.syncHandler(key.(string), handler)
   292  	if err == nil {
   293  		queue.Forget(key)
   294  		return true
   295  	}
   296  
   297  	klog.Errorf("sync kcc target %q failed with %v", key, err)
   298  	queue.AddRateLimited(key)
   299  
   300  	return true
   301  }
   302  
   303  func (k *RealKatalystCustomConfigTargetAccessor) syncHandler(key string, handlerFunc KatalystCustomConfigTargetHandlerFunc) error {
   304  	if !k.targetInformer.HasSynced() {
   305  		return fmt.Errorf("target targetInformer for %s not synced", k.gvr)
   306  	}
   307  
   308  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   309  	if err != nil {
   310  		klog.Errorf("failed to split namespace and name from key %s", key)
   311  		return err
   312  	}
   313  
   314  	target, err := k.Get(namespace, name)
   315  	if apierrors.IsNotFound(err) {
   316  		klog.Warningf("%s resource %s is not found", k.gvr.String(), key)
   317  		return nil
   318  	} else if err != nil {
   319  		klog.Errorf("%s resource %s get error: %v", k.gvr.String(), key, err)
   320  		return err
   321  	}
   322  
   323  	return handlerFunc(k.gvr, target)
   324  }