blob: b504f108534493b696ececbf504e3630caafb92f [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 (
"fmt"
"reflect"
"testing"
"time"
)
import (
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
meshapi "istio.io/api/mesh/v1alpha1"
networking "istio.io/api/networking/v1alpha3"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/serviceregistry/provider"
"github.com/apache/dubbo-go-pixiu/pilot/test/xdstest"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"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"
"github.com/apache/dubbo-go-pixiu/pkg/config/schema/gvk"
"github.com/apache/dubbo-go-pixiu/pkg/config/visibility"
)
func TestGenerateVirtualHostDomains(t *testing.T) {
cases := []struct {
name string
service *model.Service
port int
node *model.Proxy
want []string
}{
{
name: "same domain",
service: &model.Service{
Hostname: "foo.local.campus.net",
MeshExternal: false,
},
port: 80,
node: &model.Proxy{
DNSDomain: "local.campus.net",
},
want: []string{
"foo.local.campus.net", "foo.local.campus.net:80",
"foo", "foo:80",
},
},
{
name: "different domains with some shared dns",
service: &model.Service{
Hostname: "foo.local.campus.net",
MeshExternal: false,
},
port: 80,
node: &model.Proxy{
DNSDomain: "remote.campus.net",
},
want: []string{
"foo.local.campus.net",
"foo.local.campus.net:80",
"foo.local",
"foo.local:80",
"foo.local.campus",
"foo.local.campus:80",
},
},
{
name: "different domains with no shared dns",
service: &model.Service{
Hostname: "foo.local.campus.net",
MeshExternal: false,
},
port: 80,
node: &model.Proxy{
DNSDomain: "example.com",
},
want: []string{"foo.local.campus.net", "foo.local.campus.net:80"},
},
{
name: "k8s service with default domain",
service: &model.Service{
Hostname: "echo.default.svc.cluster.local",
MeshExternal: false,
},
port: 8123,
node: &model.Proxy{
DNSDomain: "default.svc.cluster.local",
},
want: []string{
"echo.default.svc.cluster.local",
"echo.default.svc.cluster.local:8123",
"echo",
"echo:8123",
"echo.default.svc",
"echo.default.svc:8123",
"echo.default",
"echo.default:8123",
},
},
{
name: "k8s service with default domain and different namespace",
service: &model.Service{
Hostname: "echo.default.svc.cluster.local",
MeshExternal: false,
},
port: 8123,
node: &model.Proxy{
DNSDomain: "mesh.svc.cluster.local",
},
want: []string{
"echo.default.svc.cluster.local",
"echo.default.svc.cluster.local:8123",
"echo.default",
"echo.default:8123",
"echo.default.svc",
"echo.default.svc:8123",
},
},
{
name: "k8s service with custom domain 2",
service: &model.Service{
Hostname: "google.local",
MeshExternal: false,
},
port: 8123,
node: &model.Proxy{
DNSDomain: "foo.svc.custom.k8s.local",
},
want: []string{"google.local", "google.local:8123"},
},
{
name: "ipv4 domain",
service: &model.Service{
Hostname: "1.2.3.4",
MeshExternal: false,
},
port: 8123,
node: &model.Proxy{
DNSDomain: "example.com",
},
want: []string{"1.2.3.4", "1.2.3.4:8123"},
},
{
name: "ipv6 domain",
service: &model.Service{
Hostname: "2406:3003:2064:35b8:864:a648:4b96:e37d",
MeshExternal: false,
},
port: 8123,
node: &model.Proxy{
DNSDomain: "example.com",
},
want: []string{"[2406:3003:2064:35b8:864:a648:4b96:e37d]", "[2406:3003:2064:35b8:864:a648:4b96:e37d]:8123"},
},
{
name: "back subset of cluster domain in address",
service: &model.Service{
Hostname: "aaa.example.local",
MeshExternal: true,
},
port: 7777,
node: &model.Proxy{
DNSDomain: "tests.svc.cluster.local",
},
want: []string{"aaa.example.local", "aaa.example.local:7777"},
},
{
name: "front subset of cluster domain in address",
service: &model.Service{
Hostname: "aaa.example.my",
MeshExternal: true,
},
port: 7777,
node: &model.Proxy{
DNSDomain: "tests.svc.my.long.domain.suffix",
},
want: []string{"aaa.example.my", "aaa.example.my:7777"},
},
{
name: "large subset of cluster domain in address",
service: &model.Service{
Hostname: "aaa.example.my.long",
MeshExternal: true,
},
port: 7777,
node: &model.Proxy{
DNSDomain: "tests.svc.my.long.domain.suffix",
},
want: []string{"aaa.example.my.long", "aaa.example.my.long:7777"},
},
{
name: "no overlap of cluster domain in address",
service: &model.Service{
Hostname: "aaa.example.com",
MeshExternal: true,
},
port: 7777,
node: &model.Proxy{
DNSDomain: "tests.svc.cluster.local",
},
want: []string{"aaa.example.com", "aaa.example.com:7777"},
},
}
testFn := func(service *model.Service, port int, node *model.Proxy, want []string) error {
out, _ := generateVirtualHostDomains(service, port, node)
if !reflect.DeepEqual(out, want) {
return fmt.Errorf("unexpected virtual hosts:\ngot %v\nwant %v", out, want)
}
return nil
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
if err := testFn(c.service, c.port, c.node, c.want); err != nil {
t.Error(err)
}
})
}
}
func TestSidecarOutboundHTTPRouteConfigWithDuplicateHosts(t *testing.T) {
virtualServiceSpec := &networking.VirtualService{
Hosts: []string{"test-duplicate-domains.default.svc.cluster.local", "test-duplicate-domains.default"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test-duplicate-domains.default",
},
},
},
},
},
}
virtualServiceSpecDefault := &networking.VirtualService{
Hosts: []string{"test.default"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.default",
},
},
},
},
},
}
cases := []struct {
name string
services []*model.Service
config []config.Config
expectedHosts map[string][]string
expectedDestination map[string]string
}{
{
"more exact first",
[]*model.Service{
buildHTTPService("test.local", visibility.Public, "", "default", 80),
buildHTTPService("test", visibility.Public, "", "default", 80),
},
nil,
map[string][]string{
"allow_any": {"*"},
// BUG: test should be below
"test.local:80": {"test.local", "test.local:80"},
"test:80": {"test", "test:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
"test.local:80": "outbound|80||test.local",
"test:80": "outbound|80||test",
},
},
{
"more exact first with full cluster domain",
[]*model.Service{
buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80),
buildHTTPService("test", visibility.Public, "", "default", 80),
},
nil,
map[string][]string{
"allow_any": {"*"},
"test.default.svc.cluster.local:80": {
"test.default.svc.cluster.local", "test.default.svc.cluster.local:80",
"test.default.svc", "test.default.svc:80",
"test.default", "test.default:80",
},
"test:80": {"test", "test:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
"test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local",
"test:80": "outbound|80||test",
},
},
{
"more exact second",
[]*model.Service{
buildHTTPService("test", visibility.Public, "", "default", 80),
buildHTTPService("test.local", visibility.Public, "", "default", 80),
},
nil,
map[string][]string{
"allow_any": {"*"},
"test.local:80": {"test.local", "test.local:80"},
"test:80": {"test", "test:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
"test.local:80": "outbound|80||test.local",
"test:80": "outbound|80||test",
},
},
{
"virtual service",
[]*model.Service{
buildHTTPService("test-duplicate-domains.default.svc.cluster.local", visibility.Public, "", "default", 80),
},
[]config.Config{{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: virtualServiceSpec,
}},
map[string][]string{
"allow_any": {"*"},
"test-duplicate-domains.default.svc.cluster.local:80": {
"test-duplicate-domains.default.svc.cluster.local", "test-duplicate-domains.default.svc.cluster.local:80",
"test-duplicate-domains", "test-duplicate-domains:80",
"test-duplicate-domains.default.svc", "test-duplicate-domains.default.svc:80",
},
"test-duplicate-domains.default:80": {"test-duplicate-domains.default", "test-duplicate-domains.default:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
// Virtual service destination takes precedence
"test-duplicate-domains.default.svc.cluster.local:80": "outbound|80||test-duplicate-domains.default",
"test-duplicate-domains.default:80": "outbound|80||test-duplicate-domains.default",
},
},
{
"virtual service conflict",
[]*model.Service{
buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80),
},
[]config.Config{{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "acme",
},
Spec: virtualServiceSpecDefault,
}},
map[string][]string{
"allow_any": {"*"},
"test.default.svc.cluster.local:80": {
"test.default.svc.cluster.local", "test.default.svc.cluster.local:80",
"test", "test:80",
"test.default.svc", "test.default.svc:80",
},
"test.default:80": {"test.default", "test.default:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
// From the service, go to the service
"test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local",
"test.default:80": "outbound|80||test.default",
},
},
{
"multiple ports",
[]*model.Service{
buildHTTPService("test.local", visibility.Public, "", "default", 70, 80, 90),
},
nil,
map[string][]string{
"allow_any": {"*"},
"test.local:80": {"test.local", "test.local:80"},
},
map[string]string{
"allow_any": "PassthroughCluster",
"test.local:80": "outbound|80||test.local",
},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
// ensure services are ordered
t0 := time.Now()
for _, svc := range tt.services {
svc.CreationTime = t0
t0 = t0.Add(time.Minute)
}
cg := NewConfigGenTest(t, TestOptions{
Services: tt.services,
Configs: tt.config,
})
vHostCache := make(map[int][]*route.VirtualHost)
//routeName := "80"
resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig(
cg.SetupProxy(nil), &model.PushRequest{Push: cg.PushContext()}, "80", vHostCache, nil, nil)
routeCfg := route.RouteConfiguration{}
resource.Resource.UnmarshalTo(&routeCfg)
xdstest.ValidateRouteConfiguration(t, &routeCfg)
got := map[string][]string{}
clusters := map[string]string{}
for _, vh := range routeCfg.VirtualHosts {
got[vh.Name] = vh.Domains
clusters[vh.Name] = vh.GetRoutes()[0].GetRoute().GetCluster()
}
if !reflect.DeepEqual(tt.expectedHosts, got) {
t.Fatalf("unexpected virtual hosts\n%v, wanted\n%v", got, tt.expectedHosts)
}
if !reflect.DeepEqual(tt.expectedDestination, clusters) {
t.Fatalf("unexpected destinations\n%v, wanted\n%v", clusters, tt.expectedDestination)
}
})
}
}
func TestSidecarOutboundHTTPRouteConfig(t *testing.T) {
services := []*model.Service{
buildHTTPService("bookinfo.com", visibility.Public, wildcardIP, "default", 9999, 70),
buildHTTPService("private.com", visibility.Private, wildcardIP, "default", 9999, 80),
buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080),
buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70),
buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60),
buildHTTPService("test-headless.com", visibility.Public, wildcardIP, "not-default", 8888),
buildHTTPService("service-A.default.svc.cluster.local", visibility.Public, "", "default", 7777),
}
sidecarConfig := &config.Config{
Meta: config.Meta{
Name: "foo",
Namespace: "not-default",
GroupVersionKind: gvk.Sidecar,
},
Spec: &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Port: &networking.Port{
// A port that is not in any of the services
Number: 9000,
Protocol: "HTTP",
Name: "something",
},
Bind: "1.1.1.1",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
// Unix domain socket listener
Number: 0,
Protocol: "HTTP",
Name: "something",
},
Bind: "unix://foo/bar/baz",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
// Unix domain socket listener
Number: 0,
Protocol: "HTTP",
Name: "something",
},
Bind: "unix://foo/bar/headless",
Hosts: []string{"*/test-headless.com"},
},
{
Port: &networking.Port{
// A port that is in one of the services
Number: 8080,
Protocol: "HTTP",
Name: "foo",
},
Hosts: []string{"default/bookinfo.com", "not-default/test.com"},
},
{
// Wildcard egress importing from all namespaces
Hosts: []string{"*/*"},
},
},
},
}
sidecarConfigWithWildcard := &config.Config{
Meta: config.Meta{
Name: "foo",
Namespace: "not-default",
GroupVersionKind: gvk.Sidecar,
},
Spec: &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Port: &networking.Port{
Number: 7443,
Protocol: "HTTP",
Name: "something",
},
Hosts: []string{"*/*"},
},
},
},
}
sidecarConfigWitHTTPProxy := &config.Config{
Meta: config.Meta{
Name: "foo",
Namespace: "not-default",
GroupVersionKind: gvk.Sidecar,
},
Spec: &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Port: &networking.Port{
Number: 7443,
Protocol: "HTTP_PROXY",
Name: "something",
},
Hosts: []string{"*/*"},
},
},
},
}
sidecarConfigWithRegistryOnly := &config.Config{
Meta: config.Meta{
Name: "foo",
Namespace: "not-default",
GroupVersionKind: gvk.Sidecar,
},
Spec: &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Port: &networking.Port{
// A port that is not in any of the services
Number: 9000,
Protocol: "HTTP",
Name: "something",
},
Bind: "1.1.1.1",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
// Unix domain socket listener
Number: 0,
Protocol: "HTTP",
Name: "something",
},
Bind: "unix://foo/bar/baz",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
Number: 0,
Protocol: "HTTP",
Name: "something",
},
Bind: "unix://foo/bar/headless",
Hosts: []string{"*/test-headless.com"},
},
{
Port: &networking.Port{
Number: 18888,
Protocol: "HTTP",
Name: "foo",
},
Hosts: []string{"*/test-headless.com"},
},
{
// Wildcard egress importing from all namespaces
Hosts: []string{"*/*"},
},
},
OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
Mode: networking.OutboundTrafficPolicy_REGISTRY_ONLY,
},
},
}
sidecarConfigWithAllowAny := &config.Config{
Meta: config.Meta{
Name: "foo",
Namespace: "not-default",
GroupVersionKind: gvk.Sidecar,
},
Spec: &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Port: &networking.Port{
// A port that is not in any of the services
Number: 9000,
Protocol: "HTTP",
Name: "something",
},
Bind: "1.1.1.1",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
// Unix domain socket listener
Number: 0,
Protocol: "HTTP",
Name: "something",
},
Bind: "unix://foo/bar/baz",
Hosts: []string{"*/bookinfo.com"},
},
{
Port: &networking.Port{
// A port that is in one of the services
Number: 8080,
Protocol: "HTTP",
Name: "foo",
},
Hosts: []string{"default/bookinfo.com", "not-default/test.com"},
},
{
// Wildcard egress importing from all namespaces
Hosts: []string{"*/*"},
},
},
OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
},
},
}
virtualServiceSpec1 := &networking.VirtualService{
Hosts: []string{"test-private-2.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
// Subset: "some-subset",
Host: "example.org",
Port: &networking.PortSelector{
Number: 61,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec2 := &networking.VirtualService{
Hosts: []string{"test-private-2.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 62,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec3 := &networking.VirtualService{
Hosts: []string{"test-private-3.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 63,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec4 := &networking.VirtualService{
Hosts: []string{"test-headless.com", "example.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 64,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec5 := &networking.VirtualService{
Hosts: []string{"test-svc.testns.svc.cluster.local"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test-svc.testn.svc.cluster.local",
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec6 := &networking.VirtualService{
Hosts: []string{"match-no-service"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "non-exist-service",
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec7 := &networking.VirtualService{
Hosts: []string{"service-A.default.svc.cluster.local", "service-A.v2", "service-A.v3"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v2"}}},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "service-A",
Subset: "v2",
},
},
},
},
{
Match: []*networking.HTTPMatchRequest{
{
Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v3"}}},
},
},
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "service-A",
Subset: "v3",
},
},
},
},
},
}
virtualService1 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme2-v1",
Namespace: "not-default",
},
Spec: virtualServiceSpec1,
}
virtualService2 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v2",
Namespace: "not-default",
},
Spec: virtualServiceSpec2,
}
virtualService3 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec3,
}
virtualService4 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v4",
Namespace: "not-default",
},
Spec: virtualServiceSpec4,
}
virtualService5 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec5,
}
virtualService6 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec6,
}
virtualService7 := config.Config{
Meta: config.Meta{
GroupVersionKind: gvk.VirtualService,
Name: "vs-1",
Namespace: "default",
},
Spec: virtualServiceSpec7,
}
// With the config above, RDS should return a valid route for the following route names
// port 9000 - [bookinfo.com:9999, *.bookinfo.com:9990], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com
// unix://foo/bar/baz - [bookinfo.com:9999, *.bookinfo.com:9999], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com
// port 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080], but no bookinfo.com or test.com
// port 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999]
// port 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9]
// port 70 - [test-private.com, test-private.com:70, 9.9.9.9, 9.9.9.9:70], [bookinfo.com, bookinfo.com:70]
// Without sidecar config [same as wildcard egress listener], expect routes
// 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999],
// 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080]
// 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9]
// 70 - [bookinfo.com, bookinfo.com:70, *.bookinfo.com:70],[test-private.com, test-private.com:70, 9.9.9.9:70, 9.9.9.9]
cases := []struct {
name string
routeName string
sidecarConfig *config.Config
virtualServiceConfigs []*config.Config
// virtualHost Name and domains
expectedHosts map[string]map[string]bool
expectedRoutes int
registryOnly bool
}{
{
name: "sidecar config port that is not in any service",
routeName: "9000",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "sidecar config with unix domain socket listener",
routeName: "unix://foo/bar/baz",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"bookinfo.com:9999": {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true},
"bookinfo.com:70": {"bookinfo.com:70": true, "*.bookinfo.com:70": true},
"allow_any": {
"*": true,
},
},
},
{
name: "sidecar config port that is in one of the services",
routeName: "8080",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test.com:8080": {"test.com": true, "test.com:8080": true, "8.8.8.8": true, "8.8.8.8:8080": true},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "sidecar config with fallthrough and registry only and allow any mesh config",
routeName: "80",
sidecarConfig: sidecarConfigWithRegistryOnly,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"block_all": {
"*": true,
},
},
registryOnly: false,
},
{
name: "sidecar config with fallthrough and allow any and registry only mesh config",
routeName: "80",
sidecarConfig: sidecarConfigWithAllowAny,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"allow_any": {
"*": true,
},
},
registryOnly: false,
},
{
name: "sidecar config with allow any and virtual service includes non existing service",
routeName: "80",
sidecarConfig: sidecarConfigWithAllowAny,
virtualServiceConfigs: []*config.Config{&virtualService6},
expectedHosts: map[string]map[string]bool{
// does not include `match-no-service`
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"match-no-service.not-default:80": {"match-no-service.not-default": true, "match-no-service.not-default:80": true},
"allow_any": {
"*": true,
},
},
registryOnly: false,
},
{
name: "sidecar config with allow any and virtual service includes non existing service",
routeName: "80",
sidecarConfig: sidecarConfigWithAllowAny,
virtualServiceConfigs: []*config.Config{&virtualService6},
expectedHosts: map[string]map[string]bool{
// does not include `match-no-service`
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"match-no-service.not-default:80": {"match-no-service.not-default": true, "match-no-service.not-default:80": true},
"allow_any": {
"*": true,
},
},
registryOnly: false,
},
{
name: "wildcard egress importing from all namespaces: 9999",
routeName: "9999",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"bookinfo.com:9999": {
"bookinfo.com:9999": true, "bookinfo.com": true,
"*.bookinfo.com:9999": true, "*.bookinfo.com": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "wildcard egress importing from all namespaces: 80",
routeName: "80",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "wildcard egress importing from all namespaces: 70",
routeName: "70",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:70": {
"test-private.com": true, "test-private.com:70": true, "9.9.9.9": true, "9.9.9.9:70": true,
},
"bookinfo.com:70": {
"bookinfo.com": true, "bookinfo.com:70": true,
"*.bookinfo.com": true, "*.bookinfo.com:70": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public service from other namespaces: 9999",
routeName: "9999",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"bookinfo.com:9999": {
"bookinfo.com:9999": true, "bookinfo.com": true,
"*.bookinfo.com:9999": true, "*.bookinfo.com": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public service from other namespaces: 8080",
routeName: "8080",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test.com:8080": {
"test.com:8080": true, "test.com": true, "8.8.8.8": true, "8.8.8.8:8080": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public services from other namespaces: 80",
routeName: "80",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public services from other namespaces: 70",
routeName: "70",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:70": {
"test-private.com": true, "test-private.com:70": true, "9.9.9.9": true, "9.9.9.9:70": true,
},
"bookinfo.com:70": {
"bookinfo.com": true, "bookinfo.com:70": true,
"*.bookinfo.com": true, "*.bookinfo.com:70": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public services from other namespaces: 70 with sniffing",
routeName: "test-private.com:70",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:70": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import public services from other namespaces: 80 with fallthrough",
routeName: "80",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"allow_any": {
"*": true,
},
},
registryOnly: false,
},
{
name: "no sidecar config - import public services from other namespaces: 80 with fallthrough and registry only",
routeName: "80",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config with virtual services with duplicate entries",
routeName: "60",
sidecarConfig: nil,
virtualServiceConfigs: []*config.Config{&virtualService1, &virtualService2},
expectedHosts: map[string]map[string]bool{
"test-private-2.com:60": {
"test-private-2.com": true, "test-private-2.com:60": true, "9.9.9.10": true, "9.9.9.10:60": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config with virtual services with no service in registry",
routeName: "80", // no service for the host in registry; use port 80 by default
sidecarConfig: nil,
virtualServiceConfigs: []*config.Config{&virtualService3},
expectedHosts: map[string]map[string]bool{
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"test-private-3.com:80": {
"test-private-3.com": true, "test-private-3.com:80": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config - import headless service from other namespaces: 8888",
routeName: "8888",
sidecarConfig: nil,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-headless.com:8888": {
"test-headless.com": true, "test-headless.com:8888": true, "*.test-headless.com": true, "*.test-headless.com:8888": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "no sidecar config with virtual services - import headless service from other namespaces: 8888",
routeName: "8888",
sidecarConfig: nil,
virtualServiceConfigs: []*config.Config{&virtualService4},
expectedHosts: map[string]map[string]bool{
"test-headless.com:8888": {
"test-headless.com": true, "test-headless.com:8888": true, "*.test-headless.com": true, "*.test-headless.com:8888": true,
},
"example.com:8888": {
"example.com": true, "example.com:8888": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "sidecar config with unix domain socket listener - import headless service",
routeName: "unix://foo/bar/headless",
sidecarConfig: sidecarConfig,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "sidecar config port - import headless service",
routeName: "18888",
sidecarConfig: sidecarConfigWithRegistryOnly,
virtualServiceConfigs: nil,
expectedHosts: map[string]map[string]bool{
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "wild card sidecar config, with non matching virtual service",
routeName: "7443",
sidecarConfig: sidecarConfigWithWildcard,
virtualServiceConfigs: []*config.Config{&virtualService5},
expectedHosts: map[string]map[string]bool{
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "http proxy sidecar config, with non matching virtual service",
routeName: "7443",
sidecarConfig: sidecarConfigWitHTTPProxy,
virtualServiceConfigs: []*config.Config{&virtualService5},
expectedHosts: map[string]map[string]bool{
"bookinfo.com:9999": {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true},
"bookinfo.com:70": {"bookinfo.com:70": true, "*.bookinfo.com:70": true},
"test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true},
"test-private-2.com:60": {
"test-private-2.com:60": true, "9.9.9.10:60": true,
},
"test-private.com:70": {
"test-private.com:70": true, "9.9.9.9:70": true,
},
"test-private.com:80": {
"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
},
"test.com:8080": {
"test.com:8080": true, "8.8.8.8:8080": true,
},
"service-A.default.svc.cluster.local:7777": {
"service-A.default.svc.cluster.local:7777": true,
},
"block_all": {
"*": true,
},
},
registryOnly: true,
},
{
name: "virtual service hosts with subsets and with existing service",
routeName: "7777",
sidecarConfig: sidecarConfigWithAllowAny,
virtualServiceConfigs: []*config.Config{&virtualService7},
expectedHosts: map[string]map[string]bool{
"allow_any": {
"*": true,
},
"service-A.default.svc.cluster.local:7777": {
"service-A.default.svc.cluster.local": true,
"service-A.default.svc.cluster.local:7777": true,
},
"service-A.v2:7777": {
"service-A.v2": true,
"service-A.v2:7777": true,
},
"service-A.v3:7777": {
"service-A.v3": true,
"service-A.v3:7777": true,
},
},
expectedRoutes: 7,
registryOnly: false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
testSidecarRDSVHosts(t, services, c.sidecarConfig, c.virtualServiceConfigs,
c.routeName, c.expectedHosts, c.expectedRoutes, c.registryOnly)
})
}
}
func TestSelectVirtualService(t *testing.T) {
services := []*model.Service{
buildHTTPService("bookinfo.com", visibility.Public, wildcardIP, "default", 9999, 70),
buildHTTPService("private.com", visibility.Private, wildcardIP, "default", 9999, 80),
buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080),
buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70),
buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60),
buildHTTPService("test-headless.com", visibility.Public, wildcardIP, "not-default", 8888),
}
servicesByName := make(map[host.Name]*model.Service, len(services))
for _, svc := range services {
servicesByName[svc.Hostname] = svc
}
virtualServiceSpec1 := &networking.VirtualService{
Hosts: []string{"test-private-2.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
// Subset: "some-subset",
Host: "example.org",
Port: &networking.PortSelector{
Number: 61,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec2 := &networking.VirtualService{
Hosts: []string{"test-private-2.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 62,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec3 := &networking.VirtualService{
Hosts: []string{"test-private-3.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 63,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec4 := &networking.VirtualService{
Hosts: []string{"test-headless.com", "example.com"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test.org",
Port: &networking.PortSelector{
Number: 64,
},
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec5 := &networking.VirtualService{
Hosts: []string{"test-svc.testns.svc.cluster.local"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test-svc.testn.svc.cluster.local",
},
Weight: 100,
},
},
},
},
}
virtualServiceSpec6 := &networking.VirtualService{
Hosts: []string{"match-no-service"},
Gateways: []string{"mesh"},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "non-exist-service",
},
Weight: 100,
},
},
},
},
}
virtualService1 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme2-v1",
Namespace: "not-default",
},
Spec: virtualServiceSpec1,
}
virtualService2 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v2",
Namespace: "not-default",
},
Spec: virtualServiceSpec2,
}
virtualService3 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec3,
}
virtualService4 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v4",
Namespace: "not-default",
},
Spec: virtualServiceSpec4,
}
virtualService5 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec5,
}
virtualService6 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "acme-v3",
Namespace: "not-default",
},
Spec: virtualServiceSpec6,
}
configs := selectVirtualServices(
[]config.Config{virtualService1, virtualService2, virtualService3, virtualService4, virtualService5, virtualService6},
servicesByName)
expectedVS := []string{virtualService1.Name, virtualService2.Name, virtualService4.Name}
if len(expectedVS) != len(configs) {
t.Fatalf("Unexpected virtualService, got %d, epxected %d", len(configs), len(expectedVS))
}
for i, config := range configs {
if config.Name != expectedVS[i] {
t.Fatalf("Unexpected virtualService, got %s, epxected %s", config.Name, expectedVS[i])
}
}
}
func testSidecarRDSVHosts(t *testing.T, services []*model.Service,
sidecarConfig *config.Config, virtualServices []*config.Config, routeName string,
expectedHosts map[string]map[string]bool, expectedRoutes int, registryOnly bool,
) {
m := mesh.DefaultMeshConfig()
if registryOnly {
m.OutboundTrafficPolicy = &meshapi.MeshConfig_OutboundTrafficPolicy{Mode: meshapi.MeshConfig_OutboundTrafficPolicy_REGISTRY_ONLY}
}
cg := NewConfigGenTest(t, TestOptions{
MeshConfig: m,
Services: services,
ConfigPointers: append(virtualServices, sidecarConfig),
})
proxy := &model.Proxy{ConfigNamespace: "not-default", DNSDomain: "default.example.org"}
vHostCache := make(map[int][]*route.VirtualHost)
resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig(
cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, routeName, vHostCache, nil, nil)
routeCfg := route.RouteConfiguration{}
resource.Resource.UnmarshalTo(&routeCfg)
xdstest.ValidateRouteConfiguration(t, &routeCfg)
if expectedRoutes == 0 {
expectedRoutes = len(expectedHosts)
}
numberOfRoutes := 0
for _, vhost := range routeCfg.VirtualHosts {
numberOfRoutes += len(vhost.Routes)
if _, found := expectedHosts[vhost.Name]; !found {
t.Fatalf("unexpected vhost block %s for route %s",
vhost.Name, routeName)
}
for _, domain := range vhost.Domains {
if !expectedHosts[vhost.Name][domain] {
t.Fatalf("unexpected vhost domain %s in vhost %s, for route %s", domain, vhost.Name, routeName)
}
}
for want := range expectedHosts[vhost.Name] {
found := false
for _, got := range vhost.Domains {
if got == want {
found = true
}
}
if !found {
t.Fatalf("expected vhost domain %s in vhost %s, for route %s not found. got domains %v", want, vhost.Name, routeName, vhost.Domains)
}
}
if !vhost.GetIncludeRequestAttemptCount() {
t.Fatal("Expected that include request attempt count is set to true, but set to false")
}
}
if (expectedRoutes >= 0) && (numberOfRoutes != expectedRoutes) {
t.Errorf("Wrong number of routes. expected: %v, Got: %v", expectedRoutes, numberOfRoutes)
}
}
func buildHTTPService(hostname string, v visibility.Instance, ip, namespace string, ports ...int) *model.Service {
service := &model.Service{
CreationTime: tnow,
Hostname: host.Name(hostname),
DefaultAddress: ip,
Resolution: model.DNSLB,
Attributes: model.ServiceAttributes{
ServiceRegistry: provider.Kubernetes,
Namespace: namespace,
ExportTo: map[visibility.Instance]bool{v: true},
},
}
if ip == wildcardIP {
service.Resolution = model.Passthrough
}
Ports := make([]*model.Port, 0)
for _, p := range ports {
Ports = append(Ports, &model.Port{
Name: fmt.Sprintf("http-%d", p),
Port: p,
Protocol: protocol.HTTP,
})
}
service.Ports = Ports
return service
}