istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/push_context_test.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package model
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"regexp"
    21  	"sort"
    22  	"sync"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	"github.com/google/go-cmp/cmp/cmpopts"
    28  	. "github.com/onsi/gomega"
    29  	"go.uber.org/atomic"
    30  	"google.golang.org/protobuf/testing/protocmp"
    31  	"google.golang.org/protobuf/types/known/durationpb"
    32  	"google.golang.org/protobuf/types/known/structpb"
    33  	"google.golang.org/protobuf/types/known/wrapperspb"
    34  	"k8s.io/apimachinery/pkg/types"
    35  
    36  	extensions "istio.io/api/extensions/v1alpha1"
    37  	meshconfig "istio.io/api/mesh/v1alpha1"
    38  	networking "istio.io/api/networking/v1alpha3"
    39  	securityBeta "istio.io/api/security/v1beta1"
    40  	selectorpb "istio.io/api/type/v1beta1"
    41  	"istio.io/istio/pilot/pkg/features"
    42  	istionetworking "istio.io/istio/pilot/pkg/networking"
    43  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    44  	"istio.io/istio/pkg/config"
    45  	"istio.io/istio/pkg/config/constants"
    46  	"istio.io/istio/pkg/config/host"
    47  	"istio.io/istio/pkg/config/labels"
    48  	"istio.io/istio/pkg/config/mesh"
    49  	"istio.io/istio/pkg/config/schema/gvk"
    50  	"istio.io/istio/pkg/config/schema/kind"
    51  	"istio.io/istio/pkg/config/visibility"
    52  	"istio.io/istio/pkg/maps"
    53  	"istio.io/istio/pkg/slices"
    54  	"istio.io/istio/pkg/test"
    55  	"istio.io/istio/pkg/test/util/assert"
    56  	"istio.io/istio/pkg/util/protomarshal"
    57  	"istio.io/istio/pkg/util/sets"
    58  )
    59  
    60  func TestMergeUpdateRequest(t *testing.T) {
    61  	push0 := &PushContext{}
    62  	// trivially different push contexts just for testing
    63  	push1 := &PushContext{ProxyStatus: make(map[string]map[string]ProxyPushStatus)}
    64  
    65  	var t0 time.Time
    66  	t1 := t0.Add(time.Minute)
    67  
    68  	cases := []struct {
    69  		name   string
    70  		left   *PushRequest
    71  		right  *PushRequest
    72  		merged PushRequest
    73  	}{
    74  		{
    75  			"left nil",
    76  			nil,
    77  			&PushRequest{Full: true},
    78  			PushRequest{Full: true},
    79  		},
    80  		{
    81  			"right nil",
    82  			&PushRequest{Full: true},
    83  			nil,
    84  			PushRequest{Full: true},
    85  		},
    86  		{
    87  			"simple merge",
    88  			&PushRequest{
    89  				Full:  true,
    90  				Push:  push0,
    91  				Start: t0,
    92  				ConfigsUpdated: sets.Set[ConfigKey]{
    93  					{Kind: kind.Kind(1), Namespace: "ns1"}: {},
    94  				},
    95  				Reason: NewReasonStats(ServiceUpdate, ServiceUpdate),
    96  			},
    97  			&PushRequest{
    98  				Full:  false,
    99  				Push:  push1,
   100  				Start: t1,
   101  				ConfigsUpdated: sets.Set[ConfigKey]{
   102  					{Kind: kind.Kind(2), Namespace: "ns2"}: {},
   103  				},
   104  				Reason: NewReasonStats(EndpointUpdate),
   105  			},
   106  			PushRequest{
   107  				Full:  true,
   108  				Push:  push1,
   109  				Start: t0,
   110  				ConfigsUpdated: sets.Set[ConfigKey]{
   111  					{Kind: kind.Kind(1), Namespace: "ns1"}: {},
   112  					{Kind: kind.Kind(2), Namespace: "ns2"}: {},
   113  				},
   114  				Reason: NewReasonStats(ServiceUpdate, ServiceUpdate, EndpointUpdate),
   115  			},
   116  		},
   117  		{
   118  			"skip config type merge: one empty",
   119  			&PushRequest{Full: true, ConfigsUpdated: nil},
   120  			&PushRequest{Full: true, ConfigsUpdated: sets.Set[ConfigKey]{{
   121  				Kind: kind.Kind(2),
   122  			}: {}}},
   123  			PushRequest{Full: true, ConfigsUpdated: nil, Reason: nil},
   124  		},
   125  	}
   126  
   127  	for _, tt := range cases {
   128  		t.Run(tt.name, func(t *testing.T) {
   129  			got := tt.left.CopyMerge(tt.right)
   130  			if !reflect.DeepEqual(&tt.merged, got) {
   131  				t.Fatalf("expected %v, got %v", &tt.merged, got)
   132  			}
   133  			got = tt.left.Merge(tt.right)
   134  			if !reflect.DeepEqual(&tt.merged, got) {
   135  				t.Fatalf("expected %v, got %v", &tt.merged, got)
   136  			}
   137  		})
   138  	}
   139  }
   140  
   141  func TestConcurrentMerge(t *testing.T) {
   142  	reqA := &PushRequest{Reason: make(ReasonStats)}
   143  	reqB := &PushRequest{Reason: NewReasonStats(ServiceUpdate, ProxyUpdate)}
   144  	for i := 0; i < 50; i++ {
   145  		go func() {
   146  			reqA.CopyMerge(reqB)
   147  		}()
   148  	}
   149  	if reqA.Reason.Count() != 0 {
   150  		t.Fatalf("reqA modified: %v", reqA.Reason)
   151  	}
   152  	if reqB.Reason.Count() != 2 {
   153  		t.Fatalf("reqB modified: %v", reqB.Reason)
   154  	}
   155  }
   156  
   157  func TestEnvoyFilters(t *testing.T) {
   158  	proxyVersionRegex := regexp.MustCompile(`1\.4.*`)
   159  	envoyFilters := []*EnvoyFilterWrapper{
   160  		{
   161  			Name:             "ef1",
   162  			workloadSelector: map[string]string{"app": "v1"},
   163  			Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{
   164  				networking.EnvoyFilter_LISTENER: {
   165  					{
   166  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   167  							Proxy: &networking.EnvoyFilter_ProxyMatch{
   168  								ProxyVersion: "1\\.4.*",
   169  							},
   170  						},
   171  						ProxyVersionRegex: proxyVersionRegex,
   172  					},
   173  				},
   174  			},
   175  		},
   176  		{
   177  			Name:             "ef2",
   178  			workloadSelector: map[string]string{"app": "v1"},
   179  			Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{
   180  				networking.EnvoyFilter_CLUSTER: {
   181  					{
   182  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   183  							Proxy: &networking.EnvoyFilter_ProxyMatch{
   184  								ProxyVersion: `1\\.4.*`,
   185  							},
   186  						},
   187  						ProxyVersionRegex: proxyVersionRegex,
   188  					},
   189  				},
   190  			},
   191  		},
   192  	}
   193  
   194  	push := &PushContext{
   195  		Mesh: &meshconfig.MeshConfig{
   196  			RootNamespace: "istio-system",
   197  		},
   198  		envoyFiltersByNamespace: map[string][]*EnvoyFilterWrapper{
   199  			"istio-system": envoyFilters,
   200  			"test-ns":      envoyFilters,
   201  		},
   202  	}
   203  
   204  	if !push.HasEnvoyFilters("ef1", "test-ns") {
   205  		t.Errorf("Check presence of EnvoyFilter ef1 at test-ns got false, want true")
   206  	}
   207  	if push.HasEnvoyFilters("ef3", "test-ns") {
   208  		t.Errorf("Check presence of EnvoyFilter ef3 at test-ns got true, want false")
   209  	}
   210  
   211  	cases := []struct {
   212  		name                    string
   213  		proxy                   *Proxy
   214  		expectedListenerPatches int
   215  		expectedClusterPatches  int
   216  	}{
   217  		{
   218  			name: "proxy matches two envoyfilters",
   219  			proxy: &Proxy{
   220  				Labels:          map[string]string{"app": "v1"},
   221  				Metadata:        &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
   222  				ConfigNamespace: "test-ns",
   223  			},
   224  			expectedListenerPatches: 2,
   225  			expectedClusterPatches:  2,
   226  		},
   227  		{
   228  			name: "proxy in root namespace matches an envoyfilter",
   229  			proxy: &Proxy{
   230  				Labels:          map[string]string{"app": "v1"},
   231  				Metadata:        &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
   232  				ConfigNamespace: "istio-system",
   233  			},
   234  			expectedListenerPatches: 1,
   235  			expectedClusterPatches:  1,
   236  		},
   237  
   238  		{
   239  			name: "proxy matches no envoyfilter",
   240  			proxy: &Proxy{
   241  				Labels:          map[string]string{"app": "v2"},
   242  				Metadata:        &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v2"}},
   243  				ConfigNamespace: "test-ns",
   244  			},
   245  			expectedListenerPatches: 0,
   246  			expectedClusterPatches:  0,
   247  		},
   248  		{
   249  			name: "proxy matches envoyfilter in root ns",
   250  			proxy: &Proxy{
   251  				Labels:          map[string]string{"app": "v1"},
   252  				Metadata:        &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}},
   253  				ConfigNamespace: "test-n2",
   254  			},
   255  			expectedListenerPatches: 1,
   256  			expectedClusterPatches:  1,
   257  		},
   258  		{
   259  			name: "proxy version matches no envoyfilters",
   260  			proxy: &Proxy{
   261  				Labels:          map[string]string{"app": "v1"},
   262  				Metadata:        &NodeMetadata{IstioVersion: "1.3.0", Labels: map[string]string{"app": "v1"}},
   263  				ConfigNamespace: "test-ns",
   264  			},
   265  			expectedListenerPatches: 0,
   266  			expectedClusterPatches:  0,
   267  		},
   268  	}
   269  
   270  	for _, tt := range cases {
   271  		t.Run(tt.name, func(t *testing.T) {
   272  			filter := push.EnvoyFilters(tt.proxy)
   273  			if filter == nil {
   274  				if tt.expectedClusterPatches != 0 || tt.expectedListenerPatches != 0 {
   275  					t.Errorf("Got no envoy filter")
   276  				}
   277  				return
   278  			}
   279  			if len(filter.Patches[networking.EnvoyFilter_CLUSTER]) != tt.expectedClusterPatches {
   280  				t.Errorf("Expect %d envoy filter cluster patches, but got %d", tt.expectedClusterPatches, len(filter.Patches[networking.EnvoyFilter_CLUSTER]))
   281  			}
   282  			if len(filter.Patches[networking.EnvoyFilter_LISTENER]) != tt.expectedListenerPatches {
   283  				t.Errorf("Expect %d envoy filter listener patches, but got %d", tt.expectedListenerPatches, len(filter.Patches[networking.EnvoyFilter_LISTENER]))
   284  			}
   285  		})
   286  	}
   287  }
   288  
   289  func TestEnvoyFilterOrder(t *testing.T) {
   290  	env := &Environment{}
   291  	store := NewFakeStore()
   292  
   293  	ctime := time.Now()
   294  
   295  	envoyFilters := []config.Config{
   296  		{
   297  			Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter},
   298  			Spec: &networking.EnvoyFilter{
   299  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   300  					{
   301  						Patch: &networking.EnvoyFilter_Patch{},
   302  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   303  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   304  						},
   305  					},
   306  				},
   307  			},
   308  		},
   309  		{
   310  			Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   311  			Spec: &networking.EnvoyFilter{
   312  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   313  					{
   314  						Patch: &networking.EnvoyFilter_Patch{},
   315  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   316  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   317  						},
   318  					},
   319  				},
   320  			},
   321  		},
   322  		{
   323  			Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   324  			Spec: &networking.EnvoyFilter{
   325  				Priority: 10,
   326  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   327  					{
   328  						Patch: &networking.EnvoyFilter_Patch{},
   329  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   330  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   331  						},
   332  					},
   333  				},
   334  			},
   335  		},
   336  		{
   337  			Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   338  			Spec: &networking.EnvoyFilter{
   339  				Priority: 10,
   340  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   341  					{
   342  						Patch: &networking.EnvoyFilter_Patch{},
   343  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   344  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   345  						},
   346  					},
   347  				},
   348  			},
   349  		},
   350  		{
   351  			Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   352  			Spec: &networking.EnvoyFilter{
   353  				Priority: 10,
   354  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   355  					{
   356  						Patch: &networking.EnvoyFilter_Patch{},
   357  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   358  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   359  						},
   360  					},
   361  				},
   362  			},
   363  		},
   364  		{
   365  			Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   366  			Spec: &networking.EnvoyFilter{
   367  				Priority: 10,
   368  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   369  					{
   370  						Patch: &networking.EnvoyFilter_Patch{},
   371  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   372  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   373  						},
   374  					},
   375  				},
   376  			},
   377  		},
   378  		{
   379  			Meta: config.Meta{Name: "a-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: time.Now()},
   380  			Spec: &networking.EnvoyFilter{
   381  				Priority: 20,
   382  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   383  					{
   384  						Patch: &networking.EnvoyFilter_Patch{},
   385  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   386  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   387  						},
   388  					},
   389  				},
   390  			},
   391  		},
   392  		{
   393  			Meta: config.Meta{Name: "b-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   394  			Spec: &networking.EnvoyFilter{
   395  				Priority: 20,
   396  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   397  					{
   398  						Patch: &networking.EnvoyFilter_Patch{},
   399  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   400  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   401  						},
   402  					},
   403  				},
   404  			},
   405  		},
   406  		{
   407  			Meta: config.Meta{Name: "high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   408  			Spec: &networking.EnvoyFilter{
   409  				Priority: -1,
   410  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   411  					{
   412  						Patch: &networking.EnvoyFilter_Patch{},
   413  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   414  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   415  						},
   416  					},
   417  				},
   418  			},
   419  		},
   420  		{
   421  			Meta: config.Meta{Name: "super-high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   422  			Spec: &networking.EnvoyFilter{
   423  				Priority: -10,
   424  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   425  					{
   426  						Patch: &networking.EnvoyFilter_Patch{},
   427  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   428  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   429  						},
   430  					},
   431  				},
   432  			},
   433  		},
   434  	}
   435  
   436  	expectedns := []string{
   437  		"testns/super-high-priority", "testns/high-priority", "testns/default-priority", "testns/a-medium-priority",
   438  		"testns/b-medium-priority", "testns/b-low-priority", "testns/a-low-priority",
   439  	}
   440  
   441  	expectedns1 := []string{"testns-1/default-priority", "testns-1/a-medium-priority", "testns-1/b-medium-priority"}
   442  
   443  	for _, cfg := range envoyFilters {
   444  		_, _ = store.Create(cfg)
   445  	}
   446  	env.ConfigStore = store
   447  	m := mesh.DefaultMeshConfig()
   448  	env.Watcher = mesh.NewFixedWatcher(m)
   449  	env.Init()
   450  
   451  	// Init a new push context
   452  	pc := NewPushContext()
   453  	pc.initEnvoyFilters(env, nil, nil)
   454  	gotns := make([]string, 0)
   455  	for _, filter := range pc.envoyFiltersByNamespace["testns"] {
   456  		gotns = append(gotns, filter.Keys()...)
   457  	}
   458  	gotns1 := make([]string, 0)
   459  	for _, filter := range pc.envoyFiltersByNamespace["testns-1"] {
   460  		gotns1 = append(gotns1, filter.Keys()...)
   461  	}
   462  	if !reflect.DeepEqual(expectedns, gotns) {
   463  		t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns, gotns)
   464  	}
   465  	if !reflect.DeepEqual(expectedns1, gotns1) {
   466  		t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns1, gotns1)
   467  	}
   468  }
   469  
   470  func buildPatchStruct(config string) *structpb.Struct {
   471  	val := &structpb.Struct{}
   472  	_ = protomarshal.UnmarshalString(config, val)
   473  	return val
   474  }
   475  
   476  func TestEnvoyFilterOrderAcrossNamespaces(t *testing.T) {
   477  	env := &Environment{}
   478  	store := NewFakeStore()
   479  
   480  	proxy := &Proxy{
   481  		Metadata:        &NodeMetadata{IstioVersion: "foobar"},
   482  		Labels:          map[string]string{"app": "v1"},
   483  		ConfigNamespace: "test-ns",
   484  	}
   485  
   486  	envoyFilters := []config.Config{
   487  		{
   488  			Meta: config.Meta{Name: "filter-1", Namespace: "test-ns", GroupVersionKind: gvk.EnvoyFilter},
   489  			Spec: &networking.EnvoyFilter{
   490  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   491  					{
   492  						ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
   493  						Patch: &networking.EnvoyFilter_Patch{
   494  							Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
   495  							Value:     buildPatchStruct(`{"name": "filter-1"}`),
   496  						},
   497  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   498  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   499  						},
   500  					},
   501  				},
   502  				Priority: -5,
   503  			},
   504  		},
   505  		{
   506  			Meta: config.Meta{Name: "filter-2", Namespace: "istio-system", GroupVersionKind: gvk.EnvoyFilter},
   507  			Spec: &networking.EnvoyFilter{
   508  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   509  					{
   510  						ApplyTo: networking.EnvoyFilter_HTTP_FILTER,
   511  						Patch: &networking.EnvoyFilter_Patch{
   512  							Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE,
   513  							Value:     buildPatchStruct(`{"name": "filter-2"}`),
   514  						},
   515  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   516  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   517  						},
   518  					},
   519  				},
   520  				Priority: -1,
   521  			},
   522  		},
   523  	}
   524  
   525  	expectedFilterOrder := []string{"test-ns/filter-1", "istio-system/filter-2"}
   526  	for _, cfg := range envoyFilters {
   527  		_, _ = store.Create(cfg)
   528  	}
   529  	env.ConfigStore = store
   530  	m := mesh.DefaultMeshConfig()
   531  	m.RootNamespace = "istio-system"
   532  	env.Watcher = mesh.NewFixedWatcher(m)
   533  	env.Init()
   534  
   535  	// Init a new push context
   536  	pc := NewPushContext()
   537  	pc.Mesh = m
   538  	pc.initEnvoyFilters(env, nil, nil)
   539  	got := make([]string, 0)
   540  	efs := pc.EnvoyFilters(proxy)
   541  	for _, filter := range efs.Patches[networking.EnvoyFilter_HTTP_FILTER] {
   542  		got = append(got, filter.Namespace+"/"+filter.Name)
   543  	}
   544  	if !slices.Equal(expectedFilterOrder, got) {
   545  		t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedFilterOrder, got)
   546  	}
   547  }
   548  
   549  func TestEnvoyFilterUpdate(t *testing.T) {
   550  	ctime := time.Now()
   551  
   552  	initialEnvoyFilters := []config.Config{
   553  		{
   554  			Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter},
   555  			Spec: &networking.EnvoyFilter{
   556  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   557  					{
   558  						Patch: &networking.EnvoyFilter_Patch{},
   559  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   560  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   561  						},
   562  					},
   563  				},
   564  			},
   565  		},
   566  		{
   567  			Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   568  			Spec: &networking.EnvoyFilter{
   569  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   570  					{
   571  						Patch: &networking.EnvoyFilter_Patch{},
   572  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   573  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   574  						},
   575  					},
   576  				},
   577  			},
   578  		},
   579  		{
   580  			Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   581  			Spec: &networking.EnvoyFilter{
   582  				Priority: 10,
   583  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   584  					{
   585  						Patch: &networking.EnvoyFilter_Patch{},
   586  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   587  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   588  						},
   589  					},
   590  				},
   591  			},
   592  		},
   593  		{
   594  			Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   595  			Spec: &networking.EnvoyFilter{
   596  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   597  					{
   598  						Patch: &networking.EnvoyFilter_Patch{},
   599  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   600  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   601  						},
   602  					},
   603  				},
   604  			},
   605  		},
   606  		{
   607  			Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   608  			Spec: &networking.EnvoyFilter{
   609  				Priority: 10,
   610  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   611  					{
   612  						Patch: &networking.EnvoyFilter_Patch{},
   613  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   614  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   615  						},
   616  					},
   617  				},
   618  			},
   619  		},
   620  		{
   621  			Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   622  			Spec: &networking.EnvoyFilter{
   623  				Priority: 10,
   624  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   625  					{
   626  						Patch: &networking.EnvoyFilter_Patch{},
   627  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   628  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   629  						},
   630  					},
   631  				},
   632  			},
   633  		},
   634  		{
   635  			Meta: config.Meta{Name: "a-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: time.Now()},
   636  			Spec: &networking.EnvoyFilter{
   637  				Priority: 20,
   638  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   639  					{
   640  						Patch: &networking.EnvoyFilter_Patch{},
   641  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   642  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   643  						},
   644  					},
   645  				},
   646  			},
   647  		},
   648  		{
   649  			Meta: config.Meta{Name: "b-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime},
   650  			Spec: &networking.EnvoyFilter{
   651  				Priority: 20,
   652  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   653  					{
   654  						Patch: &networking.EnvoyFilter_Patch{},
   655  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   656  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   657  						},
   658  					},
   659  				},
   660  			},
   661  		},
   662  		{
   663  			Meta: config.Meta{Name: "high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   664  			Spec: &networking.EnvoyFilter{
   665  				Priority: -1,
   666  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   667  					{
   668  						Patch: &networking.EnvoyFilter_Patch{},
   669  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   670  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   671  						},
   672  					},
   673  				},
   674  			},
   675  		},
   676  		{
   677  			Meta: config.Meta{Name: "super-high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   678  			Spec: &networking.EnvoyFilter{
   679  				Priority: -10,
   680  				ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   681  					{
   682  						Patch: &networking.EnvoyFilter_Patch{},
   683  						Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   684  							Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`},
   685  						},
   686  					},
   687  				},
   688  			},
   689  		},
   690  	}
   691  
   692  	cases := []struct {
   693  		name    string
   694  		creates []config.Config
   695  		updates []config.Config
   696  		deletes []ConfigKey
   697  	}{
   698  		{
   699  			name: "create one",
   700  			creates: []config.Config{
   701  				{
   702  					Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   703  					Spec: &networking.EnvoyFilter{
   704  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   705  							{
   706  								Patch: &networking.EnvoyFilter_Patch{},
   707  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   708  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   709  								},
   710  							},
   711  						},
   712  					},
   713  				},
   714  			},
   715  			updates: []config.Config{},
   716  			deletes: []ConfigKey{},
   717  		},
   718  		{
   719  			name:    "update one",
   720  			creates: []config.Config{},
   721  			updates: []config.Config{
   722  				{
   723  					Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   724  					Spec: &networking.EnvoyFilter{
   725  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   726  							{
   727  								Patch: &networking.EnvoyFilter_Patch{},
   728  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   729  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   730  								},
   731  							},
   732  						},
   733  					},
   734  				},
   735  			},
   736  			deletes: []ConfigKey{},
   737  		},
   738  		{
   739  			name:    "delete one",
   740  			creates: []config.Config{},
   741  			updates: []config.Config{},
   742  			deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns"}},
   743  		},
   744  		{
   745  			name: "create and delete one same namespace",
   746  			creates: []config.Config{
   747  				{
   748  					Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   749  					Spec: &networking.EnvoyFilter{
   750  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   751  							{
   752  								Patch: &networking.EnvoyFilter_Patch{},
   753  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   754  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   755  								},
   756  							},
   757  						},
   758  					},
   759  				},
   760  			},
   761  			updates: []config.Config{},
   762  			deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns"}},
   763  		},
   764  		{
   765  			name: "create and delete one different namespace",
   766  			creates: []config.Config{
   767  				{
   768  					Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   769  					Spec: &networking.EnvoyFilter{
   770  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   771  							{
   772  								Patch: &networking.EnvoyFilter_Patch{},
   773  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   774  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   775  								},
   776  							},
   777  						},
   778  					},
   779  				},
   780  			},
   781  			updates: []config.Config{},
   782  			deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns-1"}},
   783  		},
   784  		{
   785  			name: "create, update delete",
   786  			creates: []config.Config{
   787  				{
   788  					Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   789  					Spec: &networking.EnvoyFilter{
   790  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   791  							{
   792  								Patch: &networking.EnvoyFilter_Patch{},
   793  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   794  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   795  								},
   796  							},
   797  						},
   798  					},
   799  				},
   800  				{
   801  					Meta: config.Meta{Name: "default-priority-3", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   802  					Spec: &networking.EnvoyFilter{
   803  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   804  							{
   805  								Patch: &networking.EnvoyFilter_Patch{},
   806  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   807  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   808  								},
   809  							},
   810  						},
   811  					},
   812  				},
   813  			},
   814  			updates: []config.Config{
   815  				{
   816  					Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter},
   817  					Spec: &networking.EnvoyFilter{
   818  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   819  							{
   820  								Patch: &networking.EnvoyFilter_Patch{},
   821  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   822  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   823  								},
   824  							},
   825  						},
   826  					},
   827  				},
   828  				{
   829  					Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter},
   830  					Spec: &networking.EnvoyFilter{
   831  						ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
   832  							{
   833  								Patch: &networking.EnvoyFilter_Patch{},
   834  								Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
   835  									Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`},
   836  								},
   837  							},
   838  						},
   839  					},
   840  				},
   841  			},
   842  			deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "b-medium-priority", Namespace: "testns-1"}},
   843  		},
   844  		{
   845  			name:    "delete entire namespace",
   846  			creates: []config.Config{},
   847  			updates: []config.Config{},
   848  			deletes: []ConfigKey{
   849  				{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns-1"},
   850  				{Kind: kind.EnvoyFilter, Name: "b-medium-priority", Namespace: "testns-1"},
   851  				{Kind: kind.EnvoyFilter, Name: "a-medium-priority", Namespace: "testns-1"},
   852  			},
   853  		},
   854  	}
   855  
   856  	OptimizedConfigRebuildModes := []bool{true, false}
   857  	for _, tt := range cases {
   858  		for _, mode := range OptimizedConfigRebuildModes {
   859  			t.Run(tt.name, func(t *testing.T) {
   860  				test.SetForTest(t, &features.OptimizedConfigRebuild, mode)
   861  				env := &Environment{}
   862  				store := NewFakeStore()
   863  				for _, cfg := range initialEnvoyFilters {
   864  					_, _ = store.Create(cfg)
   865  				}
   866  				env.ConfigStore = store
   867  				m := mesh.DefaultMeshConfig()
   868  				env.Watcher = mesh.NewFixedWatcher(m)
   869  				env.Init()
   870  
   871  				// Init a new push context
   872  				pc1 := NewPushContext()
   873  				pc1.initEnvoyFilters(env, nil, nil)
   874  
   875  				// Update store with incoming changes
   876  				creates := map[ConfigKey]config.Config{}
   877  				for _, cfg := range tt.creates {
   878  					if _, err := store.Create(cfg); err != nil {
   879  						t.Errorf("Error creating config %s/%s", cfg.Namespace, cfg.Name)
   880  					}
   881  					creates[ConfigKey{Name: cfg.Name, Namespace: cfg.Namespace, Kind: kind.EnvoyFilter}] = cfg
   882  				}
   883  				updates := map[ConfigKey]config.Config{}
   884  				for _, cfg := range tt.updates {
   885  					if _, err := store.Update(cfg); err != nil {
   886  						t.Errorf("Error updating config %s/%s", cfg.Namespace, cfg.Name)
   887  					}
   888  					updates[ConfigKey{Name: cfg.Name, Namespace: cfg.Namespace, Kind: kind.EnvoyFilter}] = cfg
   889  				}
   890  				deletes := sets.Set[ConfigKey]{}
   891  				for _, key := range tt.deletes {
   892  					store.Delete(gvk.EnvoyFilter, key.Name, key.Namespace, nil)
   893  					deletes.Insert(key)
   894  				}
   895  
   896  				createSet := sets.New(maps.Keys(creates)...)
   897  				updateSet := sets.New(maps.Keys(updates)...)
   898  				changes := deletes.Union(createSet).Union(updateSet)
   899  
   900  				pc2 := NewPushContext()
   901  				pc2.initEnvoyFilters(env, changes, pc1.envoyFiltersByNamespace)
   902  
   903  				total2 := 0
   904  				for ns, envoyFilters := range pc2.envoyFiltersByNamespace {
   905  					total2 += len(envoyFilters)
   906  					for _, ef := range envoyFilters {
   907  						key := ConfigKey{Kind: kind.EnvoyFilter, Namespace: ns, Name: ef.Name}
   908  						previousVersion := slices.FindFunc(pc1.envoyFiltersByNamespace[ns], func(e *EnvoyFilterWrapper) bool {
   909  							return e.Name == ef.Name
   910  						})
   911  						switch {
   912  						// Newly created Envoy filter.
   913  						case createSet.Contains(key):
   914  							cfg := creates[key]
   915  							// If the filter is newly created, it should not have a previous version.
   916  							if previousVersion != nil {
   917  								t.Errorf("Created Envoy filter %s/%s already existed", ns, ef.Name)
   918  							}
   919  							// Validate that the generated filter is the same as the one created.
   920  							if !reflect.DeepEqual(ef, convertToEnvoyFilterWrapper(&cfg)) {
   921  								t.Errorf("Unexpected envoy filter generated %s/%s", ns, ef.Name)
   922  							}
   923  						// Updated Envoy filter.
   924  						case updateSet.Contains(key):
   925  							cfg := updates[key]
   926  							// If the filter is updated, it should have a previous version.
   927  							if previousVersion == nil {
   928  								t.Errorf("Updated Envoy filter %s/%s did not exist", ns, ef.Name)
   929  							} else if reflect.DeepEqual(*previousVersion, ef) {
   930  								// Validate that the generated filter is different from the previous version.
   931  								t.Errorf("Envoy filter %s/%s was not updated", ns, ef.Name)
   932  							}
   933  							// Validate that the generated filter is the same as the one updated.
   934  							if !reflect.DeepEqual(ef, convertToEnvoyFilterWrapper(&cfg)) {
   935  								t.Errorf("Unexpected envoy filter generated %s/%s", ns, ef.Name)
   936  							}
   937  						// Deleted Envoy filter.
   938  						case deletes.Contains(key):
   939  							t.Errorf("Found deleted EnvoyFilter %s/%s", ns, ef.Name)
   940  						// Unchanged Envoy filter.
   941  						default:
   942  							if previousVersion == nil {
   943  								t.Errorf("Unchanged EnvoyFilter was not previously found %s/%s", ns, ef.Name)
   944  							} else {
   945  								if mode && *previousVersion != ef {
   946  									// Validate that Unchanged filter is not regenerated when config optimization is enabled.
   947  									t.Errorf("Unchanged EnvoyFilter is different from original %s/%s", ns, ef.Name)
   948  								} else if !mode && *previousVersion == ef {
   949  									// Validate that Unchanged filter is regenerated when config optimization is disabled.
   950  									t.Errorf("Unchanged EnvoyFilter is not regenerated from original %s/%s", ns, ef.Name)
   951  								}
   952  								if !reflect.DeepEqual(*previousVersion, ef) {
   953  									t.Errorf("Envoy filter %s/%s has unexpected change", ns, ef.Name)
   954  								}
   955  							}
   956  						}
   957  					}
   958  				}
   959  
   960  				total1 := 0
   961  				// Validate that empty namespace is deleted when all filters in that namespace are deleted.
   962  				for ns, envoyFilters := range pc1.envoyFiltersByNamespace {
   963  					total1 += len(envoyFilters)
   964  					deleted := 0
   965  					for _, ef := range envoyFilters {
   966  						key := ConfigKey{Kind: kind.EnvoyFilter, Namespace: ns, Name: ef.Name}
   967  						if deletes.Contains(key) {
   968  							deleted++
   969  						}
   970  					}
   971  
   972  					if deleted == len(envoyFilters) {
   973  						if _, ok := pc2.envoyFiltersByNamespace[ns]; ok {
   974  							t.Errorf("Empty Namespace %s was not deleted", ns)
   975  						}
   976  					}
   977  				}
   978  
   979  				if total2 != total1+len(tt.creates)-len(tt.deletes) {
   980  					t.Errorf("Expected %d envoy filters, found %d", total1+len(tt.creates)-len(tt.deletes), total2)
   981  				}
   982  			})
   983  		}
   984  	}
   985  }
   986  
   987  func TestWasmPlugins(t *testing.T) {
   988  	env := &Environment{}
   989  	store := NewFakeStore()
   990  
   991  	wasmPlugins := map[string]config.Config{
   992  		"invalid-type": {
   993  			Meta: config.Meta{Name: "invalid-type", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
   994  			Spec: &networking.DestinationRule{},
   995  		},
   996  		"invalid-url": {
   997  			Meta: config.Meta{Name: "invalid-url", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
   998  			Spec: &extensions.WasmPlugin{
   999  				Phase:    extensions.PluginPhase_AUTHN,
  1000  				Priority: &wrapperspb.Int32Value{Value: 5},
  1001  				Url:      "notavalid%%Url;",
  1002  			},
  1003  		},
  1004  		"authn-low-prio-all": {
  1005  			Meta: config.Meta{Name: "authn-low-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin},
  1006  			Spec: &extensions.WasmPlugin{
  1007  				Phase:    extensions.PluginPhase_AUTHN,
  1008  				Priority: &wrapperspb.Int32Value{Value: 10},
  1009  				Url:      "file:///etc/istio/filters/authn.wasm",
  1010  				PluginConfig: &structpb.Struct{
  1011  					Fields: map[string]*structpb.Value{
  1012  						"test": {
  1013  							Kind: &structpb.Value_StringValue{StringValue: "test"},
  1014  						},
  1015  					},
  1016  				},
  1017  				Sha256: "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
  1018  			},
  1019  		},
  1020  		"authn-low-prio-all-network": {
  1021  			Meta: config.Meta{Name: "authn-low-prio-all-network", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin},
  1022  			Spec: &extensions.WasmPlugin{
  1023  				Type:     extensions.PluginType_NETWORK,
  1024  				Phase:    extensions.PluginPhase_AUTHN,
  1025  				Priority: &wrapperspb.Int32Value{Value: 10},
  1026  				Url:      "file:///etc/istio/filters/authn.wasm",
  1027  				PluginConfig: &structpb.Struct{
  1028  					Fields: map[string]*structpb.Value{
  1029  						"test": {
  1030  							Kind: &structpb.Value_StringValue{StringValue: "test"},
  1031  						},
  1032  					},
  1033  				},
  1034  				Sha256: "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2",
  1035  			},
  1036  		},
  1037  		"global-authn-low-prio-ingress": {
  1038  			Meta: config.Meta{Name: "global-authn-low-prio-ingress", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
  1039  			Spec: &extensions.WasmPlugin{
  1040  				Phase:    extensions.PluginPhase_AUTHN,
  1041  				Priority: &wrapperspb.Int32Value{Value: 5},
  1042  				Selector: &selectorpb.WorkloadSelector{
  1043  					MatchLabels: map[string]string{
  1044  						"istio": "ingressgateway",
  1045  					},
  1046  				},
  1047  			},
  1048  		},
  1049  		"authn-med-prio-all": {
  1050  			Meta: config.Meta{Name: "authn-med-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin},
  1051  			Spec: &extensions.WasmPlugin{
  1052  				Phase:    extensions.PluginPhase_AUTHN,
  1053  				Priority: &wrapperspb.Int32Value{Value: 50},
  1054  			},
  1055  		},
  1056  		"global-authn-high-prio-app": {
  1057  			Meta: config.Meta{Name: "global-authn-high-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
  1058  			Spec: &extensions.WasmPlugin{
  1059  				Phase:    extensions.PluginPhase_AUTHN,
  1060  				Priority: &wrapperspb.Int32Value{Value: 1000},
  1061  				Selector: &selectorpb.WorkloadSelector{
  1062  					MatchLabels: map[string]string{
  1063  						"app": "productpage",
  1064  					},
  1065  				},
  1066  				Match: []*extensions.WasmPlugin_TrafficSelector{
  1067  					{
  1068  						Mode:  selectorpb.WorkloadMode_SERVER,
  1069  						Ports: []*selectorpb.PortSelector{{Number: 1234}},
  1070  					},
  1071  				},
  1072  			},
  1073  		},
  1074  		"global-authz-med-prio-app": {
  1075  			Meta: config.Meta{Name: "global-authz-med-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin},
  1076  			Spec: &extensions.WasmPlugin{
  1077  				Phase:    extensions.PluginPhase_AUTHZ,
  1078  				Priority: &wrapperspb.Int32Value{Value: 50},
  1079  				Selector: &selectorpb.WorkloadSelector{
  1080  					MatchLabels: map[string]string{
  1081  						"app": "productpage",
  1082  					},
  1083  				},
  1084  				Match: []*extensions.WasmPlugin_TrafficSelector{
  1085  					{
  1086  						Mode:  selectorpb.WorkloadMode_SERVER,
  1087  						Ports: []*selectorpb.PortSelector{{Number: 1235}},
  1088  					},
  1089  				},
  1090  			},
  1091  		},
  1092  		"authz-high-prio-ingress": {
  1093  			Meta: config.Meta{Name: "authz-high-prio-ingress", Namespace: "testns-2", GroupVersionKind: gvk.WasmPlugin},
  1094  			Spec: &extensions.WasmPlugin{
  1095  				Phase:    extensions.PluginPhase_AUTHZ,
  1096  				Priority: &wrapperspb.Int32Value{Value: 1000},
  1097  			},
  1098  		},
  1099  	}
  1100  
  1101  	testCases := []struct {
  1102  		name               string
  1103  		node               *Proxy
  1104  		listenerInfo       WasmPluginListenerInfo
  1105  		pluginType         WasmPluginType
  1106  		expectedExtensions map[extensions.PluginPhase][]*WasmPluginWrapper
  1107  	}{
  1108  		{
  1109  			name:               "nil proxy",
  1110  			node:               nil,
  1111  			listenerInfo:       anyListener,
  1112  			pluginType:         WasmPluginTypeHTTP,
  1113  			expectedExtensions: nil,
  1114  		},
  1115  		{
  1116  			name: "nomatch",
  1117  			node: &Proxy{
  1118  				ConfigNamespace: "other",
  1119  				Metadata:        &NodeMetadata{},
  1120  			},
  1121  			listenerInfo:       anyListener,
  1122  			pluginType:         WasmPluginTypeHTTP,
  1123  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{},
  1124  		},
  1125  		{
  1126  			name: "ingress",
  1127  			node: &Proxy{
  1128  				ConfigNamespace: "other",
  1129  				Labels: map[string]string{
  1130  					"istio": "ingressgateway",
  1131  				},
  1132  				Metadata: &NodeMetadata{
  1133  					Labels: map[string]string{
  1134  						"istio": "ingressgateway",
  1135  					},
  1136  				},
  1137  			},
  1138  			listenerInfo: anyListener,
  1139  			pluginType:   WasmPluginTypeHTTP,
  1140  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1141  				extensions.PluginPhase_AUTHN: {
  1142  					convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]),
  1143  				},
  1144  			},
  1145  		},
  1146  		{
  1147  			name: "ingress-testns-1",
  1148  			node: &Proxy{
  1149  				ConfigNamespace: "testns-1",
  1150  				Labels: map[string]string{
  1151  					"istio": "ingressgateway",
  1152  				},
  1153  				Metadata: &NodeMetadata{
  1154  					Labels: map[string]string{
  1155  						"istio": "ingressgateway",
  1156  					},
  1157  				},
  1158  			},
  1159  			listenerInfo: anyListener,
  1160  			pluginType:   WasmPluginTypeHTTP,
  1161  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1162  				extensions.PluginPhase_AUTHN: {
  1163  					convertToWasmPluginWrapper(wasmPlugins["authn-med-prio-all"]),
  1164  					convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all"]),
  1165  					convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]),
  1166  				},
  1167  			},
  1168  		},
  1169  		{
  1170  			name: "ingress-testns-1-network",
  1171  			node: &Proxy{
  1172  				ConfigNamespace: "testns-1",
  1173  				Labels: map[string]string{
  1174  					"istio": "ingressgateway",
  1175  				},
  1176  				Metadata: &NodeMetadata{
  1177  					Labels: map[string]string{
  1178  						"istio": "ingressgateway",
  1179  					},
  1180  				},
  1181  			},
  1182  			listenerInfo: anyListener,
  1183  			pluginType:   WasmPluginTypeNetwork,
  1184  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1185  				extensions.PluginPhase_AUTHN: {
  1186  					convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all-network"]),
  1187  				},
  1188  			},
  1189  		},
  1190  		{
  1191  			name: "ingress-testns-1-any",
  1192  			node: &Proxy{
  1193  				ConfigNamespace: "testns-1",
  1194  				Labels: map[string]string{
  1195  					"istio": "ingressgateway",
  1196  				},
  1197  				Metadata: &NodeMetadata{
  1198  					Labels: map[string]string{
  1199  						"istio": "ingressgateway",
  1200  					},
  1201  				},
  1202  			},
  1203  			listenerInfo: anyListener,
  1204  			pluginType:   WasmPluginTypeAny,
  1205  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1206  				extensions.PluginPhase_AUTHN: {
  1207  					convertToWasmPluginWrapper(wasmPlugins["authn-med-prio-all"]),
  1208  					convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all-network"]),
  1209  					convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all"]),
  1210  					convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]),
  1211  				},
  1212  			},
  1213  		},
  1214  		{
  1215  			name: "testns-2",
  1216  			node: &Proxy{
  1217  				ConfigNamespace: "testns-2",
  1218  				Labels: map[string]string{
  1219  					"app": "productpage",
  1220  				},
  1221  				Metadata: &NodeMetadata{
  1222  					Labels: map[string]string{
  1223  						"app": "productpage",
  1224  					},
  1225  				},
  1226  			},
  1227  			listenerInfo: anyListener,
  1228  			pluginType:   WasmPluginTypeHTTP,
  1229  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1230  				extensions.PluginPhase_AUTHN: {
  1231  					convertToWasmPluginWrapper(wasmPlugins["global-authn-high-prio-app"]),
  1232  				},
  1233  				extensions.PluginPhase_AUTHZ: {
  1234  					convertToWasmPluginWrapper(wasmPlugins["authz-high-prio-ingress"]),
  1235  					convertToWasmPluginWrapper(wasmPlugins["global-authz-med-prio-app"]),
  1236  				},
  1237  			},
  1238  		},
  1239  		{
  1240  			// Detailed tests regarding TrafficSelector are in extension_test.go
  1241  			// Just test the integrity here.
  1242  			// This testcase is identical with "testns-2", but `listenerInfo`` is specified.
  1243  			// 1. `global-authn-high-prio-app` matched, because it has a port matching clause with "1234"
  1244  			// 2. `authz-high-prio-ingress` matched, because it does not have any `match` clause
  1245  			// 3. `global-authz-med-prio-app` not matched, because it has a port matching clause with "1235"
  1246  			name: "testns-2-with-port-match",
  1247  			node: &Proxy{
  1248  				ConfigNamespace: "testns-2",
  1249  				Labels: map[string]string{
  1250  					"app": "productpage",
  1251  				},
  1252  				Metadata: &NodeMetadata{
  1253  					Labels: map[string]string{
  1254  						"app": "productpage",
  1255  					},
  1256  				},
  1257  			},
  1258  			listenerInfo: WasmPluginListenerInfo{
  1259  				Port:  1234,
  1260  				Class: istionetworking.ListenerClassSidecarInbound,
  1261  			},
  1262  			pluginType: WasmPluginTypeHTTP,
  1263  			expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{
  1264  				extensions.PluginPhase_AUTHN: {
  1265  					convertToWasmPluginWrapper(wasmPlugins["global-authn-high-prio-app"]),
  1266  				},
  1267  				extensions.PluginPhase_AUTHZ: {
  1268  					convertToWasmPluginWrapper(wasmPlugins["authz-high-prio-ingress"]),
  1269  				},
  1270  			},
  1271  		},
  1272  	}
  1273  
  1274  	for _, config := range wasmPlugins {
  1275  		store.Create(config)
  1276  	}
  1277  	env.ConfigStore = store
  1278  	m := mesh.DefaultMeshConfig()
  1279  	env.Watcher = mesh.NewFixedWatcher(m)
  1280  	env.Init()
  1281  
  1282  	// Init a new push context
  1283  	pc := NewPushContext()
  1284  	pc.Mesh = m
  1285  	pc.initWasmPlugins(env)
  1286  
  1287  	for _, tc := range testCases {
  1288  		t.Run(tc.name, func(t *testing.T) {
  1289  			result := pc.WasmPluginsByListenerInfo(tc.node, tc.listenerInfo, tc.pluginType)
  1290  			if !reflect.DeepEqual(tc.expectedExtensions, result) {
  1291  				t.Errorf("WasmPlugins did not match expectations\n\ngot: %v\n\nexpected: %v", result, tc.expectedExtensions)
  1292  			}
  1293  		})
  1294  	}
  1295  }
  1296  
  1297  func TestServiceIndex(t *testing.T) {
  1298  	g := NewWithT(t)
  1299  	env := NewEnvironment()
  1300  	env.ConfigStore = NewFakeStore()
  1301  	env.ServiceDiscovery = &localServiceDiscovery{
  1302  		services: []*Service{
  1303  			{
  1304  				Hostname: "svc-unset",
  1305  				Ports:    allPorts,
  1306  				Attributes: ServiceAttributes{
  1307  					Namespace: "test1",
  1308  				},
  1309  			},
  1310  			{
  1311  				Hostname: "svc-public",
  1312  				Ports:    allPorts,
  1313  				Attributes: ServiceAttributes{
  1314  					Namespace: "test1",
  1315  					ExportTo:  sets.New(visibility.Public),
  1316  				},
  1317  			},
  1318  			{
  1319  				Hostname: "svc-private",
  1320  				Ports:    allPorts,
  1321  				Attributes: ServiceAttributes{
  1322  					Namespace: "test1",
  1323  					ExportTo:  sets.New(visibility.Private),
  1324  				},
  1325  			},
  1326  			{
  1327  				Hostname: "svc-none",
  1328  				Ports:    allPorts,
  1329  				Attributes: ServiceAttributes{
  1330  					Namespace: "test1",
  1331  					ExportTo:  sets.New(visibility.None),
  1332  				},
  1333  			},
  1334  			{
  1335  				Hostname: "svc-namespace",
  1336  				Ports:    allPorts,
  1337  				Attributes: ServiceAttributes{
  1338  					Namespace: "test1",
  1339  					ExportTo:  sets.New(visibility.Instance("namespace")),
  1340  				},
  1341  			},
  1342  		},
  1343  		serviceInstances: []*ServiceInstance{{
  1344  			Endpoint: &IstioEndpoint{
  1345  				Address:      "192.168.1.2",
  1346  				EndpointPort: 8000,
  1347  				TLSMode:      DisabledTLSModeLabel,
  1348  			},
  1349  		}},
  1350  	}
  1351  	m := mesh.DefaultMeshConfig()
  1352  	env.Watcher = mesh.NewFixedWatcher(m)
  1353  	env.Init()
  1354  
  1355  	// Init a new push context
  1356  	pc := NewPushContext()
  1357  	if err := pc.InitContext(env, nil, nil); err != nil {
  1358  		t.Fatal(err)
  1359  	}
  1360  	si := pc.ServiceIndex
  1361  
  1362  	// Should have all 5 services
  1363  	g.Expect(si.instancesByPort).To(HaveLen(5))
  1364  	g.Expect(si.HostnameAndNamespace).To(HaveLen(5))
  1365  
  1366  	// Should just have "namespace"
  1367  	g.Expect(si.exportedToNamespace).To(HaveLen(1))
  1368  	g.Expect(serviceNames(si.exportedToNamespace["namespace"])).To(Equal([]string{"svc-namespace"}))
  1369  
  1370  	g.Expect(serviceNames(si.public)).To(Equal([]string{"svc-public", "svc-unset"}))
  1371  
  1372  	// Should just have "test1"
  1373  	g.Expect(si.privateByNamespace).To(HaveLen(1))
  1374  	g.Expect(serviceNames(si.privateByNamespace["test1"])).To(Equal([]string{"svc-private"}))
  1375  }
  1376  
  1377  func TestIsServiceVisible(t *testing.T) {
  1378  	targetNamespace := "foo"
  1379  	cases := []struct {
  1380  		name        string
  1381  		pushContext *PushContext
  1382  		service     *Service
  1383  		expect      bool
  1384  	}{
  1385  		{
  1386  			name: "service whose namespace is foo has no exportTo map with global private",
  1387  			pushContext: &PushContext{
  1388  				exportToDefaults: exportToDefaults{
  1389  					service: sets.New(visibility.Private),
  1390  				},
  1391  			},
  1392  			service: &Service{
  1393  				Attributes: ServiceAttributes{
  1394  					Namespace: "foo",
  1395  				},
  1396  			},
  1397  			expect: true,
  1398  		},
  1399  		{
  1400  			name: "service whose namespace is bar has no exportTo map with global private",
  1401  			pushContext: &PushContext{
  1402  				exportToDefaults: exportToDefaults{
  1403  					service: sets.New(visibility.Private),
  1404  				},
  1405  			},
  1406  			service: &Service{
  1407  				Attributes: ServiceAttributes{
  1408  					Namespace: "bar",
  1409  				},
  1410  			},
  1411  			expect: false,
  1412  		},
  1413  		{
  1414  			name: "service whose namespace is bar has no exportTo map with global public",
  1415  			pushContext: &PushContext{
  1416  				exportToDefaults: exportToDefaults{
  1417  					service: sets.New(visibility.Public),
  1418  				},
  1419  			},
  1420  			service: &Service{
  1421  				Attributes: ServiceAttributes{
  1422  					Namespace: "bar",
  1423  				},
  1424  			},
  1425  			expect: true,
  1426  		},
  1427  		{
  1428  			name:        "service whose namespace is foo has exportTo map with private",
  1429  			pushContext: &PushContext{},
  1430  			service: &Service{
  1431  				Attributes: ServiceAttributes{
  1432  					Namespace: "foo",
  1433  					ExportTo:  sets.New(visibility.Private),
  1434  				},
  1435  			},
  1436  			expect: true,
  1437  		},
  1438  		{
  1439  			name:        "service whose namespace is bar has exportTo map with private",
  1440  			pushContext: &PushContext{},
  1441  			service: &Service{
  1442  				Attributes: ServiceAttributes{
  1443  					Namespace: "bar",
  1444  					ExportTo:  sets.New(visibility.Private),
  1445  				},
  1446  			},
  1447  			expect: false,
  1448  		},
  1449  		{
  1450  			name:        "service whose namespace is bar has exportTo map with public",
  1451  			pushContext: &PushContext{},
  1452  			service: &Service{
  1453  				Attributes: ServiceAttributes{
  1454  					Namespace: "bar",
  1455  					ExportTo:  sets.New(visibility.Public),
  1456  				},
  1457  			},
  1458  			expect: true,
  1459  		},
  1460  		{
  1461  			name:        "service whose namespace is bar has exportTo map with specific namespace foo",
  1462  			pushContext: &PushContext{},
  1463  			service: &Service{
  1464  				Attributes: ServiceAttributes{
  1465  					Namespace: "bar",
  1466  					ExportTo:  sets.New(visibility.Instance("foo")),
  1467  				},
  1468  			},
  1469  			expect: true,
  1470  		},
  1471  		{
  1472  			name:        "service whose namespace is bar has exportTo map with specific namespace baz",
  1473  			pushContext: &PushContext{},
  1474  			service: &Service{
  1475  				Attributes: ServiceAttributes{
  1476  					Namespace: "bar",
  1477  					ExportTo:  sets.New(visibility.Instance("baz")),
  1478  				},
  1479  			},
  1480  			expect: false,
  1481  		},
  1482  		{
  1483  			name:        "service visible to none",
  1484  			pushContext: &PushContext{},
  1485  			service: &Service{
  1486  				Attributes: ServiceAttributes{
  1487  					Namespace: "bar",
  1488  					ExportTo:  sets.New(visibility.None),
  1489  				},
  1490  			},
  1491  			expect: false,
  1492  		},
  1493  		{
  1494  			name:        "service has both public visibility and none visibility",
  1495  			pushContext: &PushContext{},
  1496  			service: &Service{
  1497  				Attributes: ServiceAttributes{
  1498  					Namespace: "bar",
  1499  					ExportTo: sets.New(
  1500  						visibility.Public,
  1501  						visibility.None,
  1502  					),
  1503  				},
  1504  			},
  1505  			expect: true,
  1506  		},
  1507  		{
  1508  			name:        "service has both none visibility and private visibility",
  1509  			pushContext: &PushContext{},
  1510  			service: &Service{
  1511  				Attributes: ServiceAttributes{
  1512  					Namespace: "bar",
  1513  					ExportTo: sets.New(
  1514  						visibility.Private,
  1515  						visibility.None,
  1516  					),
  1517  				},
  1518  			},
  1519  			expect: false,
  1520  		},
  1521  	}
  1522  
  1523  	for _, c := range cases {
  1524  		t.Run(c.name, func(t *testing.T) {
  1525  			isVisible := c.pushContext.IsServiceVisible(c.service, targetNamespace)
  1526  
  1527  			g := NewWithT(t)
  1528  			g.Expect(isVisible).To(Equal(c.expect))
  1529  		})
  1530  	}
  1531  }
  1532  
  1533  func serviceNames(svcs []*Service) []string {
  1534  	var s []string
  1535  	for _, ss := range svcs {
  1536  		s = append(s, string(ss.Hostname))
  1537  	}
  1538  	sort.Strings(s)
  1539  	return s
  1540  }
  1541  
  1542  func TestInitPushContext(t *testing.T) {
  1543  	env := NewEnvironment()
  1544  	configStore := NewFakeStore()
  1545  	_, _ = configStore.Create(config.Config{
  1546  		Meta: config.Meta{
  1547  			Name:             "rule1",
  1548  			Namespace:        "test1",
  1549  			GroupVersionKind: gvk.VirtualService,
  1550  		},
  1551  		Spec: &networking.VirtualService{
  1552  			Hosts:    []string{"rule1.com"},
  1553  			ExportTo: []string{".", "ns1"},
  1554  		},
  1555  	})
  1556  	_, _ = configStore.Create(config.Config{
  1557  		Meta: config.Meta{
  1558  			Name:             "rule1",
  1559  			Namespace:        "test1",
  1560  			GroupVersionKind: gvk.DestinationRule,
  1561  		},
  1562  		Spec: &networking.DestinationRule{
  1563  			ExportTo: []string{".", "ns1"},
  1564  		},
  1565  	})
  1566  	_, _ = configStore.Create(config.Config{
  1567  		Meta: config.Meta{
  1568  			Name:             "default",
  1569  			Namespace:        "istio-system",
  1570  			GroupVersionKind: gvk.Sidecar,
  1571  		},
  1572  		Spec: &networking.Sidecar{
  1573  			Egress: []*networking.IstioEgressListener{
  1574  				{Hosts: []string{"test1/*"}},
  1575  			},
  1576  		},
  1577  	})
  1578  
  1579  	env.ConfigStore = configStore
  1580  	env.ServiceDiscovery = &localServiceDiscovery{
  1581  		services: []*Service{
  1582  			{
  1583  				Hostname: "svc1",
  1584  				Ports:    allPorts,
  1585  				Attributes: ServiceAttributes{
  1586  					Namespace: "test1",
  1587  				},
  1588  			},
  1589  			{
  1590  				Hostname: "svc2",
  1591  				Ports:    allPorts,
  1592  				Attributes: ServiceAttributes{
  1593  					Namespace: "test1",
  1594  					ExportTo:  sets.New(visibility.Public),
  1595  				},
  1596  			},
  1597  		},
  1598  		serviceInstances: []*ServiceInstance{{
  1599  			Endpoint: &IstioEndpoint{
  1600  				Address:      "192.168.1.2",
  1601  				EndpointPort: 8000,
  1602  				TLSMode:      DisabledTLSModeLabel,
  1603  			},
  1604  		}},
  1605  	}
  1606  	m := mesh.DefaultMeshConfig()
  1607  	env.Watcher = mesh.NewFixedWatcher(m)
  1608  	env.Init()
  1609  
  1610  	// Init a new push context
  1611  	old := NewPushContext()
  1612  	if err := old.InitContext(env, nil, nil); err != nil {
  1613  		t.Fatal(err)
  1614  	}
  1615  
  1616  	// Create a new one, copying from the old one
  1617  	// Pass a ConfigsUpdated otherwise we would just copy it directly
  1618  	newPush := NewPushContext()
  1619  	if err := newPush.InitContext(env, old, &PushRequest{
  1620  		ConfigsUpdated: sets.Set[ConfigKey]{
  1621  			{Kind: kind.Secret}: {},
  1622  		},
  1623  	}); err != nil {
  1624  		t.Fatal(err)
  1625  	}
  1626  
  1627  	// Check to ensure the update is identical to the old one
  1628  	// There is probably a better way to do this.
  1629  	diff := cmp.Diff(old, newPush,
  1630  		// Allow looking into exported fields for parts of push context
  1631  		cmp.AllowUnexported(PushContext{}, exportToDefaults{}, serviceIndex{}, virtualServiceIndex{},
  1632  			destinationRuleIndex{}, gatewayIndex{}, consolidatedDestRules{}, IstioEgressListenerWrapper{}, SidecarScope{},
  1633  			AuthenticationPolicies{}, NetworkManager{}, sidecarIndex{}, Telemetries{}, ProxyConfigs{}, ConsolidatedDestRule{},
  1634  			ClusterLocalHosts{}),
  1635  		// These are not feasible/worth comparing
  1636  		cmpopts.IgnoreTypes(sync.RWMutex{}, localServiceDiscovery{}, FakeStore{}, atomic.Bool{}, sync.Mutex{}),
  1637  		cmpopts.IgnoreUnexported(IstioEndpoint{}),
  1638  		cmpopts.IgnoreInterfaces(struct{ mesh.Holder }{}),
  1639  		protocmp.Transform(),
  1640  	)
  1641  	if diff != "" {
  1642  		t.Fatalf("Push context had a diff after update: %v", diff)
  1643  	}
  1644  }
  1645  
  1646  func TestSidecarScope(t *testing.T) {
  1647  	test.SetForTest(t, &features.ConvertSidecarScopeConcurrency, 10)
  1648  	ps := NewPushContext()
  1649  	env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})}
  1650  	ps.Mesh = env.Mesh()
  1651  	ps.ServiceIndex.HostnameAndNamespace["svc1.default.cluster.local"] = map[string]*Service{"default": nil}
  1652  	ps.ServiceIndex.HostnameAndNamespace["svc2.nosidecar.cluster.local"] = map[string]*Service{"nosidecar": nil}
  1653  	ps.ServiceIndex.HostnameAndNamespace["svc3.istio-system.cluster.local"] = map[string]*Service{"istio-system": nil}
  1654  
  1655  	configStore := NewFakeStore()
  1656  	sidecarWithWorkloadSelector := &networking.Sidecar{
  1657  		WorkloadSelector: &networking.WorkloadSelector{
  1658  			Labels: map[string]string{"app": "foo"},
  1659  		},
  1660  		Egress: []*networking.IstioEgressListener{
  1661  			{
  1662  				Hosts: []string{"default/*"},
  1663  			},
  1664  		},
  1665  		OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{},
  1666  	}
  1667  	sidecarWithoutWorkloadSelector := &networking.Sidecar{
  1668  		Egress: []*networking.IstioEgressListener{
  1669  			{
  1670  				Hosts: []string{"default/*"},
  1671  			},
  1672  		},
  1673  		OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{},
  1674  	}
  1675  	configWithWorkloadSelector := config.Config{
  1676  		Meta: config.Meta{
  1677  			GroupVersionKind: gvk.Sidecar,
  1678  			Name:             "foo",
  1679  			Namespace:        "default",
  1680  		},
  1681  		Spec: sidecarWithWorkloadSelector,
  1682  	}
  1683  	rootConfig := config.Config{
  1684  		Meta: config.Meta{
  1685  			GroupVersionKind: gvk.Sidecar,
  1686  			Name:             "global",
  1687  			Namespace:        constants.IstioSystemNamespace,
  1688  		},
  1689  		Spec: sidecarWithoutWorkloadSelector,
  1690  	}
  1691  	_, _ = configStore.Create(configWithWorkloadSelector)
  1692  	_, _ = configStore.Create(rootConfig)
  1693  
  1694  	env.ConfigStore = configStore
  1695  	ps.initSidecarScopes(env)
  1696  	cases := []struct {
  1697  		proxy    *Proxy
  1698  		labels   labels.Instance
  1699  		sidecar  string
  1700  		describe string
  1701  	}{
  1702  		{
  1703  			proxy:    &Proxy{Type: SidecarProxy, ConfigNamespace: "default"},
  1704  			labels:   labels.Instance{"app": "foo"},
  1705  			sidecar:  "default/foo",
  1706  			describe: "match local sidecar",
  1707  		},
  1708  		{
  1709  			proxy:    &Proxy{Type: SidecarProxy, ConfigNamespace: "default"},
  1710  			labels:   labels.Instance{"app": "bar"},
  1711  			sidecar:  "default/global",
  1712  			describe: "no match local sidecar",
  1713  		},
  1714  		{
  1715  			proxy:    &Proxy{Type: SidecarProxy, ConfigNamespace: "nosidecar"},
  1716  			labels:   labels.Instance{"app": "bar"},
  1717  			sidecar:  "nosidecar/global",
  1718  			describe: "no sidecar",
  1719  		},
  1720  		{
  1721  			proxy:    &Proxy{Type: Router, ConfigNamespace: "istio-system"},
  1722  			labels:   labels.Instance{"app": "istio-gateway"},
  1723  			sidecar:  "istio-system/default-sidecar",
  1724  			describe: "gateway sidecar scope",
  1725  		},
  1726  	}
  1727  	for _, c := range cases {
  1728  		t.Run(c.describe, func(t *testing.T) {
  1729  			scope := ps.getSidecarScope(c.proxy, c.labels)
  1730  			if c.sidecar != scopeToSidecar(scope) {
  1731  				t.Errorf("should get sidecar %s but got %s", c.sidecar, scopeToSidecar(scope))
  1732  			}
  1733  		})
  1734  	}
  1735  }
  1736  
  1737  func TestRootSidecarScopePropagation(t *testing.T) {
  1738  	rootNS := "istio-system"
  1739  	defaultNS := "default"
  1740  	otherNS := "foo"
  1741  
  1742  	verifyServices := func(sidecarScopeEnabled bool, desc string, ns string, push *PushContext) {
  1743  		t.Run(desc, func(t *testing.T) {
  1744  			scScope := push.getSidecarScope(&Proxy{Type: SidecarProxy, ConfigNamespace: ns}, nil)
  1745  			services := scScope.Services()
  1746  			numSvc := 0
  1747  			svcList := []string{}
  1748  			for _, service := range services {
  1749  				svcName := service.Attributes.Name
  1750  				svcNS := service.Attributes.Namespace
  1751  				if svcNS != ns && svcNS != rootNS {
  1752  					numSvc++
  1753  				}
  1754  				svcList = append(svcList, fmt.Sprintf("%v.%v.cluster.local", svcName, svcNS))
  1755  			}
  1756  			if sidecarScopeEnabled && numSvc > 0 {
  1757  				t.Fatalf("For namespace:%v, should not see services from other ns. Instead got these services: %v", ns, svcList)
  1758  			}
  1759  		})
  1760  	}
  1761  
  1762  	env := NewEnvironment()
  1763  	configStore := NewFakeStore()
  1764  
  1765  	m := mesh.DefaultMeshConfig()
  1766  	env.Watcher = mesh.NewFixedWatcher(m)
  1767  
  1768  	env.ServiceDiscovery = &localServiceDiscovery{
  1769  		services: []*Service{
  1770  			{
  1771  				Hostname: "svc1",
  1772  				Ports:    allPorts,
  1773  				Attributes: ServiceAttributes{
  1774  					Name:      "svc1",
  1775  					Namespace: defaultNS,
  1776  				},
  1777  			},
  1778  			{
  1779  				Hostname: "svc2",
  1780  				Ports:    allPorts,
  1781  				Attributes: ServiceAttributes{
  1782  					Name:      "svc2",
  1783  					Namespace: otherNS,
  1784  				},
  1785  			},
  1786  			{
  1787  				Hostname: "svc3",
  1788  				Ports:    allPorts,
  1789  				Attributes: ServiceAttributes{
  1790  					Name:      "svc3",
  1791  					Namespace: otherNS,
  1792  				},
  1793  			},
  1794  			{
  1795  				Hostname: "svc4",
  1796  				Ports:    allPorts,
  1797  				Attributes: ServiceAttributes{
  1798  					Name:      "svc4",
  1799  					Namespace: rootNS,
  1800  				},
  1801  			},
  1802  		},
  1803  	}
  1804  	env.Init()
  1805  	globalSidecar := &networking.Sidecar{
  1806  		Egress: []*networking.IstioEgressListener{
  1807  			{
  1808  				Hosts: []string{"./*", fmt.Sprintf("%v/*", rootNS)},
  1809  			},
  1810  		},
  1811  		OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{},
  1812  	}
  1813  	rootConfig := config.Config{
  1814  		Meta: config.Meta{
  1815  			GroupVersionKind: gvk.Sidecar,
  1816  			Name:             "global",
  1817  			Namespace:        constants.IstioSystemNamespace,
  1818  		},
  1819  		Spec: globalSidecar,
  1820  	}
  1821  
  1822  	_, _ = configStore.Create(rootConfig)
  1823  	env.ConfigStore = configStore
  1824  
  1825  	testDesc := "Testing root SidecarScope for ns:%v enabled when %v is called."
  1826  	when := "createNewContext"
  1827  	newPush := NewPushContext()
  1828  	newPush.Mesh = env.Mesh()
  1829  	newPush.InitContext(env, nil, nil)
  1830  	verifyServices(true, fmt.Sprintf(testDesc, otherNS, when), otherNS, newPush)
  1831  	verifyServices(true, fmt.Sprintf(testDesc, defaultNS, when), defaultNS, newPush)
  1832  
  1833  	oldPush := newPush
  1834  	newPush = NewPushContext()
  1835  	newPush.Mesh = env.Mesh()
  1836  	svcName := "svc6.foo.cluster.local"
  1837  	if err := newPush.InitContext(env, oldPush, &PushRequest{
  1838  		ConfigsUpdated: sets.Set[ConfigKey]{
  1839  			{Kind: kind.Service, Name: svcName, Namespace: "foo"}: {},
  1840  		},
  1841  		Reason: nil,
  1842  		Full:   true,
  1843  	}); err != nil {
  1844  		t.Fatal(err)
  1845  	}
  1846  	when = "updateContext(with no changes)"
  1847  	verifyServices(true, fmt.Sprintf(testDesc, otherNS, when), otherNS, newPush)
  1848  	verifyServices(true, fmt.Sprintf(testDesc, defaultNS, when), defaultNS, newPush)
  1849  }
  1850  
  1851  func TestBestEffortInferServiceMTLSMode(t *testing.T) {
  1852  	const partialNS string = "partial"
  1853  	const wholeNS string = "whole"
  1854  	ps := NewPushContext()
  1855  	env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})}
  1856  	sd := &localServiceDiscovery{}
  1857  	env.ServiceDiscovery = sd
  1858  	ps.Mesh = env.Mesh()
  1859  
  1860  	configStore := NewFakeStore()
  1861  
  1862  	// Add beta policies
  1863  	_, _ = configStore.Create(*createTestPeerAuthenticationResource("default", wholeNS, time.Now(), nil, securityBeta.PeerAuthentication_MutualTLS_STRICT))
  1864  	// workload level beta policy.
  1865  	_, _ = configStore.Create(*createTestPeerAuthenticationResource("workload-beta-policy", partialNS, time.Now(), &selectorpb.WorkloadSelector{
  1866  		MatchLabels: map[string]string{
  1867  			"app":     "httpbin",
  1868  			"version": "v1",
  1869  		},
  1870  	}, securityBeta.PeerAuthentication_MutualTLS_DISABLE))
  1871  
  1872  	env.ConfigStore = configStore
  1873  	ps.initAuthnPolicies(env)
  1874  
  1875  	instancePlainText := &ServiceInstance{
  1876  		Endpoint: &IstioEndpoint{
  1877  			Address:      "192.168.1.2",
  1878  			EndpointPort: 1000000,
  1879  			TLSMode:      DisabledTLSModeLabel,
  1880  		},
  1881  	}
  1882  
  1883  	cases := []struct {
  1884  		name              string
  1885  		serviceNamespace  string
  1886  		servicePort       int
  1887  		serviceResolution Resolution
  1888  		serviceInstances  []*ServiceInstance
  1889  		wanted            MutualTLSMode
  1890  	}{
  1891  		{
  1892  			name:              "from namespace policy",
  1893  			serviceNamespace:  wholeNS,
  1894  			servicePort:       80,
  1895  			serviceResolution: ClientSideLB,
  1896  			wanted:            MTLSStrict,
  1897  		},
  1898  		{
  1899  			name:              "from mesh default",
  1900  			serviceNamespace:  partialNS,
  1901  			servicePort:       80,
  1902  			serviceResolution: ClientSideLB,
  1903  			wanted:            MTLSPermissive,
  1904  		},
  1905  		{
  1906  			name:              "headless service with no instances found yet",
  1907  			serviceNamespace:  partialNS,
  1908  			servicePort:       80,
  1909  			serviceResolution: Passthrough,
  1910  			wanted:            MTLSDisable,
  1911  		},
  1912  		{
  1913  			name:              "headless service with instances",
  1914  			serviceNamespace:  partialNS,
  1915  			servicePort:       80,
  1916  			serviceResolution: Passthrough,
  1917  			serviceInstances:  []*ServiceInstance{instancePlainText},
  1918  			wanted:            MTLSDisable,
  1919  		},
  1920  	}
  1921  
  1922  	serviceName := host.Name("some-service")
  1923  	for _, tc := range cases {
  1924  		t.Run(tc.name, func(t *testing.T) {
  1925  			service := &Service{
  1926  				Hostname:   host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)),
  1927  				Resolution: tc.serviceResolution,
  1928  				Attributes: ServiceAttributes{Namespace: tc.serviceNamespace},
  1929  			}
  1930  			// Intentionally use the externalService with the same name and namespace for test, though
  1931  			// these attributes don't matter.
  1932  			externalService := &Service{
  1933  				Hostname:     host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)),
  1934  				Resolution:   tc.serviceResolution,
  1935  				Attributes:   ServiceAttributes{Namespace: tc.serviceNamespace},
  1936  				MeshExternal: true,
  1937  			}
  1938  
  1939  			sd.serviceInstances = tc.serviceInstances
  1940  			port := &Port{
  1941  				Port: tc.servicePort,
  1942  			}
  1943  			if got := ps.BestEffortInferServiceMTLSMode(nil, service, port); got != tc.wanted {
  1944  				t.Fatalf("want %s, but got %s", tc.wanted, got)
  1945  			}
  1946  			if got := ps.BestEffortInferServiceMTLSMode(nil, externalService, port); got != MTLSUnknown {
  1947  				t.Fatalf("MTLS mode for external service should always be %s, but got %s", MTLSUnknown, got)
  1948  			}
  1949  		})
  1950  	}
  1951  }
  1952  
  1953  func scopeToSidecar(scope *SidecarScope) string {
  1954  	if scope == nil {
  1955  		return ""
  1956  	}
  1957  	return scope.Namespace + "/" + scope.Name
  1958  }
  1959  
  1960  func TestSetDestinationRuleWithWorkloadSelector(t *testing.T) {
  1961  	now := time.Now()
  1962  	ps := NewPushContext()
  1963  	ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "istio-system"}
  1964  	testhost := "httpbin.org"
  1965  	app1DestinationRule := config.Config{
  1966  		Meta: config.Meta{
  1967  			Name:              "nsRule1",
  1968  			Namespace:         "test",
  1969  			CreationTimestamp: now,
  1970  		},
  1971  		Spec: &networking.DestinationRule{
  1972  			Host:     testhost,
  1973  			ExportTo: []string{"test2", "."},
  1974  			WorkloadSelector: &selectorpb.WorkloadSelector{
  1975  				MatchLabels: map[string]string{"app": "app1"},
  1976  			},
  1977  			TrafficPolicy: &networking.TrafficPolicy{
  1978  				ConnectionPool: &networking.ConnectionPoolSettings{
  1979  					Tcp: &networking.ConnectionPoolSettings_TCPSettings{
  1980  						ConnectTimeout: &durationpb.Duration{Seconds: 1},
  1981  						MaxConnections: 111,
  1982  					},
  1983  				},
  1984  				Tls: &networking.ClientTLSSettings{
  1985  					Mode:              networking.ClientTLSSettings_MUTUAL,
  1986  					ClientCertificate: "/etc/certs/myclientcert.pem",
  1987  					PrivateKey:        "/etc/certs/client_private_key.pem",
  1988  					CaCertificates:    "/etc/certs/rootcacerts.pem",
  1989  				},
  1990  			},
  1991  		},
  1992  	}
  1993  	app2DestinationRule := config.Config{
  1994  		Meta: config.Meta{
  1995  			Name:              "nsRule2",
  1996  			Namespace:         "test",
  1997  			CreationTimestamp: now.Add(time.Second),
  1998  		},
  1999  		Spec: &networking.DestinationRule{
  2000  			Host:     testhost,
  2001  			ExportTo: []string{"test2", "."},
  2002  			WorkloadSelector: &selectorpb.WorkloadSelector{
  2003  				MatchLabels: map[string]string{"app": "app2"},
  2004  			},
  2005  			TrafficPolicy: &networking.TrafficPolicy{
  2006  				ConnectionPool: &networking.ConnectionPoolSettings{
  2007  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  2008  						MaxRetries: 33,
  2009  					},
  2010  					Tcp: &networking.ConnectionPoolSettings_TCPSettings{
  2011  						ConnectTimeout: &durationpb.Duration{Seconds: 33},
  2012  					},
  2013  				},
  2014  				OutlierDetection: &networking.OutlierDetection{
  2015  					Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3},
  2016  				},
  2017  				Tls: &networking.ClientTLSSettings{
  2018  					Mode: networking.ClientTLSSettings_SIMPLE,
  2019  				},
  2020  			},
  2021  		},
  2022  	}
  2023  	app3DestinationRule := config.Config{
  2024  		Meta: config.Meta{
  2025  			Name:              "nsRule3",
  2026  			Namespace:         "test",
  2027  			CreationTimestamp: now.Add(time.Second),
  2028  		},
  2029  		Spec: &networking.DestinationRule{
  2030  			Host:     testhost,
  2031  			ExportTo: []string{"test2", "."},
  2032  			WorkloadSelector: &selectorpb.WorkloadSelector{
  2033  				MatchLabels: map[string]string{"app": "app2"},
  2034  			},
  2035  			TrafficPolicy: &networking.TrafficPolicy{
  2036  				ConnectionPool: &networking.ConnectionPoolSettings{
  2037  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  2038  						MaxRetries: 33,
  2039  					},
  2040  					Tcp: &networking.ConnectionPoolSettings_TCPSettings{
  2041  						ConnectTimeout: &durationpb.Duration{Seconds: 33},
  2042  					},
  2043  				},
  2044  				OutlierDetection: &networking.OutlierDetection{
  2045  					Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3},
  2046  				},
  2047  				Tls: &networking.ClientTLSSettings{
  2048  					Mode: networking.ClientTLSSettings_SIMPLE,
  2049  				},
  2050  			},
  2051  		},
  2052  	}
  2053  	namespaceDestinationRule := config.Config{
  2054  		Meta: config.Meta{
  2055  			Name:              "nsRule4",
  2056  			Namespace:         "test",
  2057  			CreationTimestamp: now.Add(-time.Second),
  2058  		},
  2059  		Spec: &networking.DestinationRule{
  2060  			Host:     testhost,
  2061  			ExportTo: []string{".", "test2"},
  2062  			TrafficPolicy: &networking.TrafficPolicy{
  2063  				ConnectionPool: &networking.ConnectionPoolSettings{
  2064  					Http: &networking.ConnectionPoolSettings_HTTPSettings{
  2065  						MaxRetries: 33,
  2066  					},
  2067  					Tcp: &networking.ConnectionPoolSettings_TCPSettings{
  2068  						ConnectTimeout: &durationpb.Duration{Seconds: 33},
  2069  					},
  2070  				},
  2071  				OutlierDetection: &networking.OutlierDetection{
  2072  					Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3},
  2073  				},
  2074  				Tls: &networking.ClientTLSSettings{
  2075  					Mode: networking.ClientTLSSettings_SIMPLE,
  2076  				},
  2077  			},
  2078  		},
  2079  	}
  2080  	testCases := []struct {
  2081  		name                   string
  2082  		proxyNs                string
  2083  		serviceNs              string
  2084  		serviceHostname        string
  2085  		expectedDrCount        int
  2086  		expectedDrName         []string
  2087  		expectedNamespacedFrom map[string][]types.NamespacedName
  2088  	}{
  2089  		{
  2090  			name:            "return list of DRs for specific host",
  2091  			proxyNs:         "test",
  2092  			serviceNs:       "test",
  2093  			serviceHostname: testhost,
  2094  			expectedDrCount: 3,
  2095  			expectedDrName:  []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name},
  2096  			expectedNamespacedFrom: map[string][]types.NamespacedName{
  2097  				app1DestinationRule.Meta.Name:      {app1DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()},
  2098  				app2DestinationRule.Meta.Name:      {app2DestinationRule.NamespacedName(), app3DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()},
  2099  				namespaceDestinationRule.Meta.Name: {namespaceDestinationRule.NamespacedName()},
  2100  			},
  2101  		},
  2102  		{
  2103  			name:                   "workload specific DR should not be exported",
  2104  			proxyNs:                "test2",
  2105  			serviceNs:              "test",
  2106  			serviceHostname:        testhost,
  2107  			expectedDrCount:        1,
  2108  			expectedDrName:         []string{namespaceDestinationRule.Meta.Name},
  2109  			expectedNamespacedFrom: map[string][]types.NamespacedName{},
  2110  		},
  2111  		{
  2112  			name:            "rules with same workloadselector should be merged",
  2113  			proxyNs:         "test",
  2114  			serviceNs:       "test",
  2115  			serviceHostname: testhost,
  2116  			expectedDrCount: 3,
  2117  			expectedDrName:  []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name},
  2118  			expectedNamespacedFrom: map[string][]types.NamespacedName{
  2119  				app1DestinationRule.Meta.Name:      {app1DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()},
  2120  				app2DestinationRule.Meta.Name:      {app2DestinationRule.NamespacedName(), app3DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()},
  2121  				namespaceDestinationRule.Meta.Name: {namespaceDestinationRule.NamespacedName()},
  2122  			},
  2123  		},
  2124  	}
  2125  
  2126  	ps.setDestinationRules([]config.Config{app1DestinationRule, app2DestinationRule, app3DestinationRule, namespaceDestinationRule})
  2127  
  2128  	for _, tt := range testCases {
  2129  		t.Run(tt.name, func(t *testing.T) {
  2130  			drList := ps.destinationRule(tt.proxyNs,
  2131  				&Service{
  2132  					Hostname: host.Name(tt.serviceHostname),
  2133  					Attributes: ServiceAttributes{
  2134  						Namespace: tt.serviceNs,
  2135  					},
  2136  				})
  2137  			if len(drList) != tt.expectedDrCount {
  2138  				t.Errorf("expected %d destinationRules for host %v got %v", tt.expectedDrCount, tt.serviceHostname, drList)
  2139  			}
  2140  			for i, dr := range drList {
  2141  				if dr.rule.Name != tt.expectedDrName[i] {
  2142  					t.Errorf("destinationRuleName expected %v got %v", tt.expectedDrName[i], dr.rule.Name)
  2143  				}
  2144  			}
  2145  			testLocal := ps.destinationRuleIndex.namespaceLocal[tt.proxyNs]
  2146  			if testLocal != nil {
  2147  				destRules := testLocal.specificDestRules
  2148  				for _, dr := range destRules[host.Name(testhost)] {
  2149  
  2150  					// Check if the 'from' values match the expectedFrom map
  2151  					expectedFrom := tt.expectedNamespacedFrom[dr.rule.Meta.Name]
  2152  					if !reflect.DeepEqual(dr.from, expectedFrom) {
  2153  						t.Errorf("Unexpected 'from' value for destination rule %s. Got: %v, Expected: %v", dr.rule.NamespacedName(), dr.from, expectedFrom)
  2154  					}
  2155  				}
  2156  			}
  2157  		})
  2158  	}
  2159  }
  2160  
  2161  func TestSetDestinationRuleMerging(t *testing.T) {
  2162  	ps := NewPushContext()
  2163  	ps.exportToDefaults.destinationRule = sets.New(visibility.Public)
  2164  	testhost := "httpbin.org"
  2165  	destinationRuleNamespace1 := config.Config{
  2166  		Meta: config.Meta{
  2167  			Name:      "rule1",
  2168  			Namespace: "test",
  2169  		},
  2170  		Spec: &networking.DestinationRule{
  2171  			Host: testhost,
  2172  			Subsets: []*networking.Subset{
  2173  				{
  2174  					Name: "subset1",
  2175  				},
  2176  				{
  2177  					Name: "subset2",
  2178  				},
  2179  			},
  2180  		},
  2181  	}
  2182  	destinationRuleNamespace2 := config.Config{
  2183  		Meta: config.Meta{
  2184  			Name:      "rule2",
  2185  			Namespace: "test",
  2186  		},
  2187  		Spec: &networking.DestinationRule{
  2188  			Host: testhost,
  2189  			Subsets: []*networking.Subset{
  2190  				{
  2191  					Name: "subset3",
  2192  				},
  2193  				{
  2194  					Name: "subset4",
  2195  				},
  2196  			},
  2197  		},
  2198  	}
  2199  	expectedDestRules := []types.NamespacedName{
  2200  		{Namespace: "test", Name: "rule1"},
  2201  		{Namespace: "test", Name: "rule2"},
  2202  	}
  2203  	ps.setDestinationRules([]config.Config{destinationRuleNamespace1, destinationRuleNamespace2})
  2204  	private := ps.destinationRuleIndex.namespaceLocal["test"].specificDestRules[host.Name(testhost)]
  2205  	public := ps.destinationRuleIndex.exportedByNamespace["test"].specificDestRules[host.Name(testhost)]
  2206  	subsetsLocal := private[0].rule.Spec.(*networking.DestinationRule).Subsets
  2207  	subsetsExport := public[0].rule.Spec.(*networking.DestinationRule).Subsets
  2208  	assert.Equal(t, private[0].from, expectedDestRules)
  2209  	assert.Equal(t, public[0].from, expectedDestRules)
  2210  	if len(subsetsLocal) != 4 {
  2211  		t.Errorf("want %d, but got %d", 4, len(subsetsLocal))
  2212  	}
  2213  	if len(subsetsExport) != 4 {
  2214  		t.Errorf("want %d, but got %d", 4, len(subsetsExport))
  2215  	}
  2216  }
  2217  
  2218  func TestSetDestinationRuleWithExportTo(t *testing.T) {
  2219  	ps := NewPushContext()
  2220  	ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "istio-system"}
  2221  	testhost := "httpbin.org"
  2222  	appHost := "foo.app.org"
  2223  	wildcardHost1 := "*.org"
  2224  	wildcardHost2 := "*.app.org"
  2225  	destinationRuleNamespace1 := config.Config{
  2226  		Meta: config.Meta{
  2227  			Name:      "rule1",
  2228  			Namespace: "test1",
  2229  		},
  2230  		Spec: &networking.DestinationRule{
  2231  			Host:     testhost,
  2232  			ExportTo: []string{".", "ns1"},
  2233  			Subsets: []*networking.Subset{
  2234  				{
  2235  					Name: "subset1",
  2236  				},
  2237  				{
  2238  					Name: "subset2",
  2239  				},
  2240  			},
  2241  		},
  2242  	}
  2243  	destinationRuleNamespace2 := config.Config{
  2244  		Meta: config.Meta{
  2245  			Name:      "rule2",
  2246  			Namespace: "test2",
  2247  		},
  2248  		Spec: &networking.DestinationRule{
  2249  			Host:     testhost,
  2250  			ExportTo: []string{"test2", "ns1", "test1", "newNS"},
  2251  			Subsets: []*networking.Subset{
  2252  				{
  2253  					Name: "subset3",
  2254  				},
  2255  				{
  2256  					Name: "subset4",
  2257  				},
  2258  			},
  2259  		},
  2260  	}
  2261  	destinationRuleNamespace3 := config.Config{
  2262  		Meta: config.Meta{
  2263  			Name:      "rule3",
  2264  			Namespace: "test3",
  2265  		},
  2266  		Spec: &networking.DestinationRule{
  2267  			Host:     testhost,
  2268  			ExportTo: []string{"test1", "test2", "*"},
  2269  			Subsets: []*networking.Subset{
  2270  				{
  2271  					Name: "subset5",
  2272  				},
  2273  				{
  2274  					Name: "subset6",
  2275  				},
  2276  			},
  2277  		},
  2278  	}
  2279  	destinationRuleNamespace4Local := config.Config{
  2280  		Meta: config.Meta{
  2281  			Name:      "rule4-local",
  2282  			Namespace: "test4",
  2283  		},
  2284  		Spec: &networking.DestinationRule{
  2285  			Host:     testhost,
  2286  			ExportTo: []string{"test4"},
  2287  			Subsets: []*networking.Subset{
  2288  				{
  2289  					Name: "subset15",
  2290  				},
  2291  				{
  2292  					Name: "subset16",
  2293  				},
  2294  			},
  2295  		},
  2296  	}
  2297  	destinationRuleRootNamespace := config.Config{
  2298  		Meta: config.Meta{
  2299  			Name:      "rule4",
  2300  			Namespace: "istio-system",
  2301  		},
  2302  		Spec: &networking.DestinationRule{
  2303  			Host: testhost,
  2304  			Subsets: []*networking.Subset{
  2305  				{
  2306  					Name: "subset7",
  2307  				},
  2308  				{
  2309  					Name: "subset8",
  2310  				},
  2311  			},
  2312  		},
  2313  	}
  2314  	destinationRuleRootNamespaceLocal := config.Config{
  2315  		Meta: config.Meta{
  2316  			Name:      "rule1",
  2317  			Namespace: "istio-system",
  2318  		},
  2319  		Spec: &networking.DestinationRule{
  2320  			Host:     testhost,
  2321  			ExportTo: []string{"."},
  2322  			Subsets: []*networking.Subset{
  2323  				{
  2324  					Name: "subset9",
  2325  				},
  2326  				{
  2327  					Name: "subset10",
  2328  				},
  2329  			},
  2330  		},
  2331  	}
  2332  	destinationRuleRootNamespaceLocalWithWildcardHost1 := config.Config{
  2333  		Meta: config.Meta{
  2334  			Name:      "rule2",
  2335  			Namespace: "istio-system",
  2336  		},
  2337  		Spec: &networking.DestinationRule{
  2338  			Host:     wildcardHost1,
  2339  			ExportTo: []string{"."},
  2340  			Subsets: []*networking.Subset{
  2341  				{
  2342  					Name: "subset11",
  2343  				},
  2344  				{
  2345  					Name: "subset12",
  2346  				},
  2347  			},
  2348  		},
  2349  	}
  2350  	destinationRuleRootNamespaceLocalWithWildcardHost2 := config.Config{
  2351  		Meta: config.Meta{
  2352  			Name:      "rule3",
  2353  			Namespace: "istio-system",
  2354  		},
  2355  		Spec: &networking.DestinationRule{
  2356  			Host:     wildcardHost2,
  2357  			ExportTo: []string{"."},
  2358  			Subsets: []*networking.Subset{
  2359  				{
  2360  					Name: "subset13",
  2361  				},
  2362  				{
  2363  					Name: "subset14",
  2364  				},
  2365  			},
  2366  		},
  2367  	}
  2368  
  2369  	destinationRule5ExportToRootNamespace := config.Config{
  2370  		Meta: config.Meta{
  2371  			Name:      "rule5",
  2372  			Namespace: "test5",
  2373  		},
  2374  		Spec: &networking.DestinationRule{
  2375  			Host:     "api.test.com",
  2376  			ExportTo: []string{"istio-system"},
  2377  			Subsets: []*networking.Subset{
  2378  				{
  2379  					Name: "subset5-0",
  2380  				},
  2381  				{
  2382  					Name: "subset5-1",
  2383  				},
  2384  			},
  2385  		},
  2386  	}
  2387  
  2388  	destinationRule6ExportToRootNamespace := config.Config{
  2389  		Meta: config.Meta{
  2390  			Name:      "rule6",
  2391  			Namespace: "test6",
  2392  		},
  2393  		Spec: &networking.DestinationRule{
  2394  			Host:     "api.test.com",
  2395  			ExportTo: []string{"istio-system"},
  2396  			Subsets: []*networking.Subset{
  2397  				{
  2398  					Name: "subset6-0",
  2399  				},
  2400  				{
  2401  					Name: "subset6-1",
  2402  				},
  2403  			},
  2404  		},
  2405  	}
  2406  	ps.setDestinationRules([]config.Config{
  2407  		destinationRuleNamespace1, destinationRuleNamespace2,
  2408  		destinationRuleNamespace3, destinationRuleNamespace4Local,
  2409  		destinationRuleRootNamespace, destinationRuleRootNamespaceLocal,
  2410  		destinationRuleRootNamespaceLocalWithWildcardHost1, destinationRuleRootNamespaceLocalWithWildcardHost2,
  2411  		destinationRule5ExportToRootNamespace, destinationRule6ExportToRootNamespace,
  2412  	})
  2413  	cases := []struct {
  2414  		proxyNs     string
  2415  		serviceNs   string
  2416  		host        string
  2417  		wantSubsets []string
  2418  	}{
  2419  		{
  2420  			proxyNs:     "test1",
  2421  			serviceNs:   "test1",
  2422  			host:        testhost,
  2423  			wantSubsets: []string{"subset1", "subset2"},
  2424  		},
  2425  		{
  2426  			proxyNs:     "test1",
  2427  			serviceNs:   "test2",
  2428  			host:        testhost,
  2429  			wantSubsets: []string{"subset1", "subset2"},
  2430  		},
  2431  		{
  2432  			proxyNs:     "test2",
  2433  			serviceNs:   "test1",
  2434  			host:        testhost,
  2435  			wantSubsets: []string{"subset3", "subset4"},
  2436  		},
  2437  		{
  2438  			proxyNs:     "newNS",
  2439  			serviceNs:   "test2",
  2440  			host:        testhost,
  2441  			wantSubsets: []string{"subset3", "subset4"},
  2442  		},
  2443  		{
  2444  			proxyNs:     "test3",
  2445  			serviceNs:   "test1",
  2446  			host:        testhost,
  2447  			wantSubsets: []string{"subset5", "subset6"},
  2448  		},
  2449  		{
  2450  			proxyNs:     "test5",
  2451  			serviceNs:   "test4",
  2452  			host:        testhost,
  2453  			wantSubsets: []string{"subset7", "subset8"},
  2454  		},
  2455  		{
  2456  			proxyNs:     "ns1",
  2457  			serviceNs:   "test1",
  2458  			host:        testhost,
  2459  			wantSubsets: []string{"subset1", "subset2"},
  2460  		},
  2461  		{
  2462  			proxyNs:     "ns1",
  2463  			serviceNs:   "random",
  2464  			host:        testhost,
  2465  			wantSubsets: []string{"subset7", "subset8"},
  2466  		},
  2467  		{
  2468  			proxyNs:     "random",
  2469  			serviceNs:   "random",
  2470  			host:        testhost,
  2471  			wantSubsets: []string{"subset7", "subset8"},
  2472  		},
  2473  		{
  2474  			proxyNs:     "test3",
  2475  			serviceNs:   "random",
  2476  			host:        testhost,
  2477  			wantSubsets: []string{"subset5", "subset6"},
  2478  		},
  2479  		{
  2480  			proxyNs:     "istio-system",
  2481  			serviceNs:   "random",
  2482  			host:        testhost,
  2483  			wantSubsets: []string{"subset9", "subset10"},
  2484  		},
  2485  		{
  2486  			proxyNs:     "istio-system",
  2487  			serviceNs:   "istio-system",
  2488  			host:        testhost,
  2489  			wantSubsets: []string{"subset9", "subset10"},
  2490  		},
  2491  		{
  2492  			proxyNs:     "istio-system",
  2493  			serviceNs:   "istio-system",
  2494  			host:        appHost,
  2495  			wantSubsets: []string{"subset13", "subset14"},
  2496  		},
  2497  		// dr in the svc ns takes effect on proxy in root ns
  2498  		{
  2499  			proxyNs:     "istio-system",
  2500  			serviceNs:   "test5",
  2501  			host:        "api.test.com",
  2502  			wantSubsets: []string{"subset5-0", "subset5-1"},
  2503  		},
  2504  		// dr in the svc ns takes effect on proxy in root ns
  2505  		{
  2506  			proxyNs:     "istio-system",
  2507  			serviceNs:   "test6",
  2508  			host:        "api.test.com",
  2509  			wantSubsets: []string{"subset6-0", "subset6-1"},
  2510  		},
  2511  		// both svc and dr namespace is not equal to proxy ns, the dr will not take effect on the proxy
  2512  		{
  2513  			proxyNs:     "istio-system",
  2514  			serviceNs:   "test7",
  2515  			host:        "api.test.com",
  2516  			wantSubsets: nil,
  2517  		},
  2518  	}
  2519  	for _, tt := range cases {
  2520  		t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.serviceNs), func(t *testing.T) {
  2521  			out := ps.destinationRule(tt.proxyNs,
  2522  				&Service{
  2523  					Hostname: host.Name(tt.host),
  2524  					Attributes: ServiceAttributes{
  2525  						Namespace: tt.serviceNs,
  2526  					},
  2527  				})
  2528  			if tt.wantSubsets == nil {
  2529  				if len(out) != 0 {
  2530  					t.Fatalf("proxy in %s namespace: unexpected dr found %+v", tt.proxyNs, out)
  2531  				}
  2532  				return
  2533  			}
  2534  
  2535  			if len(out) == 0 {
  2536  				t.Fatalf("proxy in %s namespace: dest rule is nil, expected subsets %+v", tt.proxyNs, tt.wantSubsets)
  2537  			}
  2538  			destRuleConfig := out[0]
  2539  			destRule := destRuleConfig.rule.Spec.(*networking.DestinationRule)
  2540  			var gotSubsets []string
  2541  			for _, ss := range destRule.Subsets {
  2542  				gotSubsets = append(gotSubsets, ss.Name)
  2543  			}
  2544  			if !reflect.DeepEqual(gotSubsets, tt.wantSubsets) {
  2545  				t.Fatalf("want %+v, got %+v", tt.wantSubsets, gotSubsets)
  2546  			}
  2547  		})
  2548  	}
  2549  }
  2550  
  2551  func TestVirtualServiceWithExportTo(t *testing.T) {
  2552  	ps := NewPushContext()
  2553  	env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})}
  2554  	ps.Mesh = env.Mesh()
  2555  	configStore := NewFakeStore()
  2556  	gatewayName := "default/gateway"
  2557  
  2558  	rule1 := config.Config{
  2559  		Meta: config.Meta{
  2560  			Name:             "rule1",
  2561  			Namespace:        "test1",
  2562  			GroupVersionKind: gvk.VirtualService,
  2563  		},
  2564  		Spec: &networking.VirtualService{
  2565  			Hosts:    []string{"rule1.com"},
  2566  			ExportTo: []string{".", "ns1"},
  2567  		},
  2568  	}
  2569  	rule2 := config.Config{
  2570  		Meta: config.Meta{
  2571  			Name:             "rule2",
  2572  			Namespace:        "test2",
  2573  			GroupVersionKind: gvk.VirtualService,
  2574  		},
  2575  		Spec: &networking.VirtualService{
  2576  			Hosts:    []string{"rule2.com"},
  2577  			ExportTo: []string{"test2", "ns1", "test1"},
  2578  		},
  2579  	}
  2580  	rule2Gw := config.Config{
  2581  		Meta: config.Meta{
  2582  			Name:             "rule2Gw",
  2583  			Namespace:        "test2",
  2584  			GroupVersionKind: gvk.VirtualService,
  2585  		},
  2586  		Spec: &networking.VirtualService{
  2587  			Gateways: []string{gatewayName, constants.IstioMeshGateway},
  2588  			Hosts:    []string{"rule2gw.com"},
  2589  			ExportTo: []string{"test2", "ns1", "test1"},
  2590  		},
  2591  	}
  2592  	rule3 := config.Config{
  2593  		Meta: config.Meta{
  2594  			Name:             "rule3",
  2595  			Namespace:        "test3",
  2596  			GroupVersionKind: gvk.VirtualService,
  2597  		},
  2598  		Spec: &networking.VirtualService{
  2599  			Gateways: []string{constants.IstioMeshGateway},
  2600  			Hosts:    []string{"rule3.com"},
  2601  			ExportTo: []string{"test1", "test2", "*"},
  2602  		},
  2603  	}
  2604  	rule3Gw := config.Config{
  2605  		Meta: config.Meta{
  2606  			Name:             "rule3Gw",
  2607  			Namespace:        "test3",
  2608  			GroupVersionKind: gvk.VirtualService,
  2609  		},
  2610  		Spec: &networking.VirtualService{
  2611  			Gateways: []string{gatewayName},
  2612  			Hosts:    []string{"rule3gw.com"},
  2613  			ExportTo: []string{"test1", "test2", "*"},
  2614  		},
  2615  	}
  2616  	rootNS := config.Config{
  2617  		Meta: config.Meta{
  2618  			Name:             "zzz",
  2619  			Namespace:        "zzz",
  2620  			GroupVersionKind: gvk.VirtualService,
  2621  		},
  2622  		Spec: &networking.VirtualService{
  2623  			Hosts: []string{"rootNS.com"},
  2624  		},
  2625  	}
  2626  
  2627  	for _, c := range []config.Config{rule1, rule2, rule3, rule2Gw, rule3Gw, rootNS} {
  2628  		if _, err := configStore.Create(c); err != nil {
  2629  			t.Fatalf("could not create %v", c.Name)
  2630  		}
  2631  	}
  2632  
  2633  	env.ConfigStore = configStore
  2634  	ps.initDefaultExportMaps()
  2635  	ps.initVirtualServices(env)
  2636  
  2637  	cases := []struct {
  2638  		proxyNs   string
  2639  		gateway   string
  2640  		wantHosts []string
  2641  	}{
  2642  		{
  2643  			proxyNs:   "test1",
  2644  			wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
  2645  			gateway:   constants.IstioMeshGateway,
  2646  		},
  2647  		{
  2648  			proxyNs:   "test2",
  2649  			wantHosts: []string{"rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
  2650  			gateway:   constants.IstioMeshGateway,
  2651  		},
  2652  		{
  2653  			proxyNs:   "ns1",
  2654  			wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"},
  2655  			gateway:   constants.IstioMeshGateway,
  2656  		},
  2657  		{
  2658  			proxyNs:   "random",
  2659  			wantHosts: []string{"rule3.com", "rootNS.com"},
  2660  			gateway:   constants.IstioMeshGateway,
  2661  		},
  2662  		{
  2663  			proxyNs:   "test1",
  2664  			wantHosts: []string{"rule2gw.com", "rule3gw.com"},
  2665  			gateway:   gatewayName,
  2666  		},
  2667  		{
  2668  			proxyNs:   "test2",
  2669  			wantHosts: []string{"rule2gw.com", "rule3gw.com"},
  2670  			gateway:   gatewayName,
  2671  		},
  2672  		{
  2673  			proxyNs:   "ns1",
  2674  			wantHosts: []string{"rule2gw.com", "rule3gw.com"},
  2675  			gateway:   gatewayName,
  2676  		},
  2677  		{
  2678  			proxyNs:   "random",
  2679  			wantHosts: []string{"rule3gw.com"},
  2680  			gateway:   gatewayName,
  2681  		},
  2682  	}
  2683  	for _, tt := range cases {
  2684  		t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.gateway), func(t *testing.T) {
  2685  			rules := ps.VirtualServicesForGateway(tt.proxyNs, tt.gateway)
  2686  			gotHosts := make([]string, 0)
  2687  			for _, r := range rules {
  2688  				vs := r.Spec.(*networking.VirtualService)
  2689  				gotHosts = append(gotHosts, vs.Hosts...)
  2690  			}
  2691  			if !reflect.DeepEqual(gotHosts, tt.wantHosts) {
  2692  				t.Errorf("want %+v, got %+v", tt.wantHosts, gotHosts)
  2693  			}
  2694  		})
  2695  	}
  2696  }
  2697  
  2698  func TestInitVirtualService(t *testing.T) {
  2699  	test.SetForTest(t, &features.FilterGatewayClusterConfig, true)
  2700  	ps := NewPushContext()
  2701  	env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})}
  2702  	ps.Mesh = env.Mesh()
  2703  	configStore := NewFakeStore()
  2704  	gatewayName := "ns1/gateway"
  2705  
  2706  	root := config.Config{
  2707  		Meta: config.Meta{
  2708  			GroupVersionKind: gvk.VirtualService,
  2709  			Name:             "root",
  2710  			Namespace:        "ns1",
  2711  		},
  2712  		Spec: &networking.VirtualService{
  2713  			ExportTo: []string{"*"},
  2714  			Hosts:    []string{"*.org"},
  2715  			Gateways: []string{"gateway"},
  2716  			Http: []*networking.HTTPRoute{
  2717  				{
  2718  					Match: []*networking.HTTPMatchRequest{
  2719  						{
  2720  							Uri: &networking.StringMatch{
  2721  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  2722  							},
  2723  						},
  2724  						{
  2725  							Uri: &networking.StringMatch{
  2726  								MatchType: &networking.StringMatch_Exact{Exact: "/login"},
  2727  							},
  2728  						},
  2729  					},
  2730  					Delegate: &networking.Delegate{
  2731  						Name:      "delegate",
  2732  						Namespace: "ns2",
  2733  					},
  2734  				},
  2735  			},
  2736  		},
  2737  	}
  2738  	delegate := config.Config{
  2739  		Meta: config.Meta{
  2740  			GroupVersionKind: gvk.VirtualService,
  2741  			Name:             "delegate",
  2742  			Namespace:        "ns2",
  2743  		},
  2744  		Spec: &networking.VirtualService{
  2745  			ExportTo: []string{"*"},
  2746  			Hosts:    []string{},
  2747  			Gateways: []string{gatewayName},
  2748  			Http: []*networking.HTTPRoute{
  2749  				{
  2750  					Route: []*networking.HTTPRouteDestination{
  2751  						{
  2752  							Destination: &networking.Destination{
  2753  								Host: "delegate",
  2754  								Port: &networking.PortSelector{
  2755  									Number: 80,
  2756  								},
  2757  							},
  2758  						},
  2759  					},
  2760  				},
  2761  			},
  2762  		},
  2763  	}
  2764  	public := config.Config{
  2765  		Meta: config.Meta{
  2766  			GroupVersionKind: gvk.VirtualService,
  2767  			Name:             "public",
  2768  			Namespace:        "ns3",
  2769  		},
  2770  		Spec: &networking.VirtualService{
  2771  			Hosts:    []string{"*.org"},
  2772  			Gateways: []string{gatewayName},
  2773  			Http: []*networking.HTTPRoute{
  2774  				{
  2775  					Route: []*networking.HTTPRouteDestination{
  2776  						{
  2777  							Destination: &networking.Destination{
  2778  								Host: "public",
  2779  								Port: &networking.PortSelector{
  2780  									Number: 80,
  2781  								},
  2782  							},
  2783  						},
  2784  					},
  2785  				},
  2786  			},
  2787  		},
  2788  	}
  2789  	private := config.Config{
  2790  		Meta: config.Meta{
  2791  			GroupVersionKind: gvk.VirtualService,
  2792  			Name:             "private",
  2793  			Namespace:        "ns1",
  2794  		},
  2795  		Spec: &networking.VirtualService{
  2796  			ExportTo: []string{".", "ns2"},
  2797  			Hosts:    []string{"*.org"},
  2798  			Gateways: []string{gatewayName},
  2799  			Http: []*networking.HTTPRoute{
  2800  				{
  2801  					Route: []*networking.HTTPRouteDestination{
  2802  						{
  2803  							Destination: &networking.Destination{
  2804  								Host: "private",
  2805  								Port: &networking.PortSelector{
  2806  									Number: 80,
  2807  								},
  2808  							},
  2809  						},
  2810  					},
  2811  				},
  2812  			},
  2813  		},
  2814  	}
  2815  	invisible := config.Config{
  2816  		Meta: config.Meta{
  2817  			GroupVersionKind: gvk.VirtualService,
  2818  			Name:             "invisible",
  2819  			Namespace:        "ns5",
  2820  		},
  2821  		Spec: &networking.VirtualService{
  2822  			ExportTo: []string{".", "ns3"},
  2823  			Hosts:    []string{"*.org"},
  2824  			Gateways: []string{"gateway", "mesh"},
  2825  			Http: []*networking.HTTPRoute{
  2826  				{
  2827  					Route: []*networking.HTTPRouteDestination{
  2828  						{
  2829  							Destination: &networking.Destination{
  2830  								Host: "invisible",
  2831  								Port: &networking.PortSelector{
  2832  									Number: 80,
  2833  								},
  2834  							},
  2835  						},
  2836  					},
  2837  				},
  2838  			},
  2839  		},
  2840  	}
  2841  
  2842  	for _, c := range []config.Config{root, delegate, public, private, invisible} {
  2843  		if _, err := configStore.Create(c); err != nil {
  2844  			t.Fatalf("could not create %v", c.Name)
  2845  		}
  2846  	}
  2847  
  2848  	env.ConfigStore = configStore
  2849  	ps.initDefaultExportMaps()
  2850  	ps.initVirtualServices(env)
  2851  
  2852  	t.Run("resolve shortname", func(t *testing.T) {
  2853  		rules := ps.VirtualServicesForGateway("ns1", gatewayName)
  2854  		if len(rules) != 3 {
  2855  			t.Fatalf("wanted 3 virtualservice for gateway %s, actually got %d", gatewayName, len(rules))
  2856  		}
  2857  		gotHTTPHosts := make([]string, 0)
  2858  		for _, r := range rules {
  2859  			vs := r.Spec.(*networking.VirtualService)
  2860  			for _, route := range vs.GetHttp() {
  2861  				for _, dst := range route.Route {
  2862  					gotHTTPHosts = append(gotHTTPHosts, dst.Destination.Host)
  2863  				}
  2864  			}
  2865  		}
  2866  		if !reflect.DeepEqual(gotHTTPHosts, []string{"private.ns1", "public.ns3", "delegate.ns2"}) {
  2867  			t.Errorf("got %+v", gotHTTPHosts)
  2868  		}
  2869  	})
  2870  
  2871  	t.Run("destinations by gateway", func(t *testing.T) {
  2872  		got := ps.virtualServiceIndex.destinationsByGateway
  2873  		want := map[string]sets.String{
  2874  			gatewayName:   sets.New("delegate.ns2", "public.ns3", "private.ns1"),
  2875  			"ns5/gateway": sets.New("invisible.ns5"),
  2876  		}
  2877  		if !reflect.DeepEqual(got, want) {
  2878  			t.Errorf("destinationsByGateway: got %+v", got)
  2879  		}
  2880  	})
  2881  }
  2882  
  2883  func TestServiceWithExportTo(t *testing.T) {
  2884  	ps := NewPushContext()
  2885  	env := NewEnvironment()
  2886  	env.Watcher = mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})
  2887  	ps.Mesh = env.Mesh()
  2888  
  2889  	svc1 := &Service{
  2890  		Hostname: "svc1",
  2891  		Attributes: ServiceAttributes{
  2892  			Namespace: "test1",
  2893  			ExportTo:  sets.New(visibility.Private, visibility.Instance("ns1")),
  2894  		},
  2895  	}
  2896  	svc2 := &Service{
  2897  		Hostname: "svc2",
  2898  		Attributes: ServiceAttributes{
  2899  			Namespace: "test2",
  2900  			ExportTo: sets.New(
  2901  				visibility.Instance("test1"),
  2902  				visibility.Instance("ns1"),
  2903  				visibility.Instance("test2"),
  2904  			),
  2905  		},
  2906  	}
  2907  	svc3 := &Service{
  2908  		Hostname: "svc3",
  2909  		Attributes: ServiceAttributes{
  2910  			Namespace: "test3",
  2911  			ExportTo: sets.New(
  2912  				visibility.Instance("test1"),
  2913  				visibility.Public,
  2914  				visibility.Instance("test2"),
  2915  			),
  2916  		},
  2917  	}
  2918  	svc4 := &Service{
  2919  		Hostname: "svc4",
  2920  		Attributes: ServiceAttributes{
  2921  			Namespace:       "test4",
  2922  			ServiceRegistry: provider.External,
  2923  		},
  2924  	}
  2925  	svc4_1 := &Service{
  2926  		Hostname: "svc4",
  2927  		Attributes: ServiceAttributes{
  2928  			Namespace:       "test4",
  2929  			ServiceRegistry: provider.External,
  2930  		},
  2931  	}
  2932  	// kubernetes service will override non kubernetes
  2933  	svc4_2 := &Service{
  2934  		Hostname: "svc4",
  2935  		Attributes: ServiceAttributes{
  2936  			Namespace:       "test4",
  2937  			ServiceRegistry: provider.Kubernetes,
  2938  		},
  2939  	}
  2940  	env.ServiceDiscovery = &localServiceDiscovery{
  2941  		services: []*Service{svc1, svc2, svc3, svc4, svc4_1, svc4_2},
  2942  	}
  2943  	ps.initDefaultExportMaps()
  2944  	ps.initServiceRegistry(env, nil)
  2945  	assert.Equal(t, ps.ServiceIndex.HostnameAndNamespace[svc4.Hostname][svc4.Attributes.Namespace].Attributes.ServiceRegistry, provider.Kubernetes)
  2946  	cases := []struct {
  2947  		proxyNs   string
  2948  		wantHosts []string
  2949  	}{
  2950  		{
  2951  			proxyNs:   "test1",
  2952  			wantHosts: []string{"svc1", "svc2", "svc3", "svc4", "svc4", "svc4"},
  2953  		},
  2954  		{
  2955  			proxyNs:   "test2",
  2956  			wantHosts: []string{"svc2", "svc3", "svc4", "svc4", "svc4"},
  2957  		},
  2958  		{
  2959  			proxyNs:   "ns1",
  2960  			wantHosts: []string{"svc1", "svc2", "svc3", "svc4", "svc4", "svc4"},
  2961  		},
  2962  		{
  2963  			proxyNs:   "random",
  2964  			wantHosts: []string{"svc3", "svc4", "svc4", "svc4"},
  2965  		},
  2966  	}
  2967  	for _, tt := range cases {
  2968  		services := ps.servicesExportedToNamespace(tt.proxyNs)
  2969  		gotHosts := make([]string, 0)
  2970  		for _, r := range services {
  2971  			gotHosts = append(gotHosts, string(r.Hostname))
  2972  		}
  2973  		if !reflect.DeepEqual(gotHosts, tt.wantHosts) {
  2974  			t.Errorf("proxy in %s namespace: want %+v, got %+v", tt.proxyNs, tt.wantHosts, gotHosts)
  2975  		}
  2976  	}
  2977  }
  2978  
  2979  func TestInstancesByPort(t *testing.T) {
  2980  	ps := NewPushContext()
  2981  	env := NewEnvironment()
  2982  	env.Watcher = mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})
  2983  	ps.Mesh = env.Mesh()
  2984  
  2985  	// Test the Service Entry merge with same host with different generates
  2986  	// correct instances by port.
  2987  	svc5_1 := &Service{
  2988  		Hostname: "svc5",
  2989  		Attributes: ServiceAttributes{
  2990  			Namespace:       "test5",
  2991  			ServiceRegistry: provider.External,
  2992  			ExportTo: sets.New(
  2993  				visibility.Instance("test5"),
  2994  			),
  2995  		},
  2996  		Ports:      port7000,
  2997  		Resolution: DNSLB,
  2998  	}
  2999  	svc5_2 := &Service{
  3000  		Hostname: "svc5",
  3001  		Attributes: ServiceAttributes{
  3002  			Namespace:       "test5",
  3003  			ServiceRegistry: provider.External,
  3004  			ExportTo: sets.New(
  3005  				visibility.Instance("test5"),
  3006  			),
  3007  		},
  3008  		Ports:      port8000,
  3009  		Resolution: DNSLB,
  3010  	}
  3011  
  3012  	env.ServiceDiscovery = &localServiceDiscovery{
  3013  		services: []*Service{svc5_1, svc5_2},
  3014  	}
  3015  
  3016  	env.EndpointIndex.shardsBySvc = map[string]map[string]*EndpointShards{
  3017  		svc5_1.Hostname.String(): {
  3018  			svc5_1.Attributes.Namespace: {
  3019  				Shards: map[ShardKey][]*IstioEndpoint{
  3020  					{Cluster: "Kubernets", Provider: provider.External}: {
  3021  						&IstioEndpoint{
  3022  							Address:         "1.1.1.1",
  3023  							EndpointPort:    7000,
  3024  							ServicePortName: "uds",
  3025  						},
  3026  						&IstioEndpoint{
  3027  							Address:         "1.1.1.2",
  3028  							EndpointPort:    8000,
  3029  							ServicePortName: "uds",
  3030  						},
  3031  					},
  3032  				},
  3033  			},
  3034  		},
  3035  	}
  3036  
  3037  	ps.initServiceRegistry(env, nil)
  3038  	instancesByPort := ps.ServiceIndex.instancesByPort[svc5_1.Key()]
  3039  	assert.Equal(t, len(instancesByPort), 2)
  3040  }
  3041  
  3042  func TestGetHostsFromMeshConfig(t *testing.T) {
  3043  	ps := NewPushContext()
  3044  	env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{
  3045  		RootNamespace: "istio-system",
  3046  		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
  3047  			{
  3048  				Name: "otel",
  3049  				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls{
  3050  					EnvoyOtelAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider{
  3051  						Service: "otel.foo.svc.cluster.local",
  3052  						Port:    9811,
  3053  					},
  3054  				},
  3055  			},
  3056  		},
  3057  		DefaultProviders: &meshconfig.MeshConfig_DefaultProviders{
  3058  			AccessLogging: []string{"otel"},
  3059  		},
  3060  	})}
  3061  	ps.Mesh = env.Mesh()
  3062  	configStore := NewFakeStore()
  3063  	gatewayName := "ns1/gateway"
  3064  
  3065  	vs1 := config.Config{
  3066  		Meta: config.Meta{
  3067  			GroupVersionKind: gvk.VirtualService,
  3068  			Name:             "vs1",
  3069  			Namespace:        "ns1",
  3070  		},
  3071  		Spec: &networking.VirtualService{
  3072  			Hosts:    []string{"*.org"},
  3073  			Gateways: []string{"gateway"},
  3074  			Http: []*networking.HTTPRoute{
  3075  				{
  3076  					Match: []*networking.HTTPMatchRequest{
  3077  						{
  3078  							Uri: &networking.StringMatch{
  3079  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  3080  							},
  3081  						},
  3082  						{
  3083  							Uri: &networking.StringMatch{
  3084  								MatchType: &networking.StringMatch_Exact{Exact: "/login"},
  3085  							},
  3086  						},
  3087  					},
  3088  					Delegate: &networking.Delegate{
  3089  						Name:      "vs2",
  3090  						Namespace: "ns2",
  3091  					},
  3092  				},
  3093  			},
  3094  		},
  3095  	}
  3096  	vs2 := config.Config{
  3097  		Meta: config.Meta{
  3098  			GroupVersionKind: gvk.VirtualService,
  3099  			Name:             "vs2",
  3100  			Namespace:        "ns2",
  3101  		},
  3102  		Spec: &networking.VirtualService{
  3103  			Hosts:    []string{},
  3104  			Gateways: []string{gatewayName},
  3105  			Http: []*networking.HTTPRoute{
  3106  				{
  3107  					Route: []*networking.HTTPRouteDestination{},
  3108  				},
  3109  			},
  3110  		},
  3111  	}
  3112  
  3113  	for _, c := range []config.Config{vs1, vs2} {
  3114  		if _, err := configStore.Create(c); err != nil {
  3115  			t.Fatalf("could not create %v", c.Name)
  3116  		}
  3117  	}
  3118  
  3119  	env.ConfigStore = configStore
  3120  	test.SetForTest(t, &features.FilterGatewayClusterConfig, true)
  3121  	ps.initTelemetry(env)
  3122  	ps.initDefaultExportMaps()
  3123  	ps.initVirtualServices(env)
  3124  	assert.Equal(t, ps.virtualServiceIndex.destinationsByGateway[gatewayName], sets.String{})
  3125  	assert.Equal(t, ps.extraServicesForProxy(nil), sets.New("otel.foo.svc.cluster.local"))
  3126  }
  3127  
  3128  func TestWellKnownProvidersCount(t *testing.T) {
  3129  	msg := &meshconfig.MeshConfig_ExtensionProvider{}
  3130  	pb := msg.ProtoReflect()
  3131  	md := pb.Descriptor()
  3132  
  3133  	found := sets.New[string]()
  3134  	for i := 0; i < md.Oneofs().Get(0).Fields().Len(); i++ {
  3135  		found.Insert(string(md.Oneofs().Get(0).Fields().Get(i).Name()))
  3136  	}
  3137  	// If this fails, there is a provider added that we have not handled.
  3138  	// DO NOT JUST
  3139  	assert.Equal(t, found, wellknownProviders)
  3140  }
  3141  
  3142  // TestGetHostsFromMeshConfigExhaustiveness exhaustiveness check of `getHostsFromMeshConfig`
  3143  // Once some one add a new `Provider` in api, we should update `wellknownProviders` and
  3144  // implements of `getHostsFromMeshConfig`
  3145  func TestGetHostsFromMeshConfigExhaustiveness(t *testing.T) {
  3146  	AssertProvidersHandled(addHostsFromMeshConfigProvidersHandled)
  3147  	unexpectedProviders := make([]string, 0)
  3148  	msg := &meshconfig.MeshConfig_ExtensionProvider{}
  3149  	pb := msg.ProtoReflect()
  3150  	md := pb.Descriptor()
  3151  
  3152  	// We only consider ones with `service` field
  3153  	wellknownProviders := wellknownProviders.Copy().DeleteAll("prometheus", "stackdriver", "envoy_file_access_log")
  3154  	of := md.Oneofs().Get(0)
  3155  	for i := 0; i < of.Fields().Len(); i++ {
  3156  		o := of.Fields().Get(i)
  3157  		if o.Message().Fields().ByName("service") != nil {
  3158  			n := string(o.Name())
  3159  			if _, ok := wellknownProviders[n]; ok {
  3160  				delete(wellknownProviders, n)
  3161  			} else {
  3162  				unexpectedProviders = append(unexpectedProviders, n)
  3163  			}
  3164  		}
  3165  	}
  3166  
  3167  	if len(wellknownProviders) != 0 || len(unexpectedProviders) != 0 {
  3168  		t.Errorf("unexpected provider not implemented in getHostsFromMeshConfig: %v, %v", wellknownProviders, unexpectedProviders)
  3169  		t.Fail()
  3170  	}
  3171  }
  3172  
  3173  var _ ServiceDiscovery = &localServiceDiscovery{}
  3174  
  3175  // localServiceDiscovery is an in-memory ServiceDiscovery with mock services
  3176  type localServiceDiscovery struct {
  3177  	services         []*Service
  3178  	serviceInstances []*ServiceInstance
  3179  
  3180  	NoopAmbientIndexes
  3181  	NetworkGatewaysHandler
  3182  }
  3183  
  3184  var _ ServiceDiscovery = &localServiceDiscovery{}
  3185  
  3186  func (l *localServiceDiscovery) Services() []*Service {
  3187  	return l.services
  3188  }
  3189  
  3190  func (l *localServiceDiscovery) GetService(host.Name) *Service {
  3191  	panic("implement me")
  3192  }
  3193  
  3194  func (l *localServiceDiscovery) GetProxyServiceTargets(*Proxy) []ServiceTarget {
  3195  	panic("implement me")
  3196  }
  3197  
  3198  func (l *localServiceDiscovery) GetProxyWorkloadLabels(*Proxy) labels.Instance {
  3199  	panic("implement me")
  3200  }
  3201  
  3202  func (l *localServiceDiscovery) GetIstioServiceAccounts(*Service) []string {
  3203  	return nil
  3204  }
  3205  
  3206  func (l *localServiceDiscovery) NetworkGateways() []NetworkGateway {
  3207  	// TODO implement fromRegistry logic from kube controller if needed
  3208  	return nil
  3209  }
  3210  
  3211  func (l *localServiceDiscovery) MCSServices() []MCSServiceInfo {
  3212  	return nil
  3213  }
  3214  
  3215  func TestResolveServiceAliases(t *testing.T) {
  3216  	type service struct {
  3217  		Name         host.Name
  3218  		Aliases      host.Names
  3219  		ExternalName string
  3220  	}
  3221  	tests := []struct {
  3222  		name   string
  3223  		input  []service
  3224  		output []service
  3225  	}{
  3226  		{
  3227  			name:   "no aliases",
  3228  			input:  []service{{Name: "test"}},
  3229  			output: []service{{Name: "test"}},
  3230  		},
  3231  		{
  3232  			name: "simple alias",
  3233  			input: []service{
  3234  				{Name: "concrete"},
  3235  				{Name: "alias", ExternalName: "concrete"},
  3236  			},
  3237  			output: []service{
  3238  				{Name: "concrete", Aliases: host.Names{"alias"}},
  3239  				{Name: "alias", ExternalName: "concrete"},
  3240  			},
  3241  		},
  3242  		{
  3243  			name: "multiple alias",
  3244  			input: []service{
  3245  				{Name: "concrete"},
  3246  				{Name: "alias1", ExternalName: "concrete"},
  3247  				{Name: "alias2", ExternalName: "concrete"},
  3248  			},
  3249  			output: []service{
  3250  				{Name: "concrete", Aliases: host.Names{"alias1", "alias2"}},
  3251  				{Name: "alias1", ExternalName: "concrete"},
  3252  				{Name: "alias2", ExternalName: "concrete"},
  3253  			},
  3254  		},
  3255  		{
  3256  			name: "chained alias",
  3257  			input: []service{
  3258  				{Name: "concrete"},
  3259  				{Name: "alias1", ExternalName: "alias2"},
  3260  				{Name: "alias2", ExternalName: "concrete"},
  3261  			},
  3262  			output: []service{
  3263  				{Name: "concrete", Aliases: host.Names{"alias1", "alias2"}},
  3264  				{Name: "alias1", ExternalName: "alias2"},
  3265  				{Name: "alias2", ExternalName: "concrete"},
  3266  			},
  3267  		},
  3268  		{
  3269  			name: "looping alias",
  3270  			input: []service{
  3271  				{Name: "alias1", ExternalName: "alias2"},
  3272  				{Name: "alias2", ExternalName: "alias1"},
  3273  			},
  3274  			output: []service{
  3275  				{Name: "alias1", ExternalName: "alias2"},
  3276  				{Name: "alias2", ExternalName: "alias1"},
  3277  			},
  3278  		},
  3279  	}
  3280  	for _, tt := range tests {
  3281  		t.Run(tt.name, func(t *testing.T) {
  3282  			inps := slices.Map(tt.input, func(e service) *Service {
  3283  				resolution := ClientSideLB
  3284  				if e.ExternalName != "" {
  3285  					resolution = Alias
  3286  				}
  3287  				return &Service{
  3288  					Resolution: resolution,
  3289  					Attributes: ServiceAttributes{
  3290  						K8sAttributes: K8sAttributes{ExternalName: e.ExternalName},
  3291  					},
  3292  					Hostname: e.Name,
  3293  				}
  3294  			})
  3295  			resolveServiceAliases(inps, nil)
  3296  			out := slices.Map(inps, func(e *Service) service {
  3297  				return service{
  3298  					Name: e.Hostname,
  3299  					Aliases: slices.Map(e.Attributes.Aliases, func(e NamespacedHostname) host.Name {
  3300  						return e.Hostname
  3301  					}),
  3302  					ExternalName: e.Attributes.K8sAttributes.ExternalName,
  3303  				}
  3304  			})
  3305  			assert.Equal(t, tt.output, out)
  3306  		})
  3307  	}
  3308  }
  3309  
  3310  func BenchmarkInitServiceAccounts(b *testing.B) {
  3311  	ps := NewPushContext()
  3312  	index := NewEndpointIndex(DisabledCache{})
  3313  	env := &Environment{EndpointIndex: index}
  3314  	ps.Mesh = &meshconfig.MeshConfig{TrustDomainAliases: []string{"td1", "td2"}}
  3315  
  3316  	services := []*Service{
  3317  		{
  3318  			Hostname: "svc-unset",
  3319  			Ports:    allPorts,
  3320  			Attributes: ServiceAttributes{
  3321  				Namespace: "test1",
  3322  			},
  3323  		},
  3324  		{
  3325  			Hostname: "svc-public",
  3326  			Ports:    allPorts,
  3327  			Attributes: ServiceAttributes{
  3328  				Namespace: "test1",
  3329  				ExportTo:  sets.New(visibility.Public),
  3330  			},
  3331  		},
  3332  		{
  3333  			Hostname: "svc-private",
  3334  			Ports:    allPorts,
  3335  			Attributes: ServiceAttributes{
  3336  				Namespace: "test1",
  3337  				ExportTo:  sets.New(visibility.Private),
  3338  			},
  3339  		},
  3340  		{
  3341  			Hostname: "svc-none",
  3342  			Ports:    allPorts,
  3343  			Attributes: ServiceAttributes{
  3344  				Namespace: "test1",
  3345  				ExportTo:  sets.New(visibility.None),
  3346  			},
  3347  		},
  3348  		{
  3349  			Hostname: "svc-namespace",
  3350  			Ports:    allPorts,
  3351  			Attributes: ServiceAttributes{
  3352  				Namespace: "test1",
  3353  				ExportTo:  sets.New(visibility.Instance("namespace")),
  3354  			},
  3355  		},
  3356  	}
  3357  
  3358  	for _, svc := range services {
  3359  		if index.shardsBySvc[string(svc.Hostname)] == nil {
  3360  			index.shardsBySvc[string(svc.Hostname)] = map[string]*EndpointShards{}
  3361  		}
  3362  		index.shardsBySvc[string(svc.Hostname)][svc.Attributes.Namespace] = &EndpointShards{
  3363  			ServiceAccounts: sets.New("spiffe://cluster.local/ns/def/sa/sa1", "spiffe://cluster.local/ns/def/sa/sa2"),
  3364  		}
  3365  	}
  3366  	b.ResetTimer()
  3367  	for n := 0; n < b.N; n++ {
  3368  		ps.initServiceAccounts(env, services)
  3369  	}
  3370  }