istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/httproute_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 core
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  	"time"
    22  
    23  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    24  	route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    25  	statefulsession "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3"
    26  	cookiev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3"
    27  	headerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/header/v3"
    28  	httpv3 "github.com/envoyproxy/go-control-plane/envoy/type/http/v3"
    29  
    30  	meshapi "istio.io/api/mesh/v1alpha1"
    31  	networking "istio.io/api/networking/v1alpha3"
    32  	"istio.io/istio/pilot/pkg/features"
    33  	"istio.io/istio/pilot/pkg/model"
    34  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    35  	"istio.io/istio/pilot/pkg/util/protoconv"
    36  	"istio.io/istio/pilot/test/xdstest"
    37  	"istio.io/istio/pkg/cluster"
    38  	"istio.io/istio/pkg/config"
    39  	"istio.io/istio/pkg/config/host"
    40  	"istio.io/istio/pkg/config/mesh"
    41  	"istio.io/istio/pkg/config/protocol"
    42  	"istio.io/istio/pkg/config/schema/gvk"
    43  	"istio.io/istio/pkg/config/visibility"
    44  	"istio.io/istio/pkg/test"
    45  	"istio.io/istio/pkg/test/util/assert"
    46  	"istio.io/istio/pkg/util/sets"
    47  )
    48  
    49  func TestGenerateVirtualHostDomains(t *testing.T) {
    50  	cases := []struct {
    51  		name            string
    52  		service         *model.Service
    53  		port            int
    54  		node            *model.Proxy
    55  		want            []string
    56  		enableDualStack bool
    57  	}{
    58  		{
    59  			name: "same domain",
    60  			service: &model.Service{
    61  				Hostname:     "foo.local.campus.net",
    62  				MeshExternal: false,
    63  			},
    64  			port: 80,
    65  			node: &model.Proxy{
    66  				DNSDomain: "local.campus.net",
    67  			},
    68  			want: []string{
    69  				"foo.local.campus.net",
    70  				"foo",
    71  			},
    72  		},
    73  		{
    74  			name: "different domains with some shared dns",
    75  			service: &model.Service{
    76  				Hostname:     "foo.local.campus.net",
    77  				MeshExternal: false,
    78  			},
    79  			port: 80,
    80  			node: &model.Proxy{
    81  				DNSDomain: "remote.campus.net",
    82  			},
    83  			want: []string{
    84  				"foo.local.campus.net",
    85  				"foo.local",
    86  				"foo.local.campus",
    87  			},
    88  		},
    89  		{
    90  			name: "different domains with no shared dns",
    91  			service: &model.Service{
    92  				Hostname:     "foo.local.campus.net",
    93  				MeshExternal: false,
    94  			},
    95  			port: 80,
    96  			node: &model.Proxy{
    97  				DNSDomain: "example.com",
    98  			},
    99  			want: []string{"foo.local.campus.net"},
   100  		},
   101  		{
   102  			name: "k8s service with default domain",
   103  			service: &model.Service{
   104  				Hostname:     "echo.default.svc.cluster.local",
   105  				MeshExternal: false,
   106  			},
   107  			port: 8123,
   108  			node: &model.Proxy{
   109  				DNSDomain: "default.svc.cluster.local",
   110  			},
   111  			want: []string{
   112  				"echo.default.svc.cluster.local",
   113  				"echo",
   114  				"echo.default.svc",
   115  				"echo.default",
   116  			},
   117  		},
   118  		{
   119  			name: "non-k8s service",
   120  			service: &model.Service{
   121  				Hostname:     "foo.default.svc.bar.baz",
   122  				MeshExternal: false,
   123  			},
   124  			port: 8123,
   125  			node: &model.Proxy{
   126  				DNSDomain: "default.svc.cluster.local",
   127  			},
   128  			want: []string{
   129  				"foo.default.svc.bar.baz",
   130  			},
   131  		},
   132  		{
   133  			name: "k8s service with default domain and different namespace",
   134  			service: &model.Service{
   135  				Hostname:     "echo.default.svc.cluster.local",
   136  				MeshExternal: false,
   137  			},
   138  			port: 8123,
   139  			node: &model.Proxy{
   140  				DNSDomain: "mesh.svc.cluster.local",
   141  			},
   142  			want: []string{
   143  				"echo.default.svc.cluster.local",
   144  				"echo.default",
   145  				"echo.default.svc",
   146  			},
   147  		},
   148  		{
   149  			name: "k8s service with custom domain 2",
   150  			service: &model.Service{
   151  				Hostname:     "google.local",
   152  				MeshExternal: false,
   153  			},
   154  			port: 8123,
   155  			node: &model.Proxy{
   156  				DNSDomain: "foo.svc.custom.k8s.local",
   157  			},
   158  			want: []string{"google.local"},
   159  		},
   160  		{
   161  			name: "ipv4 domain",
   162  			service: &model.Service{
   163  				Hostname:     "1.2.3.4",
   164  				MeshExternal: false,
   165  			},
   166  			port: 8123,
   167  			node: &model.Proxy{
   168  				DNSDomain: "example.com",
   169  			},
   170  			want: []string{"1.2.3.4"},
   171  		},
   172  		{
   173  			name: "ipv6 domain",
   174  			service: &model.Service{
   175  				Hostname:     "2406:3003:2064:35b8:864:a648:4b96:e37d",
   176  				MeshExternal: false,
   177  			},
   178  			port: 8123,
   179  			node: &model.Proxy{
   180  				DNSDomain: "example.com",
   181  			},
   182  			want: []string{"[2406:3003:2064:35b8:864:a648:4b96:e37d]"},
   183  		},
   184  		{
   185  			name: "back subset of cluster domain in address",
   186  			service: &model.Service{
   187  				Hostname:     "aaa.example.local",
   188  				MeshExternal: true,
   189  			},
   190  			port: 7777,
   191  			node: &model.Proxy{
   192  				DNSDomain: "tests.svc.cluster.local",
   193  			},
   194  			want: []string{"aaa.example.local"},
   195  		},
   196  		{
   197  			name: "front subset of cluster domain in address",
   198  			service: &model.Service{
   199  				Hostname:     "aaa.example.my",
   200  				MeshExternal: true,
   201  			},
   202  			port: 7777,
   203  			node: &model.Proxy{
   204  				DNSDomain: "tests.svc.my.long.domain.suffix",
   205  			},
   206  			want: []string{"aaa.example.my"},
   207  		},
   208  		{
   209  			name: "large subset of cluster domain in address",
   210  			service: &model.Service{
   211  				Hostname:     "aaa.example.my.long",
   212  				MeshExternal: true,
   213  			},
   214  			port: 7777,
   215  			node: &model.Proxy{
   216  				DNSDomain: "tests.svc.my.long.domain.suffix",
   217  			},
   218  			want: []string{"aaa.example.my.long"},
   219  		},
   220  		{
   221  			name: "no overlap of cluster domain in address",
   222  			service: &model.Service{
   223  				Hostname:     "aaa.example.com",
   224  				MeshExternal: true,
   225  			},
   226  			port: 7777,
   227  			node: &model.Proxy{
   228  				DNSDomain: "tests.svc.cluster.local",
   229  			},
   230  			want: []string{"aaa.example.com"},
   231  		},
   232  		{
   233  			name: "wildcard",
   234  			service: &model.Service{
   235  				Hostname:     "headless.default.svc.cluster.local",
   236  				MeshExternal: true,
   237  				Resolution:   model.Passthrough,
   238  				Attributes:   model.ServiceAttributes{ServiceRegistry: provider.Kubernetes},
   239  			},
   240  			port: 7777,
   241  			node: &model.Proxy{
   242  				DNSDomain: "default.svc.cluster.local",
   243  			},
   244  			want: []string{
   245  				"headless.default.svc.cluster.local",
   246  				"headless",
   247  				"headless.default.svc",
   248  				"headless.default",
   249  				"*.headless.default.svc.cluster.local",
   250  				"*.headless",
   251  				"*.headless.default.svc",
   252  				"*.headless.default",
   253  			},
   254  		},
   255  		{
   256  			name: "dual stack k8s service with default domain",
   257  			service: &model.Service{
   258  				Hostname:       "echo.default.svc.cluster.local",
   259  				MeshExternal:   false,
   260  				DefaultAddress: "1.2.3.4",
   261  				ClusterVIPs: model.AddressMap{
   262  					Addresses: map[cluster.ID][]string{
   263  						"cluster-1": {"1.2.3.4", "2406:3003:2064:35b8:864:a648:4b96:e37d"},
   264  						"cluster-2": {"4.3.2.1"}, // ensure other clusters aren't being populated in domains slice
   265  					},
   266  				},
   267  			},
   268  			port: 8123,
   269  			node: &model.Proxy{
   270  				DNSDomain: "default.svc.cluster.local",
   271  				Metadata: &model.NodeMetadata{
   272  					ClusterID: "cluster-1",
   273  				},
   274  			},
   275  			want: []string{
   276  				"echo.default.svc.cluster.local",
   277  				"echo",
   278  				"echo.default.svc",
   279  				"echo.default",
   280  				"1.2.3.4",
   281  				"[2406:3003:2064:35b8:864:a648:4b96:e37d]",
   282  			},
   283  			enableDualStack: true,
   284  		},
   285  		{
   286  			name: "alias",
   287  			service: &model.Service{
   288  				Hostname:     "foo.local.campus.net",
   289  				MeshExternal: false,
   290  				Attributes:   model.ServiceAttributes{Aliases: []model.NamespacedHostname{{Hostname: "alias.local.campus.net", Namespace: "ns"}}},
   291  			},
   292  			port: 80,
   293  			node: &model.Proxy{
   294  				DNSDomain: "local.campus.net",
   295  			},
   296  			want: []string{
   297  				"foo.local.campus.net",
   298  				"foo",
   299  				"alias.local.campus.net",
   300  				"alias",
   301  			},
   302  		},
   303  	}
   304  
   305  	testFn := func(t test.Failer, service *model.Service, port int, node *model.Proxy, want []string) {
   306  		out, _ := generateVirtualHostDomains(service, port, port, node)
   307  		assert.Equal(t, out, want)
   308  	}
   309  
   310  	for _, c := range cases {
   311  		c := c
   312  		t.Run(c.name, func(t *testing.T) {
   313  			test.SetForTest[bool](t, &features.EnableDualStack, c.enableDualStack)
   314  			testFn(t, c.service, c.port, c.node, c.want)
   315  		})
   316  	}
   317  }
   318  
   319  func TestSidecarOutboundHTTPRouteConfigWithDuplicateHosts(t *testing.T) {
   320  	virtualServiceSpec := &networking.VirtualService{
   321  		Hosts:    []string{"test-duplicate-domains.default.svc.cluster.local", "test-duplicate-domains.default"},
   322  		Gateways: []string{"mesh"},
   323  		Http: []*networking.HTTPRoute{
   324  			{
   325  				Route: []*networking.HTTPRouteDestination{
   326  					{
   327  						Destination: &networking.Destination{
   328  							Host: "test-duplicate-domains.default",
   329  						},
   330  					},
   331  				},
   332  			},
   333  		},
   334  	}
   335  	virtualServiceSpecDuplicate := &networking.VirtualService{
   336  		Hosts:    []string{"Test-duplicate-domains.default.svc.cluster.local"},
   337  		Gateways: []string{"mesh"},
   338  		Http: []*networking.HTTPRoute{
   339  			{
   340  				Route: []*networking.HTTPRouteDestination{
   341  					{
   342  						Destination: &networking.Destination{
   343  							Host: "test-duplicate-domains.default",
   344  						},
   345  					},
   346  				},
   347  			},
   348  		},
   349  	}
   350  	virtualServiceSpecDefault := &networking.VirtualService{
   351  		Hosts:    []string{"test.default"},
   352  		Gateways: []string{"mesh"},
   353  		Http: []*networking.HTTPRoute{
   354  			{
   355  				Route: []*networking.HTTPRouteDestination{
   356  					{
   357  						Destination: &networking.Destination{
   358  							Host: "test.default",
   359  						},
   360  					},
   361  				},
   362  			},
   363  		},
   364  	}
   365  
   366  	cases := []struct {
   367  		name                string
   368  		services            []*model.Service
   369  		config              []config.Config
   370  		expectedHosts       map[string][]string
   371  		expectedDestination map[string]string
   372  	}{
   373  		{
   374  			"more exact first",
   375  			[]*model.Service{
   376  				buildHTTPService("test.local", visibility.Public, "", "default", 80),
   377  				buildHTTPService("test", visibility.Public, "", "default", 80),
   378  			},
   379  			nil,
   380  			map[string][]string{
   381  				"allow_any": {"*"},
   382  				// BUG: test should be below
   383  				"test.local:80": {"test.local"},
   384  				"test:80":       {"test"},
   385  			},
   386  			map[string]string{
   387  				"allow_any":     "PassthroughCluster",
   388  				"test.local:80": "outbound|80||test.local",
   389  				"test:80":       "outbound|80||test",
   390  			},
   391  		},
   392  		{
   393  			"more exact first with full cluster domain",
   394  			[]*model.Service{
   395  				buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80),
   396  				buildHTTPService("test", visibility.Public, "", "default", 80),
   397  			},
   398  			nil,
   399  			map[string][]string{
   400  				"allow_any": {"*"},
   401  				"test.default.svc.cluster.local:80": {
   402  					"test.default.svc.cluster.local",
   403  					"test.default.svc",
   404  					"test.default",
   405  				},
   406  				"test:80": {"test"},
   407  			},
   408  			map[string]string{
   409  				"allow_any":                         "PassthroughCluster",
   410  				"test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local",
   411  				"test:80":                           "outbound|80||test",
   412  			},
   413  		},
   414  		{
   415  			"more exact second",
   416  			[]*model.Service{
   417  				buildHTTPService("test", visibility.Public, "", "default", 80),
   418  				buildHTTPService("test.local", visibility.Public, "", "default", 80),
   419  			},
   420  			nil,
   421  			map[string][]string{
   422  				"allow_any":     {"*"},
   423  				"test.local:80": {"test.local"},
   424  				"test:80":       {"test"},
   425  			},
   426  			map[string]string{
   427  				"allow_any":     "PassthroughCluster",
   428  				"test.local:80": "outbound|80||test.local",
   429  				"test:80":       "outbound|80||test",
   430  			},
   431  		},
   432  		{
   433  			"virtual service",
   434  			[]*model.Service{
   435  				buildHTTPService("test-duplicate-domains.default.svc.cluster.local", visibility.Public, "", "default", 80),
   436  			},
   437  			[]config.Config{{
   438  				Meta: config.Meta{
   439  					GroupVersionKind: gvk.VirtualService,
   440  					Name:             "acme",
   441  				},
   442  				Spec: virtualServiceSpec,
   443  			}},
   444  			map[string][]string{
   445  				"allow_any": {"*"},
   446  				"test-duplicate-domains.default.svc.cluster.local:80": {
   447  					"test-duplicate-domains.default.svc.cluster.local",
   448  					"test-duplicate-domains",
   449  					"test-duplicate-domains.default.svc",
   450  				},
   451  				"test-duplicate-domains.default:80": {"test-duplicate-domains.default"},
   452  			},
   453  			map[string]string{
   454  				"allow_any": "PassthroughCluster",
   455  				// Virtual service destination takes precedence
   456  				"test-duplicate-domains.default.svc.cluster.local:80": "outbound|80||test-duplicate-domains.default",
   457  				"test-duplicate-domains.default:80":                   "outbound|80||test-duplicate-domains.default",
   458  			},
   459  		},
   460  		{
   461  			"virtual service duplicate case sensitive domains",
   462  			[]*model.Service{
   463  				buildHTTPService("test-duplicate-domains.default.svc.cluster.local", visibility.Public, "", "default", 80),
   464  			},
   465  			[]config.Config{
   466  				{
   467  					Meta: config.Meta{
   468  						GroupVersionKind: gvk.VirtualService,
   469  						Name:             "acme",
   470  					},
   471  					Spec: virtualServiceSpec,
   472  				},
   473  				{
   474  					Meta: config.Meta{
   475  						GroupVersionKind: gvk.VirtualService,
   476  						Name:             "acme-duplicate",
   477  					},
   478  					Spec: virtualServiceSpecDuplicate,
   479  				},
   480  			},
   481  			map[string][]string{
   482  				"allow_any": {"*"},
   483  				"test-duplicate-domains.default.svc.cluster.local:80": {
   484  					"test-duplicate-domains.default.svc.cluster.local",
   485  					"test-duplicate-domains",
   486  					"test-duplicate-domains.default.svc",
   487  				},
   488  				"test-duplicate-domains.default:80": {"test-duplicate-domains.default"},
   489  			},
   490  			map[string]string{
   491  				"allow_any": "PassthroughCluster",
   492  				// Virtual service destination takes precedence
   493  				"test-duplicate-domains.default.svc.cluster.local:80": "outbound|80||test-duplicate-domains.default",
   494  				"test-duplicate-domains.default:80":                   "outbound|80||test-duplicate-domains.default",
   495  			},
   496  		},
   497  		{
   498  			"virtual service conflict",
   499  			[]*model.Service{
   500  				buildHTTPService("test.default.svc.cluster.local", visibility.Public, "", "default", 80),
   501  			},
   502  			[]config.Config{{
   503  				Meta: config.Meta{
   504  					GroupVersionKind: gvk.VirtualService,
   505  					Name:             "acme",
   506  				},
   507  				Spec: virtualServiceSpecDefault,
   508  			}},
   509  			map[string][]string{
   510  				"allow_any": {"*"},
   511  				"test.default.svc.cluster.local:80": {
   512  					"test.default.svc.cluster.local",
   513  					"test",
   514  					"test.default.svc",
   515  				},
   516  				"test.default:80": {"test.default"},
   517  			},
   518  			map[string]string{
   519  				"allow_any": "PassthroughCluster",
   520  				// From the service, go to the service
   521  				"test.default.svc.cluster.local:80": "outbound|80||test.default.svc.cluster.local",
   522  				"test.default:80":                   "outbound|80||test.default",
   523  			},
   524  		},
   525  		{
   526  			"multiple ports",
   527  			[]*model.Service{
   528  				buildHTTPService("test.local", visibility.Public, "", "default", 70, 80, 90),
   529  			},
   530  			nil,
   531  			map[string][]string{
   532  				"allow_any":     {"*"},
   533  				"test.local:80": {"test.local"},
   534  			},
   535  			map[string]string{
   536  				"allow_any":     "PassthroughCluster",
   537  				"test.local:80": "outbound|80||test.local",
   538  			},
   539  		},
   540  	}
   541  	for _, tt := range cases {
   542  		t.Run(tt.name, func(t *testing.T) {
   543  			// ensure services are ordered
   544  			t0 := time.Now()
   545  			for _, svc := range tt.services {
   546  				svc.CreationTime = t0
   547  				t0 = t0.Add(time.Minute)
   548  			}
   549  			cg := NewConfigGenTest(t, TestOptions{
   550  				Services: tt.services,
   551  				Configs:  tt.config,
   552  			})
   553  
   554  			vHostCache := make(map[int][]*route.VirtualHost)
   555  			resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig(
   556  				cg.SetupProxy(nil), &model.PushRequest{Push: cg.PushContext()}, "80", vHostCache, nil, nil)
   557  			routeCfg := &route.RouteConfiguration{}
   558  			resource.Resource.UnmarshalTo(routeCfg)
   559  			xdstest.ValidateRouteConfiguration(t, routeCfg)
   560  
   561  			got := map[string][]string{}
   562  			clusters := map[string]string{}
   563  			for _, vh := range routeCfg.VirtualHosts {
   564  				got[vh.Name] = vh.Domains
   565  				clusters[vh.Name] = vh.GetRoutes()[0].GetRoute().GetCluster()
   566  			}
   567  
   568  			if !reflect.DeepEqual(tt.expectedHosts, got) {
   569  				t.Fatalf("unexpected virtual hosts\n%v, wanted\n%v", got, tt.expectedHosts)
   570  			}
   571  
   572  			if !reflect.DeepEqual(tt.expectedDestination, clusters) {
   573  				t.Fatalf("unexpected destinations\n%v, wanted\n%v", clusters, tt.expectedDestination)
   574  			}
   575  		})
   576  	}
   577  }
   578  
   579  func TestSidecarStatefulsessionFilter(t *testing.T) {
   580  	virtualServiceSpec := &networking.VirtualService{
   581  		Hosts:    []string{"test-service.default.svc.cluster.local", "test-service.svc.mesh.acme.net"},
   582  		Gateways: []string{"mesh"},
   583  		Http: []*networking.HTTPRoute{
   584  			{
   585  				Route: []*networking.HTTPRouteDestination{
   586  					{
   587  						Destination: &networking.Destination{
   588  							Host: "test-service.default.svc.cluster.local",
   589  						},
   590  					},
   591  				},
   592  			},
   593  		},
   594  	}
   595  
   596  	// TODO(ramaraochavali): Add more test cases.
   597  	cases := []struct {
   598  		name                  string
   599  		services              []*model.Service
   600  		config                []config.Config
   601  		expectStatefulSession *statefulsession.StatefulSessionPerRoute
   602  	}{
   603  		{
   604  			"session filter with no labels on service",
   605  			[]*model.Service{
   606  				buildHTTPService("test-service.default.svc.cluster.local", visibility.Public, "", "default", 80),
   607  			},
   608  			[]config.Config{{
   609  				Meta: config.Meta{
   610  					GroupVersionKind: gvk.VirtualService,
   611  					Name:             "acme",
   612  				},
   613  				Spec: virtualServiceSpec,
   614  			}},
   615  			nil,
   616  		},
   617  		{
   618  			"session filter with header",
   619  			[]*model.Service{
   620  				buildHTTPServiceWithLabels("test-service.default.svc.cluster.local", visibility.Public, "", "default",
   621  					map[string]string{"istio.io/persistent-session-header": "x-session-header"}, 80),
   622  			},
   623  			[]config.Config{{
   624  				Meta: config.Meta{
   625  					GroupVersionKind: gvk.VirtualService,
   626  					Name:             "acme",
   627  				},
   628  				Spec: virtualServiceSpec,
   629  			}},
   630  			&statefulsession.StatefulSessionPerRoute{
   631  				Override: &statefulsession.StatefulSessionPerRoute_StatefulSession{
   632  					StatefulSession: &statefulsession.StatefulSession{
   633  						SessionState: &core.TypedExtensionConfig{
   634  							Name: "envoy.http.stateful_session.header",
   635  							TypedConfig: protoconv.MessageToAny(&headerv3.HeaderBasedSessionState{
   636  								Name: "x-session-header",
   637  							}),
   638  						},
   639  					},
   640  				},
   641  			},
   642  		},
   643  		{
   644  			"session filter with cookie",
   645  			[]*model.Service{
   646  				buildHTTPServiceWithLabels("test-service.default.svc.cluster.local", visibility.Public, "", "default",
   647  					map[string]string{"istio.io/persistent-session": "x-session-id"}, 80),
   648  			},
   649  			[]config.Config{{
   650  				Meta: config.Meta{
   651  					GroupVersionKind: gvk.VirtualService,
   652  					Name:             "acme",
   653  				},
   654  				Spec: virtualServiceSpec,
   655  			}},
   656  			&statefulsession.StatefulSessionPerRoute{
   657  				Override: &statefulsession.StatefulSessionPerRoute_StatefulSession{
   658  					StatefulSession: &statefulsession.StatefulSession{
   659  						SessionState: &core.TypedExtensionConfig{
   660  							Name: "envoy.http.stateful_session.cookie",
   661  							TypedConfig: protoconv.MessageToAny(&cookiev3.CookieBasedSessionState{
   662  								Cookie: &httpv3.Cookie{
   663  									Name: "x-session-id",
   664  									Path: "/",
   665  								},
   666  							}),
   667  						},
   668  					},
   669  				},
   670  			},
   671  		},
   672  	}
   673  	for _, tt := range cases {
   674  		t.Run(tt.name, func(t *testing.T) {
   675  			// ensure services are ordered
   676  			t0 := time.Now()
   677  			for _, svc := range tt.services {
   678  				svc.CreationTime = t0
   679  				t0 = t0.Add(time.Minute)
   680  			}
   681  			cg := NewConfigGenTest(t, TestOptions{
   682  				Services: tt.services,
   683  				Configs:  tt.config,
   684  			})
   685  
   686  			vHostCache := make(map[int][]*route.VirtualHost)
   687  			resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig(
   688  				cg.SetupProxy(nil), &model.PushRequest{Push: cg.PushContext()}, "80", vHostCache, nil, nil)
   689  			routeCfg := &route.RouteConfiguration{}
   690  			resource.Resource.UnmarshalTo(routeCfg)
   691  			xdstest.ValidateRouteConfiguration(t, routeCfg)
   692  
   693  			for _, vh := range routeCfg.VirtualHosts {
   694  				if vh.Name == "allow_any" {
   695  					continue
   696  				}
   697  				if len(vh.Routes) == 0 {
   698  					t.Fatalf("expected routes to be found but not %s", vh.Name)
   699  				}
   700  				for _, r := range vh.Routes {
   701  					if tt.expectStatefulSession == nil {
   702  						if r.TypedPerFilterConfig != nil &&
   703  							r.TypedPerFilterConfig["envoy.filters.http.stateful_session"] != nil {
   704  							t.Fatalf("stateful session config is not expected but found for %s, %s", vh.Name, r.Name)
   705  						}
   706  					} else {
   707  						if r.TypedPerFilterConfig == nil && r.TypedPerFilterConfig["envoy.filters.http.stateful_session"] == nil {
   708  							t.Fatalf("expected stateful session config but not found for %s, %s", vh.Name, r.Name)
   709  						}
   710  						incomingStatefulSession := &statefulsession.StatefulSessionPerRoute{}
   711  						r.TypedPerFilterConfig["envoy.filters.http.stateful_session"].UnmarshalTo(incomingStatefulSession)
   712  						assert.Equal(t, incomingStatefulSession, tt.expectStatefulSession)
   713  					}
   714  				}
   715  			}
   716  		})
   717  	}
   718  }
   719  
   720  func TestSidecarOutboundHTTPRouteConfig(t *testing.T) {
   721  	services := []*model.Service{
   722  		buildHTTPService("bookinfo.com", visibility.Public, wildcardIPv4, "default", 9999, 70),
   723  		buildHTTPService("private.com", visibility.Private, wildcardIPv4, "default", 9999, 80),
   724  		buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080),
   725  		buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70),
   726  		buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60),
   727  		buildHTTPService("test-headless.com", visibility.Public, wildcardIPv4, "not-default", 8888),
   728  		buildHTTPService("service-A.default.svc.cluster.local", visibility.Public, "", "default", 7777),
   729  	}
   730  
   731  	sidecarConfig := &config.Config{
   732  		Meta: config.Meta{
   733  			Name:             "foo",
   734  			Namespace:        "not-default",
   735  			GroupVersionKind: gvk.Sidecar,
   736  		},
   737  		Spec: &networking.Sidecar{
   738  			Egress: []*networking.IstioEgressListener{
   739  				{
   740  					Port: &networking.SidecarPort{
   741  						// A port that is not in any of the services
   742  						Number:   9000,
   743  						Protocol: "HTTP",
   744  						Name:     "something",
   745  					},
   746  					Bind:  "1.1.1.1",
   747  					Hosts: []string{"*/bookinfo.com"},
   748  				},
   749  				{
   750  					Port: &networking.SidecarPort{
   751  						// Unix domain socket listener
   752  						Number:   0,
   753  						Protocol: "HTTP",
   754  						Name:     "something",
   755  					},
   756  					Bind:  "unix://foo/bar/baz",
   757  					Hosts: []string{"*/bookinfo.com"},
   758  				},
   759  				{
   760  					Port: &networking.SidecarPort{
   761  						// Unix domain socket listener
   762  						Number:   0,
   763  						Protocol: "HTTP",
   764  						Name:     "something",
   765  					},
   766  					Bind:  "unix://foo/bar/headless",
   767  					Hosts: []string{"*/test-headless.com"},
   768  				},
   769  				{
   770  					Port: &networking.SidecarPort{
   771  						// A port that is in one of the services
   772  						Number:   8080,
   773  						Protocol: "HTTP",
   774  						Name:     "foo",
   775  					},
   776  					Hosts: []string{"default/bookinfo.com", "not-default/test.com"},
   777  				},
   778  				{
   779  					// Wildcard egress importing from all namespaces
   780  					Hosts: []string{"*/*"},
   781  				},
   782  			},
   783  		},
   784  	}
   785  	sidecarConfigWithWildcard := &config.Config{
   786  		Meta: config.Meta{
   787  			Name:             "foo",
   788  			Namespace:        "not-default",
   789  			GroupVersionKind: gvk.Sidecar,
   790  		},
   791  		Spec: &networking.Sidecar{
   792  			Egress: []*networking.IstioEgressListener{
   793  				{
   794  					Port: &networking.SidecarPort{
   795  						Number:   7443,
   796  						Protocol: "HTTP",
   797  						Name:     "something",
   798  					},
   799  					Hosts: []string{"*/*"},
   800  				},
   801  			},
   802  		},
   803  	}
   804  	sidecarConfigWitHTTPProxy := &config.Config{
   805  		Meta: config.Meta{
   806  			Name:             "foo",
   807  			Namespace:        "not-default",
   808  			GroupVersionKind: gvk.Sidecar,
   809  		},
   810  		Spec: &networking.Sidecar{
   811  			Egress: []*networking.IstioEgressListener{
   812  				{
   813  					Port: &networking.SidecarPort{
   814  						Number:   7443,
   815  						Protocol: "HTTP_PROXY",
   816  						Name:     "something",
   817  					},
   818  					Hosts: []string{"*/*"},
   819  				},
   820  			},
   821  		},
   822  	}
   823  	sidecarConfigWithRegistryOnly := &config.Config{
   824  		Meta: config.Meta{
   825  			Name:             "foo",
   826  			Namespace:        "not-default",
   827  			GroupVersionKind: gvk.Sidecar,
   828  		},
   829  		Spec: &networking.Sidecar{
   830  			Egress: []*networking.IstioEgressListener{
   831  				{
   832  					Port: &networking.SidecarPort{
   833  						// A port that is not in any of the services
   834  						Number:   9000,
   835  						Protocol: "HTTP",
   836  						Name:     "something",
   837  					},
   838  					Bind:  "1.1.1.1",
   839  					Hosts: []string{"*/bookinfo.com"},
   840  				},
   841  				{
   842  					Port: &networking.SidecarPort{
   843  						// Unix domain socket listener
   844  						Number:   0,
   845  						Protocol: "HTTP",
   846  						Name:     "something",
   847  					},
   848  					Bind:  "unix://foo/bar/baz",
   849  					Hosts: []string{"*/bookinfo.com"},
   850  				},
   851  				{
   852  					Port: &networking.SidecarPort{
   853  						Number:   0,
   854  						Protocol: "HTTP",
   855  						Name:     "something",
   856  					},
   857  					Bind:  "unix://foo/bar/headless",
   858  					Hosts: []string{"*/test-headless.com"},
   859  				},
   860  				{
   861  					Port: &networking.SidecarPort{
   862  						Number:   18888,
   863  						Protocol: "HTTP",
   864  						Name:     "foo",
   865  					},
   866  					Hosts: []string{"*/test-headless.com"},
   867  				},
   868  				{
   869  					// Wildcard egress importing from all namespaces
   870  					Hosts: []string{"*/*"},
   871  				},
   872  			},
   873  			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
   874  				Mode: networking.OutboundTrafficPolicy_REGISTRY_ONLY,
   875  			},
   876  		},
   877  	}
   878  	sidecarConfigWithAllowAny := &config.Config{
   879  		Meta: config.Meta{
   880  			Name:             "foo",
   881  			Namespace:        "not-default",
   882  			GroupVersionKind: gvk.Sidecar,
   883  		},
   884  		Spec: &networking.Sidecar{
   885  			Egress: []*networking.IstioEgressListener{
   886  				{
   887  					Port: &networking.SidecarPort{
   888  						// A port that is not in any of the services
   889  						Number:   9000,
   890  						Protocol: "HTTP",
   891  						Name:     "something",
   892  					},
   893  					Bind:  "1.1.1.1",
   894  					Hosts: []string{"*/bookinfo.com"},
   895  				},
   896  				{
   897  					Port: &networking.SidecarPort{
   898  						// Unix domain socket listener
   899  						Number:   0,
   900  						Protocol: "HTTP",
   901  						Name:     "something",
   902  					},
   903  					Bind:  "unix://foo/bar/baz",
   904  					Hosts: []string{"*/bookinfo.com"},
   905  				},
   906  				{
   907  					Port: &networking.SidecarPort{
   908  						// A port that is in one of the services
   909  						Number:   8080,
   910  						Protocol: "HTTP",
   911  						Name:     "foo",
   912  					},
   913  					Hosts: []string{"default/bookinfo.com", "not-default/test.com"},
   914  				},
   915  				{
   916  					// Wildcard egress importing from all namespaces
   917  					Hosts: []string{"*/*"},
   918  				},
   919  			},
   920  			OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{
   921  				Mode: networking.OutboundTrafficPolicy_ALLOW_ANY,
   922  			},
   923  		},
   924  	}
   925  	virtualServiceSpec1 := &networking.VirtualService{
   926  		Hosts:    []string{"test-private-2.com"},
   927  		Gateways: []string{"mesh"},
   928  		Http: []*networking.HTTPRoute{
   929  			{
   930  				Route: []*networking.HTTPRouteDestination{
   931  					{
   932  						Destination: &networking.Destination{
   933  							// Subset: "some-subset",
   934  							Host: "example.org",
   935  							Port: &networking.PortSelector{
   936  								Number: 61,
   937  							},
   938  						},
   939  						Weight: 100,
   940  					},
   941  				},
   942  			},
   943  		},
   944  	}
   945  	virtualServiceSpec2 := &networking.VirtualService{
   946  		Hosts:    []string{"test-private-2.com"},
   947  		Gateways: []string{"mesh"},
   948  		Http: []*networking.HTTPRoute{
   949  			{
   950  				Route: []*networking.HTTPRouteDestination{
   951  					{
   952  						Destination: &networking.Destination{
   953  							Host: "test.org",
   954  							Port: &networking.PortSelector{
   955  								Number: 62,
   956  							},
   957  						},
   958  						Weight: 100,
   959  					},
   960  				},
   961  			},
   962  		},
   963  	}
   964  	virtualServiceSpec3 := &networking.VirtualService{
   965  		Hosts:    []string{"test-private-3.com"},
   966  		Gateways: []string{"mesh"},
   967  		Http: []*networking.HTTPRoute{
   968  			{
   969  				Route: []*networking.HTTPRouteDestination{
   970  					{
   971  						Destination: &networking.Destination{
   972  							Host: "test.org",
   973  							Port: &networking.PortSelector{
   974  								Number: 63,
   975  							},
   976  						},
   977  						Weight: 100,
   978  					},
   979  				},
   980  			},
   981  		},
   982  	}
   983  	virtualServiceSpec4 := &networking.VirtualService{
   984  		Hosts:    []string{"test-headless.com", "example.com"},
   985  		Gateways: []string{"mesh"},
   986  		Http: []*networking.HTTPRoute{
   987  			{
   988  				Route: []*networking.HTTPRouteDestination{
   989  					{
   990  						Destination: &networking.Destination{
   991  							Host: "test.org",
   992  							Port: &networking.PortSelector{
   993  								Number: 64,
   994  							},
   995  						},
   996  						Weight: 100,
   997  					},
   998  				},
   999  			},
  1000  		},
  1001  	}
  1002  	virtualServiceSpec5 := &networking.VirtualService{
  1003  		Hosts:    []string{"test-svc.testns.svc.cluster.local"},
  1004  		Gateways: []string{"mesh"},
  1005  		Http: []*networking.HTTPRoute{
  1006  			{
  1007  				Route: []*networking.HTTPRouteDestination{
  1008  					{
  1009  						Destination: &networking.Destination{
  1010  							Host: "test-svc.testn.svc.cluster.local",
  1011  						},
  1012  						Weight: 100,
  1013  					},
  1014  				},
  1015  			},
  1016  		},
  1017  	}
  1018  	virtualServiceSpec6 := &networking.VirtualService{
  1019  		Hosts:    []string{"match-no-service"},
  1020  		Gateways: []string{"mesh"},
  1021  		Http: []*networking.HTTPRoute{
  1022  			{
  1023  				Route: []*networking.HTTPRouteDestination{
  1024  					{
  1025  						Destination: &networking.Destination{
  1026  							Host: "non-exist-service",
  1027  						},
  1028  						Weight: 100,
  1029  					},
  1030  				},
  1031  			},
  1032  		},
  1033  	}
  1034  	virtualServiceSpec7 := &networking.VirtualService{
  1035  		Hosts:    []string{"service-A.default.svc.cluster.local", "service-A.v2", "service-A.v3"},
  1036  		Gateways: []string{"mesh"},
  1037  		Http: []*networking.HTTPRoute{
  1038  			{
  1039  				Match: []*networking.HTTPMatchRequest{
  1040  					{
  1041  						Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v2"}}},
  1042  					},
  1043  				},
  1044  				Route: []*networking.HTTPRouteDestination{
  1045  					{
  1046  						Destination: &networking.Destination{
  1047  							Host:   "service-A",
  1048  							Subset: "v2",
  1049  						},
  1050  					},
  1051  				},
  1052  			},
  1053  			{
  1054  				Match: []*networking.HTTPMatchRequest{
  1055  					{
  1056  						Headers: map[string]*networking.StringMatch{":authority": {MatchType: &networking.StringMatch_Exact{Exact: "service-A.v3"}}},
  1057  					},
  1058  				},
  1059  				Route: []*networking.HTTPRouteDestination{
  1060  					{
  1061  						Destination: &networking.Destination{
  1062  							Host:   "service-A",
  1063  							Subset: "v3",
  1064  						},
  1065  					},
  1066  				},
  1067  			},
  1068  		},
  1069  	}
  1070  	virtualService1 := config.Config{
  1071  		Meta: config.Meta{
  1072  			GroupVersionKind: gvk.VirtualService,
  1073  			Name:             "acme2-v1",
  1074  			Namespace:        "not-default",
  1075  		},
  1076  		Spec: virtualServiceSpec1,
  1077  	}
  1078  	virtualService2 := config.Config{
  1079  		Meta: config.Meta{
  1080  			GroupVersionKind: gvk.VirtualService,
  1081  			Name:             "acme-v2",
  1082  			Namespace:        "not-default",
  1083  		},
  1084  		Spec: virtualServiceSpec2,
  1085  	}
  1086  	virtualService3 := config.Config{
  1087  		Meta: config.Meta{
  1088  			GroupVersionKind: gvk.VirtualService,
  1089  			Name:             "acme-v3",
  1090  			Namespace:        "not-default",
  1091  		},
  1092  		Spec: virtualServiceSpec3,
  1093  	}
  1094  	virtualService4 := config.Config{
  1095  		Meta: config.Meta{
  1096  			GroupVersionKind: gvk.VirtualService,
  1097  			Name:             "acme-v4",
  1098  			Namespace:        "not-default",
  1099  		},
  1100  		Spec: virtualServiceSpec4,
  1101  	}
  1102  	virtualService5 := config.Config{
  1103  		Meta: config.Meta{
  1104  			GroupVersionKind: gvk.VirtualService,
  1105  			Name:             "acme-v3",
  1106  			Namespace:        "not-default",
  1107  		},
  1108  		Spec: virtualServiceSpec5,
  1109  	}
  1110  	virtualService6 := config.Config{
  1111  		Meta: config.Meta{
  1112  			GroupVersionKind: gvk.VirtualService,
  1113  			Name:             "acme-v3",
  1114  			Namespace:        "not-default",
  1115  		},
  1116  		Spec: virtualServiceSpec6,
  1117  	}
  1118  	virtualService7 := config.Config{
  1119  		Meta: config.Meta{
  1120  			GroupVersionKind: gvk.VirtualService,
  1121  			Name:             "vs-1",
  1122  			Namespace:        "default",
  1123  		},
  1124  		Spec: virtualServiceSpec7,
  1125  	}
  1126  
  1127  	// With the config above, RDS should return a valid route for the following route names
  1128  	// port 9000 - [bookinfo.com:9999, *.bookinfo.com:9990], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com
  1129  	// unix://foo/bar/baz - [bookinfo.com:9999, *.bookinfo.com:9999], [bookinfo.com:70, *.bookinfo.com:70] but no bookinfo.com
  1130  	// port 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080], but no bookinfo.com or test.com
  1131  	// port 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999]
  1132  	// port 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9]
  1133  	// port 70 - [test-private.com, test-private.com:70, 9.9.9.9, 9.9.9.9:70], [bookinfo.com, bookinfo.com:70]
  1134  
  1135  	// Without sidecar config [same as wildcard egress listener], expect routes
  1136  	// 9999 - [bookinfo.com, bookinfo.com:9999, *.bookinfo.com, *.bookinfo.com:9999],
  1137  	// 8080 - [test.com, test.com:8080, 8.8.8.8, 8.8.8.8:8080]
  1138  	// 80 - [test-private.com, test-private.com:80, 9.9.9.9:80, 9.9.9.9]
  1139  	// 70 - [bookinfo.com, bookinfo.com:70, *.bookinfo.com:70],[test-private.com, test-private.com:70, 9.9.9.9:70, 9.9.9.9]
  1140  	cases := []struct {
  1141  		name                  string
  1142  		routeName             string
  1143  		sidecarConfig         *config.Config
  1144  		virtualServiceConfigs []*config.Config
  1145  		// virtualHost Name and domains
  1146  		expectedHosts  map[string]map[string]bool
  1147  		expectedRoutes int
  1148  		registryOnly   bool
  1149  	}{
  1150  		{
  1151  			name:                  "sidecar config port that is not in any service",
  1152  			routeName:             "9000",
  1153  			sidecarConfig:         sidecarConfig,
  1154  			virtualServiceConfigs: nil,
  1155  			expectedHosts: map[string]map[string]bool{
  1156  				"block_all": {
  1157  					"*": true,
  1158  				},
  1159  			},
  1160  			registryOnly: true,
  1161  		},
  1162  		{
  1163  			name:                  "sidecar config with unix domain socket listener",
  1164  			routeName:             "unix://foo/bar/baz",
  1165  			sidecarConfig:         sidecarConfig,
  1166  			virtualServiceConfigs: nil,
  1167  			expectedHosts: map[string]map[string]bool{
  1168  				"bookinfo.com:9999": {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true},
  1169  				"bookinfo.com:70":   {"bookinfo.com:70": true, "*.bookinfo.com:70": true},
  1170  				"allow_any": {
  1171  					"*": true,
  1172  				},
  1173  			},
  1174  		},
  1175  		{
  1176  			name:                  "sidecar config port that is in one of the services",
  1177  			routeName:             "8080",
  1178  			sidecarConfig:         sidecarConfig,
  1179  			virtualServiceConfigs: nil,
  1180  			expectedHosts: map[string]map[string]bool{
  1181  				"test.com:8080": {"test.com": true, "8.8.8.8": true},
  1182  				"block_all": {
  1183  					"*": true,
  1184  				},
  1185  			},
  1186  			registryOnly: true,
  1187  		},
  1188  		{
  1189  			name:                  "sidecar config with fallthrough and registry only and allow any mesh config",
  1190  			routeName:             "80",
  1191  			sidecarConfig:         sidecarConfigWithRegistryOnly,
  1192  			virtualServiceConfigs: nil,
  1193  			expectedHosts: map[string]map[string]bool{
  1194  				"test-private.com:80": {
  1195  					"test-private.com": true, "9.9.9.9": true,
  1196  				},
  1197  				"block_all": {
  1198  					"*": true,
  1199  				},
  1200  			},
  1201  			registryOnly: false,
  1202  		},
  1203  		{
  1204  			name:                  "sidecar config with fallthrough and allow any and registry only mesh config",
  1205  			routeName:             "80",
  1206  			sidecarConfig:         sidecarConfigWithAllowAny,
  1207  			virtualServiceConfigs: nil,
  1208  			expectedHosts: map[string]map[string]bool{
  1209  				"test-private.com:80": {
  1210  					"test-private.com": true, "9.9.9.9": true,
  1211  				},
  1212  				"allow_any": {
  1213  					"*": true,
  1214  				},
  1215  			},
  1216  			registryOnly: false,
  1217  		},
  1218  
  1219  		{
  1220  			name:                  "sidecar config with allow any and virtual service includes non existing service",
  1221  			routeName:             "80",
  1222  			sidecarConfig:         sidecarConfigWithAllowAny,
  1223  			virtualServiceConfigs: []*config.Config{&virtualService6},
  1224  			expectedHosts: map[string]map[string]bool{
  1225  				// does not include `match-no-service`
  1226  				"test-private.com:80": {
  1227  					"test-private.com": true, "9.9.9.9": true,
  1228  				},
  1229  				"match-no-service.not-default:80": {"match-no-service.not-default": true},
  1230  				"allow_any": {
  1231  					"*": true,
  1232  				},
  1233  			},
  1234  			registryOnly: false,
  1235  		},
  1236  
  1237  		{
  1238  			name:                  "sidecar config with allow any and virtual service includes non existing service",
  1239  			routeName:             "80",
  1240  			sidecarConfig:         sidecarConfigWithAllowAny,
  1241  			virtualServiceConfigs: []*config.Config{&virtualService6},
  1242  			expectedHosts: map[string]map[string]bool{
  1243  				// does not include `match-no-service`
  1244  				"test-private.com:80": {
  1245  					"test-private.com": true, "9.9.9.9": true,
  1246  				},
  1247  				"match-no-service.not-default:80": {"match-no-service.not-default": true},
  1248  				"allow_any": {
  1249  					"*": true,
  1250  				},
  1251  			},
  1252  			registryOnly: false,
  1253  		},
  1254  
  1255  		{
  1256  			name:                  "wildcard egress importing from all namespaces: 9999",
  1257  			routeName:             "9999",
  1258  			sidecarConfig:         sidecarConfig,
  1259  			virtualServiceConfigs: nil,
  1260  			expectedHosts: map[string]map[string]bool{
  1261  				"bookinfo.com:9999": {
  1262  					"bookinfo.com":   true,
  1263  					"*.bookinfo.com": true,
  1264  				},
  1265  				"block_all": {
  1266  					"*": true,
  1267  				},
  1268  			},
  1269  			registryOnly: true,
  1270  		},
  1271  		{
  1272  			name:                  "wildcard egress importing from all namespaces: 80",
  1273  			routeName:             "80",
  1274  			sidecarConfig:         sidecarConfig,
  1275  			virtualServiceConfigs: nil,
  1276  			expectedHosts: map[string]map[string]bool{
  1277  				"test-private.com:80": {
  1278  					"test-private.com": true, "9.9.9.9": true,
  1279  				},
  1280  				"block_all": {
  1281  					"*": true,
  1282  				},
  1283  			},
  1284  			registryOnly: true,
  1285  		},
  1286  		{
  1287  			name:                  "wildcard egress importing from all namespaces: 70",
  1288  			routeName:             "70",
  1289  			sidecarConfig:         sidecarConfig,
  1290  			virtualServiceConfigs: nil,
  1291  			expectedHosts: map[string]map[string]bool{
  1292  				"test-private.com:70": {
  1293  					"test-private.com": true, "9.9.9.9": true,
  1294  				},
  1295  				"bookinfo.com:70": {
  1296  					"bookinfo.com":   true,
  1297  					"*.bookinfo.com": true,
  1298  				},
  1299  				"block_all": {
  1300  					"*": true,
  1301  				},
  1302  			},
  1303  			registryOnly: true,
  1304  		},
  1305  		{
  1306  			name:                  "no sidecar config - import public service from other namespaces: 9999",
  1307  			routeName:             "9999",
  1308  			sidecarConfig:         nil,
  1309  			virtualServiceConfigs: nil,
  1310  			expectedHosts: map[string]map[string]bool{
  1311  				"bookinfo.com:9999": {
  1312  					"bookinfo.com":   true,
  1313  					"*.bookinfo.com": true,
  1314  				},
  1315  				"block_all": {
  1316  					"*": true,
  1317  				},
  1318  			},
  1319  			registryOnly: true,
  1320  		},
  1321  		{
  1322  			name:                  "no sidecar config - import public service from other namespaces: 8080",
  1323  			routeName:             "8080",
  1324  			sidecarConfig:         nil,
  1325  			virtualServiceConfigs: nil,
  1326  			expectedHosts: map[string]map[string]bool{
  1327  				"test.com:8080": {
  1328  					"test.com": true, "8.8.8.8": true,
  1329  				},
  1330  				"block_all": {
  1331  					"*": true,
  1332  				},
  1333  			},
  1334  			registryOnly: true,
  1335  		},
  1336  		{
  1337  			name:                  "no sidecar config - import public services from other namespaces: 80",
  1338  			routeName:             "80",
  1339  			sidecarConfig:         nil,
  1340  			virtualServiceConfigs: nil,
  1341  			expectedHosts: map[string]map[string]bool{
  1342  				"test-private.com:80": {
  1343  					"test-private.com": true, "9.9.9.9": true,
  1344  				},
  1345  				"block_all": {
  1346  					"*": true,
  1347  				},
  1348  			},
  1349  			registryOnly: true,
  1350  		},
  1351  		{
  1352  			name:                  "no sidecar config - import public services from other namespaces: 70",
  1353  			routeName:             "70",
  1354  			sidecarConfig:         nil,
  1355  			virtualServiceConfigs: nil,
  1356  			expectedHosts: map[string]map[string]bool{
  1357  				"test-private.com:70": {
  1358  					"test-private.com": true, "9.9.9.9": true,
  1359  				},
  1360  				"bookinfo.com:70": {
  1361  					"bookinfo.com":   true,
  1362  					"*.bookinfo.com": true,
  1363  				},
  1364  				"block_all": {
  1365  					"*": true,
  1366  				},
  1367  			},
  1368  			registryOnly: true,
  1369  		},
  1370  		{
  1371  			name:                  "no sidecar config - import public services from other namespaces: 70 with sniffing",
  1372  			routeName:             "test-private.com:70",
  1373  			sidecarConfig:         nil,
  1374  			virtualServiceConfigs: nil,
  1375  			expectedHosts: map[string]map[string]bool{
  1376  				"test-private.com:70": {
  1377  					"*": true,
  1378  				},
  1379  			},
  1380  			registryOnly: true,
  1381  		},
  1382  		{
  1383  			name:                  "no sidecar config - import public services from other namespaces: 80 with fallthrough",
  1384  			routeName:             "80",
  1385  			sidecarConfig:         nil,
  1386  			virtualServiceConfigs: nil,
  1387  			expectedHosts: map[string]map[string]bool{
  1388  				"test-private.com:80": {
  1389  					"test-private.com": true, "9.9.9.9": true,
  1390  				},
  1391  				"allow_any": {
  1392  					"*": true,
  1393  				},
  1394  			},
  1395  			registryOnly: false,
  1396  		},
  1397  		{
  1398  			name:                  "no sidecar config - import public services from other namespaces: 80 with fallthrough and registry only",
  1399  			routeName:             "80",
  1400  			sidecarConfig:         nil,
  1401  			virtualServiceConfigs: nil,
  1402  			expectedHosts: map[string]map[string]bool{
  1403  				"test-private.com:80": {
  1404  					"test-private.com": true, "9.9.9.9": true,
  1405  				},
  1406  				"block_all": {
  1407  					"*": true,
  1408  				},
  1409  			},
  1410  			registryOnly: true,
  1411  		},
  1412  		{
  1413  			name:                  "no sidecar config with virtual services with duplicate entries",
  1414  			routeName:             "60",
  1415  			sidecarConfig:         nil,
  1416  			virtualServiceConfigs: []*config.Config{&virtualService1, &virtualService2},
  1417  			expectedHosts: map[string]map[string]bool{
  1418  				"test-private-2.com:60": {
  1419  					"test-private-2.com": true, "9.9.9.10": true,
  1420  				},
  1421  				"block_all": {
  1422  					"*": true,
  1423  				},
  1424  			},
  1425  			registryOnly: true,
  1426  		},
  1427  		{
  1428  			name:                  "no sidecar config with virtual services with no service in registry",
  1429  			routeName:             "80", // no service for the host in registry; use port 80 by default
  1430  			sidecarConfig:         nil,
  1431  			virtualServiceConfigs: []*config.Config{&virtualService3},
  1432  			expectedHosts: map[string]map[string]bool{
  1433  				"test-private.com:80": {
  1434  					"test-private.com": true, "9.9.9.9": true,
  1435  				},
  1436  				"test-private-3.com:80": {
  1437  					"test-private-3.com": true,
  1438  				},
  1439  				"block_all": {
  1440  					"*": true,
  1441  				},
  1442  			},
  1443  			registryOnly: true,
  1444  		},
  1445  		{
  1446  			name:                  "no sidecar config - import headless service from other namespaces: 8888",
  1447  			routeName:             "8888",
  1448  			sidecarConfig:         nil,
  1449  			virtualServiceConfigs: nil,
  1450  			expectedHosts: map[string]map[string]bool{
  1451  				"test-headless.com:8888": {
  1452  					"test-headless.com": true, "*.test-headless.com": true,
  1453  				},
  1454  				"block_all": {
  1455  					"*": true,
  1456  				},
  1457  			},
  1458  			registryOnly: true,
  1459  		},
  1460  		{
  1461  			name:                  "no sidecar config with virtual services - import headless service from other namespaces: 8888",
  1462  			routeName:             "8888",
  1463  			sidecarConfig:         nil,
  1464  			virtualServiceConfigs: []*config.Config{&virtualService4},
  1465  			expectedHosts: map[string]map[string]bool{
  1466  				"test-headless.com:8888": {
  1467  					"test-headless.com": true, "*.test-headless.com": true,
  1468  				},
  1469  				"example.com:8888": {
  1470  					"example.com": true,
  1471  				},
  1472  				"block_all": {
  1473  					"*": true,
  1474  				},
  1475  			},
  1476  			registryOnly: true,
  1477  		},
  1478  		{
  1479  			name:                  "sidecar config with unix domain socket listener - import headless service",
  1480  			routeName:             "unix://foo/bar/headless",
  1481  			sidecarConfig:         sidecarConfig,
  1482  			virtualServiceConfigs: nil,
  1483  			expectedHosts: map[string]map[string]bool{
  1484  				"test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true},
  1485  				"block_all": {
  1486  					"*": true,
  1487  				},
  1488  			},
  1489  			registryOnly: true,
  1490  		},
  1491  		{
  1492  			name:                  "sidecar config port - import headless service",
  1493  			routeName:             "18888",
  1494  			sidecarConfig:         sidecarConfigWithRegistryOnly,
  1495  			virtualServiceConfigs: nil,
  1496  			expectedHosts: map[string]map[string]bool{
  1497  				"block_all": {
  1498  					"*": true,
  1499  				},
  1500  			},
  1501  			registryOnly: true,
  1502  		},
  1503  		{
  1504  			name:                  "wild card sidecar config, with non matching virtual service",
  1505  			routeName:             "7443",
  1506  			sidecarConfig:         sidecarConfigWithWildcard,
  1507  			virtualServiceConfigs: []*config.Config{&virtualService5},
  1508  			expectedHosts: map[string]map[string]bool{
  1509  				"block_all": {
  1510  					"*": true,
  1511  				},
  1512  			},
  1513  			registryOnly: true,
  1514  		},
  1515  		{
  1516  			name:                  "http proxy sidecar config, with non matching virtual service",
  1517  			routeName:             "7443",
  1518  			sidecarConfig:         sidecarConfigWitHTTPProxy,
  1519  			virtualServiceConfigs: []*config.Config{&virtualService5},
  1520  			expectedHosts: map[string]map[string]bool{
  1521  				"bookinfo.com:9999":      {"bookinfo.com:9999": true, "*.bookinfo.com:9999": true},
  1522  				"bookinfo.com:70":        {"bookinfo.com:70": true, "*.bookinfo.com:70": true},
  1523  				"test-headless.com:8888": {"test-headless.com:8888": true, "*.test-headless.com:8888": true},
  1524  				"test-private-2.com:60": {
  1525  					"test-private-2.com:60": true, "9.9.9.10:60": true,
  1526  				},
  1527  				"test-private.com:70": {
  1528  					"test-private.com:70": true, "9.9.9.9:70": true,
  1529  				},
  1530  				"test-private.com:80": {
  1531  					"test-private.com": true, "test-private.com:80": true, "9.9.9.9": true, "9.9.9.9:80": true,
  1532  				},
  1533  				"test.com:8080": {
  1534  					"test.com:8080": true, "8.8.8.8:8080": true,
  1535  				},
  1536  				"service-A.default.svc.cluster.local:7777": {
  1537  					"service-A.default.svc.cluster.local:7777": true,
  1538  				},
  1539  				"block_all": {
  1540  					"*": true,
  1541  				},
  1542  			},
  1543  			registryOnly: true,
  1544  		},
  1545  		{
  1546  			name:                  "virtual service hosts with subsets and with existing service",
  1547  			routeName:             "7777",
  1548  			sidecarConfig:         sidecarConfigWithAllowAny,
  1549  			virtualServiceConfigs: []*config.Config{&virtualService7},
  1550  			expectedHosts: map[string]map[string]bool{
  1551  				"allow_any": {
  1552  					"*": true,
  1553  				},
  1554  				"service-A.default.svc.cluster.local:7777": {
  1555  					"service-A.default.svc.cluster.local": true,
  1556  				},
  1557  				"service-A.v2:7777": {
  1558  					"service-A.v2": true,
  1559  				},
  1560  				"service-A.v3:7777": {
  1561  					"service-A.v3": true,
  1562  				},
  1563  			},
  1564  			expectedRoutes: 7,
  1565  			registryOnly:   false,
  1566  		},
  1567  	}
  1568  
  1569  	for _, c := range cases {
  1570  		t.Run(c.name, func(t *testing.T) {
  1571  			testSidecarRDSVHosts(t, services, c.sidecarConfig, c.virtualServiceConfigs,
  1572  				c.routeName, c.expectedHosts, c.expectedRoutes, c.registryOnly)
  1573  		})
  1574  	}
  1575  }
  1576  
  1577  func TestSelectVirtualService(t *testing.T) {
  1578  	services := []*model.Service{
  1579  		buildHTTPService("bookinfo.com", visibility.Public, wildcardIPv4, "default", 9999, 70),
  1580  		buildHTTPService("private.com", visibility.Private, wildcardIPv4, "default", 9999, 80),
  1581  		buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080),
  1582  		buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70),
  1583  		buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60),
  1584  		buildHTTPService("test-headless.com", visibility.Public, wildcardIPv4, "not-default", 8888),
  1585  	}
  1586  
  1587  	servicesByName := make(map[host.Name]*model.Service, len(services))
  1588  	for _, svc := range services {
  1589  		servicesByName[svc.Hostname] = svc
  1590  	}
  1591  
  1592  	virtualServiceSpec1 := &networking.VirtualService{
  1593  		Hosts:    []string{"test-private-2.com"},
  1594  		Gateways: []string{"mesh"},
  1595  		Http: []*networking.HTTPRoute{
  1596  			{
  1597  				Route: []*networking.HTTPRouteDestination{
  1598  					{
  1599  						Destination: &networking.Destination{
  1600  							// Subset: "some-subset",
  1601  							Host: "example.org",
  1602  							Port: &networking.PortSelector{
  1603  								Number: 61,
  1604  							},
  1605  						},
  1606  						Weight: 100,
  1607  					},
  1608  				},
  1609  			},
  1610  		},
  1611  	}
  1612  	virtualServiceSpec2 := &networking.VirtualService{
  1613  		Hosts:    []string{"test-private-2.com"},
  1614  		Gateways: []string{"mesh"},
  1615  		Http: []*networking.HTTPRoute{
  1616  			{
  1617  				Route: []*networking.HTTPRouteDestination{
  1618  					{
  1619  						Destination: &networking.Destination{
  1620  							Host: "test.org",
  1621  							Port: &networking.PortSelector{
  1622  								Number: 62,
  1623  							},
  1624  						},
  1625  						Weight: 100,
  1626  					},
  1627  				},
  1628  			},
  1629  		},
  1630  	}
  1631  	virtualServiceSpec3 := &networking.VirtualService{
  1632  		Hosts:    []string{"test-private-3.com"},
  1633  		Gateways: []string{"mesh"},
  1634  		Http: []*networking.HTTPRoute{
  1635  			{
  1636  				Route: []*networking.HTTPRouteDestination{
  1637  					{
  1638  						Destination: &networking.Destination{
  1639  							Host: "test.org",
  1640  							Port: &networking.PortSelector{
  1641  								Number: 63,
  1642  							},
  1643  						},
  1644  						Weight: 100,
  1645  					},
  1646  				},
  1647  			},
  1648  		},
  1649  	}
  1650  	virtualServiceSpec4 := &networking.VirtualService{
  1651  		Hosts:    []string{"test-headless.com", "example.com"},
  1652  		Gateways: []string{"mesh"},
  1653  		Http: []*networking.HTTPRoute{
  1654  			{
  1655  				Route: []*networking.HTTPRouteDestination{
  1656  					{
  1657  						Destination: &networking.Destination{
  1658  							Host: "test.org",
  1659  							Port: &networking.PortSelector{
  1660  								Number: 64,
  1661  							},
  1662  						},
  1663  						Weight: 100,
  1664  					},
  1665  				},
  1666  			},
  1667  		},
  1668  	}
  1669  	virtualServiceSpec5 := &networking.VirtualService{
  1670  		Hosts:    []string{"test-svc.testns.svc.cluster.local"},
  1671  		Gateways: []string{"mesh"},
  1672  		Http: []*networking.HTTPRoute{
  1673  			{
  1674  				Route: []*networking.HTTPRouteDestination{
  1675  					{
  1676  						Destination: &networking.Destination{
  1677  							Host: "test-svc.testn.svc.cluster.local",
  1678  						},
  1679  						Weight: 100,
  1680  					},
  1681  				},
  1682  			},
  1683  		},
  1684  	}
  1685  	virtualServiceSpec6 := &networking.VirtualService{
  1686  		Hosts:    []string{"match-no-service"},
  1687  		Gateways: []string{"mesh"},
  1688  		Http: []*networking.HTTPRoute{
  1689  			{
  1690  				Route: []*networking.HTTPRouteDestination{
  1691  					{
  1692  						Destination: &networking.Destination{
  1693  							Host: "non-exist-service",
  1694  						},
  1695  						Weight: 100,
  1696  					},
  1697  				},
  1698  			},
  1699  		},
  1700  	}
  1701  	virtualService1 := config.Config{
  1702  		Meta: config.Meta{
  1703  			GroupVersionKind: gvk.VirtualService,
  1704  			Name:             "acme2-v1",
  1705  			Namespace:        "not-default",
  1706  		},
  1707  		Spec: virtualServiceSpec1,
  1708  	}
  1709  	virtualService2 := config.Config{
  1710  		Meta: config.Meta{
  1711  			GroupVersionKind: gvk.VirtualService,
  1712  			Name:             "acme-v2",
  1713  			Namespace:        "not-default",
  1714  		},
  1715  		Spec: virtualServiceSpec2,
  1716  	}
  1717  	virtualService3 := config.Config{
  1718  		Meta: config.Meta{
  1719  			GroupVersionKind: gvk.VirtualService,
  1720  			Name:             "acme-v3",
  1721  			Namespace:        "not-default",
  1722  		},
  1723  		Spec: virtualServiceSpec3,
  1724  	}
  1725  	virtualService4 := config.Config{
  1726  		Meta: config.Meta{
  1727  			GroupVersionKind: gvk.VirtualService,
  1728  			Name:             "acme-v4",
  1729  			Namespace:        "not-default",
  1730  		},
  1731  		Spec: virtualServiceSpec4,
  1732  	}
  1733  	virtualService5 := config.Config{
  1734  		Meta: config.Meta{
  1735  			GroupVersionKind: gvk.VirtualService,
  1736  			Name:             "acme-v3",
  1737  			Namespace:        "not-default",
  1738  		},
  1739  		Spec: virtualServiceSpec5,
  1740  	}
  1741  	virtualService6 := config.Config{
  1742  		Meta: config.Meta{
  1743  			GroupVersionKind: gvk.VirtualService,
  1744  			Name:             "acme-v3",
  1745  			Namespace:        "not-default",
  1746  		},
  1747  		Spec: virtualServiceSpec6,
  1748  	}
  1749  	configs := selectVirtualServices(
  1750  		[]config.Config{virtualService1, virtualService2, virtualService3, virtualService4, virtualService5, virtualService6},
  1751  		servicesByName)
  1752  	expectedVS := []string{virtualService1.Name, virtualService2.Name, virtualService4.Name}
  1753  	if len(expectedVS) != len(configs) {
  1754  		t.Fatalf("Unexpected virtualService, got %d, expected %d", len(configs), len(expectedVS))
  1755  	}
  1756  	for i, config := range configs {
  1757  		if config.Name != expectedVS[i] {
  1758  			t.Fatalf("Unexpected virtualService, got %s, expected %s", config.Name, expectedVS[i])
  1759  		}
  1760  	}
  1761  }
  1762  
  1763  func testSidecarRDSVHosts(t *testing.T, services []*model.Service,
  1764  	sidecarConfig *config.Config, virtualServices []*config.Config, routeName string,
  1765  	expectedHosts map[string]map[string]bool, expectedRoutes int, registryOnly bool,
  1766  ) {
  1767  	m := mesh.DefaultMeshConfig()
  1768  	if registryOnly {
  1769  		m.OutboundTrafficPolicy = &meshapi.MeshConfig_OutboundTrafficPolicy{Mode: meshapi.MeshConfig_OutboundTrafficPolicy_REGISTRY_ONLY}
  1770  	}
  1771  	cg := NewConfigGenTest(t, TestOptions{
  1772  		MeshConfig:     m,
  1773  		Services:       services,
  1774  		ConfigPointers: append(virtualServices, sidecarConfig),
  1775  	})
  1776  
  1777  	proxy := &model.Proxy{ConfigNamespace: "not-default", DNSDomain: "default.example.org"}
  1778  	vHostCache := make(map[int][]*route.VirtualHost)
  1779  	resource, _ := cg.ConfigGen.buildSidecarOutboundHTTPRouteConfig(
  1780  		cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, routeName, vHostCache, nil, nil)
  1781  	routeCfg := &route.RouteConfiguration{}
  1782  	resource.Resource.UnmarshalTo(routeCfg)
  1783  	xdstest.ValidateRouteConfiguration(t, routeCfg)
  1784  
  1785  	if expectedRoutes == 0 {
  1786  		expectedRoutes = len(expectedHosts)
  1787  	}
  1788  	numberOfRoutes := 0
  1789  	for _, vhost := range routeCfg.VirtualHosts {
  1790  		numberOfRoutes += len(vhost.Routes)
  1791  		if _, found := expectedHosts[vhost.Name]; !found {
  1792  			t.Fatalf("unexpected vhost block %s for route %s",
  1793  				vhost.Name, routeName)
  1794  		}
  1795  
  1796  		for _, domain := range vhost.Domains {
  1797  			if !expectedHosts[vhost.Name][domain] {
  1798  				t.Fatalf("unexpected vhost domain %s in vhost %s, for route %s", domain, vhost.Name, routeName)
  1799  			}
  1800  		}
  1801  		for want := range expectedHosts[vhost.Name] {
  1802  			found := false
  1803  			for _, got := range vhost.Domains {
  1804  				if got == want {
  1805  					found = true
  1806  				}
  1807  			}
  1808  			if !found {
  1809  				t.Fatalf("expected vhost domain %s in vhost %s, for route %s not found. got domains %v", want, vhost.Name, routeName, vhost.Domains)
  1810  			}
  1811  		}
  1812  
  1813  		if !vhost.GetIncludeRequestAttemptCount() {
  1814  			t.Fatal("Expected that include request attempt count is set to true, but set to false")
  1815  		}
  1816  	}
  1817  	if (expectedRoutes >= 0) && (numberOfRoutes != expectedRoutes) {
  1818  		t.Errorf("Wrong number of routes. expected: %v, Got: %v", expectedRoutes, numberOfRoutes)
  1819  	}
  1820  }
  1821  
  1822  func buildHTTPServiceWithLabels(hostname string, v visibility.Instance, ip, namespace string, labels map[string]string, ports ...int) *model.Service {
  1823  	svc := buildHTTPService(hostname, v, ip, namespace, ports...)
  1824  	svc.Attributes.Labels = labels
  1825  	return svc
  1826  }
  1827  
  1828  func buildHTTPService(hostname string, v visibility.Instance, ip, namespace string, ports ...int) *model.Service {
  1829  	service := &model.Service{
  1830  		CreationTime:   tnow,
  1831  		Hostname:       host.Name(hostname),
  1832  		DefaultAddress: ip,
  1833  		Resolution:     model.DNSLB,
  1834  		Attributes: model.ServiceAttributes{
  1835  			ServiceRegistry: provider.Kubernetes,
  1836  			Namespace:       namespace,
  1837  			ExportTo:        sets.New(v),
  1838  		},
  1839  	}
  1840  	if ip == wildcardIPv4 {
  1841  		service.Resolution = model.Passthrough
  1842  	}
  1843  
  1844  	Ports := make([]*model.Port, 0)
  1845  
  1846  	for _, p := range ports {
  1847  		Ports = append(Ports, &model.Port{
  1848  			Name:     fmt.Sprintf("http-%d", p),
  1849  			Port:     p,
  1850  			Protocol: protocol.HTTP,
  1851  		})
  1852  	}
  1853  
  1854  	service.Ports = Ports
  1855  	return service
  1856  }