@@ -38,9 +38,10 @@ import (
3838)
3939
4040const (
41- annotationLBType = "service.beta.kubernetes.io/aws-load-balancer-type"
42- annotationLBInternal = "service.beta.kubernetes.io/aws-load-balancer-internal"
43- annotationLBTargetNodeLabels = "service.beta.kubernetes.io/aws-load-balancer-target-node-labels"
41+ annotationLBType = "service.beta.kubernetes.io/aws-load-balancer-type"
42+ annotationLBInternal = "service.beta.kubernetes.io/aws-load-balancer-internal"
43+ annotationLBTargetNodeLabels = "service.beta.kubernetes.io/aws-load-balancer-target-node-labels"
44+ annotationLBTargetGroupAttributes = "service.beta.kubernetes.io/aws-load-balancer-target-group-attributes"
4445)
4546
4647var (
@@ -142,29 +143,106 @@ var _ = Describe("[cloud-provider-aws-e2e] loadbalancer", func() {
142143 requireAffinity : true ,
143144 },
144145 // Hairpining traffic test for NLB.
145- // Hairpin connection work with target type as instance only when preserve client IP is disabled.
146- // Currently CCM does not provide an interface to create a service with that setup, making an internal
147- // Service to fail.
148- // FIXME: https://github.com/kubernetes/cloud-provider-aws/issues/1160
149- // Once issue 1160 is fixed, the skipTestFailure must be unset/false.
146+ // The target type instance (default) sets the preserve client IP attribute to true,
147+ // the NLB target group attributes are set to preserve_client_ip.enabled=false to allow hairpining traffic.
148+ // The test also validates the target group attributes are set correctly to AWS resource.
150149 {
151150 name : "NLB internal should be reachable with hairpinning traffic" ,
152151 resourceSuffix : "hp-nlb-int" ,
153152 extraAnnotations : map [string ]string {
154- annotationLBType : "nlb" ,
155- annotationLBInternal : "true" ,
153+ annotationLBType : "nlb" ,
154+ annotationLBInternal : "true" ,
155+ annotationLBTargetGroupAttributes : "preserve_client_ip.enabled=false" ,
156156 },
157- listenerCount : 1 ,
157+ listenerCount : 1 ,
158+ overrideTestRunInClusterReachableHTTP : true ,
159+ requireAffinity : true ,
158160 hookPostServiceConfig : func (cfg * e2eTestConfig ) {
159161 framework .Logf ("running hook post-service-config patching service annotations to enforce LB pins/selects target to a single node: kubernetes.io/hostname=%s" , cfg .nodeSingleSample )
160162 if cfg .svc .Annotations == nil {
161163 cfg .svc .Annotations = map [string ]string {}
162164 }
163165 cfg .svc .Annotations [annotationLBTargetNodeLabels ] = fmt .Sprintf ("kubernetes.io/hostname=%s" , cfg .nodeSingleSample )
164166 },
165- overrideTestRunInClusterReachableHTTP : true ,
166- requireAffinity : true ,
167- skipTestFailure : true ,
167+ hookPreTest : func (e2e * e2eTestConfig ) {
168+ framework .Logf ("running hook pre-test: verify target group attributes are set correctly to AWS resource" )
169+
170+ if e2e .svc .Status .LoadBalancer .Ingress [0 ].Hostname == "" && e2e .svc .Status .LoadBalancer .Ingress [0 ].IP == "" {
171+ framework .Failf ("LoadBalancer ingress is empty (no hostname or IP) for service %s/%s" , e2e .svc .Namespace , e2e .svc .Name )
172+ }
173+
174+ hostAddr := e2eservice .GetIngressPoint (& e2e .svc .Status .LoadBalancer .Ingress [0 ])
175+ framework .Logf ("Load balancer's ingress address: %s" , hostAddr )
176+
177+ if hostAddr == "" {
178+ framework .Failf ("Unable to get LoadBalancer ingress address for service %s/%s" , e2e .svc .Namespace , e2e .svc .Name )
179+ }
180+
181+ elbClient , err := getAWSClientLoadBalancer (e2e .ctx )
182+ framework .ExpectNoError (err , "failed to create AWS ELB client" )
183+
184+ // DescribeLoadBalancers API doesn't support filtering by DNS name directly
185+ // Use AWS SDK paginator to search through all load balancers
186+ foundLB , err := getAWSLoadBalancerFromDNSName (e2e .ctx , elbClient , hostAddr )
187+ framework .ExpectNoError (err , "failed to find load balancer with DNS name %s" , hostAddr )
188+ if foundLB == nil {
189+ framework .Failf ("Found load balancer is nil for DNS name %s" , hostAddr )
190+ }
191+
192+ lbARN := aws .ToString (foundLB .LoadBalancerArn )
193+ if lbARN == "" {
194+ framework .Failf ("Load balancer ARN is empty for DNS name %s" , hostAddr )
195+ }
196+ framework .Logf ("Found load balancer: %s with ARN: %s" , aws .ToString (foundLB .LoadBalancerName ), lbARN )
197+
198+ // lookup target group ARN from load balancer ARN
199+ targetGroups , err := elbClient .DescribeTargetGroups (e2e .ctx , & elbv2.DescribeTargetGroupsInput {
200+ LoadBalancerArn : aws .String (lbARN ),
201+ })
202+ framework .ExpectNoError (err , "failed to describe target groups" )
203+ framework .ExpectEqual (len (targetGroups .TargetGroups ), 1 )
204+
205+ targetGroupAttributes , err := elbClient .DescribeTargetGroupAttributes (e2e .ctx , & elbv2.DescribeTargetGroupAttributesInput {
206+ TargetGroupArn : aws .String (aws .ToString (targetGroups .TargetGroups [0 ].TargetGroupArn )),
207+ })
208+ framework .ExpectNoError (err , "failed to describe target group attributes" )
209+
210+ // verify if the target group attributes are set correctly
211+
212+ annotationToDict := map [string ]string {}
213+ for _ , v := range strings .Split (e2e .svc .Annotations [annotationLBTargetGroupAttributes ], "," ) {
214+ parts := strings .Split (v , "=" )
215+ annotationToDict [parts [0 ]] = parts [1 ]
216+ }
217+ framework .Logf ("TG attribute Annotation to dict: %v" , annotationToDict )
218+
219+ framework .Logf ("=== All Target Group Attributes from AWS ===" )
220+ for _ , attr := range targetGroupAttributes .Attributes {
221+ framework .Logf (" %s=%s" , aws .ToString (attr .Key ), aws .ToString (attr .Value ))
222+ }
223+
224+ framework .Logf ("=== Expected Target Group Attributes from Annotation ===" )
225+ for key , value := range annotationToDict {
226+ framework .Logf (" %s=%s" , key , value )
227+ }
228+
229+ // Check if our expected attributes are present and match
230+ framework .Logf ("=== Verifying Target Group Attributes ===" )
231+ for _ , attr := range targetGroupAttributes .Attributes {
232+ if expectedValue , ok := annotationToDict [aws .ToString (attr .Key )]; ok {
233+ actualValue := aws .ToString (attr .Value )
234+ framework .Logf ("Checking attribute: %s" , aws .ToString (attr .Key ))
235+ framework .Logf (" Expected: %s" , expectedValue )
236+ framework .Logf (" Actual: %s" , actualValue )
237+
238+ if actualValue != expectedValue {
239+ framework .Failf ("Target group attribute mismatch for %s: expected %s, got %s" , aws .ToString (attr .Key ), expectedValue , actualValue )
240+ } else {
241+ framework .Logf ("✓ Target group attribute %s matches expected value %s" , aws .ToString (attr .Key ), expectedValue )
242+ }
243+ }
244+ }
245+ },
168246 },
169247 }
170248
@@ -208,7 +286,23 @@ var _ = Describe("[cloud-provider-aws-e2e] loadbalancer", func() {
208286 By ("waiting for AWS load balancer provisioning" )
209287 var err error
210288 e2e .svc , err = e2e .LBJig .WaitForLoadBalancer (loadBalancerCreateTimeout )
211- framework .ExpectNoError (err )
289+ // Collect comprehensive debugging information when LoadBalancer provisioning fails
290+ if err != nil {
291+ serviceName := e2e .LBJig .Name
292+ if e2e .svc != nil {
293+ serviceName = e2e .svc .Name
294+ }
295+ framework .Logf ("ERROR: LoadBalancer provisioning failed for service %q: %v" , serviceName , err )
296+ framework .Logf ("ERROR: LoadBalancer provisioning timeout reached after %v" , loadBalancerCreateTimeout )
297+
298+ // Ensure we have detailed debugging information before failing
299+ framework .Logf ("=== LoadBalancer Provisioning Failure Debug Information ===" )
300+ gatherEventosOnFailure (e2e .ctx , e2e .kubeClient , e2e .LBJig .Namespace , e2e .LBJig .Name )
301+ framework .Logf ("=== End of LoadBalancer Provisioning Failure Debug Information ===" )
302+
303+ // Fail the test immediately to prevent further execution
304+ framework .ExpectNoError (err , "LoadBalancer provisioning failed - check debug information above" )
305+ }
212306 framework .Logf ("[AWS] Load balancer provisioned successfully" )
213307
214308 By ("creating backend server pods" )
@@ -244,7 +338,6 @@ var _ = Describe("[cloud-provider-aws-e2e] loadbalancer", func() {
244338 framework .Logf ("=== End of Service Validation Error Debug Information ===" )
245339 framework .Failf ("Service is nil after LoadBalancer provisioning for service %s" , e2e .LBJig .Name )
246340 }
247-
248341 if len (e2e .svc .Spec .Ports ) == 0 {
249342 framework .Logf ("=== Service Ports Error Debug Information ===" )
250343 framework .Logf ("Service spec: %+v" , e2e .svc .Spec )
@@ -259,6 +352,7 @@ var _ = Describe("[cloud-provider-aws-e2e] loadbalancer", func() {
259352 framework .Logf ("=== End of LoadBalancer Ingress Error Debug Information ===" )
260353 framework .Failf ("No ingress found in LoadBalancer status for service %s/%s" , e2e .svc .Namespace , e2e .svc .Name )
261354 }
355+
262356 svcPort := int (e2e .svc .Spec .Ports [0 ].Port )
263357 ingressAddress := e2eservice .GetIngressPoint (& e2e .svc .Status .LoadBalancer .Ingress [0 ])
264358 framework .Logf ("[LB-INFO] Ingress address: %s, port: %d" , ingressAddress , svcPort )
@@ -278,15 +372,15 @@ var _ = Describe("[cloud-provider-aws-e2e] loadbalancer", func() {
278372
279373 // overrideTestRunInClusterReachableHTTP changes the default test function to run the client in the cluster.
280374 if tc .overrideTestRunInClusterReachableHTTP {
281- By ("testing HTTP connectivity from internal network " )
375+ By ("testing HTTP connectivity for internal load balancer " )
282376 framework .Logf ("[TEST] Running internal connectivity test from node: %s" , e2e .nodeSingleSample )
283377 err := inClusterTestReachableHTTP (cs , ns .Name , e2e .nodeSingleSample , ingressAddress , svcPort )
284378 if err != nil && tc .skipTestFailure {
285379 Skip (err .Error ())
286380 }
287381 framework .ExpectNoError (err )
288382 } else {
289- By ("testing HTTP connectivity from external client " )
383+ By ("testing HTTP connectivity for external/internet-facing load balancer " )
290384 framework .Logf ("[TEST] Running external connectivity test to %s:%d" , ingressAddress , svcPort )
291385 e2eservice .TestReachableHTTP (ingressAddress , svcPort , e2eservice .LoadBalancerLagTimeoutAWS )
292386 }
@@ -570,9 +664,11 @@ func getAWSLoadBalancerFromDNSName(ctx context.Context, elbClient *elbv2.Client,
570664 paginator := elbv2 .NewDescribeLoadBalancersPaginator (elbClient , & elbv2.DescribeLoadBalancersInput {})
571665 for paginator .HasMorePages () {
572666 page , err := paginator .NextPage (ctx )
573- framework .ExpectNoError (err )
667+ if err != nil {
668+ return nil , fmt .Errorf ("failed to describe load balancers: %v" , err )
669+ }
574670
575- framework .Logf ("found %d load balancers" , len (page .LoadBalancers ))
671+ framework .Logf ("found %d load balancers in page " , len (page .LoadBalancers ))
576672 // Search for the load balancer with matching DNS name in this page
577673 for i := range page .LoadBalancers {
578674 if aws .ToString (page .LoadBalancers [i ].DNSName ) == lbDNSName {
@@ -587,7 +683,7 @@ func getAWSLoadBalancerFromDNSName(ctx context.Context, elbClient *elbv2.Client,
587683 }
588684
589685 if foundLB == nil {
590- framework . Failf ( "No load balancer found with DNS name: %s" , lbDNSName )
686+ return nil , fmt . Errorf ( "no load balancer found with DNS name: %s" , lbDNSName )
591687 }
592688
593689 return foundLB , nil
0 commit comments