From 5fa11ee003b3e2d8ba592b43a093bfb5168a8fd0 Mon Sep 17 00:00:00 2001 From: Andres Morey Date: Sun, 5 Sep 2021 15:55:53 +0300 Subject: [PATCH] Add support for TCP_UDP to NLB TargetGroups and Listeners Previously, aws-load-balancer-controller ignored extra overlapping ServicePorts defined in the Kubernetes Service spec if the external port numbers were the same even if the protocols were different (e.g. TCP:53, UDP:53). This behavior prevented users from exposing services that support TCP and UDP on the same external load balancer port number. This patch solves the problem by detecting when a user defines multiple ServicePorts for the same external load balancer port number but using TCP and UDP protocols separately. In such situations, a TCP_UDP TargetGroup and Listener are created and SecurityGroup rules are updated accordingly. If more than two ServicePorts are defined, only the first two mergeable ServicePorts are used. Otherwise, the first ServicePort is used. Note: rebasing errors would be my fault -- Kevin Lyda Signed-off-by: Kevin Lyda --- .../elbv2/v1beta1/targetgroupbinding_types.go | 3 + config/webhook/manifests.yaml | 242 ++++++++-------- pkg/service/model_build_listener.go | 50 +++- pkg/service/model_build_listener_test.go | 219 ++++++++++++++- pkg/service/model_build_target_group.go | 57 ++-- pkg/service/model_build_target_group_test.go | 62 ++++- pkg/service/model_builder_test.go | 258 ++++++++++++++++++ 7 files changed, 741 insertions(+), 150 deletions(-) diff --git a/apis/elbv2/v1beta1/targetgroupbinding_types.go b/apis/elbv2/v1beta1/targetgroupbinding_types.go index 0ec065709..d39a0192d 100644 --- a/apis/elbv2/v1beta1/targetgroupbinding_types.go +++ b/apis/elbv2/v1beta1/targetgroupbinding_types.go @@ -87,6 +87,9 @@ const ( // NetworkingProtocolUDP is the UDP protocol. NetworkingProtocolUDP NetworkingProtocol = "UDP" + + // NetworkingProtocolTCP_UDP is the TCP_UDP protocol. + NetworkingProtocolTCP_UDP NetworkingProtocol = "TCP_UDP" ) // NetworkingPort defines the port and protocol for networking rules. diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 0b1a24bfe..9d1898df1 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -2,130 +2,130 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - name: webhook + name: mutating-webhook-configuration webhooks: - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v1-pod - failurePolicy: Fail - name: mpod.elbv2.k8s.aws - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - pods - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-v1-service - failurePolicy: Fail - name: mservice.elbv2.k8s.aws - rules: - - apiGroups: - - "" - apiVersions: - - v1 - operations: - - CREATE - resources: - - services - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding - failurePolicy: Fail - name: mtargetgroupbinding.elbv2.k8s.aws - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - targetgroupbindings - sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v1-pod + failurePolicy: Fail + name: mpod.elbv2.k8s.aws + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-v1-service + failurePolicy: Fail + name: mservice.elbv2.k8s.aws + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - services + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding + failurePolicy: Fail + name: mtargetgroupbinding.elbv2.k8s.aws + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - targetgroupbindings + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: webhook + name: validating-webhook-configuration webhooks: - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-elbv2-k8s-aws-v1beta1-ingressclassparams - failurePolicy: Fail - name: vingressclassparams.elbv2.k8s.aws - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - ingressclassparams - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-elbv2-k8s-aws-v1beta1-targetgroupbinding - failurePolicy: Fail - name: vtargetgroupbinding.elbv2.k8s.aws - rules: - - apiGroups: - - elbv2.k8s.aws - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - targetgroupbindings - sideEffects: None - - admissionReviewVersions: - - v1beta1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-networking-v1-ingress - failurePolicy: Fail - matchPolicy: Equivalent - name: vingress.elbv2.k8s.aws - rules: - - apiGroups: - - networking.k8s.io - apiVersions: - - v1 - operations: - - CREATE - - UPDATE - resources: - - ingresses - sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-elbv2-k8s-aws-v1beta1-ingressclassparams + failurePolicy: Fail + name: vingressclassparams.elbv2.k8s.aws + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - ingressclassparams + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-elbv2-k8s-aws-v1beta1-targetgroupbinding + failurePolicy: Fail + name: vtargetgroupbinding.elbv2.k8s.aws + rules: + - apiGroups: + - elbv2.k8s.aws + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - targetgroupbindings + sideEffects: None +- admissionReviewVersions: + - v1beta1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-networking-v1-ingress + failurePolicy: Fail + matchPolicy: Equivalent + name: vingress.elbv2.k8s.aws + rules: + - apiGroups: + - networking.k8s.io + apiVersions: + - v1 + operations: + - CREATE + - UPDATE + resources: + - ingresses + sideEffects: None diff --git a/pkg/service/model_build_listener.go b/pkg/service/model_build_listener.go index 36aafb697..7bbdcc76c 100644 --- a/pkg/service/model_build_listener.go +++ b/pkg/service/model_build_listener.go @@ -19,16 +19,29 @@ func (t *defaultModelBuildTask) buildListeners(ctx context.Context, scheme elbv2 return err } + // execute build listener + // group by listener port number + portMap := make(map[int32][]corev1.ServicePort) for _, port := range t.service.Spec.Ports { - _, err := t.buildListener(ctx, port, *cfg, scheme) - if err != nil { - return err + key := port.Port + if vals, exists := portMap[key]; exists { + if len(vals) > 1 { + port = mergeServicePortsForListener(vals) + } else { + port = vals[0] + } + _, err := t.buildListener(ctx, port, cfg, scheme) + if err != nil { + return err + } + delete(portMap, key) } } + return nil } -func (t *defaultModelBuildTask) buildListener(ctx context.Context, port corev1.ServicePort, cfg listenerConfig, +func (t *defaultModelBuildTask) buildListener(ctx context.Context, port corev1.ServicePort, cfg *listenerConfig, scheme elbv2model.LoadBalancerScheme) (*elbv2model.Listener, error) { lsSpec, err := t.buildListenerSpec(ctx, port, cfg, scheme) if err != nil { @@ -39,7 +52,7 @@ func (t *defaultModelBuildTask) buildListener(ctx context.Context, port corev1.S return ls, nil } -func (t *defaultModelBuildTask) buildListenerSpec(ctx context.Context, port corev1.ServicePort, cfg listenerConfig, +func (t *defaultModelBuildTask) buildListenerSpec(ctx context.Context, port corev1.ServicePort, cfg *listenerConfig, scheme elbv2model.LoadBalancerScheme) (elbv2model.ListenerSpec, error) { tgProtocol := elbv2model.Protocol(port.Protocol) listenerProtocol := elbv2model.Protocol(port.Protocol) @@ -149,7 +162,7 @@ func validateTLSPortsSet(rawTLSPorts []string, ports []corev1.ServicePort) error return nil } -func (t *defaultModelBuildTask) buildTLSPortsSet(_ context.Context) (sets.String, error) { +func (t *defaultModelBuildTask) buildTLSPortsSet(_ context.Context) (sets.Set[string], error) { var rawTLSPorts []string _ = t.annotationParser.ParseStringSliceAnnotation(annotations.SvcLBSuffixSSLPorts, &rawTLSPorts, t.service.Annotations) @@ -160,7 +173,7 @@ func (t *defaultModelBuildTask) buildTLSPortsSet(_ context.Context) (sets.String return nil, err } - return sets.NewString(rawTLSPorts...), nil + return sets.New[string](rawTLSPorts...), nil } func (t *defaultModelBuildTask) buildBackendProtocol(_ context.Context) string { @@ -191,7 +204,7 @@ func (t *defaultModelBuildTask) buildListenerALPNPolicy(ctx context.Context, lis type listenerConfig struct { certificates []elbv2model.Certificate - tlsPortsSet sets.String + tlsPortsSet sets.Set[string] sslPolicy *string backendProtocol string } @@ -234,3 +247,24 @@ func (t *defaultModelBuildTask) buildListenerAttributes(ctx context.Context, svc } return attributes, nil } + +func mergeServicePortsForListener(ports []corev1.ServicePort) corev1.ServicePort { + port0 := ports[0] + mergeableProtocols := map[corev1.Protocol]bool{ + corev1.ProtocolTCP: true, + corev1.ProtocolUDP: true, + } + if _, ok := mergeableProtocols[port0.Protocol]; !ok { + return port0 + } + for _, port := range ports[1:] { + if _, ok := mergeableProtocols[port.Protocol]; !ok { + continue + } + if port.NodePort == port0.NodePort && port.Protocol != port0.Protocol { + port0.Protocol = corev1.Protocol("TCP_UDP") + break + } + } + return port0 +} diff --git a/pkg/service/model_build_listener_test.go b/pkg/service/model_build_listener_test.go index 9fd455afa..cbf3686db 100644 --- a/pkg/service/model_build_listener_test.go +++ b/pkg/service/model_build_listener_test.go @@ -159,7 +159,7 @@ func Test_defaultModelBuilderTask_buildListenerConfig(t *testing.T) { }, want: &listenerConfig{ certificates: ([]elbv2model.Certificate)(nil), - tlsPortsSet: sets.NewString("83"), + tlsPortsSet: sets.New[string]("83"), sslPolicy: new(string), backendProtocol: "", }, @@ -301,3 +301,220 @@ func Test_defaultModelBuilderTask_buildListenerAttributes(t *testing.T) { }) } } + +func Test_mergeServicePortsForListener(t *testing.T) { + tests := []struct { + name string + ports []corev1.ServicePort + want corev1.ServicePort + }{ + { + name: "one port", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + }, + { + name: "two tcp ports, different target and node ports", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(8888), + Protocol: corev1.ProtocolTCP, + NodePort: 31224, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + }, + { + name: "two udp ports, different target and node ports", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(8888), + Protocol: corev1.ProtocolUDP, + NodePort: 31224, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + }, + { + name: "one tcp and one udp, different target and node ports", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(8888), + Protocol: corev1.ProtocolUDP, + NodePort: 31224, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + }, + { + name: "one tcp and one udp, same target and node ports", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.Protocol("TCP_UDP"), + NodePort: 31223, + }, + }, + { + name: "one udp and one tcp, same target and node ports", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.Protocol("TCP_UDP"), + NodePort: 31223, + }, + }, + { + name: "one tcp and one udp, same node port, different target port", + ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(8888), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + }, + want: corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.Protocol("TCP_UDP"), + NodePort: 31223, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + port := mergeServicePortsForListener(tt.ports) + assert.Equal(t, port.Name, tt.want.Name) + assert.Equal(t, port.Port, tt.want.Port) + assert.Equal(t, port.TargetPort.IntVal, tt.want.TargetPort.IntVal) + assert.Equal(t, port.Protocol, tt.want.Protocol) + assert.Equal(t, port.NodePort, tt.want.NodePort) + }) + } + + // test that function returns new ServicePort instance + p1 := corev1.ServicePort{ + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + } + p2 := corev1.ServicePort{ + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + } + ports := []corev1.ServicePort{p1, p2} + mergedPort := mergeServicePortsForListener(ports) + + assert.Equal(t, corev1.ProtocolTCP, p1.Protocol) + assert.Equal(t, corev1.Protocol("TCP_UDP"), mergedPort.Protocol) + assert.NotEqual(t, &p1, &mergedPort) +} diff --git a/pkg/service/model_build_target_group.go b/pkg/service/model_build_target_group.go index f47d631e8..be905ebc7 100644 --- a/pkg/service/model_build_target_group.go +++ b/pkg/service/model_build_target_group.go @@ -546,34 +546,59 @@ func (t *defaultModelBuildTask) buildTargetGroupBindingNetworkingLegacy(ctx cont return nil, nil } tgProtocol := port.Protocol - networkingProtocol := elbv2api.NetworkingProtocolTCP healthCheckProtocol := elbv2api.NetworkingProtocolTCP - if tgProtocol == corev1.ProtocolUDP { + var networkingProtocol elbv2api.NetworkingProtocol + switch tgProtocol { + case corev1.ProtocolUDP: networkingProtocol = elbv2api.NetworkingProtocolUDP + case corev1.Protocol("TCP_UDP"): + networkingProtocol = elbv2api.NetworkingProtocolTCP_UDP + default: + networkingProtocol = elbv2api.NetworkingProtocolTCP } loadBalancerSubnetCIDRs := t.getLoadBalancerSubnetsSourceRanges(targetGroupIPAddressType) trafficSource := loadBalancerSubnetCIDRs defaultRangeUsed := false - if networkingProtocol == elbv2api.NetworkingProtocolUDP || t.preserveClientIP { - trafficSource = t.getLoadBalancerSourceRanges(ctx) - if len(trafficSource) == 0 { - trafficSource, err = t.getDefaultIPSourceRanges(ctx, targetGroupIPAddressType, port.Protocol, scheme) - if err != nil { - return nil, err + var trafficPorts []elbv2api.NetworkingPort + switch networkingProtocol { + + case elbv2api.NetworkingProtocolTCP_UDP: + tcpProtocol := elbv2api.NetworkingProtocolTCP + udpProtocol := elbv2api.NetworkingProtocolUDP + trafficPorts = []elbv2api.NetworkingPort{ + { + Port: &tgPort, + Protocol: &tcpProtocol, + }, + { + Port: &tgPort, + Protocol: &udpProtocol, + }, + } + default: + if networkingProtocol == elbv2api.NetworkingProtocolUDP || t.preserveClientIP { + trafficSource = t.getLoadBalancerSourceRanges(ctx) + if len(trafficSource) == 0 { + trafficSource, err = t.getDefaultIPSourceRanges(ctx, targetGroupIPAddressType, port.Protocol, scheme) + if err != nil { + return nil, err + } + defaultRangeUsed = true + } + } else { + trafficPorts = []elbv2api.NetworkingPort{ + { + Port: &tgPort, + Protocol: &networkingProtocol, + }, } - defaultRangeUsed = true } } tgbNetworking := &elbv2model.TargetGroupBindingNetworking{ Ingress: []elbv2model.NetworkingIngressRule{ { - From: t.buildPeersFromSourceRangeCIDRs(ctx, trafficSource), - Ports: []elbv2api.NetworkingPort{ - { - Port: &tgPort, - Protocol: &networkingProtocol, - }, - }, + From: t.buildPeersFromSourceRangeCIDRs(ctx, trafficSource), + Ports: trafficPorts, }, }, } diff --git a/pkg/service/model_build_target_group_test.go b/pkg/service/model_build_target_group_test.go index 7839f1dc2..894d1d641 100644 --- a/pkg/service/model_build_target_group_test.go +++ b/pkg/service/model_build_target_group_test.go @@ -3,15 +3,16 @@ package service import ( "context" "errors" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" - "github.com/golang/mock/gomock" - "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" - "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" "sort" "strconv" "testing" "github.com/aws/aws-sdk-go-v2/aws" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/golang/mock/gomock" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -1228,6 +1229,59 @@ func Test_defaultModelBuilderTask_buildTargetGroupBindingNetworkingLegacy(t *tes ipAddressType: elbv2.TargetGroupIPAddressTypeIPv4, want: nil, }, + { + name: "tcpudp-service with no source ranges configuration", + svc: &corev1.Service{}, + tgPort: port80, + hcPort: port808, + scheme: elbv2.LoadBalancerSchemeInternetFacing, + subnets: []ec2types.Subnet{ + { + CidrBlock: aws.String("172.16.0.0/19"), + SubnetId: aws.String("az-1"), + }, + }, + tgProtocol: corev1.Protocol("TCP_UDP"), + ipAddressType: elbv2.TargetGroupIPAddressTypeIPv4, + want: &elbv2.TargetGroupBindingNetworking{ + Ingress: []elbv2.NetworkingIngressRule{ + { + From: []elbv2.NetworkingPeer{ + { + IPBlock: &elbv2api.IPBlock{ + CIDR: "172.16.0.0/19", + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &networkingProtocolTCP, + Port: &port80, + }, + { + Protocol: &networkingProtocolUDP, + Port: &port80, + }, + }, + }, + { + From: []elbv2.NetworkingPeer{ + { + IPBlock: &elbv2api.IPBlock{ + CIDR: "172.16.0.0/19", + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &networkingProtocolTCP, + Port: &port808, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/service/model_builder_test.go b/pkg/service/model_builder_test.go index 998d46c1a..7af94f721 100644 --- a/pkg/service/model_builder_test.go +++ b/pkg/service/model_builder_test.go @@ -2239,6 +2239,264 @@ func Test_defaultModelBuilderTask_Build(t *testing.T) { listLoadBalancerCalls: []listLoadBalancerCall{listLoadBalancerCallForEmptyLB}, wantError: true, }, + { + testName: "Instance mode, TCP/UDP same port test", + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tcpudp-protocol", + Namespace: "app", + Annotations: map[string]string{ + "service.beta.kubernetes.io/aws-load-balancer-type": "external", + "service.beta.kubernetes.io/aws-load-balancer-scheme": "internet-facing", + "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type": "instance", + }, + UID: "2dc098f0-ae33-4378-af7b-83e2a0424495", + }, + Spec: corev1.ServiceSpec{ + ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyTypeCluster, + Type: corev1.ServiceTypeLoadBalancer, + Selector: map[string]string{"app": "hello"}, + HealthCheckNodePort: 29123, + Ports: []corev1.ServicePort{ + { + Name: "p1", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolTCP, + NodePort: 31223, + }, + { + Name: "p2", + Port: 80, + TargetPort: intstr.FromInt(80), + Protocol: corev1.ProtocolUDP, + NodePort: 31223, + }, + { + Name: "alt2", + Port: 83, + TargetPort: intstr.FromInt(8883), + Protocol: corev1.ProtocolTCP, + NodePort: 32323, + }, + }, + }, + }, + resolveViaDiscoveryCalls: []resolveViaDiscoveryCall{resolveViaDiscoveryCallForThreeSubnet}, + listLoadBalancerCalls: []listLoadBalancerCall{listLoadBalancerCallForEmptyLB}, + wantError: false, + wantValue: ` +{ + "id":"app/tcpudp-protocol", + "resources":{ + "AWS::ElasticLoadBalancingV2::Listener":{ + "80":{ + "spec":{ + "loadBalancerARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" + }, + "port":80, + "protocol":"TCP_UDP", + "defaultActions":[ + { + "type":"forward", + "forwardConfig":{ + "targetGroups":[ + { + "targetGroupARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/app/tcpudp-protocol:80/status/targetGroupARN" + } + } + ] + } + } + ] + } + }, + "83":{ + "spec":{ + "loadBalancerARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::LoadBalancer/LoadBalancer/status/loadBalancerARN" + }, + "port":83, + "protocol":"TCP", + "defaultActions":[ + { + "type":"forward", + "forwardConfig":{ + "targetGroups":[ + { + "targetGroupARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/app/tcpudp-protocol:83/status/targetGroupARN" + } + } + ] + } + } + ] + } + } + }, + "AWS::ElasticLoadBalancingV2::LoadBalancer":{ + "LoadBalancer":{ + "spec":{ + "name":"k8s-app-tcpudppr-2af705447d", + "type":"network", + "scheme":"internet-facing", + "ipAddressType":"ipv4", + "subnetMapping":[ + { + "subnetID":"subnet-1" + }, + { + "subnetID":"subnet-2" + }, + { + "subnetID":"subnet-3" + } + ] + } + } + }, + "AWS::ElasticLoadBalancingV2::TargetGroup":{ + "app/tcpudp-protocol:80":{ + "spec":{ + "ipAddressType":"ipv4", + "name":"k8s-app-tcpudppr-2213a0d759", + "targetType":"instance", + "port":31223, + "protocol":"TCP_UDP", + "healthCheckConfig":{ + "port":"traffic-port", + "protocol":"TCP", + "unhealthyThresholdCount":3, + "healthyThresholdCount":3, + "intervalSeconds":10 + }, + "targetGroupAttributes":[ + { + "key":"proxy_protocol_v2.enabled", + "value":"false" + } + ] + } + }, + "app/tcpudp-protocol:83":{ + "spec":{ + "ipAddressType":"ipv4", + "name":"k8s-app-tcpudppr-c200165858", + "targetType":"instance", + "port":32323, + "protocol":"TCP", + "healthCheckConfig":{ + "port":"traffic-port", + "protocol":"TCP", + "unhealthyThresholdCount":3, + "healthyThresholdCount":3, + "intervalSeconds":10 + }, + "targetGroupAttributes":[ + { + "key":"proxy_protocol_v2.enabled", + "value":"false" + } + ] + } + } + }, + "K8S::ElasticLoadBalancingV2::TargetGroupBinding":{ + "app/tcpudp-protocol:80":{ + "spec":{ + "template":{ + "metadata":{ + "creationTimestamp":null, + "name":"k8s-app-tcpudppr-2213a0d759", + "namespace":"app" + }, + "spec":{ + "targetGroupARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/app/tcpudp-protocol:80/status/targetGroupARN" + }, + "targetType":"instance", + "serviceRef":{ + "name":"tcpudp-protocol", + "port":80 + }, + "ipAddressType": "ipv4", + "networking":{ + "ingress":[ + { + "from":[ + { + "ipBlock":{ + "cidr":"0.0.0.0/0" + } + } + ], + "ports":[ + { + "protocol":"TCP", + "port":31223 + }, + { + "protocol":"UDP", + "port":31223 + } + ] + } + ] + } + } + } + } + }, + "app/tcpudp-protocol:83":{ + "spec":{ + "template":{ + "metadata":{ + "name":"k8s-app-tcpudppr-c200165858", + "namespace":"app", + "creationTimestamp":null + }, + "spec":{ + "targetGroupARN":{ + "$ref":"#/resources/AWS::ElasticLoadBalancingV2::TargetGroup/app/tcpudp-protocol:83/status/targetGroupARN" + }, + "targetType":"instance", + "serviceRef":{ + "name":"tcpudp-protocol", + "port":83 + }, + "ipAddressType": "ipv4", + "networking":{ + "ingress":[ + { + "from":[ + { + "ipBlock":{ + "cidr":"0.0.0.0/0" + } + } + ], + "ports":[ + { + "protocol":"TCP", + "port":32323 + } + ] + } + ] + } + } + } + } + } + } + } +} +`, + wantNumResources: 7, + }, { testName: "list load balancers error", svc: &corev1.Service{