sigs.k8s.io/kueue@v0.6.2/pkg/controller/admissionchecks/multikueue/admissioncheck.go (about) 1 /* 2 Copyright 2024 The Kubernetes 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 multikueue 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "k8s.io/apimachinery/pkg/api/equality" 25 apimeta "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 "k8s.io/client-go/util/workqueue" 29 "k8s.io/klog/v2" 30 ctrl "sigs.k8s.io/controller-runtime" 31 "sigs.k8s.io/controller-runtime/pkg/client" 32 "sigs.k8s.io/controller-runtime/pkg/event" 33 "sigs.k8s.io/controller-runtime/pkg/handler" 34 "sigs.k8s.io/controller-runtime/pkg/reconcile" 35 36 kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1" 37 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 38 "sigs.k8s.io/kueue/pkg/util/admissioncheck" 39 ) 40 41 const ( 42 ControllerName = "kueue.x-k8s.io/multikueue" 43 ) 44 45 type multiKueueStoreHelper = admissioncheck.ConfigHelper[*kueuealpha.MultiKueueConfig, kueuealpha.MultiKueueConfig] 46 47 func newMultiKueueStoreHelper(c client.Client) (*multiKueueStoreHelper, error) { 48 return admissioncheck.NewConfigHelper[*kueuealpha.MultiKueueConfig](c) 49 } 50 51 // ACReconciler implements the reconciler for all the admission checks controlled by multikueue. 52 // Its main task being to maintain the active state of the admission checks based on the heath 53 // of its referenced MultiKueueClusters. 54 type ACReconciler struct { 55 client client.Client 56 helper *multiKueueStoreHelper 57 } 58 59 var _ reconcile.Reconciler = (*ACReconciler)(nil) 60 61 func (a *ACReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { 62 log := ctrl.LoggerFrom(ctx) 63 ac := &kueue.AdmissionCheck{} 64 if err := a.client.Get(ctx, req.NamespacedName, ac); err != nil || ac.Spec.ControllerName != ControllerName { 65 return reconcile.Result{}, client.IgnoreNotFound(err) 66 } 67 68 inactiveReason := "" 69 70 log.V(2).Info("Reconcile AdmissionCheck") 71 if cfg, err := a.helper.ConfigFromRef(ctx, ac.Spec.Parameters); err != nil { 72 inactiveReason = fmt.Sprintf("Cannot load the AdmissionChecks parameters: %s", err.Error()) 73 } else { 74 var missingClusters []string 75 var inactiveClusters []string 76 // check the status of the clusters 77 for _, clusterName := range cfg.Spec.Clusters { 78 cluster := &kueuealpha.MultiKueueCluster{} 79 err := a.client.Get(ctx, types.NamespacedName{Name: clusterName}, cluster) 80 if client.IgnoreNotFound(err) != nil { 81 log.Error(err, "reading cluster", "multiKueueCluster", clusterName) 82 return reconcile.Result{}, err 83 } 84 85 if err != nil { 86 missingClusters = append(missingClusters, clusterName) 87 } else { 88 if !apimeta.IsStatusConditionTrue(cluster.Status.Conditions, kueuealpha.MultiKueueClusterActive) { 89 inactiveClusters = append(inactiveClusters, clusterName) 90 } 91 } 92 } 93 94 var messageParts []string 95 if len(missingClusters) > 0 { 96 messageParts = []string{fmt.Sprintf("Missing clusters: %v", missingClusters)} 97 } 98 if len(inactiveClusters) > 0 { 99 messageParts = append(messageParts, fmt.Sprintf("Inactive clusters: %v", inactiveClusters)) 100 } 101 inactiveReason = strings.Join(messageParts, ", ") 102 } 103 104 newCondition := metav1.Condition{ 105 Type: kueue.AdmissionCheckActive, 106 } 107 if len(inactiveReason) == 0 { 108 newCondition.Status = metav1.ConditionTrue 109 newCondition.Reason = "Active" 110 newCondition.Message = "The admission check is active" 111 } else { 112 newCondition.Status = metav1.ConditionFalse 113 newCondition.Reason = "Inactive" 114 newCondition.Message = inactiveReason 115 } 116 117 oldCondition := apimeta.FindStatusCondition(ac.Status.Conditions, kueue.AdmissionCheckActive) 118 if !cmpConditionState(oldCondition, &newCondition) { 119 apimeta.SetStatusCondition(&ac.Status.Conditions, newCondition) 120 err := a.client.Status().Update(ctx, ac) 121 if err != nil { 122 log.V(2).Error(err, "Updating check condition", "newCondition", newCondition) 123 } 124 return reconcile.Result{}, err 125 } 126 127 return reconcile.Result{}, nil 128 } 129 130 // +kubebuilder:rbac:groups="",resources=events,verbs=create;watch;update 131 // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=workloads,verbs=get;list;watch;update;patch;delete 132 // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=workloads/status,verbs=get;update;patch 133 // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=admissionchecks,verbs=get;list;watch 134 // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=multikueueconfigs,verbs=get;list;watch 135 136 func newACReconciler(c client.Client, helper *multiKueueStoreHelper) *ACReconciler { 137 return &ACReconciler{ 138 client: c, 139 helper: helper, 140 } 141 } 142 143 func (a *ACReconciler) setupWithManager(mgr ctrl.Manager) error { 144 return ctrl.NewControllerManagedBy(mgr). 145 For(&kueue.AdmissionCheck{}). 146 Watches(&kueuealpha.MultiKueueConfig{}, &mkConfigHandler{client: a.client}). 147 Watches(&kueuealpha.MultiKueueCluster{}, &mkClusterHandler{client: a.client}). 148 Complete(a) 149 } 150 151 type mkConfigHandler struct { 152 client client.Client 153 } 154 155 var _ handler.EventHandler = (*mkConfigHandler)(nil) 156 157 func (m *mkConfigHandler) Create(ctx context.Context, event event.CreateEvent, q workqueue.RateLimitingInterface) { 158 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueConfig) 159 if !isMKC { 160 return 161 } 162 163 if err := queueReconcileForConfigUsers(ctx, mkc.Name, m.client, q); err != nil { 164 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on create event", "multiKueueConfig", klog.KObj(mkc)) 165 } 166 } 167 168 func (m *mkConfigHandler) Update(ctx context.Context, event event.UpdateEvent, q workqueue.RateLimitingInterface) { 169 oldMKC, isOldMKC := event.ObjectOld.(*kueuealpha.MultiKueueConfig) 170 newMKC, isNewMKC := event.ObjectNew.(*kueuealpha.MultiKueueConfig) 171 if !isOldMKC || !isNewMKC || equality.Semantic.DeepEqual(oldMKC.Spec.Clusters, newMKC.Spec.Clusters) { 172 return 173 } 174 175 if err := queueReconcileForConfigUsers(ctx, oldMKC.Name, m.client, q); err != nil { 176 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on update event", "multiKueueConfig", klog.KObj(oldMKC)) 177 } 178 } 179 180 func (m *mkConfigHandler) Delete(ctx context.Context, event event.DeleteEvent, q workqueue.RateLimitingInterface) { 181 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueConfig) 182 if !isMKC { 183 return 184 } 185 186 if err := queueReconcileForConfigUsers(ctx, mkc.Name, m.client, q); err != nil { 187 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on delete event", "multiKueueConfig", klog.KObj(mkc)) 188 } 189 } 190 191 func (m *mkConfigHandler) Generic(ctx context.Context, event event.GenericEvent, q workqueue.RateLimitingInterface) { 192 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueConfig) 193 if !isMKC { 194 return 195 } 196 197 if err := queueReconcileForConfigUsers(ctx, mkc.Name, m.client, q); err != nil { 198 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on generic event", "multiKueueConfig", klog.KObj(mkc)) 199 } 200 } 201 202 func queueReconcileForConfigUsers(ctx context.Context, config string, c client.Client, q workqueue.RateLimitingInterface) error { 203 users := &kueue.AdmissionCheckList{} 204 205 if err := c.List(ctx, users, client.MatchingFields{AdmissionCheckUsingConfigKey: config}); err != nil { 206 return err 207 } 208 209 for _, user := range users.Items { 210 req := reconcile.Request{ 211 NamespacedName: types.NamespacedName{ 212 Name: user.Name, 213 }, 214 } 215 q.Add(req) 216 } 217 218 return nil 219 } 220 221 type mkClusterHandler struct { 222 client client.Client 223 } 224 225 var _ handler.EventHandler = (*mkClusterHandler)(nil) 226 227 func (m *mkClusterHandler) Create(ctx context.Context, event event.CreateEvent, q workqueue.RateLimitingInterface) { 228 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueCluster) 229 if !isMKC { 230 return 231 } 232 233 if err := queueReconcileForConfigUsers(ctx, mkc.Name, m.client, q); err != nil { 234 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on create event", "multiKueueCluster", klog.KObj(mkc)) 235 } 236 } 237 238 func (m *mkClusterHandler) Update(ctx context.Context, event event.UpdateEvent, q workqueue.RateLimitingInterface) { 239 oldMKC, isOldMKC := event.ObjectOld.(*kueuealpha.MultiKueueCluster) 240 newMKC, isNewMKC := event.ObjectNew.(*kueuealpha.MultiKueueCluster) 241 if !isOldMKC || !isNewMKC { 242 return 243 } 244 245 oldActive := apimeta.FindStatusCondition(oldMKC.Status.Conditions, kueuealpha.MultiKueueClusterActive) 246 newActive := apimeta.FindStatusCondition(newMKC.Status.Conditions, kueuealpha.MultiKueueClusterActive) 247 if !cmpConditionState(oldActive, newActive) { 248 if err := m.queue(ctx, newMKC, q); err != nil { 249 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on update event", "multiKueueCluster", klog.KObj(oldMKC)) 250 } 251 } 252 } 253 254 func (m *mkClusterHandler) Delete(ctx context.Context, event event.DeleteEvent, q workqueue.RateLimitingInterface) { 255 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueCluster) 256 if !isMKC { 257 return 258 } 259 260 if err := m.queue(ctx, mkc, q); err != nil { 261 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on delete event", "multiKueueCluster", klog.KObj(mkc)) 262 } 263 } 264 265 func (m *mkClusterHandler) Generic(ctx context.Context, event event.GenericEvent, q workqueue.RateLimitingInterface) { 266 mkc, isMKC := event.Object.(*kueuealpha.MultiKueueCluster) 267 if !isMKC { 268 return 269 } 270 271 if err := m.queue(ctx, mkc, q); err != nil { 272 ctrl.LoggerFrom(ctx).V(2).Error(err, "Failure on generic event", "multiKueueCluster", klog.KObj(mkc)) 273 } 274 } 275 276 func (m *mkClusterHandler) queue(ctx context.Context, cluster *kueuealpha.MultiKueueCluster, q workqueue.RateLimitingInterface) error { 277 users := &kueuealpha.MultiKueueConfigList{} 278 if err := m.client.List(ctx, users, client.MatchingFields{UsingMultiKueueClusters: cluster.Name}); err != nil { 279 return err 280 } 281 282 for _, user := range users.Items { 283 if err := queueReconcileForConfigUsers(ctx, user.Name, m.client, q); err != nil { 284 return err 285 } 286 } 287 return nil 288 } 289 290 func cmpConditionState(a, b *metav1.Condition) bool { 291 if a == b { 292 return true 293 } 294 if a == nil || b == nil { 295 return false 296 } 297 return a.Status == b.Status && a.Reason == b.Reason && a.Message == b.Message 298 }