@@ -189,17 +189,42 @@ func (o *Options) Run(ctx context.Context) error {
189
189
return err
190
190
}
191
191
192
+ clusterVersionConfigInformerFactory , configInformerFactory := o .prepareConfigInformerFactories (cb )
193
+ _ , _ , err = o .processInitialFeatureGate (ctx , configInformerFactory )
194
+ if err != nil {
195
+ return fmt .Errorf ("error processing feature gates: %w" , err )
196
+ }
197
+
198
+ // initialize the controllers and attempt to load the payload information
199
+ controllerCtx , err := o .NewControllerContext (cb , clusterVersionConfigInformerFactory , configInformerFactory )
200
+ if err != nil {
201
+ return err
202
+ }
203
+ o .leaderElection = getLeaderElectionConfig (ctx , cb .RestConfig (defaultQPS ))
204
+ o .run (ctx , controllerCtx , lock , cb .RestConfig (defaultQPS ), cb .RestConfig (highQPS ))
205
+ return nil
206
+ }
207
+
208
+ func (o * Options ) prepareConfigInformerFactories (cb * ClientBuilder ) (configinformers.SharedInformerFactory , configinformers.SharedInformerFactory ) {
209
+ client := cb .ClientOrDie ("shared-informer" )
210
+ filterByName := func (opts * metav1.ListOptions ) { opts .FieldSelector = fmt .Sprintf ("metadata.name=%s" , o .Name ) }
211
+ clusterVersionConfigInformerFactory := configinformers .NewSharedInformerFactoryWithOptions (client , resyncPeriod (o .ResyncInterval ), configinformers .WithTweakListOptions (filterByName ))
212
+ configInformerFactory := configinformers .NewSharedInformerFactory (client , resyncPeriod (o .ResyncInterval ))
213
+
214
+ return clusterVersionConfigInformerFactory , configInformerFactory
215
+ }
216
+
217
+ // getOcpVersion peeks at the local release metadata to determine the version of OCP this CVO belongs to. This assumes
218
+ // the CVO is executing in a container from the payload image. This does not and should not fully load whole payload
219
+ // content, that is only loaded later once leader lease is acquired. Here we should only read as little data as possible
220
+ // to determine the version so we can establish enabled feature gate checker for all following code.
221
+ func (o * Options ) getOcpVersion () string {
192
222
payloadRoot := payload .DefaultRootPath
193
223
if o .PayloadOverride != "" {
194
224
payloadRoot = payload .RootPath (o .PayloadOverride )
195
225
}
196
226
197
227
cvoOcpVersion := "0.0.1-snapshot"
198
- // Peek at the local release metadata to determine the version of OCP this CVO belongs to. This assumes the CVO is
199
- // executing in a container from the payload image. Full payload content is only read later once leader lease is
200
- // acquired, and here we should only read as little data as possible to determine the version so we can establish
201
- // enabled feature gate checker for all following code.
202
- //
203
228
// We cannot refuse to start CVO if for some reason we cannot determine the OCP version on startup from the local
204
229
// release metadata. The only consequence is we fail to determine enabled/disabled feature gates and will have to use
205
230
// some defaults.
@@ -214,25 +239,60 @@ func (o *Options) Run(ctx context.Context) error {
214
239
klog .Infof ("Determined OCP version for this CVO: %q" , cvoOcpVersion )
215
240
}
216
241
217
- clusterVersionConfigInformerFactory , configInformerFactory := o .prepareConfigInformerFactories (cb )
242
+ return cvoOcpVersion
243
+ }
218
244
219
- // initialize the controllers and attempt to load the payload information
220
- controllerCtx , err := o .NewControllerContext (cb , clusterVersionConfigInformerFactory , configInformerFactory )
221
- if err != nil {
222
- return err
245
+ func (o * Options ) processInitialFeatureGate (ctx context.Context , configInformerFactory configinformers.SharedInformerFactory ) (configv1.FeatureSet , * featuregates.CvoGates , error ) {
246
+ featureGates := configInformerFactory .Config ().V1 ().FeatureGates ().Lister ()
247
+ configInformerFactory .Start (ctx .Done ())
248
+ configInformerFactory .WaitForCacheSync (ctx .Done ())
249
+
250
+ var startingFeatureSet configv1.FeatureSet
251
+ var clusterFeatureGate * configv1.FeatureGate
252
+
253
+ cvoOcpVersion := o .getOcpVersion ()
254
+ cvoGates := featuregates .DefaultCvoGates (cvoOcpVersion )
255
+
256
+ // client-go automatically retries some network blip errors on GETs for 30s by default, and we want to
257
+ // retry the remaining ones ourselves. If we fail longer than that, the operator won't be able to do work
258
+ // anyway. Return the error and crashloop.
259
+ //
260
+ // We implement the timeout with a context because the timeout in PollImmediateWithContext does not behave
261
+ // well when ConditionFunc takes longer time to execute, like here where the GET can be retried by client-go
262
+ var lastError error
263
+ if err := wait .PollUntilContextTimeout (context .Background (), 2 * time .Second , 25 * time .Second , true , func (ctx context.Context ) (bool , error ) {
264
+ gate , fgErr := featureGates .Get ("cluster" )
265
+ switch {
266
+ case apierrors .IsNotFound (fgErr ):
267
+ // if we have no featuregates, then the cluster is using the default featureset, which is "".
268
+ // This excludes everything that could possibly depend on a different feature set.
269
+ startingFeatureSet = ""
270
+ klog .Infof ("FeatureGate not found in cluster, will assume default feature set %q at startup" , startingFeatureSet )
271
+ return true , nil
272
+ case fgErr != nil :
273
+ lastError = fgErr
274
+ klog .Warningf ("Failed to get FeatureGate from cluster: %v" , fgErr )
275
+ return false , nil
276
+ default :
277
+ clusterFeatureGate = gate
278
+ startingFeatureSet = gate .Spec .FeatureSet
279
+ cvoGates = featuregates .CvoGatesFromFeatureGate (clusterFeatureGate , cvoOcpVersion )
280
+ klog .Infof ("FeatureGate found in cluster, using its feature set %q at startup" , startingFeatureSet )
281
+ return true , nil
282
+ }
283
+ }); err != nil {
284
+ if lastError != nil {
285
+ return "" , nil , lastError
286
+ }
287
+ return "" , nil , err
223
288
}
224
- o .leaderElection = getLeaderElectionConfig (ctx , cb .RestConfig (defaultQPS ))
225
- o .run (ctx , controllerCtx , lock , cb .RestConfig (defaultQPS ), cb .RestConfig (highQPS ))
226
- return nil
227
- }
228
289
229
- func (o * Options ) prepareConfigInformerFactories (cb * ClientBuilder ) (configinformers.SharedInformerFactory , configinformers.SharedInformerFactory ) {
230
- client := cb .ClientOrDie ("shared-informer" )
231
- filterByName := func (opts * metav1.ListOptions ) { opts .FieldSelector = fmt .Sprintf ("metadata.name=%s" , o .Name ) }
232
- clusterVersionConfigInformerFactory := configinformers .NewSharedInformerFactoryWithOptions (client , resyncPeriod (o .ResyncInterval ), configinformers .WithTweakListOptions (filterByName ))
233
- configInformerFactory := configinformers .NewSharedInformerFactory (client , resyncPeriod (o .ResyncInterval ))
290
+ if cvoGates .UnknownVersion () {
291
+ klog .Warningf ("CVO features for version %s could not be detected from FeatureGate; will use defaults plus special UnknownVersion feature gate" , cvoOcpVersion )
292
+ }
293
+ klog .Infof ("CVO features for version %s enabled at startup: %+v" , cvoOcpVersion , cvoGates )
234
294
235
- return clusterVersionConfigInformerFactory , configInformerFactory
295
+ return startingFeatureSet , & cvoGates , nil
236
296
}
237
297
238
298
// run launches a number of goroutines to handle manifest application,
0 commit comments