sigs.k8s.io/cluster-api-provider-azure@v1.14.3/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  		spClientSecret := os.Getenv(AzureClientSecret)
    80  		secret := &corev1.Secret{
    81  			ObjectMeta: metav1.ObjectMeta{
    82  				Name:      "cluster-identity-secret",
    83  				Namespace: namespace.Name,
    84  			},
    85  			Type: corev1.SecretTypeOpaque,
    86  			Data: map[string][]byte{"clientSecret": []byte(spClientSecret)},
    87  		}
    88  		err = bootstrapClusterProxy.GetClient().Create(ctx, secret)
    89  		Expect(err).NotTo(HaveOccurred())
    90  
    91  		identityName := e2eConfig.GetVariable(ClusterIdentityName)
    92  		Expect(os.Setenv(ClusterIdentityName, identityName)).To(Succeed())
    93  		Expect(os.Setenv(ClusterIdentityNamespace, namespace.Name)).To(Succeed())
    94  		Expect(os.Setenv(ClusterIdentitySecretName, "cluster-identity-secret")).To(Succeed())
    95  		Expect(os.Setenv(ClusterIdentitySecretNamespace, namespace.Name)).To(Succeed())
    96  	})
    97  
    98  	It(specName, func() {
    99  		experiment := gmeasure.NewExperiment(specName)
   100  		AddReportEntry(experiment.Name, experiment)
   101  
   102  		var err error
   103  
   104  		kubernetesVersion := e2eConfig.GetVariable(capi_e2e.KubernetesVersion)
   105  		flavor := e2eConfig.GetVariable("CONFORMANCE_FLAVOR")
   106  
   107  		// clusters with CI artifacts or PR artifacts are based on a known CI version
   108  		// PR artifacts will replace the CI artifacts during kubeadm init
   109  		if useCIArtifacts || usePRArtifacts {
   110  			kubernetesVersion, err = resolveCIVersion(kubernetesVersion)
   111  			Expect(err).NotTo(HaveOccurred())
   112  			Expect(os.Setenv("CI_VERSION", kubernetesVersion)).To(Succeed())
   113  			Expect(os.Setenv("CLOUD_PROVIDER_AZURE_LABEL", "azure-ci")).To(Succeed())
   114  
   115  			if useCIArtifacts {
   116  				flavor = "conformance-ci-artifacts"
   117  			} else if usePRArtifacts {
   118  				flavor = "conformance-presubmit-artifacts"
   119  			}
   120  		}
   121  
   122  		// use the ipv6 flavor if ipv6 IP family is specified.
   123  		if e2eConfig.GetVariable(capi_e2e.IPFamily) == "IPv6" {
   124  			flavor += "-ipv6"
   125  			kubetestConfigFilePath = strings.Replace(kubetestConfigFilePath, ".yaml", "-ipv6.yaml", 1)
   126  		} else if e2eConfig.GetVariable(capi_e2e.IPFamily) == "dual" {
   127  			flavor += "-dual-stack"
   128  			kubetestConfigFilePath = strings.Replace(kubetestConfigFilePath, ".yaml", "-dual-stack.yaml", 1)
   129  		}
   130  
   131  		// Starting with Kubernetes v1.25, the kubetest config file needs to be compatible with Ginkgo V2.
   132  		v125 := semver.MustParse("1.25.0-alpha.0.0")
   133  		v, err := semver.ParseTolerant(kubernetesVersion)
   134  		Expect(err).NotTo(HaveOccurred())
   135  		if v.GTE(v125) {
   136  			// Use the Ginkgo V2 config file.
   137  			kubetestConfigFilePath = getGinkgoV2ConfigFilePath(kubetestConfigFilePath)
   138  		}
   139  
   140  		// Set the worker counts for conformance tests that use Windows
   141  		// This is a work around until we can update cluster-api test framework to be aware of windows node counts.
   142  		conformanceNodeCount := e2eConfig.GetVariable("CONFORMANCE_WORKER_MACHINE_COUNT")
   143  		numOfConformanceNodes, err := strconv.ParseInt(conformanceNodeCount, 10, 64)
   144  		Expect(err).NotTo(HaveOccurred())
   145  
   146  		linuxWorkerMachineCount := numOfConformanceNodes
   147  		if isWindows(kubetestConfigFilePath) {
   148  			Expect(os.Setenv("WINDOWS_WORKER_MACHINE_COUNT", conformanceNodeCount)).To(Succeed())
   149  
   150  			// Conformance for windows doesn't require any linux worker machines.
   151  			// The templates use WORKER_MACHINE_COUNT for linux machines for backwards compatibility so clear it
   152  			linuxWorkerMachineCount = 0
   153  		}
   154  
   155  		controlPlaneMachineCount, err := strconv.ParseInt(e2eConfig.GetVariable("CONFORMANCE_CONTROL_PLANE_MACHINE_COUNT"), 10, 64)
   156  		Expect(err).NotTo(HaveOccurred())
   157  
   158  		stopwatch := experiment.NewStopwatch()
   159  		clusterctl.ApplyClusterTemplateAndWait(ctx, createApplyClusterTemplateInput(
   160  			specName,
   161  			withFlavor(flavor),
   162  			withNamespace(namespace.Name),
   163  			withClusterName(clusterName),
   164  			withKubernetesVersion(kubernetesVersion),
   165  			withControlPlaneMachineCount(controlPlaneMachineCount),
   166  			withWorkerMachineCount(linuxWorkerMachineCount),
   167  			withControlPlaneWaiters(clusterctl.ControlPlaneWaiters{
   168  				WaitForControlPlaneInitialized: EnsureControlPlaneInitializedNoAddons,
   169  			}),
   170  		), result)
   171  		stopwatch.Record("cluster creation")
   172  
   173  		workloadProxy := bootstrapClusterProxy.GetWorkloadCluster(ctx, namespace.Name, clusterName)
   174  
   175  		if isWindows(kubetestConfigFilePath) {
   176  			// Windows requires a taint on control nodes since not all conformance tests have ability to run
   177  			options := metav1.ListOptions{
   178  				LabelSelector: "kubernetes.io/os=linux",
   179  			}
   180  
   181  			noScheduleTaint := &corev1.Taint{
   182  				Key:    "node-role.kubernetes.io/control-plane",
   183  				Value:  "",
   184  				Effect: "NoSchedule",
   185  			}
   186  
   187  			if v, err := semver.ParseTolerant(kubernetesVersion); err == nil {
   188  				if v.LT(semver.MustParse("1.24.0-alpha.0.0")) {
   189  					noScheduleTaint = &corev1.Taint{
   190  						Key:    "node-role.kubernetes.io/master",
   191  						Value:  "",
   192  						Effect: "NoSchedule",
   193  					}
   194  				}
   195  			}
   196  
   197  			err = node.TaintNode(workloadProxy.GetClientSet(), options, noScheduleTaint)
   198  			Expect(err).NotTo(HaveOccurred())
   199  
   200  			// Windows requires a repo-list when running e2e tests against K8s versions prior to v1.25
   201  			// because some test images published to the k8s gcr do not have Windows flavors.
   202  			repoList, err = resolveKubetestRepoListPath(kubernetesVersion, kubetestRepoListPath)
   203  			Expect(err).NotTo(HaveOccurred())
   204  			fmt.Fprintf(GinkgoWriter, "INFO: Using repo-list '%s' for version '%s'\n", repoList, kubernetesVersion)
   205  		}
   206  
   207  		ginkgoNodes, err := strconv.Atoi(e2eConfig.GetVariable("CONFORMANCE_NODES"))
   208  		Expect(err).NotTo(HaveOccurred())
   209  
   210  		stopwatch.Reset()
   211  		err = kubetest.Run(context.Background(),
   212  			kubetest.RunInput{
   213  				ClusterProxy:         workloadProxy,
   214  				NumberOfNodes:        int(numOfConformanceNodes),
   215  				ConfigFilePath:       kubetestConfigFilePath,
   216  				KubeTestRepoListPath: repoList,
   217  				ConformanceImage:     e2eConfig.GetVariable("CONFORMANCE_IMAGE"),
   218  				GinkgoNodes:          ginkgoNodes,
   219  			},
   220  		)
   221  		Expect(err).NotTo(HaveOccurred())
   222  		stopwatch.Record("conformance suite")
   223  	})
   224  
   225  	AfterEach(func() {
   226  		if result.Cluster == nil {
   227  			// 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.
   228  			_ = bootstrapClusterProxy.GetClient().Get(ctx, types.NamespacedName{Name: clusterName, Namespace: namespace.Name}, result.Cluster)
   229  		}
   230  
   231  		CheckTestBeforeCleanup()
   232  
   233  		cleanInput := cleanupInput{
   234  			SpecName:          specName,
   235  			Cluster:           result.Cluster,
   236  			ClusterProxy:      bootstrapClusterProxy,
   237  			Namespace:         namespace,
   238  			CancelWatches:     cancelWatches,
   239  			IntervalsGetter:   e2eConfig.GetIntervals,
   240  			SkipCleanup:       skipCleanup,
   241  			SkipLogCollection: skipLogCollection,
   242  			ArtifactFolder:    artifactFolder,
   243  		}
   244  		// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
   245  		dumpSpecResourcesAndCleanup(ctx, cleanInput)
   246  
   247  		Expect(os.Unsetenv(AzureResourceGroup)).To(Succeed())
   248  		Expect(os.Unsetenv(AzureVNetName)).To(Succeed())
   249  	})
   250  
   251  })
   252  
   253  func isWindows(kubetestConfigFilePath string) bool {
   254  	return strings.Contains(kubetestConfigFilePath, "windows")
   255  }
   256  
   257  func getGinkgoV2ConfigFilePath(kubetestConfigFilePath string) string {
   258  	if !strings.HasSuffix(kubetestConfigFilePath, "-ginkgo-v2.yaml") {
   259  		return strings.Replace(kubetestConfigFilePath, ".yaml", "-ginkgo-v2.yaml", 1)
   260  	}
   261  	return kubetestConfigFilePath
   262  }