sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/azure_selfhosted.go (about) 1 //go:build e2e 2 // +build e2e 3 4 /* 5 Copyright 2021 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package e2e 21 22 import ( 23 "context" 24 "fmt" 25 "os" 26 "path/filepath" 27 28 . "github.com/onsi/ginkgo/v2" 29 . "github.com/onsi/gomega" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 33 clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" 34 capi_e2e "sigs.k8s.io/cluster-api/test/e2e" 35 "sigs.k8s.io/cluster-api/test/framework" 36 "sigs.k8s.io/cluster-api/test/framework/clusterctl" 37 "sigs.k8s.io/cluster-api/util" 38 "sigs.k8s.io/controller-runtime/pkg/client" 39 ) 40 41 // SelfHostedSpecInput is the input for SelfHostedSpec. 42 type SelfHostedSpecInput struct { 43 E2EConfig *clusterctl.E2EConfig 44 ClusterctlConfigPath string 45 BootstrapClusterProxy framework.ClusterProxy 46 ArtifactFolder string 47 SkipCleanup bool 48 ControlPlaneWaiters clusterctl.ControlPlaneWaiters 49 } 50 51 // SelfHostedSpec implements a test that verifies Cluster API creating a cluster, pivoting to a self-hosted cluster. 52 func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput) { 53 var ( 54 specName = "self-hosted" 55 input SelfHostedSpecInput 56 namespace *corev1.Namespace 57 cancelWatches context.CancelFunc 58 clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult 59 60 selfHostedClusterProxy framework.ClusterProxy 61 selfHostedNamespace *corev1.Namespace 62 selfHostedCancelWatches context.CancelFunc 63 selfHostedCluster *clusterv1.Cluster 64 ) 65 66 BeforeEach(func() { 67 Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName) 68 input = inputGetter() 69 Expect(input.E2EConfig).NotTo(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName) 70 Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName) 71 Expect(input.BootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName) 72 Expect(os.MkdirAll(input.ArtifactFolder, 0o750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName) 73 Expect(input.E2EConfig.Variables).To(HaveKey(capi_e2e.KubernetesVersion)) 74 75 // Setup a Namespace where to host objects for this spec and create a watcher for the namespace events. 76 var err error 77 namespace, cancelWatches, err = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder) 78 Expect(err).NotTo(HaveOccurred()) 79 clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult) 80 81 spClientSecret := os.Getenv(AzureClientSecret) 82 secret := &corev1.Secret{ 83 ObjectMeta: metav1.ObjectMeta{ 84 Name: "cluster-identity-secret", 85 Namespace: namespace.Name, 86 Labels: map[string]string{ 87 clusterctlv1.ClusterctlMoveHierarchyLabel: "true", 88 }, 89 }, 90 Type: corev1.SecretTypeOpaque, 91 Data: map[string][]byte{"clientSecret": []byte(spClientSecret)}, 92 } 93 err = bootstrapClusterProxy.GetClient().Create(ctx, secret) 94 Expect(err).NotTo(HaveOccurred()) 95 96 identityName := input.E2EConfig.GetVariable(ClusterIdentityName) 97 Expect(os.Setenv(ClusterIdentityName, identityName)).To(Succeed()) 98 Expect(os.Setenv(ClusterIdentityNamespace, namespace.Name)).To(Succeed()) 99 Expect(os.Setenv(ClusterIdentitySecretName, "cluster-identity-secret")).To(Succeed()) 100 Expect(os.Setenv(ClusterIdentitySecretNamespace, namespace.Name)).To(Succeed()) 101 }) 102 103 // Management clusters do not support Windows nodes because of cert manager 104 // We are using the capi specs located in test/e2e/data/infrastructure-azure/v1beta1 that only have linux nodes 105 // to act as the management cluster until Windows nodes are supported for management nodes 106 // Tracking support for cert manager: https://github.com/jetstack/cert-manager/issues/3606 107 It("Should pivot the bootstrap cluster to a self-hosted cluster", func() { 108 By("Creating a workload cluster") 109 clusterctl.ApplyClusterTemplateAndWait(ctx, createApplyClusterTemplateInput( 110 specName, 111 withFlavor("management"), 112 withNamespace(namespace.Name), 113 withClusterName(fmt.Sprintf("%s-%s", specName, util.RandomString(6))), 114 withControlPlaneMachineCount(1), 115 withWorkerMachineCount(1), 116 withControlPlaneWaiters(input.ControlPlaneWaiters), 117 ), clusterResources) 118 119 By("Turning the workload cluster into a management cluster") 120 cluster := clusterResources.Cluster 121 // Get a ClusterBroker so we can interact with the workload cluster 122 selfHostedClusterProxy = input.BootstrapClusterProxy.GetWorkloadCluster(ctx, cluster.Namespace, cluster.Name) 123 124 Byf("Creating a namespace for hosting the %s test spec", specName) 125 selfHostedNamespace, selfHostedCancelWatches = framework.CreateNamespaceAndWatchEvents(ctx, framework.CreateNamespaceAndWatchEventsInput{ 126 Creator: selfHostedClusterProxy.GetClient(), 127 ClientSet: selfHostedClusterProxy.GetClientSet(), 128 Name: namespace.Name, 129 LogFolder: filepath.Join(input.ArtifactFolder, "clusters", "bootstrap"), 130 }) 131 132 By("Initializing the workload cluster") 133 clusterctl.InitManagementClusterAndWatchControllerLogs(ctx, clusterctl.InitManagementClusterAndWatchControllerLogsInput{ 134 ClusterProxy: selfHostedClusterProxy, 135 ClusterctlConfigPath: input.ClusterctlConfigPath, 136 InfrastructureProviders: input.E2EConfig.InfrastructureProviders(), 137 AddonProviders: input.E2EConfig.AddonProviders(), 138 LogFolder: filepath.Join(input.ArtifactFolder, "clusters", cluster.Name), 139 }, input.E2EConfig.GetIntervals(specName, "wait-controllers")...) 140 141 By("Ensure API servers are stable before doing move") 142 // Nb. This check was introduced to prevent doing move to self-hosted in an aggressive way and thus avoid flakes. 143 // More specifically, we were observing the test failing to get objects from the API server during move, so we 144 // are now testing the API servers are stable before starting move. 145 Consistently(func() error { 146 ns := &corev1.Namespace{} 147 return input.BootstrapClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns) 148 }, "5s", "100ms").Should(BeNil(), "Failed to assert bootstrap API server stability") 149 Consistently(func() error { 150 ns := &corev1.Namespace{} 151 return selfHostedClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns) 152 }, "5s", "100ms").Should(BeNil(), "Failed to assert self-hosted API server stability") 153 154 By("Moving the cluster to self hosted") 155 clusterctl.Move(ctx, clusterctl.MoveInput{ 156 LogFolder: filepath.Join(input.ArtifactFolder, "clusters", "bootstrap"), 157 ClusterctlConfigPath: input.ClusterctlConfigPath, 158 FromKubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), 159 ToKubeconfigPath: selfHostedClusterProxy.GetKubeconfigPath(), 160 Namespace: namespace.Name, 161 }) 162 163 Log("Waiting for the cluster to be reconciled after moving to self hosted") 164 selfHostedCluster = framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{ 165 Getter: selfHostedClusterProxy.GetClient(), 166 Namespace: selfHostedNamespace.Name, 167 Name: cluster.Name, 168 }, input.E2EConfig.GetIntervals(specName, "wait-cluster")...) 169 170 controlPlane := framework.GetKubeadmControlPlaneByCluster(ctx, framework.GetKubeadmControlPlaneByClusterInput{ 171 Lister: selfHostedClusterProxy.GetClient(), 172 ClusterName: selfHostedCluster.Name, 173 Namespace: selfHostedCluster.Namespace, 174 }) 175 Expect(controlPlane).NotTo(BeNil()) 176 177 By("PASSED!") 178 }) 179 180 AfterEach(func() { 181 if input.SkipCleanup { 182 return 183 } 184 if selfHostedNamespace != nil { 185 // Dump all Cluster API related resources to artifacts before pivoting back. 186 framework.DumpAllResources(ctx, framework.DumpAllResourcesInput{ 187 Lister: selfHostedClusterProxy.GetClient(), 188 Namespace: namespace.Name, 189 LogPath: filepath.Join(input.ArtifactFolder, "clusters", clusterResources.Cluster.Name, "resources"), 190 }) 191 } 192 if selfHostedCluster != nil { 193 By("Ensure API servers are stable before doing move") 194 // Nb. This check was introduced to prevent doing move back to bootstrap in an aggressive way and thus avoid flakes. 195 // More specifically, we were observing the test failing to get objects from the API server during move, so we 196 // are now testing the API servers are stable before starting move. 197 Consistently(func() error { 198 ns := &corev1.Namespace{} 199 return input.BootstrapClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns) 200 }, "5s", "100ms").Should(BeNil(), "Failed to assert bootstrap API server stability") 201 Consistently(func() error { 202 ns := &corev1.Namespace{} 203 return selfHostedClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns) 204 }, "5s", "100ms").Should(BeNil(), "Failed to assert self-hosted API server stability") 205 206 By("Moving the cluster back to bootstrap") 207 clusterctl.Move(ctx, clusterctl.MoveInput{ 208 LogFolder: filepath.Join(input.ArtifactFolder, "clusters", clusterResources.Cluster.Name), 209 ClusterctlConfigPath: input.ClusterctlConfigPath, 210 FromKubeconfigPath: selfHostedClusterProxy.GetKubeconfigPath(), 211 ToKubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(), 212 Namespace: selfHostedNamespace.Name, 213 }) 214 215 Log("Waiting for the cluster to be reconciled after moving back to booststrap") 216 clusterResources.Cluster = framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{ 217 Getter: input.BootstrapClusterProxy.GetClient(), 218 Namespace: namespace.Name, 219 Name: clusterResources.Cluster.Name, 220 }, input.E2EConfig.GetIntervals(specName, "wait-cluster")...) 221 } 222 if selfHostedCancelWatches != nil { 223 selfHostedCancelWatches() 224 } 225 226 cleanInput := cleanupInput{ 227 SpecName: specName, 228 Cluster: clusterResources.Cluster, 229 ClusterProxy: input.BootstrapClusterProxy, 230 Namespace: namespace, 231 CancelWatches: cancelWatches, 232 IntervalsGetter: input.E2EConfig.GetIntervals, 233 SkipCleanup: input.SkipCleanup, 234 SkipLogCollection: skipLogCollection, 235 ArtifactFolder: input.ArtifactFolder, 236 } 237 // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself. 238 dumpSpecResourcesAndCleanup(ctx, cleanInput) 239 }) 240 }