Skip to content
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
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,6 @@ test: manifests generate fmt lint test-unit test-e2e test-regression #HELP Run a
e2e: #EXHELP Run the e2e tests.
go test -count=1 -v ./test/e2e/...

.PHONY: experimental-e2e
experimental-e2e: #EXHELP Run the experimental e2e tests.
go test -count=1 -v ./test/experimental-e2e/...

E2E_REGISTRY_NAME := docker-registry
E2E_REGISTRY_NAMESPACE := operator-controller-e2e

Expand Down Expand Up @@ -285,7 +281,7 @@ test-experimental-e2e: KIND_CLUSTER_NAME := operator-controller-e2e
test-experimental-e2e: GO_BUILD_EXTRA_FLAGS := -cover
test-experimental-e2e: COVERAGE_NAME := experimental-e2e
test-experimental-e2e: export MANIFEST := $(EXPERIMENTAL_RELEASE_MANIFEST)
test-experimental-e2e: run-internal image-registry prometheus experimental-e2e e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster
test-experimental-e2e: run-internal image-registry prometheus e2e e2e-coverage kind-clean #HELP Run experimental e2e test suite on local kind cluster

.PHONY: prometheus
prometheus: PROMETHEUS_NAMESPACE := olmv1-system
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package experimental_e2e
package e2e

import (
"context"
"fmt"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -16,46 +15,19 @@ import (
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

ocv1 "github.com/operator-framework/operator-controller/api/v1"
"github.com/operator-framework/operator-controller/internal/operator-controller/scheme"
utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils"
. "github.com/operator-framework/operator-controller/test/helpers"
)

const (
artifactName = "operator-controller-experimental-e2e"
pollDuration = time.Minute
pollInterval = time.Second
soNsFlag = "SingleOwnNamespaceInstallSupport"
)

var (
cfg *rest.Config
c client.Client
)

func TestMain(m *testing.M) {
cfg = ctrl.GetConfigOrDie()

var err error
utilruntime.Must(apiextensionsv1.AddToScheme(scheme.Scheme))
c, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
utilruntime.Must(err)

os.Exit(m.Run())
}

func TestNoop(t *testing.T) {
t.Log("Running experimental-e2e tests")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
}

func TestClusterExtensionSingleNamespaceSupport(t *testing.T) {
SkipIfFeatureGateDisabled(t, soNsFlag)
t.Log("Test support for cluster extension config")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)

Expand Down Expand Up @@ -213,6 +185,7 @@ func TestClusterExtensionSingleNamespaceSupport(t *testing.T) {
}

func TestClusterExtensionOwnNamespaceSupport(t *testing.T) {
SkipIfFeatureGateDisabled(t, soNsFlag)
t.Log("Test support for cluster extension with OwnNamespace install mode support")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)

Expand Down Expand Up @@ -382,6 +355,7 @@ func TestClusterExtensionOwnNamespaceSupport(t *testing.T) {
}

func TestClusterExtensionVersionUpdate(t *testing.T) {
SkipIfFeatureGateDisabled(t, soNsFlag)
t.Log("When a cluster extension is installed from a catalog")
t.Log("When resolving upgrade edges")

Expand Down
7 changes: 2 additions & 5 deletions test/e2e/webhook_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@ import (

ocv1 "github.com/operator-framework/operator-controller/api/v1"
utils "github.com/operator-framework/operator-controller/internal/shared/util/testutils"
. "github.com/operator-framework/operator-controller/test/helpers"
)

var dynamicClient dynamic.Interface

func TestNoop(t *testing.T) {
t.Log("Running experimental-e2e tests")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)
}

func TestWebhookSupport(t *testing.T) {
SkipIfFeatureGateDisabled(t, "WebhookProviderCertManager")
t.Log("Test support for bundles with webhooks")
defer utils.CollectTestArtifacts(t, artifactName, c, cfg)

Expand Down
108 changes: 108 additions & 0 deletions test/helpers/feature_gates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Package utils provides helper functions for e2e tests, including
// feature gate detection and validation utilities.
package utils

import (
"strings"
"sync"
"testing"

"github.com/stretchr/testify/require"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/component-base/featuregate"
"sigs.k8s.io/controller-runtime/pkg/client"

catdfeatures "github.com/operator-framework/operator-controller/internal/catalogd/features"
opconfeatures "github.com/operator-framework/operator-controller/internal/operator-controller/features"
)

var (
featureGateStatus map[string]bool
featureGateStatusOnce sync.Once
)

const (
fgPrefix = "--feature-gates="
)

// SkipIfFeatureGateDisabled skips the test if the specified feature gate is disabled.
// It queries the OLM deployments to detect feature gate settings and falls back to
// programmatic defaults if the feature gate is not explicitly configured.
func SkipIfFeatureGateDisabled(t *testing.T, fg string) {
if !isFeatureGateEnabled(t, fg) {
t.Skipf("Feature-gate %q disabled", fg)
}
}

func isFeatureGateEnabled(t *testing.T, fg string) bool {
gatherFeatureGates(t)
enabled, ok := featureGateStatus[fg]
if ok {
return enabled
}

// Not found (i.e. not explicitly set), so we need to find the programmed default.
// Because feature-gates are organized by catd/opcon, we need to check each individually.
// To avoid a panic, we need to check if it's a known gate first.
mfgs := []featuregate.MutableFeatureGate{
catdfeatures.CatalogdFeatureGate,
opconfeatures.OperatorControllerFeatureGate,
}
f := featuregate.Feature(fg)
for _, mfg := range mfgs {
known := mfg.GetAll()
if _, ok := known[f]; ok {
e := mfg.Enabled(f)
t.Logf("Feature-gate %q not found in arguments, defaulting to %v", fg, e)
return e
}
}

t.Fatalf("Unknown feature-gate: %q", fg)
return false // unreachable, but required for compilation
}

func processFeatureGate(t *testing.T, featureGateValue string) {
fgvs := strings.Split(featureGateValue, ",")
for _, fg := range fgvs {
v := strings.Split(fg, "=")
require.Len(t, v, 2, "invalid feature-gate format: %q (expected name=value)", fg)
switch v[1] {
case "true":
featureGateStatus[v[0]] = true
t.Logf("Feature-gate %q enabled", v[0])
case "false":
featureGateStatus[v[0]] = false
t.Logf("Feature-gate %q disabled", v[0])
default:
t.Fatalf("invalid feature-gate value: %q (expected true or false)", fg)
}
}
}

func gatherFeatureGatesFromDeployment(t *testing.T, dep *appsv1.Deployment) {
for _, con := range dep.Spec.Template.Spec.Containers {
for _, arg := range con.Args {
if strings.HasPrefix(arg, fgPrefix) {
processFeatureGate(t, strings.TrimPrefix(arg, fgPrefix))
}
}
}
}

func gatherFeatureGates(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I would reorder these functions so they are in the order they're used so a human reading the code doesn't have to scroll to the bottom and then work their way back up.

Current order:

  • processFeatureGate
  • gatherFeatureGatesFromDeployment
  • gatherFeatureGates

Suggested Order:

  • gatherFeatureGates
  • gatherFeatureGatesFromDeployment
  • processFeatureGate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you can tell I'm a C programmer this way!
(But yeah, I hear you)

featureGateStatusOnce.Do(func() {
featureGateStatus = make(map[string]bool)

depList := &appsv1.DeploymentList{}
err := c.List(t.Context(), depList, client.MatchingLabels{
"app.kubernetes.io/part-of": "olm",
})
require.NoError(t, err)
require.Len(t, depList.Items, 2)

for _, d := range depList.Items {
gatherFeatureGatesFromDeployment(t, &d)
}
})
}