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 }