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 }