sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/azure_selfhosted.go (about)

     1  //go:build e2e
     2  // +build e2e
     3  
     4  /*
     5  Copyright 2021 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  	"path/filepath"
    27  
    28  	. "github.com/onsi/ginkgo/v2"
    29  	. "github.com/onsi/gomega"
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
    34  	capi_e2e "sigs.k8s.io/cluster-api/test/e2e"
    35  	"sigs.k8s.io/cluster-api/test/framework"
    36  	"sigs.k8s.io/cluster-api/test/framework/clusterctl"
    37  	"sigs.k8s.io/cluster-api/util"
    38  	"sigs.k8s.io/controller-runtime/pkg/client"
    39  )
    40  
    41  // SelfHostedSpecInput is the input for SelfHostedSpec.
    42  type SelfHostedSpecInput struct {
    43  	E2EConfig             *clusterctl.E2EConfig
    44  	ClusterctlConfigPath  string
    45  	BootstrapClusterProxy framework.ClusterProxy
    46  	ArtifactFolder        string
    47  	SkipCleanup           bool
    48  	ControlPlaneWaiters   clusterctl.ControlPlaneWaiters
    49  }
    50  
    51  // SelfHostedSpec implements a test that verifies Cluster API creating a cluster, pivoting to a self-hosted cluster.
    52  func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput) {
    53  	var (
    54  		specName         = "self-hosted"
    55  		input            SelfHostedSpecInput
    56  		namespace        *corev1.Namespace
    57  		cancelWatches    context.CancelFunc
    58  		clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
    59  
    60  		selfHostedClusterProxy  framework.ClusterProxy
    61  		selfHostedNamespace     *corev1.Namespace
    62  		selfHostedCancelWatches context.CancelFunc
    63  		selfHostedCluster       *clusterv1.Cluster
    64  	)
    65  
    66  	BeforeEach(func() {
    67  		Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName)
    68  		input = inputGetter()
    69  		Expect(input.E2EConfig).NotTo(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName)
    70  		Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName)
    71  		Expect(input.BootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
    72  		Expect(os.MkdirAll(input.ArtifactFolder, 0o750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName)
    73  		Expect(input.E2EConfig.Variables).To(HaveKey(capi_e2e.KubernetesVersion))
    74  
    75  		// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
    76  		var err error
    77  		namespace, cancelWatches, err = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder)
    78  		Expect(err).NotTo(HaveOccurred())
    79  		clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult)
    80  
    81  		spClientSecret := os.Getenv(AzureClientSecret)
    82  		secret := &corev1.Secret{
    83  			ObjectMeta: metav1.ObjectMeta{
    84  				Name:      "cluster-identity-secret",
    85  				Namespace: namespace.Name,
    86  				Labels: map[string]string{
    87  					clusterctlv1.ClusterctlMoveHierarchyLabel: "true",
    88  				},
    89  			},
    90  			Type: corev1.SecretTypeOpaque,
    91  			Data: map[string][]byte{"clientSecret": []byte(spClientSecret)},
    92  		}
    93  		err = bootstrapClusterProxy.GetClient().Create(ctx, secret)
    94  		Expect(err).NotTo(HaveOccurred())
    95  
    96  		identityName := input.E2EConfig.GetVariable(ClusterIdentityName)
    97  		Expect(os.Setenv(ClusterIdentityName, identityName)).To(Succeed())
    98  		Expect(os.Setenv(ClusterIdentityNamespace, namespace.Name)).To(Succeed())
    99  		Expect(os.Setenv(ClusterIdentitySecretName, "cluster-identity-secret")).To(Succeed())
   100  		Expect(os.Setenv(ClusterIdentitySecretNamespace, namespace.Name)).To(Succeed())
   101  	})
   102  
   103  	// Management clusters do not support Windows nodes because of cert manager
   104  	// We are using the capi specs located in test/e2e/data/infrastructure-azure/v1beta1 that only have linux nodes
   105  	// to act as the management cluster until Windows nodes are supported for management nodes
   106  	// Tracking support for cert manager: https://github.com/jetstack/cert-manager/issues/3606
   107  	It("Should pivot the bootstrap cluster to a self-hosted cluster", func() {
   108  		By("Creating a workload cluster")
   109  		clusterctl.ApplyClusterTemplateAndWait(ctx, createApplyClusterTemplateInput(
   110  			specName,
   111  			withFlavor("management"),
   112  			withNamespace(namespace.Name),
   113  			withClusterName(fmt.Sprintf("%s-%s", specName, util.RandomString(6))),
   114  			withControlPlaneMachineCount(1),
   115  			withWorkerMachineCount(1),
   116  			withControlPlaneWaiters(input.ControlPlaneWaiters),
   117  		), clusterResources)
   118  
   119  		By("Turning the workload cluster into a management cluster")
   120  		cluster := clusterResources.Cluster
   121  		// Get a ClusterBroker so we can interact with the workload cluster
   122  		selfHostedClusterProxy = input.BootstrapClusterProxy.GetWorkloadCluster(ctx, cluster.Namespace, cluster.Name)
   123  
   124  		Byf("Creating a namespace for hosting the %s test spec", specName)
   125  		selfHostedNamespace, selfHostedCancelWatches = framework.CreateNamespaceAndWatchEvents(ctx, framework.CreateNamespaceAndWatchEventsInput{
   126  			Creator:   selfHostedClusterProxy.GetClient(),
   127  			ClientSet: selfHostedClusterProxy.GetClientSet(),
   128  			Name:      namespace.Name,
   129  			LogFolder: filepath.Join(input.ArtifactFolder, "clusters", "bootstrap"),
   130  		})
   131  
   132  		By("Initializing the workload cluster")
   133  		clusterctl.InitManagementClusterAndWatchControllerLogs(ctx, clusterctl.InitManagementClusterAndWatchControllerLogsInput{
   134  			ClusterProxy:            selfHostedClusterProxy,
   135  			ClusterctlConfigPath:    input.ClusterctlConfigPath,
   136  			InfrastructureProviders: input.E2EConfig.InfrastructureProviders(),
   137  			AddonProviders:          input.E2EConfig.AddonProviders(),
   138  			LogFolder:               filepath.Join(input.ArtifactFolder, "clusters", cluster.Name),
   139  		}, input.E2EConfig.GetIntervals(specName, "wait-controllers")...)
   140  
   141  		By("Ensure API servers are stable before doing move")
   142  		// Nb. This check was introduced to prevent doing move to self-hosted in an aggressive way and thus avoid flakes.
   143  		// More specifically, we were observing the test failing to get objects from the API server during move, so we
   144  		// are now testing the API servers are stable before starting move.
   145  		Consistently(func() error {
   146  			ns := &corev1.Namespace{}
   147  			return input.BootstrapClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns)
   148  		}, "5s", "100ms").Should(BeNil(), "Failed to assert bootstrap API server stability")
   149  		Consistently(func() error {
   150  			ns := &corev1.Namespace{}
   151  			return selfHostedClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns)
   152  		}, "5s", "100ms").Should(BeNil(), "Failed to assert self-hosted API server stability")
   153  
   154  		By("Moving the cluster to self hosted")
   155  		clusterctl.Move(ctx, clusterctl.MoveInput{
   156  			LogFolder:            filepath.Join(input.ArtifactFolder, "clusters", "bootstrap"),
   157  			ClusterctlConfigPath: input.ClusterctlConfigPath,
   158  			FromKubeconfigPath:   input.BootstrapClusterProxy.GetKubeconfigPath(),
   159  			ToKubeconfigPath:     selfHostedClusterProxy.GetKubeconfigPath(),
   160  			Namespace:            namespace.Name,
   161  		})
   162  
   163  		Log("Waiting for the cluster to be reconciled after moving to self hosted")
   164  		selfHostedCluster = framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{
   165  			Getter:    selfHostedClusterProxy.GetClient(),
   166  			Namespace: selfHostedNamespace.Name,
   167  			Name:      cluster.Name,
   168  		}, input.E2EConfig.GetIntervals(specName, "wait-cluster")...)
   169  
   170  		controlPlane := framework.GetKubeadmControlPlaneByCluster(ctx, framework.GetKubeadmControlPlaneByClusterInput{
   171  			Lister:      selfHostedClusterProxy.GetClient(),
   172  			ClusterName: selfHostedCluster.Name,
   173  			Namespace:   selfHostedCluster.Namespace,
   174  		})
   175  		Expect(controlPlane).NotTo(BeNil())
   176  
   177  		By("PASSED!")
   178  	})
   179  
   180  	AfterEach(func() {
   181  		if input.SkipCleanup {
   182  			return
   183  		}
   184  		if selfHostedNamespace != nil {
   185  			// Dump all Cluster API related resources to artifacts before pivoting back.
   186  			framework.DumpAllResources(ctx, framework.DumpAllResourcesInput{
   187  				Lister:    selfHostedClusterProxy.GetClient(),
   188  				Namespace: namespace.Name,
   189  				LogPath:   filepath.Join(input.ArtifactFolder, "clusters", clusterResources.Cluster.Name, "resources"),
   190  			})
   191  		}
   192  		if selfHostedCluster != nil {
   193  			By("Ensure API servers are stable before doing move")
   194  			// Nb. This check was introduced to prevent doing move back to bootstrap in an aggressive way and thus avoid flakes.
   195  			// More specifically, we were observing the test failing to get objects from the API server during move, so we
   196  			// are now testing the API servers are stable before starting move.
   197  			Consistently(func() error {
   198  				ns := &corev1.Namespace{}
   199  				return input.BootstrapClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns)
   200  			}, "5s", "100ms").Should(BeNil(), "Failed to assert bootstrap API server stability")
   201  			Consistently(func() error {
   202  				ns := &corev1.Namespace{}
   203  				return selfHostedClusterProxy.GetClient().Get(ctx, client.ObjectKey{Name: kubesystem}, ns)
   204  			}, "5s", "100ms").Should(BeNil(), "Failed to assert self-hosted API server stability")
   205  
   206  			By("Moving the cluster back to bootstrap")
   207  			clusterctl.Move(ctx, clusterctl.MoveInput{
   208  				LogFolder:            filepath.Join(input.ArtifactFolder, "clusters", clusterResources.Cluster.Name),
   209  				ClusterctlConfigPath: input.ClusterctlConfigPath,
   210  				FromKubeconfigPath:   selfHostedClusterProxy.GetKubeconfigPath(),
   211  				ToKubeconfigPath:     input.BootstrapClusterProxy.GetKubeconfigPath(),
   212  				Namespace:            selfHostedNamespace.Name,
   213  			})
   214  
   215  			Log("Waiting for the cluster to be reconciled after moving back to booststrap")
   216  			clusterResources.Cluster = framework.DiscoveryAndWaitForCluster(ctx, framework.DiscoveryAndWaitForClusterInput{
   217  				Getter:    input.BootstrapClusterProxy.GetClient(),
   218  				Namespace: namespace.Name,
   219  				Name:      clusterResources.Cluster.Name,
   220  			}, input.E2EConfig.GetIntervals(specName, "wait-cluster")...)
   221  		}
   222  		if selfHostedCancelWatches != nil {
   223  			selfHostedCancelWatches()
   224  		}
   225  
   226  		cleanInput := cleanupInput{
   227  			SpecName:          specName,
   228  			Cluster:           clusterResources.Cluster,
   229  			ClusterProxy:      input.BootstrapClusterProxy,
   230  			Namespace:         namespace,
   231  			CancelWatches:     cancelWatches,
   232  			IntervalsGetter:   input.E2EConfig.GetIntervals,
   233  			SkipCleanup:       input.SkipCleanup,
   234  			SkipLogCollection: skipLogCollection,
   235  			ArtifactFolder:    input.ArtifactFolder,
   236  		}
   237  		// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
   238  		dumpSpecResourcesAndCleanup(ctx, cleanInput)
   239  	})
   240  }