@@ -40,10 +40,12 @@ import (
40
40
"sigs.k8s.io/controller-runtime/pkg/client"
41
41
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
42
42
"sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
43
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
43
44
"sigs.k8s.io/controller-runtime/pkg/event"
44
45
"sigs.k8s.io/controller-runtime/pkg/handler"
45
46
ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/internal/controller/metrics"
46
47
"sigs.k8s.io/controller-runtime/pkg/internal/log"
48
+ "sigs.k8s.io/controller-runtime/pkg/manager"
47
49
"sigs.k8s.io/controller-runtime/pkg/reconcile"
48
50
"sigs.k8s.io/controller-runtime/pkg/source"
49
51
)
@@ -1089,6 +1091,77 @@ var _ = Describe("controller", func() {
1089
1091
cancel ()
1090
1092
Expect (<- resultChan ).To (BeTrue ())
1091
1093
})
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
+ })
1092
1165
})
1093
1166
1094
1167
Describe ("Warmup with warmup disabled" , func () {
0 commit comments