diff --git a/docs/guide/globalaccelerator/installation.md b/docs/guide/globalaccelerator/installation.md index 13b9216ed..1e32a8c9b 100644 --- a/docs/guide/globalaccelerator/installation.md +++ b/docs/guide/globalaccelerator/installation.md @@ -40,7 +40,7 @@ The AWS Global Accelerator Controller is built into the AWS Load Balancer Contro - **Install the GlobalAccelerator Custom Resource Definition (CRD)**: ```bash - kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/config/crd/aga/bases/aga.k8s.aws_globalaccelerators.yaml + kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/refs/heads/main/config/crd/aga/aga.k8s.aws_globalaccelerators.yaml ``` Verify the CRD is installed: diff --git a/test/e2e/gateway/alb_instance_target_test.go b/test/e2e/gateway/alb_instance_target_test.go index 53052407a..ef61006c4 100644 --- a/test/e2e/gateway/alb_instance_target_test.go +++ b/test/e2e/gateway/alb_instance_target_test.go @@ -72,7 +72,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the }, } auxiliaryStack = newAuxiliaryResourceStack(ctx, tf, tgSpec, true) - httpr := buildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) + httpr := BuildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) By("deploying stack", func() { err := stack.DeployHTTP(ctx, auxiliaryStack, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) Expect(err).NotTo(HaveOccurred()) @@ -228,7 +228,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the Protocol: gwv1.HTTPProtocolType, }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndTargetGroupWeights, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndTargetGroupWeights, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -448,7 +448,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the } // HTTPRoute with incompatible hostname (should only attach to listener-no-hostname) - httpr := buildHTTPRoute([]string{"test.com"}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{"test.com"}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -482,7 +482,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the Protocol: gwv1.HTTPProtocolType, }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndFilters, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndFilters, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -562,7 +562,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMultiMatchesInSingleRule, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMultiMatchesInSingleRule, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -760,7 +760,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the Hostname: (*gwv1.Hostname)(awssdk.String(testHostname)), }, } - httpr := buildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) Expect(err).NotTo(HaveOccurred()) @@ -865,7 +865,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the Hostname: (*gwv1.Hostname)(awssdk.String(testHostname)), }, } - httpr := buildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) Expect(err).NotTo(HaveOccurred()) @@ -1010,7 +1010,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the }, }, } - httpr := buildHTTPRoute([]string{testHostname}, httpRouteRules, &gwListeners[0].Name) + httpr := BuildHTTPRoute([]string{testHostname}, httpRouteRules, &gwListeners[0].Name) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, false) @@ -1217,7 +1217,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the }, }, } - httpr := buildHTTPRoute([]string{testHostname}, httpRouteRules, &gwListeners[0].Name) + httpr := BuildHTTPRoute([]string{testHostname}, httpRouteRules, &gwListeners[0].Name) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, oidcSecret, false) @@ -1388,7 +1388,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the Hostname: (*gwv1.Hostname)(awssdk.String(testHostname)), }, } - httpr := buildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) diff --git a/test/e2e/gateway/alb_ip_target_test.go b/test/e2e/gateway/alb_ip_target_test.go index eb7771b29..dbb48b40e 100644 --- a/test/e2e/gateway/alb_ip_target_test.go +++ b/test/e2e/gateway/alb_ip_target_test.go @@ -72,7 +72,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } auxiliaryStack = newAuxiliaryResourceStack(ctx, tf, tgSpec, true) - httpr := buildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) + httpr := BuildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) By("deploying stack", func() { err := stack.DeployHTTP(ctx, auxiliaryStack, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) Expect(err).NotTo(HaveOccurred()) @@ -217,7 +217,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndTargetGroupWeights, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndTargetGroupWeights, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) Expect(err).NotTo(HaveOccurred()) @@ -432,7 +432,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{"test.com"}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{"test.com"}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -466,7 +466,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo Protocol: gwv1.HTTPProtocolType, }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndFilters, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMatchesAndFilters, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -545,7 +545,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{}, httpRouteRuleWithMultiMatchesInSingleRule, nil) + httpr := BuildHTTPRoute([]string{}, httpRouteRuleWithMultiMatchesInSingleRule, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -729,7 +729,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo } lrcSpec := elbv2gw.ListenerRuleConfigurationSpec{} - httpr := buildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) + httpr := BuildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, &gwListeners[0].Name) httpr.Spec.Rules[0].Filters = []gwv1.HTTPRouteFilter{ { Type: gwv1.HTTPRouteFilterURLRewrite, @@ -877,7 +877,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -989,7 +989,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{testHostname}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) @@ -1141,7 +1141,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, }, } - httpr := buildHTTPRoute([]string{testHostname}, httpRouteRules, nil) + httpr := BuildHTTPRoute([]string{testHostname}, httpRouteRules, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, false) @@ -1351,7 +1351,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, }, } - httpr := buildHTTPRoute([]string{testHostname}, httpRouteRules, nil) + httpr := BuildHTTPRoute([]string{testHostname}, httpRouteRules, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, oidcSecret, false) @@ -1520,7 +1520,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo }, } - httpr := buildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, nil) By("deploying stack", func() { err := stack.DeployHTTP(ctx, nil, tf, gwListeners, []*gwv1.HTTPRoute{httpr}, lbcSpec, tgSpec, lrcSpec, nil, true) diff --git a/test/e2e/gateway/alb_nlb_test.go b/test/e2e/gateway/alb_nlb_test.go index 707df40b5..956705eca 100644 --- a/test/e2e/gateway/alb_nlb_test.go +++ b/test/e2e/gateway/alb_nlb_test.go @@ -3,6 +3,9 @@ package gateway import ( "context" "fmt" + "strings" + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1" @@ -10,8 +13,6 @@ import ( "sigs.k8s.io/aws-load-balancer-controller/test/framework/verifier" gwv1 "sigs.k8s.io/gateway-api/apis/v1" gwbeta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - "strings" - "time" ) var _ = Describe("test combined ALB and NLB gateways with HTTPRoute and TCPRoute", func() { @@ -94,7 +95,7 @@ var _ = Describe("test combined ALB and NLB gateways with HTTPRoute and TCPRoute } // HTTPRoute for ALB - httpr := buildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, nil) + httpr := BuildHTTPRoute([]string{}, []gwv1.HTTPRouteRule{}, nil) By("deploying ALB stack", func() { err := albStack.DeployHTTP(ctx, nil, tf, albGwListeners, []*gwv1.HTTPRoute{httpr}, albLbcSpec, tgSpec, lrcSpec, nil, true) diff --git a/test/e2e/gateway/alb_resource_stack.go b/test/e2e/gateway/alb_resource_stack.go index 945be6d79..2d1577a70 100644 --- a/test/e2e/gateway/alb_resource_stack.go +++ b/test/e2e/gateway/alb_resource_stack.go @@ -3,6 +3,7 @@ package gateway import ( "context" "fmt" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" rbac "k8s.io/api/rbac/v1" @@ -64,9 +65,11 @@ func (s *albResourceStack) Deploy(ctx context.Context, f *framework.Framework) e }) } -func (s *albResourceStack) Cleanup(ctx context.Context, f *framework.Framework) { - s.commonStack.Cleanup(ctx, f) - s.deleteOIDCSecretWithRBAC(ctx, f) +func (s *albResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { + if err := s.commonStack.Cleanup(ctx, f); err != nil { + return err + } + return s.deleteOIDCSecretWithRBAC(ctx, f) } func (s *albResourceStack) GetLoadBalancerIngressHostname() string { @@ -77,6 +80,10 @@ func (s *albResourceStack) getListenersPortMap() map[string]string { return s.commonStack.getListenersPortMap() } +func (s *albResourceStack) GetNamespace() string { + return s.commonStack.ns.Name +} + func (s *albResourceStack) waitUntilDeploymentReady(ctx context.Context, f *framework.Framework) error { return waitUntilDeploymentReady(ctx, f, s.commonStack.dps) } diff --git a/test/e2e/gateway/alb_test_helper.go b/test/e2e/gateway/alb_test_helper.go index 23ec66a0c..a88ba7a35 100644 --- a/test/e2e/gateway/alb_test_helper.go +++ b/test/e2e/gateway/alb_test_helper.go @@ -73,14 +73,18 @@ func (s *ALBTestStack) deploy(ctx context.Context, f *framework.Framework, gwLis return s.albResourceStack.Deploy(ctx, f) } -func (s *ALBTestStack) Cleanup(ctx context.Context, f *framework.Framework) { - s.albResourceStack.Cleanup(ctx, f) +func (s *ALBTestStack) Cleanup(ctx context.Context, f *framework.Framework) error { + return s.albResourceStack.Cleanup(ctx, f) } func (s *ALBTestStack) GetLoadBalancerIngressHostName() string { return s.albResourceStack.GetLoadBalancerIngressHostname() } +func (s *ALBTestStack) GetNamespace() string { + return s.albResourceStack.GetNamespace() +} + func (s *ALBTestStack) GetWorkerNodes(ctx context.Context, f *framework.Framework) ([]corev1.Node, error) { allNodes := &corev1.NodeList{} err := f.K8sClient.List(ctx, allNodes) diff --git a/test/e2e/gateway/common_resource_stack.go b/test/e2e/gateway/common_resource_stack.go index 0edbca97c..7f840755d 100644 --- a/test/e2e/gateway/common_resource_stack.go +++ b/test/e2e/gateway/common_resource_stack.go @@ -66,12 +66,16 @@ func (s *commonResourceStack) Deploy(ctx context.Context, f *framework.Framework v.Namespace = s.ns.Name } - for _, v := range s.tgcs { - v.Namespace = s.ns.Name + if s.tgcs != nil { + for _, v := range s.tgcs { + v.Namespace = s.ns.Name + } } - for _, v := range s.lrcs { - v.Namespace = s.ns.Name + if s.lrcs != nil { + for _, v := range s.lrcs { + v.Namespace = s.ns.Name + } } s.gw.Namespace = s.ns.Name @@ -120,9 +124,11 @@ func (s *commonResourceStack) Deploy(ctx context.Context, f *framework.Framework return nil } -func (s *commonResourceStack) Cleanup(ctx context.Context, f *framework.Framework) { - _ = deleteNamespace(ctx, f, s.ns) - _ = deleteGatewayClass(ctx, f, s.gwc) +func (s *commonResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { + if err := deleteNamespace(ctx, f, s.ns); err != nil { + return err + } + return deleteGatewayClass(ctx, f, s.gwc) } func (s *commonResourceStack) GetLoadBalancerIngressHostname() string { diff --git a/test/e2e/gateway/nlb_resource_stack.go b/test/e2e/gateway/nlb_resource_stack.go index cbbed99f9..878782a45 100644 --- a/test/e2e/gateway/nlb_resource_stack.go +++ b/test/e2e/gateway/nlb_resource_stack.go @@ -2,6 +2,7 @@ package gateway import ( "context" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1" @@ -58,8 +59,8 @@ func (s *nlbResourceStack) Deploy(ctx context.Context, f *framework.Framework) e }) } -func (s *nlbResourceStack) Cleanup(ctx context.Context, f *framework.Framework) { - s.commonStack.Cleanup(ctx, f) +func (s *nlbResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { + return s.commonStack.Cleanup(ctx, f) } func (s *nlbResourceStack) GetLoadBalancerIngressHostname() string { @@ -74,6 +75,10 @@ func (s *nlbResourceStack) waitUntilDeploymentReady(ctx context.Context, f *fram return waitUntilDeploymentReady(ctx, f, s.commonStack.dps) } +func (s *nlbResourceStack) GetNamespace() string { + return s.commonStack.ns.Name +} + func (s *nlbResourceStack) createTCPRoutes(ctx context.Context, f *framework.Framework) error { for _, tcpr := range s.tcprs { f.Logger.Info("creating tcp route", "tcpr", k8s.NamespacedName(tcpr)) diff --git a/test/e2e/gateway/nlb_test_helper.go b/test/e2e/gateway/nlb_test_helper.go index a288343a9..4bbe30d21 100644 --- a/test/e2e/gateway/nlb_test_helper.go +++ b/test/e2e/gateway/nlb_test_helper.go @@ -316,14 +316,18 @@ func (s *NLBTestStack) CreateFENLBReferenceGrant(ctx context.Context, f *framewo return refGrant, nil } -func (s *NLBTestStack) Cleanup(ctx context.Context, f *framework.Framework) { - s.nlbResourceStack.Cleanup(ctx, f) +func (s *NLBTestStack) Cleanup(ctx context.Context, f *framework.Framework) error { + return s.nlbResourceStack.Cleanup(ctx, f) } func (s *NLBTestStack) GetLoadBalancerIngressHostName() string { return s.nlbResourceStack.GetLoadBalancerIngressHostname() } +func (s *NLBTestStack) GetNamespace() string { + return s.nlbResourceStack.GetNamespace() +} + func (s *NLBTestStack) GetWorkerNodes(ctx context.Context, f *framework.Framework) ([]corev1.Node, error) { allNodes := &corev1.NodeList{} err := f.K8sClient.List(ctx, allNodes) diff --git a/test/e2e/gateway/shared_resource_definitions.go b/test/e2e/gateway/shared_resource_definitions.go index 85814681f..057d05cb3 100644 --- a/test/e2e/gateway/shared_resource_definitions.go +++ b/test/e2e/gateway/shared_resource_definitions.go @@ -437,7 +437,7 @@ func buildUDPRoute(sectionName string) *gwalpha2.UDPRoute { return udpr } -func buildHTTPRoute(hostnames []string, rules []gwv1.HTTPRouteRule, sectionName *gwv1.SectionName) *gwv1.HTTPRoute { +func BuildHTTPRoute(hostnames []string, rules []gwv1.HTTPRouteRule, sectionName *gwv1.SectionName) *gwv1.HTTPRoute { routeName := fmt.Sprintf("%v-%v", defaultName, utils.RandomDNS1123Label(6)) httpr := &gwv1.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/e2e/globalaccelerator/aga_verifier.go b/test/e2e/globalaccelerator/aga_verifier.go new file mode 100644 index 000000000..29b3e95a4 --- /dev/null +++ b/test/e2e/globalaccelerator/aga_verifier.go @@ -0,0 +1,392 @@ +package globalaccelerator + +import ( + "context" + "fmt" + "net/http" + "sort" + "time" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator" + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/aws-load-balancer-controller/test/framework" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +type PortRangeExpectation struct { + FromPort int32 + ToPort int32 +} + +type PortOverrideExpectation struct { + ListenerPort int32 + EndpointPort int32 +} + +type EndpointGroupExpectation struct { + TrafficDialPercentage int32 + PortOverrides []PortOverrideExpectation + NumEndpoints int +} + +type ListenerExpectation struct { + Protocol string + PortRanges []PortRangeExpectation + ClientAffinity string + EndpointGroups []EndpointGroupExpectation +} + +type GlobalAcceleratorExpectation struct { + Name string + IPAddressType string + Status string + Listeners []ListenerExpectation +} + +func verifyGlobalAcceleratorConfiguration(ctx context.Context, f *framework.Framework, acceleratorARN string, expected GlobalAcceleratorExpectation) error { + agaClient := f.Cloud.GlobalAccelerator() + + describeAccelResp, err := agaClient.DescribeAcceleratorWithContext(ctx, &globalaccelerator.DescribeAcceleratorInput{ + AcceleratorArn: awssdk.String(acceleratorARN), + }) + if err != nil { + return err + } + + accelerator := describeAccelResp.Accelerator + if expected.Name != "" && awssdk.ToString(accelerator.Name) != expected.Name { + return fmt.Errorf("name mismatch: expected %s, got %s", expected.Name, awssdk.ToString(accelerator.Name)) + } + if expected.IPAddressType != "" && string(accelerator.IpAddressType) != expected.IPAddressType { + return fmt.Errorf("IP address type mismatch: expected %s, got %s", expected.IPAddressType, string(accelerator.IpAddressType)) + } + if expected.Status != "" && string(accelerator.Status) != expected.Status { + return fmt.Errorf("status mismatch: expected %s, got %s", expected.Status, string(accelerator.Status)) + } + + if len(expected.Listeners) > 0 { + listListenersResp, err := agaClient.ListListenersForAcceleratorWithContext(ctx, &globalaccelerator.ListListenersInput{ + AcceleratorArn: awssdk.String(acceleratorARN), + }) + if err != nil { + return err + } + if len(listListenersResp.Listeners) != len(expected.Listeners) { + return fmt.Errorf("listener count mismatch: expected %d, got %d", len(expected.Listeners), len(listListenersResp.Listeners)) + } + + for i, expectedListener := range expected.Listeners { + listener := listListenersResp.Listeners[i] + + if expectedListener.Protocol != "" && string(listener.Protocol) != expectedListener.Protocol { + return fmt.Errorf("listener[%d] protocol mismatch: expected %s, got %s", i, expectedListener.Protocol, string(listener.Protocol)) + } + if expectedListener.ClientAffinity != "" && string(listener.ClientAffinity) != expectedListener.ClientAffinity { + return fmt.Errorf("listener[%d] client affinity mismatch: expected %s, got %s", i, expectedListener.ClientAffinity, string(listener.ClientAffinity)) + } + + if len(expectedListener.PortRanges) > 0 { + if len(listener.PortRanges) != len(expectedListener.PortRanges) { + return fmt.Errorf("listener[%d] port range count mismatch: expected %d, got %d", i, len(expectedListener.PortRanges), len(listener.PortRanges)) + } + sort.Slice(listener.PortRanges, func(a, b int) bool { + return awssdk.ToInt32(listener.PortRanges[a].FromPort) < awssdk.ToInt32(listener.PortRanges[b].FromPort) + }) + sortedExpected := make([]PortRangeExpectation, len(expectedListener.PortRanges)) + copy(sortedExpected, expectedListener.PortRanges) + sort.Slice(sortedExpected, func(a, b int) bool { + return sortedExpected[a].FromPort < sortedExpected[b].FromPort + }) + for j, expectedPortRange := range sortedExpected { + if awssdk.ToInt32(listener.PortRanges[j].FromPort) != expectedPortRange.FromPort { + return fmt.Errorf("listener[%d] port range[%d] from port mismatch: expected %d, got %d", i, j, expectedPortRange.FromPort, awssdk.ToInt32(listener.PortRanges[j].FromPort)) + } + if awssdk.ToInt32(listener.PortRanges[j].ToPort) != expectedPortRange.ToPort { + return fmt.Errorf("listener[%d] port range[%d] to port mismatch: expected %d, got %d", i, j, expectedPortRange.ToPort, awssdk.ToInt32(listener.PortRanges[j].ToPort)) + } + } + } + + if len(expectedListener.EndpointGroups) > 0 { + listEGResp, err := agaClient.ListEndpointGroupsAsList(ctx, &globalaccelerator.ListEndpointGroupsInput{ + ListenerArn: listener.ListenerArn, + }) + if err != nil { + return err + } + if len(listEGResp) != len(expectedListener.EndpointGroups) { + return fmt.Errorf("listener[%d] endpoint group count mismatch: expected %d, got %d", i, len(expectedListener.EndpointGroups), len(listEGResp)) + } + + for k, expectedEG := range expectedListener.EndpointGroups { + eg := listEGResp[k] + + if expectedEG.TrafficDialPercentage > 0 && awssdk.ToFloat32(eg.TrafficDialPercentage) != float32(expectedEG.TrafficDialPercentage) { + return fmt.Errorf("listener[%d] endpoint group[%d] traffic dial percentage mismatch: expected %d, got %f", i, k, expectedEG.TrafficDialPercentage, awssdk.ToFloat32(eg.TrafficDialPercentage)) + } + + if len(expectedEG.PortOverrides) > 0 { + if len(eg.PortOverrides) != len(expectedEG.PortOverrides) { + return fmt.Errorf("listener[%d] endpoint group[%d] port override count mismatch: expected %d, got %d", i, k, len(expectedEG.PortOverrides), len(eg.PortOverrides)) + } + for l, expectedPO := range expectedEG.PortOverrides { + if awssdk.ToInt32(eg.PortOverrides[l].ListenerPort) != expectedPO.ListenerPort { + return fmt.Errorf("listener[%d] endpoint group[%d] port override[%d] listener port mismatch: expected %d, got %d", i, k, l, expectedPO.ListenerPort, awssdk.ToInt32(eg.PortOverrides[l].ListenerPort)) + } + if awssdk.ToInt32(eg.PortOverrides[l].EndpointPort) != expectedPO.EndpointPort { + return fmt.Errorf("listener[%d] endpoint group[%d] port override[%d] endpoint port mismatch: expected %d, got %d", i, k, l, expectedPO.EndpointPort, awssdk.ToInt32(eg.PortOverrides[l].EndpointPort)) + } + } + } + + if expectedEG.NumEndpoints > 0 && len(eg.EndpointDescriptions) != expectedEG.NumEndpoints { + return fmt.Errorf("listener[%d] endpoint group[%d] endpoint count mismatch: expected %d, got %d", i, k, expectedEG.NumEndpoints, len(eg.EndpointDescriptions)) + } + } + } + } + } + + return nil +} + +func waitForAcceleratorDeployed(ctx context.Context, f *framework.Framework, acceleratorARN string) error { + agaClient := f.Cloud.GlobalAccelerator() + timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Minute) + defer cancel() + + return wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + describeResp, err := agaClient.DescribeAcceleratorWithContext(timeoutCtx, &globalaccelerator.DescribeAcceleratorInput{ + AcceleratorArn: awssdk.String(acceleratorARN), + }) + if err != nil { + return false, err + } + if describeResp.Accelerator.Status != types.AcceleratorStatusDeployed { + f.Logger.Info("waiting for accelerator to be deployed", "status", string(describeResp.Accelerator.Status)) + return false, nil + } + return true, nil + }, timeoutCtx.Done()) +} + +func waitForEndpointsHealthy(ctx context.Context, f *framework.Framework, acceleratorARN string) error { + agaClient := f.Cloud.GlobalAccelerator() + timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Minute) + defer cancel() + + return wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + listListenersResp, err := agaClient.ListListenersForAcceleratorWithContext(timeoutCtx, &globalaccelerator.ListListenersInput{ + AcceleratorArn: awssdk.String(acceleratorARN), + }) + if err != nil { + return false, err + } + + hasEndpoints := false + allHealthy := true + for _, listener := range listListenersResp.Listeners { + listEGResp, err := agaClient.ListEndpointGroupsAsList(timeoutCtx, &globalaccelerator.ListEndpointGroupsInput{ + ListenerArn: listener.ListenerArn, + }) + if err != nil { + return false, err + } + + for _, eg := range listEGResp { + if len(eg.EndpointDescriptions) == 0 { + f.Logger.Info("waiting for endpoints to be added", "endpointGroupArn", awssdk.ToString(eg.EndpointGroupArn)) + return false, nil + } + hasEndpoints = true + for _, endpoint := range eg.EndpointDescriptions { + if endpoint.HealthState != types.HealthStateHealthy { + f.Logger.Info("waiting for endpoint to be healthy", + "endpointId", awssdk.ToString(endpoint.EndpointId), + "healthState", string(endpoint.HealthState)) + allHealthy = false + } + } + } + } + if !hasEndpoints { + f.Logger.Info("no endpoints found in any endpoint group") + return false, nil + } + return allHealthy, nil + }, timeoutCtx.Done()) +} + +func verifyLoadBalancerScheme(ctx context.Context, f *framework.Framework, lbHostname, expectedScheme string) error { + elbClient := f.Cloud.ELBV2() + lbs, err := elbClient.DescribeLoadBalancersAsList(ctx, &elasticloadbalancingv2.DescribeLoadBalancersInput{}) + if err != nil { + return fmt.Errorf("failed to describe load balancers: %w", err) + } + + for _, lb := range lbs { + if awssdk.ToString(lb.DNSName) == lbHostname { + actualScheme := string(lb.Scheme) + if actualScheme != expectedScheme { + return fmt.Errorf("load balancer scheme mismatch: expected %s, got %s", expectedScheme, actualScheme) + } + f.Logger.Info("verified load balancer scheme", "hostname", lbHostname, "scheme", actualScheme) + return nil + } + } + return fmt.Errorf("load balancer with hostname %s not found", lbHostname) +} + +func verifyEndpointPointsToLoadBalancer(ctx context.Context, f *framework.Framework, acceleratorARN, expectedLBHostname string) error { + agaClient := f.Cloud.GlobalAccelerator() + elbClient := f.Cloud.ELBV2() + + lbs, err := elbClient.DescribeLoadBalancersAsList(ctx, &elasticloadbalancingv2.DescribeLoadBalancersInput{}) + if err != nil { + return fmt.Errorf("failed to describe load balancers: %w", err) + } + + var expectedLBARN string + for _, lb := range lbs { + if awssdk.ToString(lb.DNSName) == expectedLBHostname { + expectedLBARN = awssdk.ToString(lb.LoadBalancerArn) + break + } + } + if expectedLBARN == "" { + return fmt.Errorf("load balancer with hostname %s not found", expectedLBHostname) + } + + listListenersResp, err := agaClient.ListListenersForAcceleratorWithContext(ctx, &globalaccelerator.ListListenersInput{ + AcceleratorArn: awssdk.String(acceleratorARN), + }) + if err != nil { + return err + } + + for _, listener := range listListenersResp.Listeners { + listEGResp, err := agaClient.ListEndpointGroupsAsList(ctx, &globalaccelerator.ListEndpointGroupsInput{ + ListenerArn: listener.ListenerArn, + }) + if err != nil { + return err + } + + for _, eg := range listEGResp { + if len(eg.EndpointDescriptions) == 0 { + return fmt.Errorf("no endpoints in endpoint group %s", awssdk.ToString(eg.EndpointGroupArn)) + } + for _, endpoint := range eg.EndpointDescriptions { + if endpoint.HealthState != types.HealthStateHealthy { + return fmt.Errorf("endpoint %s not healthy: %s", awssdk.ToString(endpoint.EndpointId), string(endpoint.HealthState)) + } + if awssdk.ToString(endpoint.EndpointId) != expectedLBARN { + return fmt.Errorf("endpoint ARN mismatch: expected %s, got %s", expectedLBARN, awssdk.ToString(endpoint.EndpointId)) + } + f.Logger.Info("verified endpoint points to correct load balancer", + "endpointId", awssdk.ToString(endpoint.EndpointId), + "expectedLBARN", expectedLBARN, + "healthState", string(endpoint.HealthState)) + } + } + } + return nil +} + +// verifyAGAStatusFields verifies GlobalAccelerator ARN and DNS name are populated +func verifyAGAStatusFields(stack *ResourceStack) { + gaARN := stack.GetGlobalAcceleratorARN() + Expect(gaARN).NotTo(BeEmpty()) + dnsName := stack.GetGlobalAcceleratorDNSName() + Expect(dnsName).NotTo(BeEmpty()) +} + +// verifyAGATrafficFlows verifies traffic reaches backend through GlobalAccelerator endpoints +func verifyAGATrafficFlows(ctx context.Context, f *framework.Framework, stack *ResourceStack, port ...int) error { + gaARN := stack.GetGlobalAcceleratorARN() + if err := waitForAcceleratorDeployed(ctx, f, gaARN); err != nil { + return err + } + if err := waitForEndpointsHealthy(ctx, f, gaARN); err != nil { + return err + } + + dnsName := stack.GetGlobalAcceleratorDNSName() + ports := []int{80} + if len(port) > 0 { + ports = port + } + + client := &http.Client{Timeout: 10 * time.Second} + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + + for _, listenerPort := range ports { + f.Logger.Info("waiting for GlobalAccelerator to stabilize routing", "dnsName", dnsName, "port", listenerPort) + err := wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + resp, err := client.Get(fmt.Sprintf("http://%v:%d/", dnsName, listenerPort)) + if err != nil { + f.Logger.Info("waiting for AGA endpoint connectivity", "port", listenerPort, "error", err.Error()) + return false, nil + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + f.Logger.Info("waiting for successful response", "port", listenerPort, "statusCode", resp.StatusCode) + return false, nil + } + return true, nil + }, timeoutCtx.Done()) + if err != nil { + return fmt.Errorf("traffic verification failed for port %d: %w", listenerPort, err) + } + } + + return nil +} + +// verifyAGATrafficFlowsViaDualStack verifies traffic reaches backend through dual-stack DNS +func verifyAGATrafficFlowsViaDualStack(ctx context.Context, f *framework.Framework, stack *ResourceStack, port ...int) error { + gaARN := stack.GetGlobalAcceleratorARN() + if err := waitForAcceleratorDeployed(ctx, f, gaARN); err != nil { + return err + } + if err := waitForEndpointsHealthy(ctx, f, gaARN); err != nil { + return err + } + + dnsName := stack.GetGlobalAcceleratorDualStackDNSName() + ports := []int{80} + if len(port) > 0 { + ports = port + } + + client := &http.Client{Timeout: 10 * time.Second} + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + + for _, listenerPort := range ports { + f.Logger.Info("waiting for GlobalAccelerator to stabilize routing", "dnsName", dnsName, "port", listenerPort) + err := wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + resp, err := client.Get(fmt.Sprintf("http://%v:%d/", dnsName, listenerPort)) + if err != nil { + f.Logger.Info("waiting for AGA endpoint connectivity", "port", listenerPort, "error", err.Error()) + return false, nil + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + f.Logger.Info("waiting for successful response", "port", listenerPort, "statusCode", resp.StatusCode) + return false, nil + } + return true, nil + }, timeoutCtx.Done()) + if err != nil { + return fmt.Errorf("traffic verification failed for port %d: %w", listenerPort, err) + } + } + + return nil +} diff --git a/test/e2e/globalaccelerator/gateway_endpoint_test.go b/test/e2e/globalaccelerator/gateway_endpoint_test.go new file mode 100644 index 000000000..edd479b14 --- /dev/null +++ b/test/e2e/globalaccelerator/gateway_endpoint_test.go @@ -0,0 +1,216 @@ +package globalaccelerator + +import ( + "context" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/test/e2e/gateway" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" + gwv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +var _ = Describe("GlobalAccelerator with Gateway endpoint", func() { + var ( + ctx context.Context + agaStack *ResourceStack + aga *agav1beta1.GlobalAccelerator + ) + + BeforeEach(func() { + if !tf.Options.EnableAGATests || !tf.Options.EnableGatewayTests { + Skip("Skipping Global Accelerator Gateway tests (requires --enable-aga-tests and --enable-gateway-tests)") + } + ctx = context.Background() + }) + + Context("Gateway endpoint with ALB", func() { + var ( + gwStack *gateway.ALBTestStack + gatewayName string + namespace string + ) + + BeforeEach(func() { + gwStack = &gateway.ALBTestStack{} + scheme := elbv2gw.LoadBalancerSchemeInternetFacing + listeners := []gwv1.Listener{{Name: "http", Protocol: gwv1.HTTPProtocolType, Port: gwv1.PortNumber(80)}} + httpRoute := gateway.BuildHTTPRoute(nil, nil, nil) + err := gwStack.DeployHTTP(ctx, nil, tf, listeners, []*gwv1.HTTPRoute{httpRoute}, elbv2gw.LoadBalancerConfigurationSpec{Scheme: &scheme}, elbv2gw.TargetGroupConfigurationSpec{}, elbv2gw.ListenerRuleConfigurationSpec{}, nil, false) + Expect(err).NotTo(HaveOccurred()) + namespace = gwStack.GetNamespace() + gatewayName = "gateway-e2e" + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if gwStack != nil { + err := gwStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + + It("Should create and verify GlobalAccelerator with ALB Gateway endpoint", func() { + acceleratorName := "aga-alb-gw-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + aga = createAGAWithGatewayEndpoint(gaName, namespace, acceleratorName, gatewayName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeGateway, + Name: awssdk.String(gatewayName), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("Gateway endpoint with NLB", func() { + var ( + gwStack *gateway.NLBTestStack + gatewayName string + namespace string + ) + + BeforeEach(func() { + gwStack = &gateway.NLBTestStack{} + scheme := elbv2gw.LoadBalancerSchemeInternetFacing + err := gwStack.Deploy(ctx, tf, nil, elbv2gw.LoadBalancerConfigurationSpec{Scheme: &scheme}, elbv2gw.TargetGroupConfigurationSpec{}, false) + Expect(err).NotTo(HaveOccurred()) + namespace = gwStack.GetNamespace() + gatewayName = "gateway-e2e" + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if gwStack != nil { + err := gwStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + + It("Should create and verify GlobalAccelerator with NLB Gateway endpoint", func() { + acceleratorName := "aga-nlb-gw-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + aga = createAGAWithGatewayEndpoint(gaName, namespace, acceleratorName, gatewayName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeGateway, + Name: awssdk.String(gatewayName), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) +}) diff --git a/test/e2e/globalaccelerator/globalaccelerator_suite_test.go b/test/e2e/globalaccelerator/globalaccelerator_suite_test.go new file mode 100644 index 000000000..7342191e2 --- /dev/null +++ b/test/e2e/globalaccelerator/globalaccelerator_suite_test.go @@ -0,0 +1,27 @@ +package globalaccelerator + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "sigs.k8s.io/aws-load-balancer-controller/test/framework" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +var tf *framework.Framework + +func TestGlobalAccelerator(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "GlobalAccelerator Suite") +} + +var _ = BeforeSuite(func() { + var err error + tf, err = framework.InitFramework() + Expect(err).NotTo(HaveOccurred()) + + if !utils.IsCommercialPartition(tf.Options.AWSRegion) { + Skip("GlobalAccelerator is only available in commercial AWS partition") + } +}) diff --git a/test/e2e/globalaccelerator/ingress_endpoint_test.go b/test/e2e/globalaccelerator/ingress_endpoint_test.go new file mode 100644 index 000000000..2449f96a1 --- /dev/null +++ b/test/e2e/globalaccelerator/ingress_endpoint_test.go @@ -0,0 +1,381 @@ +package globalaccelerator + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/test/e2e/ingress" + "sigs.k8s.io/aws-load-balancer-controller/test/framework" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +var _ = Describe("GlobalAccelerator with Ingress endpoint", func() { + var ( + ctx context.Context + agaStack *ResourceStack + ingStack *ingress.ResourceStack + namespace string + ingName string + aga *agav1beta1.GlobalAccelerator + baseName string + ) + + BeforeEach(func() { + ctx = context.Background() + if !tf.Options.EnableAGATests { + Skip("Skipping Global Accelerator Gateway tests (requires --enable-aga-tests)") + } + ns, err := tf.NSManager.AllocateNamespace(ctx, "aga-ing-e2e") + Expect(err).NotTo(HaveOccurred()) + namespace = ns.Name + baseName = "aga-ing-" + utils.RandomDNS1123Label(8) + ingName = baseName + "-ingress" + labels := map[string]string{ + "app": baseName, + } + + deployment := createDeployment(baseName, namespace, labels) + svc := createNodePortService(baseName+"-service", namespace, labels) + ing := createALBIngress(ingName, namespace, baseName+"-service") + + ingStack = ingress.NewResourceStack([]*appsv1.Deployment{deployment}, []*corev1.Service{svc}, []*networkingv1.Ingress{ing}) + err = ingStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if ingStack != nil { + err := ingStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + + Context("Basic Ingress endpoint with configuration verification", func() { + It("Should create and verify GlobalAccelerator basic lifecycle", func() { + acceleratorName := "aga-ing-basic-" + utils.RandomDNS1123Label(6) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + gaName := "aga-" + utils.RandomDNS1123Label(8) + aga = createAGAWithIngressEndpoint(gaName, namespace, acceleratorName, ingName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + {FromPort: 443, ToPort: 443}, + }, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeIngress, + Name: awssdk.String(ingName), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + {FromPort: 443, ToPort: 443}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {TrafficDialPercentage: 100, NumEndpoints: 1}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + + By("updating GlobalAccelerator port ranges", func() { + err := agaStack.UpdateGlobalAccelerator(ctx, tf, func(aga *agav1beta1.GlobalAccelerator) { + (*aga.Spec.Listeners)[0].PortRanges = &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + } + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying updated AWS configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + Eventually(func() error { + return verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {TrafficDialPercentage: 100, NumEndpoints: 1}, + }, + }, + }, + }) + }, utils.PollTimeoutLong, utils.PollIntervalMedium).Should(Succeed()) + }) + + By("verifying traffic still flows after update", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("Auto-discovery with Ingress endpoint", func() { + It("Should auto-discover protocol and ports from Ingress", func() { + acceleratorName := "aga-autodiscovery-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + aga = createAGAWithIngressEndpoint(gaName, namespace, acceleratorName, ingName, "", nil) + + By("deploying GlobalAccelerator without protocol and port ranges", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying controller auto-discovered protocol and ports from Ingress and applied CRD defaults", func() { + verifyAGAStatusFields(agaStack) + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1, TrafficDialPercentage: 100}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("IPV4 to DUAL_STACK migration", func() { + It("Should migrate from IPV4 to DUAL_STACK address type", func() { + if tf.Options.IPFamily != framework.IPv6 { + Skip("Test requires IPv6 cluster") + } + acceleratorName := "aga-migration-" + utils.RandomDNS1123Label(6) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + gaName := "aga-" + utils.RandomDNS1123Label(8) + aga = createAGAWithIngressEndpoint(gaName, namespace, acceleratorName, ingName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 80, ToPort: 80}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeIngress, + Name: awssdk.String(ingName), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator with IPV4", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator IPV4 configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {TrafficDialPercentage: 100, NumEndpoints: 1}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying IPV4 status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying IPV4 traffic flows", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + + By("updating to DUAL_STACK address type", func() { + err := agaStack.UpdateGlobalAccelerator(ctx, tf, func(aga *agav1beta1.GlobalAccelerator) { + aga.Spec.IPAddressType = agav1beta1.IPAddressTypeDualStack + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator DUAL_STACK configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + Eventually(func() error { + return verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeDualStack), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: string(types.ProtocolTcp), + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {TrafficDialPercentage: 100, NumEndpoints: 1}, + }, + }, + }, + }) + }, utils.PollTimeoutMedium, utils.PollIntervalMedium).Should(Succeed()) + }) + + By("verifying DUAL_STACK status fields", func() { + Eventually(func() string { + _ = agaStack.RefreshGlobalAcceleratorStatus(ctx, tf) + return agaStack.GetGlobalAcceleratorDualStackDNSName() + }, utils.PollTimeoutMedium, utils.PollIntervalMedium).ShouldNot(BeEmpty()) + }) + + By("verifying DUAL_STACK traffic flows", func() { + err := verifyAGATrafficFlowsViaDualStack(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("Port overrides with Ingress endpoint", func() { + It("Should configure port overrides correctly", func() { + acceleratorName := "aga-portoverride-" + utils.RandomDNS1123Label(6) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + gaName := "aga-" + utils.RandomDNS1123Label(8) + + aga = createAGAWithIngressEndpoint(gaName, namespace, acceleratorName, ingName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{{ + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 443, ToPort: 443}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + PortOverrides: &[]agav1beta1.PortOverride{ + {ListenerPort: 443, EndpointPort: 80}, + }, + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + {Type: agav1beta1.GlobalAcceleratorEndpointTypeIngress, Name: awssdk.String(ingName)}, + }, + }}, + }}) + + By("deploying GlobalAccelerator with port overrides", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying port overrides configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{{ + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 443, ToPort: 443}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{ + TrafficDialPercentage: 100, + NumEndpoints: 1, + PortOverrides: []PortOverrideExpectation{ + {ListenerPort: 443, EndpointPort: 80}, + }, + }}, + }}, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through port override", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack, 443) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) +}) diff --git a/test/e2e/globalaccelerator/multi_endpoint_test.go b/test/e2e/globalaccelerator/multi_endpoint_test.go new file mode 100644 index 000000000..a289989be --- /dev/null +++ b/test/e2e/globalaccelerator/multi_endpoint_test.go @@ -0,0 +1,139 @@ +package globalaccelerator + +import ( + "context" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/test/e2e/ingress" + "sigs.k8s.io/aws-load-balancer-controller/test/e2e/service" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +var _ = Describe("GlobalAccelerator with multiple endpoint types", func() { + var ( + ctx context.Context + agaStack *ResourceStack + svcStack *service.ResourceStack + ingStack *ingress.ResourceStack + namespace string + svcName string + ingName string + aga *agav1beta1.GlobalAccelerator + baseName string + ) + + BeforeEach(func() { + if !tf.Options.EnableAGATests { + Skip("Skipping Global Accelerator Gateway tests (requires --enable-aga-tests)") + } + ctx = context.Background() + ns, err := tf.NSManager.AllocateNamespace(ctx, "aga-multi-e2e") + Expect(err).NotTo(HaveOccurred()) + namespace = ns.Name + tf.Logger.Info("allocated namespace for multi-endpoint test", "namespace", namespace) + baseName = "aga-multi-" + utils.RandomDNS1123Label(8) + svcName = baseName + "-service" + ingName = baseName + "-ingress" + labels := map[string]string{"app": baseName} + + // Deploy Ingress endpoint resources first + ingDeployment := createDeployment(baseName+"-ing", namespace, labels) + nodeSvc := createNodePortService(baseName+"-nodesvc", namespace, labels) + ing := createALBIngress(ingName, namespace, baseName+"-nodesvc") + ingStack = ingress.NewResourceStack([]*appsv1.Deployment{ingDeployment}, []*corev1.Service{nodeSvc}, []*networkingv1.Ingress{ing}) + err = ingStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + + // Deploy Service endpoint resources in the same namespace + svcDeployment := createDeployment(baseName+"-svc", namespace, labels) + nlbAnnotations := createServiceAnnotations("nlb-ip", "internet-facing", tf.Options.IPFamily) + nlbSvc := createLoadBalancerService(svcName, labels, nlbAnnotations) + nlbSvc.Namespace = namespace + svcStack = service.NewResourceStack(svcDeployment, nlbSvc, nil, namespace, true) + err = svcStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if ingStack != nil { + err := ingStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if svcStack != nil { + err := svcStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + + Context("Multiple endpoint types with port overrides", func() { + It("Should create GlobalAccelerator with Service and Ingress endpoints", func() { + acceleratorName := "aga-multi-" + utils.RandomDNS1123Label(6) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + gaName := "aga-" + utils.RandomDNS1123Label(8) + + tf.Logger.Info("creating GlobalAccelerator with multiple endpoints in same namespace", + "namespace", namespace, + "svcName", svcName, + "ingName", ingName) + + aga = createAGA(gaName, namespace, acceleratorName, agav1beta1.IPAddressTypeIPV4, &[]agav1beta1.GlobalAcceleratorListener{{ + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 80, ToPort: 80}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + {Type: agav1beta1.GlobalAcceleratorEndpointTypeService, Name: awssdk.String(svcName)}, + {Type: agav1beta1.GlobalAcceleratorEndpointTypeIngress, Name: awssdk.String(ingName)}, + }, + }}, + }}) + + By("deploying GlobalAccelerator with multiple endpoint types", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{{ + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 80, ToPort: 80}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{ + TrafficDialPercentage: 100, + NumEndpoints: 1, + }}, + }}, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + }) +}) diff --git a/test/e2e/globalaccelerator/resource_stack.go b/test/e2e/globalaccelerator/resource_stack.go new file mode 100644 index 000000000..2d33ba607 --- /dev/null +++ b/test/e2e/globalaccelerator/resource_stack.go @@ -0,0 +1,158 @@ +package globalaccelerator + +import ( + "context" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/util/wait" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" + "sigs.k8s.io/aws-load-balancer-controller/test/framework" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// ResourceStack manages GlobalAccelerator lifecycle +type ResourceStack struct { + agaSpec *agav1beta1.GlobalAccelerator // Desired GlobalAccelerator specification + deployedAGA *agav1beta1.GlobalAccelerator // Deployed GlobalAccelerator with AWS status +} + +// NewResourceStack creates a new ResourceStack with GlobalAccelerator spec +func NewResourceStack(agaSpec *agav1beta1.GlobalAccelerator) *ResourceStack { + return &ResourceStack{ + agaSpec: agaSpec, + } +} + +// Deploy creates GlobalAccelerator +func (s *ResourceStack) Deploy(ctx context.Context, f *framework.Framework) error { + if s.agaSpec != nil { + if err := s.createGlobalAccelerator(ctx, f); err != nil { + return err + } + if err := s.waitUntilGlobalAcceleratorReady(ctx, f); err != nil { + return err + } + } + return nil +} + +// UpdateGlobalAccelerator applies changes to the GlobalAccelerator spec and waits for reconciliation +func (s *ResourceStack) UpdateGlobalAccelerator(ctx context.Context, f *framework.Framework, updateFn func(*agav1beta1.GlobalAccelerator)) error { + oldSpec := s.agaSpec.DeepCopy() + updateFn(s.agaSpec) + if err := f.K8sClient.Patch(ctx, s.agaSpec, client.MergeFrom(oldSpec)); err != nil { + return err + } + return s.waitUntilGlobalAcceleratorReady(ctx, f) +} + +// Cleanup deletes GlobalAccelerator +func (s *ResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { + if s.agaSpec != nil { + if err := s.deleteGlobalAccelerator(ctx, f); err != nil { + return err + } + } + return nil +} + +func (s *ResourceStack) GetGlobalAcceleratorARN() string { + if s.deployedAGA != nil && s.deployedAGA.Status.AcceleratorARN != nil { + return *s.deployedAGA.Status.AcceleratorARN + } + return "" +} + +func (s *ResourceStack) GetGlobalAcceleratorDNSName() string { + if s.deployedAGA != nil && s.deployedAGA.Status.DNSName != nil { + return *s.deployedAGA.Status.DNSName + } + return "" +} + +func (s *ResourceStack) GetGlobalAcceleratorDualStackDNSName() string { + if s.deployedAGA != nil && s.deployedAGA.Status.DualStackDnsName != nil { + return *s.deployedAGA.Status.DualStackDnsName + } + return "" +} + +// RefreshGlobalAcceleratorStatus fetches the latest GlobalAccelerator status from Kubernetes +func (s *ResourceStack) RefreshGlobalAcceleratorStatus(ctx context.Context, f *framework.Framework) error { + if s.agaSpec == nil { + return nil + } + observedAGA := &agav1beta1.GlobalAccelerator{} + if err := f.K8sClient.Get(ctx, k8s.NamespacedName(s.agaSpec), observedAGA); err != nil { + return err + } + s.deployedAGA = observedAGA + return nil +} + +func (s *ResourceStack) createGlobalAccelerator(ctx context.Context, f *framework.Framework) error { + f.Logger.Info("creating globalaccelerator", "aga", k8s.NamespacedName(s.agaSpec)) + if err := f.K8sClient.Create(ctx, s.agaSpec); err != nil { + return err + } + f.Logger.Info("created globalaccelerator", "aga", k8s.NamespacedName(s.agaSpec)) + return nil +} + +func (s *ResourceStack) waitUntilGlobalAcceleratorReady(ctx context.Context, f *framework.Framework) error { + f.Logger.Info("waiting for globalaccelerator to be ready", "aga", k8s.NamespacedName(s.agaSpec)) + var err error + s.deployedAGA, err = waitUntilGlobalAcceleratorActive(ctx, f, s.agaSpec) + if err != nil { + return err + } + f.Logger.Info("globalaccelerator is ready", "aga", k8s.NamespacedName(s.agaSpec)) + return nil +} + +func (s *ResourceStack) deleteGlobalAccelerator(ctx context.Context, f *framework.Framework) error { + f.Logger.Info("deleting globalaccelerator", "aga", k8s.NamespacedName(s.agaSpec)) + if err := f.K8sClient.Delete(ctx, s.agaSpec); err != nil { + return err + } + if err := waitUntilGlobalAcceleratorDeleted(ctx, f, s.agaSpec); err != nil { + return err + } + f.Logger.Info("deleted globalaccelerator", "aga", k8s.NamespacedName(s.agaSpec)) + return nil +} + +// waitUntilGlobalAcceleratorActive polls until GlobalAccelerator is provisioned in AWS with status DEPLOYED +func waitUntilGlobalAcceleratorActive(ctx context.Context, f *framework.Framework, aga *agav1beta1.GlobalAccelerator) (*agav1beta1.GlobalAccelerator, error) { + observedAGA := &agav1beta1.GlobalAccelerator{} + return observedAGA, wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + if err := f.K8sClient.Get(ctx, k8s.NamespacedName(aga), observedAGA); err != nil { + return false, err + } + // Check if AWS has populated ARN and DNS + if observedAGA.Status.AcceleratorARN == nil || observedAGA.Status.DNSName == nil { + return false, nil + } + // Check if status is DEPLOYED + if observedAGA.Status.Status != nil && *observedAGA.Status.Status == "DEPLOYED" { + return true, nil + } + return false, nil + }, ctx.Done()) +} + +// waitUntilGlobalAcceleratorDeleted polls until GlobalAccelerator resource is removed from K8s +func waitUntilGlobalAcceleratorDeleted(ctx context.Context, f *framework.Framework, aga *agav1beta1.GlobalAccelerator) error { + observedAGA := &agav1beta1.GlobalAccelerator{} + return wait.PollImmediateUntil(utils.PollIntervalMedium, func() (bool, error) { + if err := f.K8sClient.Get(ctx, k8s.NamespacedName(aga), observedAGA); err != nil { + if apierrors.IsNotFound(err) { + return true, nil + } + return false, err + } + return false, nil + }, ctx.Done()) +} diff --git a/test/e2e/globalaccelerator/service_endpoint_test.go b/test/e2e/globalaccelerator/service_endpoint_test.go new file mode 100644 index 000000000..e80abc8c9 --- /dev/null +++ b/test/e2e/globalaccelerator/service_endpoint_test.go @@ -0,0 +1,564 @@ +package globalaccelerator + +import ( + "context" + + awssdk "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/globalaccelerator/types" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" + "sigs.k8s.io/aws-load-balancer-controller/test/e2e/service" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +var _ = Describe("GlobalAccelerator with Service endpoint", func() { + var ( + ctx context.Context + agaStack *ResourceStack + aga *agav1beta1.GlobalAccelerator + ) + + BeforeEach(func() { + if !tf.Options.EnableAGATests { + Skip("Skipping Global Accelerator Gateway tests (requires --enable-aga-tests)") + } + ctx = context.Background() + }) + + Context("Service endpoint with IP target type", func() { + var ( + svcStack *service.ResourceStack + namespace string + svcName string + ) + + BeforeEach(func() { + baseName := "aga-svc-" + utils.RandomDNS1123Label(8) + svcName = baseName + "-service" + labels := map[string]string{ + "app": baseName, + } + + deployment := createDeployment(baseName, "", labels) + annotations := createServiceAnnotations("nlb-ip", "internet-facing", tf.Options.IPFamily) + svc := createLoadBalancerService(svcName, labels, annotations) + + svcStack = service.NewResourceStack(deployment, svc, nil, baseName, false) + err := svcStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + namespace = svcStack.GetNamespace() + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if svcStack != nil { + err := svcStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + It("Should create and verify GlobalAccelerator lifecycle", func() { + acceleratorName := "aga-svc-ip-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + aga = createAGAWithServiceEndpoint(gaName, namespace, acceleratorName, svcName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(svcName), + Weight: awssdk.Int32(128), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: "TCP", + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1, TrafficDialPercentage: 100}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + + By("updating GlobalAccelerator configuration", func() { + err := agaStack.UpdateGlobalAccelerator(ctx, tf, func(ga *agav1beta1.GlobalAccelerator) { + (*ga.Spec.Listeners)[0].ClientAffinity = agav1beta1.ClientAffinitySourceIP + (*(*ga.Spec.Listeners)[0].EndpointGroups)[0].TrafficDialPercentage = awssdk.Int32(50) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying updated AWS configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + Eventually(func() error { + return verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: "TCP", + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinitySourceIp), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1, TrafficDialPercentage: 50}, + }, + }, + }, + }) + }, utils.PollTimeoutLong, utils.PollIntervalMedium).Should(Succeed()) + }) + + By("verifying traffic still flows after update", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("Service endpoint with Instance target type", func() { + var ( + instanceSvcStack *service.ResourceStack + instanceSvcName string + instanceNs string + ) + + BeforeEach(func() { + instanceBaseName := "aga-inst-" + utils.RandomDNS1123Label(8) + instanceSvcName = instanceBaseName + "-service" + labels := map[string]string{ + "app": instanceBaseName, + } + + deployment := createDeployment(instanceBaseName, "", labels) + annotations := createServiceAnnotations("external", "internet-facing", tf.Options.IPFamily) + annotations["service.beta.kubernetes.io/aws-load-balancer-nlb-target-type"] = "instance" + svc := createLoadBalancerService(instanceSvcName, labels, annotations) + + instanceSvcStack = service.NewResourceStack(deployment, svc, nil, instanceBaseName, false) + err := instanceSvcStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + instanceNs = instanceSvcStack.GetNamespace() + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if instanceSvcStack != nil { + err := instanceSvcStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + + It("Should update GlobalAccelerator endpoint when load balancer scheme changes", func() { + acceleratorName := "aga-svc-ins" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + aga = createAGAWithServiceEndpoint(gaName, instanceNs, acceleratorName, instanceSvcName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(instanceSvcName), + Weight: awssdk.Int32(128), + }, + }, + }, + }, + }, + }) + + By("deploying GlobalAccelerator", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: "TCP", + PortRanges: []PortRangeExpectation{ + {FromPort: 80, ToPort: 80}, + }, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{ + {NumEndpoints: 1, TrafficDialPercentage: 100}, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + + var originalLBHostname string + By("capturing original load balancer hostname", func() { + svc := &corev1.Service{} + svcKey := k8s.NamespacedName(&corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: instanceSvcName, + Namespace: instanceNs, + }, + }) + err := tf.K8sClient.Get(ctx, svcKey, svc) + Expect(err).NotTo(HaveOccurred()) + Expect(svc.Status.LoadBalancer.Ingress).NotTo(BeEmpty()) + originalLBHostname = svc.Status.LoadBalancer.Ingress[0].Hostname + Expect(originalLBHostname).NotTo(BeEmpty()) + }) + + By("updating service scheme to internal", func() { + svc := &corev1.Service{} + svcKey := k8s.NamespacedName(&corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: instanceSvcName, + Namespace: instanceNs, + }, + }) + err := tf.K8sClient.Get(ctx, svcKey, svc) + Expect(err).NotTo(HaveOccurred()) + svc.Annotations["service.beta.kubernetes.io/aws-load-balancer-scheme"] = "internal" + err = tf.K8sClient.Update(ctx, svc) + Expect(err).NotTo(HaveOccurred()) + }) + + var newLBHostname string + By("verifying load balancer was replaced with internal scheme", func() { + svcKey := k8s.NamespacedName(&corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: instanceSvcName, + Namespace: instanceNs, + }, + }) + Eventually(func() bool { + svc := &corev1.Service{} + if err := tf.K8sClient.Get(ctx, svcKey, svc); err != nil { + return false + } + if len(svc.Status.LoadBalancer.Ingress) == 0 { + return false + } + newLBHostname = svc.Status.LoadBalancer.Ingress[0].Hostname + return newLBHostname != "" && newLBHostname != originalLBHostname + }, utils.PollTimeoutLong, utils.PollIntervalMedium).Should(BeTrue()) + + err := verifyLoadBalancerScheme(ctx, tf, newLBHostname, "internal") + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator updated to new load balancer", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + Eventually(func() error { + return verifyEndpointPointsToLoadBalancer(ctx, tf, gaARN, newLBHostname) + }, utils.PollTimeoutLong*2, utils.PollIntervalMedium).Should(Succeed()) + }) + }) + + It("Should create GlobalAccelerator with direct endpoint ID", func() { + acceleratorName := "aga-endpointid-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + + var lbARN string + By("getting load balancer ARN", func() { + svc := &corev1.Service{} + svcKey := k8s.NamespacedName(&corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: instanceSvcName, + Namespace: instanceNs, + }, + }) + err := tf.K8sClient.Get(ctx, svcKey, svc) + Expect(err).NotTo(HaveOccurred()) + Expect(svc.Status.LoadBalancer.Ingress).NotTo(BeEmpty()) + lbHostname := svc.Status.LoadBalancer.Ingress[0].Hostname + lbARN, err = tf.LBManager.FindLoadBalancerByDNSName(ctx, lbHostname) + Expect(err).NotTo(HaveOccurred()) + Expect(lbARN).NotTo(BeEmpty()) + }) + + aga = createAGAWithEndpointID(gaName, instanceNs, acceleratorName, lbARN, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{{ + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 80, ToPort: 80}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{{ + Type: agav1beta1.GlobalAcceleratorEndpointTypeEndpointID, + EndpointID: awssdk.String(lbARN), + }}, + }}, + }}) + + By("deploying GlobalAccelerator with endpoint ID", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{{ + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 80, ToPort: 80}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{NumEndpoints: 1, TrafficDialPercentage: 100}}, + }}, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through GlobalAccelerator", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + + Context("Service endpoint with instance target type and multiple listeners", func() { + var ( + instanceSvcStack *service.ResourceStack + instanceSvcName string + instanceNs string + ) + + BeforeEach(func() { + instanceBaseName := "aga-inst-" + utils.RandomDNS1123Label(8) + instanceSvcName = instanceBaseName + "-service" + labels := map[string]string{ + "app": instanceBaseName, + } + + deployment := createDeployment(instanceBaseName, "", labels) + annotations := createServiceAnnotations("external", "internet-facing", tf.Options.IPFamily) + annotations["service.beta.kubernetes.io/aws-load-balancer-nlb-target-type"] = "instance" + svc := createLoadBalancerServiceWithPorts(instanceSvcName, labels, annotations, 80, 443) + + instanceSvcStack = service.NewResourceStack(deployment, svc, nil, instanceBaseName, false) + err := instanceSvcStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + instanceNs = instanceSvcStack.GetNamespace() + }) + + AfterEach(func() { + if agaStack != nil { + err := agaStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + if instanceSvcStack != nil { + err := instanceSvcStack.Cleanup(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + } + }) + It("Should create GlobalAccelerator with two listeners on ports 80 and 443", func() { + acceleratorName := "aga-multi-listener-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + + aga = createAGAWithServiceEndpoint(gaName, instanceNs, acceleratorName, instanceSvcName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{ + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 80, ToPort: 80}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{{ + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(instanceSvcName), + Weight: awssdk.Int32(128), + }}, + }}, + }, + { + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 443, ToPort: 443}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{{ + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(instanceSvcName), + Weight: awssdk.Int32(128), + }}, + }}, + }, + }) + + By("deploying GlobalAccelerator with two listeners", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration with two listeners", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{ + { + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 80, ToPort: 80}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{NumEndpoints: 1, TrafficDialPercentage: 100}}, + }, + { + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 443, ToPort: 443}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{NumEndpoints: 1, TrafficDialPercentage: 100}}, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through both listeners", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack, 80, 443) + Expect(err).NotTo(HaveOccurred()) + }) + }) + + It("Should create GlobalAccelerator with port range 80-443", func() { + acceleratorName := "aga-port-range-" + utils.RandomDNS1123Label(6) + gaName := "aga-" + utils.RandomDNS1123Label(8) + protocol := agav1beta1.GlobalAcceleratorProtocolTCP + + aga = createAGAWithServiceEndpoint(gaName, instanceNs, acceleratorName, instanceSvcName, agav1beta1.IPAddressTypeIPV4, + &[]agav1beta1.GlobalAcceleratorListener{{ + Protocol: &protocol, + PortRanges: &[]agav1beta1.PortRange{{FromPort: 80, ToPort: 443}}, + ClientAffinity: agav1beta1.ClientAffinityNone, + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{{ + TrafficDialPercentage: awssdk.Int32(100), + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{{ + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(instanceSvcName), + Weight: awssdk.Int32(128), + }}, + }}, + }}) + + By("deploying GlobalAccelerator with port range", func() { + agaStack = NewResourceStack(aga) + err := agaStack.Deploy(ctx, tf) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying AWS GlobalAccelerator configuration with port range", func() { + gaARN := agaStack.GetGlobalAcceleratorARN() + err := verifyGlobalAcceleratorConfiguration(ctx, tf, gaARN, GlobalAcceleratorExpectation{ + Name: acceleratorName, + IPAddressType: string(types.IpAddressTypeIpv4), + Status: string(types.AcceleratorStatusDeployed), + Listeners: []ListenerExpectation{{ + Protocol: "TCP", + PortRanges: []PortRangeExpectation{{FromPort: 80, ToPort: 443}}, + ClientAffinity: string(types.ClientAffinityNone), + EndpointGroups: []EndpointGroupExpectation{{NumEndpoints: 1, TrafficDialPercentage: 100}}, + }}, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + By("verifying GlobalAccelerator status fields", func() { + verifyAGAStatusFields(agaStack) + }) + + By("verifying traffic flows through port range", func() { + err := verifyAGATrafficFlows(ctx, tf, agaStack, 80, 443) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) + +}) diff --git a/test/e2e/globalaccelerator/test_helpers_test.go b/test/e2e/globalaccelerator/test_helpers_test.go new file mode 100644 index 000000000..1104be233 --- /dev/null +++ b/test/e2e/globalaccelerator/test_helpers_test.go @@ -0,0 +1,254 @@ +package globalaccelerator + +import ( + awssdk "github.com/aws/aws-sdk-go-v2/aws" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" + "sigs.k8s.io/aws-load-balancer-controller/test/framework" + "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" +) + +// createAGA creates a GlobalAccelerator resource with the specified configuration +func createAGA(name, namespace, acceleratorName string, ipAddressType agav1beta1.IPAddressType, listeners *[]agav1beta1.GlobalAcceleratorListener) *agav1beta1.GlobalAccelerator { + return &agav1beta1.GlobalAccelerator{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: agav1beta1.GlobalAcceleratorSpec{ + Name: &acceleratorName, + IPAddressType: ipAddressType, + Listeners: listeners, + }, + } +} + +// createAGAWithIngressEndpoint creates a GlobalAccelerator with an Ingress endpoint +// Pass nil for listeners to use auto-discovery (omits Protocol, PortRanges, ClientAffinity, TrafficDialPercentage) +func createAGAWithIngressEndpoint(name, namespace, acceleratorName, endpointName string, ipAddressType agav1beta1.IPAddressType, listeners *[]agav1beta1.GlobalAcceleratorListener) *agav1beta1.GlobalAccelerator { + if listeners == nil { + listeners = &[]agav1beta1.GlobalAcceleratorListener{ + { + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeIngress, + Name: awssdk.String(endpointName), + }, + }, + }, + }, + }, + } + } + return createAGA(name, namespace, acceleratorName, ipAddressType, listeners) +} + +// createAGAWithServiceEndpoint creates a GlobalAccelerator with a Service endpoint +// Pass nil for listeners to use auto-discovery (omits Protocol, PortRanges, ClientAffinity, TrafficDialPercentage) +func createAGAWithServiceEndpoint(name, namespace, acceleratorName, endpointName string, ipAddressType agav1beta1.IPAddressType, listeners *[]agav1beta1.GlobalAcceleratorListener) *agav1beta1.GlobalAccelerator { + if listeners == nil { + listeners = &[]agav1beta1.GlobalAcceleratorListener{ + { + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeService, + Name: awssdk.String(endpointName), + }, + }, + }, + }, + }, + } + } + return createAGA(name, namespace, acceleratorName, ipAddressType, listeners) +} + +// createAGAWithGatewayEndpoint creates a GlobalAccelerator with a Gateway endpoint +// Pass nil for listeners to use auto-discovery (omits Protocol, PortRanges, ClientAffinity, TrafficDialPercentage) +func createAGAWithGatewayEndpoint(name, namespace, acceleratorName, endpointName string, ipAddressType agav1beta1.IPAddressType, listeners *[]agav1beta1.GlobalAcceleratorListener) *agav1beta1.GlobalAccelerator { + if listeners == nil { + listeners = &[]agav1beta1.GlobalAcceleratorListener{ + { + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeGateway, + Name: awssdk.String(endpointName), + }, + }, + }, + }, + }, + } + } + return createAGA(name, namespace, acceleratorName, ipAddressType, listeners) +} + +// createAGAWithEndpointID creates a GlobalAccelerator with a direct endpoint ID (ARN) +// Pass nil for listeners to use auto-discovery (omits Protocol, PortRanges, ClientAffinity, TrafficDialPercentage) +func createAGAWithEndpointID(name, namespace, acceleratorName, endpointID string, ipAddressType agav1beta1.IPAddressType, listeners *[]agav1beta1.GlobalAcceleratorListener) *agav1beta1.GlobalAccelerator { + if listeners == nil { + listeners = &[]agav1beta1.GlobalAcceleratorListener{ + { + EndpointGroups: &[]agav1beta1.GlobalAcceleratorEndpointGroup{ + { + Endpoints: &[]agav1beta1.GlobalAcceleratorEndpoint{ + { + Type: agav1beta1.GlobalAcceleratorEndpointTypeEndpointID, + EndpointID: awssdk.String(endpointID), + }, + }, + }, + }, + }, + } + } + return createAGA(name, namespace, acceleratorName, ipAddressType, listeners) +} + +// createDeployment creates a deployment for testing +func createDeployment(baseName, namespace string, labels map[string]string) *appsv1.Deployment { + replicas := int32(2) + dpImage := utils.GetDeploymentImage(tf.Options.TestImageRegistry, utils.HelloImage) + + return &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: baseName + "-deployment", + Namespace: namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + Image: dpImage, + ImagePullPolicy: corev1.PullAlways, + Ports: []corev1.ContainerPort{ + {ContainerPort: 80}, + }, + }, + }, + }, + }, + }, + } +} + +func createNodePortService(svcName, namespace string, labels map[string]string) *corev1.Service { + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: svcName, + Namespace: namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Selector: labels, + Ports: []corev1.ServicePort{ + { + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + }, + }, + }, + } +} + +func createALBIngress(ingName, namespace, serviceName string) *networkingv1.Ingress { + pathType := networkingv1.PathTypePrefix + annotations := map[string]string{ + "alb.ingress.kubernetes.io/scheme": "internet-facing", + } + if tf.Options.IPFamily == framework.IPv6 { + annotations["alb.ingress.kubernetes.io/ip-address-type"] = "dualstack" + } + + return &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: ingName, + Namespace: namespace, + Annotations: annotations, + }, + Spec: networkingv1.IngressSpec{ + IngressClassName: awssdk.String("alb"), + Rules: []networkingv1.IngressRule{ + { + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: serviceName, + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func createLoadBalancerService(svcName string, labels map[string]string, annotations map[string]string) *corev1.Service { + return createLoadBalancerServiceWithPorts(svcName, labels, annotations, 80) +} + +func createLoadBalancerServiceWithPorts(svcName string, labels map[string]string, annotations map[string]string, ports ...int32) *corev1.Service { + servicePorts := make([]corev1.ServicePort, len(ports)) + for i, port := range ports { + portName := intstr.FromInt(int(port)) + servicePorts[i] = corev1.ServicePort{ + Name: "port-" + portName.String(), + Port: port, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + } + } + return &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: svcName, + Annotations: annotations, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + Selector: labels, + Ports: servicePorts, + }, + } +} + +func createServiceAnnotations(lbType, scheme string, ipFamily string) map[string]string { + annotations := map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-type": lbType, + "service.beta.kubernetes.io/aws-load-balancer-scheme": scheme, + } + if ipFamily == framework.IPv6 { + annotations["service.beta.kubernetes.io/aws-load-balancer-ip-address-type"] = "dualstack" + } + return annotations +} diff --git a/test/e2e/ingress/multi_path_backend.go b/test/e2e/ingress/multi_path_backend.go index ddef4157b..a71676486 100644 --- a/test/e2e/ingress/multi_path_backend.go +++ b/test/e2e/ingress/multi_path_backend.go @@ -47,7 +47,7 @@ func NewMultiPathBackendStack(namespacedResourcesCFGs map[string]NamespacedResou enablePodReadinessGate: enablePodReadinessGate, nsByNSID: make(map[string]*corev1.Namespace), - resStackByNSID: make(map[string]*resourceStack), + resStackByNSID: make(map[string]*ResourceStack), ingByIngIDByNSID: make(map[string]map[string]*networking.Ingress), } } @@ -58,7 +58,7 @@ type multiPathBackendStack struct { // runtime variables nsByNSID map[string]*corev1.Namespace - resStackByNSID map[string]*resourceStack + resStackByNSID map[string]*ResourceStack ingByIngIDByNSID map[string]map[string]*networking.Ingress } @@ -173,7 +173,7 @@ func (s *multiPathBackendStack) cleanupResourceStacks(ctx context.Context, f *fr for nsID, resStack := range s.resStackByNSID { wg.Add(1) - go func(nsID string, resStack *resourceStack) { + go func(nsID string, resStack *ResourceStack) { defer wg.Done() f.Logger.Info("begin cleanup resource stack", "nsID", nsID) if err := resStack.Cleanup(ctx, f); err != nil { @@ -193,8 +193,8 @@ func (s *multiPathBackendStack) cleanupResourceStacks(ctx context.Context, f *fr return nil } -func (s *multiPathBackendStack) buildResourceStacks(namespacedResourcesCFGs map[string]NamespacedResourcesConfig, nsByNSID map[string]*corev1.Namespace, f *framework.Framework) (map[string]*resourceStack, map[string]map[string]*networking.Ingress) { - resStackByNSID := make(map[string]*resourceStack, len(namespacedResourcesCFGs)) +func (s *multiPathBackendStack) buildResourceStacks(namespacedResourcesCFGs map[string]NamespacedResourcesConfig, nsByNSID map[string]*corev1.Namespace, f *framework.Framework) (map[string]*ResourceStack, map[string]map[string]*networking.Ingress) { + resStackByNSID := make(map[string]*ResourceStack, len(namespacedResourcesCFGs)) ingByIngIDByNSID := make(map[string]map[string]*networking.Ingress, len(namespacedResourcesCFGs)) for nsID, resCFG := range namespacedResourcesCFGs { ns := nsByNSID[nsID] @@ -205,7 +205,7 @@ func (s *multiPathBackendStack) buildResourceStacks(namespacedResourcesCFGs map[ return resStackByNSID, ingByIngIDByNSID } -func (s *multiPathBackendStack) buildResourceStack(ns *corev1.Namespace, resourcesCFG NamespacedResourcesConfig, f *framework.Framework) (*resourceStack, map[string]*networking.Ingress) { +func (s *multiPathBackendStack) buildResourceStack(ns *corev1.Namespace, resourcesCFG NamespacedResourcesConfig, f *framework.Framework) (*ResourceStack, map[string]*networking.Ingress) { dpByBackendID, svcByBackendID := s.buildBackendResources(ns, resourcesCFG.BackendCFGs, f.Options.TestImageRegistry) ingByIngID := s.buildIngressResources(ns, resourcesCFG.IngCFGs, svcByBackendID, f) diff --git a/test/e2e/ingress/resource_stack.go b/test/e2e/ingress/resource_stack.go index 98ae9f7f1..0aaed4295 100644 --- a/test/e2e/ingress/resource_stack.go +++ b/test/e2e/ingress/resource_stack.go @@ -2,25 +2,26 @@ package ingress import ( "context" + "sync" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" networking "k8s.io/api/networking/v1" "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s" "sigs.k8s.io/aws-load-balancer-controller/test/framework" "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" - "sync" ) -func NewResourceStack(dps []*appsv1.Deployment, svcs []*corev1.Service, ings []*networking.Ingress) *resourceStack { - return &resourceStack{ +func NewResourceStack(dps []*appsv1.Deployment, svcs []*corev1.Service, ings []*networking.Ingress) *ResourceStack { + return &ResourceStack{ dps: dps, svcs: svcs, ings: ings, } } -// resourceStack can deploy and cleanup itself from a Kubernetes cluster. -type resourceStack struct { +// ResourceStack can deploy and cleanup itself from a Kubernetes cluster. +type ResourceStack struct { // configurations dps []*appsv1.Deployment svcs []*corev1.Service @@ -37,7 +38,7 @@ type resourceStack struct { createdINGsMutex sync.Mutex } -func (s *resourceStack) Deploy(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) Deploy(ctx context.Context, f *framework.Framework) error { if err := s.createDeployments(ctx, f); err != nil { return err } @@ -56,7 +57,7 @@ func (s *resourceStack) Deploy(ctx context.Context, f *framework.Framework) erro return nil } -func (s *resourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { if err := s.cleanupIngresses(ctx, f); err != nil { return err } @@ -69,7 +70,7 @@ func (s *resourceStack) Cleanup(ctx context.Context, f *framework.Framework) err return nil } -func (s *resourceStack) createDeployments(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) createDeployments(ctx context.Context, f *framework.Framework) error { f.Logger.Info("create all deployments") var createErrs []error var createErrsMutex sync.Mutex @@ -101,7 +102,7 @@ func (s *resourceStack) createDeployments(ctx context.Context, f *framework.Fram return nil } -func (s *resourceStack) waitDeploymentsBecomesReady(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) waitDeploymentsBecomesReady(ctx context.Context, f *framework.Framework) error { f.Logger.Info("wait all deployments becomes ready") var waitErrs []error var waitErrsMutex sync.Mutex @@ -133,7 +134,7 @@ func (s *resourceStack) waitDeploymentsBecomesReady(ctx context.Context, f *fram return nil } -func (s *resourceStack) cleanupDeployments(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) cleanupDeployments(ctx context.Context, f *framework.Framework) error { f.Logger.Info("cleanup all deployments") var cleanupErrs []error var cleanupErrsMutex sync.Mutex @@ -169,7 +170,7 @@ func (s *resourceStack) cleanupDeployments(ctx context.Context, f *framework.Fra return nil } -func (s *resourceStack) createServices(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) createServices(ctx context.Context, f *framework.Framework) error { f.Logger.Info("create all services") var createErrs []error var createErrsMutex sync.Mutex @@ -201,7 +202,7 @@ func (s *resourceStack) createServices(ctx context.Context, f *framework.Framewo return nil } -func (s *resourceStack) cleanupServices(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) cleanupServices(ctx context.Context, f *framework.Framework) error { f.Logger.Info("cleanup all services") var cleanupErrs []error var cleanupErrsMutex sync.Mutex @@ -237,7 +238,7 @@ func (s *resourceStack) cleanupServices(ctx context.Context, f *framework.Framew return nil } -func (s *resourceStack) createIngresses(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) createIngresses(ctx context.Context, f *framework.Framework) error { f.Logger.Info("create all ingresses") var createErrs []error var createErrsMutex sync.Mutex @@ -269,7 +270,7 @@ func (s *resourceStack) createIngresses(ctx context.Context, f *framework.Framew return nil } -func (s *resourceStack) waitIngressesBecomesReady(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) waitIngressesBecomesReady(ctx context.Context, f *framework.Framework) error { f.Logger.Info("wait all ingresses becomes ready") var waitErrs []error var waitErrsMutex sync.Mutex @@ -301,7 +302,7 @@ func (s *resourceStack) waitIngressesBecomesReady(ctx context.Context, f *framew return nil } -func (s *resourceStack) cleanupIngresses(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) cleanupIngresses(ctx context.Context, f *framework.Framework) error { f.Logger.Info("cleanup all ingresses") var cleanupErrs []error var cleanupErrsMutex sync.Mutex @@ -336,3 +337,7 @@ func (s *resourceStack) cleanupIngresses(ctx context.Context, f *framework.Frame } return nil } + +func (s *ResourceStack) GetNamespace() string { + return s.ings[0].Namespace +} diff --git a/test/e2e/service/nlb_instance_target.go b/test/e2e/service/nlb_instance_target.go index 0531548ba..284b0f2cf 100644 --- a/test/e2e/service/nlb_instance_target.go +++ b/test/e2e/service/nlb_instance_target.go @@ -2,6 +2,7 @@ package service import ( "context" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -19,7 +20,7 @@ const ( ) type NLBInstanceTestStack struct { - resourceStack *resourceStack + resourceStack *ResourceStack } func (s *NLBInstanceTestStack) Deploy(ctx context.Context, f *framework.Framework, svcAnnotations map[string]string, svcs []*corev1.Service) error { diff --git a/test/e2e/service/nlb_ip_target.go b/test/e2e/service/nlb_ip_target.go index ba2e6d070..b0c757291 100644 --- a/test/e2e/service/nlb_ip_target.go +++ b/test/e2e/service/nlb_ip_target.go @@ -4,16 +4,17 @@ import ( "context" "crypto/tls" "fmt" + "net/http" + "time" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "net/http" "sigs.k8s.io/aws-load-balancer-controller/test/framework" "sigs.k8s.io/aws-load-balancer-controller/test/framework/utils" - "time" ) type NLBIPTestStack struct { - resourceStack *resourceStack + resourceStack *ResourceStack } func (s *NLBIPTestStack) Deploy(ctx context.Context, f *framework.Framework, svc *corev1.Service, dp *appsv1.Deployment, svcs []*corev1.Service) error { diff --git a/test/e2e/service/resource_stack.go b/test/e2e/service/resource_stack.go index be7b50013..34231a10e 100644 --- a/test/e2e/service/resource_stack.go +++ b/test/e2e/service/resource_stack.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -func NewResourceStack(dp *appsv1.Deployment, svc *corev1.Service, svcs []*corev1.Service, baseName string, enablePodReadinessGate bool) *resourceStack { - return &resourceStack{ +func NewResourceStack(dp *appsv1.Deployment, svc *corev1.Service, svcs []*corev1.Service, baseName string, enablePodReadinessGate bool) *ResourceStack { + return &ResourceStack{ dp: dp, svc: svc, nonLbTypeSvcs: svcs, @@ -23,8 +23,8 @@ func NewResourceStack(dp *appsv1.Deployment, svc *corev1.Service, svcs []*corev1 } } -// resourceStack containing the deployment and service resources -type resourceStack struct { +// ResourceStack containing the deployment and service resources +type ResourceStack struct { // configurations svc *corev1.Service // Load balancer type service nonLbTypeSvcs []*corev1.Service // Use this for non-load balancer type services @@ -38,7 +38,7 @@ type resourceStack struct { createdSVC *corev1.Service } -func (s *resourceStack) Deploy(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) Deploy(ctx context.Context, f *framework.Framework) error { if err := s.allocateNamespace(ctx, f); err != nil { return err } @@ -62,7 +62,7 @@ func (s *resourceStack) Deploy(ctx context.Context, f *framework.Framework) erro return nil } -func (s *resourceStack) UpdateServiceAnnotations(ctx context.Context, f *framework.Framework, svcAnnotations map[string]string) error { +func (s *ResourceStack) UpdateServiceAnnotations(ctx context.Context, f *framework.Framework, svcAnnotations map[string]string) error { if err := s.updateServiceAnnotations(ctx, f, svcAnnotations); err != nil { return err } @@ -72,7 +72,7 @@ func (s *resourceStack) UpdateServiceAnnotations(ctx context.Context, f *framewo return nil } -func (s *resourceStack) DeleteServiceAnnotations(ctx context.Context, f *framework.Framework, annotationKeys []string) error { +func (s *ResourceStack) DeleteServiceAnnotations(ctx context.Context, f *framework.Framework, annotationKeys []string) error { if err := s.removeServiceAnnotations(ctx, f, annotationKeys); err != nil { return err } @@ -82,7 +82,7 @@ func (s *resourceStack) DeleteServiceAnnotations(ctx context.Context, f *framewo return nil } -func (s *resourceStack) UpdateServiceTrafficPolicy(ctx context.Context, f *framework.Framework, trafficPolicy corev1.ServiceExternalTrafficPolicyType) error { +func (s *ResourceStack) UpdateServiceTrafficPolicy(ctx context.Context, f *framework.Framework, trafficPolicy corev1.ServiceExternalTrafficPolicyType) error { if err := s.updateServiceTrafficPolicy(ctx, f, trafficPolicy); err != nil { return err } @@ -92,7 +92,7 @@ func (s *resourceStack) UpdateServiceTrafficPolicy(ctx context.Context, f *frame return nil } -func (s *resourceStack) ScaleDeployment(ctx context.Context, f *framework.Framework, numReplicas int32) error { +func (s *ResourceStack) ScaleDeployment(ctx context.Context, f *framework.Framework, numReplicas int32) error { f.Logger.Info("scaling deployment", "dp", k8s.NamespacedName(s.dp), "currentReplicas", s.dp.Spec.Replicas, "desiredReplicas", numReplicas) oldDP := s.dp.DeepCopy() s.dp.Spec.Replicas = &numReplicas @@ -106,7 +106,7 @@ func (s *resourceStack) ScaleDeployment(ctx context.Context, f *framework.Framew return nil } -func (s *resourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) Cleanup(ctx context.Context, f *framework.Framework) error { if err := s.deleteDeployment(ctx, f); err != nil { return err } @@ -119,15 +119,19 @@ func (s *resourceStack) Cleanup(ctx context.Context, f *framework.Framework) err return nil } -func (s *resourceStack) GetLoadBalancerIngressHostname() string { +func (s *ResourceStack) GetLoadBalancerIngressHostname() string { return s.createdSVC.Status.LoadBalancer.Ingress[0].Hostname } -func (s *resourceStack) GetStackName() string { +func (s *ResourceStack) GetStackName() string { return fmt.Sprintf("%v/%v", s.ns.Name, s.svc.Name) } -func (s *resourceStack) getListenersPortMap() map[string]string { +func (s *ResourceStack) GetNamespace() string { + return s.ns.Name +} + +func (s *ResourceStack) getListenersPortMap() map[string]string { listenersMap := map[string]string{} for _, port := range s.createdSVC.Spec.Ports { listenersMap[strconv.Itoa(int(port.Port))] = string(port.Protocol) @@ -135,7 +139,7 @@ func (s *resourceStack) getListenersPortMap() map[string]string { return listenersMap } -func (s *resourceStack) getTargetGroupNodePortMap() map[string][]string { +func (s *ResourceStack) getTargetGroupNodePortMap() map[string][]string { tgPortProtocolMap := map[string][]string{} for _, port := range s.createdSVC.Spec.Ports { tgPortProtocolMap[strconv.Itoa(int(port.NodePort))] = []string{string(port.Protocol)} @@ -143,18 +147,18 @@ func (s *resourceStack) getTargetGroupNodePortMap() map[string][]string { return tgPortProtocolMap } -func (s *resourceStack) getHealthCheckNodePort() string { +func (s *ResourceStack) getHealthCheckNodePort() string { return strconv.Itoa(int(s.svc.Spec.HealthCheckNodePort)) } -func (s *resourceStack) updateServiceTrafficPolicy(ctx context.Context, f *framework.Framework, trafficPolicy corev1.ServiceExternalTrafficPolicyType) error { +func (s *ResourceStack) updateServiceTrafficPolicy(ctx context.Context, f *framework.Framework, trafficPolicy corev1.ServiceExternalTrafficPolicyType) error { f.Logger.Info("updating service annotations", "svc", k8s.NamespacedName(s.svc)) oldSvc := s.svc.DeepCopy() s.svc.Spec.ExternalTrafficPolicy = trafficPolicy return s.updateService(ctx, f, oldSvc) } -func (s *resourceStack) updateServiceAnnotations(ctx context.Context, f *framework.Framework, svcAnnotations map[string]string) error { +func (s *ResourceStack) updateServiceAnnotations(ctx context.Context, f *framework.Framework, svcAnnotations map[string]string) error { f.Logger.Info("updating service annotations", "svc", k8s.NamespacedName(s.svc)) oldSvc := s.svc.DeepCopy() for key, value := range svcAnnotations { @@ -163,7 +167,7 @@ func (s *resourceStack) updateServiceAnnotations(ctx context.Context, f *framewo return s.updateService(ctx, f, oldSvc) } -func (s *resourceStack) removeServiceAnnotations(ctx context.Context, f *framework.Framework, annotationKeys []string) error { +func (s *ResourceStack) removeServiceAnnotations(ctx context.Context, f *framework.Framework, annotationKeys []string) error { f.Logger.Info("removing service annotations", "svc", k8s.NamespacedName(s.svc)) oldSvc := s.svc.DeepCopy() for _, key := range annotationKeys { @@ -172,7 +176,7 @@ func (s *resourceStack) removeServiceAnnotations(ctx context.Context, f *framewo return s.updateService(ctx, f, oldSvc) } -func (s *resourceStack) updateService(ctx context.Context, f *framework.Framework, oldSvc *corev1.Service) error { +func (s *ResourceStack) updateService(ctx context.Context, f *framework.Framework, oldSvc *corev1.Service) error { f.Logger.Info("updating service", "svc", k8s.NamespacedName(s.svc)) if err := f.K8sClient.Patch(ctx, s.svc, client.MergeFrom(oldSvc)); err != nil { f.Logger.Info("failed to update service", "svc", k8s.NamespacedName(s.svc)) @@ -181,7 +185,7 @@ func (s *resourceStack) updateService(ctx context.Context, f *framework.Framewor return nil } -func (s *resourceStack) createDeployment(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) createDeployment(ctx context.Context, f *framework.Framework) error { f.Logger.Info("creating deployment", "dp", k8s.NamespacedName(s.dp)) if err := f.K8sClient.Create(ctx, s.dp); err != nil { f.Logger.Info("failed to create deployment") @@ -191,7 +195,7 @@ func (s *resourceStack) createDeployment(ctx context.Context, f *framework.Frame return nil } -func (s *resourceStack) waitUntilDeploymentReady(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) waitUntilDeploymentReady(ctx context.Context, f *framework.Framework) error { f.Logger.Info("waiting until deployment becomes ready", "dp", k8s.NamespacedName(s.dp)) observedDP, err := f.DPManager.WaitUntilDeploymentReady(ctx, s.dp) if err != nil { @@ -203,7 +207,7 @@ func (s *resourceStack) waitUntilDeploymentReady(ctx context.Context, f *framewo return nil } -func (s *resourceStack) createServices(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) createServices(ctx context.Context, f *framework.Framework) error { f.Logger.Info("creating service", "svc", k8s.NamespacedName(s.svc)) if err := f.K8sClient.Create(ctx, s.svc); err != nil { return err @@ -220,7 +224,7 @@ func (s *resourceStack) createServices(ctx context.Context, f *framework.Framewo return nil } -func (s *resourceStack) waitUntilServiceReady(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) waitUntilServiceReady(ctx context.Context, f *framework.Framework) error { f.Logger.Info("waiting until service becomes ready", "svc", k8s.NamespacedName(s.svc)) observedSVC, err := f.SVCManager.WaitUntilServiceActive(ctx, s.svc) if err != nil { @@ -230,7 +234,7 @@ func (s *resourceStack) waitUntilServiceReady(ctx context.Context, f *framework. return nil } -func (s *resourceStack) deleteDeployment(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) deleteDeployment(ctx context.Context, f *framework.Framework) error { f.Logger.Info("deleting deployment", "dp", k8s.NamespacedName(s.dp)) if err := f.K8sClient.Delete(ctx, s.dp); err != nil { f.Logger.Info("failed to delete deployment", "dp", k8s.NamespacedName(s.dp)) @@ -244,7 +248,7 @@ func (s *resourceStack) deleteDeployment(ctx context.Context, f *framework.Frame return nil } -func (s *resourceStack) deleteService(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) deleteService(ctx context.Context, f *framework.Framework) error { f.Logger.Info("deleting service", "svc", k8s.NamespacedName(s.svc)) if err := f.K8sClient.Delete(ctx, s.svc); err != nil { f.Logger.Info("failed to delete service", "svc", k8s.NamespacedName(s.svc)) @@ -258,7 +262,7 @@ func (s *resourceStack) deleteService(ctx context.Context, f *framework.Framewor return nil } -func (s *resourceStack) allocateNamespace(ctx context.Context, f *framework.Framework) error { +func (s *ResourceStack) allocateNamespace(ctx context.Context, f *framework.Framework) error { f.Logger.Info("allocating namespace") ns, err := f.NSManager.AllocateNamespace(ctx, s.baseName) if err != nil { @@ -281,7 +285,7 @@ func (s *resourceStack) allocateNamespace(ctx context.Context, f *framework.Fram return nil } -func (s *resourceStack) deleteNamespace(ctx context.Context, tf *framework.Framework) error { +func (s *ResourceStack) deleteNamespace(ctx context.Context, tf *framework.Framework) error { tf.Logger.Info("deleting namespace", "ns", k8s.NamespacedName(s.ns)) if err := tf.K8sClient.Delete(ctx, s.ns); err != nil { tf.Logger.Info("failed to delete namespace", "ns", k8s.NamespacedName(s.ns)) diff --git a/test/framework/framework.go b/test/framework/framework.go index 125efb773..5d247c4cd 100644 --- a/test/framework/framework.go +++ b/test/framework/framework.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + agav1beta1 "sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1" elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1" elbv2gw "sigs.k8s.io/aws-load-balancer-controller/apis/gateway/v1beta1" "sigs.k8s.io/aws-load-balancer-controller/pkg/aws" @@ -55,6 +56,7 @@ func InitFramework() (*Framework, error) { k8sSchema := runtime.NewScheme() clientgoscheme.AddToScheme(k8sSchema) + agav1beta1.AddToScheme(k8sSchema) elbv2api.AddToScheme(k8sSchema) gwv1.AddToScheme(k8sSchema) gwalpha2.AddToScheme(k8sSchema) diff --git a/test/framework/options.go b/test/framework/options.go index 45b537fa5..87dd37a4d 100644 --- a/test/framework/options.go +++ b/test/framework/options.go @@ -39,6 +39,7 @@ type Options struct { IPFamily string TestImageRegistry string EnableGatewayTests bool + EnableAGATests bool // Cognito configuration for e2e tests CognitoUserPoolArn string @@ -61,6 +62,7 @@ func (options *Options) BindFlags() { flag.StringVar(&options.TestImageRegistry, "test-image-registry", "617930562442.dkr.ecr.us-west-2.amazonaws.com", "the aws registry in test-infra-* accounts where e2e test images are stored") flag.BoolVar(&options.EnableGatewayTests, "enable-gateway-tests", false, "enables gateway tests") + flag.BoolVar(&options.EnableAGATests, "enable-aga-tests", false, "enables AWS Global Accelerator tests") flag.StringVar(&options.CognitoUserPoolArn, "cognito-user-pool-arn", "", `Cognito User Pool ARN for authenticate-cognito tests`) flag.StringVar(&options.CognitoUserPoolClientId, "cognito-user-pool-client-id", "", `Cognito User Pool Client ID for authenticate-cognito tests`) diff --git a/test/framework/utils/aws.go b/test/framework/utils/aws.go new file mode 100644 index 000000000..1aec7408d --- /dev/null +++ b/test/framework/utils/aws.go @@ -0,0 +1,14 @@ +package utils + +import "strings" + +// IsCommercialPartition returns true if the region is in the commercial AWS partition +func IsCommercialPartition(region string) bool { + unsupportedPrefixes := []string{"cn-", "us-gov-", "us-iso", "eu-isoe-"} + for _, prefix := range unsupportedPrefixes { + if strings.HasPrefix(strings.ToLower(region), prefix) { + return false + } + } + return true +}