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  }