Skip to content

Commit 56c59ce

Browse files
committed
Pass feature gating information through to sync workers
1 parent b29e99b commit 56c59ce

13 files changed

Lines changed: 693 additions & 41 deletions

File tree

hack/cluster-version-util/task_graph.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/spf13/cobra"
9+
"k8s.io/apimachinery/pkg/util/sets"
910

1011
"github.com/openshift/cluster-version-operator/pkg/payload"
1112
)
@@ -30,7 +31,7 @@ func newTaskGraphCmd() *cobra.Command {
3031

3132
func runTaskGraphCmd(cmd *cobra.Command, args []string) error {
3233
manifestDir := args[0]
33-
release, err := payload.LoadUpdate(manifestDir, "", "", "", payload.DefaultClusterProfile, nil)
34+
release, err := payload.LoadUpdate(manifestDir, "", "", "", payload.DefaultClusterProfile, nil, sets.Set[string]{})
3435
if err != nil {
3536
return err
3637
}

lib/manifest/manifest.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func GetImplicitlyEnabledCapabilities(
4848
currentPayloadManifests []manifest.Manifest,
4949
manifestInclusionConfiguration InclusionConfiguration,
5050
currentImplicitlyEnabled sets.Set[configv1.ClusterVersionCapability],
51+
enabledFeatureGates sets.Set[string],
5152
) sets.Set[configv1.ClusterVersionCapability] {
5253
ret := currentImplicitlyEnabled.Clone()
5354
for _, updateManifest := range updatePayloadManifests {
@@ -57,6 +58,7 @@ func GetImplicitlyEnabledCapabilities(
5758
manifestInclusionConfiguration.Profile,
5859
manifestInclusionConfiguration.Capabilities,
5960
manifestInclusionConfiguration.Overrides,
61+
enabledFeatureGates,
6062
true,
6163
)
6264
// update manifest is enabled, no need to check
@@ -74,6 +76,7 @@ func GetImplicitlyEnabledCapabilities(
7476
manifestInclusionConfiguration.Profile,
7577
manifestInclusionConfiguration.Capabilities,
7678
manifestInclusionConfiguration.Overrides,
79+
enabledFeatureGates,
7780
true,
7881
); err != nil {
7982
continue

pkg/cvo/cvo.go

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)