@@ -13,6 +13,7 @@ import (
1313 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414 "k8s.io/apimachinery/pkg/types"
1515 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
16+ "k8s.io/apimachinery/pkg/util/sets"
1617 "k8s.io/apimachinery/pkg/util/wait"
1718 informerscorev1 "k8s.io/client-go/informers/core/v1"
1819 "k8s.io/client-go/kubernetes"
@@ -120,6 +121,7 @@ type Operator struct {
120121 cmConfigLister listerscorev1.ConfigMapNamespaceLister
121122 cmConfigManagedLister listerscorev1.ConfigMapNamespaceLister
122123 proxyLister configlistersv1.ProxyLister
124+ featureGateLister configlistersv1.FeatureGateLister
123125 cacheSynced []cache.InformerSynced
124126
125127 // queue tracks applying updates to a cluster.
@@ -189,6 +191,8 @@ type Operator struct {
189191 // featurechangestopper controller will detect when cluster feature gate config changes and shutdown the CVO.
190192 enabledFeatureGates featuregates.CvoGateChecker
191193
194+ enabledManifestFeatureGates sets.Set [string ]
195+
192196 clusterProfile string
193197 uid types.UID
194198
@@ -213,6 +217,7 @@ func New(
213217 cmConfigManagedInformer informerscorev1.ConfigMapInformer ,
214218 proxyInformer configinformersv1.ProxyInformer ,
215219 operatorInformerFactory operatorexternalversions.SharedInformerFactory ,
220+ featureGateInformer configinformersv1.FeatureGateInformer ,
216221 client clientset.Interface ,
217222 kubeClient kubernetes.Interface ,
218223 operatorClient operatorclientset.Interface ,
@@ -248,9 +253,9 @@ func New(
248253 kubeClient : kubeClient ,
249254 operatorClient : operatorClient ,
250255 eventRecorder : eventBroadcaster .NewRecorder (scheme .Scheme , corev1.EventSource {Component : namespace }),
251- queue : workqueue .NewTypedRateLimitingQueueWithConfig [ any ] (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "clusterversion" }),
252- availableUpdatesQueue : workqueue .NewTypedRateLimitingQueueWithConfig [ any ] (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "availableupdates" }),
253- upgradeableQueue : workqueue .NewTypedRateLimitingQueueWithConfig [ any ] (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "upgradeable" }),
256+ queue : workqueue .NewTypedRateLimitingQueueWithConfig (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "clusterversion" }),
257+ availableUpdatesQueue : workqueue .NewTypedRateLimitingQueueWithConfig (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "availableupdates" }),
258+ upgradeableQueue : workqueue .NewTypedRateLimitingQueueWithConfig (workqueue .DefaultTypedControllerRateLimiter [any ](), workqueue.TypedRateLimitingQueueConfig [any ]{Name : "upgradeable" }),
254259
255260 hypershift : hypershift ,
256261 exclude : exclude ,
@@ -276,6 +281,9 @@ func New(
276281 if _ , err := coInformer .Informer ().AddEventHandler (optr .clusterOperatorEventHandler ()); err != nil {
277282 return nil , err
278283 }
284+ if _ , err := featureGateInformer .Informer ().AddEventHandler (optr .featureGateEventHandler ()); err != nil {
285+ return nil , err
286+ }
279287
280288 optr .coLister = coInformer .Lister ()
281289 optr .cacheSynced = append (optr .cacheSynced , coInformer .Informer ().HasSynced )
@@ -287,6 +295,12 @@ func New(
287295 optr .cmConfigLister = cmConfigInformer .Lister ().ConfigMaps (internal .ConfigNamespace )
288296 optr .cmConfigManagedLister = cmConfigManagedInformer .Lister ().ConfigMaps (internal .ConfigManagedNamespace )
289297
298+ optr .featureGateLister = featureGateInformer .Lister ()
299+ optr .cacheSynced = append (optr .cacheSynced , featureGateInformer .Informer ().HasSynced )
300+
301+ // Initialize cluster feature gates
302+ optr .initializeFeatureGates ()
303+
290304 // make sure this is initialized after all the listers are initialized
291305 optr .upgradeableChecks = optr .defaultUpgradeableChecks ()
292306
@@ -318,7 +332,7 @@ func (optr *Operator) LoadInitialPayload(ctx context.Context, restConfig *rest.C
318332 }
319333
320334 update , err := payload .LoadUpdate (optr .defaultPayloadDir (), optr .release .Image , optr .exclude , string (optr .requiredFeatureSet ),
321- optr .clusterProfile , configv1 .KnownClusterVersionCapabilities )
335+ optr .clusterProfile , configv1 .KnownClusterVersionCapabilities , optr . getEnabledFeatureGates () )
322336
323337 if err != nil {
324338 return nil , fmt .Errorf ("the local release contents are invalid - no current version can be determined from disk: %v" , err )
@@ -779,7 +793,7 @@ func (optr *Operator) sync(ctx context.Context, key string) error {
779793 }
780794
781795 // inform the config sync loop about our desired state
782- status := optr .configSync .Update (ctx , config .Generation , desired , config , state )
796+ status := optr .configSync .Update (ctx , config .Generation , desired , config , state , optr . getEnabledFeatureGates () )
783797
784798 // write cluster version status
785799 return optr .syncStatus (ctx , original , config , status , errs )
@@ -1084,6 +1098,86 @@ func (optr *Operator) HTTPClient() (*http.Client, error) {
10841098 }, nil
10851099}
10861100
1101+ // featureGateEventHandler handles changes to FeatureGate objects and updates the cluster feature gates
1102+ func (optr * Operator ) featureGateEventHandler () cache.ResourceEventHandler {
1103+ workQueueKey := optr .queueKey ()
1104+ return cache.ResourceEventHandlerFuncs {
1105+ AddFunc : func (obj interface {}) {
1106+ optr .updateEnabledFeatureGates (obj )
1107+ optr .queue .Add (workQueueKey )
1108+ },
1109+ UpdateFunc : func (old , new interface {}) {
1110+ optr .updateEnabledFeatureGates (new )
1111+ optr .queue .Add (workQueueKey )
1112+ },
1113+ }
1114+ }
1115+
1116+ // initializeFeatureGates initializes the cluster feature gates from the current FeatureGate object
1117+ func (optr * Operator ) initializeFeatureGates () {
1118+ optr .enabledManifestFeatureGates = sets.Set [string ]{}
1119+
1120+ // Try to load initial state from the cluster FeatureGate object
1121+ if optr .featureGateLister != nil {
1122+ if featureGate , err := optr .featureGateLister .Get ("cluster" ); err == nil {
1123+ optr .enabledManifestFeatureGates = optr .extractEnabledGates (featureGate )
1124+ }
1125+ }
1126+ }
1127+
1128+ // updateEnabledFeatureGates updates the cluster feature gates based on a FeatureGate object
1129+ func (optr * Operator ) updateEnabledFeatureGates (obj interface {}) {
1130+ featureGate , ok := obj .(* configv1.FeatureGate )
1131+ if ! ok {
1132+ klog .Warningf ("Expected FeatureGate object but got %T" , obj )
1133+ return
1134+ }
1135+
1136+ newGates := optr .extractEnabledGates (featureGate )
1137+
1138+ // Check if gates actually changed to avoid unnecessary work
1139+ if ! optr .enabledManifestFeatureGates .Equal (newGates ) {
1140+ klog .V (2 ).Infof ("Cluster feature gates changed from %v to %v" ,
1141+ sets .List (optr .enabledManifestFeatureGates ), sets .List (newGates ))
1142+ optr .enabledManifestFeatureGates = newGates
1143+ }
1144+ }
1145+
1146+ // getEnabledFeatureGates returns a copy of the current cluster feature gates for safe consumption
1147+ func (optr * Operator ) getEnabledFeatureGates () sets.Set [string ] {
1148+ // Return a copy to prevent external modification
1149+ result := sets.Set [string ]{}
1150+ for gate := range optr .enabledManifestFeatureGates {
1151+ result .Insert (gate )
1152+ }
1153+ return result
1154+ }
1155+
1156+ // extractEnabledGates extracts the list of enabled feature gates for the current cluster version
1157+ func (optr * Operator ) extractEnabledGates (featureGate * configv1.FeatureGate ) sets.Set [string ] {
1158+ enabledGates := sets.Set [string ]{}
1159+
1160+ // Find the feature gate details for the current cluster version
1161+ currentVersion := optr .release .Version
1162+ for _ , details := range featureGate .Status .FeatureGates {
1163+ if details .Version == currentVersion {
1164+ for _ , enabled := range details .Enabled {
1165+ enabledGates .Insert (string (enabled .Name ))
1166+ }
1167+ klog .V (4 ).Infof ("Found %d enabled feature gates for version %s: %v" ,
1168+ enabledGates .Len (), currentVersion , sets .List (enabledGates ))
1169+ break
1170+ }
1171+ }
1172+
1173+ // If no matching version found, log a warning but continue with empty set
1174+ if enabledGates .Len () == 0 {
1175+ klog .V (2 ).Infof ("No feature gates found for current version %s, using empty set" , currentVersion )
1176+ }
1177+
1178+ return enabledGates
1179+ }
1180+
10871181// shouldReconcileCVOConfiguration returns whether the CVO should reconcile its configuration using the API server.
10881182//
10891183// enabledFeatureGates must be initialized before the function is called.
0 commit comments