@@ -40,10 +40,12 @@ import (
4040 "sigs.k8s.io/controller-runtime/pkg/client"
4141 "sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
4242 "sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
43+ "sigs.k8s.io/controller-runtime/pkg/envtest"
4344 "sigs.k8s.io/controller-runtime/pkg/event"
4445 "sigs.k8s.io/controller-runtime/pkg/handler"
4546 ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
4647 "sigs.k8s.io/controller-runtime/pkg/internal/log"
48+ "sigs.k8s.io/controller-runtime/pkg/manager"
4749 "sigs.k8s.io/controller-runtime/pkg/reconcile"
4850 "sigs.k8s.io/controller-runtime/pkg/source"
4951)
@@ -1089,6 +1091,77 @@ var _ = Describe("controller", func() {
10891091 cancel ()
10901092 Expect (<- resultChan ).To (BeTrue ())
10911093 })
1094+
1095+ It ("should be called before leader election runnables if warmup is enabled" , func () {
1096+ // This unit test exists to ensure that a warmup enabled controller will actually be
1097+ // called in the warmup phase before the leader election runnables are started. It
1098+ // catches regressions in the controller that would not implement warmupRunnable from
1099+ // pkg/manager.
1100+
1101+ ctx , cancel := context .WithCancel (context .Background ())
1102+ defer cancel ()
1103+
1104+ hasCtrlWatchStarted , hasNonWarmupCtrlWatchStarted := atomic.Bool {}, atomic.Bool {}
1105+
1106+ // ctrl watch will block from finishing until the channel is produced to
1107+ ctrlWatchBlockingChan := make (chan struct {})
1108+
1109+ ctrl .CacheSyncTimeout = time .Second
1110+ ctrl .startWatches = []source.TypedSource [reconcile.Request ]{
1111+ source .Func (func (ctx context.Context , _ workqueue.TypedRateLimitingInterface [reconcile.Request ]) error {
1112+ hasCtrlWatchStarted .Store (true )
1113+ <- ctrlWatchBlockingChan
1114+ return nil
1115+ }),
1116+ }
1117+
1118+ nonWarmupCtrl := & Controller [reconcile.Request ]{
1119+ MaxConcurrentReconciles : 1 ,
1120+ Do : fakeReconcile ,
1121+ NewQueue : func (string , workqueue.TypedRateLimiter [reconcile.Request ]) workqueue.TypedRateLimitingInterface [reconcile.Request ] {
1122+ return queue
1123+ },
1124+ LogConstructor : func (_ * reconcile.Request ) logr.Logger {
1125+ return log .RuntimeLog .WithName ("controller" ).WithName ("test" )
1126+ },
1127+ CacheSyncTimeout : time .Second ,
1128+ NeedWarmup : ptr .To (false ),
1129+ LeaderElected : ptr .To (true ),
1130+ startWatches : []source.TypedSource [reconcile.Request ]{
1131+ source .Func (func (ctx context.Context , _ workqueue.TypedRateLimitingInterface [reconcile.Request ]) error {
1132+ hasNonWarmupCtrlWatchStarted .Store (true )
1133+ return nil
1134+ }),
1135+ },
1136+ }
1137+
1138+ By ("Creating a manager" )
1139+ testenv = & envtest.Environment {}
1140+ cfg , err := testenv .Start ()
1141+ Expect (err ).NotTo (HaveOccurred ())
1142+ m , err := manager .New (cfg , manager.Options {
1143+ LeaderElection : false ,
1144+ })
1145+ Expect (err ).NotTo (HaveOccurred ())
1146+
1147+ By ("Adding warmup and non-warmup controllers to the manager" )
1148+ Expect (m .Add (ctrl )).To (Succeed ())
1149+ Expect (m .Add (nonWarmupCtrl )).To (Succeed ())
1150+
1151+ By ("Starting the manager" )
1152+ go func () {
1153+ defer GinkgoRecover ()
1154+ Expect (m .Start (ctx )).To (Succeed ())
1155+ }()
1156+
1157+ By ("Waiting for the warmup controller to start" )
1158+ Eventually (hasCtrlWatchStarted .Load ).Should (BeTrue ())
1159+ Expect (hasNonWarmupCtrlWatchStarted .Load ()).To (BeFalse ())
1160+
1161+ By ("Unblocking the warmup controller source start" )
1162+ close (ctrlWatchBlockingChan )
1163+ Eventually (hasNonWarmupCtrlWatchStarted .Load ).Should (BeTrue ())
1164+ })
10921165 })
10931166
10941167 Describe ("Warmup with warmup disabled" , func () {
0 commit comments