Skip to content

feature-gates: adding initial structure #371

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ COPY main.go main.go
COPY api api/
COPY config config/
COPY controllers controllers/
COPY internal internal/

RUN go mod download
# needed for docker build but not for local builds
Expand Down
1 change: 1 addition & 0 deletions bundle-custom.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ COPY go.sum go.sum
COPY api api/
COPY config config/
COPY controllers controllers/
COPY internal internal/

RUN go mod download
# needed for docker build but not for local builds
Expand Down
22 changes: 22 additions & 0 deletions config/samples/featuregates.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: osc-feature-gates
namespace: openshift-sandboxed-containers-operator
data:
# timeTravel allows navigating through cluster states across time.
# It is useful for scenarios where you want to view historical data or
# predict future states based on current trends. Default is "false".
# timeTravel: "false"

# quantumEntanglementSync enables instant state consistency across clusters
# using principles of quantum mechanics. This advanced feature ensures
# data is synchronized across different locations without traditional
# network latency. Default is "false".
# quantumEntanglementSync: "false"

# autoHealingWithAI employs artificial intelligence to automatically
# detect and resolve cluster issues. It leverages machine learning algorithms
# to predict potential problems before they occur, ensuring high availability.
# Default is "true".
# autoHealingWithAI: "true"
49 changes: 47 additions & 2 deletions controllers/openshift_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1"
mcfgconsts "github.com/openshift/machine-config-operator/pkg/daemon/constants"
kataconfigurationv1 "github.com/openshift/sandboxed-containers-operator/api/v1"
"github.com/openshift/sandboxed-containers-operator/internal/featuregates"
corev1 "k8s.io/api/core/v1"
nodeapi "k8s.io/api/node/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -44,9 +45,12 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

Expand All @@ -59,7 +63,8 @@ type KataConfigOpenShiftReconciler struct {
Log logr.Logger
Scheme *runtime.Scheme

kataConfig *kataconfigurationv1.KataConfig
kataConfig *kataconfigurationv1.KataConfig
FeatureGates *featuregates.FeatureGates
}

const (
Expand Down Expand Up @@ -120,6 +125,10 @@ func (r *KataConfigOpenShiftReconciler) Reconcile(ctx context.Context, req ctrl.
return ctrl.Result{}, err
}

if r.FeatureGates.IsEnabled(ctx, "timeTravel") {
r.Log.Info("TimeTravel feature is enabled. Performing feature-specific logic...")
}

return func() (ctrl.Result, error) {

// k8s resource correctness checking on creation/modification
Expand Down Expand Up @@ -1563,6 +1572,38 @@ func (eh *NodeEventHandler) Delete(ctx context.Context, event event.DeleteEvent,
func (eh *NodeEventHandler) Generic(ctx context.Context, event event.GenericEvent, queue workqueue.RateLimitingInterface) {
}

func configMapFilterPredicate(reconciler *KataConfigOpenShiftReconciler) predicate.Predicate {
isRelevantConfigMap := func(obj client.Object) bool {
if reconciler == nil || reconciler.FeatureGates == nil {
return false
}

configMap, ok := obj.(*corev1.ConfigMap)
if !ok {
return false
}

relevantNamespace := configMap.Namespace == reconciler.FeatureGates.Namespace
relevantName := configMap.Name == reconciler.FeatureGates.ConfigMapName
return relevantNamespace && relevantName
}

return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return isRelevantConfigMap(e.Object)
},
UpdateFunc: func(e event.UpdateEvent) bool {
return isRelevantConfigMap(e.ObjectNew)
},
DeleteFunc: func(e event.DeleteEvent) bool {
return isRelevantConfigMap(e.Object)
},
GenericFunc: func(e event.GenericEvent) bool {
return isRelevantConfigMap(e.Object)
},
}
}

func (r *KataConfigOpenShiftReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&kataconfigurationv1.KataConfig{}).
Expand All @@ -1572,7 +1613,11 @@ func (r *KataConfigOpenShiftReconciler) SetupWithManager(mgr ctrl.Manager) error
Watches(
&corev1.Node{},
&NodeEventHandler{r}).
Complete(r)
Watches(
&corev1.ConfigMap{},
&handler.EnqueueRequestForObject{},
builder.WithPredicates(configMapFilterPredicate(r)),
).Complete(r)
}

func (r *KataConfigOpenShiftReconciler) getNodes() (*corev1.NodeList, error) {
Expand Down
61 changes: 61 additions & 0 deletions docs/FEATURE-GATES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Feature Gates

Feature Gates in the Openshift Sandboxed Containers operator allow
administrators to enable or disable specific features. These features are
controlled through the `FeatureGates` struct in the operator's configuration,
providing a flexible approach to testing new features and managing the rollout
of stable functionalities.

## Maturity Levels

Our feature gates adhere to simplified lifecycle stages inspired by Kubernetes:

- **DevPreview**:
- Disabled by default.
- May contain bugs; enabling the feature could expose these bugs.
- No long-term support guarantees.

- **TechPreview**:
- Usually disabled by default.
- Support for the feature will not be dropped, but details may change.
- Recommended for non-business-critical usage due to potential for changes.

- **GA (General Availability)**:
- Enabled by default
- Well-tested and considered safe.
- Stable features will be maintained in future software releases.

## Disclaimer

> [!WARNING]
> Remember, the availability and default state of each feature may change between
> releases as features progress through their maturity levels. Always refer to
> the latest documentation for up-to-date information on feature support and
> configuration.

## Configuring Feature Gates

Feature gates can be enabled or disabled by editing the `osc-feature-gates` ConfigMap
resource. Each feature gate allows you to toggle specific functionalities,
adjusting your cluster's behavior as needed.

### Enabling and disabling features

To enable a feature, you modify your `ConfigMap` object like so:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: osc-feature-gates
namespace: openshift-sandboxed-containers-operator
data:
timeTravel: "true"
quantumEntanglementSync: "false"
autoHealingWithAI: "true"
```

In this example, `timeTravel` is explicitly enabled, while
`quantumEntanglementSync` is disabled, and `autoHealingWithAI` is enabled,
showcasing how to manage the state of each feature individually. Regardless the
default values they have.
44 changes: 44 additions & 0 deletions internal/featuregates/featuregates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package featuregates

import (
"context"
corev1 "k8s.io/api/core/v1"
"log"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type FeatureGates struct {
Client client.Client
Namespace string
ConfigMapName string
}

var DefaultFeatureGates = map[string]bool{
"timeTravel": false,
"quantumEntanglementSync": false,
"autoHealingWithAI": true,
}

func (fg *FeatureGates) IsEnabled(ctx context.Context, feature string) bool {
if fg == nil {
return false
}
cfgMap := &corev1.ConfigMap{}
err := fg.Client.Get(ctx,
client.ObjectKey{Name: fg.ConfigMapName, Namespace: fg.Namespace},
cfgMap)

if err != nil {
log.Printf("Error fetching feature gates: %v", err)
} else {
if value, exists := cfgMap.Data[feature]; exists {
return value == "true"
}
}

defaultValue, exists := DefaultFeatureGates[feature]
if exists {
return defaultValue
}
return false
}
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (

kataconfigurationv1 "github.com/openshift/sandboxed-containers-operator/api/v1"
"github.com/openshift/sandboxed-containers-operator/controllers"
"github.com/openshift/sandboxed-containers-operator/internal/featuregates"
// +kubebuilder:scaffold:imports
)

Expand Down Expand Up @@ -142,6 +143,11 @@ func main() {
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("KataConfig"),
Scheme: mgr.GetScheme(),
FeatureGates: &featuregates.FeatureGates{
Client: mgr.GetClient(),
Namespace: OperatorNamespace,
ConfigMapName: "osc-feature-gates",
},
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create KataConfig controller for OpenShift cluster", "controller", "KataConfig")
os.Exit(1)
Expand Down