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 }