diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 4a1eb6d93..3df78ebde 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/api/v1" "github.com/mongodb/mongodb-kubernetes-operator/controllers" @@ -11,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" @@ -60,18 +62,20 @@ func main() { } // Get watch namespace from environment variable. - namespace, nsSpecified := os.LookupEnv(WatchNamespaceEnv) + namespaces, nsSpecified := os.LookupEnv(WatchNamespaceEnv) if !nsSpecified { log.Sugar().Fatal("No namespace specified to watch") } // If namespace is a wildcard use the empty string to represent all namespaces - watchNamespace := "" - if namespace == "*" { + var watchNamespaces []string + if namespaces == "*" { log.Info("Watching all namespaces") } else { - watchNamespace = namespace - log.Sugar().Infof("Watching namespace: %s", watchNamespace) + for _, ns := range strings.Split(namespaces, ",") { + watchNamespaces = append(watchNamespaces, strings.TrimSpace(ns)) + } + log.Sugar().Infof("Watching namespace: %s", strings.Join(watchNamespaces, ",")) } // Get a config to talk to the apiserver @@ -82,7 +86,9 @@ func main() { // Create a new Cmd to provide shared dependencies and start components mgr, err := manager.New(cfg, manager.Options{ - Namespace: watchNamespace, + Cache: cache.Options{ + Namespaces: watchNamespaces, + }, }) if err != nil { log.Sugar().Fatalf("Unable to create manager: %v", err) diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go index 35392f7f2..2b5a4a507 100644 --- a/pkg/helm/helm.go +++ b/pkg/helm/helm.go @@ -20,7 +20,7 @@ func DependencyUpdate(chartPath string) error { } // Install a helm chert at the given path with the given name and the provided set arguments. -func Install(chartPath, chartName string, flags map[string]string, templateValues map[string]string) error { +func Install(chartPath, chartName string, flags map[string]string, templateValues map[string]interface{}) error { helmArgs := []string{"install"} helmArgs = append(helmArgs, chartName, chartPath) for flagKey, flagValue := range flags { @@ -53,10 +53,15 @@ func executeHelmCommand(args []string, messagePredicate func(string) bool) error // mapToHelmValuesArg accepts a map of string to string and returns a list of arguments // that can be passed to a shell helm command. -func mapToHelmValuesArg(m map[string]string) []string { +func mapToHelmValuesArg(m map[string]interface{}) []string { var args []string for k, v := range m { - args = append(args, "--set", fmt.Sprintf("%s=%s", k, v)) + if strValue, ok := v.(string); ok { + args = append(args, "--set", fmt.Sprintf("%s=%s", k, strValue)) + } + if sliceValue, ok := v.([]string); ok { + args = append(args, "--set", fmt.Sprintf("%s={%s}", k, strings.Join(sliceValue, ","))) + } } return args } diff --git a/pkg/util/envvar/envvars.go b/pkg/util/envvar/envvars.go index 00f054995..c87a74c01 100644 --- a/pkg/util/envvar/envvars.go +++ b/pkg/util/envvar/envvars.go @@ -41,3 +41,10 @@ func ReadBool(envVarName string) bool { envVar := GetEnvOrDefault(envVarName, "false") return strings.TrimSpace(strings.ToLower(envVar)) == "true" } + +func ReadCSVOrDefault(envVarName string, defaultValue []string) []string { + if val, ok := os.LookupEnv(envVarName); ok { + return strings.Split(val, ",") + } + return defaultValue +} diff --git a/test/e2e/replica_set_multiple_namespaces/replica_set_multiple_namespaces_test.go b/test/e2e/replica_set_multiple_namespaces/replica_set_multiple_namespaces_test.go new file mode 100644 index 000000000..638a680c6 --- /dev/null +++ b/test/e2e/replica_set_multiple_namespaces/replica_set_multiple_namespaces_test.go @@ -0,0 +1,62 @@ +package replica_set_multiple_namespaces + +import ( + "context" + "fmt" + "os" + "strings" + "testing" + + "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/util/mongotester" + + e2eutil "github.com/mongodb/mongodb-kubernetes-operator/test/e2e" + "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/mongodbtests" + "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/setup" +) + +const ( + testWatchNamespaceEnvName = "TEST_WATCH_NAMESPACE" + testMongoDBNamespaces = "ns1,ns2" +) + +func TestMain(m *testing.M) { + code, err := e2eutil.RunTest(m) + if err != nil { + fmt.Println(err) + } + os.Exit(code) +} + +// TestReplicaSetMultipleNamespaces creates two MongoDB resources in separate +// namespaces to be processed by the Operator simultaneously. +func TestReplicaSetMultipleNamespaces(t *testing.T) { + t.Setenv(testWatchNamespaceEnvName, testMongoDBNamespaces) + ctx := context.Background() + + testCtx := setup.Setup(ctx, t) + defer testCtx.Teardown() + + for _, namespace := range strings.Split(testMongoDBNamespaces, ",") { + mdb, user := e2eutil.NewTestMongoDB(testCtx, "mdb", namespace) + + _, err := setup.GeneratePasswordForUser(testCtx, user, namespace) + if err != nil { + t.Fatal(err) + } + + tester, err := mongotester.FromResource(ctx, t, mdb) + if err != nil { + t.Fatal(err) + } + + t.Run("Create MongoDB Resource mdb", mongodbtests.CreateMongoDBResource(&mdb, testCtx)) + + t.Run("mdb: Basic tests", mongodbtests.BasicFunctionality(ctx, &mdb)) + + t.Run("mdb: Test Basic Connectivity", tester.ConnectivitySucceeds()) + + t.Run("mdb: AutomationConfig has the correct version", mongodbtests.AutomationConfigVersionHasTheExpectedVersion(ctx, &mdb, 1)) + + t.Run("mdb: Ensure Authentication", tester.EnsureAuthenticationIsConfigured(3)) + } +} diff --git a/test/e2e/setup/setup.go b/test/e2e/setup/setup.go index 22e87f253..57ad9c3ae 100644 --- a/test/e2e/setup/setup.go +++ b/test/e2e/setup/setup.go @@ -49,6 +49,9 @@ func Setup(ctx context.Context, t *testing.T) *e2eutil.TestContext { } config := LoadTestConfigFromEnv() + if err := ensureWatchNamespaces(testCtx, config); err != nil { + t.Fatal(err) + } if err := DeployOperator(ctx, config, "mdb", false, false); err != nil { t.Fatal(err) } @@ -57,13 +60,16 @@ func Setup(ctx context.Context, t *testing.T) *e2eutil.TestContext { } func SetupWithTLS(ctx context.Context, t *testing.T, resourceName string, additionalHelmArgs ...HelmArg) (*e2eutil.TestContext, TestConfig) { - textCtx, err := e2eutil.NewContext(ctx, t, envvar.ReadBool(performCleanupEnv)) + testCtx, err := e2eutil.NewContext(ctx, t, envvar.ReadBool(performCleanupEnv)) if err != nil { t.Fatal(err) } config := LoadTestConfigFromEnv() + if err := ensureWatchNamespaces(testCtx, config); err != nil { + t.Fatal(err) + } if err := deployCertManager(config); err != nil { t.Fatal(err) } @@ -72,7 +78,7 @@ func SetupWithTLS(ctx context.Context, t *testing.T, resourceName string, additi t.Fatal(err) } - return textCtx, config + return testCtx, config } func SetupWithTestConfig(ctx context.Context, t *testing.T, testConfig TestConfig, withTLS, defaultOperator bool, resourceName string) *e2eutil.TestContext { @@ -88,6 +94,9 @@ func SetupWithTestConfig(ctx context.Context, t *testing.T, testConfig TestConfi } } + if err := ensureWatchNamespaces(testCtx, testConfig); err != nil { + t.Fatal(err) + } if err := DeployOperator(ctx, testConfig, resourceName, withTLS, defaultOperator); err != nil { t.Fatal(err) } @@ -138,17 +147,17 @@ func extractRegistryNameAndVersion(fullImage string) (string, string, string) { } // getHelmArgs returns a map of helm arguments that are required to install the operator. -func getHelmArgs(testConfig TestConfig, watchNamespace string, resourceName string, withTLS bool, defaultOperator bool, additionalHelmArgs ...HelmArg) map[string]string { +func getHelmArgs(testConfig TestConfig, watchNamespaces []string, resourceName string, withTLS bool, defaultOperator bool, additionalHelmArgs ...HelmArg) map[string]interface{} { agentRegistry, agentName, agentVersion := extractRegistryNameAndVersion(testConfig.AgentImage) versionUpgradeHookRegistry, versionUpgradeHookName, versionUpgradeHookVersion := extractRegistryNameAndVersion(testConfig.VersionUpgradeHookImage) readinessProbeRegistry, readinessProbeName, readinessProbeVersion := extractRegistryNameAndVersion(testConfig.ReadinessProbeImage) operatorRegistry, operatorName, operatorVersion := extractRegistryNameAndVersion(testConfig.OperatorImage) - helmArgs := make(map[string]string) + helmArgs := make(map[string]interface{}) helmArgs["namespace"] = testConfig.Namespace - helmArgs["operator.watchNamespace"] = watchNamespace + helmArgs["operator.watchNamespaces"] = watchNamespaces if !defaultOperator { helmArgs["operator.operatorImageName"] = operatorName @@ -189,18 +198,18 @@ func getHelmArgs(testConfig TestConfig, watchNamespace string, resourceName stri func DeployOperator(ctx context.Context, config TestConfig, resourceName string, withTLS bool, defaultOperator bool, additionalHelmArgs ...HelmArg) error { e2eutil.OperatorNamespace = config.Namespace fmt.Printf("Setting operator namespace to %s\n", e2eutil.OperatorNamespace) - watchNamespace := config.Namespace + watchNamespaces := config.WatchNamespaces if config.ClusterWide { - watchNamespace = "*" + watchNamespaces = []string{"*"} } - fmt.Printf("Setting namespace to watch to %s\n", watchNamespace) + fmt.Printf("Setting namespace to watch to %s\n", strings.Join(watchNamespaces, ",")) helmChartName := "mongodb-kubernetes-operator" if err := helm.Uninstall(helmChartName, config.Namespace); err != nil { return err } - helmArgs := getHelmArgs(config, watchNamespace, resourceName, withTLS, defaultOperator, additionalHelmArgs...) + helmArgs := getHelmArgs(config, watchNamespaces, resourceName, withTLS, defaultOperator, additionalHelmArgs...) helmFlags := map[string]string{ "namespace": config.Namespace, "create-namespace": "", @@ -256,7 +265,7 @@ func deployCertManager(config TestConfig) error { "namespace": config.CertManagerNamespace, "create-namespace": "", } - values := map[string]string{"installCRDs": "true"} + values := map[string]interface{}{"installCRDs": "true"} if err := helm.Install(charlUrl, helmChartName, flags, values); err != nil { return fmt.Errorf("failed to install cert-manager Helm chart: %s", err) } @@ -283,3 +292,13 @@ func hasDeploymentRequiredReplicas(dep *appsv1.Deployment) wait.ConditionWithCon return false, nil } } + +func ensureWatchNamespaces(ctx *e2eutil.TestContext, config TestConfig) error { + for _, namespace := range config.WatchNamespaces { + err := e2eutil.EnsureNamespace(ctx, namespace) + if err != nil { + return err + } + } + return nil +} diff --git a/test/e2e/setup/test_config.go b/test/e2e/setup/test_config.go index 06f2018fe..9d336602c 100644 --- a/test/e2e/setup/test_config.go +++ b/test/e2e/setup/test_config.go @@ -7,6 +7,7 @@ import ( const ( testNamespaceEnvName = "TEST_NAMESPACE" + testWatchNamespacesEnvName = "TEST_WATCH_NAMESPACE" testCertManagerNamespaceEnvName = "TEST_CERT_MANAGER_NAMESPACE" testCertManagerVersionEnvName = "TEST_CERT_MANAGER_VERSION" operatorImageEnvName = "OPERATOR_IMAGE" @@ -18,6 +19,7 @@ const ( type TestConfig struct { Namespace string + WatchNamespaces []string CertManagerNamespace string CertManagerVersion string OperatorImage string @@ -35,6 +37,7 @@ type TestConfig struct { func LoadTestConfigFromEnv() TestConfig { return TestConfig{ Namespace: envvar.GetEnvOrDefault(testNamespaceEnvName, "mongodb"), + WatchNamespaces: envvar.ReadCSVOrDefault(testWatchNamespacesEnvName, []string{"mongodb"}), CertManagerNamespace: envvar.GetEnvOrDefault(testCertManagerNamespaceEnvName, "cert-manager"), CertManagerVersion: envvar.GetEnvOrDefault(testCertManagerVersionEnvName, "v1.5.3"), OperatorImage: envvar.GetEnvOrDefault(operatorImageEnvName, "quay.io/mongodb/community-operator-dev:latest"),