sigs.k8s.io/cluster-api-provider-aws@v1.5.5/test/e2e/shared/suite.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 shared
    21  
    22  import (
    23  	"context"
    24  	"flag"
    25  	"os"
    26  	"os/exec"
    27  	"path"
    28  	"path/filepath"
    29  	"strconv"
    30  	"time"
    31  
    32  	"github.com/aws/aws-sdk-go/service/iam"
    33  	"github.com/gofrs/flock"
    34  	. "github.com/onsi/ginkgo"
    35  	. "github.com/onsi/gomega"
    36  	"sigs.k8s.io/yaml"
    37  
    38  	"sigs.k8s.io/cluster-api/test/framework"
    39  	"sigs.k8s.io/cluster-api/test/framework/clusterctl"
    40  	"sigs.k8s.io/cluster-api/test/framework/kubernetesversions"
    41  )
    42  
    43  type synchronizedBeforeTestSuiteConfig struct {
    44  	ArtifactFolder           string               `json:"artifactFolder,omitempty"`
    45  	ConfigPath               string               `json:"configPath,omitempty"`
    46  	ClusterctlConfigPath     string               `json:"clusterctlConfigPath,omitempty"`
    47  	KubeconfigPath           string               `json:"kubeconfigPath,omitempty"`
    48  	Region                   string               `json:"region,omitempty"`
    49  	E2EConfig                clusterctl.E2EConfig `json:"e2eConfig,omitempty"`
    50  	BootstrapAccessKey       *iam.AccessKey       `json:"bootstrapAccessKey,omitempty"`
    51  	KubetestConfigFilePath   string               `json:"kubetestConfigFilePath,omitempty"`
    52  	UseCIArtifacts           bool                 `json:"useCIArtifacts,omitempty"`
    53  	GinkgoNodes              int                  `json:"ginkgoNodes,omitempty"`
    54  	GinkgoSlowSpecThreshold  int                  `json:"ginkgoSlowSpecThreshold,omitempty"`
    55  	Base64EncodedCredentials string               `json:"base64EncodedCredentials,omitempty"`
    56  }
    57  
    58  // Node1BeforeSuite is the common setup down on the first ginkgo node before the test suite runs.
    59  func Node1BeforeSuite(e2eCtx *E2EContext) []byte {
    60  	flag.Parse()
    61  	Expect(e2eCtx.Settings.ConfigPath).To(BeAnExistingFile(), "Invalid test suite argument. configPath should be an existing file.")
    62  	Expect(os.MkdirAll(e2eCtx.Settings.ArtifactFolder, 0o750)).To(Succeed(), "Invalid test suite argument. Can't create artifacts-folder %q", e2eCtx.Settings.ArtifactFolder)
    63  	Byf("Loading the e2e test configuration from %q", e2eCtx.Settings.ConfigPath)
    64  	e2eCtx.E2EConfig = LoadE2EConfig(e2eCtx.Settings.ConfigPath)
    65  	sourceTemplate, err := os.ReadFile(filepath.Join(e2eCtx.Settings.DataFolder, e2eCtx.Settings.SourceTemplate))
    66  	Expect(err).NotTo(HaveOccurred())
    67  	e2eCtx.StartOfSuite = time.Now()
    68  
    69  	var clusterctlCITemplate clusterctl.Files
    70  	if !e2eCtx.IsManaged {
    71  		// Create CI manifest for upgrading to Kubernetes main test
    72  		platformKustomization, err := os.ReadFile(filepath.Join(e2eCtx.Settings.DataFolder, "ci-artifacts-platform-kustomization-for-upgrade.yaml"))
    73  		Expect(err).NotTo(HaveOccurred())
    74  		sourceTemplateForUpgrade, err := os.ReadFile(filepath.Join(e2eCtx.Settings.DataFolder, "infrastructure-aws/generated/cluster-template-upgrade-to-main.yaml"))
    75  		Expect(err).NotTo(HaveOccurred())
    76  
    77  		ciTemplateForUpgradePath, err := kubernetesversions.GenerateCIArtifactsInjectedTemplateForDebian(
    78  			kubernetesversions.GenerateCIArtifactsInjectedTemplateForDebianInput{
    79  				ArtifactsDirectory:    e2eCtx.Settings.ArtifactFolder,
    80  				SourceTemplate:        sourceTemplateForUpgrade,
    81  				PlatformKustomization: platformKustomization,
    82  			},
    83  		)
    84  		Expect(err).NotTo(HaveOccurred())
    85  
    86  		ciTemplateForUpgradeName := "cluster-template-upgrade-ci-artifacts.yaml"
    87  		templateDir := path.Join(e2eCtx.Settings.ArtifactFolder, "templates")
    88  		newTemplatePath := templateDir + "/" + ciTemplateForUpgradeName
    89  
    90  		err = exec.Command("cp", ciTemplateForUpgradePath, newTemplatePath).Run() //nolint:gosec
    91  		Expect(err).NotTo(HaveOccurred())
    92  
    93  		clusterctlCITemplateForUpgrade := clusterctl.Files{
    94  			SourcePath: newTemplatePath,
    95  			TargetName: ciTemplateForUpgradeName,
    96  		}
    97  
    98  		// Create CI manifest for conformance test
    99  		platformKustomization, err = os.ReadFile(filepath.Join(e2eCtx.Settings.DataFolder, "ci-artifacts-platform-kustomization.yaml"))
   100  		Expect(err).NotTo(HaveOccurred())
   101  		ciTemplatePath, err := kubernetesversions.GenerateCIArtifactsInjectedTemplateForDebian(
   102  			kubernetesversions.GenerateCIArtifactsInjectedTemplateForDebianInput{
   103  				ArtifactsDirectory:    e2eCtx.Settings.ArtifactFolder,
   104  				SourceTemplate:        sourceTemplate,
   105  				PlatformKustomization: platformKustomization,
   106  			},
   107  		)
   108  		Expect(err).NotTo(HaveOccurred())
   109  
   110  		clusterctlCITemplate = clusterctl.Files{
   111  			SourcePath: ciTemplatePath,
   112  			TargetName: "cluster-template-conformance-ci-artifacts.yaml",
   113  		}
   114  
   115  		providers := e2eCtx.E2EConfig.Providers
   116  		for i, prov := range providers {
   117  			if prov.Name != "aws" {
   118  				continue
   119  			}
   120  			e2eCtx.E2EConfig.Providers[i].Files = append(e2eCtx.E2EConfig.Providers[i].Files, clusterctlCITemplate)
   121  			e2eCtx.E2EConfig.Providers[i].Files = append(e2eCtx.E2EConfig.Providers[i].Files, clusterctlCITemplateForUpgrade)
   122  		}
   123  	}
   124  
   125  	Expect(err).NotTo(HaveOccurred())
   126  	e2eCtx.AWSSession = NewAWSSession()
   127  	boostrapTemplate := getBootstrapTemplate(e2eCtx)
   128  	bootstrapTags := map[string]string{"capa-e2e-test": "true"}
   129  	e2eCtx.CloudFormationTemplate = renderCustomCloudFormation(boostrapTemplate)
   130  	if !e2eCtx.Settings.SkipCloudFormationCreation {
   131  		err = createCloudFormationStack(e2eCtx.AWSSession, boostrapTemplate, bootstrapTags)
   132  		if err != nil {
   133  			deleteCloudFormationStack(e2eCtx.AWSSession, boostrapTemplate)
   134  			err = createCloudFormationStack(e2eCtx.AWSSession, boostrapTemplate, bootstrapTags)
   135  			Expect(err).NotTo(HaveOccurred())
   136  		}
   137  	}
   138  	ensureStackTags(e2eCtx.AWSSession, boostrapTemplate.Spec.StackName, bootstrapTags)
   139  	ensureNoServiceLinkedRoles(e2eCtx.AWSSession)
   140  	ensureSSHKeyPair(e2eCtx.AWSSession, DefaultSSHKeyPairName)
   141  	e2eCtx.Environment.BootstrapAccessKey = newUserAccessKey(e2eCtx.AWSSession, boostrapTemplate.Spec.BootstrapUser.UserName)
   142  	e2eCtx.BootstrapUserAWSSession = NewAWSSessionWithKey(e2eCtx.Environment.BootstrapAccessKey)
   143  	Expect(ensureTestImageUploaded(e2eCtx)).NotTo(HaveOccurred())
   144  
   145  	// Image ID is needed when using a CI Kubernetes version. This is used in conformance test and upgrade to main test.
   146  	if !e2eCtx.IsManaged {
   147  		e2eCtx.E2EConfig.Variables["IMAGE_ID"] = conformanceImageID(e2eCtx)
   148  	}
   149  
   150  	Byf("Creating a clusterctl local repository into %q", e2eCtx.Settings.ArtifactFolder)
   151  	e2eCtx.Environment.ClusterctlConfigPath = createClusterctlLocalRepository(e2eCtx, filepath.Join(e2eCtx.Settings.ArtifactFolder, "repository"))
   152  
   153  	By("Setting up the bootstrap cluster")
   154  	e2eCtx.Environment.BootstrapClusterProvider, e2eCtx.Environment.BootstrapClusterProxy = setupBootstrapCluster(e2eCtx.E2EConfig, e2eCtx.Environment.Scheme, e2eCtx.Settings.UseExistingCluster)
   155  
   156  	base64EncodedCredentials := encodeCredentials(e2eCtx.Environment.BootstrapAccessKey, boostrapTemplate.Spec.Region)
   157  	SetEnvVar("AWS_B64ENCODED_CREDENTIALS", base64EncodedCredentials, true)
   158  
   159  	By("Writing AWS service quotas to a file for parallel tests")
   160  	quotas := EnsureServiceQuotas(e2eCtx.BootstrapUserAWSSession)
   161  	WriteResourceQuotesToFile(ResourceQuotaFilePath, quotas)
   162  	WriteResourceQuotesToFile(path.Join(e2eCtx.Settings.ArtifactFolder, "initial-resource-quotas.yaml"), quotas)
   163  
   164  	e2eCtx.Settings.InstanceVCPU, err = strconv.Atoi(e2eCtx.E2EConfig.GetVariable(InstanceVcpu))
   165  	Expect(err).NotTo(HaveOccurred())
   166  
   167  	By("Initializing the bootstrap cluster")
   168  	initBootstrapCluster(e2eCtx)
   169  
   170  	CreateAWSClusterControllerIdentity(e2eCtx.Environment.BootstrapClusterProxy.GetClient())
   171  
   172  	if e2eCtx.IsManaged {
   173  		By("Setting up AWS static credentials")
   174  		SetupStaticCredentials(e2eCtx)
   175  	}
   176  
   177  	conf := synchronizedBeforeTestSuiteConfig{
   178  		ArtifactFolder:           e2eCtx.Settings.ArtifactFolder,
   179  		ConfigPath:               e2eCtx.Settings.ConfigPath,
   180  		ClusterctlConfigPath:     e2eCtx.Environment.ClusterctlConfigPath,
   181  		KubeconfigPath:           e2eCtx.Environment.BootstrapClusterProxy.GetKubeconfigPath(),
   182  		Region:                   getBootstrapTemplate(e2eCtx).Spec.Region,
   183  		E2EConfig:                *e2eCtx.E2EConfig,
   184  		BootstrapAccessKey:       e2eCtx.Environment.BootstrapAccessKey,
   185  		KubetestConfigFilePath:   e2eCtx.Settings.KubetestConfigFilePath,
   186  		UseCIArtifacts:           e2eCtx.Settings.UseCIArtifacts,
   187  		GinkgoNodes:              e2eCtx.Settings.GinkgoNodes,
   188  		GinkgoSlowSpecThreshold:  e2eCtx.Settings.GinkgoSlowSpecThreshold,
   189  		Base64EncodedCredentials: base64EncodedCredentials,
   190  	}
   191  
   192  	data, err := yaml.Marshal(conf)
   193  	Expect(err).NotTo(HaveOccurred())
   194  	return data
   195  }
   196  
   197  // AllNodesBeforeSuite is the common setup down on each ginkgo parallel node before the test suite runs.
   198  func AllNodesBeforeSuite(e2eCtx *E2EContext, data []byte) {
   199  	conf := &synchronizedBeforeTestSuiteConfig{}
   200  	err := yaml.UnmarshalStrict(data, conf)
   201  	Expect(err).NotTo(HaveOccurred())
   202  	e2eCtx.Settings.ArtifactFolder = conf.ArtifactFolder
   203  	e2eCtx.Settings.ConfigPath = conf.ConfigPath
   204  	e2eCtx.Environment.ClusterctlConfigPath = conf.ClusterctlConfigPath
   205  	e2eCtx.Environment.BootstrapClusterProxy = framework.NewClusterProxy("bootstrap", conf.KubeconfigPath, e2eCtx.Environment.Scheme)
   206  	e2eCtx.E2EConfig = &conf.E2EConfig
   207  	e2eCtx.BootstrapUserAWSSession = NewAWSSessionWithKey(conf.BootstrapAccessKey)
   208  	e2eCtx.Settings.FileLock = flock.New(ResourceQuotaFilePath)
   209  	e2eCtx.Settings.KubetestConfigFilePath = conf.KubetestConfigFilePath
   210  	e2eCtx.Settings.UseCIArtifacts = conf.UseCIArtifacts
   211  	e2eCtx.Settings.GinkgoNodes = conf.GinkgoNodes
   212  	e2eCtx.Settings.GinkgoSlowSpecThreshold = conf.GinkgoSlowSpecThreshold
   213  	e2eCtx.AWSSession = NewAWSSession()
   214  	azs := GetAvailabilityZones(e2eCtx.AWSSession)
   215  	SetEnvVar(AwsAvailabilityZone1, *azs[0].ZoneName, false)
   216  	SetEnvVar(AwsAvailabilityZone2, *azs[1].ZoneName, false)
   217  	SetEnvVar("AWS_REGION", conf.Region, false)
   218  	SetEnvVar("AWS_SSH_KEY_NAME", DefaultSSHKeyPairName, false)
   219  	SetEnvVar("AWS_B64ENCODED_CREDENTIALS", conf.Base64EncodedCredentials, true)
   220  	e2eCtx.Environment.ResourceTicker = time.NewTicker(time.Second * 5)
   221  	e2eCtx.Environment.ResourceTickerDone = make(chan bool)
   222  	// Get EC2 logs every minute
   223  	e2eCtx.Environment.MachineTicker = time.NewTicker(time.Second * 60)
   224  	e2eCtx.Environment.MachineTickerDone = make(chan bool)
   225  	resourceCtx, resourceCancel := context.WithCancel(context.Background())
   226  	machineCtx, machineCancel := context.WithCancel(context.Background())
   227  	// Dump resources every 5 seconds
   228  	go func() {
   229  		defer GinkgoRecover()
   230  		for {
   231  			select {
   232  			case <-e2eCtx.Environment.ResourceTickerDone:
   233  				resourceCancel()
   234  				return
   235  			case <-e2eCtx.Environment.ResourceTicker.C:
   236  				for k := range e2eCtx.Environment.Namespaces {
   237  					DumpSpecResources(resourceCtx, e2eCtx, k)
   238  				}
   239  			}
   240  		}
   241  	}()
   242  
   243  	// Dump machine logs every 60 seconds
   244  	go func() {
   245  		defer GinkgoRecover()
   246  		for {
   247  			select {
   248  			case <-e2eCtx.Environment.MachineTickerDone:
   249  				machineCancel()
   250  				return
   251  			case <-e2eCtx.Environment.MachineTicker.C:
   252  				for k := range e2eCtx.Environment.Namespaces {
   253  					DumpMachines(machineCtx, e2eCtx, k)
   254  				}
   255  			}
   256  		}
   257  	}()
   258  }
   259  
   260  // Node1AfterSuite is cleanup that runs on the first ginkgo node after the test suite finishes.
   261  func Node1AfterSuite(e2eCtx *E2EContext) {
   262  	ctx, cancel := context.WithTimeout(context.TODO(), 15*time.Minute)
   263  	DumpEKSClusters(ctx, e2eCtx)
   264  	DumpCloudTrailEvents(e2eCtx)
   265  
   266  	if e2eCtx.IsManaged {
   267  		By("Deleting AWS static credentials")
   268  		CleanupStaticCredentials(ctx, e2eCtx)
   269  	}
   270  
   271  	defer cancel()
   272  	By("Tearing down the management cluster")
   273  	if !e2eCtx.Settings.SkipCleanup {
   274  		tearDown(e2eCtx.Environment.BootstrapClusterProvider, e2eCtx.Environment.BootstrapClusterProxy)
   275  		if !e2eCtx.Settings.SkipCloudFormationDeletion {
   276  			deleteCloudFormationStack(e2eCtx.AWSSession, getBootstrapTemplate(e2eCtx))
   277  		}
   278  	}
   279  }
   280  
   281  // AllNodesAfterSuite is cleanup that runs on all ginkgo parallel nodes after the test suite finishes.
   282  func AllNodesAfterSuite(e2eCtx *E2EContext) {
   283  	if e2eCtx.Environment.ResourceTickerDone != nil {
   284  		e2eCtx.Environment.ResourceTickerDone <- true
   285  	}
   286  	if e2eCtx.Environment.MachineTickerDone != nil {
   287  		e2eCtx.Environment.MachineTickerDone <- true
   288  	}
   289  	ctx, cancel := context.WithTimeout(context.TODO(), 45*time.Minute)
   290  	defer cancel()
   291  	for k := range e2eCtx.Environment.Namespaces {
   292  		DumpSpecResourcesAndCleanup(ctx, "", k, e2eCtx)
   293  		DumpMachines(ctx, e2eCtx, k)
   294  	}
   295  }