sigs.k8s.io/kueue@v0.6.2/pkg/controller/core/admissioncheck_controller.go (about)

     1  /*
     2  Copyright 2023 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 core
    18  
    19  import (
    20  	"context"
    21  
    22  	"github.com/go-logr/logr"
    23  	"k8s.io/apimachinery/pkg/types"
    24  	"k8s.io/client-go/util/workqueue"
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/utils/ptr"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/controller"
    30  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    31  	"sigs.k8s.io/controller-runtime/pkg/event"
    32  	"sigs.k8s.io/controller-runtime/pkg/log"
    33  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    34  	"sigs.k8s.io/controller-runtime/pkg/source"
    35  
    36  	config "sigs.k8s.io/kueue/apis/config/v1beta1"
    37  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    38  	"sigs.k8s.io/kueue/pkg/cache"
    39  	"sigs.k8s.io/kueue/pkg/queue"
    40  	"sigs.k8s.io/kueue/pkg/util/slices"
    41  )
    42  
    43  type AdmissionCheckUpdateWatcher interface {
    44  	NotifyAdmissionCheckUpdate(oldAc, newAc *kueue.AdmissionCheck)
    45  }
    46  
    47  // AdmissionCheckReconciler reconciles a AdmissionCheck object
    48  type AdmissionCheckReconciler struct {
    49  	log        logr.Logger
    50  	qManager   *queue.Manager
    51  	client     client.Client
    52  	cache      *cache.Cache
    53  	cqUpdateCh chan event.GenericEvent
    54  	watchers   []AdmissionCheckUpdateWatcher
    55  }
    56  
    57  func NewAdmissionCheckReconciler(
    58  	client client.Client,
    59  	qMgr *queue.Manager,
    60  	cache *cache.Cache,
    61  ) *AdmissionCheckReconciler {
    62  	return &AdmissionCheckReconciler{
    63  		log:        ctrl.Log.WithName("admissioncheck-reconciler"),
    64  		qManager:   qMgr,
    65  		client:     client,
    66  		cache:      cache,
    67  		cqUpdateCh: make(chan event.GenericEvent, updateChBuffer),
    68  	}
    69  }
    70  
    71  // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=admissionchecks,verbs=get;list;watch;create;update;patch;delete
    72  // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=admissionchecks/status,verbs=get;update;patch
    73  // +kubebuilder:rbac:groups=kueue.x-k8s.io,resources=admissionchecks/finalizers,verbs=update
    74  
    75  // Reconcile is part of the main kubernetes reconciliation loop which aims to
    76  // move the current state of the cluster closer to the desired state.
    77  func (r *AdmissionCheckReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    78  	ac := &kueue.AdmissionCheck{}
    79  
    80  	if err := r.client.Get(ctx, req.NamespacedName, ac); err != nil {
    81  		return ctrl.Result{}, client.IgnoreNotFound(err)
    82  	}
    83  
    84  	log := log.FromContext(ctx).WithValues("admissionCheck", klog.KObj(ac))
    85  	if ac.DeletionTimestamp.IsZero() {
    86  		if controllerutil.AddFinalizer(ac, kueue.ResourceInUseFinalizerName) {
    87  			if err := r.client.Update(ctx, ac); err != nil {
    88  				return ctrl.Result{}, err
    89  			}
    90  			log.V(5).Info("Added finalizer")
    91  		}
    92  	} else {
    93  		if controllerutil.ContainsFinalizer(ac, kueue.ResourceInUseFinalizerName) {
    94  			if cqs := r.cache.ClusterQueuesUsingAdmissionCheck(ac.Name); len(cqs) != 0 {
    95  				log.V(3).Info("admissionCheck is still in use", "ClusterQueues", cqs)
    96  				// We avoid to return error here to prevent backoff requeue, which is passive and wasteful.
    97  				// Instead, we drive the removal of finalizer by ClusterQueue Update/Delete events
    98  				// when the admissionCheck is no longer in use.
    99  				return ctrl.Result{}, nil
   100  			}
   101  			controllerutil.RemoveFinalizer(ac, kueue.ResourceInUseFinalizerName)
   102  			if err := r.client.Update(ctx, ac); err != nil {
   103  				return ctrl.Result{}, err
   104  			}
   105  			log.V(5).Info("Removed finalizer")
   106  		}
   107  	}
   108  	return ctrl.Result{}, nil
   109  }
   110  func (r *AdmissionCheckReconciler) notifyWatchers(oldAc, newAc *kueue.AdmissionCheck) {
   111  	for _, w := range r.watchers {
   112  		w.NotifyAdmissionCheckUpdate(oldAc, newAc)
   113  	}
   114  }
   115  
   116  func (r *AdmissionCheckReconciler) AddUpdateWatchers(watchers ...AdmissionCheckUpdateWatcher) {
   117  	r.watchers = append(r.watchers, watchers...)
   118  }
   119  
   120  func (r *AdmissionCheckReconciler) Create(e event.CreateEvent) bool {
   121  	ac, isAc := e.Object.(*kueue.AdmissionCheck)
   122  	if !isAc {
   123  		return false
   124  	}
   125  	defer r.notifyWatchers(nil, ac)
   126  	r.log.WithValues("admissionCheck", klog.KObj(ac)).V(5).Info("Create event")
   127  	if cqNames := r.cache.AddOrUpdateAdmissionCheck(ac); len(cqNames) > 0 {
   128  		r.qManager.QueueInadmissibleWorkloads(context.Background(), cqNames)
   129  	}
   130  	return true
   131  }
   132  
   133  func (r *AdmissionCheckReconciler) Update(e event.UpdateEvent) bool {
   134  	newAc, isAc := e.ObjectNew.(*kueue.AdmissionCheck)
   135  	if !isAc {
   136  		return false
   137  	}
   138  	oldAc, isAc := e.ObjectNew.(*kueue.AdmissionCheck)
   139  	if !isAc {
   140  		return false
   141  	}
   142  	defer r.notifyWatchers(oldAc, newAc)
   143  	r.log.WithValues("admissionCheck", klog.KObj(newAc)).V(5).Info("Update event")
   144  	if !newAc.DeletionTimestamp.IsZero() {
   145  		return true
   146  	}
   147  	if cqNames := r.cache.AddOrUpdateAdmissionCheck(newAc); len(cqNames) > 0 {
   148  		r.qManager.QueueInadmissibleWorkloads(context.Background(), cqNames)
   149  	}
   150  	return false
   151  }
   152  
   153  func (r *AdmissionCheckReconciler) Delete(e event.DeleteEvent) bool {
   154  	ac, isAc := e.Object.(*kueue.AdmissionCheck)
   155  	if !isAc {
   156  		return false
   157  	}
   158  	defer r.notifyWatchers(ac, nil)
   159  	r.log.WithValues("admissionCheck", klog.KObj(ac)).V(5).Info("Delete event")
   160  
   161  	if cqNames := r.cache.DeleteAdmissionCheck(ac); len(cqNames) > 0 {
   162  		r.qManager.QueueInadmissibleWorkloads(context.Background(), cqNames)
   163  	}
   164  	return true
   165  }
   166  
   167  func (r *AdmissionCheckReconciler) Generic(e event.GenericEvent) bool {
   168  	r.log.WithValues("object", klog.KObj(e.Object), "kind", e.Object.GetObjectKind().GroupVersionKind()).V(5).Info("Generic event")
   169  	return true
   170  }
   171  
   172  func (r *AdmissionCheckReconciler) NotifyClusterQueueUpdate(oldCq *kueue.ClusterQueue, newCq *kueue.ClusterQueue) {
   173  	log := r.log.WithValues("oldClusterQueue", klog.KObj(oldCq), "newClusterQueue", klog.KObj(newCq))
   174  	log.V(5).Info("Cluster queue notification")
   175  	noChange := newCq != nil && oldCq != nil && slices.CmpNoOrder(oldCq.Spec.AdmissionChecks, newCq.Spec.AdmissionChecks)
   176  	if noChange {
   177  		return
   178  	}
   179  
   180  	if oldCq != nil {
   181  		r.cqUpdateCh <- event.GenericEvent{Object: oldCq}
   182  	}
   183  
   184  	if newCq != nil {
   185  		r.cqUpdateCh <- event.GenericEvent{Object: newCq}
   186  	}
   187  }
   188  
   189  type acCqHandler struct {
   190  	cache *cache.Cache
   191  }
   192  
   193  func (h *acCqHandler) Create(context.Context, event.CreateEvent, workqueue.RateLimitingInterface) {
   194  }
   195  
   196  func (h *acCqHandler) Update(context.Context, event.UpdateEvent, workqueue.RateLimitingInterface) {
   197  }
   198  
   199  func (h *acCqHandler) Delete(context.Context, event.DeleteEvent, workqueue.RateLimitingInterface) {
   200  }
   201  
   202  func (h *acCqHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
   203  	cq := e.Object.(*kueue.ClusterQueue)
   204  	log := log.FromContext(ctx).WithValues("clusterQueue", klog.KObj(cq))
   205  	log.V(6).Info("Cluster queue generic event")
   206  
   207  	for _, ac := range cq.Spec.AdmissionChecks {
   208  		if cqs := h.cache.ClusterQueuesUsingAdmissionCheck(ac); len(cqs) == 0 {
   209  			req := reconcile.Request{
   210  				NamespacedName: types.NamespacedName{
   211  					Name: ac,
   212  				},
   213  			}
   214  			q.Add(req)
   215  		}
   216  	}
   217  }
   218  
   219  // SetupWithManager sets up the controller with the Manager.
   220  func (r *AdmissionCheckReconciler) SetupWithManager(mgr ctrl.Manager, cfg *config.Configuration) error {
   221  	handler := acCqHandler{
   222  		cache: r.cache,
   223  	}
   224  	return ctrl.NewControllerManagedBy(mgr).
   225  		For(&kueue.AdmissionCheck{}).
   226  		WithOptions(controller.Options{NeedLeaderElection: ptr.To(false)}).
   227  		WatchesRawSource(&source.Channel{Source: r.cqUpdateCh}, &handler).
   228  		WithEventFilter(r).
   229  		Complete(WithLeadingManager(mgr, r, &kueue.AdmissionCheck{}, cfg))
   230  }