diff --git a/controllers/sriovnetworknodepolicy_controller.go b/controllers/sriovnetworknodepolicy_controller.go index b949b7864..5bb9dbf8f 100644 --- a/controllers/sriovnetworknodepolicy_controller.go +++ b/controllers/sriovnetworknodepolicy_controller.go @@ -48,6 +48,7 @@ import ( sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" constants "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" + "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/featuregate" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/render" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars" @@ -58,7 +59,8 @@ const nodePolicySyncEventName = "node-policy-sync-event" // SriovNetworkNodePolicyReconciler reconciles a SriovNetworkNodePolicy object type SriovNetworkNodePolicyReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + FeatureGate featuregate.FeatureGate } //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=sriovnetworknodepolicies,verbs=get;list;watch;create;update;patch;delete diff --git a/controllers/sriovnetworknodepolicy_controller_test.go b/controllers/sriovnetworknodepolicy_controller_test.go index 3894f5a93..e3d717fdd 100644 --- a/controllers/sriovnetworknodepolicy_controller_test.go +++ b/controllers/sriovnetworknodepolicy_controller_test.go @@ -18,6 +18,7 @@ import ( sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" v1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" + "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/featuregate" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars" ) @@ -211,7 +212,9 @@ func TestRenderDevicePluginConfigData(t *testing.T) { }, } - reconciler := SriovNetworkNodePolicyReconciler{} + reconciler := SriovNetworkNodePolicyReconciler{ + FeatureGate: featuregate.New(), + } node := corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: "node1"}} nodeState := sriovnetworkv1.SriovNetworkNodeState{ObjectMeta: metav1.ObjectMeta{Name: node.Name, Namespace: vars.Namespace}} diff --git a/controllers/sriovoperatorconfig_controller.go b/controllers/sriovoperatorconfig_controller.go index 91e078f69..9e90a5688 100644 --- a/controllers/sriovoperatorconfig_controller.go +++ b/controllers/sriovoperatorconfig_controller.go @@ -42,6 +42,7 @@ import ( sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" apply "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/apply" consts "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" + "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/featuregate" snolog "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/log" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/platforms" render "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/render" @@ -53,6 +54,7 @@ type SriovOperatorConfigReconciler struct { client.Client Scheme *runtime.Scheme PlatformHelper platforms.Interface + FeatureGate featuregate.FeatureGate } //+kubebuilder:rbac:groups=sriovnetwork.openshift.io,resources=sriovoperatorconfigs,verbs=get;list;watch;create;update;patch;delete @@ -87,6 +89,9 @@ func (r *SriovOperatorConfigReconciler) Reconcile(ctx context.Context, req ctrl. snolog.SetLogLevel(defaultConfig.Spec.LogLevel) + r.FeatureGate.Init(defaultConfig.Spec.FeatureGates) + logger.Info("enabled featureGates", "featureGates", r.FeatureGate.String()) + if !defaultConfig.Spec.EnableInjector { logger.Info("SR-IOV Network Resource Injector is disabled.") } @@ -172,10 +177,7 @@ func (r *SriovOperatorConfigReconciler) syncConfigDaemonSet(ctx context.Context, } else { data.Data["UsedSystemdMode"] = false } - data.Data["ParallelNicConfig"] = false - if parallelConfig, ok := dc.Spec.FeatureGates[consts.ParallelNicConfigFeatureGate]; ok { - data.Data["ParallelNicConfig"] = parallelConfig - } + data.Data["ParallelNicConfig"] = r.FeatureGate.IsEnabled(consts.ParallelNicConfigFeatureGate) envCniBinPath := os.Getenv("SRIOV_CNI_BIN_PATH") if envCniBinPath == "" { @@ -249,10 +251,7 @@ func (r *SriovOperatorConfigReconciler) syncWebhookObjs(ctx context.Context, dc } // check for ResourceInjectorMatchConditionFeatureGate feature gate - data.Data[consts.ResourceInjectorMatchConditionFeatureGate] = false - if resourceInjector, ok := dc.Spec.FeatureGates[consts.ResourceInjectorMatchConditionFeatureGate]; ok { - data.Data[consts.ResourceInjectorMatchConditionFeatureGate] = resourceInjector - } + data.Data[consts.ResourceInjectorMatchConditionFeatureGate] = r.FeatureGate.IsEnabled(consts.ResourceInjectorMatchConditionFeatureGate) objs, err := render.RenderDir(path, &data) if err != nil { diff --git a/controllers/sriovoperatorconfig_controller_test.go b/controllers/sriovoperatorconfig_controller_test.go index 567c17a65..d7cc7fcb9 100644 --- a/controllers/sriovoperatorconfig_controller_test.go +++ b/controllers/sriovoperatorconfig_controller_test.go @@ -18,6 +18,7 @@ import ( sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" constants "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" + "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/featuregate" mock_platforms "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/platforms/mock" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/platforms/openshift" util "github.com/k8snetworkplumbingwg/sriov-network-operator/test/util" @@ -75,6 +76,7 @@ var _ = Describe("SriovOperatorConfig controller", Ordered, func() { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), PlatformHelper: platformHelper, + FeatureGate: featuregate.New(), }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) diff --git a/main.go b/main.go index f04bbb7ca..857ac628d 100644 --- a/main.go +++ b/main.go @@ -47,6 +47,7 @@ import ( sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" "github.com/k8snetworkplumbingwg/sriov-network-operator/controllers" + "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/featuregate" "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/leaderelection" snolog "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/log" @@ -156,6 +157,8 @@ func main() { os.Exit(1) } + featureGate := featuregate.New() + if err = (&controllers.SriovNetworkReconciler{ Client: mgrGlobal.GetClient(), Scheme: mgrGlobal.GetScheme(), @@ -171,8 +174,9 @@ func main() { os.Exit(1) } if err = (&controllers.SriovNetworkNodePolicyReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + FeatureGate: featureGate, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SriovNetworkNodePolicy") os.Exit(1) @@ -181,6 +185,7 @@ func main() { Client: mgr.GetClient(), Scheme: mgr.GetScheme(), PlatformHelper: platformsHelper, + FeatureGate: featureGate, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "SriovOperatorConfig") os.Exit(1) diff --git a/pkg/featuregate/featuregate.go b/pkg/featuregate/featuregate.go new file mode 100644 index 000000000..e14cd0329 --- /dev/null +++ b/pkg/featuregate/featuregate.go @@ -0,0 +1,64 @@ +package featuregate + +import ( + "fmt" + "strings" + "sync" +) + +// FeatureGate provides methods to check state of the feature +type FeatureGate interface { + // IsEnabled returns state of the feature, + // if feature name is unknown will always return false + IsEnabled(feature string) bool + // Init set state for the features from the provided map. + // completely removes the previous state + Init(features map[string]bool) + // String returns string representation of the feature state + String() string +} + +// New returns default implementation of the FeatureGate interface +func New() FeatureGate { + return &featureGate{ + lock: &sync.RWMutex{}, + state: map[string]bool{}, + } +} + +type featureGate struct { + lock *sync.RWMutex + state map[string]bool +} + +// IsEnabled returns state of the feature, +// if feature name is unknown will always return false +func (fg *featureGate) IsEnabled(feature string) bool { + fg.lock.RLock() + defer fg.lock.RUnlock() + return fg.state[feature] +} + +// Init set state for the features from the provided map. +// completely removes the previous state +func (fg *featureGate) Init(features map[string]bool) { + fg.lock.Lock() + defer fg.lock.Unlock() + fg.state = make(map[string]bool, len(features)) + for k, v := range features { + fg.state[k] = v + } +} + +// String returns string representation of the feature state +func (fg *featureGate) String() string { + fg.lock.RLock() + defer fg.lock.RUnlock() + var result strings.Builder + var sep string + for k, v := range fg.state { + result.WriteString(fmt.Sprintf("%s%s:%t", sep, k, v)) + sep = ", " + } + return result.String() +} diff --git a/pkg/featuregate/featuregate_test.go b/pkg/featuregate/featuregate_test.go new file mode 100644 index 000000000..39f773f58 --- /dev/null +++ b/pkg/featuregate/featuregate_test.go @@ -0,0 +1,32 @@ +package featuregate + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("FeatureGate", func() { + Context("IsEnabled", func() { + It("return false for unknown feature", func() { + Expect(New().IsEnabled("something")).To(BeFalse()) + }) + }) + Context("Init", func() { + It("should update the state", func() { + f := New() + f.Init(map[string]bool{"feat1": true, "feat2": false}) + Expect(f.IsEnabled("feat1")).To(BeTrue()) + Expect(f.IsEnabled("feat2")).To(BeFalse()) + }) + }) + Context("String", func() { + It("no features", func() { + Expect(New().String()).To(Equal("")) + }) + It("print feature state", func() { + f := New() + f.Init(map[string]bool{"feat1": true, "feat2": false}) + Expect(f.String()).To(And(ContainSubstring("feat1:true"), ContainSubstring("feat2:false"))) + }) + }) +}) diff --git a/pkg/featuregate/suite_test.go b/pkg/featuregate/suite_test.go new file mode 100644 index 000000000..e61d366d9 --- /dev/null +++ b/pkg/featuregate/suite_test.go @@ -0,0 +1,21 @@ +package featuregate + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "go.uber.org/zap/zapcore" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestFeatureGate(t *testing.T) { + log.SetLogger(zap.New( + zap.WriteTo(GinkgoWriter), + zap.Level(zapcore.Level(-2)), + zap.UseDevMode(true))) + RegisterFailHandler(Fail) + RunSpecs(t, "Package featuregate Suite") +}