github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/kcc/cnc.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 kcc
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    30  	"k8s.io/apimachinery/pkg/util/wait"
    31  	"k8s.io/client-go/tools/cache"
    32  	"k8s.io/client-go/util/workqueue"
    33  	"k8s.io/klog/v2"
    34  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    35  
    36  	configapi "github.com/kubewharf/katalyst-api/pkg/apis/config/v1alpha1"
    37  	configinformers "github.com/kubewharf/katalyst-api/pkg/client/informers/externalversions/config/v1alpha1"
    38  	"github.com/kubewharf/katalyst-api/pkg/client/listers/config/v1alpha1"
    39  	kcclient "github.com/kubewharf/katalyst-core/pkg/client"
    40  	"github.com/kubewharf/katalyst-core/pkg/client/control"
    41  	"github.com/kubewharf/katalyst-core/pkg/config/controller"
    42  	"github.com/kubewharf/katalyst-core/pkg/config/generic"
    43  	"github.com/kubewharf/katalyst-core/pkg/consts"
    44  	kcctarget "github.com/kubewharf/katalyst-core/pkg/controller/kcc/target"
    45  	"github.com/kubewharf/katalyst-core/pkg/controller/kcc/util"
    46  	"github.com/kubewharf/katalyst-core/pkg/metrics"
    47  	katalystutil "github.com/kubewharf/katalyst-core/pkg/util"
    48  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    49  )
    50  
    51  const (
    52  	cncControllerName = "cnc"
    53  )
    54  
    55  const (
    56  	cncWorkerCount = 16
    57  )
    58  
    59  type CustomNodeConfigController struct {
    60  	ctx       context.Context
    61  	dryRun    bool
    62  	kccConfig *controller.KCCConfig
    63  
    64  	client              *kcclient.GenericClientSet
    65  	cncControl          control.CNCControl
    66  	unstructuredControl control.UnstructuredControl
    67  
    68  	// customNodeConfigLister can list/get CustomNodeConfig from the shared informer's store
    69  	customNodeConfigLister v1alpha1.CustomNodeConfigLister
    70  	// customNodeConfigSyncQueue queue for CustomNodeConfig
    71  	customNodeConfigSyncQueue workqueue.RateLimitingInterface
    72  
    73  	syncedFunc []cache.InformerSynced
    74  
    75  	// targetHandler store gvr of kcc and gvr
    76  	targetHandler *kcctarget.KatalystCustomConfigTargetHandler
    77  
    78  	// metricsEmitter for emit metrics
    79  	metricsEmitter metrics.MetricEmitter
    80  }
    81  
    82  func NewCustomNodeConfigController(
    83  	ctx context.Context,
    84  	genericConf *generic.GenericConfiguration,
    85  	_ *controller.GenericControllerConfiguration,
    86  	kccConfig *controller.KCCConfig,
    87  	client *kcclient.GenericClientSet,
    88  	customNodeConfigInformer configinformers.CustomNodeConfigInformer,
    89  	metricsEmitter metrics.MetricEmitter,
    90  	targetHandler *kcctarget.KatalystCustomConfigTargetHandler,
    91  ) (*CustomNodeConfigController, error) {
    92  	c := &CustomNodeConfigController{
    93  		ctx:                       ctx,
    94  		client:                    client,
    95  		dryRun:                    genericConf.DryRun,
    96  		kccConfig:                 kccConfig,
    97  		customNodeConfigLister:    customNodeConfigInformer.Lister(),
    98  		targetHandler:             targetHandler,
    99  		customNodeConfigSyncQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), cncControllerName),
   100  		syncedFunc: []cache.InformerSynced{
   101  			customNodeConfigInformer.Informer().HasSynced,
   102  			targetHandler.HasSynced,
   103  		},
   104  	}
   105  
   106  	customNodeConfigInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   107  		AddFunc:    c.addCustomNodeConfigEventHandle,
   108  		UpdateFunc: c.updateCustomNodeConfigEventHandle,
   109  	})
   110  
   111  	if metricsEmitter == nil {
   112  		c.metricsEmitter = metrics.DummyMetrics{}
   113  	} else {
   114  		c.metricsEmitter = metricsEmitter.WithTags(cncControllerName)
   115  	}
   116  
   117  	c.cncControl = control.DummyCNCControl{}
   118  	c.unstructuredControl = control.DummyUnstructuredControl{}
   119  	if !c.dryRun {
   120  		c.cncControl = control.NewRealCNCControl(client.InternalClient)
   121  		c.unstructuredControl = control.NewRealUnstructuredControl(client.DynamicClient)
   122  	}
   123  
   124  	// register kcc-target informer handler
   125  	targetHandler.RegisterTargetHandler(cncControllerName, c.katalystCustomConfigTargetHandler)
   126  	return c, nil
   127  }
   128  
   129  func (c *CustomNodeConfigController) Run() {
   130  	defer utilruntime.HandleCrash()
   131  	defer func() {
   132  		c.customNodeConfigSyncQueue.ShutDown()
   133  	}()
   134  
   135  	defer klog.Infof("shutting down %s controller", cncControllerName)
   136  
   137  	if !cache.WaitForCacheSync(c.ctx.Done(), c.syncedFunc...) {
   138  		utilruntime.HandleError(fmt.Errorf("unable to sync caches for %s controller", cncControllerName))
   139  		return
   140  	}
   141  	klog.Infof("caches are synced for %s controller", cncControllerName)
   142  	klog.Infof("start %d workers for %s controller", cncWorkerCount, cncControllerName)
   143  
   144  	for i := 0; i < cncWorkerCount; i++ {
   145  		go wait.Until(c.worker, 10*time.Millisecond, c.ctx.Done())
   146  	}
   147  	go wait.Until(c.clearUnusedConfig, 5*time.Minute, c.ctx.Done())
   148  
   149  	<-c.ctx.Done()
   150  }
   151  
   152  // katalystCustomConfigTargetHandler process object of kcc target type from targetAccessor, and
   153  // KatalystCustomConfigTargetAccessor will call this handler when some update event on target is added.
   154  func (c *CustomNodeConfigController) katalystCustomConfigTargetHandler(gvr metav1.GroupVersionResource, target *unstructured.Unstructured) error {
   155  	for _, syncFunc := range c.syncedFunc {
   156  		if !syncFunc() {
   157  			return fmt.Errorf("informer has not synced")
   158  		}
   159  	}
   160  
   161  	targetAccessor, ok := c.targetHandler.GetTargetAccessorByGVR(gvr)
   162  	if !ok || targetAccessor == nil {
   163  		return fmt.Errorf("%s cnc target accessor not found", gvr)
   164  	}
   165  
   166  	klog.V(4).Infof("[cnc] gvr: %s, target: %s updated", gvr.String(), native.GenerateUniqObjectNameKey(target))
   167  
   168  	if target.GetDeletionTimestamp() != nil {
   169  		err := c.handleKCCTargetFinalizer(gvr, target)
   170  		if err != nil {
   171  			return err
   172  		}
   173  		return nil
   174  	}
   175  
   176  	target, err := util.EnsureKCCTargetFinalizer(c.ctx, c.unstructuredControl, consts.KatalystCustomConfigTargetFinalizerCNC, gvr, target)
   177  	if err != nil {
   178  		return err
   179  	}
   180  
   181  	err = c.enqueueAllRelatedCNCForTargetConfig(target)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (c *CustomNodeConfigController) handleKCCTargetFinalizer(gvr metav1.GroupVersionResource, target *unstructured.Unstructured) error {
   190  	if !controllerutil.ContainsFinalizer(target, consts.KatalystCustomConfigTargetFinalizerCNC) {
   191  		return nil
   192  	}
   193  
   194  	klog.Infof("[cnc] handling gvr: %s kcc target %s finalizer", gvr.String(), native.GenerateUniqObjectNameKey(target))
   195  	err := c.enqueueAllRelatedCNCForTargetConfig(target)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	err = util.RemoveKCCTargetFinalizer(c.ctx, c.unstructuredControl, consts.KatalystCustomConfigTargetFinalizerCNC, gvr, target)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	klog.Infof("[cnc] success remove gvr: %s kcc target %s finalizer", gvr.String(), native.GenerateUniqObjectNameKey(target))
   206  	return nil
   207  }
   208  
   209  func (c *CustomNodeConfigController) enqueueAllRelatedCNCForTargetConfig(target *unstructured.Unstructured) error {
   210  	relatedCNCs, err := util.GetRelatedCNCForTargetConfig(c.customNodeConfigLister, target)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	for _, cnc := range relatedCNCs {
   216  		c.enqueueCustomNodeConfig(cnc)
   217  	}
   218  
   219  	return nil
   220  }
   221  
   222  func (c *CustomNodeConfigController) addCustomNodeConfigEventHandle(obj interface{}) {
   223  	t, ok := obj.(*configapi.CustomNodeConfig)
   224  	if !ok {
   225  		klog.Errorf("[cnc] cannot convert obj to *CustomNodeConfig: %v", obj)
   226  		return
   227  	}
   228  
   229  	klog.V(4).Infof("[cnc] notice addition of CustomNodeConfig %s", native.GenerateUniqObjectNameKey(t))
   230  	c.enqueueCustomNodeConfig(t)
   231  }
   232  
   233  func (c *CustomNodeConfigController) updateCustomNodeConfigEventHandle(_, new interface{}) {
   234  	newCNC, ok := new.(*configapi.CustomNodeConfig)
   235  	if !ok {
   236  		klog.Errorf("[cnc] cannot convert obj to *CustomNodeConfig: %v", new)
   237  		return
   238  	}
   239  
   240  	klog.V(4).Infof("[cnc] notice update of CustomNodeConfig %s", native.GenerateUniqObjectNameKey(newCNC))
   241  	c.enqueueCustomNodeConfig(newCNC)
   242  }
   243  
   244  func (c *CustomNodeConfigController) enqueueCustomNodeConfig(cnc *configapi.CustomNodeConfig) {
   245  	if cnc == nil {
   246  		klog.Warning("[cnc] trying to enqueue a nil cnc")
   247  		return
   248  	}
   249  
   250  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(cnc)
   251  	if err != nil {
   252  		utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", cnc, err))
   253  		return
   254  	}
   255  
   256  	c.customNodeConfigSyncQueue.Add(key)
   257  }
   258  
   259  func (c *CustomNodeConfigController) worker() {
   260  	for c.processNextKatalystCustomConfigWorkItem() {
   261  	}
   262  }
   263  
   264  func (c *CustomNodeConfigController) processNextKatalystCustomConfigWorkItem() bool {
   265  	key, quit := c.customNodeConfigSyncQueue.Get()
   266  	if quit {
   267  		return false
   268  	}
   269  	defer c.customNodeConfigSyncQueue.Done(key)
   270  
   271  	err := c.syncCustomNodeConfig(key.(string))
   272  	if err == nil {
   273  		c.customNodeConfigSyncQueue.Forget(key)
   274  		return true
   275  	}
   276  
   277  	utilruntime.HandleError(fmt.Errorf("sync kcc %q failed with %v", key, err))
   278  	c.customNodeConfigSyncQueue.AddRateLimited(key)
   279  
   280  	return true
   281  }
   282  
   283  func (c *CustomNodeConfigController) syncCustomNodeConfig(key string) error {
   284  	klog.V(5).Infof("[cnc] processing cnc key %s", key)
   285  	_, name, err := cache.SplitMetaNamespaceKey(key)
   286  	if err != nil {
   287  		klog.Errorf("[cnc] failed to split namespace and name from key %s", key)
   288  		return err
   289  	}
   290  
   291  	cnc, err := c.customNodeConfigLister.Get(name)
   292  	if apierrors.IsNotFound(err) {
   293  		klog.Warningf("[cnc] cnc %s is not found", key)
   294  		return nil
   295  	} else if err != nil {
   296  		klog.Errorf("[cnc] cnc %s get error: %v", key, err)
   297  		return err
   298  	}
   299  
   300  	_, err = c.patchCNC(cnc, c.updateCustomNodeConfig)
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  func (c *CustomNodeConfigController) patchCNC(cnc *configapi.CustomNodeConfig,
   309  	setFunc func(*configapi.CustomNodeConfig),
   310  ) (*configapi.CustomNodeConfig, error) {
   311  	cncCopy := cnc.DeepCopy()
   312  	setFunc(cncCopy)
   313  	if apiequality.Semantic.DeepEqual(cnc, cncCopy) {
   314  		return cnc, nil
   315  	}
   316  	klog.Infof("[cnc] cnc %s config changed need to patch", cnc.GetName())
   317  	return c.cncControl.PatchCNCStatus(c.ctx, cnc.Name, cnc, cncCopy)
   318  }
   319  
   320  func (c *CustomNodeConfigController) updateCustomNodeConfig(cnc *configapi.CustomNodeConfig) {
   321  	c.targetHandler.RangeGVRTargetAccessor(func(gvr metav1.GroupVersionResource, targetAccessor kcctarget.KatalystCustomConfigTargetAccessor) bool {
   322  		matchedTarget, err := util.FindMatchedKCCTargetConfigForNode(cnc, targetAccessor)
   323  		if err != nil {
   324  			klog.Errorf("[cnc] gvr %s find matched target failed: %s", gvr, err)
   325  			return true
   326  		}
   327  
   328  		util.ApplyKCCTargetConfigToCNC(cnc, gvr, matchedTarget)
   329  		return true
   330  	})
   331  }
   332  
   333  func (c *CustomNodeConfigController) clearUnusedConfig() {
   334  	cncList, err := c.customNodeConfigLister.List(labels.Everything())
   335  	if err != nil {
   336  		klog.Errorf("[cnc] clear unused config list all custom node config failed")
   337  		return
   338  	}
   339  
   340  	// save all gvr to map
   341  	configGVRSet := make(map[string]metav1.GroupVersionResource)
   342  	c.targetHandler.RangeGVRTargetAccessor(func(gvr metav1.GroupVersionResource, _ kcctarget.KatalystCustomConfigTargetAccessor) bool {
   343  		configGVRSet[gvr.String()] = gvr
   344  		return true
   345  	})
   346  
   347  	needToDeleteFunc := func(config configapi.TargetConfig) bool {
   348  		if _, ok := configGVRSet[config.ConfigType.String()]; !ok {
   349  			return true
   350  		}
   351  		return false
   352  	}
   353  
   354  	// func for clear cnc config if gvr config not exists
   355  	setFunc := func(cnc *configapi.CustomNodeConfig) {
   356  		cnc.Status.KatalystCustomConfigList = katalystutil.RemoveUnusedTargetConfig(cnc.Status.KatalystCustomConfigList, needToDeleteFunc)
   357  	}
   358  
   359  	clearCNCConfigs := func(i int) {
   360  		cnc := cncList[i]
   361  		_, err = c.patchCNC(cnc, setFunc)
   362  		if err != nil {
   363  			klog.Errorf("[cnc] clearUnusedConfig patch cnc %s failed", cnc.GetName())
   364  			return
   365  		}
   366  	}
   367  
   368  	// parallelize to clear cnc configs
   369  	workqueue.ParallelizeUntil(c.ctx, 16, len(cncList), clearCNCConfigs)
   370  }