sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/scheduler/reconciler.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 scheduler
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"github.com/sirupsen/logrus"
    24  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    25  	controllerruntime "sigs.k8s.io/controller-runtime"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/controller"
    28  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    29  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    30  	prowv1 "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    31  	"sigs.k8s.io/prow/pkg/config"
    32  	"sigs.k8s.io/prow/pkg/scheduler/strategy"
    33  )
    34  
    35  const ControllerName = "scheduler"
    36  
    37  func Add(mgr controllerruntime.Manager, cfg config.Getter, numWorkers int) error {
    38  	predicates := predicate.NewPredicateFuncs(func(object client.Object) bool {
    39  		pj, isPJ := object.(*prowv1.ProwJob)
    40  		return isPJ && pj.Status.State == prowv1.SchedulingState
    41  	})
    42  
    43  	reconciler := NewReconciler(mgr.GetClient(), cfg, strategy.Get)
    44  	if err := controllerruntime.NewControllerManagedBy(mgr).
    45  		Named(ControllerName).
    46  		For(&prowv1.ProwJob{}).
    47  		WithEventFilter(predicates).
    48  		WithOptions(controller.Options{MaxConcurrentReconciles: numWorkers}).
    49  		Complete(reconciler); err != nil {
    50  		return fmt.Errorf("failed to construct controller: %w", err)
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  type StrategyGetter func(cfg *config.Config) strategy.Interface
    57  
    58  type Reconciler struct {
    59  	pjClient    client.Client
    60  	passthrough strategy.Interface
    61  	log         *logrus.Entry
    62  	cfg         config.Getter
    63  	strategy    StrategyGetter
    64  }
    65  
    66  func (r *Reconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
    67  	log := r.log.WithField("request", request)
    68  
    69  	pj := &prowv1.ProwJob{}
    70  	if err := r.pjClient.Get(ctx, request.NamespacedName, pj); err != nil {
    71  		if !kerrors.IsNotFound(err) {
    72  			return reconcile.Result{}, fmt.Errorf("get prowjob %s: %w", request.Name, err)
    73  		}
    74  		return reconcile.Result{}, nil
    75  	}
    76  
    77  	log = log.WithField("job", pj.Spec.Job)
    78  
    79  	var result strategy.Result
    80  	var err error
    81  	// So far only k8s and tekton use the cluster field in a meaninful way. Hence
    82  	// if we're reconciling a job having a different agent (or no agent at all) applying
    83  	// the passthrough strategy may be the safest approach.
    84  	if pj.Spec.Agent == prowv1.KubernetesAgent || pj.Spec.Agent == prowv1.TektonAgent {
    85  		result, err = r.strategy(r.cfg()).Schedule(ctx, pj)
    86  	} else {
    87  		result, err = r.passthrough.Schedule(ctx, pj)
    88  	}
    89  
    90  	if err != nil {
    91  		return reconcile.Result{}, fmt.Errorf("schedule prowjob %s: %w", request.Name, err)
    92  	}
    93  	log.WithField("cluster", result.Cluster).Info("Cluster assigned")
    94  
    95  	// Don't mess the cache up
    96  	scheduled := pj.DeepCopy()
    97  	scheduled.Spec.Cluster = result.Cluster
    98  	scheduled.Status.State = prowv1.TriggeredState
    99  
   100  	if err := r.pjClient.Patch(ctx, scheduled, client.MergeFrom(pj.DeepCopy())); err != nil {
   101  		return reconcile.Result{}, fmt.Errorf("patch prowjob: %w", err)
   102  	}
   103  
   104  	return reconcile.Result{}, nil
   105  }
   106  
   107  func NewReconciler(pjClient client.Client, cfg config.Getter, strtgy StrategyGetter) *Reconciler {
   108  	return &Reconciler{
   109  		pjClient:    pjClient,
   110  		passthrough: &strategy.Passthrough{},
   111  		log:         logrus.NewEntry(logrus.StandardLogger()).WithField("controller", ControllerName),
   112  		cfg:         cfg,
   113  		strategy:    strtgy,
   114  	}
   115  }