blob: ee152d84f04963ef705f72cdd2286b99e06a8e39 [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 model
import (
"fmt"
"reflect"
"regexp"
"sort"
"sync"
"testing"
"time"
)
import (
"github.com/golang/protobuf/ptypes/wrappers"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
. "github.com/onsi/gomega"
"go.uber.org/atomic"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/structpb"
extensions "istio.io/api/extensions/v1alpha1"
meshconfig "istio.io/api/mesh/v1alpha1"
networking "istio.io/api/networking/v1alpha3"
securityBeta "istio.io/api/security/v1beta1"
selectorpb "istio.io/api/type/v1beta1"
"k8s.io/apimachinery/pkg/types"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/features"
"github.com/apache/dubbo-go-pixiu/pkg/config"
"github.com/apache/dubbo-go-pixiu/pkg/config/constants"
"github.com/apache/dubbo-go-pixiu/pkg/config/host"
"github.com/apache/dubbo-go-pixiu/pkg/config/labels"
"github.com/apache/dubbo-go-pixiu/pkg/config/mesh"
"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"
"github.com/apache/dubbo-go-pixiu/pkg/test"
"github.com/apache/dubbo-go-pixiu/pkg/test/util/assert"
)
func TestMergeUpdateRequest(t *testing.T) {
push0 := &PushContext{}
// trivially different push contexts just for testing
push1 := &PushContext{ProxyStatus: make(map[string]map[string]ProxyPushStatus)}
var t0 time.Time
t1 := t0.Add(time.Minute)
cases := []struct {
name string
left *PushRequest
right *PushRequest
merged PushRequest
}{
{
"left nil",
nil,
&PushRequest{Full: true},
PushRequest{Full: true},
},
{
"right nil",
&PushRequest{Full: true},
nil,
PushRequest{Full: true},
},
{
"simple merge",
&PushRequest{
Full: true,
Push: push0,
Start: t0,
ConfigsUpdated: map[ConfigKey]struct{}{
{Kind: config.GroupVersionKind{Kind: "cfg1"}, Namespace: "ns1"}: {},
},
Reason: []TriggerReason{ServiceUpdate, ServiceUpdate},
},
&PushRequest{
Full: false,
Push: push1,
Start: t1,
ConfigsUpdated: map[ConfigKey]struct{}{
{Kind: config.GroupVersionKind{Kind: "cfg2"}, Namespace: "ns2"}: {},
},
Reason: []TriggerReason{EndpointUpdate},
},
PushRequest{
Full: true,
Push: push1,
Start: t0,
ConfigsUpdated: map[ConfigKey]struct{}{
{Kind: config.GroupVersionKind{Kind: "cfg1"}, Namespace: "ns1"}: {},
{Kind: config.GroupVersionKind{Kind: "cfg2"}, Namespace: "ns2"}: {},
},
Reason: []TriggerReason{ServiceUpdate, ServiceUpdate, EndpointUpdate},
},
},
{
"skip config type merge: one empty",
&PushRequest{Full: true, ConfigsUpdated: nil},
&PushRequest{Full: true, ConfigsUpdated: map[ConfigKey]struct{}{{
Kind: config.GroupVersionKind{Kind: "cfg2"},
}: {}}},
PushRequest{Full: true, ConfigsUpdated: nil, Reason: nil},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
got := tt.left.CopyMerge(tt.right)
if !reflect.DeepEqual(&tt.merged, got) {
t.Fatalf("expected %v, got %v", &tt.merged, got)
}
got = tt.left.Merge(tt.right)
if !reflect.DeepEqual(&tt.merged, got) {
t.Fatalf("expected %v, got %v", &tt.merged, got)
}
})
}
}
func TestConcurrentMerge(t *testing.T) {
reqA := &PushRequest{Reason: make([]TriggerReason, 0, 100)}
reqB := &PushRequest{Reason: []TriggerReason{ServiceUpdate, ProxyUpdate}}
for i := 0; i < 50; i++ {
go func() {
reqA.CopyMerge(reqB)
}()
}
if len(reqA.Reason) != 0 {
t.Fatalf("reqA modified: %v", reqA.Reason)
}
if len(reqB.Reason) != 2 {
t.Fatalf("reqB modified: %v", reqB.Reason)
}
}
func TestEnvoyFilters(t *testing.T) {
proxyVersionRegex := regexp.MustCompile(`1\.4.*`)
envoyFilters := []*EnvoyFilterWrapper{
{
Name: "ef1",
workloadSelector: map[string]string{"app": "v1"},
Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{
networking.EnvoyFilter_LISTENER: {
{
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{
ProxyVersion: "1\\.4.*",
},
},
ProxyVersionRegex: proxyVersionRegex,
},
},
},
},
{
Name: "ef2",
workloadSelector: map[string]string{"app": "v1"},
Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{
networking.EnvoyFilter_CLUSTER: {
{
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{
ProxyVersion: `1\\.4.*`,
},
},
ProxyVersionRegex: proxyVersionRegex,
},
},
},
},
}
push := &PushContext{
Mesh: &meshconfig.MeshConfig{
RootNamespace: "dubbo-system",
},
envoyFiltersByNamespace: map[string][]*EnvoyFilterWrapper{
"dubbo-system": envoyFilters,
"test-ns": envoyFilters,
},
}
if !push.HasEnvoyFilters("ef1", "test-ns") {
t.Errorf("Check presence of EnvoyFilter ef1 at test-ns got false, want true")
}
if push.HasEnvoyFilters("ef3", "test-ns") {
t.Errorf("Check presence of EnvoyFilter ef3 at test-ns got true, want false")
}
cases := []struct {
name string
proxy *Proxy
expectedListenerPatches int
expectedClusterPatches int
}{
{
name: "proxy matches two envoyfilters",
proxy: &Proxy{
Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
ConfigNamespace: "test-ns",
},
expectedListenerPatches: 2,
expectedClusterPatches: 2,
},
{
name: "proxy in root namespace matches an envoyfilter",
proxy: &Proxy{
Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
ConfigNamespace: "dubbo-system",
},
expectedListenerPatches: 1,
expectedClusterPatches: 1,
},
{
name: "proxy matches no envoyfilter",
proxy: &Proxy{
Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v2"}},
ConfigNamespace: "test-ns",
},
expectedListenerPatches: 0,
expectedClusterPatches: 0,
},
{
name: "proxy matches envoyfilter in root ns",
proxy: &Proxy{
Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
ConfigNamespace: "test-n2",
},
expectedListenerPatches: 1,
expectedClusterPatches: 1,
},
{
name: "proxy version matches no envoyfilters",
proxy: &Proxy{
Metadata: &NodeMetadata{IstioVersion: "1.3.0", Labels: map[string]string{"app": "v1"}},
ConfigNamespace: "test-ns",
},
expectedListenerPatches: 0,
expectedClusterPatches: 0,
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
filter := push.EnvoyFilters(tt.proxy)
if filter == nil {
if tt.expectedClusterPatches != 0 || tt.expectedListenerPatches != 0 {
t.Errorf("Got no envoy filter")
}
return
}
if len(filter.Patches[networking.EnvoyFilter_CLUSTER]) != tt.expectedClusterPatches {
t.Errorf("Expect %d envoy filter cluster patches, but got %d", tt.expectedClusterPatches, len(filter.Patches[networking.EnvoyFilter_CLUSTER]))
}
if len(filter.Patches[networking.EnvoyFilter_LISTENER]) != tt.expectedListenerPatches {
t.Errorf("Expect %d envoy filter listener patches, but got %d", tt.expectedListenerPatches, len(filter.Patches[networking.EnvoyFilter_LISTENER]))
}
})
}
}
func TestEnvoyFilterOrder(t *testing.T) {
env := &Environment{}
store := istioConfigStore{ConfigStore: NewFakeStore()}
ctime := time.Now()
envoyFilters := []config.Config{
{
Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
Spec: &networking.EnvoyFilter{
Priority: 10,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
Spec: &networking.EnvoyFilter{
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
Spec: &networking.EnvoyFilter{
Priority: 10,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
Spec: &networking.EnvoyFilter{
Priority: 10,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "a-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: time.Now()},
Spec: &networking.EnvoyFilter{
Priority: 20,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "b-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
Spec: &networking.EnvoyFilter{
Priority: 20,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
Spec: &networking.EnvoyFilter{
Priority: -1,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
{
Meta: config.Meta{Name: "super-high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
Spec: &networking.EnvoyFilter{
Priority: -10,
ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
Patch: &networking.EnvoyFilter_Patch{},
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
},
},
},
},
},
}
expectedns := []string{
"testns/super-high-priority", "testns/high-priority", "testns/default-priority", "testns/a-medium-priority",
"testns/b-medium-priority", "testns/b-low-priority", "testns/a-low-priority",
}
expectedns1 := []string{"testns-1/default-priority", "testns-1/a-medium-priority", "testns-1/b-medium-priority"}
for _, cfg := range envoyFilters {
_, _ = store.Create(cfg)
}
env.ConfigStore = &store
m := mesh.DefaultMeshConfig()
env.Watcher = mesh.NewFixedWatcher(m)
env.Init()
// Init a new push context
pc := NewPushContext()
if err := pc.initEnvoyFilters(env); err != nil {
t.Fatal(err)
}
gotns := make([]string, 0)
for _, filter := range pc.envoyFiltersByNamespace["testns"] {
gotns = append(gotns, filter.Keys()...)
}
gotns1 := make([]string, 0)
for _, filter := range pc.envoyFiltersByNamespace["testns-1"] {
gotns1 = append(gotns1, filter.Keys()...)
}
if !reflect.DeepEqual(expectedns, gotns) {
t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns, gotns)
}
if !reflect.DeepEqual(expectedns1, gotns1) {
t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns1, gotns1)
}
}
func TestWasmPlugins(t *testing.T) {
env := &Environment{}
store := istioConfigStore{ConfigStore: NewFakeStore()}
wasmPlugins := map[string]config.Config{
"invalid-type": {
Meta: config.Meta{Name: "invalid-type", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
Spec: &networking.DestinationRule{},
},
"invalid-url": {
Meta: config.Meta{Name: "invalid-url", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
Priority: &wrappers.Int64Value{Value: 5},
Url: "notavalid%%Url;",
},
},
"authn-low-prio-all": {
Meta: config.Meta{Name: "authn-low-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
Priority: &wrappers.Int64Value{Value: 10},
Url: "file:///etc/istio/filters/authn.wasm",
PluginConfig: &structpb.Struct{
Fields: map[string]*structpb.Value{
"test": {
Kind: &structpb.Value_StringValue{StringValue: "test"},
},
},
},
Sha256: "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
},
},
"global-authn-low-prio-ingress": {
Meta: config.Meta{Name: "global-authn-low-prio-ingress", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
Priority: &wrappers.Int64Value{Value: 5},
Selector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{
"istio": "ingressgateway",
},
},
},
},
"authn-med-prio-all": {
Meta: config.Meta{Name: "authn-med-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
Priority: &wrappers.Int64Value{Value: 50},
},
},
"global-authn-high-prio-app": {
Meta: config.Meta{Name: "global-authn-high-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHN,
Priority: &wrappers.Int64Value{Value: 1000},
Selector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{
"app": "productpage",
},
},
},
},
"global-authz-med-prio-app": {
Meta: config.Meta{Name: "global-authz-med-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHZ,
Priority: &wrappers.Int64Value{Value: 50},
Selector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{
"app": "productpage",
},
},
},
},
"authz-high-prio-ingress": {
Meta: config.Meta{Name: "authz-high-prio-ingress", Namespace: "testns-2", GroupVersionKind: gvk.WasmPlugin},
Spec: &extensions.WasmPlugin{
Phase: extensions.PluginPhase_AUTHZ,
Priority: &wrappers.Int64Value{Value: 1000},
},
},
}
testCases := []struct {
name string
node *Proxy
expectedExtensions map[extensions.PluginPhase][]*WasmPluginWrapper
}{
{
name: "nil proxy",
node: nil,
expectedExtensions: nil,
},
{
name: "nomatch",
node: &Proxy{
ConfigNamespace: "other",
Metadata: &NodeMetadata{},
},
expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{},
},
{
name: "ingress",
node: &Proxy{
ConfigNamespace: "other",
Metadata: &NodeMetadata{
Labels: map[string]string{
"istio": "ingressgateway",
},
},
},
expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
extensions.PluginPhase_AUTHN: {
convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]),
},
},
},
{
name: "ingress-testns-1",
node: &Proxy{
ConfigNamespace: "testns-1",
Metadata: &NodeMetadata{
Labels: map[string]string{
"istio": "ingressgateway",
},
},
},
expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
extensions.PluginPhase_AUTHN: {
convertToWasmPluginWrapper(wasmPlugins["authn-med-prio-all"]),
convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all"]),
convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]),
},
},
},
{
name: "testns-2",
node: &Proxy{
ConfigNamespace: "testns-2",
Metadata: &NodeMetadata{
Labels: map[string]string{
"app": "productpage",
},
},
},
expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
extensions.PluginPhase_AUTHN: {
convertToWasmPluginWrapper(wasmPlugins["global-authn-high-prio-app"]),
},
extensions.PluginPhase_AUTHZ: {
convertToWasmPluginWrapper(wasmPlugins["authz-high-prio-ingress"]),
convertToWasmPluginWrapper(wasmPlugins["global-authz-med-prio-app"]),
},
},
},
}
for _, config := range wasmPlugins {
store.Create(config)
}
env.ConfigStore = &store
m := mesh.DefaultMeshConfig()
env.Watcher = mesh.NewFixedWatcher(m)
env.Init()
// Init a new push context
pc := NewPushContext()
pc.Mesh = m
if err := pc.initWasmPlugins(env); err != nil {
t.Fatal(err)
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := pc.WasmPlugins(tc.node)
if !reflect.DeepEqual(tc.expectedExtensions, result) {
t.Errorf("WasmPlugins did not match expectations\n\ngot: %v\n\nexpected: %v", result, tc.expectedExtensions)
}
})
}
}
func TestServiceIndex(t *testing.T) {
g := NewWithT(t)
env := &Environment{}
store := istioConfigStore{ConfigStore: NewFakeStore()}
env.ConfigStore = &store
env.ServiceDiscovery = &localServiceDiscovery{
services: []*Service{
{
Hostname: "svc-unset",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
},
},
{
Hostname: "svc-public",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{visibility.Public: true},
},
},
{
Hostname: "svc-private",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{visibility.Private: true},
},
},
{
Hostname: "svc-none",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{visibility.None: true},
},
},
{
Hostname: "svc-namespace",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{"namespace": true},
},
},
},
serviceInstances: []*ServiceInstance{{
Endpoint: &IstioEndpoint{
Address: "192.168.1.2",
EndpointPort: 8000,
TLSMode: DisabledTLSModeLabel,
},
}},
}
m := mesh.DefaultMeshConfig()
env.Watcher = mesh.NewFixedWatcher(m)
env.Init()
// Init a new push context
pc := NewPushContext()
if err := pc.InitContext(env, nil, nil); err != nil {
t.Fatal(err)
}
si := pc.ServiceIndex
// Should have all 5 services
g.Expect(si.instancesByPort).To(HaveLen(5))
g.Expect(si.HostnameAndNamespace).To(HaveLen(5))
// Should just have "namespace"
g.Expect(si.exportedToNamespace).To(HaveLen(1))
g.Expect(serviceNames(si.exportedToNamespace["namespace"])).To(Equal([]string{"svc-namespace"}))
g.Expect(serviceNames(si.public)).To(Equal([]string{"svc-public", "svc-unset"}))
// Should just have "test1"
g.Expect(si.privateByNamespace).To(HaveLen(1))
g.Expect(serviceNames(si.privateByNamespace["test1"])).To(Equal([]string{"svc-private"}))
}
func TestIsServiceVisible(t *testing.T) {
targetNamespace := "foo"
cases := []struct {
name string
pushContext *PushContext
service *Service
expect bool
}{
{
name: "service whose namespace is foo has no exportTo map with global private",
pushContext: &PushContext{
exportToDefaults: exportToDefaults{
service: map[visibility.Instance]bool{
visibility.Private: true,
},
},
},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "foo",
},
},
expect: true,
},
{
name: "service whose namespace is bar has no exportTo map with global private",
pushContext: &PushContext{
exportToDefaults: exportToDefaults{
service: map[visibility.Instance]bool{
visibility.Private: true,
},
},
},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
},
},
expect: false,
},
{
name: "service whose namespace is bar has no exportTo map with global public",
pushContext: &PushContext{
exportToDefaults: exportToDefaults{
service: map[visibility.Instance]bool{
visibility.Public: true,
},
},
},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
},
},
expect: true,
},
{
name: "service whose namespace is foo has exportTo map with private",
pushContext: &PushContext{},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "foo",
ExportTo: map[visibility.Instance]bool{
visibility.Private: true,
},
},
},
expect: true,
},
{
name: "service whose namespace is bar has exportTo map with private",
pushContext: &PushContext{},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
ExportTo: map[visibility.Instance]bool{
visibility.Private: true,
},
},
},
expect: false,
},
{
name: "service whose namespace is bar has exportTo map with public",
pushContext: &PushContext{},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
ExportTo: map[visibility.Instance]bool{
visibility.Public: true,
},
},
},
expect: true,
},
{
name: "service whose namespace is bar has exportTo map with specific namespace foo",
pushContext: &PushContext{},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
ExportTo: map[visibility.Instance]bool{
visibility.Instance("foo"): true,
},
},
},
expect: true,
},
{
name: "service whose namespace is bar has exportTo map with specific namespace baz",
pushContext: &PushContext{},
service: &Service{
Attributes: ServiceAttributes{
Namespace: "bar",
ExportTo: map[visibility.Instance]bool{
visibility.Instance("baz"): true,
},
},
},
expect: false,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
isVisible := c.pushContext.IsServiceVisible(c.service, targetNamespace)
g := NewWithT(t)
g.Expect(isVisible).To(Equal(c.expect))
})
}
}
func serviceNames(svcs []*Service) []string {
var s []string
for _, ss := range svcs {
s = append(s, string(ss.Hostname))
}
sort.Strings(s)
return s
}
func TestInitPushContext(t *testing.T) {
env := &Environment{}
configStore := NewFakeStore()
_, _ = configStore.Create(config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "test1",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Hosts: []string{"rule1.com"},
ExportTo: []string{".", "ns1"},
},
})
_, _ = configStore.Create(config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "test1",
GroupVersionKind: gvk.DestinationRule,
},
Spec: &networking.DestinationRule{
ExportTo: []string{".", "ns1"},
},
})
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
env.ServiceDiscovery = &localServiceDiscovery{
services: []*Service{
{
Hostname: "svc1",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
},
},
{
Hostname: "svc2",
Ports: allPorts,
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{visibility.Public: true},
},
},
},
serviceInstances: []*ServiceInstance{{
Endpoint: &IstioEndpoint{
Address: "192.168.1.2",
EndpointPort: 8000,
TLSMode: DisabledTLSModeLabel,
},
}},
}
m := mesh.DefaultMeshConfig()
env.Watcher = mesh.NewFixedWatcher(m)
env.Init()
// Init a new push context
old := NewPushContext()
if err := old.InitContext(env, nil, nil); err != nil {
t.Fatal(err)
}
// Create a new one, copying from the old one
// Pass a ConfigsUpdated otherwise we would just copy it directly
newPush := NewPushContext()
if err := newPush.InitContext(env, old, &PushRequest{
ConfigsUpdated: map[ConfigKey]struct{}{
{Kind: gvk.Secret}: {},
},
}); err != nil {
t.Fatal(err)
}
// Check to ensure the update is identical to the old one
// There is probably a better way to do this.
diff := cmp.Diff(old, newPush,
// Allow looking into exported fields for parts of push context
cmp.AllowUnexported(PushContext{}, exportToDefaults{}, serviceIndex{}, virtualServiceIndex{},
destinationRuleIndex{}, gatewayIndex{}, consolidatedDestRules{}, IstioEgressListenerWrapper{}, SidecarScope{},
AuthenticationPolicies{}, NetworkManager{}, sidecarIndex{}, Telemetries{}, ProxyConfigs{}, consolidatedDestRule{}, serviceNameMappingIndex{}, serviceMetadataIndex{}),
// These are not feasible/worth comparing
cmpopts.IgnoreTypes(sync.RWMutex{}, localServiceDiscovery{}, FakeStore{}, atomic.Bool{}, sync.Mutex{}),
cmpopts.IgnoreInterfaces(struct{ mesh.Holder }{}),
protocmp.Transform(),
)
if diff != "" {
t.Fatalf("Push context had a diff after update: %v", diff)
}
}
func TestSidecarScope(t *testing.T) {
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "dubbo-system"})}
ps.Mesh = env.Mesh()
ps.ServiceIndex.HostnameAndNamespace["svc1.default.cluster.local"] = map[string]*Service{"default": nil}
ps.ServiceIndex.HostnameAndNamespace["svc2.nosidecar.cluster.local"] = map[string]*Service{"nosidecar": nil}
ps.ServiceIndex.HostnameAndNamespace["svc3.dubbo-system.cluster.local"] = map[string]*Service{"dubbo-system": nil}
configStore := NewFakeStore()
sidecarWithWorkloadSelector := &networking.Sidecar{
WorkloadSelector: &networking.WorkloadSelector{
Labels: map[string]string{"app": "foo"},
},
Egress: []*networking.IstioEgressListener{
{
Hosts: []string{"default/*"},
},
},
OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{},
}
sidecarWithoutWorkloadSelector := &networking.Sidecar{
Egress: []*networking.IstioEgressListener{
{
Hosts: []string{"default/*"},
},
},
OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{},
}
configWithWorkloadSelector := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Sidecars.Resource().GroupVersionKind(),
Name: "foo",
Namespace: "default",
},
Spec: sidecarWithWorkloadSelector,
}
rootConfig := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Sidecars.Resource().GroupVersionKind(),
Name: "global",
Namespace: constants.IstioSystemNamespace,
},
Spec: sidecarWithoutWorkloadSelector,
}
_, _ = configStore.Create(configWithWorkloadSelector)
_, _ = configStore.Create(rootConfig)
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
if err := ps.initSidecarScopes(env); err != nil {
t.Fatalf("init sidecar scope failed: %v", err)
}
cases := []struct {
proxy *Proxy
labels labels.Instance
sidecar string
describe string
}{
{
proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "default"},
labels: labels.Instance{"app": "foo"},
sidecar: "default/foo",
describe: "match local sidecar",
},
{
proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "default"},
labels: labels.Instance{"app": "bar"},
sidecar: "default/global",
describe: "no match local sidecar",
},
{
proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "nosidecar"},
labels: labels.Instance{"app": "bar"},
sidecar: "nosidecar/global",
describe: "no sidecar",
},
{
proxy: &Proxy{Type: Router, ConfigNamespace: "dubbo-system"},
labels: labels.Instance{"app": "istio-gateway"},
sidecar: "dubbo-system/default-sidecar",
describe: "gateway sidecar scope",
},
}
for _, c := range cases {
t.Run(c.describe, func(t *testing.T) {
scope := ps.getSidecarScope(c.proxy, c.labels)
if c.sidecar != scopeToSidecar(scope) {
t.Errorf("should get sidecar %s but got %s", c.sidecar, scopeToSidecar(scope))
}
})
}
}
func TestBestEffortInferServiceMTLSMode(t *testing.T) {
const partialNS string = "partial"
const wholeNS string = "whole"
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "dubbo-system"})}
sd := &localServiceDiscovery{}
env.ServiceDiscovery = sd
ps.Mesh = env.Mesh()
configStore := NewFakeStore()
// Add beta policies
_, _ = configStore.Create(*createTestPeerAuthenticationResource("default", wholeNS, time.Now(), nil, securityBeta.PeerAuthentication_MutualTLS_STRICT))
// workload level beta policy.
_, _ = configStore.Create(*createTestPeerAuthenticationResource("workload-beta-policy", partialNS, time.Now(), &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{
"app": "httpbin",
"version": "v1",
},
}, securityBeta.PeerAuthentication_MutualTLS_DISABLE))
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
if err := ps.initAuthnPolicies(env); err != nil {
t.Fatalf("init authn policies failed: %v", err)
}
instancePlainText := &ServiceInstance{
Endpoint: &IstioEndpoint{
Address: "192.168.1.2",
EndpointPort: 1000000,
TLSMode: DisabledTLSModeLabel,
},
}
cases := []struct {
name string
serviceNamespace string
servicePort int
serviceResolution Resolution
serviceInstances []*ServiceInstance
wanted MutualTLSMode
}{
{
name: "from namespace policy",
serviceNamespace: wholeNS,
servicePort: 80,
serviceResolution: ClientSideLB,
wanted: MTLSStrict,
},
{
name: "from mesh default",
serviceNamespace: partialNS,
servicePort: 80,
serviceResolution: ClientSideLB,
wanted: MTLSPermissive,
},
{
name: "headless service with no instances found yet",
serviceNamespace: partialNS,
servicePort: 80,
serviceResolution: Passthrough,
wanted: MTLSDisable,
},
{
name: "headless service with instances",
serviceNamespace: partialNS,
servicePort: 80,
serviceResolution: Passthrough,
serviceInstances: []*ServiceInstance{instancePlainText},
wanted: MTLSDisable,
},
}
serviceName := host.Name("some-service")
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
service := &Service{
Hostname: host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)),
Resolution: tc.serviceResolution,
Attributes: ServiceAttributes{Namespace: tc.serviceNamespace},
}
// Intentionally use the externalService with the same name and namespace for test, though
// these attributes don't matter.
externalService := &Service{
Hostname: host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)),
Resolution: tc.serviceResolution,
Attributes: ServiceAttributes{Namespace: tc.serviceNamespace},
MeshExternal: true,
}
sd.serviceInstances = tc.serviceInstances
port := &Port{
Port: tc.servicePort,
}
if got := ps.BestEffortInferServiceMTLSMode(nil, service, port); got != tc.wanted {
t.Fatalf("want %s, but got %s", tc.wanted, got)
}
if got := ps.BestEffortInferServiceMTLSMode(nil, externalService, port); got != MTLSUnknown {
t.Fatalf("MTLS mode for external service should always be %s, but got %s", MTLSUnknown, got)
}
})
}
}
func scopeToSidecar(scope *SidecarScope) string {
if scope == nil {
return ""
}
return scope.Namespace + "/" + scope.Name
}
func TestSetDestinationRuleInheritance(t *testing.T) {
test.SetBoolForTest(t, &features.EnableDestinationRuleInheritance, true)
ps := NewPushContext()
ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "dubbo-system"}
testhost := "httpbin.org"
meshDestinationRule := config.Config{
Meta: config.Meta{
Name: "meshRule",
Namespace: ps.Mesh.RootNamespace,
},
Spec: &networking.DestinationRule{
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 1},
MaxConnections: 111,
},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_MUTUAL,
ClientCertificate: "/etc/certs/myclientcert.pem",
PrivateKey: "/etc/certs/client_private_key.pem",
CaCertificates: "/etc/certs/rootcacerts.pem",
},
},
},
}
nsDestinationRule := config.Config{
Meta: config.Meta{
Name: "nsRule",
Namespace: "test",
},
Spec: &networking.DestinationRule{
TrafficPolicy: &networking.TrafficPolicy{
OutlierDetection: &networking.OutlierDetection{
ConsecutiveGatewayErrors: &wrappers.UInt32Value{Value: 222},
Interval: &durationpb.Duration{Seconds: 22},
},
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 2,
},
},
},
},
}
svcDestinationRule := config.Config{
Meta: config.Meta{
Name: "svcRule",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 33},
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_SIMPLE,
},
},
},
}
destinationRuleNamespace2 := config.Config{
Meta: config.Meta{
Name: "svcRule2",
Namespace: "test2",
},
Spec: &networking.DestinationRule{
Host: testhost,
Subsets: []*networking.Subset{
{
Name: "subset1",
},
{
Name: "subset2",
},
},
},
}
workloadSpecificDrNamespace2 := config.Config{
Meta: config.Meta{
Name: "drRule2",
Namespace: "test2",
},
Spec: &networking.DestinationRule{
Host: testhost,
WorkloadSelector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{"app": "app1"},
},
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
},
},
}
testCases := []struct {
name string
proxyNs string
serviceNs string
serviceHostname string
expectedConfig string
expectedSourceRule []types.NamespacedName
expectedPolicy *networking.TrafficPolicy
}{
{
name: "merge mesh+namespace+service DR",
proxyNs: "test",
serviceNs: "test",
serviceHostname: testhost,
expectedConfig: "svcRule",
expectedSourceRule: []types.NamespacedName{
{Namespace: "dubbo-system", Name: "meshRule"},
{Namespace: "test", Name: "nsRule"},
{Namespace: "test", Name: "svcRule"},
},
expectedPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 33},
MaxConnections: 111,
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
ConsecutiveGatewayErrors: &wrappers.UInt32Value{Value: 222},
Interval: &durationpb.Duration{Seconds: 22},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_SIMPLE,
},
},
},
{
name: "merge mesh+service DR",
proxyNs: "test2",
serviceNs: "test2",
serviceHostname: testhost,
expectedConfig: "svcRule2",
expectedSourceRule: []types.NamespacedName{
{Namespace: "dubbo-system", Name: "meshRule"},
{Namespace: "test2", Name: "svcRule2"},
},
expectedPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 1},
MaxConnections: 111,
},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_MUTUAL,
ClientCertificate: "/etc/certs/myclientcert.pem",
PrivateKey: "/etc/certs/client_private_key.pem",
CaCertificates: "/etc/certs/rootcacerts.pem",
},
},
},
{
name: "merge mesh+workloadselector DR",
proxyNs: "test2",
serviceNs: "test2",
serviceHostname: testhost,
expectedConfig: "drRule2",
expectedSourceRule: []types.NamespacedName{
{Namespace: "dubbo-system", Name: "meshRule"},
{Namespace: "test2", Name: "drRule2"},
{Namespace: "test2", Name: "svcRule2"},
},
expectedPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 1},
MaxConnections: 111,
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_MUTUAL,
ClientCertificate: "/etc/certs/myclientcert.pem",
PrivateKey: "/etc/certs/client_private_key.pem",
CaCertificates: "/etc/certs/rootcacerts.pem",
},
},
},
{
name: "unknown host returns merged mesh+namespace",
proxyNs: "test",
serviceNs: "test",
serviceHostname: "unknown.host",
expectedConfig: "nsRule",
expectedSourceRule: []types.NamespacedName{
{Namespace: "dubbo-system", Name: "meshRule"},
{Namespace: "test", Name: "nsRule"},
},
expectedPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 2,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 1},
MaxConnections: 111,
},
},
OutlierDetection: &networking.OutlierDetection{
ConsecutiveGatewayErrors: &wrappers.UInt32Value{Value: 222},
Interval: &durationpb.Duration{Seconds: 22},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_MUTUAL,
ClientCertificate: "/etc/certs/myclientcert.pem",
PrivateKey: "/etc/certs/client_private_key.pem",
CaCertificates: "/etc/certs/rootcacerts.pem",
},
},
},
{
name: "unknown namespace+host returns mesh",
proxyNs: "unknown",
serviceNs: "unknown",
serviceHostname: "unknown.host",
expectedConfig: "meshRule",
expectedSourceRule: []types.NamespacedName{
{Namespace: "dubbo-system", Name: "meshRule"},
},
expectedPolicy: meshDestinationRule.Spec.(*networking.DestinationRule).TrafficPolicy,
},
}
ps.SetDestinationRules([]config.Config{meshDestinationRule, nsDestinationRule, svcDestinationRule, destinationRuleNamespace2, workloadSpecificDrNamespace2})
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
mergedConfigList := ps.destinationRule(tt.proxyNs,
&Service{
Hostname: host.Name(tt.serviceHostname),
Attributes: ServiceAttributes{
Namespace: tt.serviceNs,
},
})
expectedConfigPresent := false
for _, mergedConfig := range mergedConfigList {
if mergedConfig.rule.Name == tt.expectedConfig {
expectedConfigPresent = true
mergedPolicy := mergedConfig.rule.Spec.(*networking.DestinationRule).TrafficPolicy
assert.Equal(t, mergedPolicy, tt.expectedPolicy)
assert.Equal(t, mergedConfig.from, tt.expectedSourceRule)
}
}
if !expectedConfigPresent {
t.Errorf("case %s failed, merged config should contain most specific config name, wanted %v but missing", tt.name, tt.expectedConfig)
}
})
}
}
func TestSetDestinationRuleWithWorkloadSelector(t *testing.T) {
ps := NewPushContext()
ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "dubbo-system"}
testhost := "httpbin.org"
app1DestinationRule := config.Config{
Meta: config.Meta{
Name: "nsRule1",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test2", "."},
WorkloadSelector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{"app": "app1"},
},
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 1},
MaxConnections: 111,
},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_MUTUAL,
ClientCertificate: "/etc/certs/myclientcert.pem",
PrivateKey: "/etc/certs/client_private_key.pem",
CaCertificates: "/etc/certs/rootcacerts.pem",
},
},
},
}
app2DestinationRule := config.Config{
Meta: config.Meta{
Name: "nsRule2",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test2", "."},
WorkloadSelector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{"app": "app2"},
},
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 33},
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_SIMPLE,
},
},
},
}
app3DestinationRule := config.Config{
Meta: config.Meta{
Name: "nsRule3",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test2", "."},
WorkloadSelector: &selectorpb.WorkloadSelector{
MatchLabels: map[string]string{"app": "app2"},
},
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 33},
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_SIMPLE,
},
},
},
}
namespaceDestinationRule := config.Config{
Meta: config.Meta{
Name: "nsRule4",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{".", "test2"},
TrafficPolicy: &networking.TrafficPolicy{
ConnectionPool: &networking.ConnectionPoolSettings{
Http: &networking.ConnectionPoolSettings_HTTPSettings{
MaxRetries: 33,
},
Tcp: &networking.ConnectionPoolSettings_TCPSettings{
ConnectTimeout: &durationpb.Duration{Seconds: 33},
},
},
OutlierDetection: &networking.OutlierDetection{
Consecutive_5XxErrors: &wrappers.UInt32Value{Value: 3},
},
Tls: &networking.ClientTLSSettings{
Mode: networking.ClientTLSSettings_SIMPLE,
},
},
},
}
testCases := []struct {
name string
proxyNs string
serviceNs string
serviceHostname string
expectedDrCount int
expectedDrName []string
}{
{
name: "return list of DRs for specific host",
proxyNs: "test",
serviceNs: "test",
serviceHostname: testhost,
expectedDrCount: 3,
expectedDrName: []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name},
},
{
name: "workload specific DR should not be exported",
proxyNs: "test2",
serviceNs: "test",
serviceHostname: testhost,
expectedDrCount: 1,
expectedDrName: []string{namespaceDestinationRule.Meta.Name},
},
{
name: "rules with same workloadselector should be merged",
proxyNs: "test",
serviceNs: "test",
serviceHostname: testhost,
expectedDrCount: 3,
expectedDrName: []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name},
},
}
ps.SetDestinationRules([]config.Config{app1DestinationRule, app2DestinationRule, app3DestinationRule, namespaceDestinationRule})
for _, tt := range testCases {
drList := ps.destinationRule(tt.proxyNs,
&Service{
Hostname: host.Name(tt.serviceHostname),
Attributes: ServiceAttributes{
Namespace: tt.serviceNs,
},
})
if len(drList) != tt.expectedDrCount {
t.Errorf("case %s failed, %d destinationRules for host %v got %v", tt.name, tt.expectedDrCount, tt.serviceHostname, drList)
}
for i, dr := range drList {
if dr.rule.Name != tt.expectedDrName[i] {
t.Errorf("case %s failed, destinationRuleName expected %v got %v", tt.name, tt.expectedDrName[i], dr.rule.Name)
}
}
}
}
func TestSetDestinationRuleMerging(t *testing.T) {
ps := NewPushContext()
ps.exportToDefaults.destinationRule = map[visibility.Instance]bool{visibility.Public: true}
testhost := "httpbin.org"
destinationRuleNamespace1 := config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
Subsets: []*networking.Subset{
{
Name: "subset1",
},
{
Name: "subset2",
},
},
},
}
destinationRuleNamespace2 := config.Config{
Meta: config.Meta{
Name: "rule2",
Namespace: "test",
},
Spec: &networking.DestinationRule{
Host: testhost,
Subsets: []*networking.Subset{
{
Name: "subset3",
},
{
Name: "subset4",
},
},
},
}
expectedDestRules := []types.NamespacedName{
{Namespace: "test", Name: "rule1"},
{Namespace: "test", Name: "rule2"},
}
ps.SetDestinationRules([]config.Config{destinationRuleNamespace1, destinationRuleNamespace2})
private := ps.destinationRuleIndex.namespaceLocal["test"].destRules[host.Name(testhost)]
public := ps.destinationRuleIndex.exportedByNamespace["test"].destRules[host.Name(testhost)]
subsetsLocal := private[0].rule.Spec.(*networking.DestinationRule).Subsets
subsetsExport := public[0].rule.Spec.(*networking.DestinationRule).Subsets
assert.Equal(t, private[0].from, expectedDestRules)
assert.Equal(t, public[0].from, expectedDestRules)
if len(subsetsLocal) != 4 {
t.Errorf("want %d, but got %d", 4, len(subsetsLocal))
}
if len(subsetsExport) != 4 {
t.Errorf("want %d, but got %d", 4, len(subsetsExport))
}
}
func TestSetDestinationRuleWithExportTo(t *testing.T) {
ps := NewPushContext()
ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "dubbo-system"}
testhost := "httpbin.org"
appHost := "foo.app.org"
wildcardHost1 := "*.org"
wildcardHost2 := "*.app.org"
destinationRuleNamespace1 := config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "test1",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{".", "ns1"},
Subsets: []*networking.Subset{
{
Name: "subset1",
},
{
Name: "subset2",
},
},
},
}
destinationRuleNamespace2 := config.Config{
Meta: config.Meta{
Name: "rule2",
Namespace: "test2",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test2", "ns1", "test1"},
Subsets: []*networking.Subset{
{
Name: "subset3",
},
{
Name: "subset4",
},
},
},
}
destinationRuleNamespace3 := config.Config{
Meta: config.Meta{
Name: "rule3",
Namespace: "test3",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test1", "test2", "*"},
Subsets: []*networking.Subset{
{
Name: "subset5",
},
{
Name: "subset6",
},
},
},
}
destinationRuleNamespace4Local := config.Config{
Meta: config.Meta{
Name: "rule4-local",
Namespace: "test4",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"test4"},
Subsets: []*networking.Subset{
{
Name: "subset15",
},
{
Name: "subset16",
},
},
},
}
destinationRuleRootNamespace := config.Config{
Meta: config.Meta{
Name: "rule4",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: testhost,
Subsets: []*networking.Subset{
{
Name: "subset7",
},
{
Name: "subset8",
},
},
},
}
destinationRuleRootNamespaceLocal := config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: testhost,
ExportTo: []string{"."},
Subsets: []*networking.Subset{
{
Name: "subset9",
},
{
Name: "subset10",
},
},
},
}
destinationRuleRootNamespaceLocalWithWildcardHost1 := config.Config{
Meta: config.Meta{
Name: "rule2",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: wildcardHost1,
ExportTo: []string{"."},
Subsets: []*networking.Subset{
{
Name: "subset11",
},
{
Name: "subset12",
},
},
},
}
destinationRuleRootNamespaceLocalWithWildcardHost2 := config.Config{
Meta: config.Meta{
Name: "rule3",
Namespace: "dubbo-system",
},
Spec: &networking.DestinationRule{
Host: wildcardHost2,
ExportTo: []string{"."},
Subsets: []*networking.Subset{
{
Name: "subset13",
},
{
Name: "subset14",
},
},
},
}
ps.SetDestinationRules([]config.Config{
destinationRuleNamespace1, destinationRuleNamespace2,
destinationRuleNamespace3, destinationRuleNamespace4Local,
destinationRuleRootNamespace, destinationRuleRootNamespaceLocal,
destinationRuleRootNamespaceLocalWithWildcardHost1, destinationRuleRootNamespaceLocalWithWildcardHost2,
})
cases := []struct {
proxyNs string
serviceNs string
host string
wantSubsets []string
}{
{
proxyNs: "test1",
serviceNs: "test1",
host: testhost,
wantSubsets: []string{"subset1", "subset2"},
},
{
proxyNs: "test1",
serviceNs: "test2",
host: testhost,
wantSubsets: []string{"subset1", "subset2"},
},
{
proxyNs: "test2",
serviceNs: "test1",
host: testhost,
wantSubsets: []string{"subset3", "subset4"},
},
{
proxyNs: "test3",
serviceNs: "test1",
host: testhost,
wantSubsets: []string{"subset5", "subset6"},
},
{
proxyNs: "test5",
serviceNs: "test4",
host: testhost,
wantSubsets: []string{"subset7", "subset8"},
},
{
proxyNs: "ns1",
serviceNs: "test1",
host: testhost,
wantSubsets: []string{"subset1", "subset2"},
},
{
proxyNs: "ns1",
serviceNs: "random",
host: testhost,
wantSubsets: []string{"subset7", "subset8"},
},
{
proxyNs: "random",
serviceNs: "random",
host: testhost,
wantSubsets: []string{"subset7", "subset8"},
},
{
proxyNs: "test3",
serviceNs: "random",
host: testhost,
wantSubsets: []string{"subset5", "subset6"},
},
{
proxyNs: "dubbo-system",
serviceNs: "random",
host: testhost,
wantSubsets: []string{"subset9", "subset10"},
},
{
proxyNs: "dubbo-system",
serviceNs: "dubbo-system",
host: testhost,
wantSubsets: []string{"subset9", "subset10"},
},
{
proxyNs: "dubbo-system",
serviceNs: "dubbo-system",
host: appHost,
wantSubsets: []string{"subset13", "subset14"},
},
}
for _, tt := range cases {
t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.serviceNs), func(t *testing.T) {
destRuleConfig := ps.destinationRule(tt.proxyNs,
&Service{
Hostname: host.Name(tt.host),
Attributes: ServiceAttributes{
Namespace: tt.serviceNs,
},
})[0]
if destRuleConfig == nil {
t.Fatalf("proxy in %s namespace: dest rule is nil, expected subsets %+v", tt.proxyNs, tt.wantSubsets)
}
destRule := destRuleConfig.rule.Spec.(*networking.DestinationRule)
var gotSubsets []string
for _, ss := range destRule.Subsets {
gotSubsets = append(gotSubsets, ss.Name)
}
if !reflect.DeepEqual(gotSubsets, tt.wantSubsets) {
t.Fatalf("want %+v, got %+v", tt.wantSubsets, gotSubsets)
}
})
}
}
func TestVirtualServiceWithExportTo(t *testing.T) {
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})}
ps.Mesh = env.Mesh()
configStore := NewFakeStore()
gatewayName := "default/gateway"
rule1 := config.Config{
Meta: config.Meta{
Name: "rule1",
Namespace: "test1",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Hosts: []string{"rule1.com"},
ExportTo: []string{".", "ns1"},
},
}
rule2 := config.Config{
Meta: config.Meta{
Name: "rule2",
Namespace: "test2",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Hosts: []string{"rule2.com"},
ExportTo: []string{"test2", "ns1", "test1"},
},
}
rule2Gw := config.Config{
Meta: config.Meta{
Name: "rule2Gw",
Namespace: "test2",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Gateways: []string{gatewayName, constants.IstioMeshGateway},
Hosts: []string{"rule2gw.com"},
ExportTo: []string{"test2", "ns1", "test1"},
},
}
rule3 := config.Config{
Meta: config.Meta{
Name: "rule3",
Namespace: "test3",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Gateways: []string{constants.IstioMeshGateway},
Hosts: []string{"rule3.com"},
ExportTo: []string{"test1", "test2", "*"},
},
}
rule3Gw := config.Config{
Meta: config.Meta{
Name: "rule3Gw",
Namespace: "test3",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Gateways: []string{gatewayName},
Hosts: []string{"rule3gw.com"},
ExportTo: []string{"test1", "test2", "*"},
},
}
rootNS := config.Config{
Meta: config.Meta{
Name: "zzz",
Namespace: "zzz",
GroupVersionKind: gvk.VirtualService,
},
Spec: &networking.VirtualService{
Hosts: []string{"rootNS.com"},
},
}
for _, c := range []config.Config{rule1, rule2, rule3, rule2Gw, rule3Gw, rootNS} {
if _, err := configStore.Create(c); err != nil {
t.Fatalf("could not create %v", c.Name)
}
}
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
ps.initDefaultExportMaps()
if err := ps.initVirtualServices(env); err != nil {
t.Fatalf("init virtual services failed: %v", err)
}
cases := []struct {
proxyNs string
gateway string
wantHosts []string
}{
{
proxyNs: "test1",
wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
gateway: constants.IstioMeshGateway,
},
{
proxyNs: "test2",
wantHosts: []string{"rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
gateway: constants.IstioMeshGateway,
},
{
proxyNs: "ns1",
wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
gateway: constants.IstioMeshGateway,
},
{
proxyNs: "random",
wantHosts: []string{"rule3.com", "rootNS.com"},
gateway: constants.IstioMeshGateway,
},
{
proxyNs: "test1",
wantHosts: []string{"rule2gw.com", "rule3gw.com"},
gateway: gatewayName,
},
{
proxyNs: "test2",
wantHosts: []string{"rule2gw.com", "rule3gw.com"},
gateway: gatewayName,
},
{
proxyNs: "ns1",
wantHosts: []string{"rule2gw.com", "rule3gw.com"},
gateway: gatewayName,
},
{
proxyNs: "random",
wantHosts: []string{"rule3gw.com"},
gateway: gatewayName,
},
}
for _, tt := range cases {
t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.gateway), func(t *testing.T) {
rules := ps.VirtualServicesForGateway(tt.proxyNs, tt.gateway)
gotHosts := make([]string, 0)
for _, r := range rules {
vs := r.Spec.(*networking.VirtualService)
gotHosts = append(gotHosts, vs.Hosts...)
}
if !reflect.DeepEqual(gotHosts, tt.wantHosts) {
t.Errorf("want %+v, got %+v", tt.wantHosts, gotHosts)
}
})
}
}
func TestInitVirtualService(t *testing.T) {
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "dubbo-system"})}
ps.Mesh = env.Mesh()
configStore := NewFakeStore()
gatewayName := "ns1/gateway"
vs1 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "vs1",
Namespace: "ns1",
},
Spec: &networking.VirtualService{
Hosts: []string{"*.org"},
Gateways: []string{"gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
},
},
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: "/login"},
},
},
},
Delegate: &networking.Delegate{
Name: "vs2",
Namespace: "ns2",
},
},
},
},
}
vs2 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "vs2",
Namespace: "ns2",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{gatewayName},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test",
Port: &networking.PortSelector{
Number: 80,
},
},
},
},
},
},
},
}
for _, c := range []config.Config{vs1, vs2} {
if _, err := configStore.Create(c); err != nil {
t.Fatalf("could not create %v", c.Name)
}
}
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
ps.initDefaultExportMaps()
if err := ps.initVirtualServices(env); err != nil {
t.Fatalf("init virtual services failed: %v", err)
}
t.Run("resolve shortname", func(t *testing.T) {
rules := ps.VirtualServicesForGateway("ns1", gatewayName)
if len(rules) != 1 {
t.Fatalf("wanted 1 virtualservice for gateway %s, actually got %d", gatewayName, len(rules))
}
gotHTTPHosts := make([]string, 0)
for _, r := range rules {
vs := r.Spec.(*networking.VirtualService)
for _, route := range vs.GetHttp() {
for _, dst := range route.Route {
gotHTTPHosts = append(gotHTTPHosts, dst.Destination.Host)
}
}
}
if !reflect.DeepEqual(gotHTTPHosts, []string{"test.ns2"}) {
t.Errorf("got %+v", gotHTTPHosts)
}
})
}
func TestServiceWithExportTo(t *testing.T) {
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})}
ps.Mesh = env.Mesh()
svc1 := &Service{
Hostname: "svc1",
Attributes: ServiceAttributes{
Namespace: "test1",
ExportTo: map[visibility.Instance]bool{visibility.Private: true, visibility.Instance("ns1"): true},
},
}
svc2 := &Service{
Hostname: "svc2",
Attributes: ServiceAttributes{
Namespace: "test2",
ExportTo: map[visibility.Instance]bool{
visibility.Instance("test1"): true,
visibility.Instance("ns1"): true,
visibility.Instance("test2"): true,
},
},
}
svc3 := &Service{
Hostname: "svc3",
Attributes: ServiceAttributes{
Namespace: "test3",
ExportTo: map[visibility.Instance]bool{
visibility.Instance("test1"): true,
visibility.Public: true,
visibility.Instance("test2"): true,
},
},
}
svc4 := &Service{
Hostname: "svc4",
Attributes: ServiceAttributes{
Namespace: "test4",
},
}
env.ServiceDiscovery = &localServiceDiscovery{
services: []*Service{svc1, svc2, svc3, svc4},
}
ps.initDefaultExportMaps()
if err := ps.initServiceRegistry(env); err != nil {
t.Fatalf("init services failed: %v", err)
}
cases := []struct {
proxyNs string
wantHosts []string
}{
{
proxyNs: "test1",
wantHosts: []string{"svc1", "svc2", "svc3", "svc4"},
},
{
proxyNs: "test2",
wantHosts: []string{"svc2", "svc3", "svc4"},
},
{
proxyNs: "ns1",
wantHosts: []string{"svc1", "svc2", "svc3", "svc4"},
},
{
proxyNs: "random",
wantHosts: []string{"svc3", "svc4"},
},
}
for _, tt := range cases {
services := ps.servicesExportedToNamespace(tt.proxyNs)
gotHosts := make([]string, 0)
for _, r := range services {
gotHosts = append(gotHosts, string(r.Hostname))
}
if !reflect.DeepEqual(gotHosts, tt.wantHosts) {
t.Errorf("proxy in %s namespace: want %+v, got %+v", tt.proxyNs, tt.wantHosts, gotHosts)
}
}
}
func TestGetHostsFromMeshConfig(t *testing.T) {
ps := NewPushContext()
env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{
RootNamespace: "dubbo-system",
ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
{
Name: "otel",
Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls{
EnvoyOtelAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider{
Service: "otel.foo.svc.cluster.local",
Port: 9811,
},
},
},
},
DefaultProviders: &meshconfig.MeshConfig_DefaultProviders{
AccessLogging: []string{"otel"},
},
})}
ps.Mesh = env.Mesh()
configStore := NewFakeStore()
gatewayName := "ns1/gateway"
vs1 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "vs1",
Namespace: "ns1",
},
Spec: &networking.VirtualService{
Hosts: []string{"*.org"},
Gateways: []string{"gateway"},
Http: []*networking.HTTPRoute{
{
Match: []*networking.HTTPMatchRequest{
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
},
},
{
Uri: &networking.StringMatch{
MatchType: &networking.StringMatch_Exact{Exact: "/login"},
},
},
},
Delegate: &networking.Delegate{
Name: "vs2",
Namespace: "ns2",
},
},
},
},
}
vs2 := config.Config{
Meta: config.Meta{
GroupVersionKind: collections.IstioNetworkingV1Alpha3Virtualservices.Resource().GroupVersionKind(),
Name: "vs2",
Namespace: "ns2",
},
Spec: &networking.VirtualService{
Hosts: []string{},
Gateways: []string{gatewayName},
Http: []*networking.HTTPRoute{
{
Route: []*networking.HTTPRouteDestination{
{
Destination: &networking.Destination{
Host: "test",
Port: &networking.PortSelector{
Number: 80,
},
},
},
},
},
},
},
}
for _, c := range []config.Config{vs1, vs2} {
if _, err := configStore.Create(c); err != nil {
t.Fatalf("could not create %v", c.Name)
}
}
store := istioConfigStore{ConfigStore: configStore}
env.ConfigStore = &store
ps.initTelemetry(env)
ps.initDefaultExportMaps()
if err := ps.initVirtualServices(env); err != nil {
t.Fatalf("init virtual services failed: %v", err)
}
got := getHostsFromMeshConfig(ps)
assert.Equal(t, []string{"otel.foo.svc.cluster.local"}, got.SortedList())
}
// TestGetHostsFromMeshConfigExhaustiveness exhaustiveness check of `getHostsFromMeshConfig`
// Once some one add a new `Provider` in api, we should update `wellknownProviders` and
// implements of `getHostsFromMeshConfig`
func TestGetHostsFromMeshConfigExhaustiveness(t *testing.T) {
wellknownProviders := map[string]struct{}{
"envoy_ext_authz_http": {},
"envoy_ext_authz_grpc": {},
"zipkin": {},
"lightstep": {},
"datadog": {},
"opencensus": {},
"skywalking": {},
"envoy_http_als": {},
"envoy_tcp_als": {},
"envoy_otel_als": {},
}
unexpectedProviders := make([]string, 0)
msg := &meshconfig.MeshConfig_ExtensionProvider{}
pb := msg.ProtoReflect()
md := pb.Descriptor()
of := md.Oneofs().Get(0)
for i := 0; i < of.Fields().Len(); i++ {
o := of.Fields().Get(i)
if o.Message().Fields().ByName("service") != nil {
n := string(o.Name())
if _, ok := wellknownProviders[n]; ok {
delete(wellknownProviders, n)
} else {
unexpectedProviders = append(unexpectedProviders, n)
}
}
}
if len(wellknownProviders) != 0 || len(unexpectedProviders) != 0 {
t.Errorf("unexpected provider not implemented in getHostsFromMeshConfig")
t.Fail()
}
}
var _ ServiceDiscovery = &localServiceDiscovery{}
// MockDiscovery is an in-memory ServiceDiscover with mock services
type localServiceDiscovery struct {
services []*Service
serviceInstances []*ServiceInstance
NetworkGatewaysHandler
}
var _ ServiceDiscovery = &localServiceDiscovery{}
func (l *localServiceDiscovery) Services() []*Service {
return l.services
}
func (l *localServiceDiscovery) GetService(host.Name) *Service {
panic("implement me")
}
func (l *localServiceDiscovery) InstancesByPort(*Service, int, labels.Instance) []*ServiceInstance {
return l.serviceInstances
}
func (l *localServiceDiscovery) GetProxyServiceInstances(*Proxy) []*ServiceInstance {
panic("implement me")
}
func (l *localServiceDiscovery) GetProxyWorkloadLabels(*Proxy) labels.Instance {
panic("implement me")
}
func (l *localServiceDiscovery) GetIstioServiceAccounts(*Service, []int) []string {
return nil
}
func (l *localServiceDiscovery) NetworkGateways() []NetworkGateway {
// TODO implement fromRegistry logic from kube controller if needed
return nil
}
func (l *localServiceDiscovery) MCSServices() []MCSServiceInfo {
return nil
}