diff --git a/Makefile b/Makefile index d39786238fd9..8a366fc1de74 100644 --- a/Makefile +++ b/Makefile @@ -617,8 +617,6 @@ generate-e2e-templates-main: $(KUSTOMIZE) $(KUSTOMIZE) build $(DOCKER_TEMPLATES)/main/cluster-template-topology --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/main/cluster-template-topology.yaml $(KUSTOMIZE) build $(DOCKER_TEMPLATES)/main/cluster-template-ignition --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/main/cluster-template-ignition.yaml $(KUSTOMIZE) build $(DOCKER_TEMPLATES)/main/clusterclass-quick-start-kcp-only --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/main/clusterclass-quick-start-kcp-only.yaml - $(KUSTOMIZE) build $(DOCKER_TEMPLATES)/main/cluster-template-cross-ns-topology --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/main/cluster-template-cross-ns-topology.yaml - $(KUSTOMIZE) build $(DOCKER_TEMPLATES)/main/cluster-template-cross-ns-upgrades-runtimesdk --load-restrictor LoadRestrictionsNone > $(DOCKER_TEMPLATES)/main/cluster-template-cross-ns-upgrades-runtimesdk.yaml $(KUSTOMIZE) build $(INMEMORY_TEMPLATES)/main/cluster-template --load-restrictor LoadRestrictionsNone > $(INMEMORY_TEMPLATES)/main/cluster-template.yaml .PHONY: generate-metrics-config diff --git a/cmd/clusterctl/client/clusterclass_test.go b/cmd/clusterctl/client/clusterclass_test.go index ddefab234d1c..108a3a269c48 100644 --- a/cmd/clusterctl/client/clusterclass_test.go +++ b/cmd/clusterctl/client/clusterclass_test.go @@ -126,6 +126,17 @@ func TestAddClusterClassIfMissing(t *testing.T) { wantClusterClassInTemplate: true, wantError: false, }, + { + name: "should add the cluster class form the same explicitly specified namespace to the template if cluster is not initialized", + clusterInitialized: false, + objs: []client.Object{}, + targetNamespace: "ns1", + clusterClassNamespace: "ns1", + clusterClassTemplateContent: clusterClassYAML("ns1", "dev"), + listVariablesOnly: false, + wantClusterClassInTemplate: true, + wantError: false, + }, { name: "should add the cluster class to the template if cluster is initialized and cluster class is not installed", clusterInitialized: true, @@ -231,7 +242,9 @@ func TestAddClusterClassIfMissing(t *testing.T) { g.Expect(err).To(HaveOccurred()) } else { if tt.wantClusterClassInTemplate { - if tt.clusterClassNamespace != "" { + if tt.clusterClassNamespace == tt.targetNamespace { + g.Expect(template.Objs()).To(ContainElement(MatchClusterClass("dev", tt.targetNamespace))) + } else if tt.clusterClassNamespace != "" { g.Expect(template.Objs()).To(ContainElement(MatchClusterClass("dev", tt.clusterClassNamespace))) g.Expect(template.Objs()).ToNot(ContainElement(MatchClusterClass("dev", tt.targetNamespace))) } else { diff --git a/cmd/clusterctl/cmd/util.go b/cmd/clusterctl/cmd/util.go index 01b65357fd44..7004a0006f16 100644 --- a/cmd/clusterctl/cmd/util.go +++ b/cmd/clusterctl/cmd/util.go @@ -111,6 +111,13 @@ func printVariablesOutput(template client.Template, options client.GetClusterTem } else if val, ok := os.LookupEnv("KUBERNETES_VERSION"); ok { variableMap[name] = ptr.To(val) } + case "CLUSTER_CLASS_NAMESPACE": + // Namespace name from the cmd flags or from the kubeconfig is used instead of template default. + if val, ok := os.LookupEnv("CLUSTER_CLASS_NAMESPACE"); ok { + variableMap[name] = ptr.To(val) + } else { + variableMap[name] = ptr.To("") + } } if variableMap[name] != nil { diff --git a/test/e2e/cluster_upgrade_runtimesdk_test.go b/test/e2e/cluster_upgrade_runtimesdk_test.go index e87247dac609..f5d3a18f6c75 100644 --- a/test/e2e/cluster_upgrade_runtimesdk_test.go +++ b/test/e2e/cluster_upgrade_runtimesdk_test.go @@ -82,7 +82,7 @@ var _ = Describe("When upgrading a workload cluster using ClusterClass in a diff framework.ValidateResourceVersionStable(ctx, proxy, namespace, clusterctlcluster.FilterClusterObjectsWithNameFilter(clusterName)) }, // "upgrades" is the same as the "topology" flavor but with an additional MachinePool. - Flavor: ptr.To("cross-ns-upgrades-runtimesdk"), + Flavor: ptr.To("upgrades-runtimesdk"), ClassNamespace: true, // The runtime extension gets deployed to the test-extension-system namespace and is exposed // by the test-extension-webhook-service. diff --git a/test/e2e/config/docker.yaml b/test/e2e/config/docker.yaml index e9d2dc2c0244..466b2fd187b3 100644 --- a/test/e2e/config/docker.yaml +++ b/test/e2e/config/docker.yaml @@ -347,8 +347,6 @@ providers: - sourcePath: "../data/infrastructure-docker/main/cluster-template-topology-autoscaler.yaml" - sourcePath: "../data/infrastructure-docker/main/cluster-template-topology.yaml" - sourcePath: "../data/infrastructure-docker/main/cluster-template-ignition.yaml" - - sourcePath: "../data/infrastructure-docker/main/cluster-template-cross-ns-topology.yaml" - - sourcePath: "../data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk.yaml" - sourcePath: "../data/infrastructure-docker/main/clusterclass-quick-start.yaml" - sourcePath: "../data/infrastructure-docker/main/clusterclass-quick-start-kcp-only.yaml" - sourcePath: "../data/infrastructure-docker/main/clusterclass-quick-start-runtimesdk.yaml" diff --git a/test/e2e/cross-ns-quick-start.go b/test/e2e/cross-ns-quick-start.go deleted file mode 100644 index ccd450aada24..000000000000 --- a/test/e2e/cross-ns-quick-start.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package e2e - -import ( - "context" - "fmt" - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - "k8s.io/utils/ptr" - - "sigs.k8s.io/cluster-api/test/framework" - "sigs.k8s.io/cluster-api/test/framework/clusterctl" - "sigs.k8s.io/cluster-api/util" -) - -// CrossNsSpecInput is the input for QuickStartSpec in separate namespaces. -type CrossNsSpecInput struct { - QuickStartSpecInput -} - -// CrossNsSpecQuickstart implements a spec that mimics the operation described in the Cluster API quick start, that is -// creating a workload cluster, but uses a cross-namespaced cluster class reference. -// This test is meant to provide a first, fast signal to detect regression; it is recommended to use it as a PR blocker test. -// NOTE: This test works with Clusters with and without ClusterClass. -func CrossNsSpecQuickstart(ctx context.Context, inputGetter func() CrossNsSpecInput) { - var ( - specName = "quick-start" - input CrossNsSpecInput - namespace *corev1.Namespace - clusterNamespace *corev1.Namespace - cancelWatches context.CancelFunc - cancelWatchesCluster context.CancelFunc - clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult - ) - - BeforeEach(func() { - Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName) - input = inputGetter() - Expect(input.E2EConfig).ToNot(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName) - Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName) - Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName) - Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName) - - Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion)) - - // Setup a Namespace where to host objects for this spec and create a watcher for the namespace events. - namespace, cancelWatches = framework.SetupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, input.PostNamespaceCreated) - clusterNamespace, cancelWatchesCluster = framework.SetupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, input.PostNamespaceCreated) - clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult) - }) - - It("Should create a workload cluster in a separate namespace", func() { - infrastructureProvider := clusterctl.DefaultInfrastructureProvider - if input.InfrastructureProvider != nil { - infrastructureProvider = *input.InfrastructureProvider - } - - flavor := clusterctl.DefaultFlavor - if input.Flavor != nil { - flavor = *input.Flavor - } - - controlPlaneMachineCount := ptr.To[int64](1) - if input.ControlPlaneMachineCount != nil { - controlPlaneMachineCount = input.ControlPlaneMachineCount - } - - workerMachineCount := ptr.To[int64](1) - if input.WorkerMachineCount != nil { - workerMachineCount = input.WorkerMachineCount - } - - clusterName := fmt.Sprintf("%s-%s", specName, util.RandomString(6)) - if input.ClusterName != nil { - clusterName = *input.ClusterName - } - - By("Creating a cluster referencing a clusterClass from another namespace") - clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{ - ClusterProxy: input.BootstrapClusterProxy, - ConfigCluster: clusterctl.ConfigClusterInput{ - LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()), - ClusterctlConfigPath: input.ClusterctlConfigPath, - ClusterctlVariables: map[string]string{ - "CLUSTER_CLASS_NAMESPACE": namespace.Name, - }, - KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), - InfrastructureProvider: infrastructureProvider, - Flavor: flavor, - Namespace: clusterNamespace.Name, - ClusterName: clusterName, - KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion), - ControlPlaneMachineCount: controlPlaneMachineCount, - WorkerMachineCount: workerMachineCount, - }, - ControlPlaneWaiters: input.ControlPlaneWaiters, - WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"), - WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"), - WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"), - PostMachinesProvisioned: func() { - if input.PostMachinesProvisioned != nil { - input.PostMachinesProvisioned(input.BootstrapClusterProxy, clusterNamespace.Name, clusterName) - } - }, - }, clusterResources) - - By("PASSED!") - }) - - AfterEach(func() { - // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself. - framework.DumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, clusterNamespace, cancelWatchesCluster, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup) - framework.DeleteNamespace(ctx, framework.DeleteNamespaceInput{ - Deleter: input.BootstrapClusterProxy.GetClient(), - Name: namespace.Name, - }) - cancelWatches() - }) -} diff --git a/test/e2e/data/infrastructure-docker/main/bases/cluster-with-topology.yaml b/test/e2e/data/infrastructure-docker/main/bases/cluster-with-topology.yaml index 1fa907f3aab9..8e60cac08e51 100644 --- a/test/e2e/data/infrastructure-docker/main/bases/cluster-with-topology.yaml +++ b/test/e2e/data/infrastructure-docker/main/bases/cluster-with-topology.yaml @@ -14,6 +14,7 @@ spec: serviceDomain: '${DOCKER_SERVICE_DOMAIN}' topology: class: "quick-start" + classNamespace: '${CLUSTER_CLASS_NAMESPACE:-""}' version: "${KUBERNETES_VERSION}" controlPlane: metadata: diff --git a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/cluster.yaml b/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/cluster.yaml deleted file mode 100644 index 066f8b174900..000000000000 --- a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/cluster.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: cluster.x-k8s.io/v1beta1 -kind: Cluster -metadata: - name: '${CLUSTER_NAME}' -spec: - topology: - classNamespace: '${CLUSTER_CLASS_NAMESPACE}' diff --git a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/kustomization.yaml b/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/kustomization.yaml deleted file mode 100644 index 621b9b58fdd1..000000000000 --- a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-topology/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -resources: - - ../cluster-template-topology-no-workers - -patches: -- path: cluster.yaml \ No newline at end of file diff --git a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/cluster.yaml b/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/cluster.yaml deleted file mode 100644 index 066f8b174900..000000000000 --- a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/cluster.yaml +++ /dev/null @@ -1,8 +0,0 @@ ---- -apiVersion: cluster.x-k8s.io/v1beta1 -kind: Cluster -metadata: - name: '${CLUSTER_NAME}' -spec: - topology: - classNamespace: '${CLUSTER_CLASS_NAMESPACE}' diff --git a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/kustomization.yaml b/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/kustomization.yaml deleted file mode 100644 index df18913aca6e..000000000000 --- a/test/e2e/data/infrastructure-docker/main/cluster-template-cross-ns-upgrades-runtimesdk/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -resources: - - ../cluster-template-upgrades-runtimesdk - -patches: -- path: cluster.yaml \ No newline at end of file diff --git a/test/e2e/data/infrastructure-docker/main/cluster-template-upgrades-runtimesdk/cluster-runtimesdk.yaml b/test/e2e/data/infrastructure-docker/main/cluster-template-upgrades-runtimesdk/cluster-runtimesdk.yaml index 004350219aa6..c27d9bb62220 100644 --- a/test/e2e/data/infrastructure-docker/main/cluster-template-upgrades-runtimesdk/cluster-runtimesdk.yaml +++ b/test/e2e/data/infrastructure-docker/main/cluster-template-upgrades-runtimesdk/cluster-runtimesdk.yaml @@ -14,6 +14,7 @@ spec: serviceDomain: '${DOCKER_SERVICE_DOMAIN}' topology: class: "quick-start-runtimesdk" + classNamespace: '${CLUSTER_CLASS_NAMESPACE:-""}' version: "${KUBERNETES_VERSION}" controlPlane: metadata: {} diff --git a/test/e2e/quick_start.go b/test/e2e/quick_start.go index 14d17c5d80ce..4998060608e1 100644 --- a/test/e2e/quick_start.go +++ b/test/e2e/quick_start.go @@ -44,6 +44,9 @@ type QuickStartSpecInput struct { // If not set, a random one will be generated. ClusterName *string + // ClassNamespace is an optional class namespace reference, configuring cross-namespace cluster class reference + ClassNamespace *string + // InfrastructureProvider allows to specify the infrastructure provider to be used when looking for // cluster templates. // If not set, clusterctl will look at the infrastructure provider installed in the management cluster; @@ -81,11 +84,12 @@ type QuickStartSpecInput struct { // NOTE: This test works with Clusters with and without ClusterClass. func QuickStartSpec(ctx context.Context, inputGetter func() QuickStartSpecInput) { var ( - specName = "quick-start" - input QuickStartSpecInput - namespace *corev1.Namespace - cancelWatches context.CancelFunc - clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult + specName = "quick-start" + input QuickStartSpecInput + namespace *corev1.Namespace + clusterClassNamespace *corev1.Namespace + cancelWatches context.CancelFunc + clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult ) BeforeEach(func() { @@ -100,6 +104,12 @@ func QuickStartSpec(ctx context.Context, inputGetter func() QuickStartSpecInput) // Setup a Namespace where to host objects for this spec and create a watcher for the namespace events. namespace, cancelWatches = framework.SetupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, input.PostNamespaceCreated) + + if input.ClassNamespace != nil { + clusterClassNamespace = framework.CreateNamespace(ctx, framework.CreateNamespaceInput{Creator: input.BootstrapClusterProxy.GetClient(), Name: *input.ClassNamespace}, "40s", "10s") + Expect(clusterClassNamespace).ToNot(BeNil(), "Failed to create namespace %q", *input.ClassNamespace) + } + clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult) }) @@ -130,11 +140,19 @@ func QuickStartSpec(ctx context.Context, inputGetter func() QuickStartSpecInput) if input.ClusterName != nil { clusterName = *input.ClusterName } + + variables := map[string]string{} + if input.ClassNamespace != nil { + variables["CLUSTER_CLASS_NAMESPACE"] = *input.ClassNamespace + } + + By("Creating a cluster referencing a clusterClass from another namespace") clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{ ClusterProxy: input.BootstrapClusterProxy, ConfigCluster: clusterctl.ConfigClusterInput{ LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()), ClusterctlConfigPath: input.ClusterctlConfigPath, + ClusterctlVariables: variables, KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), InfrastructureProvider: infrastructureProvider, Flavor: flavor, @@ -154,11 +172,18 @@ func QuickStartSpec(ctx context.Context, inputGetter func() QuickStartSpecInput) } }, }, clusterResources) + By("PASSED!") }) AfterEach(func() { // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself. framework.DumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup) + if input.ClassNamespace != nil { + framework.DeleteNamespace(ctx, framework.DeleteNamespaceInput{ + Deleter: input.BootstrapClusterProxy.GetClient(), + Name: clusterClassNamespace.Name, + }) + } }) } diff --git a/test/e2e/quick_start_test.go b/test/e2e/quick_start_test.go index 9d859929a85a..fb6f77ce0d35 100644 --- a/test/e2e/quick_start_test.go +++ b/test/e2e/quick_start_test.go @@ -20,6 +20,8 @@ limitations under the License. package e2e import ( + "fmt" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/utils/ptr" @@ -27,6 +29,7 @@ import ( clusterctlcluster "sigs.k8s.io/cluster-api/cmd/clusterctl/client/cluster" "sigs.k8s.io/cluster-api/test/framework" "sigs.k8s.io/cluster-api/test/framework/kubetest" + "sigs.k8s.io/cluster-api/util" ) var _ = Describe("When following the Cluster API quick-start", func() { @@ -125,17 +128,16 @@ var _ = Describe("When following the Cluster API quick-start with ClusterClass [ }) var _ = Describe("When following the Cluster API quick-start with a cross-ns referenced ClusterClass [PR-Blocking] [ClusterClass]", func() { - CrossNsSpecQuickstart(ctx, func() CrossNsSpecInput { - return CrossNsSpecInput{ - QuickStartSpecInput: QuickStartSpecInput{ - E2EConfig: e2eConfig, - ClusterctlConfigPath: clusterctlConfigPath, - BootstrapClusterProxy: bootstrapClusterProxy, - ArtifactFolder: artifactFolder, - SkipCleanup: skipCleanup, - Flavor: ptr.To("cross-ns-topology"), - InfrastructureProvider: ptr.To("docker"), - }, + QuickStartSpec(ctx, func() QuickStartSpecInput { + return QuickStartSpecInput{ + E2EConfig: e2eConfig, + ClusterctlConfigPath: clusterctlConfigPath, + BootstrapClusterProxy: bootstrapClusterProxy, + ArtifactFolder: artifactFolder, + SkipCleanup: skipCleanup, + Flavor: ptr.To("topology"), + InfrastructureProvider: ptr.To("docker"), + ClassNamespace: ptr.To(fmt.Sprintf("quick-start-%s", util.RandomString(6))), } }) }) diff --git a/test/framework/clusterctl/clusterctl_helpers.go b/test/framework/clusterctl/clusterctl_helpers.go index 9bf13099666b..110f9fd1fb1e 100644 --- a/test/framework/clusterctl/clusterctl_helpers.go +++ b/test/framework/clusterctl/clusterctl_helpers.go @@ -448,7 +448,7 @@ func ApplyCustomClusterTemplateAndWait(ctx context.Context, input ApplyCustomClu if result.Cluster.Spec.Topology != nil { result.ClusterClass = framework.GetClusterClassByName(ctx, framework.GetClusterClassByNameInput{ Getter: input.ClusterProxy.GetClient(), - Namespace: result.Cluster.GetInfrastructureNamespace(), + Namespace: result.Cluster.GetClassKey().Namespace, Name: result.Cluster.Spec.Topology.Class, }) }