Skip to content

Commit 2c6401d

Browse files
committed
Migrate to OKD featureset for OKD clusters
In lieu of openshift/cluster-version-operator#1287 similar to #324
1 parent a245572 commit 2c6401d

File tree

4 files changed

+250
-0
lines changed

4 files changed

+250
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
reviewers:
2+
- Prashanth684
3+
- joelspeed
4+
approvers:
5+
- Prashanth684
6+
- joelspeed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package migrateokdfeatureset
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
configv1 "github.com/openshift/api/config/v1"
9+
applyconfigurationsconfigv1 "github.com/openshift/client-go/config/applyconfigurations/config/v1"
10+
configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
11+
v1 "github.com/openshift/client-go/config/informers/externalversions/config/v1"
12+
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
13+
"github.com/openshift/cluster-config-operator/pkg/version"
14+
"github.com/openshift/library-go/pkg/controller/factory"
15+
"github.com/openshift/library-go/pkg/operator/events"
16+
operatorv1helpers "github.com/openshift/library-go/pkg/operator/v1helpers"
17+
apierrors "k8s.io/apimachinery/pkg/api/errors"
18+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19+
)
20+
21+
// OKDFeatureSetMigrationController migrates Default featureset to OKD for OKD builds
22+
type OKDFeatureSetMigrationController struct {
23+
featureGatesClient configv1client.FeatureGatesGetter
24+
featureGatesLister configlistersv1.FeatureGateLister
25+
26+
eventRecorder events.Recorder
27+
}
28+
29+
func NewOKDFeatureSetMigrationController(operatorClient operatorv1helpers.OperatorClient,
30+
featureGatesClient configv1client.FeatureGatesGetter, featureGatesInformer v1.FeatureGateInformer,
31+
eventRecorder events.Recorder) factory.Controller {
32+
c := &OKDFeatureSetMigrationController{
33+
featureGatesClient: featureGatesClient,
34+
featureGatesLister: featureGatesInformer.Lister(),
35+
eventRecorder: eventRecorder,
36+
}
37+
38+
return factory.New().
39+
WithSync(c.sync).
40+
WithSyncDegradedOnError(operatorClient).
41+
ResyncEvery(time.Minute).
42+
ToController("OKDFeatureSetMigrationController", eventRecorder)
43+
}
44+
45+
func (c OKDFeatureSetMigrationController) sync(ctx context.Context, syncCtx factory.SyncContext) error {
46+
// Only run this controller for OKD builds
47+
if !version.IsSCOS() {
48+
return nil
49+
}
50+
51+
featureGates, err := c.featureGatesLister.Get("cluster")
52+
if apierrors.IsNotFound(err) {
53+
return nil
54+
}
55+
if err != nil {
56+
return fmt.Errorf("unable to get FeatureGate: %w", err)
57+
}
58+
59+
return c.syncFeatureGate(ctx, featureGates)
60+
}
61+
62+
func (c OKDFeatureSetMigrationController) syncFeatureGate(ctx context.Context, featureGates *configv1.FeatureGate) error {
63+
// Only migrate if the current featureset is Default (empty string or explicit "Default")
64+
// The installer creates FeatureGate with OKD featureset for new installations.
65+
// This controller migrates existing clusters upgraded to OKD.
66+
if featureGates.Spec.FeatureSet != "" && featureGates.Spec.FeatureSet != configv1.Default {
67+
return nil
68+
}
69+
70+
desiredFeatureGate := applyconfigurationsconfigv1.FeatureGate("cluster").
71+
WithSpec(
72+
applyconfigurationsconfigv1.FeatureGateSpec().
73+
WithFeatureSet(configv1.OKD),
74+
)
75+
applyOptions := metav1.ApplyOptions{
76+
Force: true,
77+
FieldManager: "OKDFeatureSetMigrationController",
78+
}
79+
80+
if _, err := c.featureGatesClient.FeatureGates().Apply(ctx, desiredFeatureGate, applyOptions); err != nil {
81+
return fmt.Errorf("unable to migrate FeatureGate to OKD: %w", err)
82+
}
83+
84+
return nil
85+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package migrateokdfeatureset
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
configv1 "github.com/openshift/api/config/v1"
8+
configv1fake "github.com/openshift/client-go/config/clientset/versioned/fake"
9+
"github.com/openshift/cluster-config-operator/pkg/version"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/types"
12+
kubetesting "k8s.io/client-go/testing"
13+
)
14+
15+
func TestOKDFeatureSetMigrationController_syncFeatureGate(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
featureGate *configv1.FeatureGate
19+
20+
changeVerifier func(t *testing.T, actions []kubetesting.Action)
21+
}{
22+
{
23+
name: "migrate-empty-featureset-to-okd",
24+
featureGate: &configv1.FeatureGate{
25+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
26+
Spec: configv1.FeatureGateSpec{
27+
FeatureGateSelection: configv1.FeatureGateSelection{
28+
FeatureSet: "",
29+
},
30+
},
31+
},
32+
changeVerifier: func(t *testing.T, actions []kubetesting.Action) {
33+
if len(actions) != 1 {
34+
t.Fatalf("expected 1 action, got %d: %v", len(actions), actions)
35+
}
36+
patchAction := actions[0].(kubetesting.PatchAction)
37+
if patchAction.GetPatchType() != types.ApplyPatchType {
38+
t.Fatalf("unexpected patch type: %v", patchAction.GetPatchType())
39+
}
40+
applied := string(patchAction.GetPatch())
41+
expectedApplied := `{"kind":"FeatureGate","apiVersion":"config.openshift.io/v1","metadata":{"name":"cluster"},"spec":{"featureSet":"OKD"}}`
42+
if applied != expectedApplied {
43+
t.Fatalf("unexpected patch:\ngot: %s\nwant: %s", applied, expectedApplied)
44+
}
45+
},
46+
},
47+
{
48+
name: "migrate-default-featureset-to-okd",
49+
featureGate: &configv1.FeatureGate{
50+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
51+
Spec: configv1.FeatureGateSpec{
52+
FeatureGateSelection: configv1.FeatureGateSelection{
53+
FeatureSet: configv1.Default,
54+
},
55+
},
56+
},
57+
changeVerifier: func(t *testing.T, actions []kubetesting.Action) {
58+
if len(actions) != 1 {
59+
t.Fatalf("expected 1 action, got %d: %v", len(actions), actions)
60+
}
61+
patchAction := actions[0].(kubetesting.PatchAction)
62+
if patchAction.GetPatchType() != types.ApplyPatchType {
63+
t.Fatalf("unexpected patch type: %v", patchAction.GetPatchType())
64+
}
65+
applied := string(patchAction.GetPatch())
66+
expectedApplied := `{"kind":"FeatureGate","apiVersion":"config.openshift.io/v1","metadata":{"name":"cluster"},"spec":{"featureSet":"OKD"}}`
67+
if applied != expectedApplied {
68+
t.Fatalf("unexpected patch:\ngot: %s\nwant: %s", applied, expectedApplied)
69+
}
70+
},
71+
},
72+
{
73+
name: "leave-techpreview-unchanged",
74+
featureGate: &configv1.FeatureGate{
75+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
76+
Spec: configv1.FeatureGateSpec{
77+
FeatureGateSelection: configv1.FeatureGateSelection{
78+
FeatureSet: configv1.TechPreviewNoUpgrade,
79+
},
80+
},
81+
},
82+
changeVerifier: func(t *testing.T, actions []kubetesting.Action) {
83+
if len(actions) != 0 {
84+
t.Fatalf("expected no actions, got %d: %v", len(actions), actions)
85+
}
86+
},
87+
},
88+
{
89+
name: "leave-okd-unchanged",
90+
featureGate: &configv1.FeatureGate{
91+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
92+
Spec: configv1.FeatureGateSpec{
93+
FeatureGateSelection: configv1.FeatureGateSelection{
94+
FeatureSet: configv1.OKD,
95+
},
96+
},
97+
},
98+
changeVerifier: func(t *testing.T, actions []kubetesting.Action) {
99+
if len(actions) != 0 {
100+
t.Fatalf("expected no actions, got %d: %v", len(actions), actions)
101+
}
102+
},
103+
},
104+
{
105+
name: "leave-custompoupgrade-unchanged",
106+
featureGate: &configv1.FeatureGate{
107+
ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
108+
Spec: configv1.FeatureGateSpec{
109+
FeatureGateSelection: configv1.FeatureGateSelection{
110+
FeatureSet: configv1.CustomNoUpgrade,
111+
},
112+
},
113+
},
114+
changeVerifier: func(t *testing.T, actions []kubetesting.Action) {
115+
if len(actions) != 0 {
116+
t.Fatalf("expected no actions, got %d: %v", len(actions), actions)
117+
}
118+
},
119+
},
120+
}
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
// Skip tests if not running in SCOS mode
124+
if !version.IsSCOS() {
125+
t.Skipf("Skipping test %s because version.IsSCOS() is false", tt.name)
126+
}
127+
128+
ctx := context.Background()
129+
_, cancel := context.WithCancel(ctx)
130+
defer cancel()
131+
132+
fakeClient := configv1fake.NewSimpleClientset(tt.featureGate)
133+
134+
c := OKDFeatureSetMigrationController{
135+
featureGatesClient: fakeClient.ConfigV1(),
136+
}
137+
if err := c.syncFeatureGate(ctx, tt.featureGate); err != nil {
138+
t.Fatal(err)
139+
}
140+
141+
tt.changeVerifier(t, fakeClient.Actions())
142+
})
143+
}
144+
}
145+
146+
// Note: Testing the non-SCOS code path (early return in sync) is not necessary
147+
// as it's a trivial check. The version.IsSCOS() function is tested in the version package.
148+
// All meaningful functionality is tested in the syncFeatureGate tests above.

pkg/operator/starter.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/openshift/cluster-config-operator/pkg/operator/featuregates"
2222
"github.com/openshift/cluster-config-operator/pkg/operator/featureupgradablecontroller"
2323
kubecloudconfig "github.com/openshift/cluster-config-operator/pkg/operator/kube_cloud_config"
24+
"github.com/openshift/cluster-config-operator/pkg/operator/migrateokdfeatureset"
2425
"github.com/openshift/cluster-config-operator/pkg/operator/migration_platform_status"
2526
"github.com/openshift/cluster-config-operator/pkg/operator/operatorclient"
2627
"github.com/openshift/cluster-config-operator/pkg/operator/removelatencysensitive"
@@ -123,6 +124,15 @@ func (o *OperatorOptions) RunOperator(ctx context.Context, controllerContext *co
123124
controllerContext.EventRecorder,
124125
)
125126

127+
// Migrates Default featureset to OKD for OKD builds
128+
// to be removed a release after OKD upgrades are stable
129+
okdFeatureSetMigrator := migrateokdfeatureset.NewOKDFeatureSetMigrationController(
130+
operatorClient,
131+
configClient.ConfigV1(),
132+
configInformers.Config().V1().FeatureGates(),
133+
controllerContext.EventRecorder,
134+
)
135+
126136
featureUpgradeableController := featureupgradablecontroller.NewFeatureUpgradeableController(
127137
operatorClient,
128138
configInformers,
@@ -224,6 +234,7 @@ func (o *OperatorOptions) RunOperator(ctx context.Context, controllerContext *co
224234
go staleConditionsController.Run(ctx, 1)
225235
go featureGateController.Run(ctx, 1)
226236
go latencySensitiveRemover.Run(ctx, 1)
237+
go okdFeatureSetMigrator.Run(ctx, 1)
227238
go featureUpgradeableController.Run(ctx, 1)
228239

229240
<-ctx.Done()

0 commit comments

Comments
 (0)