blob: 65db9480c332002d711d77cb7559381095ef37ab [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 v1alpha3
import (
"reflect"
"testing"
"time"
)
import (
listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
redis "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/redis_proxy/v3"
tcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/tcp_proxy/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
"google.golang.org/protobuf/types/known/durationpb"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/telemetry"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/mesh"
"github.com/apache/dubbo-go-pixiu/pkg/config/protocol"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/collections"
)
func TestBuildRedisFilter(t *testing.T) {
redisFilter := buildRedisFilter("redis", "redis-cluster")
if redisFilter.Name != wellknown.RedisProxy {
t.Errorf("redis filter name is %s not %s", redisFilter.Name, wellknown.RedisProxy)
}
if config, ok := redisFilter.ConfigType.(*listener.Filter_TypedConfig); ok {
redisProxy := redis.RedisProxy{}
if err := config.TypedConfig.UnmarshalTo(&redisProxy); err != nil {
t.Errorf("unmarshal failed: %v", err)
}
if redisProxy.StatPrefix != "redis" {
t.Errorf("redis proxy statPrefix is %s", redisProxy.StatPrefix)
}
if !redisProxy.LatencyInMicros {
t.Errorf("redis proxy latency stat is not configured for microseconds")
}
if redisProxy.PrefixRoutes.CatchAllRoute.Cluster != "redis-cluster" {
t.Errorf("redis proxy's PrefixRoutes.CatchAllCluster is %s", redisProxy.PrefixRoutes.CatchAllRoute.Cluster)
}
} else {
t.Errorf("redis filter type is %T not listener.Filter_TypedConfig ", redisFilter.ConfigType)
}
}
func TestInboundNetworkFilterStatPrefix(t *testing.T) {
cases := []struct {
name string
statPattern string
expectedStatPrefix string
}{
{
"no pattern",
"",
"inbound|8888||",
},
{
"service only pattern",
"%SERVICE%",
"v0.default.example.org",
},
}
services := []*model.Service{
buildService("test.com", "10.10.0.0/24", protocol.TCP, tnow),
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
m := mesh.DefaultMeshConfig()
m.InboundClusterStatName = tt.statPattern
cg := NewConfigGenTest(t, TestOptions{
Services: services,
MeshConfig: m,
})
fcc := inboundChainConfig{
telemetryMetadata: telemetry.FilterChainMetadata{InstanceHostname: "v0.default.example.org"},
clusterName: "inbound|8888||",
}
listenerFilters := NewListenerBuilder(cg.SetupProxy(nil), cg.PushContext()).buildInboundNetworkFilters(fcc)
tcp := &tcp.TcpProxy{}
listenerFilters[len(listenerFilters)-1].GetTypedConfig().UnmarshalTo(tcp)
if tcp.StatPrefix != tt.expectedStatPrefix {
t.Fatalf("Unexpected Stat Prefix, Expecting %s, Got %s", tt.expectedStatPrefix, tcp.StatPrefix)
}
})
}
}
func TestInboundNetworkFilterIdleTimeout(t *testing.T) {
cases := []struct {
name string
idleTimeout string
expected *durationpb.Duration
}{
{
"no idle timeout",
"",
nil,
},
{
"invalid timeout",
"invalid-30s",
nil,
},
{
"valid idle timeout 30s",
"30s",
durationpb.New(30 * time.Second),
},
}
services := []*model.Service{
buildService("test.com", "10.10.0.0/24", protocol.TCP, tnow),
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
cg := NewConfigGenTest(t, TestOptions{Services: services})
fcc := inboundChainConfig{}
node := &model.Proxy{Metadata: &model.NodeMetadata{IdleTimeout: tt.idleTimeout}}
listenerFilters := NewListenerBuilder(cg.SetupProxy(node), cg.PushContext()).buildInboundNetworkFilters(fcc)
tcp := &tcp.TcpProxy{}
listenerFilters[len(listenerFilters)-1].GetTypedConfig().UnmarshalTo(tcp)
if !reflect.DeepEqual(tcp.IdleTimeout, tt.expected) {
t.Fatalf("Unexpected IdleTimeout, Expecting %s, Got %s", tt.expected, tcp.IdleTimeout)
}
})
}
}
func TestOutboundNetworkFilterStatPrefix(t *testing.T) {
cases := []struct {
name string
statPattern string
routes []*networking.RouteDestination
expectedStatPrefix string
}{
{
"no pattern, single route",
"",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
},
},
"outbound|9999||test.com",
},
{
"service only pattern, single route",
"%SERVICE%",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
},
},
"test.com",
},
{
"no pattern, multiple routes",
"",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
Weight: 50,
},
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 8888,
},
},
Weight: 50,
},
},
"test.com.ns", // No stat pattern will be applied for multiple routes, as it will be always be name.namespace.
},
{
"service pattern, multiple routes",
"%SERVICE%",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
Weight: 50,
},
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 8888,
},
},
Weight: 50,
},
},
"test.com.ns", // No stat pattern will be applied for multiple routes, as it will be always be name.namespace.
},
}
services := []*model.Service{
buildService("test.com", "10.10.0.0/24", protocol.TCP, tnow),
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
m := mesh.DefaultMeshConfig()
m.OutboundClusterStatName = tt.statPattern
cg := NewConfigGenTest(t, TestOptions{MeshConfig: m, Services: services})
listeners := buildOutboundNetworkFilters(
cg.SetupProxy(nil), tt.routes, cg.PushContext(),
&model.Port{Port: 9999}, config.Meta{Name: "test.com", Namespace: "ns"})
tcp := &tcp.TcpProxy{}
listeners[0].GetTypedConfig().UnmarshalTo(tcp)
if tcp.StatPrefix != tt.expectedStatPrefix {
t.Fatalf("Unexpected Stat Prefix, Expecting %s, Got %s", tt.expectedStatPrefix, tcp.StatPrefix)
}
})
}
}
func TestOutboundNetworkFilterWithSourceIPHashing(t *testing.T) {
services := []*model.Service{
buildService("test.com", "10.10.0.0/24", protocol.TCP, tnow),
buildService("testsimple.com", "10.10.0.0/24", protocol.TCP, tnow),
buildService("subsettest.com", "10.10.0.0/24", protocol.TCP, tnow),
buildService("subsettestdifferent.com", "10.10.0.0/24", protocol.TCP, tnow),
}
simpleDestinationRuleSpec := &networking.DestinationRule{
Host: "testsimple.com",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_Simple{},
},
},
}
simpleDestinationRule := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(),
Name: "acme-v3-0",
Namespace: "not-default",
},
Spec: simpleDestinationRuleSpec,
}
destinationRuleSpec := &networking.DestinationRule{
Host: "test.com",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{UseSourceIp: true},
},
},
},
},
}
destinationRule := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(),
Name: "acme-v3-1",
Namespace: "not-default",
},
Spec: destinationRuleSpec,
}
subsetdestinationRuleSpec := &networking.DestinationRule{
Host: "subsettest.com",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{UseSourceIp: true},
},
},
},
},
Subsets: []*networking.Subset{{Name: "v1", Labels: map[string]string{"version": "v1"}}},
}
subsetdestinationRule := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(),
Name: "acme-v3-2",
Namespace: "not-default",
},
Spec: subsetdestinationRuleSpec,
}
subsetdestinationRuleDifferentSpec := &networking.DestinationRule{
Host: "subsettestdifferent.com",
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_ConsistentHash{
ConsistentHash: &networking.LoadBalancerSettings_ConsistentHashLB{
HashKey: &networking.LoadBalancerSettings_ConsistentHashLB_UseSourceIp{UseSourceIp: true},
},
},
},
},
Subsets: []*networking.Subset{
{
Name: "v1",
Labels: map[string]string{"version": "v1"},
TrafficPolicy: &networking.TrafficPolicy{
LoadBalancer: &networking.LoadBalancerSettings{
LbPolicy: &networking.LoadBalancerSettings_Simple{},
},
},
},
},
}
subsetDifferentdestinationRule := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Destinationrules.Resource().GroupVersionKind(),
Name: "acme-v3-3",
Namespace: "not-default",
},
Spec: subsetdestinationRuleDifferentSpec,
}
destinationRules := []*config.Config{&destinationRule, &simpleDestinationRule, &subsetdestinationRule, &subsetDifferentdestinationRule}
cg := NewConfigGenTest(t, TestOptions{
ConfigPointers: destinationRules,
Services: services,
})
proxy := cg.SetupProxy(&model.Proxy{ConfigNamespace: "not-default"})
cases := []struct {
name string
routes []*networking.RouteDestination
configMeta config.Meta
useSourceIP bool
}{
{
"destination rule without sourceip",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "testsimple.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
},
},
config.Meta{Name: "testsimple.com", Namespace: "ns"},
false,
},
{
"destination rule has sourceip",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "test.com",
Port: &networking.PortSelector{
Number: 9999,
},
},
},
},
config.Meta{Name: "test.com", Namespace: "ns"},
true,
},
{
"subset destination rule does not have traffic policy",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "subsettest.com",
Port: &networking.PortSelector{
Number: 9999,
},
Subset: "v1",
},
},
},
config.Meta{Name: "subsettest.com", Namespace: "ns"},
true,
},
{
"subset destination rule overrides traffic policy",
[]*networking.RouteDestination{
{
Destination: &networking.Destination{
Host: "subsettestdifferent.com",
Port: &networking.PortSelector{
Number: 9999,
},
Subset: "v1",
},
},
},
config.Meta{Name: "subsettestdifferent.com", Namespace: "ns"},
false,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
listeners := buildOutboundNetworkFilters(proxy, tt.routes, cg.PushContext(), &model.Port{Port: 9999}, tt.configMeta)
tcp := &tcp.TcpProxy{}
listeners[0].GetTypedConfig().UnmarshalTo(tcp)
hasSourceIP := tcp.HashPolicy != nil && len(tcp.HashPolicy) == 1 && tcp.HashPolicy[0].GetSourceIp() != nil
if hasSourceIP != tt.useSourceIP {
t.Fatalf("Unexpected SourceIp hash policy. expected: %v, got: %v", tt.useSourceIP, hasSourceIP)
}
})
}
}