diff --git a/apis/elbv2/v1beta1/targetgroupbinding_types.go b/apis/elbv2/v1beta1/targetgroupbinding_types.go index 0ec065709b..d39a0192d4 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 0b1a24bfed..9d1898df1f 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 d320fe5eaf..c8ae32934a 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) @@ -212,3 +225,24 @@ func (t *defaultModelBuildTask) buildListenerConfig(ctx context.Context) (*liste func (t *defaultModelBuildTask) buildListenerTags(ctx context.Context) (map[string]string, error) { return t.buildAdditionalResourceTags(ctx) } + +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 25d0ac76cb..e41e6e7eca 100644 --- a/pkg/service/model_build_listener_test.go +++ b/pkg/service/model_build_listener_test.go @@ -182,3 +182,220 @@ func Test_defaultModelBuilderTask_buildListenerConfig(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 16682eb6ef..2f0eeb66e9 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 78bebd158c..5d5a90f4fd 100644 --- a/pkg/service/model_build_target_group_test.go +++ b/pkg/service/model_build_target_group_test.go @@ -3,13 +3,14 @@ package service import ( "context" "errors" - "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/golang/mock/gomock" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" + "sigs.k8s.io/aws-load-balancer-controller/pkg/networking" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" "github.com/stretchr/testify/assert" @@ -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: []*ec2.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 4b598d80a9..3698949bd3 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{