diff --git a/Makefile b/Makefile index 379599d92..cf7b1d650 100644 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 diff --git a/test/experimental-e2e/single_namespace_support_test.go b/test/e2e/single_namespace_support_test.go similarity index 95% rename from test/experimental-e2e/single_namespace_support_test.go rename to test/e2e/single_namespace_support_test.go index 77a0cba42..7e8fe7bd5 100644 --- a/test/experimental-e2e/single_namespace_support_test.go +++ b/test/e2e/single_namespace_support_test.go @@ -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" @@ -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) @@ -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) @@ -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") diff --git a/test/e2e/webhook_support_test.go b/test/e2e/webhook_support_test.go index 0809efb54..1c80c615b 100644 --- a/test/e2e/webhook_support_test.go +++ b/test/e2e/webhook_support_test.go @@ -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) diff --git a/test/helpers/feature_gates.go b/test/helpers/feature_gates.go new file mode 100644 index 000000000..bd8362448 --- /dev/null +++ b/test/helpers/feature_gates.go @@ -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) { + 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) + } + }) +}