google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/xdsresource/matcher_test.go (about) 1 /* 2 * 3 * Copyright 2020 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package xdsresource 19 20 import ( 21 "context" 22 rand "math/rand/v2" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "google.golang.org/grpc/internal/grpcutil" 27 iresolver "google.golang.org/grpc/internal/resolver" 28 "google.golang.org/grpc/internal/xds/matcher" 29 "google.golang.org/grpc/metadata" 30 "google.golang.org/protobuf/proto" 31 ) 32 33 func (s) TestAndMatcherMatch(t *testing.T) { 34 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 35 defer cancel() 36 tests := []struct { 37 name string 38 pm pathMatcher 39 hm matcher.HeaderMatcher 40 info iresolver.RPCInfo 41 want bool 42 }{ 43 { 44 name: "both match", 45 pm: newPathExactMatcher("/a/b", false), 46 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 47 info: iresolver.RPCInfo{ 48 Method: "/a/b", 49 Context: metadata.NewOutgoingContext(ctx, metadata.Pairs("th", "tv")), 50 }, 51 want: true, 52 }, 53 { 54 name: "both match with path case insensitive", 55 pm: newPathExactMatcher("/A/B", true), 56 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 57 info: iresolver.RPCInfo{ 58 Method: "/a/b", 59 Context: metadata.NewOutgoingContext(ctx, metadata.Pairs("th", "tv")), 60 }, 61 want: true, 62 }, 63 { 64 name: "only one match", 65 pm: newPathExactMatcher("/a/b", false), 66 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 67 info: iresolver.RPCInfo{ 68 Method: "/z/y", 69 Context: metadata.NewOutgoingContext(ctx, metadata.Pairs("th", "tv")), 70 }, 71 want: false, 72 }, 73 { 74 name: "both not match", 75 pm: newPathExactMatcher("/z/y", false), 76 hm: matcher.NewHeaderExactMatcher("th", "abc", false), 77 info: iresolver.RPCInfo{ 78 Method: "/a/b", 79 Context: metadata.NewOutgoingContext(ctx, metadata.Pairs("th", "tv")), 80 }, 81 want: false, 82 }, 83 { 84 name: "fake header", 85 pm: newPathPrefixMatcher("/", false), 86 hm: matcher.NewHeaderExactMatcher("content-type", "fake", false), 87 info: iresolver.RPCInfo{ 88 Method: "/a/b", 89 Context: grpcutil.WithExtraMetadata(ctx, metadata.Pairs( 90 "content-type", "fake", 91 )), 92 }, 93 want: true, 94 }, 95 { 96 name: "binary header", 97 pm: newPathPrefixMatcher("/", false), 98 hm: matcher.NewHeaderPresentMatcher("t-bin", true, false), 99 info: iresolver.RPCInfo{ 100 Method: "/a/b", 101 Context: grpcutil.WithExtraMetadata( 102 metadata.NewOutgoingContext(ctx, metadata.Pairs("t-bin", "123")), metadata.Pairs( 103 "content-type", "fake", 104 )), 105 }, 106 // Shouldn't match binary header, even though it's in metadata. 107 want: false, 108 }, 109 } 110 for _, tt := range tests { 111 t.Run(tt.name, func(t *testing.T) { 112 a := newCompositeMatcher(tt.pm, []matcher.HeaderMatcher{tt.hm}, nil) 113 if got := a.Match(tt.info); got != tt.want { 114 t.Errorf("match() = %v, want %v", got, tt.want) 115 } 116 }) 117 } 118 } 119 120 func (s) TestFractionMatcherMatch(t *testing.T) { 121 const fraction = 500000 122 fm := newFractionMatcher(fraction) 123 defer func() { 124 RandInt64n = rand.Int64N 125 }() 126 127 // rand > fraction, should return false. 128 RandInt64n = func(int64) int64 { 129 return fraction + 1 130 } 131 if matched := fm.match(); matched { 132 t.Errorf("match() = %v, want not match", matched) 133 } 134 135 // rand == fraction, should return true. 136 RandInt64n = func(int64) int64 { 137 return fraction 138 } 139 if matched := fm.match(); !matched { 140 t.Errorf("match() = %v, want match", matched) 141 } 142 143 // rand < fraction, should return true. 144 RandInt64n = func(int64) int64 { 145 return fraction - 1 146 } 147 if matched := fm.match(); !matched { 148 t.Errorf("match() = %v, want match", matched) 149 } 150 } 151 152 func (s) TestMatchTypeForDomain(t *testing.T) { 153 tests := []struct { 154 d string 155 want domainMatchType 156 }{ 157 {d: "", want: domainMatchTypeInvalid}, 158 {d: "*", want: domainMatchTypeUniversal}, 159 {d: "bar.*", want: domainMatchTypePrefix}, 160 {d: "*.abc.com", want: domainMatchTypeSuffix}, 161 {d: "foo.bar.com", want: domainMatchTypeExact}, 162 {d: "foo.*.com", want: domainMatchTypeInvalid}, 163 } 164 for _, tt := range tests { 165 if got := matchTypeForDomain(tt.d); got != tt.want { 166 t.Errorf("matchTypeForDomain(%q) = %v, want %v", tt.d, got, tt.want) 167 } 168 } 169 } 170 171 func (s) TestMatch(t *testing.T) { 172 tests := []struct { 173 name string 174 domain string 175 host string 176 wantTyp domainMatchType 177 wantMatched bool 178 }{ 179 {name: "invalid-empty", domain: "", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false}, 180 {name: "invalid", domain: "a.*.b", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false}, 181 {name: "universal", domain: "*", host: "abc.com", wantTyp: domainMatchTypeUniversal, wantMatched: true}, 182 {name: "prefix-match", domain: "abc.*", host: "abc.123", wantTyp: domainMatchTypePrefix, wantMatched: true}, 183 {name: "prefix-no-match", domain: "abc.*", host: "abcd.123", wantTyp: domainMatchTypePrefix, wantMatched: false}, 184 {name: "suffix-match", domain: "*.123", host: "abc.123", wantTyp: domainMatchTypeSuffix, wantMatched: true}, 185 {name: "suffix-no-match", domain: "*.123", host: "abc.1234", wantTyp: domainMatchTypeSuffix, wantMatched: false}, 186 {name: "exact-match", domain: "foo.bar", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: true}, 187 {name: "exact-no-match", domain: "foo.bar.com", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: false}, 188 } 189 for _, tt := range tests { 190 t.Run(tt.name, func(t *testing.T) { 191 if gotTyp, gotMatched := match(tt.domain, tt.host); gotTyp != tt.wantTyp || gotMatched != tt.wantMatched { 192 t.Errorf("match() = %v, %v, want %v, %v", gotTyp, gotMatched, tt.wantTyp, tt.wantMatched) 193 } 194 }) 195 } 196 } 197 198 func (s) TestFindBestMatchingVirtualHost(t *testing.T) { 199 var ( 200 oneExactMatch = &VirtualHost{Domains: []string{"foo.bar.com"}} 201 oneSuffixMatch = &VirtualHost{Domains: []string{"*.bar.com"}} 202 onePrefixMatch = &VirtualHost{Domains: []string{"foo.bar.*"}} 203 oneUniversalMatch = &VirtualHost{Domains: []string{"*"}} 204 longExactMatch = &VirtualHost{Domains: []string{"v2.foo.bar.com"}} 205 multipleMatch = &VirtualHost{Domains: []string{"pi.foo.bar.com", "314.*", "*.159"}} 206 vhs = []*VirtualHost{oneExactMatch, oneSuffixMatch, onePrefixMatch, oneUniversalMatch, longExactMatch, multipleMatch} 207 ) 208 209 tests := []struct { 210 name string 211 host string 212 vHosts []*VirtualHost 213 want *VirtualHost 214 }{ 215 {name: "exact-match", host: "foo.bar.com", vHosts: vhs, want: oneExactMatch}, 216 {name: "suffix-match", host: "123.bar.com", vHosts: vhs, want: oneSuffixMatch}, 217 {name: "prefix-match", host: "foo.bar.org", vHosts: vhs, want: onePrefixMatch}, 218 {name: "universal-match", host: "abc.123", vHosts: vhs, want: oneUniversalMatch}, 219 {name: "long-exact-match", host: "v2.foo.bar.com", vHosts: vhs, want: longExactMatch}, 220 // Matches suffix "*.bar.com" and exact "pi.foo.bar.com". Takes exact. 221 {name: "multiple-match-exact", host: "pi.foo.bar.com", vHosts: vhs, want: multipleMatch}, 222 // Matches suffix "*.159" and prefix "foo.bar.*". Takes suffix. 223 {name: "multiple-match-suffix", host: "foo.bar.159", vHosts: vhs, want: multipleMatch}, 224 // Matches suffix "*.bar.com" and prefix "314.*". Takes suffix. 225 {name: "multiple-match-prefix", host: "314.bar.com", vHosts: vhs, want: oneSuffixMatch}, 226 } 227 for _, tt := range tests { 228 t.Run(tt.name, func(t *testing.T) { 229 if got := FindBestMatchingVirtualHost(tt.host, tt.vHosts); !cmp.Equal(got, tt.want, cmp.Comparer(proto.Equal)) { 230 t.Errorf("FindBestMatchingxdsclient.VirtualHost() = %v, want %v", got, tt.want) 231 } 232 }) 233 } 234 }