sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/azure_lb.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  	"net"
    26  	"time"
    27  
    28  	"github.com/hashicorp/go-retryablehttp"
    29  	. "github.com/onsi/ginkgo/v2"
    30  	. "github.com/onsi/gomega"
    31  	corev1 "k8s.io/api/core/v1"
    32  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/client-go/kubernetes"
    35  	k8snet "k8s.io/utils/net"
    36  	deploymentBuilder "sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/deployment"
    37  	"sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/job"
    38  	"sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/node"
    39  	"sigs.k8s.io/cluster-api-provider-azure/test/e2e/kubernetes/windows"
    40  	"sigs.k8s.io/cluster-api/test/framework"
    41  	"sigs.k8s.io/cluster-api/util"
    42  )
    43  
    44  // AzureLBSpecInput is the input for AzureLBSpec.
    45  type AzureLBSpecInput struct {
    46  	BootstrapClusterProxy framework.ClusterProxy
    47  	Namespace             *corev1.Namespace
    48  	ClusterName           string
    49  	SkipCleanup           bool
    50  	Windows               bool
    51  	IPFamilies            []corev1.IPFamily
    52  }
    53  
    54  // AzureLBSpec implements a test that verifies Azure internal and external load balancers can
    55  // be created and work properly through a Kubernetes service.
    56  func AzureLBSpec(ctx context.Context, inputGetter func() AzureLBSpecInput) {
    57  	var (
    58  		specName     = "azure-lb"
    59  		input        AzureLBSpecInput
    60  		clusterProxy framework.ClusterProxy
    61  		clientset    *kubernetes.Clientset
    62  	)
    63  
    64  	input = inputGetter()
    65  	Expect(input.BootstrapClusterProxy).NotTo(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
    66  	Expect(input.Namespace).NotTo(BeNil(), "Invalid argument. input.Namespace can't be nil when calling %s spec", specName)
    67  	By("creating a Kubernetes client to the workload cluster")
    68  	clusterProxy = input.BootstrapClusterProxy.GetWorkloadCluster(ctx, input.Namespace.Name, input.ClusterName)
    69  	Expect(clusterProxy).NotTo(BeNil())
    70  	clientset = clusterProxy.GetClientSet()
    71  	Expect(clientset).NotTo(BeNil())
    72  
    73  	By("creating an HTTP deployment")
    74  	deploymentName := "web" + util.RandomString(6)
    75  	// if case of input.SkipCleanup we need a unique name for windows
    76  	if input.Windows {
    77  		deploymentName = "web-windows" + util.RandomString(6)
    78  	}
    79  
    80  	webDeployment := deploymentBuilder.Create("httpd", deploymentName, corev1.NamespaceDefault)
    81  	webDeployment.AddContainerPort("http", "http", 80, corev1.ProtocolTCP)
    82  
    83  	if input.Windows {
    84  		var windowsVersion windows.OSVersion
    85  		Eventually(func(g Gomega) {
    86  			var err error
    87  			windowsVersion, err = node.GetWindowsVersion(ctx, clientset)
    88  			g.Expect(err).NotTo(HaveOccurred())
    89  		}, 300*time.Second, 5*time.Second).Should(Succeed())
    90  		iisImage := windows.GetWindowsImage(windows.Httpd, windowsVersion)
    91  		webDeployment.SetImage(deploymentName, iisImage)
    92  		webDeployment.AddWindowsSelectors()
    93  	}
    94  
    95  	deployment, err := webDeployment.Deploy(ctx, clientset)
    96  	Expect(err).NotTo(HaveOccurred())
    97  	deployInput := WaitForDeploymentsAvailableInput{
    98  		Getter:     deploymentsClientAdapter{client: webDeployment.Client(clientset)},
    99  		Deployment: deployment,
   100  		Clientset:  clientset,
   101  	}
   102  	WaitForDeploymentsAvailable(ctx, deployInput, e2eConfig.GetIntervals(specName, "wait-deployment")...)
   103  
   104  	servicesClient := clientset.CoreV1().Services(corev1.NamespaceDefault)
   105  	jobsClient := clientset.BatchV1().Jobs(corev1.NamespaceDefault)
   106  
   107  	ports := []corev1.ServicePort{
   108  		{
   109  			Name:     "http",
   110  			Port:     80,
   111  			Protocol: corev1.ProtocolTCP,
   112  		},
   113  		{
   114  			Name:     "https",
   115  			Port:     443,
   116  			Protocol: corev1.ProtocolTCP,
   117  		},
   118  	}
   119  
   120  	By("creating an internal Load Balancer service")
   121  
   122  	ilbService := webDeployment.CreateServiceResourceSpec(ports, deploymentBuilder.InternalLoadbalancer, input.IPFamilies)
   123  	Log("starting to create an internal Load Balancer service")
   124  	Eventually(func(g Gomega) {
   125  		_, err := servicesClient.Create(ctx, ilbService, metav1.CreateOptions{})
   126  		if err != nil {
   127  			LogWarningf("failed creating service (%s):%s\n", ilbService.Name, err.Error())
   128  		}
   129  		g.Expect(err).NotTo(HaveOccurred())
   130  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   131  	ilbSvcInput := WaitForServiceAvailableInput{
   132  		Getter:    servicesClientAdapter{client: servicesClient},
   133  		Service:   ilbService,
   134  		Clientset: clientset,
   135  	}
   136  	WaitForServiceAvailable(ctx, ilbSvcInput, e2eConfig.GetIntervals(specName, "wait-service")...)
   137  
   138  	By("connecting to the internal LB service from a curl pod")
   139  
   140  	var svc *corev1.Service
   141  	Eventually(func(g Gomega) {
   142  		var err error
   143  		svc, err = servicesClient.Get(ctx, ilbService.Name, metav1.GetOptions{})
   144  		if err != nil {
   145  			LogWarningf("failed getting service (%s):%s\n", ilbService.Name, err.Error())
   146  		}
   147  		g.Expect(err).NotTo(HaveOccurred())
   148  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   149  	ilbIP := extractServiceIP(svc)
   150  
   151  	ilbJob := job.CreateCurlJobResourceSpec("curl-to-ilb-job", ilbIP)
   152  	Log("starting to create a curl to ilb job")
   153  	Eventually(func(g Gomega) {
   154  		_, err := jobsClient.Create(ctx, ilbJob, metav1.CreateOptions{})
   155  		if err != nil {
   156  			LogWarningf("failed creating job (%s):%s\n", ilbJob.Name, err.Error())
   157  		}
   158  		g.Expect(err).NotTo(HaveOccurred())
   159  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   160  	ilbJobInput := WaitForJobCompleteInput{
   161  		Getter:    jobsClientAdapter{client: jobsClient},
   162  		Job:       ilbJob,
   163  		Clientset: clientset,
   164  	}
   165  	WaitForJobComplete(ctx, ilbJobInput, e2eConfig.GetIntervals(specName, "wait-job")...)
   166  
   167  	if !input.SkipCleanup {
   168  		By("deleting the ilb test resources")
   169  		Logf("starting to delete the ilb service: %s", ilbService.Name)
   170  		Eventually(func(g Gomega) {
   171  			err := servicesClient.Delete(ctx, ilbService.Name, metav1.DeleteOptions{})
   172  			if err != nil {
   173  				LogWarningf("failed deleting service (%s):%s\n", ilbService.Name, err.Error())
   174  			}
   175  			g.Expect(err).NotTo(HaveOccurred())
   176  		}, retryableDeleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   177  		Logf("waiting for the ilb service to be deleted: %s", ilbService.Name)
   178  		Eventually(func(g Gomega) {
   179  			_, err := servicesClient.Get(ctx, ilbService.GetName(), metav1.GetOptions{})
   180  			g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
   181  		}, deleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   182  		Logf("deleting the ilb job: %s", ilbJob.Name)
   183  		Eventually(func(g Gomega) {
   184  			err := jobsClient.Delete(ctx, ilbJob.Name, metav1.DeleteOptions{})
   185  			if err != nil {
   186  				LogWarningf("failed deleting job (%s):%s\n", ilbJob.Name, err.Error())
   187  			}
   188  			g.Expect(err).NotTo(HaveOccurred())
   189  		}, deleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   190  	}
   191  
   192  	By("creating an external Load Balancer service")
   193  	elbService := webDeployment.CreateServiceResourceSpec(ports, deploymentBuilder.ExternalLoadbalancer, input.IPFamilies)
   194  	Log("starting to create an external Load Balancer service")
   195  	Eventually(func(g Gomega) {
   196  		_, err := servicesClient.Create(ctx, elbService, metav1.CreateOptions{})
   197  		if err != nil {
   198  			LogWarningf("failed creating service (%s):%s\n", elbService.Name, err.Error())
   199  		}
   200  		g.Expect(err).NotTo(HaveOccurred())
   201  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   202  	elbSvcInput := WaitForServiceAvailableInput{
   203  		Getter:    servicesClientAdapter{client: servicesClient},
   204  		Service:   elbService,
   205  		Clientset: clientset,
   206  	}
   207  	WaitForServiceAvailable(ctx, elbSvcInput, e2eConfig.GetIntervals(specName, "wait-service")...)
   208  
   209  	By("connecting to the external LB service from a curl pod")
   210  	Eventually(func(g Gomega) {
   211  		var err error
   212  		svc, err = servicesClient.Get(ctx, elbService.Name, metav1.GetOptions{})
   213  		if err != nil {
   214  			LogWarningf("failed getting service (%s):%s\n", elbService.Name, err.Error())
   215  		}
   216  		g.Expect(err).NotTo(HaveOccurred())
   217  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   218  
   219  	elbIP := extractServiceIP(svc)
   220  	Log("starting to create curl-to-elb job")
   221  	elbJob := job.CreateCurlJobResourceSpec("curl-to-elb-job"+util.RandomString(6), elbIP)
   222  	Eventually(func(g Gomega) {
   223  		_, err := jobsClient.Create(ctx, elbJob, metav1.CreateOptions{})
   224  		if err != nil {
   225  			LogWarningf("failed creating job (%s):%s\n", elbJob.Name, err.Error())
   226  		}
   227  		g.Expect(err).NotTo(HaveOccurred())
   228  	}, retryableOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   229  	elbJobInput := WaitForJobCompleteInput{
   230  		Getter:    jobsClientAdapter{client: jobsClient},
   231  		Job:       elbJob,
   232  		Clientset: clientset,
   233  	}
   234  	WaitForJobComplete(ctx, elbJobInput, e2eConfig.GetIntervals(specName, "wait-job")...)
   235  
   236  	// connecting directly to the external LB service only works for IPv4
   237  	// for IPv6 this is only possible when externalTrafficPolicy is set to "Local"
   238  	if k8snet.IsIPv4String(elbIP) {
   239  		By("connecting directly to the external LB service")
   240  		url := fmt.Sprintf("http://%s", elbIP)
   241  		Log("starting attempts to connect directly to the external LB service")
   242  		resp, err := retryablehttp.Get(url)
   243  		if resp != nil {
   244  			defer resp.Body.Close()
   245  		}
   246  		Expect(err).NotTo(HaveOccurred())
   247  		Expect(resp.StatusCode).To(Equal(200))
   248  		Log("successfully connected to the external LB service")
   249  	}
   250  
   251  	if input.SkipCleanup {
   252  		return
   253  	}
   254  	By("deleting the test resources")
   255  	Logf("starting to delete external LB service %s", elbService.Name)
   256  	Eventually(func(g Gomega) {
   257  		err := servicesClient.Delete(ctx, elbService.Name, metav1.DeleteOptions{})
   258  		if err != nil {
   259  			LogWarningf("failed deleting service (%s):%s\n", elbService.Name, err.Error())
   260  		}
   261  		g.Expect(err).NotTo(HaveOccurred())
   262  	}, retryableDeleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   263  	Logf("waiting for the external LB service to be deleted: %s", elbService.Name)
   264  	Eventually(func(g Gomega) {
   265  		_, err := servicesClient.Get(ctx, elbService.GetName(), metav1.GetOptions{})
   266  		g.Expect(apierrors.IsNotFound(err)).To(BeTrue())
   267  	}, deleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   268  	Logf("starting to delete deployment %s", deployment.Name)
   269  	Eventually(func(g Gomega) {
   270  		err := webDeployment.Client(clientset).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
   271  		if err != nil {
   272  			LogWarningf("failed deleting deployment (%s):%s\n", deployment.Name, err.Error())
   273  		}
   274  		g.Expect(err).NotTo(HaveOccurred())
   275  	}, deleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   276  	Logf("starting to delete job %s", elbJob.Name)
   277  	Eventually(func(g Gomega) {
   278  		err := jobsClient.Delete(ctx, elbJob.Name, metav1.DeleteOptions{})
   279  		if err != nil {
   280  			LogWarningf("failed deleting job (%s):%s\n", elbJob.Name, err.Error())
   281  		}
   282  		g.Expect(err).NotTo(HaveOccurred())
   283  	}, deleteOperationTimeout, retryableOperationSleepBetweenRetries).Should(Succeed())
   284  }
   285  
   286  func extractServiceIP(svc *corev1.Service) string {
   287  	var ilbIP string
   288  	for _, i := range svc.Status.LoadBalancer.Ingress {
   289  		if net.ParseIP(i.IP) != nil {
   290  			if k8snet.IsIPv6String(i.IP) {
   291  				ilbIP = fmt.Sprintf("[%s]", i.IP)
   292  				break
   293  			}
   294  			ilbIP = i.IP
   295  			break
   296  		}
   297  	}
   298  
   299  	return ilbIP
   300  }