istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/virtualservice_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  	"testing"
    21  	"time"
    22  
    23  	fuzz "github.com/google/gofuzz"
    24  	"google.golang.org/protobuf/types/known/durationpb"
    25  	"google.golang.org/protobuf/types/known/wrapperspb"
    26  
    27  	networking "istio.io/api/networking/v1alpha3"
    28  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    29  	"istio.io/istio/pkg/config"
    30  	"istio.io/istio/pkg/config/constants"
    31  	"istio.io/istio/pkg/config/host"
    32  	"istio.io/istio/pkg/config/protocol"
    33  	"istio.io/istio/pkg/config/schema/gvk"
    34  	"istio.io/istio/pkg/config/schema/kind"
    35  	"istio.io/istio/pkg/config/visibility"
    36  	"istio.io/istio/pkg/test/util/assert"
    37  	"istio.io/istio/pkg/util/sets"
    38  )
    39  
    40  const wildcardIP = "0.0.0.0"
    41  
    42  func TestMergeVirtualServices(t *testing.T) {
    43  	independentVs := config.Config{
    44  		Meta: config.Meta{
    45  			GroupVersionKind: gvk.VirtualService,
    46  			Name:             "virtual-service",
    47  			Namespace:        "default",
    48  		},
    49  		Spec: &networking.VirtualService{
    50  			Hosts:    []string{"example.org"},
    51  			Gateways: []string{"gateway"},
    52  			Http: []*networking.HTTPRoute{
    53  				{
    54  					Route: []*networking.HTTPRouteDestination{
    55  						{
    56  							Destination: &networking.Destination{
    57  								Host: "example.org",
    58  								Port: &networking.PortSelector{
    59  									Number: 80,
    60  								},
    61  							},
    62  						},
    63  					},
    64  				},
    65  			},
    66  		},
    67  	}
    68  
    69  	rootVs := config.Config{
    70  		Meta: config.Meta{
    71  			GroupVersionKind: gvk.VirtualService,
    72  			Name:             "root-vs",
    73  			Namespace:        "istio-system",
    74  		},
    75  		Spec: &networking.VirtualService{
    76  			Hosts:    []string{"*.org"},
    77  			Gateways: []string{"gateway"},
    78  			Http: []*networking.HTTPRoute{
    79  				{
    80  					Match: []*networking.HTTPMatchRequest{
    81  						{
    82  							Uri: &networking.StringMatch{
    83  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
    84  							},
    85  						},
    86  						{
    87  							Uri: &networking.StringMatch{
    88  								MatchType: &networking.StringMatch_Exact{Exact: "/login"},
    89  							},
    90  						},
    91  					},
    92  					Delegate: &networking.Delegate{
    93  						Name:      "productpage-vs",
    94  						Namespace: "default",
    95  					},
    96  				},
    97  				{
    98  					Route: []*networking.HTTPRouteDestination{
    99  						{
   100  							Destination: &networking.Destination{
   101  								Host: "example.org",
   102  								Port: &networking.PortSelector{
   103  									Number: 80,
   104  								},
   105  							},
   106  						},
   107  					},
   108  				},
   109  			},
   110  		},
   111  	}
   112  
   113  	defaultVs := config.Config{
   114  		Meta: config.Meta{
   115  			GroupVersionKind: gvk.VirtualService,
   116  			Name:             "default-vs",
   117  			Namespace:        "default",
   118  		},
   119  		Spec: &networking.VirtualService{
   120  			Hosts:    []string{"*.org"},
   121  			Gateways: []string{"gateway"},
   122  			Http: []*networking.HTTPRoute{
   123  				{
   124  					Match: []*networking.HTTPMatchRequest{
   125  						{
   126  							Uri: &networking.StringMatch{
   127  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   128  							},
   129  						},
   130  						{
   131  							Uri: &networking.StringMatch{
   132  								MatchType: &networking.StringMatch_Exact{Exact: "/login"},
   133  							},
   134  						},
   135  					},
   136  					Delegate: &networking.Delegate{
   137  						Name: "productpage-vs",
   138  					},
   139  				},
   140  				{
   141  					Route: []*networking.HTTPRouteDestination{
   142  						{
   143  							Destination: &networking.Destination{
   144  								Host: "example.org",
   145  								Port: &networking.PortSelector{
   146  									Number: 80,
   147  								},
   148  							},
   149  						},
   150  					},
   151  				},
   152  			},
   153  		},
   154  	}
   155  
   156  	oneRoot := config.Config{
   157  		Meta: config.Meta{
   158  			GroupVersionKind: gvk.VirtualService,
   159  			Name:             "root-vs",
   160  			Namespace:        "istio-system",
   161  		},
   162  		Spec: &networking.VirtualService{
   163  			Hosts:    []string{"*.org"},
   164  			Gateways: []string{"gateway"},
   165  			Http: []*networking.HTTPRoute{
   166  				{
   167  					Route: []*networking.HTTPRouteDestination{
   168  						{
   169  							Destination: &networking.Destination{
   170  								Host: "example.org",
   171  								Port: &networking.PortSelector{
   172  									Number: 80,
   173  								},
   174  							},
   175  						},
   176  					},
   177  				},
   178  			},
   179  		},
   180  	}
   181  
   182  	createDelegateVs := func(name, namespace string, exportTo []string) config.Config {
   183  		return config.Config{
   184  			Meta: config.Meta{
   185  				GroupVersionKind: gvk.VirtualService,
   186  				Name:             name,
   187  				Namespace:        namespace,
   188  			},
   189  			Spec: &networking.VirtualService{
   190  				Hosts:    []string{},
   191  				Gateways: []string{"gateway"},
   192  				ExportTo: exportTo,
   193  				Http: []*networking.HTTPRoute{
   194  					{
   195  						Match: []*networking.HTTPMatchRequest{
   196  							{
   197  								Uri: &networking.StringMatch{
   198  									MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   199  								},
   200  							},
   201  						},
   202  						Route: []*networking.HTTPRouteDestination{
   203  							{
   204  								Destination: &networking.Destination{
   205  									Host: "productpage.org",
   206  									Port: &networking.PortSelector{
   207  										Number: 80,
   208  									},
   209  									Subset: "v1",
   210  								},
   211  							},
   212  						},
   213  					},
   214  					{
   215  						Match: []*networking.HTTPMatchRequest{
   216  							{
   217  								Uri: &networking.StringMatch{
   218  									MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   219  								},
   220  							},
   221  						},
   222  						Route: []*networking.HTTPRouteDestination{
   223  							{
   224  								Destination: &networking.Destination{
   225  									Host: "productpage.org",
   226  									Port: &networking.PortSelector{
   227  										Number: 80,
   228  									},
   229  									Subset: "v2",
   230  								},
   231  							},
   232  						},
   233  					},
   234  					{
   235  						Route: []*networking.HTTPRouteDestination{
   236  							{
   237  								Destination: &networking.Destination{
   238  									Host: "productpage.org",
   239  									Port: &networking.PortSelector{
   240  										Number: 80,
   241  									},
   242  									Subset: "v3",
   243  								},
   244  							},
   245  						},
   246  					},
   247  				},
   248  			},
   249  		}
   250  	}
   251  
   252  	delegateVs := createDelegateVs("productpage-vs", "default", []string{"istio-system"})
   253  	delegateVsExportedToAll := createDelegateVs("productpage-vs", "default", []string{})
   254  
   255  	delegateVsNotExported := config.Config{
   256  		Meta: config.Meta{
   257  			GroupVersionKind: gvk.VirtualService,
   258  			Name:             "productpage-vs",
   259  			Namespace:        "default2",
   260  		},
   261  		Spec: &networking.VirtualService{
   262  			Hosts:    []string{},
   263  			Gateways: []string{"gateway"},
   264  			ExportTo: []string{"."},
   265  		},
   266  	}
   267  
   268  	mergedVs := config.Config{
   269  		Meta: config.Meta{
   270  			GroupVersionKind: gvk.VirtualService,
   271  			Name:             "root-vs",
   272  			Namespace:        "istio-system",
   273  		},
   274  		Spec: &networking.VirtualService{
   275  			Hosts:    []string{"*.org"},
   276  			Gateways: []string{"gateway"},
   277  			Http: []*networking.HTTPRoute{
   278  				{
   279  					Match: []*networking.HTTPMatchRequest{
   280  						{
   281  							Uri: &networking.StringMatch{
   282  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   283  							},
   284  						},
   285  					},
   286  					Route: []*networking.HTTPRouteDestination{
   287  						{
   288  							Destination: &networking.Destination{
   289  								Host: "productpage.org",
   290  								Port: &networking.PortSelector{
   291  									Number: 80,
   292  								},
   293  								Subset: "v1",
   294  							},
   295  						},
   296  					},
   297  				},
   298  				{
   299  					Match: []*networking.HTTPMatchRequest{
   300  						{
   301  							Uri: &networking.StringMatch{
   302  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   303  							},
   304  						},
   305  					},
   306  					Route: []*networking.HTTPRouteDestination{
   307  						{
   308  							Destination: &networking.Destination{
   309  								Host: "productpage.org",
   310  								Port: &networking.PortSelector{
   311  									Number: 80,
   312  								},
   313  								Subset: "v2",
   314  							},
   315  						},
   316  					},
   317  				},
   318  				{
   319  					Match: []*networking.HTTPMatchRequest{
   320  						{
   321  							Uri: &networking.StringMatch{
   322  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   323  							},
   324  						},
   325  						{
   326  							Uri: &networking.StringMatch{
   327  								MatchType: &networking.StringMatch_Exact{Exact: "/login"},
   328  							},
   329  						},
   330  					},
   331  					Route: []*networking.HTTPRouteDestination{
   332  						{
   333  							Destination: &networking.Destination{
   334  								Host: "productpage.org",
   335  								Port: &networking.PortSelector{
   336  									Number: 80,
   337  								},
   338  								Subset: "v3",
   339  							},
   340  						},
   341  					},
   342  				},
   343  				{
   344  					Route: []*networking.HTTPRouteDestination{
   345  						{
   346  							Destination: &networking.Destination{
   347  								Host: "example.org",
   348  								Port: &networking.PortSelector{
   349  									Number: 80,
   350  								},
   351  							},
   352  						},
   353  					},
   354  				},
   355  			},
   356  		},
   357  	}
   358  
   359  	mergedVsInDefault := mergedVs.DeepCopy()
   360  	mergedVsInDefault.Name = "default-vs"
   361  	mergedVsInDefault.Namespace = "default"
   362  
   363  	// invalid delegate, match condition conflicts with root
   364  	delegateVs2 := config.Config{
   365  		Meta: config.Meta{
   366  			GroupVersionKind: gvk.VirtualService,
   367  			Name:             "productpage-vs",
   368  			Namespace:        "default",
   369  		},
   370  		Spec: &networking.VirtualService{
   371  			Hosts:    []string{},
   372  			Gateways: []string{"gateway"},
   373  			Http: []*networking.HTTPRoute{
   374  				{
   375  					Match: []*networking.HTTPMatchRequest{
   376  						{
   377  							Uri: &networking.StringMatch{
   378  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   379  							},
   380  						},
   381  					},
   382  					Route: []*networking.HTTPRouteDestination{
   383  						{
   384  							Destination: &networking.Destination{
   385  								Host: "productpage.org",
   386  								Port: &networking.PortSelector{
   387  									Number: 80,
   388  								},
   389  								Subset: "v1",
   390  							},
   391  						},
   392  					},
   393  				},
   394  				{
   395  					Match: []*networking.HTTPMatchRequest{
   396  						{
   397  							Uri: &networking.StringMatch{
   398  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   399  							},
   400  						},
   401  					},
   402  					Route: []*networking.HTTPRouteDestination{
   403  						{
   404  							Destination: &networking.Destination{
   405  								Host: "productpage.org",
   406  								Port: &networking.PortSelector{
   407  									Number: 80,
   408  								},
   409  								Subset: "v2",
   410  							},
   411  						},
   412  					},
   413  				},
   414  				{
   415  					// mismatch, this route will be ignored
   416  					Match: []*networking.HTTPMatchRequest{
   417  						{
   418  							Name: "mismatch",
   419  							Uri: &networking.StringMatch{
   420  								// conflicts with root's HTTPMatchRequest
   421  								MatchType: &networking.StringMatch_Prefix{Prefix: "/mis-match/path"},
   422  							},
   423  						},
   424  					},
   425  					Route: []*networking.HTTPRouteDestination{
   426  						{
   427  							Destination: &networking.Destination{
   428  								Host: "productpage.org",
   429  								Port: &networking.PortSelector{
   430  									Number: 80,
   431  								},
   432  								Subset: "v3",
   433  							},
   434  						},
   435  					},
   436  				},
   437  			},
   438  		},
   439  	}
   440  
   441  	mergedVs2 := config.Config{
   442  		Meta: config.Meta{
   443  			GroupVersionKind: gvk.VirtualService,
   444  			Name:             "root-vs",
   445  			Namespace:        "istio-system",
   446  		},
   447  		Spec: &networking.VirtualService{
   448  			Hosts:    []string{"*.org"},
   449  			Gateways: []string{"gateway"},
   450  			Http: []*networking.HTTPRoute{
   451  				{
   452  					Match: []*networking.HTTPMatchRequest{
   453  						{
   454  							Uri: &networking.StringMatch{
   455  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   456  							},
   457  						},
   458  					},
   459  					Route: []*networking.HTTPRouteDestination{
   460  						{
   461  							Destination: &networking.Destination{
   462  								Host: "productpage.org",
   463  								Port: &networking.PortSelector{
   464  									Number: 80,
   465  								},
   466  								Subset: "v1",
   467  							},
   468  						},
   469  					},
   470  				},
   471  				{
   472  					Match: []*networking.HTTPMatchRequest{
   473  						{
   474  							Uri: &networking.StringMatch{
   475  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   476  							},
   477  						},
   478  					},
   479  					Route: []*networking.HTTPRouteDestination{
   480  						{
   481  							Destination: &networking.Destination{
   482  								Host: "productpage.org",
   483  								Port: &networking.PortSelector{
   484  									Number: 80,
   485  								},
   486  								Subset: "v2",
   487  							},
   488  						},
   489  					},
   490  				},
   491  				{
   492  					Route: []*networking.HTTPRouteDestination{
   493  						{
   494  							Destination: &networking.Destination{
   495  								Host: "example.org",
   496  								Port: &networking.PortSelector{
   497  									Number: 80,
   498  								},
   499  							},
   500  						},
   501  					},
   502  				},
   503  			},
   504  		},
   505  	}
   506  
   507  	// multiple routes delegate to one single sub VS
   508  	multiRoutes := config.Config{
   509  		Meta: config.Meta{
   510  			GroupVersionKind: gvk.VirtualService,
   511  			Name:             "root-vs",
   512  			Namespace:        "istio-system",
   513  		},
   514  		Spec: &networking.VirtualService{
   515  			Hosts:    []string{"*.org"},
   516  			Gateways: []string{"gateway"},
   517  			Http: []*networking.HTTPRoute{
   518  				{
   519  					Match: []*networking.HTTPMatchRequest{
   520  						{
   521  							Uri: &networking.StringMatch{
   522  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   523  							},
   524  						},
   525  					},
   526  					Delegate: &networking.Delegate{
   527  						Name:      "productpage-vs",
   528  						Namespace: "default",
   529  					},
   530  				},
   531  				{
   532  					Match: []*networking.HTTPMatchRequest{
   533  						{
   534  							Uri: &networking.StringMatch{
   535  								MatchType: &networking.StringMatch_Prefix{Prefix: "/legacy/path"},
   536  							},
   537  						},
   538  					},
   539  					Rewrite: &networking.HTTPRewrite{
   540  						Uri: "/productpage",
   541  					},
   542  					Delegate: &networking.Delegate{
   543  						Name:      "productpage-vs",
   544  						Namespace: "default",
   545  					},
   546  				},
   547  			},
   548  		},
   549  	}
   550  
   551  	singleDelegate := config.Config{
   552  		Meta: config.Meta{
   553  			GroupVersionKind: gvk.VirtualService,
   554  			Name:             "productpage-vs",
   555  			Namespace:        "default",
   556  		},
   557  		Spec: &networking.VirtualService{
   558  			Hosts:    []string{},
   559  			Gateways: []string{"gateway"},
   560  			Http: []*networking.HTTPRoute{
   561  				{
   562  					Route: []*networking.HTTPRouteDestination{
   563  						{
   564  							Destination: &networking.Destination{
   565  								Host: "productpage.org",
   566  								Port: &networking.PortSelector{
   567  									Number: 80,
   568  								},
   569  								Subset: "v1",
   570  							},
   571  						},
   572  					},
   573  				},
   574  			},
   575  		},
   576  	}
   577  
   578  	mergedVs3 := config.Config{
   579  		Meta: config.Meta{
   580  			GroupVersionKind: gvk.VirtualService,
   581  			Name:             "root-vs",
   582  			Namespace:        "istio-system",
   583  		},
   584  		Spec: &networking.VirtualService{
   585  			Hosts:    []string{"*.org"},
   586  			Gateways: []string{"gateway"},
   587  			Http: []*networking.HTTPRoute{
   588  				{
   589  					Match: []*networking.HTTPMatchRequest{
   590  						{
   591  							Uri: &networking.StringMatch{
   592  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   593  							},
   594  						},
   595  					},
   596  					Route: []*networking.HTTPRouteDestination{
   597  						{
   598  							Destination: &networking.Destination{
   599  								Host: "productpage.org",
   600  								Port: &networking.PortSelector{
   601  									Number: 80,
   602  								},
   603  								Subset: "v1",
   604  							},
   605  						},
   606  					},
   607  				},
   608  				{
   609  					Match: []*networking.HTTPMatchRequest{
   610  						{
   611  							Uri: &networking.StringMatch{
   612  								MatchType: &networking.StringMatch_Prefix{Prefix: "/legacy/path"},
   613  							},
   614  						},
   615  					},
   616  					Rewrite: &networking.HTTPRewrite{
   617  						Uri: "/productpage",
   618  					},
   619  					Route: []*networking.HTTPRouteDestination{
   620  						{
   621  							Destination: &networking.Destination{
   622  								Host: "productpage.org",
   623  								Port: &networking.PortSelector{
   624  									Number: 80,
   625  								},
   626  								Subset: "v1",
   627  							},
   628  						},
   629  					},
   630  				},
   631  			},
   632  		},
   633  	}
   634  
   635  	cases := []struct {
   636  		name                    string
   637  		virtualServices         []config.Config
   638  		expectedVirtualServices []config.Config
   639  		defaultExportTo         sets.Set[visibility.Instance]
   640  	}{
   641  		{
   642  			name:                    "one independent vs",
   643  			virtualServices:         []config.Config{independentVs},
   644  			expectedVirtualServices: []config.Config{independentVs},
   645  			defaultExportTo:         sets.New(visibility.Public),
   646  		},
   647  		{
   648  			name:                    "one root vs",
   649  			virtualServices:         []config.Config{rootVs},
   650  			expectedVirtualServices: []config.Config{oneRoot},
   651  			defaultExportTo:         sets.New(visibility.Public),
   652  		},
   653  		{
   654  			name:                    "one delegate vs",
   655  			virtualServices:         []config.Config{delegateVs},
   656  			expectedVirtualServices: []config.Config{},
   657  			defaultExportTo:         sets.New(visibility.Public),
   658  		},
   659  		{
   660  			name:                    "root and delegate vs",
   661  			virtualServices:         []config.Config{rootVs.DeepCopy(), delegateVs},
   662  			expectedVirtualServices: []config.Config{mergedVs},
   663  			defaultExportTo:         sets.New(visibility.Public),
   664  		},
   665  		{
   666  			name:                    "root and conflicted delegate vs",
   667  			virtualServices:         []config.Config{rootVs.DeepCopy(), delegateVs2},
   668  			expectedVirtualServices: []config.Config{mergedVs2},
   669  			defaultExportTo:         sets.New(visibility.Public),
   670  		},
   671  		{
   672  			name:                    "multiple routes delegate to one",
   673  			virtualServices:         []config.Config{multiRoutes.DeepCopy(), singleDelegate},
   674  			expectedVirtualServices: []config.Config{mergedVs3},
   675  			defaultExportTo:         sets.New(visibility.Public),
   676  		},
   677  		{
   678  			name:                    "root not specify delegate namespace default public",
   679  			virtualServices:         []config.Config{defaultVs.DeepCopy(), delegateVsExportedToAll},
   680  			expectedVirtualServices: []config.Config{mergedVsInDefault},
   681  			defaultExportTo:         sets.New(visibility.Public),
   682  		},
   683  		{
   684  			name:                    "delegate not exported to root vs namespace default public",
   685  			virtualServices:         []config.Config{rootVs, delegateVsNotExported},
   686  			expectedVirtualServices: []config.Config{oneRoot},
   687  			defaultExportTo:         sets.New(visibility.Public),
   688  		},
   689  		{
   690  			name:                    "root not specify delegate namespace default private",
   691  			virtualServices:         []config.Config{defaultVs.DeepCopy(), delegateVsExportedToAll},
   692  			expectedVirtualServices: []config.Config{mergedVsInDefault},
   693  			defaultExportTo:         sets.New(visibility.Private),
   694  		},
   695  		{
   696  			name:                    "delegate not exported to root vs namespace default private",
   697  			virtualServices:         []config.Config{rootVs, delegateVsNotExported},
   698  			expectedVirtualServices: []config.Config{oneRoot},
   699  			defaultExportTo:         sets.New(visibility.Private),
   700  		},
   701  	}
   702  
   703  	for _, tc := range cases {
   704  		t.Run(tc.name, func(t *testing.T) {
   705  			got, _ := mergeVirtualServicesIfNeeded(tc.virtualServices, tc.defaultExportTo)
   706  			assert.Equal(t, got, tc.expectedVirtualServices)
   707  		})
   708  	}
   709  
   710  	t.Run("test merge order", func(t *testing.T) {
   711  		root := rootVs.DeepCopy()
   712  		delegate := delegateVs.DeepCopy()
   713  		normal := independentVs.DeepCopy()
   714  
   715  		// make sorting results predictable.
   716  		t0 := time.Now()
   717  		root.CreationTimestamp = t0.Add(1)
   718  		delegate.CreationTimestamp = t0.Add(2)
   719  		normal.CreationTimestamp = t0.Add(3)
   720  
   721  		checkOrder := func(got []config.Config, _ map[ConfigKey][]ConfigKey) {
   722  			gotOrder := make([]string, 0, len(got))
   723  			for _, c := range got {
   724  				gotOrder = append(gotOrder, fmt.Sprintf("%s/%s", c.Namespace, c.Name))
   725  			}
   726  			wantOrder := []string{"istio-system/root-vs", "default/virtual-service"}
   727  			assert.Equal(t, gotOrder, wantOrder)
   728  		}
   729  
   730  		vses := []config.Config{root, delegate, normal}
   731  		checkOrder(mergeVirtualServicesIfNeeded(vses, sets.New(visibility.Public)))
   732  
   733  		vses = []config.Config{normal, delegate, root}
   734  		checkOrder(mergeVirtualServicesIfNeeded(vses, sets.New(visibility.Public)))
   735  	})
   736  }
   737  
   738  func TestMergeHttpRoutes(t *testing.T) {
   739  	dstV1 := &networking.Destination{
   740  		Host: "productpage.org",
   741  		Port: &networking.PortSelector{
   742  			Number: 80,
   743  		},
   744  		Subset: "v1",
   745  	}
   746  	dstV2 := &networking.Destination{
   747  		Host: "productpage.org",
   748  		Port: &networking.PortSelector{
   749  			Number: 80,
   750  		},
   751  		Subset: "v2",
   752  	}
   753  	dstV3 := &networking.Destination{
   754  		Host: "productpage.org",
   755  		Port: &networking.PortSelector{
   756  			Number: 80,
   757  		},
   758  		Subset: "v3",
   759  	}
   760  	dstMirrorV1 := dstV1.DeepCopy()
   761  	dstMirrorV1.Host = "productpage-mirror.org"
   762  	dstMirrorV2 := dstV2.DeepCopy()
   763  	dstMirrorV2.Host = "productpage-mirror.org"
   764  	dstMirrorV3 := dstV3.DeepCopy()
   765  	dstMirrorV3.Host = "productpage-mirror.org"
   766  
   767  	cases := []struct {
   768  		name     string
   769  		root     *networking.HTTPRoute
   770  		delegate []*networking.HTTPRoute
   771  		expected []*networking.HTTPRoute
   772  	}{
   773  		{
   774  			name: "root catch all",
   775  			root: &networking.HTTPRoute{
   776  				Match:   nil, // catch all
   777  				Timeout: &durationpb.Duration{Seconds: 10},
   778  				Headers: &networking.Headers{
   779  					Request: &networking.Headers_HeaderOperations{
   780  						Add: map[string]string{
   781  							"istio": "test",
   782  						},
   783  						Remove: []string{"trace-id"},
   784  					},
   785  				},
   786  				Delegate: &networking.Delegate{
   787  					Name:      "delegate",
   788  					Namespace: "default",
   789  				},
   790  			},
   791  			delegate: []*networking.HTTPRoute{
   792  				{
   793  					Match: []*networking.HTTPMatchRequest{
   794  						{
   795  							Uri: &networking.StringMatch{
   796  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   797  							},
   798  							Headers: map[string]*networking.StringMatch{
   799  								"version": {
   800  									MatchType: &networking.StringMatch_Exact{Exact: "v1"},
   801  								},
   802  							},
   803  						},
   804  					},
   805  					Route: []*networking.HTTPRouteDestination{{Destination: dstV1}},
   806  				},
   807  				{
   808  					Match: []*networking.HTTPMatchRequest{
   809  						{
   810  							Uri: &networking.StringMatch{
   811  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   812  							},
   813  							Headers: map[string]*networking.StringMatch{
   814  								"version": {
   815  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   816  								},
   817  							},
   818  						},
   819  						{
   820  							Headers: map[string]*networking.StringMatch{
   821  								"version": {
   822  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   823  								},
   824  							},
   825  							QueryParams: map[string]*networking.StringMatch{
   826  								"version": {
   827  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   828  								},
   829  							},
   830  						},
   831  					},
   832  					Route: []*networking.HTTPRouteDestination{{Destination: dstV2}},
   833  				},
   834  			},
   835  			expected: []*networking.HTTPRoute{
   836  				{
   837  					Match: []*networking.HTTPMatchRequest{
   838  						{
   839  							Uri: &networking.StringMatch{
   840  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   841  							},
   842  							Headers: map[string]*networking.StringMatch{
   843  								"version": {
   844  									MatchType: &networking.StringMatch_Exact{Exact: "v1"},
   845  								},
   846  							},
   847  						},
   848  					},
   849  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV1}},
   850  					Timeout: &durationpb.Duration{Seconds: 10},
   851  					Headers: &networking.Headers{
   852  						Request: &networking.Headers_HeaderOperations{
   853  							Add: map[string]string{
   854  								"istio": "test",
   855  							},
   856  							Remove: []string{"trace-id"},
   857  						},
   858  					},
   859  				},
   860  				{
   861  					Match: []*networking.HTTPMatchRequest{
   862  						{
   863  							Uri: &networking.StringMatch{
   864  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   865  							},
   866  							Headers: map[string]*networking.StringMatch{
   867  								"version": {
   868  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   869  								},
   870  							},
   871  						},
   872  						{
   873  							Headers: map[string]*networking.StringMatch{
   874  								"version": {
   875  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   876  								},
   877  							},
   878  							QueryParams: map[string]*networking.StringMatch{
   879  								"version": {
   880  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   881  								},
   882  							},
   883  						},
   884  					},
   885  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV2}},
   886  					Timeout: &durationpb.Duration{Seconds: 10},
   887  					Headers: &networking.Headers{
   888  						Request: &networking.Headers_HeaderOperations{
   889  							Add: map[string]string{
   890  								"istio": "test",
   891  							},
   892  							Remove: []string{"trace-id"},
   893  						},
   894  					},
   895  				},
   896  			},
   897  		},
   898  		{
   899  			name: "delegate with empty match",
   900  			root: &networking.HTTPRoute{
   901  				Match: []*networking.HTTPMatchRequest{
   902  					{
   903  						Uri: &networking.StringMatch{
   904  							MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   905  						},
   906  						Port: 8080,
   907  					},
   908  				},
   909  				Delegate: &networking.Delegate{
   910  					Name:      "delegate",
   911  					Namespace: "default",
   912  				},
   913  			},
   914  			delegate: []*networking.HTTPRoute{
   915  				{
   916  					Match: []*networking.HTTPMatchRequest{
   917  						{
   918  							Uri: &networking.StringMatch{
   919  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   920  							},
   921  							Headers: map[string]*networking.StringMatch{
   922  								"version": {
   923  									MatchType: &networking.StringMatch_Exact{Exact: "v1"},
   924  								},
   925  							},
   926  						},
   927  					},
   928  					Route: []*networking.HTTPRouteDestination{{Destination: dstV1}},
   929  				},
   930  				{
   931  					Match: []*networking.HTTPMatchRequest{
   932  						{
   933  							Uri: &networking.StringMatch{
   934  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   935  							},
   936  							Headers: map[string]*networking.StringMatch{
   937  								"version": {
   938  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   939  								},
   940  							},
   941  						},
   942  						{
   943  							Headers: map[string]*networking.StringMatch{
   944  								"version": {
   945  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   946  								},
   947  							},
   948  							QueryParams: map[string]*networking.StringMatch{
   949  								"version": {
   950  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   951  								},
   952  							},
   953  						},
   954  					},
   955  					Route: []*networking.HTTPRouteDestination{{Destination: dstV2}},
   956  				},
   957  				{
   958  					// default route to v3
   959  					Route: []*networking.HTTPRouteDestination{{Destination: dstV3}},
   960  				},
   961  			},
   962  			expected: []*networking.HTTPRoute{
   963  				{
   964  					Match: []*networking.HTTPMatchRequest{
   965  						{
   966  							Uri: &networking.StringMatch{
   967  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
   968  							},
   969  							Headers: map[string]*networking.StringMatch{
   970  								"version": {
   971  									MatchType: &networking.StringMatch_Exact{Exact: "v1"},
   972  								},
   973  							},
   974  							Port: 8080,
   975  						},
   976  					},
   977  					Route: []*networking.HTTPRouteDestination{{Destination: dstV1}},
   978  				},
   979  				{
   980  					Match: []*networking.HTTPMatchRequest{
   981  						{
   982  							Uri: &networking.StringMatch{
   983  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
   984  							},
   985  							Headers: map[string]*networking.StringMatch{
   986  								"version": {
   987  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   988  								},
   989  							},
   990  							Port: 8080,
   991  						},
   992  						{
   993  							Uri: &networking.StringMatch{
   994  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
   995  							},
   996  							Headers: map[string]*networking.StringMatch{
   997  								"version": {
   998  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
   999  								},
  1000  							},
  1001  							QueryParams: map[string]*networking.StringMatch{
  1002  								"version": {
  1003  									MatchType: &networking.StringMatch_Exact{Exact: "v2"},
  1004  								},
  1005  							},
  1006  							Port: 8080,
  1007  						},
  1008  					},
  1009  					Route: []*networking.HTTPRouteDestination{{Destination: dstV2}},
  1010  				},
  1011  				{
  1012  					Match: []*networking.HTTPMatchRequest{
  1013  						{
  1014  							Uri: &networking.StringMatch{
  1015  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1016  							},
  1017  							Port: 8080,
  1018  						},
  1019  					},
  1020  					// default route to v3
  1021  					Route: []*networking.HTTPRouteDestination{{Destination: dstV3}},
  1022  				},
  1023  			},
  1024  		},
  1025  		{
  1026  			name: "delegate with mirrors",
  1027  			root: &networking.HTTPRoute{
  1028  				Match:   nil,
  1029  				Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV3}},
  1030  				Delegate: &networking.Delegate{
  1031  					Name:      "delegate",
  1032  					Namespace: "default",
  1033  				},
  1034  			},
  1035  			delegate: []*networking.HTTPRoute{
  1036  				{
  1037  					Match: []*networking.HTTPMatchRequest{
  1038  						{
  1039  							Uri: &networking.StringMatch{
  1040  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
  1041  							},
  1042  						},
  1043  					},
  1044  					Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV1}},
  1045  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV1}},
  1046  				},
  1047  				{
  1048  					Match: []*networking.HTTPMatchRequest{
  1049  						{
  1050  							Uri: &networking.StringMatch{
  1051  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1052  							},
  1053  						},
  1054  					},
  1055  					Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV2}},
  1056  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV2}},
  1057  				},
  1058  				{
  1059  					// default route to v3 with no specified mirrors
  1060  					Route: []*networking.HTTPRouteDestination{{Destination: dstV3}},
  1061  				},
  1062  			},
  1063  			expected: []*networking.HTTPRoute{
  1064  				{
  1065  					Match: []*networking.HTTPMatchRequest{
  1066  						{
  1067  							Uri: &networking.StringMatch{
  1068  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v1"},
  1069  							},
  1070  						},
  1071  					},
  1072  					Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV1}},
  1073  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV1}},
  1074  				},
  1075  				{
  1076  					Match: []*networking.HTTPMatchRequest{
  1077  						{
  1078  							Uri: &networking.StringMatch{
  1079  								MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1080  							},
  1081  						},
  1082  					},
  1083  					Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV2}},
  1084  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV2}},
  1085  				},
  1086  				{
  1087  					// default route to v3
  1088  					Mirrors: []*networking.HTTPMirrorPolicy{{Destination: dstMirrorV3}},
  1089  					Route:   []*networking.HTTPRouteDestination{{Destination: dstV3}},
  1090  				},
  1091  			},
  1092  		},
  1093  		{
  1094  			name: "multiple header merge",
  1095  			root: &networking.HTTPRoute{
  1096  				Match: []*networking.HTTPMatchRequest{
  1097  					{
  1098  						Headers: map[string]*networking.StringMatch{
  1099  							"header1": {
  1100  								MatchType: &networking.StringMatch_Regex{
  1101  									Regex: "regex",
  1102  								},
  1103  							},
  1104  						},
  1105  					},
  1106  					{
  1107  						Headers: map[string]*networking.StringMatch{
  1108  							"header2": {
  1109  								MatchType: &networking.StringMatch_Exact{
  1110  									Exact: "exact",
  1111  								},
  1112  							},
  1113  						},
  1114  					},
  1115  				},
  1116  				Delegate: &networking.Delegate{
  1117  					Name:      "delegate",
  1118  					Namespace: "default",
  1119  				},
  1120  			},
  1121  			delegate: []*networking.HTTPRoute{
  1122  				{
  1123  					Match: []*networking.HTTPMatchRequest{
  1124  						{
  1125  							Uri: &networking.StringMatch{
  1126  								MatchType: &networking.StringMatch_Prefix{
  1127  									Prefix: "/",
  1128  								},
  1129  							},
  1130  						},
  1131  					},
  1132  				},
  1133  			},
  1134  			expected: []*networking.HTTPRoute{
  1135  				{
  1136  					Match: []*networking.HTTPMatchRequest{
  1137  						{
  1138  							Uri: &networking.StringMatch{
  1139  								MatchType: &networking.StringMatch_Prefix{
  1140  									Prefix: "/",
  1141  								},
  1142  							},
  1143  							Headers: map[string]*networking.StringMatch{
  1144  								"header1": {
  1145  									MatchType: &networking.StringMatch_Regex{
  1146  										Regex: "regex",
  1147  									},
  1148  								},
  1149  							},
  1150  						},
  1151  						{
  1152  							Uri: &networking.StringMatch{
  1153  								MatchType: &networking.StringMatch_Prefix{
  1154  									Prefix: "/",
  1155  								},
  1156  							},
  1157  							Headers: map[string]*networking.StringMatch{
  1158  								"header2": {
  1159  									MatchType: &networking.StringMatch_Exact{
  1160  										Exact: "exact",
  1161  									},
  1162  								},
  1163  							},
  1164  						},
  1165  					},
  1166  				},
  1167  			},
  1168  		},
  1169  	}
  1170  
  1171  	for _, tc := range cases {
  1172  		t.Run(tc.name, func(t *testing.T) {
  1173  			got := mergeHTTPRoutes(tc.root, tc.delegate)
  1174  			assert.Equal(t, got, tc.expected)
  1175  		})
  1176  	}
  1177  }
  1178  
  1179  func TestMergeHTTPMatchRequests(t *testing.T) {
  1180  	cases := []struct {
  1181  		name     string
  1182  		root     []*networking.HTTPMatchRequest
  1183  		delegate []*networking.HTTPMatchRequest
  1184  		expected []*networking.HTTPMatchRequest
  1185  	}{
  1186  		{
  1187  			name: "url match",
  1188  			root: []*networking.HTTPMatchRequest{
  1189  				{
  1190  					Uri: &networking.StringMatch{
  1191  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1192  					},
  1193  				},
  1194  			},
  1195  			delegate: []*networking.HTTPMatchRequest{
  1196  				{
  1197  					Uri: &networking.StringMatch{
  1198  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1199  					},
  1200  				},
  1201  			},
  1202  			expected: []*networking.HTTPMatchRequest{
  1203  				{
  1204  					Uri: &networking.StringMatch{
  1205  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1206  					},
  1207  				},
  1208  			},
  1209  		},
  1210  		{
  1211  			name: "url regex noconflict",
  1212  			root: []*networking.HTTPMatchRequest{
  1213  				{
  1214  					Uri: &networking.StringMatch{
  1215  						MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1216  					},
  1217  				},
  1218  			},
  1219  			delegate: []*networking.HTTPMatchRequest{
  1220  				{},
  1221  			},
  1222  			expected: []*networking.HTTPMatchRequest{
  1223  				{
  1224  					Uri: &networking.StringMatch{
  1225  						MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1226  					},
  1227  				},
  1228  			},
  1229  		},
  1230  		{
  1231  			name: "url regex conflict",
  1232  			root: []*networking.HTTPMatchRequest{
  1233  				{
  1234  					Uri: &networking.StringMatch{
  1235  						MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1236  					},
  1237  				},
  1238  			},
  1239  			delegate: []*networking.HTTPMatchRequest{
  1240  				{
  1241  					Uri: &networking.StringMatch{
  1242  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1243  					},
  1244  				},
  1245  			},
  1246  			expected: nil,
  1247  		},
  1248  		{
  1249  			name: "multi url match",
  1250  			root: []*networking.HTTPMatchRequest{
  1251  				{
  1252  					Uri: &networking.StringMatch{
  1253  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1254  					},
  1255  				},
  1256  				{
  1257  					Uri: &networking.StringMatch{
  1258  						MatchType: &networking.StringMatch_Prefix{Prefix: "/reviews"},
  1259  					},
  1260  				},
  1261  			},
  1262  			delegate: []*networking.HTTPMatchRequest{
  1263  				{
  1264  					Uri: &networking.StringMatch{
  1265  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1266  					},
  1267  				},
  1268  				{
  1269  					Uri: &networking.StringMatch{
  1270  						MatchType: &networking.StringMatch_Exact{Exact: "/reviews/v1"},
  1271  					},
  1272  				},
  1273  			},
  1274  			expected: []*networking.HTTPMatchRequest{
  1275  				{
  1276  					Uri: &networking.StringMatch{
  1277  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1278  					},
  1279  				},
  1280  				{
  1281  					Uri: &networking.StringMatch{
  1282  						MatchType: &networking.StringMatch_Exact{Exact: "/reviews/v1"},
  1283  					},
  1284  				},
  1285  			},
  1286  		},
  1287  		{
  1288  			name: "url mismatch",
  1289  			root: []*networking.HTTPMatchRequest{
  1290  				{
  1291  					Uri: &networking.StringMatch{
  1292  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1293  					},
  1294  				},
  1295  			},
  1296  			delegate: []*networking.HTTPMatchRequest{
  1297  				{
  1298  					Uri: &networking.StringMatch{
  1299  						MatchType: &networking.StringMatch_Exact{Exact: "/reviews"},
  1300  					},
  1301  				},
  1302  			},
  1303  			expected: nil,
  1304  		},
  1305  		{
  1306  			name: "url match",
  1307  			root: []*networking.HTTPMatchRequest{
  1308  				{
  1309  					Uri: &networking.StringMatch{
  1310  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1311  					},
  1312  				},
  1313  			},
  1314  			delegate: []*networking.HTTPMatchRequest{
  1315  				{
  1316  					Uri: &networking.StringMatch{
  1317  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1318  					},
  1319  				},
  1320  				{
  1321  					Uri: &networking.StringMatch{
  1322  						// conflicts
  1323  						MatchType: &networking.StringMatch_Exact{Exact: "/reviews"},
  1324  					},
  1325  				},
  1326  			},
  1327  			expected: nil,
  1328  		},
  1329  		{
  1330  			name: "headers",
  1331  			root: []*networking.HTTPMatchRequest{
  1332  				{
  1333  					Headers: map[string]*networking.StringMatch{
  1334  						"header": {
  1335  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1336  						},
  1337  					},
  1338  				},
  1339  			},
  1340  			delegate: []*networking.HTTPMatchRequest{},
  1341  			expected: []*networking.HTTPMatchRequest{
  1342  				{
  1343  					Headers: map[string]*networking.StringMatch{
  1344  						"header": {
  1345  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1346  						},
  1347  					},
  1348  				},
  1349  			},
  1350  		},
  1351  		{
  1352  			name: "headers conflict",
  1353  			root: []*networking.HTTPMatchRequest{
  1354  				{
  1355  					Headers: map[string]*networking.StringMatch{
  1356  						"header": {
  1357  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1358  						},
  1359  					},
  1360  				},
  1361  			},
  1362  			delegate: []*networking.HTTPMatchRequest{
  1363  				{
  1364  					Headers: map[string]*networking.StringMatch{
  1365  						"header": {
  1366  							MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1367  						},
  1368  					},
  1369  				},
  1370  			},
  1371  			expected: nil,
  1372  		},
  1373  		{
  1374  			name: "headers",
  1375  			root: []*networking.HTTPMatchRequest{
  1376  				{
  1377  					Headers: map[string]*networking.StringMatch{
  1378  						"header": {
  1379  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1380  						},
  1381  					},
  1382  				},
  1383  			},
  1384  			delegate: []*networking.HTTPMatchRequest{
  1385  				{
  1386  					Headers: map[string]*networking.StringMatch{
  1387  						"header-2": {
  1388  							MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1389  						},
  1390  					},
  1391  				},
  1392  			},
  1393  			expected: []*networking.HTTPMatchRequest{
  1394  				{
  1395  					Headers: map[string]*networking.StringMatch{
  1396  						"header": {
  1397  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1398  						},
  1399  						"header-2": {
  1400  							MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1401  						},
  1402  					},
  1403  				},
  1404  			},
  1405  		},
  1406  		{
  1407  			name: "complicated merge",
  1408  			root: []*networking.HTTPMatchRequest{
  1409  				{
  1410  					Uri: &networking.StringMatch{
  1411  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1412  					},
  1413  					Headers: map[string]*networking.StringMatch{
  1414  						"header": {
  1415  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1416  						},
  1417  					},
  1418  					Port: 8080,
  1419  					Authority: &networking.StringMatch{
  1420  						MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"},
  1421  					},
  1422  				},
  1423  			},
  1424  			delegate: []*networking.HTTPMatchRequest{
  1425  				{
  1426  					Uri: &networking.StringMatch{
  1427  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1428  					},
  1429  				},
  1430  				{
  1431  					Uri: &networking.StringMatch{
  1432  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1433  					},
  1434  				},
  1435  			},
  1436  			expected: []*networking.HTTPMatchRequest{
  1437  				{
  1438  					Uri: &networking.StringMatch{
  1439  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1440  					},
  1441  					Headers: map[string]*networking.StringMatch{
  1442  						"header": {
  1443  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1444  						},
  1445  					},
  1446  					Port: 8080,
  1447  					Authority: &networking.StringMatch{
  1448  						MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"},
  1449  					},
  1450  				},
  1451  				{
  1452  					Uri: &networking.StringMatch{
  1453  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1454  					},
  1455  					Headers: map[string]*networking.StringMatch{
  1456  						"header": {
  1457  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1458  						},
  1459  					},
  1460  					Port: 8080,
  1461  					Authority: &networking.StringMatch{
  1462  						MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"},
  1463  					},
  1464  				},
  1465  			},
  1466  		},
  1467  		{
  1468  			name: "conflicted merge",
  1469  			root: []*networking.HTTPMatchRequest{
  1470  				{
  1471  					Uri: &networking.StringMatch{
  1472  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1473  					},
  1474  					Headers: map[string]*networking.StringMatch{
  1475  						"header": {
  1476  							MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1477  						},
  1478  					},
  1479  					Port: 8080,
  1480  					Authority: &networking.StringMatch{
  1481  						MatchType: &networking.StringMatch_Exact{Exact: "productpage.com"},
  1482  					},
  1483  				},
  1484  			},
  1485  			delegate: []*networking.HTTPMatchRequest{
  1486  				{
  1487  					Uri: &networking.StringMatch{
  1488  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1489  					},
  1490  				},
  1491  				{
  1492  					Uri: &networking.StringMatch{
  1493  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1494  					},
  1495  					Port: 9090, // conflicts
  1496  				},
  1497  			},
  1498  			expected: nil,
  1499  		},
  1500  		{
  1501  			name: "gateway merge",
  1502  			root: []*networking.HTTPMatchRequest{
  1503  				{
  1504  					Uri: &networking.StringMatch{
  1505  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1506  					},
  1507  					Gateways: []string{"ingress-gateway", "mesh"},
  1508  				},
  1509  			},
  1510  			delegate: []*networking.HTTPMatchRequest{
  1511  				{
  1512  					Uri: &networking.StringMatch{
  1513  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1514  					},
  1515  					Gateways: []string{"ingress-gateway"},
  1516  				},
  1517  				{
  1518  					Uri: &networking.StringMatch{
  1519  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1520  					},
  1521  					Gateways: []string{"mesh"},
  1522  				},
  1523  			},
  1524  			expected: []*networking.HTTPMatchRequest{
  1525  				{
  1526  					Uri: &networking.StringMatch{
  1527  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1528  					},
  1529  					Gateways: []string{"ingress-gateway"},
  1530  				},
  1531  				{
  1532  					Uri: &networking.StringMatch{
  1533  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1534  					},
  1535  					Gateways: []string{"mesh"},
  1536  				},
  1537  			},
  1538  		},
  1539  		{
  1540  			name: "gateway conflicted merge",
  1541  			root: []*networking.HTTPMatchRequest{
  1542  				{
  1543  					Uri: &networking.StringMatch{
  1544  						MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1545  					},
  1546  					Gateways: []string{"ingress-gateway"},
  1547  				},
  1548  			},
  1549  			delegate: []*networking.HTTPMatchRequest{
  1550  				{
  1551  					Uri: &networking.StringMatch{
  1552  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v1"},
  1553  					},
  1554  					Gateways: []string{"ingress-gateway"},
  1555  				},
  1556  				{
  1557  					Uri: &networking.StringMatch{
  1558  						MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1559  					},
  1560  					Gateways: []string{"mesh"},
  1561  				},
  1562  			},
  1563  			expected: nil,
  1564  		},
  1565  		{
  1566  			name: "source labels merge",
  1567  			root: []*networking.HTTPMatchRequest{
  1568  				{
  1569  					SourceLabels: map[string]string{"app": "test"},
  1570  				},
  1571  			},
  1572  			delegate: []*networking.HTTPMatchRequest{
  1573  				{
  1574  					SourceLabels: map[string]string{"version": "v1"},
  1575  				},
  1576  			},
  1577  			expected: []*networking.HTTPMatchRequest{
  1578  				{
  1579  					SourceLabels: map[string]string{"app": "test", "version": "v1"},
  1580  				},
  1581  			},
  1582  		},
  1583  	}
  1584  
  1585  	for _, tc := range cases {
  1586  		t.Run(tc.name, func(t *testing.T) {
  1587  			tc.delegate = config.DeepCopy(tc.delegate).([]*networking.HTTPMatchRequest)
  1588  			got, _ := mergeHTTPMatchRequests(tc.root, tc.delegate)
  1589  			assert.Equal(t, got, tc.expected)
  1590  		})
  1591  	}
  1592  }
  1593  
  1594  func TestHasConflict(t *testing.T) {
  1595  	cases := []struct {
  1596  		name     string
  1597  		root     *networking.HTTPMatchRequest
  1598  		leaf     *networking.HTTPMatchRequest
  1599  		expected bool
  1600  	}{
  1601  		{
  1602  			name: "regex uri",
  1603  			root: &networking.HTTPMatchRequest{
  1604  				Uri: &networking.StringMatch{
  1605  					MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1606  				},
  1607  			},
  1608  			leaf: &networking.HTTPMatchRequest{
  1609  				Uri: &networking.StringMatch{
  1610  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1611  				},
  1612  			},
  1613  			expected: true,
  1614  		},
  1615  		{
  1616  			name: "regex uri in root and delegate does not have uri",
  1617  			root: &networking.HTTPMatchRequest{
  1618  				Uri: &networking.StringMatch{
  1619  					MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1620  				},
  1621  			},
  1622  			leaf:     &networking.HTTPMatchRequest{},
  1623  			expected: false,
  1624  		},
  1625  		{
  1626  			name: "regex uri in delegate and root does not have uri",
  1627  			root: &networking.HTTPMatchRequest{},
  1628  			leaf: &networking.HTTPMatchRequest{
  1629  				Uri: &networking.StringMatch{
  1630  					MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1631  				},
  1632  			},
  1633  			expected: false,
  1634  		},
  1635  		{
  1636  			name: "regex uri in root and delegate has conflicting uri match",
  1637  			root: &networking.HTTPMatchRequest{
  1638  				Uri: &networking.StringMatch{
  1639  					MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1640  				},
  1641  			},
  1642  			leaf: &networking.HTTPMatchRequest{
  1643  				Uri: &networking.StringMatch{
  1644  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1645  				},
  1646  			},
  1647  			expected: true,
  1648  		},
  1649  		{
  1650  			name: "regex uri in delegate and root has conflicting uri match",
  1651  			root: &networking.HTTPMatchRequest{
  1652  				Uri: &networking.StringMatch{
  1653  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1654  				},
  1655  			},
  1656  			leaf: &networking.HTTPMatchRequest{
  1657  				Uri: &networking.StringMatch{
  1658  					MatchType: &networking.StringMatch_Regex{Regex: "^/productpage"},
  1659  				},
  1660  			},
  1661  			expected: true,
  1662  		},
  1663  		{
  1664  			name: "match uri",
  1665  			root: &networking.HTTPMatchRequest{
  1666  				Uri: &networking.StringMatch{
  1667  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1668  				},
  1669  			},
  1670  			leaf: &networking.HTTPMatchRequest{
  1671  				Uri: &networking.StringMatch{
  1672  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1673  				},
  1674  			},
  1675  			expected: false,
  1676  		},
  1677  		{
  1678  			name: "mismatch uri",
  1679  			root: &networking.HTTPMatchRequest{
  1680  				Uri: &networking.StringMatch{
  1681  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1682  				},
  1683  			},
  1684  			leaf: &networking.HTTPMatchRequest{
  1685  				Uri: &networking.StringMatch{
  1686  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"},
  1687  				},
  1688  			},
  1689  			expected: true,
  1690  		},
  1691  		{
  1692  			name: "match uri",
  1693  			root: &networking.HTTPMatchRequest{
  1694  				Uri: &networking.StringMatch{
  1695  					MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage/v2"},
  1696  				},
  1697  			},
  1698  			leaf: &networking.HTTPMatchRequest{
  1699  				Uri: &networking.StringMatch{
  1700  					MatchType: &networking.StringMatch_Exact{Exact: "/productpage/v2"},
  1701  				},
  1702  			},
  1703  			expected: false,
  1704  		},
  1705  		{
  1706  			name: "headers not equal",
  1707  			root: &networking.HTTPMatchRequest{
  1708  				Headers: map[string]*networking.StringMatch{
  1709  					"header": {
  1710  						MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1711  					},
  1712  				},
  1713  			},
  1714  			leaf: &networking.HTTPMatchRequest{
  1715  				Headers: map[string]*networking.StringMatch{
  1716  					"header": {
  1717  						MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1718  					},
  1719  				},
  1720  			},
  1721  			expected: true,
  1722  		},
  1723  		{
  1724  			name: "headers equal",
  1725  			root: &networking.HTTPMatchRequest{
  1726  				Headers: map[string]*networking.StringMatch{
  1727  					"header": {
  1728  						MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1729  					},
  1730  				},
  1731  			},
  1732  			leaf: &networking.HTTPMatchRequest{
  1733  				Headers: map[string]*networking.StringMatch{
  1734  					"header": {
  1735  						MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1736  					},
  1737  				},
  1738  			},
  1739  			expected: false,
  1740  		},
  1741  		{
  1742  			name: "headers match",
  1743  			root: &networking.HTTPMatchRequest{
  1744  				Headers: map[string]*networking.StringMatch{
  1745  					"header": {
  1746  						MatchType: &networking.StringMatch_Prefix{Prefix: "h1"},
  1747  					},
  1748  				},
  1749  			},
  1750  			leaf: &networking.HTTPMatchRequest{
  1751  				Headers: map[string]*networking.StringMatch{
  1752  					"header": {
  1753  						MatchType: &networking.StringMatch_Exact{Exact: "h1-v1"},
  1754  					},
  1755  				},
  1756  			},
  1757  			expected: false,
  1758  		},
  1759  		{
  1760  			name: "headers mismatch",
  1761  			root: &networking.HTTPMatchRequest{
  1762  				Headers: map[string]*networking.StringMatch{
  1763  					"header": {
  1764  						MatchType: &networking.StringMatch_Prefix{Prefix: "h1"},
  1765  					},
  1766  				},
  1767  			},
  1768  			leaf: &networking.HTTPMatchRequest{
  1769  				Headers: map[string]*networking.StringMatch{
  1770  					"header": {
  1771  						MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1772  					},
  1773  				},
  1774  			},
  1775  			expected: true,
  1776  		},
  1777  		{
  1778  			name: "headers prefix mismatch",
  1779  			root: &networking.HTTPMatchRequest{
  1780  				Headers: map[string]*networking.StringMatch{
  1781  					"header": {
  1782  						MatchType: &networking.StringMatch_Prefix{Prefix: "h1"},
  1783  					},
  1784  				},
  1785  			},
  1786  			leaf: &networking.HTTPMatchRequest{
  1787  				Headers: map[string]*networking.StringMatch{
  1788  					"header": {
  1789  						MatchType: &networking.StringMatch_Prefix{Prefix: "h2"},
  1790  					},
  1791  				},
  1792  			},
  1793  			expected: true,
  1794  		},
  1795  		{
  1796  			name: "diff headers",
  1797  			root: &networking.HTTPMatchRequest{
  1798  				Headers: map[string]*networking.StringMatch{
  1799  					"header": {
  1800  						MatchType: &networking.StringMatch_Exact{Exact: "h1"},
  1801  					},
  1802  				},
  1803  			},
  1804  			leaf: &networking.HTTPMatchRequest{
  1805  				Headers: map[string]*networking.StringMatch{
  1806  					"header-2": {
  1807  						MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1808  					},
  1809  				},
  1810  			},
  1811  			expected: false,
  1812  		},
  1813  		{
  1814  			name: "withoutHeaders mismatch",
  1815  			root: &networking.HTTPMatchRequest{
  1816  				WithoutHeaders: map[string]*networking.StringMatch{
  1817  					"header": {
  1818  						MatchType: &networking.StringMatch_Prefix{Prefix: "h1"},
  1819  					},
  1820  				},
  1821  			},
  1822  			leaf: &networking.HTTPMatchRequest{
  1823  				WithoutHeaders: map[string]*networking.StringMatch{
  1824  					"header": {
  1825  						MatchType: &networking.StringMatch_Exact{Exact: "h2"},
  1826  					},
  1827  				},
  1828  			},
  1829  			expected: true,
  1830  		},
  1831  		{
  1832  			name: "port",
  1833  			root: &networking.HTTPMatchRequest{
  1834  				Port: 0,
  1835  			},
  1836  			leaf: &networking.HTTPMatchRequest{
  1837  				Port: 8080,
  1838  			},
  1839  			expected: false,
  1840  		},
  1841  		{
  1842  			name: "port",
  1843  			root: &networking.HTTPMatchRequest{
  1844  				Port: 8080,
  1845  			},
  1846  			leaf: &networking.HTTPMatchRequest{
  1847  				Port: 0,
  1848  			},
  1849  			expected: false,
  1850  		},
  1851  		{
  1852  			name: "port",
  1853  			root: &networking.HTTPMatchRequest{
  1854  				Port: 8080,
  1855  			},
  1856  			leaf: &networking.HTTPMatchRequest{
  1857  				Port: 8090,
  1858  			},
  1859  			expected: true,
  1860  		},
  1861  		{
  1862  			name: "sourceLabels mismatch",
  1863  			root: &networking.HTTPMatchRequest{
  1864  				SourceLabels: map[string]string{"a": "b"},
  1865  			},
  1866  			leaf: &networking.HTTPMatchRequest{
  1867  				SourceLabels: map[string]string{"a": "c"},
  1868  			},
  1869  			expected: true,
  1870  		},
  1871  		{
  1872  			name: "sourceNamespace mismatch",
  1873  			root: &networking.HTTPMatchRequest{
  1874  				SourceNamespace: "test1",
  1875  			},
  1876  			leaf: &networking.HTTPMatchRequest{
  1877  				SourceNamespace: "test2",
  1878  			},
  1879  			expected: true,
  1880  		},
  1881  		{
  1882  			name: "root has less gateways than delegate",
  1883  			root: &networking.HTTPMatchRequest{
  1884  				Gateways: []string{"ingress-gateway"},
  1885  			},
  1886  			leaf: &networking.HTTPMatchRequest{
  1887  				Gateways: []string{"ingress-gateway", "mesh"},
  1888  			},
  1889  			expected: true,
  1890  		},
  1891  	}
  1892  
  1893  	for _, tc := range cases {
  1894  		t.Run(tc.name, func(t *testing.T) {
  1895  			got := hasConflict(tc.root, tc.leaf)
  1896  			if got != tc.expected {
  1897  				t.Errorf("got %v, expected %v", got, tc.expected)
  1898  			}
  1899  		})
  1900  	}
  1901  }
  1902  
  1903  // Note: this is to prevent missing merge new added HTTPRoute fields
  1904  func TestFuzzMergeHttpRoute(t *testing.T) {
  1905  	f := fuzz.New().NilChance(0.5).NumElements(0, 1).Funcs(
  1906  		func(r *networking.HTTPRoute, c fuzz.Continue) {
  1907  			c.FuzzNoCustom(r)
  1908  			r.Match = []*networking.HTTPMatchRequest{{}}
  1909  			r.Route = nil
  1910  			r.Redirect = nil
  1911  			r.Delegate = nil
  1912  			r.Mirrors = []*networking.HTTPMirrorPolicy{{}}
  1913  		},
  1914  		func(r *networking.HTTPMatchRequest, c fuzz.Continue) {
  1915  			*r = networking.HTTPMatchRequest{}
  1916  		},
  1917  		func(r *networking.HTTPRouteDestination, c fuzz.Continue) {
  1918  			*r = networking.HTTPRouteDestination{}
  1919  		},
  1920  		func(r *networking.HTTPRedirect, c fuzz.Continue) {
  1921  			*r = networking.HTTPRedirect{}
  1922  		},
  1923  		func(r *networking.HTTPDirectResponse, c fuzz.Continue) {
  1924  			*r = networking.HTTPDirectResponse{}
  1925  		},
  1926  		func(r *networking.Delegate, c fuzz.Continue) {
  1927  			*r = networking.Delegate{}
  1928  		},
  1929  
  1930  		func(r *networking.HTTPRewrite, c fuzz.Continue) {
  1931  			*r = networking.HTTPRewrite{}
  1932  		},
  1933  
  1934  		func(r *durationpb.Duration, c fuzz.Continue) {
  1935  			*r = durationpb.Duration{}
  1936  		},
  1937  		func(r *networking.HTTPRetry, c fuzz.Continue) {
  1938  			*r = networking.HTTPRetry{}
  1939  		},
  1940  		func(r *networking.HTTPFaultInjection, c fuzz.Continue) {
  1941  			*r = networking.HTTPFaultInjection{}
  1942  		},
  1943  		func(r *networking.Destination, c fuzz.Continue) {
  1944  			*r = networking.Destination{}
  1945  		},
  1946  		func(r *wrapperspb.UInt32Value, c fuzz.Continue) {
  1947  			*r = wrapperspb.UInt32Value{}
  1948  		},
  1949  		func(r *networking.Percent, c fuzz.Continue) {
  1950  			*r = networking.Percent{}
  1951  		},
  1952  		func(r *networking.CorsPolicy, c fuzz.Continue) {
  1953  			*r = networking.CorsPolicy{}
  1954  		},
  1955  		func(r *networking.Headers, c fuzz.Continue) {
  1956  			*r = networking.Headers{}
  1957  		},
  1958  		func(r *networking.HTTPMirrorPolicy, c fuzz.Continue) {
  1959  			*r = networking.HTTPMirrorPolicy{}
  1960  		})
  1961  
  1962  	root := &networking.HTTPRoute{}
  1963  	f.Fuzz(root)
  1964  
  1965  	delegate := &networking.HTTPRoute{}
  1966  	expected := mergeHTTPRoute(root, delegate)
  1967  	assert.Equal(t, expected, root)
  1968  }
  1969  
  1970  // Note: this is to prevent missing merge new added HTTPMatchRequest fields
  1971  func TestFuzzMergeHttpMatchRequest(t *testing.T) {
  1972  	f := fuzz.New().NilChance(0.5).NumElements(1, 1).Funcs(
  1973  		func(r *networking.StringMatch, c fuzz.Continue) {
  1974  			*r = networking.StringMatch{
  1975  				MatchType: &networking.StringMatch_Exact{
  1976  					Exact: "fuzz",
  1977  				},
  1978  			}
  1979  		},
  1980  		func(m *map[string]*networking.StringMatch, c fuzz.Continue) {
  1981  			*m = map[string]*networking.StringMatch{
  1982  				"test": nil,
  1983  			}
  1984  		},
  1985  		func(m *map[string]string, c fuzz.Continue) {
  1986  			*m = map[string]string{"test": "fuzz"}
  1987  		})
  1988  
  1989  	root := &networking.HTTPMatchRequest{}
  1990  	f.Fuzz(root)
  1991  	root.SourceNamespace = ""
  1992  	root.SourceLabels = nil
  1993  	root.Gateways = nil
  1994  	root.IgnoreUriCase = false
  1995  	delegate := &networking.HTTPMatchRequest{}
  1996  	merged := mergeHTTPMatchRequest(root, delegate)
  1997  
  1998  	assert.Equal(t, merged, root)
  1999  }
  2000  
  2001  var gatewayNameTests = []struct {
  2002  	gateway   string
  2003  	namespace string
  2004  	resolved  string
  2005  }{
  2006  	{
  2007  		"./gateway",
  2008  		"default",
  2009  		"default/gateway",
  2010  	},
  2011  	{
  2012  		"gateway",
  2013  		"default",
  2014  		"default/gateway",
  2015  	},
  2016  	{
  2017  		"default/gateway",
  2018  		"foo",
  2019  		"default/gateway",
  2020  	},
  2021  	{
  2022  		"gateway.default",
  2023  		"default",
  2024  		"default/gateway",
  2025  	},
  2026  	{
  2027  		"gateway.default",
  2028  		"foo",
  2029  		"default/gateway",
  2030  	},
  2031  	{
  2032  		"private.ingress.svc.cluster.local",
  2033  		"foo",
  2034  		"ingress/private",
  2035  	},
  2036  }
  2037  
  2038  func TestResolveGatewayName(t *testing.T) {
  2039  	for _, tt := range gatewayNameTests {
  2040  		t.Run(fmt.Sprintf("%s-%s", tt.gateway, tt.namespace), func(t *testing.T) {
  2041  			if got := resolveGatewayName(tt.gateway, config.Meta{Namespace: tt.namespace}); got != tt.resolved {
  2042  				t.Fatalf("expected %q got %q", tt.resolved, got)
  2043  			}
  2044  		})
  2045  	}
  2046  }
  2047  
  2048  func BenchmarkResolveGatewayName(b *testing.B) {
  2049  	for i := 0; i < b.N; i++ {
  2050  		for _, tt := range gatewayNameTests {
  2051  			_ = resolveGatewayName(tt.gateway, config.Meta{Namespace: tt.namespace})
  2052  		}
  2053  	}
  2054  }
  2055  
  2056  func TestSelectVirtualService(t *testing.T) {
  2057  	services := []*Service{
  2058  		buildHTTPService("bookinfo.com", visibility.Public, wildcardIP, "default", 9999, 70),
  2059  		buildHTTPService("private.com", visibility.Private, wildcardIP, "default", 9999, 80),
  2060  		buildHTTPService("test.com", visibility.Public, "8.8.8.8", "not-default", 8080),
  2061  		buildHTTPService("test-private.com", visibility.Private, "9.9.9.9", "not-default", 80, 70),
  2062  		buildHTTPService("test-private-2.com", visibility.Private, "9.9.9.10", "not-default", 60),
  2063  		buildHTTPService("test-headless.com", visibility.Public, wildcardIP, "not-default", 8888),
  2064  		buildHTTPService("test-headless-someother.com", visibility.Public, wildcardIP, "some-other-ns", 8888),
  2065  		buildHTTPService("a.test1.wildcard.com", visibility.Public, wildcardIP, "default", 8888),
  2066  		buildHTTPService("*.test2.wildcard.com", visibility.Public, wildcardIP, "default", 8888),
  2067  	}
  2068  
  2069  	hostsByNamespace := make(map[string]hostClassification)
  2070  	for _, svc := range services {
  2071  		ns := svc.Attributes.Namespace
  2072  		if _, exists := hostsByNamespace[ns]; !exists {
  2073  			hostsByNamespace[ns] = hostClassification{exactHosts: sets.New[host.Name](), allHosts: make([]host.Name, 0)}
  2074  		}
  2075  
  2076  		hc := hostsByNamespace[ns]
  2077  		hc.allHosts = append(hc.allHosts, svc.Hostname)
  2078  		hostsByNamespace[ns] = hc
  2079  
  2080  		if !svc.Hostname.IsWildCarded() {
  2081  			hostsByNamespace[ns].exactHosts.Insert(svc.Hostname)
  2082  		}
  2083  	}
  2084  
  2085  	virtualServiceSpec1 := &networking.VirtualService{
  2086  		Hosts:    []string{"test-private-2.com"},
  2087  		Gateways: []string{"mesh"},
  2088  		Http: []*networking.HTTPRoute{
  2089  			{
  2090  				Route: []*networking.HTTPRouteDestination{
  2091  					{
  2092  						Destination: &networking.Destination{
  2093  							// Subset: "some-subset",
  2094  							Host: "example.org",
  2095  							Port: &networking.PortSelector{
  2096  								Number: 61,
  2097  							},
  2098  						},
  2099  						Weight: 100,
  2100  					},
  2101  				},
  2102  			},
  2103  		},
  2104  	}
  2105  	virtualServiceSpec2 := &networking.VirtualService{
  2106  		Hosts:    []string{"test-private-2.com"},
  2107  		Gateways: []string{"mesh"},
  2108  		Http: []*networking.HTTPRoute{
  2109  			{
  2110  				Route: []*networking.HTTPRouteDestination{
  2111  					{
  2112  						Destination: &networking.Destination{
  2113  							Host: "test.org",
  2114  							Port: &networking.PortSelector{
  2115  								Number: 62,
  2116  							},
  2117  						},
  2118  						Weight: 100,
  2119  					},
  2120  				},
  2121  			},
  2122  		},
  2123  	}
  2124  	virtualServiceSpec3 := &networking.VirtualService{
  2125  		Hosts:    []string{"test-private-3.com"},
  2126  		Gateways: []string{"mesh"},
  2127  		Http: []*networking.HTTPRoute{
  2128  			{
  2129  				Route: []*networking.HTTPRouteDestination{
  2130  					{
  2131  						Destination: &networking.Destination{
  2132  							Host: "test.org",
  2133  							Port: &networking.PortSelector{
  2134  								Number: 63,
  2135  							},
  2136  						},
  2137  						Weight: 100,
  2138  					},
  2139  				},
  2140  			},
  2141  		},
  2142  	}
  2143  	virtualServiceSpec4 := &networking.VirtualService{
  2144  		Hosts:    []string{"test-headless.com", "example.com"},
  2145  		Gateways: []string{"mesh"},
  2146  		Http: []*networking.HTTPRoute{
  2147  			{
  2148  				Route: []*networking.HTTPRouteDestination{
  2149  					{
  2150  						Destination: &networking.Destination{
  2151  							Host: "test.org",
  2152  							Port: &networking.PortSelector{
  2153  								Number: 64,
  2154  							},
  2155  						},
  2156  						Weight: 100,
  2157  					},
  2158  				},
  2159  			},
  2160  		},
  2161  	}
  2162  	virtualServiceSpec5 := &networking.VirtualService{
  2163  		Hosts:    []string{"test-svc.testns.svc.cluster.local"},
  2164  		Gateways: []string{"mesh"},
  2165  		Http: []*networking.HTTPRoute{
  2166  			{
  2167  				Route: []*networking.HTTPRouteDestination{
  2168  					{
  2169  						Destination: &networking.Destination{
  2170  							Host: "test-svc.testn.svc.cluster.local",
  2171  						},
  2172  						Weight: 100,
  2173  					},
  2174  				},
  2175  			},
  2176  		},
  2177  	}
  2178  	virtualServiceSpec6 := &networking.VirtualService{
  2179  		Hosts:    []string{"match-no-service"},
  2180  		Gateways: []string{"mesh"},
  2181  		Http: []*networking.HTTPRoute{
  2182  			{
  2183  				Route: []*networking.HTTPRouteDestination{
  2184  					{
  2185  						Destination: &networking.Destination{
  2186  							Host: "non-exist-service",
  2187  						},
  2188  						Weight: 100,
  2189  					},
  2190  				},
  2191  			},
  2192  		},
  2193  	}
  2194  	virtualServiceSpec7 := &networking.VirtualService{
  2195  		Hosts:    []string{"test-headless-someother.com"},
  2196  		Gateways: []string{"mesh"},
  2197  		Http: []*networking.HTTPRoute{
  2198  			{
  2199  				Route: []*networking.HTTPRouteDestination{
  2200  					{
  2201  						Destination: &networking.Destination{
  2202  							Host: "test.org",
  2203  							Port: &networking.PortSelector{
  2204  								Number: 64,
  2205  							},
  2206  						},
  2207  						Weight: 100,
  2208  					},
  2209  				},
  2210  			},
  2211  		},
  2212  	}
  2213  	virtualServiceSpec8 := &networking.VirtualService{
  2214  		Hosts:    []string{"*.test1.wildcard.com"}, // match: a.test1.wildcard.com
  2215  		Gateways: []string{"mesh"},
  2216  		Http: []*networking.HTTPRoute{
  2217  			{
  2218  				Route: []*networking.HTTPRouteDestination{
  2219  					{
  2220  						Destination: &networking.Destination{
  2221  							Host: "test.org",
  2222  							Port: &networking.PortSelector{
  2223  								Number: 64,
  2224  							},
  2225  						},
  2226  						Weight: 100,
  2227  					},
  2228  				},
  2229  			},
  2230  		},
  2231  	}
  2232  	virtualServiceSpec9 := &networking.VirtualService{
  2233  		Hosts:    []string{"foo.test2.wildcard.com"}, // match: *.test2.wildcard.com
  2234  		Gateways: []string{"mesh"},
  2235  		Http: []*networking.HTTPRoute{
  2236  			{
  2237  				Route: []*networking.HTTPRouteDestination{
  2238  					{
  2239  						Destination: &networking.Destination{
  2240  							Host: "test.org",
  2241  							Port: &networking.PortSelector{
  2242  								Number: 64,
  2243  							},
  2244  						},
  2245  						Weight: 100,
  2246  					},
  2247  				},
  2248  			},
  2249  		},
  2250  	}
  2251  	virtualService1 := config.Config{
  2252  		Meta: config.Meta{
  2253  			GroupVersionKind: gvk.VirtualService,
  2254  			Name:             "acme2-v1",
  2255  			Namespace:        "not-default",
  2256  		},
  2257  		Spec: virtualServiceSpec1,
  2258  	}
  2259  	virtualService2 := config.Config{
  2260  		Meta: config.Meta{
  2261  			GroupVersionKind: gvk.VirtualService,
  2262  			Name:             "acme-v2",
  2263  			Namespace:        "not-default",
  2264  		},
  2265  		Spec: virtualServiceSpec2,
  2266  	}
  2267  	virtualService3 := config.Config{
  2268  		Meta: config.Meta{
  2269  			GroupVersionKind: gvk.VirtualService,
  2270  			Name:             "acme-v3",
  2271  			Namespace:        "not-default",
  2272  		},
  2273  		Spec: virtualServiceSpec3,
  2274  	}
  2275  	virtualService4 := config.Config{
  2276  		Meta: config.Meta{
  2277  			GroupVersionKind: gvk.VirtualService,
  2278  			Name:             "acme-v4",
  2279  			Namespace:        "not-default",
  2280  		},
  2281  		Spec: virtualServiceSpec4,
  2282  	}
  2283  	virtualService5 := config.Config{
  2284  		Meta: config.Meta{
  2285  			GroupVersionKind: gvk.VirtualService,
  2286  			Name:             "acme-v3",
  2287  			Namespace:        "not-default",
  2288  		},
  2289  		Spec: virtualServiceSpec5,
  2290  	}
  2291  	virtualService6 := config.Config{
  2292  		Meta: config.Meta{
  2293  			GroupVersionKind: gvk.VirtualService,
  2294  			Name:             "acme-v3",
  2295  			Namespace:        "not-default",
  2296  		},
  2297  		Spec: virtualServiceSpec6,
  2298  	}
  2299  	virtualService7 := config.Config{
  2300  		Meta: config.Meta{
  2301  			GroupVersionKind: gvk.VirtualService,
  2302  			Name:             "acme2-v1",
  2303  			Namespace:        "some-other-ns",
  2304  		},
  2305  		Spec: virtualServiceSpec7,
  2306  	}
  2307  	virtualService8 := config.Config{
  2308  		Meta: config.Meta{
  2309  			GroupVersionKind: gvk.VirtualService,
  2310  			Name:             "vs-wildcard-v1",
  2311  			Namespace:        "default",
  2312  		},
  2313  		Spec: virtualServiceSpec8,
  2314  	}
  2315  	virtualService9 := config.Config{
  2316  		Meta: config.Meta{
  2317  			GroupVersionKind: gvk.VirtualService,
  2318  			Name:             "service-wildcard-v1",
  2319  			Namespace:        "default",
  2320  		},
  2321  		Spec: virtualServiceSpec9,
  2322  	}
  2323  
  2324  	index := virtualServiceIndex{
  2325  		publicByGateway: map[string][]config.Config{
  2326  			constants.IstioMeshGateway: {
  2327  				virtualService1,
  2328  				virtualService2,
  2329  				virtualService3,
  2330  				virtualService4,
  2331  				virtualService5,
  2332  				virtualService6,
  2333  				virtualService7,
  2334  				virtualService8,
  2335  				virtualService9,
  2336  			},
  2337  		},
  2338  	}
  2339  
  2340  	configs := SelectVirtualServices(index, "some-ns", hostsByNamespace)
  2341  	expectedVS := []string{
  2342  		virtualService1.Name, virtualService2.Name, virtualService4.Name, virtualService7.Name,
  2343  		virtualService8.Name, virtualService9.Name,
  2344  	}
  2345  	if len(expectedVS) != len(configs) {
  2346  		t.Fatalf("Unexpected virtualService, got %d, expected %d", len(configs), len(expectedVS))
  2347  	}
  2348  	for i, config := range configs {
  2349  		if config.Name != expectedVS[i] {
  2350  			t.Fatalf("Unexpected virtualService, got %s, expected %s", config.Name, expectedVS[i])
  2351  		}
  2352  	}
  2353  }
  2354  
  2355  func buildHTTPService(hostname string, v visibility.Instance, ip, namespace string, ports ...int) *Service {
  2356  	service := &Service{
  2357  		CreationTime:   time.Now(),
  2358  		Hostname:       host.Name(hostname),
  2359  		DefaultAddress: ip,
  2360  		Resolution:     DNSLB,
  2361  		Attributes: ServiceAttributes{
  2362  			ServiceRegistry: provider.Kubernetes,
  2363  			Namespace:       namespace,
  2364  			ExportTo:        sets.New(v),
  2365  		},
  2366  	}
  2367  	if ip == wildcardIP {
  2368  		service.Resolution = Passthrough
  2369  	}
  2370  
  2371  	Ports := make([]*Port, 0)
  2372  
  2373  	for _, p := range ports {
  2374  		Ports = append(Ports, &Port{
  2375  			Name:     fmt.Sprintf("http-%d", p),
  2376  			Port:     p,
  2377  			Protocol: protocol.HTTP,
  2378  		})
  2379  	}
  2380  
  2381  	service.Ports = Ports
  2382  	return service
  2383  }
  2384  
  2385  func TestVirtualServiceDependencies(t *testing.T) {
  2386  	tests := []struct {
  2387  		name string
  2388  		vs   config.Config
  2389  		want []ConfigKey
  2390  	}{
  2391  		{
  2392  			name: "normal vs",
  2393  			vs: config.Config{
  2394  				Meta: config.Meta{
  2395  					Namespace: "ns",
  2396  					Name:      "foo",
  2397  				},
  2398  			},
  2399  			want: []ConfigKey{
  2400  				{
  2401  					Kind:      kind.VirtualService,
  2402  					Namespace: "ns",
  2403  					Name:      "foo",
  2404  				},
  2405  			},
  2406  		},
  2407  		{
  2408  			name: "internal vs generated from http routes",
  2409  			vs: config.Config{
  2410  				Meta: config.Meta{
  2411  					Namespace: "ns",
  2412  					Name:      "foo-0-istio-autogenerated-k8s-gateway",
  2413  					Annotations: map[string]string{
  2414  						constants.InternalRouteSemantics: constants.RouteSemanticsGateway,
  2415  						constants.InternalParentNames:    "HTTPRoute/foo.ns,HTTPRoute/bar.ns",
  2416  					},
  2417  				},
  2418  			},
  2419  			want: []ConfigKey{
  2420  				{
  2421  					Kind:      kind.HTTPRoute,
  2422  					Namespace: "ns",
  2423  					Name:      "foo",
  2424  				},
  2425  				{
  2426  					Kind:      kind.HTTPRoute,
  2427  					Namespace: "ns",
  2428  					Name:      "bar",
  2429  				},
  2430  			},
  2431  		},
  2432  	}
  2433  	for _, tt := range tests {
  2434  		t.Run(tt.name, func(t *testing.T) {
  2435  			if got := VirtualServiceDependencies(tt.vs); !reflect.DeepEqual(got, tt.want) {
  2436  				t.Errorf("VirtualServiceDependencies got %v, want %v", got, tt.want)
  2437  			}
  2438  		})
  2439  	}
  2440  }