@@ -17,6 +17,7 @@ import (
1717 apierrors "k8s.io/apimachinery/pkg/api/errors"
1818 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1919 "k8s.io/apimachinery/pkg/fields"
20+ "k8s.io/apimachinery/pkg/types"
2021 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2122 "k8s.io/apimachinery/pkg/util/sets"
2223 coreinformers "k8s.io/client-go/informers"
@@ -46,6 +47,7 @@ import (
4647 "github.com/openshift/cluster-version-operator/pkg/featuregates"
4748 "github.com/openshift/cluster-version-operator/pkg/internal"
4849 "github.com/openshift/cluster-version-operator/pkg/payload"
50+ "github.com/openshift/cluster-version-operator/pkg/version"
4951)
5052
5153const (
@@ -188,7 +190,8 @@ func (o *Options) Run(ctx context.Context) error {
188190 }
189191
190192 clusterVersionConfigInformerFactory , configInformerFactory := o .prepareConfigInformerFactories (cb )
191- startingFeatureSet , startingCvoGates , err := o .processInitialFeatureGate (ctx , configInformerFactory )
193+ configClient := cb .ClientOrDie ("feature-gate-migration" )
194+ startingFeatureSet , startingCvoGates , err := o .processInitialFeatureGate (ctx , configInformerFactory , configClient )
192195 if err != nil {
193196 return fmt .Errorf ("error processing feature gates: %w" , err )
194197 }
@@ -242,7 +245,7 @@ func (o *Options) getOpenShiftVersion() string {
242245 return releaseMetadata .Version
243246}
244247
245- func (o * Options ) processInitialFeatureGate (ctx context.Context , configInformerFactory configinformers.SharedInformerFactory ) (configv1.FeatureSet , featuregates.CvoGates , error ) {
248+ func (o * Options ) processInitialFeatureGate (ctx context.Context , configInformerFactory configinformers.SharedInformerFactory , configClient clientset. Interface ) (configv1.FeatureSet , featuregates.CvoGates , error ) {
246249 var startingFeatureSet configv1.FeatureSet
247250 var cvoGates featuregates.CvoGates
248251
@@ -266,19 +269,52 @@ func (o *Options) processInitialFeatureGate(ctx context.Context, configInformerF
266269 gate , err := featureGates .Get ("cluster" )
267270 switch {
268271 case apierrors .IsNotFound (err ):
269- // if we have no featuregates, then the cluster is using the default featureset, which is "".
270- // This excludes everything that could possibly depend on a different feature set.
271- startingFeatureSet = ""
272- klog .Infof ("FeatureGate not found in cluster, will assume default feature set %q at startup" , startingFeatureSet )
272+ // if we have no featuregates and this is an OKD build, then the cluster is using OKD featureset.
273+ // The FeatureGate resource will be created by the installer.
274+ if version .IsSCOS () {
275+ startingFeatureSet = configv1 .FeatureSet (configv1 .OKD )
276+ klog .Infof ("FeatureGate not found in cluster, will assume OKD feature set %q at startup" , startingFeatureSet )
277+ } else {
278+ klog .Infof ("FeatureGate not found in cluster, using default feature set at startup" )
279+ }
273280 case err != nil :
274281 // This should not happen because featureGates is backed by the informer cache which successfully synced earlier
275282 klog .Errorf ("Failed to get FeatureGate from cluster: %v" , err )
276283 return startingFeatureSet , cvoGates , fmt .Errorf ("failed to get FeatureGate from informer cache: %w" , err )
277284 default :
278285 clusterFeatureGate = gate
279286 startingFeatureSet = gate .Spec .FeatureSet
280- cvoGates = featuregates .CvoGatesFromFeatureGate (clusterFeatureGate , cvoOpenShiftVersion )
281287 klog .Infof ("FeatureGate found in cluster, using its feature set %q at startup" , startingFeatureSet )
288+
289+ // Migrate from Default ("") to OKD for existing clusters during upgrade (only for OKD builds)
290+ if version .IsSCOS () && (startingFeatureSet == "" || startingFeatureSet == configv1 .Default ) {
291+ klog .Infof ("Detected Default feature set, migrating to OKD feature set" )
292+
293+ // Patch the FeatureGate to change from Default to OKD
294+ patchData := []byte (fmt .Sprintf (`{"spec":{"featureSet":"%s"}}` , configv1 .OKD ))
295+ patchedGate , patchErr := configClient .ConfigV1 ().FeatureGates ().Patch (
296+ ctx ,
297+ "cluster" ,
298+ types .MergePatchType ,
299+ patchData ,
300+ metav1.PatchOptions {},
301+ )
302+ if patchErr != nil {
303+ klog .Errorf ("Failed to migrate FeatureGate from Default to OKD: %v" , patchErr )
304+ klog .Warningf ("Continuing with Default feature set; migration will be retried on next restart" )
305+ // Continue with Default - don't fail startup
306+ } else {
307+ klog .Infof ("Successfully migrated FeatureGate from Default to OKD" )
308+ startingFeatureSet = configv1 .FeatureSet (configv1 .OKD )
309+ clusterFeatureGate = patchedGate
310+
311+ // Note: The FeatureChangeStopper will detect this change and trigger a restart
312+ // This is expected and ensures CVO starts cleanly with the new feature set
313+ klog .Infof ("CVO will restart to apply OKD feature set changes" )
314+ }
315+ }
316+ // Compute CVO gates from the feature gate (potentially migrated)
317+ cvoGates = featuregates .CvoGatesFromFeatureGate (clusterFeatureGate , cvoOpenShiftVersion )
282318 }
283319
284320 if cvoGates .UnknownVersion () {
0 commit comments