Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🏃[e2e] improve and cleanup e2e clusterctl #2909

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions test/framework/clusterctl/client.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e

/*
Copyright 2020 The Kubernetes Authors.

Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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{
Expand All @@ -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())

Expand Down
2 changes: 0 additions & 2 deletions test/framework/clusterctl/clusterctl_config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need this? @wfernandes has a different PR that adds this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vincepri @wfernandes
The test framework is basically a library that CAPI is offering to external users, so IMO it should build (and run tests) like any other package

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW I was adding a build tag to docker/e2e/custom_management.go


/*
Copyright 2020 The Kubernetes Authors.

Expand Down
165 changes: 116 additions & 49 deletions test/framework/clusterctl/e2e_config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e

/*
Copyright 2020 The Kubernetes Authors.

Expand All @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
}

Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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
}
8 changes: 3 additions & 5 deletions test/framework/clusterctl/logger/log_file.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e

/*
Copyright 2020 The Kubernetes Authors.

Expand Down Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions test/framework/clusterctl/logger/logger.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e

/*
Copyright 2020 The Kubernetes Authors.

Expand Down
12 changes: 5 additions & 7 deletions test/framework/clusterctl/repository.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// +build e2e

/*
Copyright 2020 The Kubernetes Authors.

Expand Down Expand Up @@ -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))

Expand All @@ -60,21 +58,21 @@ 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,
})

for _, file := range provider.Files {
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)
}
}
Expand Down