github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/kcc/kcc.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 v1 "k8s.io/api/core/v1" 25 apiequality "k8s.io/apimachinery/pkg/api/equality" 26 apierrors "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/labels" 30 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 "k8s.io/apimachinery/pkg/util/sets" 32 "k8s.io/apimachinery/pkg/util/wait" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/client-go/util/retry" 35 "k8s.io/client-go/util/workqueue" 36 "k8s.io/klog/v2" 37 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 38 39 configapis "github.com/kubewharf/katalyst-api/pkg/apis/config/v1alpha1" 40 configinformers "github.com/kubewharf/katalyst-api/pkg/client/informers/externalversions/config/v1alpha1" 41 "github.com/kubewharf/katalyst-api/pkg/client/listers/config/v1alpha1" 42 kcclient "github.com/kubewharf/katalyst-core/pkg/client" 43 "github.com/kubewharf/katalyst-core/pkg/client/control" 44 "github.com/kubewharf/katalyst-core/pkg/config/controller" 45 "github.com/kubewharf/katalyst-core/pkg/config/generic" 46 "github.com/kubewharf/katalyst-core/pkg/consts" 47 kcctarget "github.com/kubewharf/katalyst-core/pkg/controller/kcc/target" 48 kccutil "github.com/kubewharf/katalyst-core/pkg/controller/kcc/util" 49 "github.com/kubewharf/katalyst-core/pkg/metrics" 50 "github.com/kubewharf/katalyst-core/pkg/util" 51 "github.com/kubewharf/katalyst-core/pkg/util/native" 52 ) 53 54 const ( 55 kccControllerName = "kcc" 56 ) 57 58 const ( 59 kccWorkerCount = 1 60 ) 61 62 const ( 63 kccConditionTypeValidReasonNormal = "Normal" 64 kccConditionTypeValidReasonPrioritySelectorKeyInvalid = "PrioritySelectorKeyInvalid" 65 kccConditionTypeValidReasonTargetTypeOverlap = "TargetTypeOverlap" 66 kccConditionTypeValidReasonTargetTypeNotAllowed = "TargetTypeNotAllowed" 67 kccConditionTypeValidReasonTargetTypeNotExist = "TargetTypeNotExist" 68 kccConditionTypeValidReasonTerminating = "Terminating" 69 ) 70 71 type KatalystCustomConfigController struct { 72 ctx context.Context 73 dryRun bool 74 kccConfig *controller.KCCConfig 75 76 client *kcclient.GenericClientSet 77 kccControl control.KCCControl 78 unstructuredControl control.UnstructuredControl 79 80 // katalystCustomConfigLister can list/get KatalystCustomConfig from the shared informer's store 81 katalystCustomConfigLister v1alpha1.KatalystCustomConfigLister 82 // katalystCustomConfigSyncQueue queue for KatalystCustomConfig 83 katalystCustomConfigSyncQueue workqueue.RateLimitingInterface 84 85 syncedFunc []cache.InformerSynced 86 87 // targetHandler store gvr kcc and gvr 88 targetHandler *kcctarget.KatalystCustomConfigTargetHandler 89 90 // metricsEmitter for emit metrics 91 metricsEmitter metrics.MetricEmitter 92 } 93 94 func NewKatalystCustomConfigController( 95 ctx context.Context, 96 genericConf *generic.GenericConfiguration, 97 _ *controller.GenericControllerConfiguration, 98 kccConfig *controller.KCCConfig, 99 client *kcclient.GenericClientSet, 100 katalystCustomConfigInformer configinformers.KatalystCustomConfigInformer, 101 metricsEmitter metrics.MetricEmitter, 102 targetHandler *kcctarget.KatalystCustomConfigTargetHandler, 103 ) (*KatalystCustomConfigController, error) { 104 k := &KatalystCustomConfigController{ 105 ctx: ctx, 106 client: client, 107 dryRun: genericConf.DryRun, 108 kccConfig: kccConfig, 109 katalystCustomConfigLister: katalystCustomConfigInformer.Lister(), 110 targetHandler: targetHandler, 111 katalystCustomConfigSyncQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), kccControllerName), 112 syncedFunc: []cache.InformerSynced{ 113 katalystCustomConfigInformer.Informer().HasSynced, 114 targetHandler.HasSynced, 115 }, 116 } 117 118 katalystCustomConfigInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 119 AddFunc: k.addKatalystCustomConfigEventHandle, 120 UpdateFunc: k.updateKatalystCustomConfigEventHandle, 121 }) 122 123 if metricsEmitter == nil { 124 k.metricsEmitter = metrics.DummyMetrics{} 125 } else { 126 k.metricsEmitter = metricsEmitter.WithTags(kccControllerName) 127 } 128 129 k.kccControl = control.DummyKCCControl{} 130 k.unstructuredControl = control.DummyUnstructuredControl{} 131 if !k.dryRun { 132 k.kccControl = control.NewRealKCCControl(client.InternalClient) 133 k.unstructuredControl = control.NewRealUnstructuredControl(client.DynamicClient) 134 } 135 136 // register kcc-target informer handler 137 targetHandler.RegisterTargetHandler(kccControllerName, k.katalystCustomConfigTargetHandler) 138 return k, nil 139 } 140 141 func (k *KatalystCustomConfigController) Run() { 142 defer utilruntime.HandleCrash() 143 defer k.katalystCustomConfigSyncQueue.ShutDown() 144 145 defer klog.Infof("shutting down %s controller", kccControllerName) 146 147 if !cache.WaitForCacheSync(k.ctx.Done(), k.syncedFunc...) { 148 utilruntime.HandleError(fmt.Errorf("unable to sync caches for %s controller", kccControllerName)) 149 return 150 } 151 klog.Infof("caches are synced for %s controller", kccControllerName) 152 klog.Infof("start %d workers for %s controller", kccWorkerCount, kccControllerName) 153 154 for i := 0; i < kccWorkerCount; i++ { 155 go wait.Until(k.worker, time.Second, k.ctx.Done()) 156 } 157 158 <-k.ctx.Done() 159 } 160 161 func (k *KatalystCustomConfigController) addKatalystCustomConfigEventHandle(obj interface{}) { 162 t, ok := obj.(*configapis.KatalystCustomConfig) 163 if !ok { 164 klog.Errorf("[kcc] cannot convert obj to *KatalystCustomConfig: %v", obj) 165 return 166 } 167 168 klog.V(4).Infof("[kcc] notice addition of KatalystCustomConfig %s", native.GenerateUniqObjectNameKey(t)) 169 k.enqueueKatalystCustomConfig(t) 170 } 171 172 func (k *KatalystCustomConfigController) updateKatalystCustomConfigEventHandle(_, new interface{}) { 173 t, ok := new.(*configapis.KatalystCustomConfig) 174 if !ok { 175 klog.Errorf("[kcc] cannot convert obj to *KatalystCustomConfig: %v", new) 176 return 177 } 178 179 klog.V(4).Infof("[kcc] notice update of KatalystCustomConfig %s", native.GenerateUniqObjectNameKey(t)) 180 k.enqueueKatalystCustomConfig(t) 181 } 182 183 func (k *KatalystCustomConfigController) enqueueKatalystCustomConfig(kcc *configapis.KatalystCustomConfig) { 184 if kcc == nil { 185 klog.Warning("[kcc] trying to enqueue a nil kcc") 186 return 187 } 188 189 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(kcc) 190 if err != nil { 191 utilruntime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", kcc, err)) 192 return 193 } 194 195 k.katalystCustomConfigSyncQueue.Add(key) 196 197 // if this kcc has same gvr with others, we also enqueue them to reconcile 198 if kccKeys := k.targetHandler.GetKCCKeyListByGVR(kcc.Spec.TargetType); len(kccKeys) > 1 { 199 klog.Infof("[kcc] kcc %s whose target type is overlap with keys: %s", native.GenerateUniqObjectNameKey(kcc), kccKeys) 200 for _, otherKey := range kccKeys { 201 if key == otherKey { 202 continue 203 } 204 k.katalystCustomConfigSyncQueue.Add(otherKey) 205 } 206 } 207 } 208 209 func (k *KatalystCustomConfigController) worker() { 210 for k.processNextKatalystCustomConfigWorkItem() { 211 } 212 } 213 214 func (k *KatalystCustomConfigController) processNextKatalystCustomConfigWorkItem() bool { 215 key, quit := k.katalystCustomConfigSyncQueue.Get() 216 if quit { 217 return false 218 } 219 defer k.katalystCustomConfigSyncQueue.Done(key) 220 221 err := k.syncKatalystCustomConfig(key.(string)) 222 if err == nil { 223 k.katalystCustomConfigSyncQueue.Forget(key) 224 return true 225 } 226 227 utilruntime.HandleError(fmt.Errorf("sync kcc %q failed with %v", key, err)) 228 k.katalystCustomConfigSyncQueue.AddRateLimited(key) 229 230 return true 231 } 232 233 func (k *KatalystCustomConfigController) syncKatalystCustomConfig(key string) error { 234 klog.V(4).Infof("[kcc] processing kcc %s", key) 235 kcc, err := k.getKCCByKey(key) 236 if apierrors.IsNotFound(err) { 237 klog.Warningf("[kcc] kcc %s is not found", key) 238 return nil 239 } else if err != nil { 240 klog.Errorf("[kcc] kcc %s get error: %v", key, err) 241 return err 242 } 243 244 // handle kcc deletion 245 if kcc.DeletionTimestamp != nil { 246 err := k.handleKCCFinalizer(kcc) 247 if err != nil { 248 return err 249 } 250 return nil 251 } 252 253 // make sure kcc obj has finalizer to prevent it from being deleted by mistake 254 kcc, err = k.ensureKCCFinalizer(kcc) 255 if err != nil { 256 return err 257 } 258 259 // get related kcc keys of gvr 260 kccKeys := k.targetHandler.GetKCCKeyListByGVR(kcc.Spec.TargetType) 261 if len(kccKeys) == 0 { 262 if !k.kccConfig.ValidAPIGroupSet.Has(kcc.Spec.TargetType.Group) { 263 // kcc with not allowed target type, of which api group is not allowed 264 return k.updateKCCStatusCondition(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionFalse, 265 kccConditionTypeValidReasonTargetTypeNotAllowed, fmt.Sprintf("api group %s of target type %s is not in valid api group set %v", 266 kcc.Spec.TargetType.Group, kcc.Spec.TargetType, k.kccConfig.ValidAPIGroupSet)) 267 } 268 269 // kcc target type is not exist, we will re-enqueue after 30s as 270 // crd of the gvr may not have been created yet. Because we not list/watch crd add/update event, we 271 // reconcile it periodically to check it whether the gvr is created 272 k.katalystCustomConfigSyncQueue.AddAfter(key, 30*time.Second) 273 274 return k.updateKCCStatusCondition(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionFalse, 275 kccConditionTypeValidReasonTargetTypeNotExist, fmt.Sprintf("crd of target type %s is not created", kcc.Spec.TargetType)) 276 } else if len(kccKeys) > 1 { 277 // kcc with overlap target type 278 // we will check other kcc whether is alive 279 aliveKeys := make([]string, 0, len(kccKeys)) 280 for _, otherKey := range kccKeys { 281 if otherKey == key { 282 continue 283 } 284 285 otherKCC, err := k.getKCCByKey(otherKey) 286 if err != nil && !apierrors.IsNotFound(err) { 287 return err 288 } 289 290 if err == nil && otherKCC.GetDeletionTimestamp() == nil { 291 aliveKeys = append(aliveKeys, otherKey) 292 } 293 } 294 295 if len(aliveKeys) > 0 { 296 klog.Errorf("[kcc] kcc %s is overlap with other key: %s", native.GenerateUniqObjectNameKey(kcc), aliveKeys) 297 return k.updateKCCStatusCondition(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionFalse, 298 kccConditionTypeValidReasonTargetTypeOverlap, fmt.Sprintf("it is overlap with other kcc %v", aliveKeys)) 299 } 300 } 301 302 // check whether kcc node selector allowed key list is valid 303 msg, ok := checkNodeLabelSelectorAllowedKeyList(kcc) 304 if !ok { 305 return k.updateKCCStatusCondition(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionFalse, 306 kccConditionTypeValidReasonPrioritySelectorKeyInvalid, msg) 307 } 308 309 targetAccessor, ok := k.targetHandler.GetTargetAccessorByGVR(kcc.Spec.TargetType) 310 if !ok { 311 return fmt.Errorf("cannot get accessor by gvr %s", kcc.Spec.TargetType.String()) 312 } 313 314 // get all related targets 315 targets, err := targetAccessor.List(labels.Everything()) 316 if err != nil { 317 return err 318 } 319 320 // filter all deleting targets 321 targets = native.FilterOutDeletingUnstructured(targets) 322 323 // collect all invalid configs 324 invalidConfigList, err := k.collectInvalidConfigs(targets) 325 if err != nil { 326 return err 327 } 328 329 oldKCC := kcc.DeepCopy() 330 kcc.Status.InvalidTargetConfigList = invalidConfigList 331 kcc.Status.ObservedGeneration = kcc.Generation 332 setKatalystCustomConfigConditions(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionTrue, 333 kccConditionTypeValidReasonNormal, "") 334 if !apiequality.Semantic.DeepEqual(oldKCC.Status, kcc.Status) { 335 _, err = k.kccControl.UpdateKCCStatus(k.ctx, kcc, metav1.UpdateOptions{}) 336 if err != nil { 337 return err 338 } 339 } 340 341 return nil 342 } 343 344 func (k *KatalystCustomConfigController) getKCCByKey(key string) (*configapis.KatalystCustomConfig, error) { 345 namespace, name, err := cache.SplitMetaNamespaceKey(key) 346 if err != nil { 347 klog.Errorf("[kcc] failed to split namespace and name from key %s", key) 348 return nil, err 349 } 350 351 return k.client.InternalClient.ConfigV1alpha1().KatalystCustomConfigs(namespace).Get(k.ctx, name, metav1.GetOptions{ 352 ResourceVersion: "0", 353 }) 354 } 355 356 // katalystCustomConfigTargetHandler process object of kcc target type from targetAccessor, and 357 // KatalystCustomConfigTargetAccessor will call this handler when some update event on target is added. 358 func (k *KatalystCustomConfigController) katalystCustomConfigTargetHandler(gvr metav1.GroupVersionResource, target *unstructured.Unstructured) error { 359 for _, syncFunc := range k.syncedFunc { 360 if !syncFunc() { 361 return fmt.Errorf("[kcc] informer has not synced") 362 } 363 } 364 365 klog.V(4).Infof("[kcc] gvr: %s, target: %s updated", gvr.String(), native.GenerateUniqObjectNameKey(target)) 366 if target.GetDeletionTimestamp() != nil { 367 err := k.handleKCCTargetFinalizer(gvr, target) 368 if err != nil { 369 return err 370 } 371 return nil 372 } 373 374 target, err := kccutil.EnsureKCCTargetFinalizer(k.ctx, k.unstructuredControl, 375 consts.KatalystCustomConfigTargetFinalizerKCC, gvr, target) 376 if err != nil { 377 return err 378 } 379 380 // kcc target updated trigger its kcc to reconcile 381 if util.ToKCCTargetResource(target).IsUpdated() { 382 kccKeys := k.targetHandler.GetKCCKeyListByGVR(gvr) 383 for _, key := range kccKeys { 384 k.katalystCustomConfigSyncQueue.Add(key) 385 } 386 return nil 387 } 388 389 return nil 390 } 391 392 // handleKCCTargetFinalizer enqueue all related kcc to reconcile when a kcc target was deleted 393 func (k *KatalystCustomConfigController) handleKCCTargetFinalizer(gvr metav1.GroupVersionResource, target *unstructured.Unstructured) error { 394 if !controllerutil.ContainsFinalizer(target, consts.KatalystCustomConfigTargetFinalizerKCC) { 395 return nil 396 } 397 398 klog.Infof("[kcc] handling gvr: %s kcc target %s finalizer", gvr.String(), native.GenerateUniqObjectNameKey(target)) 399 kccKeys := k.targetHandler.GetKCCKeyListByGVR(gvr) 400 for _, key := range kccKeys { 401 k.katalystCustomConfigSyncQueue.Add(key) 402 } 403 404 err := kccutil.RemoveKCCTargetFinalizer(k.ctx, k.unstructuredControl, consts.KatalystCustomConfigTargetFinalizerKCC, gvr, target) 405 if err != nil { 406 return err 407 } 408 409 klog.Infof("[kcc] success remove gvr: %s kcc target %s finalizer", gvr.String(), native.GenerateUniqObjectNameKey(target)) 410 return nil 411 } 412 413 func (k *KatalystCustomConfigController) collectInvalidConfigs(list []*unstructured.Unstructured) ([]string, error) { 414 invalidConfigs := sets.String{} 415 for _, o := range list { 416 if !util.ToKCCTargetResource(o).CheckValid() { 417 invalidConfigs.Insert(native.GenerateUniqObjectNameKey(o)) 418 } 419 } 420 421 return invalidConfigs.List(), nil 422 } 423 424 func (k *KatalystCustomConfigController) updateKCCStatusCondition(kcc *configapis.KatalystCustomConfig, 425 conditionType configapis.KatalystCustomConfigConditionType, status v1.ConditionStatus, reason, message string, 426 ) error { 427 updated := setKatalystCustomConfigConditions(kcc, conditionType, status, reason, message) 428 if updated || kcc.Status.ObservedGeneration != kcc.Generation { 429 kcc.Status.ObservedGeneration = kcc.Generation 430 _, err := k.kccControl.UpdateKCCStatus(k.ctx, kcc, metav1.UpdateOptions{}) 431 if err != nil { 432 return err 433 } 434 } 435 436 return nil 437 } 438 439 // handleKCCFinalizer checks if there still exist CRs for the given kcc 440 // if true, protect kcc CR from deleting before its configuration CRs been deleted. 441 func (k *KatalystCustomConfigController) handleKCCFinalizer(kcc *configapis.KatalystCustomConfig) error { 442 if !controllerutil.ContainsFinalizer(kcc, consts.KatalystCustomConfigFinalizerKCC) { 443 return nil 444 } 445 446 accessor, ok := k.targetHandler.GetTargetAccessorByGVR(kcc.Spec.TargetType) 447 if ok { 448 // only if accessor of gvr exists, we will check its object whether exists 449 list, err := accessor.List(labels.Everything()) 450 if err != nil { 451 return err 452 } 453 454 if len(list) > 0 { 455 residueObjNames := sets.String{} 456 for _, o := range list { 457 residueObjNames.Insert(native.GenerateUniqObjectNameKey(o)) 458 } 459 460 return k.updateKCCStatusCondition(kcc, configapis.KatalystCustomConfigConditionTypeValid, v1.ConditionFalse, 461 kccConditionTypeValidReasonTerminating, fmt.Sprintf("residue configs: %s", residueObjNames.List())) 462 } 463 } 464 465 err := k.removeKCCFinalizer(kcc) 466 if err != nil { 467 return err 468 } 469 return nil 470 } 471 472 func (k *KatalystCustomConfigController) ensureKCCFinalizer(kcc *configapis.KatalystCustomConfig) (*configapis.KatalystCustomConfig, error) { 473 err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 474 var err, getErr error 475 if controllerutil.ContainsFinalizer(kcc, consts.KatalystCustomConfigFinalizerKCC) { 476 return nil 477 } 478 479 controllerutil.AddFinalizer(kcc, consts.KatalystCustomConfigFinalizerKCC) 480 newKCC, err := k.kccControl.UpdateKCC(k.ctx, kcc, metav1.UpdateOptions{}) 481 if apierrors.IsConflict(err) { 482 newKCC, getErr = k.client.InternalClient.ConfigV1alpha1().KatalystCustomConfigs(kcc.Namespace).Get(k.ctx, kcc.Name, metav1.GetOptions{ResourceVersion: "0"}) 483 if err != nil { 484 return getErr 485 } 486 } 487 488 kcc = newKCC 489 return err 490 }) 491 if err != nil { 492 return nil, err 493 } 494 495 return kcc, nil 496 } 497 498 func (k *KatalystCustomConfigController) removeKCCFinalizer(kcc *configapis.KatalystCustomConfig) error { 499 err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { 500 var err, getErr error 501 if !controllerutil.ContainsFinalizer(kcc, consts.KatalystCustomConfigFinalizerKCC) { 502 return nil 503 } 504 505 controllerutil.RemoveFinalizer(kcc, consts.KatalystCustomConfigFinalizerKCC) 506 newKCC, err := k.kccControl.UpdateKCC(k.ctx, kcc, metav1.UpdateOptions{}) 507 if apierrors.IsConflict(err) { 508 newKCC, getErr = k.client.InternalClient.ConfigV1alpha1().KatalystCustomConfigs(kcc.Namespace).Get(k.ctx, kcc.Name, metav1.GetOptions{ResourceVersion: "0"}) 509 if err != nil { 510 return getErr 511 } 512 } 513 514 kcc = newKCC 515 return err 516 }) 517 if err != nil { 518 return err 519 } 520 521 return nil 522 } 523 524 // checkNodeLabelSelectorAllowedKeyList checks if the priority of NodeLabelSelectorAllowedKeyList is duplicated 525 func checkNodeLabelSelectorAllowedKeyList(kcc *configapis.KatalystCustomConfig) (string, bool) { 526 duplicatedPrioritySet := sets.NewInt32() 527 priorityKeyListMap := map[int32]bool{} 528 for _, priorityAllowedKeyList := range kcc.Spec.NodeLabelSelectorAllowedKeyList { 529 if priorityKeyListMap[priorityAllowedKeyList.Priority] { 530 duplicatedPrioritySet.Insert(priorityAllowedKeyList.Priority) 531 continue 532 } 533 priorityKeyListMap[priorityAllowedKeyList.Priority] = true 534 } 535 536 if len(duplicatedPrioritySet) > 0 { 537 return fmt.Sprintf("duplicated priority: %v", duplicatedPrioritySet.List()), false 538 } 539 540 return "", true 541 } 542 543 // setKatalystCustomConfigConditions is used to set conditions for kcc 544 func setKatalystCustomConfigConditions( 545 kcc *configapis.KatalystCustomConfig, 546 conditionType configapis.KatalystCustomConfigConditionType, 547 conditionStatus v1.ConditionStatus, 548 reason, message string, 549 ) bool { 550 var conditionIndex int 551 conditions := kcc.Status.Conditions 552 for conditionIndex = 0; conditionIndex < len(conditions); conditionIndex++ { 553 if conditions[conditionIndex].Type == conditionType { 554 break 555 } 556 } 557 558 if conditionIndex == len(conditions) { 559 conditions = append(conditions, configapis.KatalystCustomConfigCondition{ 560 Type: conditionType, 561 }) 562 } 563 564 condition := &conditions[conditionIndex] 565 if condition.Status != conditionStatus || condition.Message != message || 566 condition.Reason != reason { 567 condition.LastTransitionTime = metav1.NewTime(time.Now()) 568 condition.Status = conditionStatus 569 condition.Reason = reason 570 condition.Message = message 571 kcc.Status.Conditions = conditions 572 return true 573 } 574 575 return false 576 }