//go:build unit // Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may // not use this file except in compliance with the License. A copy of the // License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. package serviceconnect import ( "fmt" "strings" "testing" "strconv" "github.com/aws/amazon-ecs-agent/ecs-agent/acs/model/ecsacs" "github.com/aws/aws-sdk-go/aws" "github.com/stretchr/testify/assert" ) const ( testAwsVpcPortOverride = "8080" testAwsVpcPortDefault = "9090" testBridgePortOverride = "8080" testBridgePortDefault = "15000" testBridgeHostPort = "8080" testServiceConnectContainerName = "ecs-service-connect" testServiceConnectAttachmentType = "ServiceConnect" testHostName = "testHostName" testAddress = "testAddress" testOutboundListenerName = "testOutboundListener" testInboundListenerName = "testInboundListener" testIPv4Address = "172.31.21.40" testIPv6Address = "abcd:dcba:1234:4321::" testIPv4Cidr = "127.255.0.0/16" testIPv6Cidr = "2002::1234:abcd:ffff:c0a8:101/64" testEgressConfigFormat = `\"egressConfig\":{\"listenerName\":\"%s\",\"vip\":{\"ipv4Cidr\":\"%s\",\"ipv6Cidr\":\"%s\"}}` testDnsConfigFormat = `\"dnsConfig\":[{\"hostname\":\"%s\",\"address\":\"%s\"}]` testIngressConfigAwsVpcDefaultFormat = `\"ingressConfig\":[{\"interceptPort\":%s,\"listenerName\":\"%s\"}]` testIngressConfigBridgeOverrideFormat = `\"ingressConfig\":[{\"listenerPort\":%s,\"hostPort\":%s}]` testIngressConfigListenerPortOnlyFormat = `\"ingressConfig\":[{\"listenerPort\":%s}]` ) var ( testAwsVpcDefaultInterceptPort = aws.Uint16(9090) testListenerPort = uint16(8080) testBridgeOverrideHostPort = aws.Uint16(8080) testBridgeDefaultListenerPort = uint16(15000) testAwsVpcDefaultSCConfig = "" testAwsVpcDefaultIPv6EnabledSCConfig = "" testAwsVpcOverrideSCConfig = "" testAwsVpcOverrideIPv6EnabledSCConfig = "" testBridgeDefaultSCConfig = "" testBridgeDefaultEmptyIngressSCConfig = "" testBridgeDefaultEmptyEgressSCConfig = "" ) func initServiceConnectConfValue() { testAwsVpcDefaultSCConfig = constructTestServiceConnectConfig(AWSVPCNetworkMode, false, false, false, false) testAwsVpcDefaultIPv6EnabledSCConfig = constructTestServiceConnectConfig(AWSVPCNetworkMode, false, false, false, true) testAwsVpcOverrideSCConfig = constructTestServiceConnectConfig(AWSVPCNetworkMode, true, false, false, false) testAwsVpcOverrideIPv6EnabledSCConfig = constructTestServiceConnectConfig(AWSVPCNetworkMode, true, false, false, true) testBridgeDefaultSCConfig = constructTestServiceConnectConfig(BridgeNetworkMode, false, false, false, false) testBridgeDefaultEmptyIngressSCConfig = constructTestServiceConnectConfig(BridgeNetworkMode, false, true, false, false) testBridgeDefaultEmptyEgressSCConfig = constructTestServiceConnectConfig(BridgeNetworkMode, false, false, true, false) } func strptr(s string) *string { return &s } // constructTestServiceConnectConfig returns service connect config value as string based on the passed values. func constructTestServiceConnectConfig(networkMode string, override, emptyIngress, emptyEgress, ipv6Enabled bool) string { ingressConfig := "" switch networkMode { case AWSVPCNetworkMode: if override { // awsvpc override case has listener port(s) in the ingress config ingressConfig = fmt.Sprintf(testIngressConfigListenerPortOnlyFormat, testAwsVpcPortOverride) } else { // awsvpc default case has intercept port(s) and listener name(s) in the ingress config ingressConfig = fmt.Sprintf(testIngressConfigAwsVpcDefaultFormat, testAwsVpcPortDefault, testInboundListenerName) } case BridgeNetworkMode: if override { // bridge override case has listener port(s) and host port(s) in the ingress config ingressConfig = fmt.Sprintf(testIngressConfigBridgeOverrideFormat, testBridgePortOverride, testBridgeHostPort) } else { // bridge default case has listener port(s) in the ingress config ingressConfig = fmt.Sprintf(testIngressConfigListenerPortOnlyFormat, testBridgePortDefault) } } testEgressConfig := fmt.Sprintf(testEgressConfigFormat, testOutboundListenerName, testIPv4Cidr, "") testDnsConfig := fmt.Sprintf(testDnsConfigFormat, testHostName, testIPv4Address) if ipv6Enabled { testEgressConfig = fmt.Sprintf(testEgressConfigFormat, testOutboundListenerName, "", testIPv6Cidr) testDnsConfig = fmt.Sprintf(testDnsConfigFormat, testHostName, testIPv6Address) } testServiceConnectConfig := strings.Join([]string{`"{`, testEgressConfig + `,`, testDnsConfig + `,`, ingressConfig, `}"`, }, "") if emptyIngress { testServiceConnectConfig = strings.Join([]string{`"{`, testEgressConfig + `,`, testDnsConfig, `}"`, }, "") } if emptyEgress { testServiceConnectConfig = strings.Join([]string{`"{`, ingressConfig, `}"`, }, "") } unquotedSCConfig, _ := strconv.Unquote(testServiceConnectConfig) return unquotedSCConfig } // getTestACSAttachmentProperty returns *ecsacs.AttachmentProperty with passed parameters. func getTestACSAttachmentProperty(propertyName, propertyValue string) *ecsacs.AttachmentProperty { return &ecsacs.AttachmentProperty{ Name: strptr(propertyName), Value: strptr(propertyValue), } } // getTestACSAttachments returns *ecsacs.Task.getTestACSAttachments. func getTestACSAttachments(attachmentProperties []*ecsacs.AttachmentProperty) *ecsacs.Attachment { return &ecsacs.Attachment{ AttachmentArn: strptr("attachmentArn"), AttachmentProperties: attachmentProperties, AttachmentType: strptr(testServiceConnectAttachmentType), } } // getExpectedTestServiceConnectConfig returns *Config based on given parameters. func getExpectedTestServiceConnectConfig(scContainerName string, scIngressConfig []IngressConfigEntry, scEgressConfig *EgressConfig, scDNSConfig []DNSConfigEntry) *Config { return &Config{ ContainerName: scContainerName, IngressConfig: scIngressConfig, EgressConfig: scEgressConfig, DNSConfig: scDNSConfig, } } func TestParseServiceConnectAttachment(t *testing.T) { initServiceConnectConfValue() testSCContainerNameAttachmentProperty := getTestACSAttachmentProperty(serviceConnectContainerNameKey, testServiceConnectContainerName) tt := []struct { testName string testSCAttachmentProperty *ecsacs.AttachmentProperty expectedIngressConfig []IngressConfigEntry expectedEgressConfig *EgressConfig expectedDnsConfig []DNSConfigEntry }{ { testName: "AWSVPC default case", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testAwsVpcDefaultSCConfig), expectedIngressConfig: []IngressConfigEntry{ { InterceptPort: testAwsVpcDefaultInterceptPort, ListenerName: testInboundListenerName, }, }, expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: testIPv4Cidr, IPV6CIDR: "", }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv4Address, }, }, }, { testName: "AWSVPC default case with IPv6 enabled", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testAwsVpcDefaultIPv6EnabledSCConfig), expectedIngressConfig: []IngressConfigEntry{ { InterceptPort: testAwsVpcDefaultInterceptPort, ListenerName: testInboundListenerName, }, }, expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: "", IPV6CIDR: testIPv6Cidr, }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv6Address, }, }, }, { testName: "AWSVPC override case", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testAwsVpcOverrideSCConfig), expectedIngressConfig: []IngressConfigEntry{ { ListenerPort: testListenerPort, }, }, expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: testIPv4Cidr, IPV6CIDR: "", }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv4Address, }, }, }, { testName: "AWSVPC override case with IPv6 enabled", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testAwsVpcOverrideIPv6EnabledSCConfig), expectedIngressConfig: []IngressConfigEntry{ { ListenerPort: testListenerPort, }, }, expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: "", IPV6CIDR: testIPv6Cidr, }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv6Address, }, }, }, { testName: "Bridge default case", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testBridgeDefaultSCConfig), expectedIngressConfig: []IngressConfigEntry{ { ListenerPort: testBridgeDefaultListenerPort, }, }, expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: testIPv4Cidr, IPV6CIDR: "", }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv4Address, }, }, }, { testName: "Bridge default case with no ingress config", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testBridgeDefaultEmptyIngressSCConfig), expectedEgressConfig: &EgressConfig{ ListenerName: testOutboundListenerName, VIP: VIP{ IPV4CIDR: testIPv4Cidr, IPV6CIDR: "", }, }, expectedDnsConfig: []DNSConfigEntry{ { HostName: testHostName, Address: testIPv4Address, }, }, }, { testName: "Bridge default case with no egress config and dns config", testSCAttachmentProperty: getTestACSAttachmentProperty(serviceConnectConfigKey, testBridgeDefaultEmptyEgressSCConfig), expectedIngressConfig: []IngressConfigEntry{ { ListenerPort: testBridgeDefaultListenerPort, }, }, }, } for _, tc := range tt { t.Run(tc.testName, func(t *testing.T) { expectedTestServiceConnectConfig := getExpectedTestServiceConnectConfig(testServiceConnectContainerName, tc.expectedIngressConfig, tc.expectedEgressConfig, tc.expectedDnsConfig) testAttachmentProperties := []*ecsacs.AttachmentProperty{testSCContainerNameAttachmentProperty} testAttachmentProperties = append(testAttachmentProperties, tc.testSCAttachmentProperty) testSCAttachment := getTestACSAttachments(testAttachmentProperties) parsedServiceConnectConfig, err := ParseServiceConnectAttachment(testSCAttachment) assert.NoError(t, err) assert.Equal(t, expectedTestServiceConnectConfig, parsedServiceConnectConfig) }) } } func TestParseServiceConnectAttachmentWithError(t *testing.T) { initServiceConnectConfValue() testSCAttachmentProperty := getTestACSAttachmentProperty(serviceConnectConfigKey, testAwsVpcDefaultSCConfig) tt := []struct { testName string testSCContainerName string testAttachmentPropertyValue string }{ { testName: "AWSVPC default case with the invalid attachment property value", testSCContainerName: testServiceConnectContainerName, testAttachmentPropertyValue: "////hellooooooo////worlddddddd", }, } for _, tc := range tt { t.Run(tc.testName, func(t *testing.T) { testSCAttachmentProperty = getTestACSAttachmentProperty(serviceConnectConfigKey, tc.testAttachmentPropertyValue) testSCContainerNameAttachmentProperty := getTestACSAttachmentProperty(serviceConnectContainerNameKey, tc.testSCContainerName) testAttachmentProperties := []*ecsacs.AttachmentProperty{testSCAttachmentProperty} testAttachmentProperties = append(testAttachmentProperties, testSCContainerNameAttachmentProperty) testSCAttachment := getTestACSAttachments(testAttachmentProperties) _, err := ParseServiceConnectAttachment(testSCAttachment) assert.Error(t, err) }) } }