istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/config_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_test
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"strconv"
    21  	"testing"
    22  
    23  	"github.com/davecgh/go-spew/spew"
    24  
    25  	"istio.io/istio/pilot/pkg/model"
    26  	mock_config "istio.io/istio/pilot/test/mock"
    27  	"istio.io/istio/pkg/config"
    28  	"istio.io/istio/pkg/config/host"
    29  	"istio.io/istio/pkg/config/labels"
    30  	"istio.io/istio/pkg/config/protocol"
    31  	"istio.io/istio/pkg/config/schema/collection"
    32  	"istio.io/istio/pkg/config/schema/kind"
    33  	"istio.io/istio/pkg/config/schema/resource"
    34  	"istio.io/istio/pkg/util/sets"
    35  )
    36  
    37  // getByMessageName finds a schema by message name if it is available
    38  // In test setup, we do not have more than one descriptor with the same message type, so this
    39  // function is ok for testing purpose.
    40  func getByMessageName(schemas collection.Schemas, name string) (resource.Schema, bool) {
    41  	for _, s := range schemas.All() {
    42  		if s.Proto() == name {
    43  			return s, true
    44  		}
    45  	}
    46  	return nil, false
    47  }
    48  
    49  func schemaFor(kind, proto string) resource.Schema {
    50  	return resource.Builder{
    51  		Kind:   kind,
    52  		Plural: kind + "s",
    53  		Proto:  proto,
    54  	}.BuildNoValidate()
    55  }
    56  
    57  func TestConfigDescriptor(t *testing.T) {
    58  	a := schemaFor("a", "proxy.A")
    59  	schemas := collection.SchemasFor(
    60  		a,
    61  		schemaFor("b", "proxy.B"),
    62  		schemaFor("c", "proxy.C"))
    63  	want := []string{"a", "b", "c"}
    64  	got := schemas.Kinds()
    65  	if !reflect.DeepEqual(got, want) {
    66  		t.Errorf("descriptor.Types() => got %+vwant %+v", spew.Sdump(got), spew.Sdump(want))
    67  	}
    68  
    69  	aType, aExists := schemas.FindByGroupVersionKind(a.GroupVersionKind())
    70  	if !aExists || !reflect.DeepEqual(aType, a) {
    71  		t.Errorf("descriptor.GetByType(a) => got %+v, want %+v", aType, a)
    72  	}
    73  	if _, exists := schemas.FindByGroupVersionKind(config.GroupVersionKind{Kind: "missing"}); exists {
    74  		t.Error("descriptor.GetByType(missing) => got true, want false")
    75  	}
    76  
    77  	aSchema, aSchemaExists := getByMessageName(schemas, a.Proto())
    78  	if !aSchemaExists || !reflect.DeepEqual(aSchema, a) {
    79  		t.Errorf("descriptor.GetByMessageName(a) => got %+v, want %+v", aType, a)
    80  	}
    81  	_, aSchemaNotExist := getByMessageName(schemas, "blah")
    82  	if aSchemaNotExist {
    83  		t.Errorf("descriptor.GetByMessageName(blah) => got true, want false")
    84  	}
    85  }
    86  
    87  func TestEventString(t *testing.T) {
    88  	cases := []struct {
    89  		in   model.Event
    90  		want string
    91  	}{
    92  		{model.EventAdd, "add"},
    93  		{model.EventUpdate, "update"},
    94  		{model.EventDelete, "delete"},
    95  	}
    96  	for _, c := range cases {
    97  		if got := c.in.String(); got != c.want {
    98  			t.Errorf("Failed: got %q want %q", got, c.want)
    99  		}
   100  	}
   101  }
   102  
   103  func TestPortList(t *testing.T) {
   104  	pl := model.PortList{
   105  		{Name: "http", Port: 80, Protocol: protocol.HTTP},
   106  		{Name: "http-alt", Port: 8080, Protocol: protocol.HTTP},
   107  	}
   108  
   109  	gotNames := pl.GetNames()
   110  	wantNames := []string{"http", "http-alt"}
   111  	if !reflect.DeepEqual(gotNames, wantNames) {
   112  		t.Errorf("GetNames() failed: got %v want %v", gotNames, wantNames)
   113  	}
   114  
   115  	cases := []struct {
   116  		name  string
   117  		port  *model.Port
   118  		found bool
   119  	}{
   120  		{name: pl[0].Name, port: pl[0], found: true},
   121  		{name: "foobar", found: false},
   122  	}
   123  
   124  	for _, c := range cases {
   125  		gotPort, gotFound := pl.Get(c.name)
   126  		if c.found != gotFound || !reflect.DeepEqual(gotPort, c.port) {
   127  			t.Errorf("Get() failed: gotFound=%v wantFound=%v\ngot %+vwant %+v",
   128  				gotFound, c.found, spew.Sdump(gotPort), spew.Sdump(c.port))
   129  		}
   130  	}
   131  }
   132  
   133  func TestSubsetKey(t *testing.T) {
   134  	hostname := host.Name("hostname")
   135  	cases := []struct {
   136  		hostname host.Name
   137  		subset   string
   138  		port     int
   139  		want     string
   140  	}{
   141  		{
   142  			hostname: "hostname",
   143  			subset:   "subset",
   144  			port:     80,
   145  			want:     "outbound|80|subset|hostname",
   146  		},
   147  		{
   148  			hostname: "hostname",
   149  			subset:   "",
   150  			port:     80,
   151  			want:     "outbound|80||hostname",
   152  		},
   153  	}
   154  
   155  	for _, c := range cases {
   156  		got := model.BuildSubsetKey(model.TrafficDirectionOutbound, c.subset, hostname, c.port)
   157  		if got != c.want {
   158  			t.Errorf("Failed: got %q want %q", got, c.want)
   159  		}
   160  
   161  		// test parse subset key. ParseSubsetKey is the inverse of BuildSubsetKey
   162  		_, s, h, p := model.ParseSubsetKey(got)
   163  		if s != c.subset || h != c.hostname || p != c.port {
   164  			t.Errorf("Failed: got %s,%s,%d want %s,%s,%d", s, h, p, c.subset, c.hostname, c.port)
   165  		}
   166  	}
   167  }
   168  
   169  func TestLabelsEquals(t *testing.T) {
   170  	cases := []struct {
   171  		a, b labels.Instance
   172  		want bool
   173  	}{
   174  		{
   175  			a:    nil,
   176  			b:    nil,
   177  			want: true,
   178  		},
   179  		{
   180  			a: nil,
   181  			b: labels.Instance{"a": "b"},
   182  		},
   183  		{
   184  			a: labels.Instance{"a": "b"},
   185  			b: nil,
   186  		},
   187  		{
   188  			a:    labels.Instance{"a": "b"},
   189  			b:    labels.Instance{"a": "b"},
   190  			want: true,
   191  		},
   192  		{
   193  			a: labels.Instance{"a": "b"},
   194  			b: labels.Instance{"a": "b", "c": "d"},
   195  		},
   196  		{
   197  			b: labels.Instance{"a": "b", "c": "d"},
   198  			a: labels.Instance{"a": "b"},
   199  		},
   200  		{
   201  			b:    labels.Instance{"a": "b", "c": "d"},
   202  			a:    labels.Instance{"a": "b", "c": "d"},
   203  			want: true,
   204  		},
   205  	}
   206  	for _, c := range cases {
   207  		if got := c.a.Equals(c.b); got != c.want {
   208  			t.Errorf("Failed: got eq=%v want=%v for %q ?= %q", got, c.want, c.a, c.b)
   209  		}
   210  	}
   211  }
   212  
   213  func TestConfigKey(t *testing.T) {
   214  	cfg := mock_config.Make("ns", 2)
   215  	want := "test.istio.io/v1/MockConfig/ns/mock-config2"
   216  	if key := cfg.Meta.Key(); key != want {
   217  		t.Fatalf("config.Key() => got %q, want %q", key, want)
   218  	}
   219  }
   220  
   221  func TestResolveShortnameToFQDN(t *testing.T) {
   222  	tests := []struct {
   223  		name string
   224  		meta config.Meta
   225  		out  host.Name
   226  	}{
   227  		{
   228  			"*", config.Meta{}, "*",
   229  		},
   230  		{
   231  			"*", config.Meta{Namespace: "default", Domain: "cluster.local"}, "*",
   232  		},
   233  		{
   234  			"foo", config.Meta{Namespace: "default", Domain: "cluster.local"}, "foo.default.svc.cluster.local",
   235  		},
   236  		{
   237  			"foo.bar", config.Meta{Namespace: "default", Domain: "cluster.local"}, "foo.bar",
   238  		},
   239  		{
   240  			"foo", config.Meta{Domain: "cluster.local"}, "foo.svc.cluster.local",
   241  		},
   242  		{
   243  			"foo", config.Meta{Namespace: "default"}, "foo.default",
   244  		},
   245  		{
   246  			"42.185.131.210", config.Meta{Namespace: "default"}, "42.185.131.210",
   247  		},
   248  		{
   249  			"42.185.131.210", config.Meta{Namespace: "cluster.local"}, "42.185.131.210",
   250  		},
   251  		{
   252  			"2a00:4000::614", config.Meta{Namespace: "default"}, "2a00:4000::614",
   253  		},
   254  		{
   255  			"2a00:4000::614", config.Meta{Namespace: "cluster.local"}, "2a00:4000::614",
   256  		},
   257  	}
   258  
   259  	for idx, tt := range tests {
   260  		t.Run(fmt.Sprintf("[%d] %s", idx, tt.out), func(t *testing.T) {
   261  			if actual := model.ResolveShortnameToFQDN(tt.name, tt.meta); actual != tt.out {
   262  				t.Fatalf("model.ResolveShortnameToFQDN(%q, %v) = %q wanted %q", tt.name, tt.meta, actual, tt.out)
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func TestMostSpecificHostMatch(t *testing.T) {
   269  	tests := []struct {
   270  		in     []host.Name
   271  		needle host.Name
   272  		want   host.Name
   273  	}{
   274  		// this has to be a sorted list
   275  		{[]host.Name{}, "*", ""},
   276  		{[]host.Name{"*.foo.com", "*.com"}, "bar.foo.com", "*.foo.com"},
   277  		{[]host.Name{"*.foo.com", "*.com"}, "foo.com", "*.com"},
   278  		{[]host.Name{"foo.com", "*.com"}, "*.foo.com", "*.com"},
   279  
   280  		{[]host.Name{"*.foo.com", "foo.com"}, "foo.com", "foo.com"},
   281  		{[]host.Name{"*.foo.com", "foo.com"}, "*.foo.com", "*.foo.com"},
   282  
   283  		// this passes because we sort alphabetically
   284  		{[]host.Name{"bar.com", "foo.com"}, "*.com", ""},
   285  
   286  		{[]host.Name{"bar.com", "*.foo.com"}, "*foo.com", ""},
   287  		{[]host.Name{"foo.com", "*.foo.com"}, "*foo.com", ""},
   288  
   289  		// should prioritize closest match
   290  		{[]host.Name{"*.bar.com", "foo.bar.com"}, "foo.bar.com", "foo.bar.com"},
   291  		{[]host.Name{"*.foo.bar.com", "bar.foo.bar.com"}, "bar.foo.bar.com", "bar.foo.bar.com"},
   292  
   293  		// should not match non-wildcards for wildcard needle
   294  		{[]host.Name{"bar.foo.com", "foo.bar.com"}, "*.foo.com", ""},
   295  		{[]host.Name{"foo.bar.foo.com", "bar.foo.bar.com"}, "*.bar.foo.com", ""},
   296  	}
   297  
   298  	for idx, tt := range tests {
   299  		specific := sets.New[host.Name]()
   300  		wildcard := sets.New[host.Name]()
   301  		for _, h := range tt.in {
   302  			if h.IsWildCarded() {
   303  				wildcard.Insert(h)
   304  			} else {
   305  				specific.Insert(h)
   306  			}
   307  		}
   308  
   309  		t.Run(fmt.Sprintf("[%d] %s", idx, tt.needle), func(t *testing.T) {
   310  			actual, value, found := model.MostSpecificHostMatch(tt.needle, specific, wildcard)
   311  			if tt.want != "" && !found {
   312  				t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %v, %t; want: %v", tt.needle, tt.in, actual, value, found, tt.want)
   313  			} else if actual != tt.want {
   314  				t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %v, %t; want: %v", tt.needle, tt.in, actual, value, found, tt.want)
   315  			}
   316  			if found {
   317  				if actual.IsWildCarded() && value != wildcard[actual] {
   318  					t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %v, %t; want: %v", tt.needle, tt.in, actual, value, found, tt.want)
   319  				}
   320  				if !actual.IsWildCarded() && value != specific[actual] {
   321  					t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %v, %t; want: %v", tt.needle, tt.in, actual, value, found, tt.want)
   322  				}
   323  			}
   324  		})
   325  	}
   326  }
   327  
   328  func BenchmarkMostSpecificHostMatch(b *testing.B) {
   329  	benchmarks := []struct {
   330  		name             string
   331  		needle           host.Name
   332  		baseHost         string
   333  		hosts            []host.Name
   334  		specificHostsMap sets.Set[host.Name]
   335  		wildcardHostsMap sets.Set[host.Name]
   336  		time             int
   337  		matches          bool
   338  	}{
   339  		{"10ExactNoMatch", host.Name("foo.bar.com.10"), "bar.com", []host.Name{}, nil, nil, 10, false},
   340  		{"50ExactNoMatch", host.Name("foo.bar.com.50"), "bar.com", []host.Name{}, nil, nil, 50, false},
   341  		{"100ExactNoMatch", host.Name("foo.bar.com.100"), "bar.com", []host.Name{}, nil, nil, 100, false},
   342  		{"1000ExactNoMatch", host.Name("foo.bar.com.1000"), "bar.com", []host.Name{}, nil, nil, 1000, false},
   343  		{"5000ExactNoMatch", host.Name("foo.bar.com.5000"), "bar.com", []host.Name{}, nil, nil, 5000, false},
   344  
   345  		{"10ExactMatch", host.Name("foo.bar.com.10"), "foo.bar.com", []host.Name{}, nil, nil, 10, true},
   346  		{"50ExactMatch", host.Name("foo.bar.com.50"), "foo.bar.com", []host.Name{}, nil, nil, 50, true},
   347  		{"100ExactMatch", host.Name("foo.bar.com.100"), "foo.bar.com", []host.Name{}, nil, nil, 100, true},
   348  		{"1000ExactMatch", host.Name("foo.bar.com.1000"), "foo.bar.com", []host.Name{}, nil, nil, 1000, true},
   349  		{"5000ExactMatch", host.Name("foo.bar.com.5000"), "foo.bar.com", []host.Name{}, nil, nil, 5000, true},
   350  
   351  		{"10DestRuleWildcardNoMatch", host.Name("foo.bar.com.10"), "*.foo.bar.com", []host.Name{}, nil, nil, 10, false},
   352  		{"50DestRuleWildcardNoMatch", host.Name("foo.bar.com.50"), "*.foo.bar.com", []host.Name{}, nil, nil, 50, false},
   353  		{"100DestRuleWildcardNoMatch", host.Name("foo.bar.com.100"), "*.foo.bar.com", []host.Name{}, nil, nil, 100, false},
   354  		{"1000DestRuleWildcardNoMatch", host.Name("foo.bar.com.1000"), "*.foo.bar.com", []host.Name{}, nil, nil, 1000, false},
   355  		{"5000DestRuleWildcardNoMatch", host.Name("foo.bar.com.5000"), "*.foo.bar.com", []host.Name{}, nil, nil, 5000, false},
   356  
   357  		{"10DestRuleWildcardMatch", host.Name("foo.bar.baz.com.10"), "*.bar.baz.com", []host.Name{}, nil, nil, 10, true},
   358  		{"50DestRuleWildcardMatch", host.Name("foo.bar.baz.com.50"), "*.bar.baz.com", []host.Name{}, nil, nil, 50, true},
   359  		{"100DestRuleWildcardMatch", host.Name("foo.bar.baz.com.100"), "*.bar.baz.com", []host.Name{}, nil, nil, 100, true},
   360  		{"1000DestRuleWildcardMatch", host.Name("foo.bar.baz.com.1000"), "*.bar.baz.com", []host.Name{}, nil, nil, 1000, true},
   361  		{"5000DestRuleWildcardMatch", host.Name("foo.bar.baz.com.5000"), "*.bar.baz.com", []host.Name{}, nil, nil, 5000, true},
   362  
   363  		{"10NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, nil, 10, false},
   364  		{"50NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, nil, 50, false},
   365  		{"100NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, nil, 100, false},
   366  		{"1000NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, nil, 1000, false},
   367  		{"5000NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "*.foo.bar.com", []host.Name{}, nil, nil, 5000, false},
   368  
   369  		{"10NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.10"), "*.foo.bar.com", []host.Name{}, nil, nil, 10, true},
   370  		{"50NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.50"), "*.foo.bar.com", []host.Name{}, nil, nil, 50, true},
   371  		{"100NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.100"), "*.foo.bar.com", []host.Name{}, nil, nil, 100, true},
   372  		{"1000NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.1000"), "*.foo.bar.com", []host.Name{}, nil, nil, 1000, true},
   373  		{"5000NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.5000"), "*.foo.bar.com", []host.Name{}, nil, nil, 5000, true},
   374  	}
   375  
   376  	for _, bm := range benchmarks {
   377  		bm.specificHostsMap = sets.NewWithLength[host.Name](bm.time)
   378  		bm.wildcardHostsMap = sets.NewWithLength[host.Name](bm.time)
   379  
   380  		for i := 1; i <= bm.time; i++ {
   381  			h := host.Name(bm.baseHost + "." + strconv.Itoa(i))
   382  			if h.IsWildCarded() {
   383  				bm.wildcardHostsMap.Insert(h)
   384  			} else {
   385  				bm.specificHostsMap.Insert(h)
   386  			}
   387  		}
   388  
   389  		b.Run(bm.name, func(b *testing.B) {
   390  			for n := 0; n < b.N; n++ {
   391  				_, _, ok := model.MostSpecificHostMatch(bm.needle, bm.specificHostsMap, bm.wildcardHostsMap)
   392  				if bm.matches != ok {
   393  					b.Fatalf("expected to find match")
   394  				}
   395  			}
   396  		})
   397  	}
   398  }
   399  
   400  func BenchmarkMostSpecificHostMatchMixed(b *testing.B) {
   401  	benchmarks := []struct {
   402  		name             string
   403  		needle           host.Name
   404  		baseHost         string
   405  		hosts            []host.Name
   406  		specificHostsMap sets.Set[host.Name]
   407  		wildcardHostsMap sets.Set[host.Name]
   408  		time             int
   409  		matches          bool
   410  	}{
   411  		{"10DestRuleWildcardNoMatch", host.Name("foo.bar.com.10"), "foo.bar.com", []host.Name{}, nil, nil, 10, false},
   412  		{"50DestRuleWildcardNoMatch", host.Name("foo.bar.com.50"), "foo.bar.com", []host.Name{}, nil, nil, 50, false},
   413  		{"100DestRuleWildcardNoMatch", host.Name("foo.bar.com.100"), "foo.bar.com", []host.Name{}, nil, nil, 100, false},
   414  		{"1000DestRuleWildcardNoMatch", host.Name("foo.bar.com.1000"), "foo.bar.com", []host.Name{}, nil, nil, 1000, false},
   415  		{"5000DestRuleWildcardNoMatch", host.Name("foo.bar.com.5000"), "foo.bar.com", []host.Name{}, nil, nil, 5000, false},
   416  
   417  		{"10DestRuleWildcardMatch", host.Name("foo.bar.baz.com.10"), "bar.baz.com", []host.Name{}, nil, nil, 10, true},
   418  		{"50DestRuleWildcardMatch", host.Name("foo.bar.baz.com.50"), "bar.baz.com", []host.Name{}, nil, nil, 50, true},
   419  		{"100DestRuleWildcardMatch", host.Name("foo.bar.baz.com.100"), "bar.baz.com", []host.Name{}, nil, nil, 100, true},
   420  		{"1000DestRuleWildcardMatch", host.Name("foo.bar.baz.com.1000"), "bar.baz.com", []host.Name{}, nil, nil, 1000, true},
   421  		{"5000DestRuleWildcardMatch", host.Name("foo.bar.baz.com.5000"), "bar.baz.com", []host.Name{}, nil, nil, 5000, true},
   422  
   423  		{"10NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "foo.bar.com", []host.Name{}, nil, nil, 10, false},
   424  		{"50NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "foo.bar.com", []host.Name{}, nil, nil, 50, false},
   425  		{"100NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "foo.bar.com", []host.Name{}, nil, nil, 100, false},
   426  		{"1000NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "foo.bar.com", []host.Name{}, nil, nil, 1000, false},
   427  		{"5000NeedleWildcardNoMatch", host.Name("*.bar.foo.bar.com"), "foo.bar.com", []host.Name{}, nil, nil, 5000, false},
   428  
   429  		{"10NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.10"), "foo.bar.com", []host.Name{}, nil, nil, 10, true},
   430  		{"50NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.50"), "foo.bar.com", []host.Name{}, nil, nil, 50, true},
   431  		{"100NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.100"), "foo.bar.com", []host.Name{}, nil, nil, 100, true},
   432  		{"1000NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.1000"), "foo.bar.com", []host.Name{}, nil, nil, 1000, true},
   433  		{"5000NeedleWildcardMatch", host.Name("*.bar.foo.bar.com.5000"), "foo.bar.com", []host.Name{}, nil, nil, 5000, true},
   434  	}
   435  
   436  	for _, bm := range benchmarks {
   437  		bm.specificHostsMap = make(map[host.Name]struct{}, bm.time)
   438  		bm.wildcardHostsMap = make(map[host.Name]struct{}, bm.time)
   439  
   440  		for i := 1; i <= bm.time; i++ {
   441  			// these specific non-wildcard hosts are crafted this way to never match the needle,
   442  			// this should replicate real-world scenarios of mixed specific and wildcard hosts
   443  			specific := host.Name(strconv.Itoa(i) + "." + bm.baseHost)
   444  			// generate correct wildcard hosts, one of these will match
   445  			wildcard := host.Name("*." + bm.baseHost + "." + strconv.Itoa(i))
   446  
   447  			bm.specificHostsMap[specific] = struct{}{}
   448  			bm.wildcardHostsMap[wildcard] = struct{}{}
   449  		}
   450  
   451  		b.Run(bm.name, func(b *testing.B) {
   452  			for n := 0; n < b.N; n++ {
   453  				_, _, ok := model.MostSpecificHostMatch(bm.needle, bm.specificHostsMap, bm.wildcardHostsMap)
   454  				if bm.matches != ok {
   455  					b.Fatalf("expected to find match")
   456  				}
   457  			}
   458  		})
   459  	}
   460  }
   461  
   462  func BenchmarkMostSpecificHostMatchMultiMatch(b *testing.B) {
   463  	benchmarks := []struct {
   464  		name             string
   465  		needle           host.Name
   466  		hosts            []host.Name
   467  		specificHostsMap map[host.Name]struct{}
   468  		wildcardHostsMap map[host.Name]struct{}
   469  	}{
   470  		{"DestRuleWildcard", host.Name("a.foo.bar.baz.com"), []host.Name{"*.foo.bar.baz.com", "*.bar.baz.com", "*.baz.com", "*.com"}, nil, nil},
   471  
   472  		{"NeedleWildcard", host.Name("*.a.foo.bar.baz.com"), []host.Name{"*.foo.bar.baz.com", "*.bar.baz.com", "*.baz.com", "*.com"}, nil, nil},
   473  	}
   474  
   475  	for _, bm := range benchmarks {
   476  		bm.specificHostsMap = sets.New[host.Name]()
   477  		bm.wildcardHostsMap = sets.NewWithLength[host.Name](len(bm.hosts))
   478  
   479  		for _, h := range bm.hosts {
   480  			if h.IsWildCarded() {
   481  				bm.wildcardHostsMap[h] = struct{}{}
   482  			} else {
   483  				bm.specificHostsMap[h] = struct{}{}
   484  			}
   485  		}
   486  
   487  		b.Run(bm.name, func(b *testing.B) {
   488  			for n := 0; n < b.N; n++ {
   489  				_, _, ok := model.MostSpecificHostMatch(bm.needle, bm.specificHostsMap, bm.wildcardHostsMap)
   490  				if !ok {
   491  					b.Fatalf("expected to find match")
   492  				}
   493  			}
   494  		})
   495  	}
   496  }
   497  
   498  func BenchmarkHashCode(b *testing.B) {
   499  	benchmarks := []struct {
   500  		name   string
   501  		config model.ConfigKey
   502  	}{
   503  		{
   504  			name: "small string",
   505  			config: model.ConfigKey{
   506  				Kind:      kind.VirtualService,
   507  				Name:      "abc",
   508  				Namespace: "ns-foo",
   509  			},
   510  		},
   511  		{
   512  			name: "middle string",
   513  			config: model.ConfigKey{
   514  				Kind:      kind.VirtualService,
   515  				Name:      "foo.svc.cluster.local.middle.len",
   516  				Namespace: "ns-foo-a-middle-string-with-len",
   517  			},
   518  		},
   519  		{
   520  			name: "long string",
   521  			config: model.ConfigKey{
   522  				Kind:      kind.VirtualService,
   523  				Name:      "foo.svc.cluster.local.middle.len.foo.svc.cluster.local.middle.len",
   524  				Namespace: "ns-foo-a-middle-string-with-len.ns-foo-a-middle-string-with-len",
   525  			},
   526  		},
   527  	}
   528  	for _, bm := range benchmarks {
   529  		b.Run(bm.name, func(b *testing.B) {
   530  			for n := 0; n < b.N; n++ {
   531  				bm.config.HashCode()
   532  			}
   533  		})
   534  	}
   535  }
   536  
   537  func TestHashCodeCollision(t *testing.T) {
   538  	config1 := model.ConfigKey{
   539  		Kind:      kind.VirtualService,
   540  		Name:      "abc",
   541  		Namespace: "ns-foo",
   542  	}
   543  
   544  	config2 := model.ConfigKey{
   545  		Kind:      kind.VirtualService,
   546  		Name:      "ab",
   547  		Namespace: "cns-foo",
   548  	}
   549  
   550  	if config1.HashCode() == config2.HashCode() {
   551  		t.Fatalf("Hash code of config1 %s should not be equal to config2 %s", config1.String(), config2.String())
   552  	}
   553  }