From eeb69f8eefa44b3de67a6460ac3da776cb496306 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Wed, 15 Apr 2020 12:38:41 +0200 Subject: [PATCH] improve and cleanup e2e clusterctl --- test/framework/clusterctl/client.go | 25 +-- .../framework/clusterctl/clusterctl_config.go | 2 - test/framework/clusterctl/e2e_config.go | 165 ++++++++++++------ test/framework/clusterctl/logger/log_file.go | 8 +- test/framework/clusterctl/logger/logger.go | 2 - test/framework/clusterctl/repository.go | 12 +- 6 files changed, 138 insertions(+), 76 deletions(-) diff --git a/test/framework/clusterctl/client.go b/test/framework/clusterctl/client.go index bb517bbb5213..80c31d6b3d0d 100644 --- a/test/framework/clusterctl/client.go +++ b/test/framework/clusterctl/client.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. @@ -38,9 +36,14 @@ const ( DefaultFlavor = "" ) +const ( + // DefaultInfrastructureProvider for ConfigClusterInput; use it for using the only infrastructure provider installed in a cluster. + DefaultInfrastructureProvider = "" +) + // InitInput is the input for Init. type InitInput struct { - LogPath string + LogFolder string ClusterctlConfigPath string KubeconfigPath string CoreProvider string @@ -67,7 +70,7 @@ func Init(ctx context.Context, input InitInput) { LogUsageInstructions: true, } - clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-init.log", input.LogPath) + clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-init.log", input.LogFolder) defer log.Close() _, err := clusterctlClient.Init(initOpt) @@ -76,7 +79,7 @@ func Init(ctx context.Context, input InitInput) { // ConfigClusterInput is the input for ConfigCluster. type ConfigClusterInput struct { - LogPath string + LogFolder string ClusterctlConfigPath string KubeconfigPath string InfrastructureProvider string @@ -112,7 +115,7 @@ func ConfigCluster(ctx context.Context, input ConfigClusterInput) []byte { TargetNamespace: input.Namespace, } - clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-config-cluster.log", input.LogPath) + clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, fmt.Sprintf("%s-cluster-template.yaml", input.ClusterName), input.LogFolder) defer log.Close() template, err := clusterctlClient.GetClusterTemplate(templateOptions) @@ -128,7 +131,7 @@ func ConfigCluster(ctx context.Context, input ConfigClusterInput) []byte { // MoveInput is the input for ClusterctlMove. type MoveInput struct { - LogPath string + LogFolder string ClusterctlConfigPath string FromKubeconfigPath string ToKubeconfigPath string @@ -139,7 +142,7 @@ type MoveInput struct { func Move(ctx context.Context, input MoveInput) { By("Moving workload clusters") - clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-move.log", input.LogPath) + clusterctlClient, log := getClusterctlClientWithLogger(input.ClusterctlConfigPath, "clusterctl-move.log", input.LogFolder) defer log.Close() options := clusterctlclient.MoveOptions{ @@ -151,10 +154,10 @@ func Move(ctx context.Context, input MoveInput) { Expect(clusterctlClient.Move(options)).To(Succeed(), "Failed to run clusterctl move") } -func getClusterctlClientWithLogger(configPath, logName, logPath string) (clusterctlclient.Client, *logger.LogFile) { +func getClusterctlClientWithLogger(configPath, logName, logFolder string) (clusterctlclient.Client, *logger.LogFile) { log := logger.CreateLogFile(logger.CreateLogFileInput{ - LogPath: logPath, - Name: logName, + LogFolder: logFolder, + Name: logName, }) clusterctllog.SetLogger(log.Logger()) diff --git a/test/framework/clusterctl/clusterctl_config.go b/test/framework/clusterctl/clusterctl_config.go index f3c2a331a2ec..5f53966ea38d 100644 --- a/test/framework/clusterctl/clusterctl_config.go +++ b/test/framework/clusterctl/clusterctl_config.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. diff --git a/test/framework/clusterctl/e2e_config.go b/test/framework/clusterctl/e2e_config.go index 3f24b2161ae4..0afda6ca76d5 100644 --- a/test/framework/clusterctl/e2e_config.go +++ b/test/framework/clusterctl/e2e_config.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. @@ -25,11 +23,13 @@ import ( "os" "path/filepath" "regexp" + "strconv" "time" . "github.com/onsi/gomega" "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/util/version" clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3" clusterctlconfig "sigs.k8s.io/cluster-api/cmd/clusterctl/client/config" "sigs.k8s.io/cluster-api/test/framework" @@ -39,6 +39,11 @@ import ( // Provides access to the configuration for an e2e test. +// Define constants for well known clusterctl config variables +const ( + kubernetesVersion = "KUBERNETES_VERSION" +) + // LoadE2EConfigInput is the input for LoadE2EConfig. type LoadE2EConfigInput struct { // ConfigPath for the e2e test. @@ -181,22 +186,69 @@ func errEmptyArg(argName string) error { // Validate validates the configuration. More specifically: // - ManagementClusterName should not be empty. -// - Providers name should not be empty. -// - Providers type should be one of [CoreProvider, BootstrapProvider, ControlPlaneProvider, InfrastructureProvider]. -// - Providers version should have a name. -// - Providers version.type should be one of [url, kustomize]. -// - Providers version.replacements.old should be a valid regex. -// - Providers files should be an existing file and have a target name. // - There should be one CoreProvider (cluster-api), one BootstrapProvider (kubeadm), one ControlPlaneProvider (kubeadm). // - There should be one InfraProvider (pick your own). // - Image should have name and loadBehavior be one of [mustload, tryload]. // - Intervals should be valid ginkgo intervals. +// - KubernetesVersion is not nil and valid. func (c *E2EConfig) Validate() error { // ManagementClusterName should not be empty. if c.ManagementClusterName == "" { return errEmptyArg("ManagementClusterName") } + if err := c.validateProviders(); err != nil { + return err + } + + // Image should have name and loadBehavior be one of [mustload, tryload]. + for i, containerImage := range c.Images { + if containerImage.Name == "" { + return errEmptyArg(fmt.Sprintf("Images[%d].Name=%q", i, containerImage.Name)) + } + switch containerImage.LoadBehavior { + case framework.MustLoadImage, framework.TryLoadImage: + // Valid + default: + return errInvalidArg("Images[%d].LoadBehavior=%q", i, containerImage.LoadBehavior) + } + } + + // Intervals should be valid ginkgo intervals. + for k, intervals := range c.Intervals { + switch len(intervals) { + case 0: + return errInvalidArg("Intervals[%s]=%q", k, intervals) + case 1, 2: + default: + return errInvalidArg("Intervals[%s]=%q", k, intervals) + } + for _, i := range intervals { + if _, err := time.ParseDuration(i); err != nil { + return errInvalidArg("Intervals[%s]=%q", k, intervals) + } + } + } + + // If kubernetesVersion is nil or not valid, return error. + k8sVersion := c.GetKubernetesVersion() + if k8sVersion == "" { + return errEmptyArg(fmt.Sprintf("Variables[%s]", kubernetesVersion)) + } else if _, err := version.ParseSemantic(k8sVersion); err != nil { + return errInvalidArg("Variables[%s]=%q", kubernetesVersion, k8sVersion) + } + + return nil +} + +// validateProviders validates the provider configuration. More specifically: +// - Providers name should not be empty. +// - Providers type should be one of [CoreProvider, BootstrapProvider, ControlPlaneProvider, InfrastructureProvider]. +// - Providers version should have a name. +// - Providers version.type should be one of [url, kustomize]. +// - Providers version.replacements.old should be a valid regex. +// - Providers files should be an existing file and have a target name. +func (c *E2EConfig) validateProviders() error { providersByType := map[clusterctlv1.ProviderType][]string{ clusterctlv1.CoreProviderType: nil, clusterctlv1.BootstrapProviderType: nil, @@ -217,23 +269,25 @@ func (c *E2EConfig) Validate() error { return errInvalidArg("Providers[%d].Type=%q", i, providerConfig.Type) } - // Providers version should have a name. - // Providers version.type should be one of [url, kustomize]. - // Providers version.replacements.old should be a valid regex. - for j, version := range providerConfig.Versions { - if version.Name == "" { + // Providers providerVersion should have a name. + // Providers providerVersion.type should be one of [url, kustomize]. + // Providers providerVersion.replacements.old should be a valid regex. + for j, providerVersion := range providerConfig.Versions { + if providerVersion.Name == "" { return errEmptyArg(fmt.Sprintf("Providers[%d].Sources[%d].Name", i, j)) } - //TODO: check if name is a valid semantic version - switch version.Type { + if _, err := version.ParseSemantic(providerVersion.Name); err != nil { + return errInvalidArg("Providers[%d].Sources[%d].Name=%q", i, j, providerVersion.Name) + } + switch providerVersion.Type { case framework.URLSource, framework.KustomizeSource: - if version.Value == "" { + if providerVersion.Value == "" { return errEmptyArg(fmt.Sprintf("Providers[%d].Sources[%d].Value", i, j)) } default: - return errInvalidArg("Providers[%d].Sources[%d].Type=%q", i, j, version.Type) + return errInvalidArg("Providers[%d].Sources[%d].Type=%q", i, j, providerVersion.Type) } - for k, replacement := range version.Replacements { + for k, replacement := range providerVersion.Replacements { if _, err := regexp.Compile(replacement.Old); err != nil { return errInvalidArg("Providers[%d].Sources[%d].Replacements[%d].Old=%q: %v", i, j, k, replacement.Old, err) } @@ -280,36 +334,6 @@ func (c *E2EConfig) Validate() error { if len(providersByType[clusterctlv1.InfrastructureProviderType]) != 1 { return errInvalidArg("invalid config: it is required to have exactly one infrastructure-provider") } - - // Image should have name and loadBehavior be one of [mustload, tryload]. - for i, containerImage := range c.Images { - if containerImage.Name == "" { - return errEmptyArg(fmt.Sprintf("Images[%d].Name=%q", i, containerImage.Name)) - } - switch containerImage.LoadBehavior { - case framework.MustLoadImage, framework.TryLoadImage: - // Valid - default: - return errInvalidArg("Images[%d].LoadBehavior=%q", i, containerImage.LoadBehavior) - } - } - - // Intervals should be valid ginkgo intervals. - for k, intervals := range c.Intervals { - switch len(intervals) { - case 0: - return errInvalidArg("Intervals[%s]=%q", k, intervals) - case 1, 2: - default: - return errInvalidArg("Intervals[%s]=%q", k, intervals) - } - for _, i := range intervals { - if _, err := time.ParseDuration(i); err != nil { - return errInvalidArg("Intervals[%s]=%q", k, intervals) - } - } - } - return nil } @@ -322,7 +346,7 @@ func fileExists(filename string) bool { } // InfraProvider returns the infrastructure provider selected for running this E2E test. -func (c *E2EConfig) InfraProviders() []string { +func (c *E2EConfig) InfrastructureProviders() []string { InfraProviders := []string{} for _, provider := range c.Providers { if provider.Type == string(clusterctlv1.InfrastructureProviderType) { @@ -332,6 +356,15 @@ func (c *E2EConfig) InfraProviders() []string { return InfraProviders } +func (c *E2EConfig) HasDockerProvider() bool { + for _, i := range c.InfrastructureProviders() { + if i == "docker" { + return true + } + } + return false +} + // GetIntervals returns the intervals to be applied to a Eventually operation. // It searches for [spec]/[key] intervals first, and if it is not found, it searches // for default/[key]. If also the default/[key] intervals are not found, @@ -349,3 +382,37 @@ func (c *E2EConfig) GetIntervals(spec, key string) []interface{} { } return intervalsInterfaces } + +// GetVariable returns a variable from the e2e config file. +func (c *E2EConfig) GetVariable(varName string) string { + version, ok := c.Variables[varName] + if !ok { + return "" + } + return version +} + +// GetVariable returns an Int64Ptr variable from the e2e config file. +func (c *E2EConfig) GetInt64PtrVariable(varName string) *int64 { + wCountStr := c.GetVariable(varName) + if wCountStr == "" { + return nil + } + + wCount, err := strconv.ParseInt(wCountStr, 10, 64) + Expect(err).NotTo(HaveOccurred()) + return Int64Ptr(wCount) +} + +func Int64Ptr(n int64) *int64 { + return &n +} + +// GetKubernetesVersion returns the kubernetes version provided in e2e config. +func (c *E2EConfig) GetKubernetesVersion() string { + version, ok := c.Variables[kubernetesVersion] + if !ok { + return "" + } + return version +} diff --git a/test/framework/clusterctl/logger/log_file.go b/test/framework/clusterctl/logger/log_file.go index 78c87df9d295..b7c9db138077 100644 --- a/test/framework/clusterctl/logger/log_file.go +++ b/test/framework/clusterctl/logger/log_file.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. @@ -31,12 +29,12 @@ import ( // Provides a log_file that can be used to store the logs generated by clusterctl actions. type CreateLogFileInput struct { - LogPath string - Name string + LogFolder string + Name string } func CreateLogFile(input CreateLogFileInput) *LogFile { - filePath := filepath.Join(input.LogPath, input.Name) + filePath := filepath.Join(input.LogFolder, input.Name) Expect(os.MkdirAll(filepath.Dir(filePath), 0755)).To(Succeed(), "Failed to create log folder %s", filepath.Dir(filePath)) f, err := os.Create(filePath) diff --git a/test/framework/clusterctl/logger/logger.go b/test/framework/clusterctl/logger/logger.go index 85830ef9034f..33d673fdf9f0 100644 --- a/test/framework/clusterctl/logger/logger.go +++ b/test/framework/clusterctl/logger/logger.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. diff --git a/test/framework/clusterctl/repository.go b/test/framework/clusterctl/repository.go index 2d949563227f..def9dc0de5f9 100644 --- a/test/framework/clusterctl/repository.go +++ b/test/framework/clusterctl/repository.go @@ -1,5 +1,3 @@ -// +build e2e - /* Copyright 2020 The Kubernetes Authors. @@ -46,7 +44,7 @@ func CreateRepository(ctx context.Context, input CreateRepositoryInput) string { providers := []providerConfig{} for _, provider := range input.E2EConfig.Providers { - providerUrl := "" + providerURL := "" for _, version := range provider.Versions { providerLabel := clusterctlv1.ManifestLabel(provider.Name, clusterctlv1.ProviderType(provider.Type)) @@ -60,13 +58,13 @@ func CreateRepository(ctx context.Context, input CreateRepositoryInput) string { filePath := filepath.Join(sourcePath, "components.yaml") Expect(ioutil.WriteFile(filePath, manifest, 0755)).To(Succeed(), "Failed to write manifest in the clusterctl local repository for %q / %q", providerLabel, version.Name) - if providerUrl == "" { - providerUrl = filePath + if providerURL == "" { + providerURL = filePath } } providers = append(providers, providerConfig{ Name: provider.Name, - URL: providerUrl, + URL: providerURL, Type: provider.Type, }) @@ -74,7 +72,7 @@ func CreateRepository(ctx context.Context, input CreateRepositoryInput) string { data, err := ioutil.ReadFile(file.SourcePath) Expect(err).ToNot(HaveOccurred(), "Failed to read file %q / %q", provider.Name, file.SourcePath) - destinationFile := filepath.Join(filepath.Dir(providerUrl), file.TargetName) + destinationFile := filepath.Join(filepath.Dir(providerURL), file.TargetName) Expect(ioutil.WriteFile(destinationFile, data, 0644)).To(Succeed(), "Failed to write clusterctl local repository file %q / %q", provider.Name, file.TargetName) } }