sigs.k8s.io/cluster-api-provider-azure@v1.17.0/test/e2e/conformance_test.go (about) 1 //go:build e2e 2 // +build e2e 3 4 /* 5 Copyright 2020 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 "strconv" 27 "strings" 28 29 "github.com/blang/semver" 30 . "github.com/onsi/ginkgo/v2" 31 . "github.com/onsi/gomega" 32 "github.com/onsi/gomega/gmeasure" 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/types" 36 "sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/node" 37 capi_e2e "sigs.k8s.io/cluster-api/test/e2e" 38 "sigs.k8s.io/cluster-api/test/framework/clusterctl" 39 "sigs.k8s.io/cluster-api/test/framework/kubetest" 40 "sigs.k8s.io/cluster-api/util" 41 ) 42 43 var _ = Describe("Conformance Tests", func() { 44 var ( 45 ctx = context.TODO() 46 cancelWatches context.CancelFunc 47 result *clusterctl.ApplyClusterTemplateAndWaitResult 48 clusterName string 49 namespace *corev1.Namespace 50 specName = "conformance-tests" 51 repoList = "" 52 ) 53 54 BeforeEach(func() { 55 Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName) 56 Expect(bootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. BootstrapClusterProxy can't be nil") 57 Expect(kubetestConfigFilePath).NotTo(BeNil(), "Invalid argument. kubetestConfigFilePath can't be nil") 58 Expect(e2eConfig).NotTo(BeNil(), "Invalid argument. e2eConfig can't be nil when calling %s spec", specName) 59 Expect(clusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. clusterctlConfigPath must be an existing file when calling %s spec", specName) 60 61 Expect(e2eConfig.Variables).To(HaveKey(capi_e2e.KubernetesVersion)) 62 63 clusterName = os.Getenv("CLUSTER_NAME") 64 if clusterName == "" { 65 clusterName = fmt.Sprintf("capz-conf-%s", util.RandomString(6)) 66 } 67 fmt.Fprintf(GinkgoWriter, "INFO: Cluster name is %s\n", clusterName) 68 69 // Setup a Namespace where to host objects for this spec and create a watcher for the namespace events. 70 var err error 71 namespace, cancelWatches, err = setupSpecNamespace(ctx, clusterName, bootstrapClusterProxy, artifactFolder) 72 Expect(err).NotTo(HaveOccurred()) 73 74 Expect(os.Setenv(AzureResourceGroup, clusterName)).To(Succeed()) 75 Expect(os.Setenv(AzureVNetName, fmt.Sprintf("%s-vnet", clusterName))).To(Succeed()) 76 77 result = new(clusterctl.ApplyClusterTemplateAndWaitResult) 78 79 identityName := e2eConfig.GetVariable(ClusterIdentityName) 80 Expect(os.Setenv(ClusterIdentityName, identityName)).To(Succeed()) 81 Expect(os.Setenv(ClusterIdentityNamespace, namespace.Name)).To(Succeed()) 82 }) 83 84 It(specName, func() { 85 experiment := gmeasure.NewExperiment(specName) 86 AddReportEntry(experiment.Name, experiment) 87 88 var err error 89 90 kubernetesVersion := e2eConfig.GetVariable(capi_e2e.KubernetesVersion) 91 flavor := e2eConfig.GetVariable("CONFORMANCE_FLAVOR") 92 93 // clusters with CI artifacts or PR artifacts are based on a known CI version 94 // PR artifacts will replace the CI artifacts during kubeadm init 95 if useCIArtifacts || usePRArtifacts { 96 kubernetesVersion, err = resolveCIVersion(kubernetesVersion) 97 Expect(err).NotTo(HaveOccurred()) 98 Expect(os.Setenv("CI_VERSION", kubernetesVersion)).To(Succeed()) 99 Expect(os.Setenv("CLOUD_PROVIDER_AZURE_LABEL", "azure-ci")).To(Succeed()) 100 } 101 102 if flavor == "" { 103 if useCIArtifacts { 104 flavor = "conformance-ci-artifacts" 105 } else if usePRArtifacts { 106 flavor = "conformance-presubmit-artifacts" 107 } 108 // use the ipv6 flavor if ipv6 IP family is specified. 109 if e2eConfig.GetVariable(capi_e2e.IPFamily) == "IPv6" { 110 flavor += "-ipv6" 111 kubetestConfigFilePath = strings.Replace(kubetestConfigFilePath, ".yaml", "-ipv6.yaml", 1) 112 } else if e2eConfig.GetVariable(capi_e2e.IPFamily) == "dual" { 113 flavor += "-dual-stack" 114 kubetestConfigFilePath = strings.Replace(kubetestConfigFilePath, ".yaml", "-dual-stack.yaml", 1) 115 } 116 } 117 118 // Starting with Kubernetes v1.25, the kubetest config file needs to be compatible with Ginkgo V2. 119 v125 := semver.MustParse("1.25.0-alpha.0.0") 120 v, err := semver.ParseTolerant(kubernetesVersion) 121 Expect(err).NotTo(HaveOccurred()) 122 if v.GTE(v125) { 123 // Use the Ginkgo V2 config file. 124 kubetestConfigFilePath = getGinkgoV2ConfigFilePath(kubetestConfigFilePath) 125 } 126 127 // Set the worker counts for conformance tests that use Windows 128 // This is a work around until we can update cluster-api test framework to be aware of windows node counts. 129 conformanceNodeCount := e2eConfig.GetVariable("CONFORMANCE_WORKER_MACHINE_COUNT") 130 numOfConformanceNodes, err := strconv.ParseInt(conformanceNodeCount, 10, 64) 131 Expect(err).NotTo(HaveOccurred()) 132 133 linuxWorkerMachineCount := numOfConformanceNodes 134 if isWindows(kubetestConfigFilePath) { 135 Expect(os.Setenv("WINDOWS_WORKER_MACHINE_COUNT", conformanceNodeCount)).To(Succeed()) 136 137 // Conformance for windows doesn't require any linux worker machines. 138 // The templates use WORKER_MACHINE_COUNT for linux machines for backwards compatibility so clear it 139 linuxWorkerMachineCount = 0 140 } 141 142 controlPlaneMachineCount, err := strconv.ParseInt(e2eConfig.GetVariable("CONFORMANCE_CONTROL_PLANE_MACHINE_COUNT"), 10, 64) 143 Expect(err).NotTo(HaveOccurred()) 144 145 stopwatch := experiment.NewStopwatch() 146 clusterctl.ApplyClusterTemplateAndWait(ctx, createApplyClusterTemplateInput( 147 specName, 148 withFlavor(flavor), 149 withNamespace(namespace.Name), 150 withClusterName(clusterName), 151 withKubernetesVersion(kubernetesVersion), 152 withControlPlaneMachineCount(controlPlaneMachineCount), 153 withWorkerMachineCount(linuxWorkerMachineCount), 154 withControlPlaneWaiters(clusterctl.ControlPlaneWaiters{ 155 WaitForControlPlaneInitialized: EnsureControlPlaneInitializedNoAddons, 156 }), 157 ), result) 158 stopwatch.Record("cluster creation") 159 160 workloadProxy := bootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, clusterName) 161 162 if isWindows(kubetestConfigFilePath) { 163 // Windows requires a taint on control nodes since not all conformance tests have ability to run 164 options := metav1.ListOptions{ 165 LabelSelector: "kubernetes.io/os=linux", 166 } 167 168 noScheduleTaint := &corev1.Taint{ 169 Key: "node-role.kubernetes.io/control-plane", 170 Value: "", 171 Effect: "NoSchedule", 172 } 173 174 if v, err := semver.ParseTolerant(kubernetesVersion); err == nil { 175 if v.LT(semver.MustParse("1.24.0-alpha.0.0")) { 176 noScheduleTaint = &corev1.Taint{ 177 Key: "node-role.kubernetes.io/master", 178 Value: "", 179 Effect: "NoSchedule", 180 } 181 } 182 } 183 184 err = node.TaintNode(workloadProxy.GetClientSet(), options, noScheduleTaint) 185 Expect(err).NotTo(HaveOccurred()) 186 187 // Windows requires a repo-list when running e2e tests against K8s versions prior to v1.25 188 // because some test images published to the k8s gcr do not have Windows flavors. 189 repoList, err = resolveKubetestRepoListPath(kubernetesVersion, kubetestRepoListPath) 190 Expect(err).NotTo(HaveOccurred()) 191 fmt.Fprintf(GinkgoWriter, "INFO: Using repo-list '%s' for version '%s'\n", repoList, kubernetesVersion) 192 } 193 194 ginkgoNodes, err := strconv.Atoi(e2eConfig.GetVariable("CONFORMANCE_NODES")) 195 Expect(err).NotTo(HaveOccurred()) 196 197 stopwatch.Reset() 198 err = kubetest.Run(context.Background(), 199 kubetest.RunInput{ 200 ClusterProxy: workloadProxy, 201 NumberOfNodes: int(numOfConformanceNodes), 202 ConfigFilePath: kubetestConfigFilePath, 203 KubeTestRepoListPath: repoList, 204 ConformanceImage: e2eConfig.GetVariable("CONFORMANCE_IMAGE"), 205 GinkgoNodes: ginkgoNodes, 206 }, 207 ) 208 Expect(err).NotTo(HaveOccurred()) 209 stopwatch.Record("conformance suite") 210 }) 211 212 AfterEach(func() { 213 if result.Cluster == nil { 214 // this means the cluster failed to come up. We make an attempt to find the cluster to be able to fetch logs for the failed bootstrapping. 215 _ = bootstrapClusterProxy.GetClient().Get(ctx, types.NamespacedName{Name: clusterName, Namespace: namespace.Name}, result.Cluster) 216 } 217 218 CheckTestBeforeCleanup() 219 220 cleanInput := cleanupInput{ 221 SpecName: specName, 222 Cluster: result.Cluster, 223 ClusterProxy: bootstrapClusterProxy, 224 Namespace: namespace, 225 CancelWatches: cancelWatches, 226 IntervalsGetter: e2eConfig.GetIntervals, 227 SkipCleanup: skipCleanup, 228 SkipLogCollection: skipLogCollection, 229 ArtifactFolder: artifactFolder, 230 } 231 // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself. 232 dumpSpecResourcesAndCleanup(ctx, cleanInput) 233 234 Expect(os.Unsetenv(AzureResourceGroup)).To(Succeed()) 235 Expect(os.Unsetenv(AzureVNetName)).To(Succeed()) 236 }) 237 238 }) 239 240 func isWindows(kubetestConfigFilePath string) bool { 241 return strings.Contains(kubetestConfigFilePath, "windows") 242 } 243 244 func getGinkgoV2ConfigFilePath(kubetestConfigFilePath string) string { 245 if !strings.HasSuffix(kubetestConfigFilePath, "-ginkgo-v2.yaml") { 246 return strings.Replace(kubetestConfigFilePath, ".yaml", "-ginkgo-v2.yaml", 1) 247 } 248 return kubetestConfigFilePath 249 }