diff --git a/apis/gateway/v1beta1/targetgroupconfig_types.go b/apis/gateway/v1beta1/targetgroupconfig_types.go index c5fe018f1..8794e6b52 100644 --- a/apis/gateway/v1beta1/targetgroupconfig_types.go +++ b/apis/gateway/v1beta1/targetgroupconfig_types.go @@ -112,9 +112,9 @@ const ( type TargetGroupHealthCheckProtocol string const ( - TargetGroupHealthCheckProtocolHTTP TargetGroupHealthCheckProtocol = "http" - TargetGroupHealthCheckProtocolHTTPS TargetGroupHealthCheckProtocol = "https" - TargetGroupHealthCheckProtocolTCP TargetGroupHealthCheckProtocol = "tcp" + TargetGroupHealthCheckProtocolHTTP TargetGroupHealthCheckProtocol = "HTTP" + TargetGroupHealthCheckProtocolHTTPS TargetGroupHealthCheckProtocol = "HTTPS" + TargetGroupHealthCheckProtocolTCP TargetGroupHealthCheckProtocol = "TCP" ) // +kubebuilder:validation:Enum=HTTP;HTTPS;TCP;TLS;UDP;TCP_UDP @@ -133,9 +133,9 @@ const ( type ProtocolVersion string const ( - ProtocolVersionHTTP1 ProtocolVersion = "http1" - ProtocolVersionHTTP2 ProtocolVersion = "http2" - ProtocolVersionGRPC ProtocolVersion = "grpc" + ProtocolVersionHTTP1 ProtocolVersion = "HTTP1" + ProtocolVersionHTTP2 ProtocolVersion = "HTTP2" + ProtocolVersionGRPC ProtocolVersion = "GRPC" ) // +kubebuilder:validation:Enum=none;prefer-route-specific;prefer-default diff --git a/pkg/gateway/model/model_build_target_group.go b/pkg/gateway/model/model_build_target_group.go index 0f5baafa4..2288c3257 100644 --- a/pkg/gateway/model/model_build_target_group.go +++ b/pkg/gateway/model/model_build_target_group.go @@ -89,7 +89,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroup(tgByResID *map[string]bu return buildTargetGroupOutput{}, err } nodeSelector := builder.buildTargetGroupBindingNodeSelector(targetGroupProps, tgSpec.TargetType) - bindingSpec := builder.buildTargetGroupBindingSpec(lbConfig, targetGroupProps, tgSpec, nodeSelector, backend, backendSGIDToken) + bindingSpec := builder.buildTargetGroupBindingSpec(targetGroupProps, tgSpec, nodeSelector, backend, backendSGIDToken) output := buildTargetGroupOutput{ targetGroupSpec: tgSpec, @@ -109,7 +109,7 @@ func (builder *targetGroupBuilderImpl) getTargetGroupProps(routeDescriptor route return targetGroupProps } -func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(lbConfig *elbv2gw.LoadBalancerConfiguration, tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec { +func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(tgProps *elbv2gw.TargetGroupProps, tgSpec elbv2model.TargetGroupSpec, nodeSelector *metav1.LabelSelector, backend routeutils.Backend, backendSGIDToken core.StringToken) elbv2model.TargetGroupBindingResourceSpec { targetType := elbv2api.TargetType(tgSpec.TargetType) targetPort := backend.ServicePort.TargetPort if targetType == elbv2api.TargetTypeInstance { @@ -142,14 +142,14 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupBindingSpec(lbConfig *elb } } -func (builder *targetGroupBuilderImpl) buildTargetGroupBindingNetworking(targetPort intstr.IntOrString, healthCheckPort intstr.IntOrString, port corev1.ServicePort, backendSGIDToken core.StringToken) *elbv2model.TargetGroupBindingNetworking { +func (builder *targetGroupBuilderImpl) buildTargetGroupBindingNetworking(targetPort intstr.IntOrString, healthCheckPort intstr.IntOrString, svcPort corev1.ServicePort, backendSGIDToken core.StringToken) *elbv2model.TargetGroupBindingNetworking { if backendSGIDToken == nil { return nil } protocolTCP := elbv2api.NetworkingProtocolTCP protocolUDP := elbv2api.NetworkingProtocolUDP - udpSupported := port.Protocol == corev1.ProtocolUDP + udpSupported := svcPort.Protocol == corev1.ProtocolUDP if builder.disableRestrictedSGRules { ports := []elbv2api.NetworkingPort{ @@ -183,7 +183,6 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupBindingNetworking(targetP } var networkingPorts []elbv2api.NetworkingPort - var networkingRules []elbv2model.NetworkingIngressRule protocolToUse := &protocolTCP if udpSupported { @@ -209,6 +208,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupBindingNetworking(targetP }) } + var networkingRules []elbv2model.NetworkingIngressRule for _, port := range networkingPorts { networkingRules = append(networkingRules, elbv2model.NetworkingIngressRule{ From: []elbv2model.NetworkingPeer{ @@ -232,7 +232,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupSpec(gw *gwv1.Gateway, ro if err != nil { return elbv2model.TargetGroupSpec{}, err } - tgProtocolVersion := builder.buildTargetGroupProtocolVersion(targetGroupProps) + tgProtocolVersion := builder.buildTargetGroupProtocolVersion(targetGroupProps, route) healthCheckConfig, err := builder.buildTargetGroupHealthCheckConfig(targetGroupProps, tgProtocol, tgProtocolVersion, targetType, backend) if err != nil { @@ -249,8 +249,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupSpec(gw *gwv1.Gateway, ro return elbv2model.TargetGroupSpec{}, err } tgPort := builder.buildTargetGroupPort(targetType, *backend.ServicePort) - // TODO - backend.ServicePort.TargetPort might not be correct. - name := builder.buildTargetGroupName(targetGroupProps, k8s.NamespacedName(gw), route.GetRouteNamespacedName(), k8s.NamespacedName(backend.Service), backend.ServicePort.TargetPort, tgPort, targetType, tgProtocol, tgProtocolVersion) + name := builder.buildTargetGroupName(targetGroupProps, k8s.NamespacedName(gw), route.GetRouteNamespacedName(), k8s.NamespacedName(backend.Service), tgPort, targetType, tgProtocol, tgProtocolVersion) return elbv2model.TargetGroupSpec{ Name: name, TargetType: targetType, @@ -268,7 +267,7 @@ var invalidTargetGroupNamePattern = regexp.MustCompile("[[:^alnum:]]") // buildTargetGroupName will calculate the targetGroup's name. func (builder *targetGroupBuilderImpl) buildTargetGroupName(targetGroupProps *elbv2gw.TargetGroupProps, - gwKey types.NamespacedName, routeKey types.NamespacedName, svcKey types.NamespacedName, port intstr.IntOrString, tgPort int32, + gwKey types.NamespacedName, routeKey types.NamespacedName, svcKey types.NamespacedName, tgPort int32, targetType elbv2model.TargetType, tgProtocol elbv2model.Protocol, tgProtocolVersion *elbv2model.ProtocolVersion) string { if targetGroupProps != nil && targetGroupProps.TargetGroupName != "" { @@ -283,7 +282,6 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupName(targetGroupProps *el _, _ = uuidHash.Write([]byte(routeKey.Name)) _, _ = uuidHash.Write([]byte(svcKey.Namespace)) _, _ = uuidHash.Write([]byte(svcKey.Name)) - _, _ = uuidHash.Write([]byte(port.String())) _, _ = uuidHash.Write([]byte(strconv.Itoa(int(tgPort)))) _, _ = uuidHash.Write([]byte(targetType)) _, _ = uuidHash.Write([]byte(tgProtocol)) @@ -365,10 +363,7 @@ func (builder *targetGroupBuilderImpl) buildL7TargetGroupProtocol(targetGroupPro } func (builder *targetGroupBuilderImpl) buildL4TargetGroupProtocol(targetGroupProps *elbv2gw.TargetGroupProps, route routeutils.RouteDescriptor) (elbv2model.Protocol, error) { - // TODO, auto infer? if targetGroupProps == nil || targetGroupProps.Protocol == nil { - // infer this somehow!? - // use the backend config to get the protocol type. return builder.inferTargetGroupProtocolFromRoute(route), nil } @@ -406,7 +401,12 @@ func (builder *targetGroupBuilderImpl) inferTargetGroupProtocolFromRoute(route r return elbv2model.ProtocolTCP } -func (builder *targetGroupBuilderImpl) buildTargetGroupProtocolVersion(targetGroupProps *elbv2gw.TargetGroupProps) *elbv2model.ProtocolVersion { +var ( + http1 = elbv2model.ProtocolVersionHTTP1 + grpc = elbv2model.ProtocolVersionGRPC +) + +func (builder *targetGroupBuilderImpl) buildTargetGroupProtocolVersion(targetGroupProps *elbv2gw.TargetGroupProps, route routeutils.RouteDescriptor) *elbv2model.ProtocolVersion { // NLB doesn't support protocol version if builder.loadBalancerType == elbv2model.LoadBalancerTypeNetwork { return nil @@ -416,7 +416,11 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupProtocolVersion(targetGro pv := elbv2model.ProtocolVersion(*targetGroupProps.ProtocolVersion) return &pv } - http1 := elbv2model.ProtocolVersionHTTP1 + + if route.GetRouteKind() == routeutils.GRPCRouteKind { + return &grpc + } + return &http1 } @@ -425,13 +429,13 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckConfig(targetG // https://github.com/kubernetes-sigs/gateway-api/issues/451 // Gateway API doesn't have the same ServiceExternalTrafficPolicyLocal support. // TODO - Maybe a TargetGroupConfig attribute to support the same behavior? - healthCheckPort, err := builder.buildTargetGroupHealthCheckPort(targetGroupProps, targetType, backend) + healthCheckPort, err := builder.buildTargetGroupHealthCheckPort(targetGroupProps, targetType, backend.Service) if err != nil { return elbv2model.TargetGroupHealthCheckConfig{}, err } healthCheckProtocol := builder.buildTargetGroupHealthCheckProtocol(targetGroupProps, tgProtocol) healthCheckPath := builder.buildTargetGroupHealthCheckPath(targetGroupProps, tgProtocolVersion, healthCheckProtocol) - healthCheckMatcher := builder.buildTargetGroupHealthCheckMatcher(targetGroupProps, healthCheckProtocol) + healthCheckMatcher := builder.buildTargetGroupHealthCheckMatcher(targetGroupProps, tgProtocolVersion, healthCheckProtocol) healthCheckIntervalSeconds := builder.buildTargetGroupHealthCheckIntervalSeconds(targetGroupProps) healthCheckTimeoutSeconds := builder.buildTargetGroupHealthCheckTimeoutSeconds(targetGroupProps) healthCheckHealthyThresholdCount := builder.buildTargetGroupHealthCheckHealthyThresholdCount(targetGroupProps) @@ -450,8 +454,10 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckConfig(targetG return hcConfig, nil } -func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckPort(targetGroupProps *elbv2gw.TargetGroupProps, targetType elbv2model.TargetType, backend routeutils.Backend) (intstr.IntOrString, error) { - if targetGroupProps == nil || targetGroupProps.HealthCheckConfig == nil || targetGroupProps.HealthCheckConfig.HealthCheckPort == nil || *targetGroupProps.HealthCheckConfig.HealthCheckPort == shared_constants.HealthCheckPortTrafficPort { +func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckPort(targetGroupProps *elbv2gw.TargetGroupProps, targetType elbv2model.TargetType, svc *corev1.Service) (intstr.IntOrString, error) { + + portConfigNotExist := targetGroupProps == nil || targetGroupProps.HealthCheckConfig == nil || targetGroupProps.HealthCheckConfig.HealthCheckPort == nil + if portConfigNotExist || *targetGroupProps.HealthCheckConfig.HealthCheckPort == shared_constants.HealthCheckPortTrafficPort { return intstr.FromString(shared_constants.HealthCheckPortTrafficPort), nil } @@ -459,13 +465,17 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckPort(targetGro if healthCheckPort.Type == intstr.Int { return healthCheckPort, nil } + hcSvcPort, err := k8s.LookupServicePort(svc, healthCheckPort) + if err != nil { + return intstr.FromString(""), err + } - /* TODO - Zac revisit this? */ if targetType == elbv2model.TargetTypeInstance { - return intstr.FromInt(int(backend.ServicePort.NodePort)), nil + return intstr.FromInt(int(hcSvcPort.NodePort)), nil } - if backend.ServicePort.TargetPort.Type == intstr.Int { - return backend.ServicePort.TargetPort, nil + + if hcSvcPort.TargetPort.Type == intstr.Int { + return hcSvcPort.TargetPort, nil } return intstr.IntOrString{}, errors.New("cannot use named healthCheckPort for IP TargetType when service's targetPort is a named port") } @@ -487,7 +497,8 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckProtocol(targe case elbv2gw.TargetGroupHealthCheckProtocolHTTPS: return elbv2model.ProtocolHTTPS default: - return tgProtocol + // This should never happen, the CRD validation takes care of this. + return elbv2model.ProtocolHTTP } } @@ -507,15 +518,17 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckPath(targetGro return &builder.defaultHealthCheckPathHTTP } -func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckMatcher(targetGroupProps *elbv2gw.TargetGroupProps, hcProtocol elbv2model.Protocol) *elbv2model.HealthCheckMatcher { +func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckMatcher(targetGroupProps *elbv2gw.TargetGroupProps, tgProtocolVersion *elbv2model.ProtocolVersion, hcProtocol elbv2model.Protocol) *elbv2model.HealthCheckMatcher { if hcProtocol == elbv2model.ProtocolTCP { return nil } - if targetGroupProps != nil && targetGroupProps.ProtocolVersion != nil && string(*targetGroupProps.ProtocolVersion) == string(elbv2model.ProtocolVersionGRPC) { + useGRPC := tgProtocolVersion != nil && *tgProtocolVersion == elbv2model.ProtocolVersionGRPC + + if useGRPC { matcher := builder.defaultHealthCheckMatcherGRPCCode - if targetGroupProps.ProtocolVersion != nil && targetGroupProps.HealthCheckConfig != nil && targetGroupProps.HealthCheckConfig.Matcher != nil && targetGroupProps.HealthCheckConfig.Matcher.GRPCCode != nil { + if targetGroupProps != nil && targetGroupProps.HealthCheckConfig != nil && targetGroupProps.HealthCheckConfig.Matcher != nil && targetGroupProps.HealthCheckConfig.Matcher.GRPCCode != nil { matcher = *targetGroupProps.HealthCheckConfig.Matcher.GRPCCode } return &elbv2model.HealthCheckMatcher{ @@ -523,7 +536,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupHealthCheckMatcher(target } } matcher := builder.defaultHealthCheckMatcherHTTPCode - if targetGroupProps != nil && targetGroupProps.ProtocolVersion != nil && targetGroupProps.HealthCheckConfig != nil && targetGroupProps.HealthCheckConfig.Matcher != nil && targetGroupProps.HealthCheckConfig.Matcher.HTTPCode != nil { + if targetGroupProps != nil && targetGroupProps.HealthCheckConfig != nil && targetGroupProps.HealthCheckConfig.Matcher != nil && targetGroupProps.HealthCheckConfig.Matcher.HTTPCode != nil { matcher = *targetGroupProps.HealthCheckConfig.Matcher.HTTPCode } return &elbv2model.HealthCheckMatcher{ @@ -570,9 +583,7 @@ func (builder *targetGroupBuilderImpl) buildTargetGroupAttributes(targetGroupPro attributeMap[attr.Key] = attr.Value } - if builder.loadBalancerType == elbv2model.LoadBalancerTypeNetwork { - builder.buildL4TargetGroupAttributes(&attributeMap, targetGroupProps) - } + // TODO -- buildPreserveClientIPFlag Might need special logic return attributeMap } @@ -588,22 +599,12 @@ func (builder *targetGroupBuilderImpl) convertMapToAttributes(attributeMap map[s return convertedAttributes } -func (builder *targetGroupBuilderImpl) buildL4TargetGroupAttributes(attributeMap *map[string]string, targetGroupProps *elbv2gw.TargetGroupProps) { - if targetGroupProps == nil { - return - } - // TODO -- buildPreserveClientIPFlag -} - func (builder *targetGroupBuilderImpl) buildTargetGroupResourceID(gwKey types.NamespacedName, svcKey types.NamespacedName, routeKey types.NamespacedName, port intstr.IntOrString) string { return fmt.Sprintf("%s/%s:%s-%s:%s-%s:%s", gwKey.Namespace, gwKey.Name, routeKey.Namespace, routeKey.Name, svcKey.Namespace, svcKey.Name, port.String()) } func (builder *targetGroupBuilderImpl) buildTargetGroupBindingNodeSelector(tgProps *elbv2gw.TargetGroupProps, targetType elbv2model.TargetType) *metav1.LabelSelector { - if targetType != elbv2model.TargetTypeInstance { - return nil - } - if tgProps == nil { + if targetType != elbv2model.TargetTypeInstance || tgProps == nil { return nil } return tgProps.NodeSelector diff --git a/pkg/gateway/model/model_build_target_group_test.go b/pkg/gateway/model/model_build_target_group_test.go index 292263c02..5c478df89 100644 --- a/pkg/gateway/model/model_build_target_group_test.go +++ b/pkg/gateway/model/model_build_target_group_test.go @@ -5,9 +5,12 @@ import ( "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" 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/gateway/routeutils" + "sigs.k8s.io/aws-load-balancer-controller/pkg/model/core" elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2" "sigs.k8s.io/aws-load-balancer-controller/pkg/shared_constants" gwv1 "sigs.k8s.io/gateway-api/apis/v1" @@ -67,7 +70,7 @@ func Test_buildTargetGroup(t *testing.T) { }, }, expectedTgSpec: elbv2model.TargetGroupSpec{ - Name: "k8s-myrouten-myroute-1949ae79d7", + Name: "k8s-myrouten-myroute-d02da2803b", TargetType: elbv2model.TargetTypeInstance, Port: awssdk.Int32(8080), Protocol: elbv2model.ProtocolTCP, @@ -90,7 +93,7 @@ func Test_buildTargetGroup(t *testing.T) { Template: elbv2model.TargetGroupBindingTemplate{ ObjectMeta: metav1.ObjectMeta{ Namespace: "my-svc-ns", - Name: "k8s-myrouten-myroute-1949ae79d7", + Name: "k8s-myrouten-myroute-d02da2803b", }, Spec: elbv2model.TargetGroupBindingSpec{ TargetType: &instanceType, @@ -139,7 +142,7 @@ func Test_buildTargetGroup(t *testing.T) { }, }, expectedTgSpec: elbv2model.TargetGroupSpec{ - Name: "k8s-myrouten-myroute-e99d898968", + Name: "k8s-myrouten-myroute-d146029dfb", TargetType: elbv2model.TargetTypeInstance, Port: awssdk.Int32(8080), Protocol: elbv2model.ProtocolHTTP, @@ -167,7 +170,7 @@ func Test_buildTargetGroup(t *testing.T) { Template: elbv2model.TargetGroupBindingTemplate{ ObjectMeta: metav1.ObjectMeta{ Namespace: "my-svc-ns", - Name: "k8s-myrouten-myroute-e99d898968", + Name: "k8s-myrouten-myroute-d146029dfb", }, Spec: elbv2model.TargetGroupBindingSpec{ TargetType: &instanceType, @@ -216,7 +219,7 @@ func Test_buildTargetGroup(t *testing.T) { }, }, expectedTgSpec: elbv2model.TargetGroupSpec{ - Name: "k8s-myrouten-myroute-7ac9e90fa0", + Name: "k8s-myrouten-myroute-d9d6c4e6eb", TargetType: elbv2model.TargetTypeIP, Port: awssdk.Int32(80), Protocol: elbv2model.ProtocolTCP, @@ -239,7 +242,7 @@ func Test_buildTargetGroup(t *testing.T) { Template: elbv2model.TargetGroupBindingTemplate{ ObjectMeta: metav1.ObjectMeta{ Namespace: "my-svc-ns", - Name: "k8s-myrouten-myroute-7ac9e90fa0", + Name: "k8s-myrouten-myroute-d9d6c4e6eb", }, Spec: elbv2model.TargetGroupBindingSpec{ TargetType: &ipType, @@ -288,7 +291,7 @@ func Test_buildTargetGroup(t *testing.T) { }, }, expectedTgSpec: elbv2model.TargetGroupSpec{ - Name: "k8s-myrouten-myroute-8a97d3dcbe", + Name: "k8s-myrouten-myroute-400113e816", TargetType: elbv2model.TargetTypeIP, Port: awssdk.Int32(80), Protocol: elbv2model.ProtocolHTTP, @@ -316,7 +319,7 @@ func Test_buildTargetGroup(t *testing.T) { Template: elbv2model.TargetGroupBindingTemplate{ ObjectMeta: metav1.ObjectMeta{ Namespace: "my-svc-ns", - Name: "k8s-myrouten-myroute-8a97d3dcbe", + Name: "k8s-myrouten-myroute-400113e816", }, Spec: elbv2model.TargetGroupBindingSpec{ TargetType: &ipType, @@ -360,3 +363,1332 @@ func Test_buildTargetGroup(t *testing.T) { }) } } + +func Test_getTargetGroupProps(t *testing.T) { + props := elbv2gw.TargetGroupProps{} + testCases := []struct { + name string + expected *elbv2gw.TargetGroupProps + backend routeutils.Backend + }{ + { + name: "no tg config", + }, + { + name: "with tg config", + backend: routeutils.Backend{ + ELBv2TargetGroupConfig: &elbv2gw.TargetGroupConfiguration{ + Spec: elbv2gw.TargetGroupConfigurationSpec{ + DefaultConfiguration: props, + }, + }, + }, + expected: &props, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := &targetGroupBuilderImpl{} + mockRoute := &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "my-route", + Namespace: "my-ns", + } + + result := builder.getTargetGroupProps(mockRoute, tc.backend) + assert.Equal(t, tc.expected, result) + }) + } +} + +func Test_buildTargetGroupBindingNetworking(t *testing.T) { + protocolTCP := elbv2api.NetworkingProtocolTCP + protocolUDP := elbv2api.NetworkingProtocolUDP + + intstr80 := intstr.FromInt32(80) + intstr85 := intstr.FromInt32(85) + intstrTrafficPort := intstr.FromString(shared_constants.HealthCheckPortTrafficPort) + + testCases := []struct { + name string + disableRestrictedSGRules bool + + targetPort intstr.IntOrString + healthCheckPort intstr.IntOrString + svcPort corev1.ServicePort + backendSGIDToken core.StringToken + + expected *elbv2model.TargetGroupBindingNetworking + }{ + { + name: "disable restricted sg rules", + disableRestrictedSGRules: true, + backendSGIDToken: core.LiteralStringToken("foo"), + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: nil, + }, + }, + }, + }, + }, + }, + { + name: "disable restricted sg rules - with udp", + disableRestrictedSGRules: true, + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolUDP, + }, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: nil, + }, + { + Protocol: &protocolUDP, + Port: nil, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - int hc port", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolTCP, + }, + targetPort: intstr80, + healthCheckPort: intstr80, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr80, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - int hc port - udp traffic", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolUDP, + }, + targetPort: intstr80, + healthCheckPort: intstr80, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolUDP, + Port: &intstr80, + }, + }, + }, + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr80, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - str hc port", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolTCP, + }, + targetPort: intstr80, + healthCheckPort: intstrTrafficPort, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr80, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - str hc port - udp", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolUDP, + }, + targetPort: intstr80, + healthCheckPort: intstrTrafficPort, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolUDP, + Port: &intstr80, + }, + }, + }, + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr80, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - diff hc port", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolTCP, + }, + targetPort: intstr80, + healthCheckPort: intstr85, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr80, + }, + }, + }, + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr85, + }, + }, + }, + }, + }, + }, + { + name: "use restricted sg rules - str hc port - udp", + backendSGIDToken: core.LiteralStringToken("foo"), + svcPort: corev1.ServicePort{ + Protocol: corev1.ProtocolUDP, + }, + targetPort: intstr80, + healthCheckPort: intstr85, + expected: &elbv2model.TargetGroupBindingNetworking{ + Ingress: []elbv2model.NetworkingIngressRule{ + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolUDP, + Port: &intstr80, + }, + }, + }, + { + From: []elbv2model.NetworkingPeer{ + { + SecurityGroup: &elbv2model.SecurityGroup{ + GroupID: core.LiteralStringToken("foo"), + }, + }, + }, + Ports: []elbv2api.NetworkingPort{ + { + Protocol: &protocolTCP, + Port: &intstr85, + }, + }, + }, + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := &targetGroupBuilderImpl{ + disableRestrictedSGRules: tc.disableRestrictedSGRules, + } + + result := builder.buildTargetGroupBindingNetworking(tc.targetPort, tc.healthCheckPort, tc.svcPort, tc.backendSGIDToken) + assert.Equal(t, tc.expected, result) + }) + } +} + +func Test_buildTargetGroupName(t *testing.T) { + http2 := elbv2model.ProtocolVersionHTTP2 + clusterName := "foo" + gwKey := types.NamespacedName{ + Namespace: "my-ns", + Name: "my-gw", + } + routeKey := types.NamespacedName{ + Namespace: "my-ns", + Name: "my-route", + } + svcKey := types.NamespacedName{ + Namespace: "my-ns", + Name: "my-svc", + } + testCases := []struct { + name string + targetGroupProps *elbv2gw.TargetGroupProps + protocolVersion *elbv2model.ProtocolVersion + expected string + }{ + { + name: "name override", + targetGroupProps: &elbv2gw.TargetGroupProps{TargetGroupName: "foobaz"}, + expected: "foobaz", + }, + { + name: "no name in props", + targetGroupProps: &elbv2gw.TargetGroupProps{}, + expected: "k8s-myns-myroute-719950e570", + }, + { + name: "no props", + expected: "k8s-myns-myroute-719950e570", + }, + { + name: "protocol specified props", + protocolVersion: &http2, + expected: "k8s-myns-myroute-ce262fa9fe", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + clusterName: clusterName, + } + + result := builder.buildTargetGroupName(tc.targetGroupProps, gwKey, routeKey, svcKey, 80, elbv2model.TargetTypeIP, elbv2model.ProtocolTCP, tc.protocolVersion) + assert.Equal(t, tc.expected, result) + }) + } +} + +func Test_buildTargetGroupTargetType(t *testing.T) { + builder := targetGroupBuilderImpl{ + defaultTargetType: elbv2model.TargetTypeIP, + } + + res := builder.buildTargetGroupTargetType(nil) + assert.Equal(t, elbv2model.TargetTypeIP, res) + + res = builder.buildTargetGroupTargetType(&elbv2gw.TargetGroupProps{}) + assert.Equal(t, elbv2model.TargetTypeIP, res) + + inst := elbv2gw.TargetTypeInstance + res = builder.buildTargetGroupTargetType(&elbv2gw.TargetGroupProps{ + TargetType: &inst, + }) + assert.Equal(t, elbv2model.TargetTypeInstance, res) +} + +func Test_buildTargetGroupIPAddressType(t *testing.T) { + testCases := []struct { + name string + svc *corev1.Service + loadBalancerIPAddressType elbv2model.IPAddressType + expectErr bool + expected elbv2model.TargetGroupIPAddressType + }{ + { + name: "no ip families", + svc: &corev1.Service{}, + loadBalancerIPAddressType: elbv2model.IPAddressTypeIPV4, + expected: elbv2model.TargetGroupIPAddressTypeIPv4, + }, + { + name: "ipv4 family", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + IPFamilies: []corev1.IPFamily{ + corev1.IPv4Protocol, + }, + }, + }, + loadBalancerIPAddressType: elbv2model.IPAddressTypeIPV4, + expected: elbv2model.TargetGroupIPAddressTypeIPv4, + }, + { + name: "ipv6 family", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + IPFamilies: []corev1.IPFamily{ + corev1.IPv6Protocol, + }, + }, + }, + loadBalancerIPAddressType: elbv2model.IPAddressTypeDualStack, + expected: elbv2model.TargetGroupIPAddressTypeIPv6, + }, + { + name: "ipv6 family - dual stack no ipv4", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + IPFamilies: []corev1.IPFamily{ + corev1.IPv6Protocol, + }, + }, + }, + loadBalancerIPAddressType: elbv2model.IPAddressTypeDualStackWithoutPublicIPV4, + expected: elbv2model.TargetGroupIPAddressTypeIPv6, + }, + { + name: "ipv6 family - bad lb type", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + IPFamilies: []corev1.IPFamily{ + corev1.IPv6Protocol, + }, + }, + }, + loadBalancerIPAddressType: elbv2model.IPAddressTypeIPV4, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{} + res, err := builder.buildTargetGroupIPAddressType(tc.svc, tc.loadBalancerIPAddressType) + if tc.expectErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, res) + + }) + } +} + +func Test_buildTargetGroupPort(t *testing.T) { + testCases := []struct { + name string + targetType elbv2model.TargetType + svcPort corev1.ServicePort + expected int32 + }{ + { + name: "instance", + svcPort: corev1.ServicePort{ + NodePort: 8080, + }, + targetType: elbv2model.TargetTypeInstance, + expected: 8080, + }, + { + name: "instance - no node port", + svcPort: corev1.ServicePort{}, + targetType: elbv2model.TargetTypeInstance, + expected: 1, + }, + { + name: "ip", + svcPort: corev1.ServicePort{ + NodePort: 8080, + TargetPort: intstr.FromInt32(80), + }, + targetType: elbv2model.TargetTypeIP, + expected: 80, + }, + { + name: "ip - str port", + svcPort: corev1.ServicePort{ + NodePort: 8080, + TargetPort: intstr.FromString("foo"), + }, + targetType: elbv2model.TargetTypeIP, + expected: 1, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{} + res := builder.buildTargetGroupPort(tc.targetType, tc.svcPort) + assert.Equal(t, tc.expected, res) + + }) + } +} + +func Test_buildTargetGroupProtocol(t *testing.T) { + testCases := []struct { + name string + lbType elbv2model.LoadBalancerType + targetGroupProps *elbv2gw.TargetGroupProps + route routeutils.RouteDescriptor + expected elbv2model.Protocol + expectErr bool + }{ + { + name: "alb - auto detect - http", + lbType: elbv2model.LoadBalancerTypeApplication, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolHTTP, + }, + { + name: "alb - auto detect - grpc", + lbType: elbv2model.LoadBalancerTypeApplication, + route: &routeutils.MockRoute{ + Kind: routeutils.GRPCRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolHTTP, + }, + { + name: "alb - auto detect - tls", + lbType: elbv2model.LoadBalancerTypeApplication, + route: &routeutils.MockRoute{ + Kind: routeutils.TLSRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolHTTPS, + }, + { + name: "nlb - auto detect - tcp", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.TCPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTCP, + }, + { + name: "alb - auto detect - udp", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.UDPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolUDP, + }, + { + name: "nlb - auto detect - tls", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.TLSRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTLS, + }, + { + name: "alb - specified - http", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolHTTP), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.TCPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolHTTP, + }, + { + name: "alb - specified - https", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolHTTPS), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.TCPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolHTTPS, + }, + { + name: "alb - specified - invalid protocol", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolTCP), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.TCPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expectErr: true, + }, + { + name: "nlb - auto detect - tcp", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.TCPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTCP, + }, + { + name: "alb - auto detect - udp", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.UDPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolUDP, + }, + { + name: "nlb - auto detect - tls", + lbType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{ + Kind: routeutils.TLSRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTLS, + }, + { + name: "nlb - specified - tcp protocol", + lbType: elbv2model.LoadBalancerTypeNetwork, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolTCP), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTCP, + }, + { + name: "nlb - specified - udp protocol", + lbType: elbv2model.LoadBalancerTypeNetwork, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolUDP), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolUDP, + }, + { + name: "nlb - specified - tcpudp protocol", + lbType: elbv2model.LoadBalancerTypeNetwork, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolTCP_UDP), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTCP_UDP, + }, + { + name: "nlb - specified - tls protocol", + lbType: elbv2model.LoadBalancerTypeNetwork, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolTLS), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expected: elbv2model.ProtocolTLS, + }, + { + name: "nlb - specified - invalid protocol", + lbType: elbv2model.LoadBalancerTypeNetwork, + targetGroupProps: &elbv2gw.TargetGroupProps{ + Protocol: protocolPtr(elbv2gw.ProtocolHTTPS), + }, + route: &routeutils.MockRoute{ + Kind: routeutils.HTTPRouteKind, + Name: "r1", + Namespace: "ns", + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + loadBalancerType: tc.lbType, + } + res, err := builder.buildTargetGroupProtocol(tc.targetGroupProps, tc.route) + if tc.expectErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupProtocolVersion(t *testing.T) { + http2Gw := elbv2gw.ProtocolVersionHTTP2 + http2Elb := elbv2model.ProtocolVersionHTTP2 + http1Elb := elbv2model.ProtocolVersionHTTP1 + grpcElb := elbv2model.ProtocolVersionGRPC + testCases := []struct { + name string + loadBalancerType elbv2model.LoadBalancerType + route routeutils.RouteDescriptor + targetGroupProps *elbv2gw.TargetGroupProps + expected *elbv2model.ProtocolVersion + }{ + { + name: "nlb - no props", + loadBalancerType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{Kind: routeutils.TCPRouteKind}, + }, + { + name: "nlb - with props", + loadBalancerType: elbv2model.LoadBalancerTypeNetwork, + route: &routeutils.MockRoute{Kind: routeutils.TCPRouteKind}, + targetGroupProps: &elbv2gw.TargetGroupProps{ + ProtocolVersion: &http2Gw, + }, + }, + { + name: "alb - no props", + route: &routeutils.MockRoute{Kind: routeutils.HTTPRouteKind}, + loadBalancerType: elbv2model.LoadBalancerTypeApplication, + expected: &http1Elb, + }, + { + name: "alb - no props - grpc", + route: &routeutils.MockRoute{Kind: routeutils.GRPCRouteKind}, + loadBalancerType: elbv2model.LoadBalancerTypeApplication, + expected: &grpcElb, + }, + { + name: "alb - with props", + route: &routeutils.MockRoute{Kind: routeutils.HTTPRouteKind}, + loadBalancerType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + ProtocolVersion: &http2Gw, + }, + expected: &http2Elb, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + loadBalancerType: tc.loadBalancerType, + } + res := builder.buildTargetGroupProtocolVersion(tc.targetGroupProps, tc.route) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupHealthCheckPort(t *testing.T) { + testCases := []struct { + name string + targetGroupProps *elbv2gw.TargetGroupProps + targetType elbv2model.TargetType + svc *corev1.Service + expected intstr.IntOrString + expectErr bool + }{ + { + name: "nil props", + expected: intstr.FromString(shared_constants.HealthCheckPortTrafficPort), + }, + { + name: "nil hc props", + targetGroupProps: &elbv2gw.TargetGroupProps{}, + expected: intstr.FromString(shared_constants.HealthCheckPortTrafficPort), + }, + { + name: "nil hc port", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{}, + }, + expected: intstr.FromString(shared_constants.HealthCheckPortTrafficPort), + }, + { + name: "explicit is use traffic port hc port", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String(shared_constants.HealthCheckPortTrafficPort), + }, + }, + expected: intstr.FromString(shared_constants.HealthCheckPortTrafficPort), + }, + { + name: "explicit port", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("80"), + }, + }, + expected: intstr.FromInt32(80), + }, + { + name: "resolve str port", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + TargetPort: intstr.FromInt32(80), + }, + }, + }, + }, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("foo"), + }, + }, + expected: intstr.FromInt32(80), + }, + { + name: "resolve str port - instance", + targetType: elbv2model.TargetTypeInstance, + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + TargetPort: intstr.FromInt32(80), + NodePort: 1000, + }, + }, + }, + }, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("foo"), + }, + }, + expected: intstr.FromInt32(1000), + }, + { + name: "resolve str port - resolves to other str port (error)", + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + TargetPort: intstr.FromString("bar"), + NodePort: 1000, + }, + }, + }, + }, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("foo"), + }, + }, + expectErr: true, + }, + { + name: "resolve str port - resolves to other str port but instance mode", + targetType: elbv2model.TargetTypeInstance, + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "foo", + TargetPort: intstr.FromString("bar"), + NodePort: 1000, + }, + }, + }, + }, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("foo"), + }, + }, + expected: intstr.FromInt32(1000), + }, + { + name: "resolve str port - cant find configured port", + targetType: elbv2model.TargetTypeInstance, + svc: &corev1.Service{ + Spec: corev1.ServiceSpec{ + Ports: []corev1.ServicePort{ + { + Name: "baz", + TargetPort: intstr.FromString("bar"), + NodePort: 1000, + }, + }, + }, + }, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPort: awssdk.String("foo"), + }, + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{} + res, err := builder.buildTargetGroupHealthCheckPort(tc.targetGroupProps, tc.targetType, tc.svc) + if tc.expectErr { + assert.Error(t, err, res) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupHealthCheckProtocol(t *testing.T) { + testCases := []struct { + name string + lbType elbv2model.LoadBalancerType + targetGroupProps *elbv2gw.TargetGroupProps + tgProtocol elbv2model.Protocol + expected elbv2model.Protocol + }{ + { + name: "nlb - default", + lbType: elbv2model.LoadBalancerTypeNetwork, + tgProtocol: elbv2model.ProtocolUDP, + expected: elbv2model.ProtocolTCP, + }, + { + name: "alb - default", + lbType: elbv2model.LoadBalancerTypeApplication, + tgProtocol: elbv2model.ProtocolHTTP, + expected: elbv2model.ProtocolHTTP, + }, + { + name: "specified http", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckProtocol: (*elbv2gw.TargetGroupHealthCheckProtocol)(awssdk.String(string(elbv2gw.ProtocolHTTP))), + }, + }, + tgProtocol: elbv2model.ProtocolHTTP, + expected: elbv2model.ProtocolHTTP, + }, + { + name: "specified https", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckProtocol: (*elbv2gw.TargetGroupHealthCheckProtocol)(awssdk.String(string(elbv2gw.ProtocolHTTPS))), + }, + }, + tgProtocol: elbv2model.ProtocolHTTP, + expected: elbv2model.ProtocolHTTPS, + }, + { + name: "specified tcp", + lbType: elbv2model.LoadBalancerTypeApplication, + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckProtocol: (*elbv2gw.TargetGroupHealthCheckProtocol)(awssdk.String(string(elbv2gw.ProtocolTCP))), + }, + }, + tgProtocol: elbv2model.ProtocolTCP, + expected: elbv2model.ProtocolTCP, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + loadBalancerType: tc.lbType, + } + + res := builder.buildTargetGroupHealthCheckProtocol(tc.targetGroupProps, tc.tgProtocol) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupHealthCheckPath(t *testing.T) { + httpDefaultPath := "httpDefault" + grpcDefaultPath := "grpcDefault" + testCases := []struct { + name string + targetGroupProps *elbv2gw.TargetGroupProps + tgProtocolVersion *elbv2model.ProtocolVersion + hcProtocol elbv2model.Protocol + expected *string + }{ + { + name: "path specified", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthCheckPath: awssdk.String("foo"), + }, + }, + expected: awssdk.String("foo"), + }, + { + name: "default - tcp", + hcProtocol: elbv2model.ProtocolTCP, + }, + { + name: "default - http", + hcProtocol: elbv2model.ProtocolHTTP, + expected: &httpDefaultPath, + }, + { + name: "default - grpc", + hcProtocol: elbv2model.ProtocolHTTP, + tgProtocolVersion: (*elbv2model.ProtocolVersion)(awssdk.String(string(elbv2model.ProtocolVersionGRPC))), + expected: &grpcDefaultPath, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + defaultHealthCheckPathHTTP: httpDefaultPath, + defaultHealthCheckPathGRPC: grpcDefaultPath, + } + + res := builder.buildTargetGroupHealthCheckPath(tc.targetGroupProps, tc.tgProtocolVersion, tc.hcProtocol) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupHealthCheckMatcher(t *testing.T) { + httpDefaultMatcher := "httpMatcher" + grpcDefaultMatcher := "grpcMatcher" + testCases := []struct { + name string + targetGroupProps *elbv2gw.TargetGroupProps + tgProtocolVersion *elbv2model.ProtocolVersion + hcProtocol elbv2model.Protocol + expected *elbv2model.HealthCheckMatcher + }{ + { + name: "default - tcp", + hcProtocol: elbv2model.ProtocolTCP, + }, + { + name: "specified - grpc", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + Matcher: &elbv2gw.HealthCheckMatcher{ + GRPCCode: awssdk.String("foo"), + }, + }, + }, + hcProtocol: elbv2model.ProtocolHTTP, + tgProtocolVersion: (*elbv2model.ProtocolVersion)(awssdk.String(string(elbv2model.ProtocolVersionGRPC))), + expected: &elbv2model.HealthCheckMatcher{ + GRPCCode: awssdk.String("foo"), + }, + }, + { + name: "specified - http", + targetGroupProps: &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + Matcher: &elbv2gw.HealthCheckMatcher{ + HTTPCode: awssdk.String("foo"), + }, + }, + }, + hcProtocol: elbv2model.ProtocolHTTP, + expected: &elbv2model.HealthCheckMatcher{ + HTTPCode: awssdk.String("foo"), + }, + }, + { + name: "default - grpc", + hcProtocol: elbv2model.ProtocolHTTP, + tgProtocolVersion: (*elbv2model.ProtocolVersion)(awssdk.String(string(elbv2model.ProtocolVersionGRPC))), + expected: &elbv2model.HealthCheckMatcher{ + GRPCCode: &grpcDefaultMatcher, + }, + }, + { + name: "default - http1", + hcProtocol: elbv2model.ProtocolHTTP, + tgProtocolVersion: (*elbv2model.ProtocolVersion)(awssdk.String(string(elbv2model.ProtocolVersionHTTP1))), + expected: &elbv2model.HealthCheckMatcher{ + HTTPCode: &httpDefaultMatcher, + }, + }, + { + name: "default - no protocol version", + hcProtocol: elbv2model.ProtocolHTTP, + tgProtocolVersion: (*elbv2model.ProtocolVersion)(awssdk.String(string(elbv2model.ProtocolVersionHTTP1))), + expected: &elbv2model.HealthCheckMatcher{ + HTTPCode: &httpDefaultMatcher, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{ + defaultHealthCheckMatcherHTTPCode: httpDefaultMatcher, + defaultHealthCheckMatcherGRPCCode: grpcDefaultMatcher, + } + + res := builder.buildTargetGroupHealthCheckMatcher(tc.targetGroupProps, tc.tgProtocolVersion, tc.hcProtocol) + assert.Equal(t, tc.expected, res) + }) + } +} + +func Test_basicHealthCheckParams(t *testing.T) { + builder := targetGroupBuilderImpl{ + defaultHealthCheckInterval: 1, + defaultHealthCheckTimeout: 2, + defaultHealthyThresholdCount: 3, + defaultHealthCheckUnhealthyThresholdCount: 4, + } + + defaultProps := []*elbv2gw.TargetGroupProps{ + nil, + {}, + { + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{}, + }, + } + + for _, prop := range defaultProps { + assert.Equal(t, int32(1), builder.buildTargetGroupHealthCheckIntervalSeconds(prop)) + assert.Equal(t, int32(2), builder.buildTargetGroupHealthCheckTimeoutSeconds(prop)) + assert.Equal(t, int32(3), builder.buildTargetGroupHealthCheckHealthyThresholdCount(prop)) + assert.Equal(t, int32(4), builder.buildTargetGroupHealthCheckUnhealthyThresholdCount(prop)) + } + + filledInProps := &elbv2gw.TargetGroupProps{ + HealthCheckConfig: &elbv2gw.HealthCheckConfiguration{ + HealthyThresholdCount: awssdk.Int32(30), + HealthCheckInterval: awssdk.Int32(10), + HealthCheckPath: nil, + HealthCheckPort: nil, + HealthCheckProtocol: nil, + HealthCheckTimeout: awssdk.Int32(20), + UnhealthyThresholdCount: awssdk.Int32(40), + Matcher: nil, + }} + + assert.Equal(t, int32(10), builder.buildTargetGroupHealthCheckIntervalSeconds(filledInProps)) + assert.Equal(t, int32(20), builder.buildTargetGroupHealthCheckTimeoutSeconds(filledInProps)) + assert.Equal(t, int32(30), builder.buildTargetGroupHealthCheckHealthyThresholdCount(filledInProps)) + assert.Equal(t, int32(40), builder.buildTargetGroupHealthCheckUnhealthyThresholdCount(filledInProps)) +} + +func Test_targetGroupAttributes(t *testing.T) { + testCases := []struct { + name string + props *elbv2gw.TargetGroupProps + expected []elbv2model.TargetGroupAttribute + }{ + { + name: "no props - nil", + expected: make([]elbv2model.TargetGroupAttribute, 0), + }, + { + name: "no props", + props: &elbv2gw.TargetGroupProps{}, + expected: make([]elbv2model.TargetGroupAttribute, 0), + }, + { + name: "some props", + props: &elbv2gw.TargetGroupProps{ + TargetGroupAttributes: []elbv2gw.TargetGroupAttribute{ + { + Key: "foo", + Value: "bar", + }, + { + Key: "foo1", + Value: "bar1", + }, + { + Key: "foo2", + Value: "bar2", + }, + }, + }, + expected: []elbv2model.TargetGroupAttribute{ + { + Key: "foo", + Value: "bar", + }, + { + Key: "foo1", + Value: "bar1", + }, + { + Key: "foo2", + Value: "bar2", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + builder := targetGroupBuilderImpl{} + + res := builder.convertMapToAttributes(builder.buildTargetGroupAttributes(tc.props)) + assert.ElementsMatch(t, tc.expected, res) + }) + } +} + +func Test_buildTargetGroupBindingNodeSelector(t *testing.T) { + builder := targetGroupBuilderImpl{} + + res := builder.buildTargetGroupBindingNodeSelector(nil, elbv2model.TargetTypeInstance) + assert.Nil(t, res) + + propWithSelector := &elbv2gw.TargetGroupProps{ + NodeSelector: &metav1.LabelSelector{}, + } + + res = builder.buildTargetGroupBindingNodeSelector(propWithSelector, elbv2model.TargetTypeIP) + assert.Nil(t, res) + + assert.NotNil(t, builder.buildTargetGroupBindingNodeSelector(propWithSelector, elbv2model.TargetTypeInstance)) +} + +func Test_buildTargetGroupBindingMultiClusterFlag(t *testing.T) { + builder := targetGroupBuilderImpl{} + + assert.False(t, builder.buildTargetGroupBindingMultiClusterFlag(nil)) + + props := &elbv2gw.TargetGroupProps{ + EnableMultiCluster: false, + } + + assert.False(t, builder.buildTargetGroupBindingMultiClusterFlag(props)) + props.EnableMultiCluster = true + assert.True(t, builder.buildTargetGroupBindingMultiClusterFlag(props)) +} + +func protocolPtr(protocol elbv2gw.Protocol) *elbv2gw.Protocol { + return &protocol +}