Skip to content

Commit 2f48abb

Browse files
committed
Update featuregates to read from featuregateaccess
1 parent c0e95bc commit 2f48abb

3 files changed

Lines changed: 87 additions & 61 deletions

File tree

cmd/cluster-cloud-controller-manager-operator/main.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20+
"errors"
2021
"flag"
2122
"os"
2223
"time"
@@ -25,6 +26,7 @@ import (
2526

2627
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2728
// to ensure that exec-entrypoint and run can make use of them.
29+
"k8s.io/client-go/kubernetes"
2830
_ "k8s.io/client-go/plugin/pkg/client/auth"
2931

3032
"k8s.io/apimachinery/pkg/runtime"
@@ -39,6 +41,10 @@ import (
3941

4042
configv1 "github.com/openshift/api/config/v1"
4143
operatorv1 "github.com/openshift/api/operator/v1"
44+
configv1client "github.com/openshift/client-go/config/clientset/versioned"
45+
configinformers "github.com/openshift/client-go/config/informers/externalversions"
46+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
47+
"github.com/openshift/library-go/pkg/operator/events"
4248

4349
"github.com/openshift/cluster-cloud-controller-manager-operator/pkg/controllers"
4450
"github.com/openshift/cluster-cloud-controller-manager-operator/pkg/restmapper"
@@ -111,6 +117,8 @@ func main() {
111117
LeaseDuration: leaderElectionConfig.LeaseDuration,
112118
})
113119

120+
ctx := ctrl.SetupSignalHandler()
121+
114122
syncPeriod := 10 * time.Minute
115123
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
116124
Namespace: *managedNamespace,
@@ -137,15 +145,62 @@ func main() {
137145
os.Exit(1)
138146
}
139147

148+
// Setup for the feature gate accessor. This reads and monitors feature gates
149+
// from the FeatureGate object status for the given version.
150+
desiredVersion := controllers.GetReleaseVersion()
151+
missingVersion := "0.0.1-snapshot"
152+
153+
configClient, err := configv1client.NewForConfig(mgr.GetConfig())
154+
if err != nil {
155+
setupLog.Error(err, "unable to create config client")
156+
os.Exit(1)
157+
}
158+
configInformers := configinformers.NewSharedInformerFactory(configClient, 10*time.Minute)
159+
160+
kubeClient, err := kubernetes.NewForConfig(mgr.GetConfig())
161+
if err != nil {
162+
setupLog.Error(err, "unable to create kube client")
163+
os.Exit(1)
164+
}
165+
166+
controllerRef, err := events.GetControllerReferenceForCurrentPod(ctx, kubeClient, *managedNamespace, nil)
167+
if err != nil {
168+
klog.Warningf("unable to get owner reference (falling back to namespace): %v", err)
169+
}
170+
171+
recorder := events.NewKubeRecorder(kubeClient.CoreV1().Events(*managedNamespace), "cloud-controller-manager-operator", controllerRef)
172+
featureGateAccessor := featuregates.NewFeatureGateAccess(
173+
desiredVersion, missingVersion,
174+
configInformers.Config().V1().ClusterVersions(), configInformers.Config().V1().FeatureGates(),
175+
recorder,
176+
)
177+
178+
featureGateAccessor.SetChangeHandler(func(featureChange featuregates.FeatureChange) {
179+
// Do nothing here. The controller watches feature gate changes and will react to them.
180+
klog.InfoS("FeatureGates changed", "enabled", featureChange.New.Enabled, "disabled", featureChange.New.Disabled)
181+
})
182+
183+
go featureGateAccessor.Run(ctx)
184+
go configInformers.Start(ctx.Done())
185+
186+
select {
187+
case <-featureGateAccessor.InitialFeatureGatesObserved():
188+
enabled, disabled, _ := featureGateAccessor.CurrentFeatureGates()
189+
setupLog.Info("FeatureGates initialized", "enabled", enabled, "disabled", disabled)
190+
case <-time.After(1 * time.Minute):
191+
setupLog.Error(errors.New("timed out waiting for FeatureGate detection"), "unable to start manager")
192+
}
193+
140194
if err = (&controllers.CloudOperatorReconciler{
141195
ClusterOperatorStatusClient: controllers.ClusterOperatorStatusClient{
142196
Client: mgr.GetClient(),
143197
Recorder: mgr.GetEventRecorderFor("cloud-controller-manager-operator"),
144198
ReleaseVersion: controllers.GetReleaseVersion(),
145199
ManagedNamespace: *managedNamespace,
146200
},
147-
Scheme: mgr.GetScheme(),
148-
ImagesFile: *imagesFile,
201+
Scheme: mgr.GetScheme(),
202+
ImagesFile: *imagesFile,
203+
FeatureGateAccess: featureGateAccessor,
149204
}).SetupWithManager(mgr); err != nil {
150205
setupLog.Error(err, "unable to create controller", "controller", "ClusterOperator")
151206
os.Exit(1)
@@ -162,7 +217,7 @@ func main() {
162217
}
163218

164219
setupLog.Info("starting manager")
165-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
220+
if err := mgr.Start(ctx); err != nil {
166221
setupLog.Error(err, "problem running manager")
167222
os.Exit(1)
168223
}

pkg/controllers/clusteroperator_controller.go

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
configv1 "github.com/openshift/api/config/v1"
2424
operatorv1 "github.com/openshift/api/operator/v1"
2525
"github.com/openshift/library-go/pkg/cloudprovider"
26+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
2627
corev1 "k8s.io/api/core/v1"
2728
"k8s.io/apimachinery/pkg/api/errors"
2829
"k8s.io/apimachinery/pkg/runtime"
@@ -49,9 +50,10 @@ const (
4950
// CloudOperatorReconciler reconciles a ClusterOperator object
5051
type CloudOperatorReconciler struct {
5152
ClusterOperatorStatusClient
52-
Scheme *runtime.Scheme
53-
watcher ObjectWatcher
54-
ImagesFile string
53+
Scheme *runtime.Scheme
54+
watcher ObjectWatcher
55+
ImagesFile string
56+
FeatureGateAccess featuregates.FeatureGateAccess
5557
}
5658

5759
// +kubebuilder:rbac:groups=config.openshift.io,resources=clusteroperators,verbs=get;list;watch;create;update;patch;delete
@@ -234,27 +236,8 @@ func (r *CloudOperatorReconciler) provisioningAllowed(ctx context.Context, infra
234236
return true, nil
235237
}
236238

237-
featureGate := &configv1.FeatureGate{}
238-
if err := r.Get(ctx, client.ObjectKey{Name: externalFeatureGateName}, featureGate); errors.IsNotFound(err) {
239-
klog.Infof("FeatureGate cluster does not exist. Skipping...")
240-
241-
if err := r.setStatusAvailable(ctx); err != nil {
242-
klog.Errorf("Unable to sync cluster operator status: %s", err)
243-
return false, err
244-
}
245-
return false, nil
246-
} else if err != nil {
247-
klog.Errorf("Unable to retrive FeatureGate object: %v", err)
248-
249-
if err := r.setStatusDegraded(ctx, err); err != nil {
250-
klog.Errorf("Error syncing ClusterOperatorStatus: %v", err)
251-
return false, fmt.Errorf("error syncing ClusterOperatorStatus: %v", err)
252-
}
253-
return false, err
254-
}
255-
256239
// Verify FeatureGate ExternalCloudProvider is enabled for operator to work in TP phase
257-
external, err := cloudprovider.IsCloudProviderExternal(infra.Status.PlatformStatus, featureGate)
240+
external, err := cloudprovider.IsCloudProviderExternal(infra.Status.PlatformStatus, r.FeatureGateAccess)
258241
if err != nil {
259242
klog.Errorf("Could not determine external cloud provider state: %v", err)
260243

pkg/controllers/clusteroperator_controller_test.go

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
. "github.com/onsi/gomega"
1212
configv1 "github.com/openshift/api/config/v1"
1313
operatorv1 "github.com/openshift/api/operator/v1"
14-
"github.com/openshift/library-go/pkg/cloudprovider"
1514
"github.com/openshift/library-go/pkg/config/clusteroperator/v1helpers"
15+
"github.com/openshift/library-go/pkg/operator/configobserver/featuregates"
1616
appsv1 "k8s.io/api/apps/v1"
1717
corev1 "k8s.io/api/core/v1"
1818
"k8s.io/apimachinery/pkg/api/equality"
@@ -221,21 +221,16 @@ func constructKeyForWatchedObject(object client.Object, scheme *runtime.Scheme)
221221

222222
var _ = Describe("Component sync controller", func() {
223223
var infra *configv1.Infrastructure
224-
var fg *configv1.FeatureGate
225224
var kcm *operatorv1.KubeControllerManager
226225
var co *configv1.ClusterOperator
227226
var operatorController *CloudOperatorReconciler
228227
var operands []client.Object
229228
var watcher mockedWatcher
230229

231-
externalFeatureGateSpec := &configv1.FeatureGateSpec{
232-
FeatureGateSelection: configv1.FeatureGateSelection{
233-
FeatureSet: configv1.CustomNoUpgrade,
234-
CustomNoUpgrade: &configv1.CustomFeatureGates{
235-
Enabled: []configv1.FeatureGateName{cloudprovider.ExternalCloudProviderFeature},
236-
},
237-
},
238-
}
230+
externalFeatureGateAccessor := featuregates.NewHardcodedFeatureGateAccess(
231+
[]configv1.FeatureGateName{configv1.FeatureGateExternalCloudProvider},
232+
nil,
233+
)
239234

240235
kcmStatus := &operatorv1.KubeControllerManagerStatus{
241236
StaticPodOperatorStatus: operatorv1.StaticPodOperatorStatus{
@@ -299,9 +294,6 @@ var _ = Describe("Component sync controller", func() {
299294
infra = &configv1.Infrastructure{}
300295
infra.SetName(infrastructureResourceName)
301296

302-
fg = &configv1.FeatureGate{}
303-
fg.SetName(externalFeatureGateName)
304-
305297
kcm = &operatorv1.KubeControllerManager{
306298
ObjectMeta: metav1.ObjectMeta{
307299
Name: kcmResourceName,
@@ -326,29 +318,27 @@ var _ = Describe("Component sync controller", func() {
326318
ManagedNamespace: testManagedNamespace,
327319
Recorder: record.NewFakeRecorder(32),
328320
},
329-
Scheme: scheme.Scheme,
330-
watcher: w,
331-
ImagesFile: testImagesFilePath,
321+
Scheme: scheme.Scheme,
322+
watcher: w,
323+
ImagesFile: testImagesFilePath,
324+
FeatureGateAccess: featuregates.NewHardcodedFeatureGateAccess(nil, nil),
332325
}
333326
originalWatcher, _ := w.(*objectWatcher)
334327
watcher = mockedWatcher{watcher: originalWatcher}
335328

336329
Expect(cl.Create(context.Background(), infra.DeepCopy())).To(Succeed())
337-
Expect(cl.Create(context.Background(), fg.DeepCopy())).To(Succeed())
338330
Expect(cl.Create(context.Background(), kcm.DeepCopy())).To(Succeed())
339331
Expect(cl.Create(context.Background(), co.DeepCopy())).To(Succeed())
340332
})
341333

342334
AfterEach(func() {
343335
Expect(cl.Delete(context.Background(), infra.DeepCopy())).To(Succeed())
344-
Expect(cl.Delete(context.Background(), fg.DeepCopy())).To(Succeed())
345336
Expect(cl.Delete(context.Background(), kcm.DeepCopy())).To(Succeed())
346337
Expect(cl.Delete(context.Background(), co.DeepCopy())).To(Succeed())
347338

348339
Eventually(func() bool {
349340
return apierrors.IsNotFound(cl.Get(context.Background(), client.ObjectKeyFromObject(infra), infra.DeepCopy())) &&
350-
apierrors.IsNotFound(cl.Get(context.Background(), client.ObjectKeyFromObject(fg), fg.DeepCopy())) &&
351-
apierrors.IsNotFound(cl.Get(context.Background(), client.ObjectKeyFromObject(co), fg.DeepCopy())) &&
341+
apierrors.IsNotFound(cl.Get(context.Background(), client.ObjectKeyFromObject(co), co.DeepCopy())) &&
352342
apierrors.IsNotFound(cl.Get(context.Background(), client.ObjectKeyFromObject(kcm), kcm.DeepCopy()))
353343
}, timeout).Should(BeTrue())
354344

@@ -363,7 +353,7 @@ var _ = Describe("Component sync controller", func() {
363353

364354
type testCase struct {
365355
status *configv1.InfrastructureStatus
366-
featureGateSpec *configv1.FeatureGateSpec
356+
featureGate featuregates.FeatureGateAccess
367357
kcmStatus *operatorv1.KubeControllerManagerStatus
368358
coStatus *configv1.ClusterOperatorStatus
369359
expectProvisioned bool
@@ -376,10 +366,8 @@ var _ = Describe("Component sync controller", func() {
376366
infra.Status = *tc.status
377367
Expect(cl.Status().Update(context.Background(), infra.DeepCopy())).To(Succeed())
378368

379-
if tc.featureGateSpec != nil {
380-
Expect(cl.Get(context.Background(), client.ObjectKeyFromObject(fg), fg)).To(Succeed())
381-
fg.Spec = *tc.featureGateSpec
382-
Expect(cl.Update(context.Background(), fg.DeepCopy())).To(Succeed())
369+
if tc.featureGate != nil {
370+
operatorController.FeatureGateAccess = tc.featureGate
383371
}
384372

385373
if tc.kcmStatus != nil {
@@ -454,7 +442,7 @@ var _ = Describe("Component sync controller", func() {
454442
Type: configv1.AWSPlatformType,
455443
},
456444
},
457-
featureGateSpec: externalFeatureGateSpec,
445+
featureGate: externalFeatureGateAccessor,
458446
kcmStatus: kcmStatus,
459447
coStatus: coStatus,
460448
expectProvisioned: true,
@@ -468,7 +456,7 @@ var _ = Describe("Component sync controller", func() {
468456
Type: configv1.OpenStackPlatformType,
469457
},
470458
},
471-
featureGateSpec: externalFeatureGateSpec,
459+
featureGate: externalFeatureGateAccessor,
472460
kcmStatus: kcmStatus,
473461
coStatus: coStatus,
474462
expectProvisioned: true,
@@ -482,7 +470,7 @@ var _ = Describe("Component sync controller", func() {
482470
Type: configv1.AWSPlatformType,
483471
},
484472
},
485-
featureGateSpec: externalFeatureGateSpec,
473+
featureGate: externalFeatureGateAccessor,
486474
kcmStatus: &operatorv1.KubeControllerManagerStatus{},
487475
coStatus: coStatus,
488476
expectProvisioned: true,
@@ -496,7 +484,7 @@ var _ = Describe("Component sync controller", func() {
496484
Type: configv1.KubevirtPlatformType,
497485
},
498486
},
499-
featureGateSpec: externalFeatureGateSpec,
487+
featureGate: externalFeatureGateAccessor,
500488
kcmStatus: kcmStatus,
501489
coStatus: coStatus,
502490
expectProvisioned: false,
@@ -534,7 +522,7 @@ var _ = Describe("Component sync controller", func() {
534522
Type: configv1.AWSPlatformType,
535523
},
536524
},
537-
featureGateSpec: externalFeatureGateSpec,
525+
featureGate: externalFeatureGateAccessor,
538526
kcmStatus: &operatorv1.KubeControllerManagerStatus{
539527
StaticPodOperatorStatus: operatorv1.StaticPodOperatorStatus{
540528
OperatorStatus: operatorv1.OperatorStatus{
@@ -560,7 +548,7 @@ var _ = Describe("Component sync controller", func() {
560548
Type: configv1.AWSPlatformType,
561549
},
562550
},
563-
featureGateSpec: externalFeatureGateSpec,
551+
featureGate: externalFeatureGateAccessor,
564552
kcmStatus: &operatorv1.KubeControllerManagerStatus{
565553
StaticPodOperatorStatus: operatorv1.StaticPodOperatorStatus{
566554
OperatorStatus: operatorv1.OperatorStatus{
@@ -586,8 +574,8 @@ var _ = Describe("Component sync controller", func() {
586574
Type: configv1.AWSPlatformType,
587575
},
588576
},
589-
featureGateSpec: externalFeatureGateSpec,
590-
kcmStatus: kcmStatus,
577+
featureGate: externalFeatureGateAccessor,
578+
kcmStatus: kcmStatus,
591579
coStatus: &configv1.ClusterOperatorStatus{
592580
Conditions: []configv1.ClusterOperatorStatusCondition{
593581
{
@@ -624,8 +612,8 @@ var _ = Describe("Component sync controller", func() {
624612
Type: configv1.AWSPlatformType,
625613
},
626614
},
627-
featureGateSpec: externalFeatureGateSpec,
628-
kcmStatus: kcmStatus,
615+
featureGate: externalFeatureGateAccessor,
616+
kcmStatus: kcmStatus,
629617
coStatus: &configv1.ClusterOperatorStatus{
630618
Conditions: []configv1.ClusterOperatorStatusCondition{
631619
{

0 commit comments

Comments
 (0)