Skip to content

Commit ab1a554

Browse files
committed
feature-gates: adding initial structure
This commit introduces the `FeatureGates` struct in the operator's configuration, enabling a flexible and controlled approach to feature rollout. The `FeatureGates` configMap allows for the enabling or disabling of specific features, with a default behaviour based on each feature's maturity level. For each new feature, developers need to modify the "default" struct, just in case users don't have to set their desired behaviour. User's choice will override default values. I'm trying to use a simple approach here where we could change promote/depromote a feature by just changing the comments and the default values. Fixes: #KATA-2677 Signed-off-by: Beraldo Leal <[email protected]>
1 parent cd3716d commit ab1a554

File tree

5 files changed

+117
-2
lines changed

5 files changed

+117
-2
lines changed

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ COPY main.go main.go
1515
COPY api api/
1616
COPY config config/
1717
COPY controllers controllers/
18+
COPY internal internal/
1819

1920
RUN go mod download
2021
# needed for docker build but not for local builds

config/samples/featuregates.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: osc-feature-gates
5+
namespace: openshift-sandboxed-containers-operator
6+
data:
7+
# timeTravel allows navigating through cluster states across time.
8+
# It is useful for scenarios where you want to view historical data or
9+
# predict future states based on current trends. Default is "false".
10+
# timeTravel: "false"
11+
12+
# quantumEntanglementSync enables instant state consistency across clusters
13+
# using principles of quantum mechanics. This advanced feature ensures
14+
# data is synchronized across different locations without traditional
15+
# network latency. Default is "false".
16+
# quantumEntanglementSync: "false"
17+
18+
# autoHealingWithAI employs artificial intelligence to automatically
19+
# detect and resolve cluster issues. It leverages machine learning algorithms
20+
# to predict potential problems before they occur, ensuring high availability.
21+
# Default is "true".
22+
# autoHealingWithAI: "true"

controllers/openshift_controller.go

+47-2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
3636
mcfgconsts "github.com/openshift/machine-config-operator/pkg/daemon/constants"
3737
kataconfigurationv1 "github.com/openshift/sandboxed-containers-operator/api/v1"
38+
"github.com/openshift/sandboxed-containers-operator/internal/featuregates"
3839
corev1 "k8s.io/api/core/v1"
3940
nodeapi "k8s.io/api/node/v1"
4041
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -60,7 +61,8 @@ type KataConfigOpenShiftReconciler struct {
6061
Log logr.Logger
6162
Scheme *runtime.Scheme
6263

63-
kataConfig *kataconfigurationv1.KataConfig
64+
kataConfig *kataconfigurationv1.KataConfig
65+
FeatureGates *featuregates.FeatureGates
6466
}
6567

6668
const (
@@ -121,6 +123,10 @@ func (r *KataConfigOpenShiftReconciler) Reconcile(ctx context.Context, req ctrl.
121123
return ctrl.Result{}, err
122124
}
123125

126+
if r.FeatureGates.IsEnabled(ctx, "timeTravel") {
127+
r.Log.Info("TimeTravel feature is enabled. Performing feature-specific logic...")
128+
}
129+
124130
return func() (ctrl.Result, error) {
125131

126132
// k8s resource correctness checking on creation/modification
@@ -1285,6 +1291,42 @@ func logMcpChange(log logr.Logger, statusOld, statusNew mcfgv1.MachineConfigPool
12851291
}
12861292
}
12871293

1294+
type ConfigMapEventHandler struct {
1295+
reconciler *KataConfigOpenShiftReconciler
1296+
}
1297+
1298+
func (eh *ConfigMapEventHandler) isConfigMapRelevant(configMap *corev1.ConfigMap) bool {
1299+
relevantNamespace := configMap.Namespace == eh.reconciler.FeatureGates.Namespace
1300+
relevantName := configMap.Name == eh.reconciler.FeatureGates.ConfigMapName
1301+
return relevantNamespace && relevantName
1302+
}
1303+
1304+
func (eh *ConfigMapEventHandler) Create(evt event.CreateEvent, queue workqueue.RateLimitingInterface) {
1305+
configMap, ok := evt.Object.(*corev1.ConfigMap)
1306+
if !ok || !eh.isConfigMapRelevant(configMap) {
1307+
return
1308+
}
1309+
queue.Add(eh.reconciler.makeReconcileRequest())
1310+
}
1311+
1312+
func (eh *ConfigMapEventHandler) Update(evt event.UpdateEvent, queue workqueue.RateLimitingInterface) {
1313+
configMap, ok := evt.ObjectNew.(*corev1.ConfigMap)
1314+
if !ok || !eh.isConfigMapRelevant(configMap) {
1315+
return
1316+
}
1317+
}
1318+
1319+
func (eh *ConfigMapEventHandler) Delete(evt event.DeleteEvent, queue workqueue.RateLimitingInterface) {
1320+
configMap, ok := evt.Object.(*corev1.ConfigMap)
1321+
if !ok || !eh.isConfigMapRelevant(configMap) {
1322+
return
1323+
}
1324+
queue.Add(eh.reconciler.makeReconcileRequest())
1325+
}
1326+
1327+
func (eh *ConfigMapEventHandler) Generic(evt event.GenericEvent, queue workqueue.RateLimitingInterface) {
1328+
}
1329+
12881330
type McpEventHandler struct {
12891331
reconciler *KataConfigOpenShiftReconciler
12901332
}
@@ -1528,7 +1570,10 @@ func (r *KataConfigOpenShiftReconciler) SetupWithManager(mgr ctrl.Manager) error
15281570
Watches(
15291571
&source.Kind{Type: &corev1.Node{}},
15301572
&NodeEventHandler{r}).
1531-
Complete(r)
1573+
Watches(
1574+
&source.Kind{Type: &corev1.ConfigMap{}},
1575+
&ConfigMapEventHandler{r},
1576+
).Complete(r)
15321577
}
15331578

15341579
func (r *KataConfigOpenShiftReconciler) getNodes() (error, *corev1.NodeList) {

internal/featuregates/featuregates.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package featuregates
2+
3+
import (
4+
"context"
5+
corev1 "k8s.io/api/core/v1"
6+
"log"
7+
"sigs.k8s.io/controller-runtime/pkg/client"
8+
)
9+
10+
type FeatureGates struct {
11+
Client client.Client
12+
Namespace string
13+
ConfigMapName string
14+
}
15+
16+
var DefaultFeatureGates = map[string]bool{
17+
"timeTravel": false,
18+
"quantumEntanglementSync": false,
19+
"autoHealingWithAI": true,
20+
}
21+
22+
func (fg *FeatureGates) IsEnabled(ctx context.Context, feature string) bool {
23+
cfgMap := &corev1.ConfigMap{}
24+
err := fg.Client.Get(ctx,
25+
client.ObjectKey{Name: fg.ConfigMapName, Namespace: fg.Namespace},
26+
cfgMap)
27+
28+
if err != nil {
29+
log.Printf("Error fetching feature gates: %v", err)
30+
} else {
31+
if value, exists := cfgMap.Data[feature]; exists {
32+
return value == "true"
33+
}
34+
}
35+
36+
defaultValue, exists := DefaultFeatureGates[feature]
37+
if exists {
38+
return defaultValue
39+
}
40+
return false
41+
}

main.go

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
peerpodconfig "github.com/confidential-containers/cloud-api-adaptor/peerpodconfig-ctrl/api/v1alpha1"
5353
kataconfigurationv1 "github.com/openshift/sandboxed-containers-operator/api/v1"
5454
"github.com/openshift/sandboxed-containers-operator/controllers"
55+
"github.com/openshift/sandboxed-containers-operator/internal/featuregates"
5556
// +kubebuilder:scaffold:imports
5657
)
5758

@@ -138,6 +139,11 @@ func main() {
138139
Client: mgr.GetClient(),
139140
Log: ctrl.Log.WithName("controllers").WithName("KataConfig"),
140141
Scheme: mgr.GetScheme(),
142+
FeatureGates: &featuregates.FeatureGates{
143+
Client: mgr.GetClient(),
144+
Namespace: OperatorNamespace,
145+
ConfigMapName: "osc-feature-gates",
146+
},
141147
}).SetupWithManager(mgr); err != nil {
142148
setupLog.Error(err, "unable to create KataConfig controller for OpenShift cluster", "controller", "KataConfig")
143149
os.Exit(1)

0 commit comments

Comments
 (0)