google.golang.org/grpc@v1.62.1/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 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "google.golang.org/grpc/internal/grpcrand" 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 tests := []struct { 35 name string 36 pm pathMatcher 37 hm matcher.HeaderMatcher 38 info iresolver.RPCInfo 39 want bool 40 }{ 41 { 42 name: "both match", 43 pm: newPathExactMatcher("/a/b", false), 44 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 45 info: iresolver.RPCInfo{ 46 Method: "/a/b", 47 Context: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")), 48 }, 49 want: true, 50 }, 51 { 52 name: "both match with path case insensitive", 53 pm: newPathExactMatcher("/A/B", true), 54 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 55 info: iresolver.RPCInfo{ 56 Method: "/a/b", 57 Context: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")), 58 }, 59 want: true, 60 }, 61 { 62 name: "only one match", 63 pm: newPathExactMatcher("/a/b", false), 64 hm: matcher.NewHeaderExactMatcher("th", "tv", false), 65 info: iresolver.RPCInfo{ 66 Method: "/z/y", 67 Context: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")), 68 }, 69 want: false, 70 }, 71 { 72 name: "both not match", 73 pm: newPathExactMatcher("/z/y", false), 74 hm: matcher.NewHeaderExactMatcher("th", "abc", false), 75 info: iresolver.RPCInfo{ 76 Method: "/a/b", 77 Context: metadata.NewOutgoingContext(context.Background(), metadata.Pairs("th", "tv")), 78 }, 79 want: false, 80 }, 81 { 82 name: "fake header", 83 pm: newPathPrefixMatcher("/", false), 84 hm: matcher.NewHeaderExactMatcher("content-type", "fake", false), 85 info: iresolver.RPCInfo{ 86 Method: "/a/b", 87 Context: grpcutil.WithExtraMetadata(context.Background(), metadata.Pairs( 88 "content-type", "fake", 89 )), 90 }, 91 want: true, 92 }, 93 { 94 name: "binary header", 95 pm: newPathPrefixMatcher("/", false), 96 hm: matcher.NewHeaderPresentMatcher("t-bin", true, false), 97 info: iresolver.RPCInfo{ 98 Method: "/a/b", 99 Context: grpcutil.WithExtraMetadata( 100 metadata.NewOutgoingContext(context.Background(), metadata.Pairs("t-bin", "123")), metadata.Pairs( 101 "content-type", "fake", 102 )), 103 }, 104 // Shouldn't match binary header, even though it's in metadata. 105 want: false, 106 }, 107 } 108 for _, tt := range tests { 109 t.Run(tt.name, func(t *testing.T) { 110 a := newCompositeMatcher(tt.pm, []matcher.HeaderMatcher{tt.hm}, nil) 111 if got := a.Match(tt.info); got != tt.want { 112 t.Errorf("match() = %v, want %v", got, tt.want) 113 } 114 }) 115 } 116 } 117 118 func (s) TestFractionMatcherMatch(t *testing.T) { 119 const fraction = 500000 120 fm := newFractionMatcher(fraction) 121 defer func() { 122 RandInt63n = grpcrand.Int63n 123 }() 124 125 // rand > fraction, should return false. 126 RandInt63n = func(n int64) int64 { 127 return fraction + 1 128 } 129 if matched := fm.match(); matched { 130 t.Errorf("match() = %v, want not match", matched) 131 } 132 133 // rand == fraction, should return true. 134 RandInt63n = func(n int64) int64 { 135 return fraction 136 } 137 if matched := fm.match(); !matched { 138 t.Errorf("match() = %v, want match", matched) 139 } 140 141 // rand < fraction, should return true. 142 RandInt63n = func(n int64) int64 { 143 return fraction - 1 144 } 145 if matched := fm.match(); !matched { 146 t.Errorf("match() = %v, want match", matched) 147 } 148 } 149 150 func (s) TestMatchTypeForDomain(t *testing.T) { 151 tests := []struct { 152 d string 153 want domainMatchType 154 }{ 155 {d: "", want: domainMatchTypeInvalid}, 156 {d: "*", want: domainMatchTypeUniversal}, 157 {d: "bar.*", want: domainMatchTypePrefix}, 158 {d: "*.abc.com", want: domainMatchTypeSuffix}, 159 {d: "foo.bar.com", want: domainMatchTypeExact}, 160 {d: "foo.*.com", want: domainMatchTypeInvalid}, 161 } 162 for _, tt := range tests { 163 if got := matchTypeForDomain(tt.d); got != tt.want { 164 t.Errorf("matchTypeForDomain(%q) = %v, want %v", tt.d, got, tt.want) 165 } 166 } 167 } 168 169 func (s) TestMatch(t *testing.T) { 170 tests := []struct { 171 name string 172 domain string 173 host string 174 wantTyp domainMatchType 175 wantMatched bool 176 }{ 177 {name: "invalid-empty", domain: "", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false}, 178 {name: "invalid", domain: "a.*.b", host: "", wantTyp: domainMatchTypeInvalid, wantMatched: false}, 179 {name: "universal", domain: "*", host: "abc.com", wantTyp: domainMatchTypeUniversal, wantMatched: true}, 180 {name: "prefix-match", domain: "abc.*", host: "abc.123", wantTyp: domainMatchTypePrefix, wantMatched: true}, 181 {name: "prefix-no-match", domain: "abc.*", host: "abcd.123", wantTyp: domainMatchTypePrefix, wantMatched: false}, 182 {name: "suffix-match", domain: "*.123", host: "abc.123", wantTyp: domainMatchTypeSuffix, wantMatched: true}, 183 {name: "suffix-no-match", domain: "*.123", host: "abc.1234", wantTyp: domainMatchTypeSuffix, wantMatched: false}, 184 {name: "exact-match", domain: "foo.bar", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: true}, 185 {name: "exact-no-match", domain: "foo.bar.com", host: "foo.bar", wantTyp: domainMatchTypeExact, wantMatched: false}, 186 } 187 for _, tt := range tests { 188 t.Run(tt.name, func(t *testing.T) { 189 if gotTyp, gotMatched := match(tt.domain, tt.host); gotTyp != tt.wantTyp || gotMatched != tt.wantMatched { 190 t.Errorf("match() = %v, %v, want %v, %v", gotTyp, gotMatched, tt.wantTyp, tt.wantMatched) 191 } 192 }) 193 } 194 } 195 196 func (s) TestFindBestMatchingVirtualHost(t *testing.T) { 197 var ( 198 oneExactMatch = &VirtualHost{Domains: []string{"foo.bar.com"}} 199 oneSuffixMatch = &VirtualHost{Domains: []string{"*.bar.com"}} 200 onePrefixMatch = &VirtualHost{Domains: []string{"foo.bar.*"}} 201 oneUniversalMatch = &VirtualHost{Domains: []string{"*"}} 202 longExactMatch = &VirtualHost{Domains: []string{"v2.foo.bar.com"}} 203 multipleMatch = &VirtualHost{Domains: []string{"pi.foo.bar.com", "314.*", "*.159"}} 204 vhs = []*VirtualHost{oneExactMatch, oneSuffixMatch, onePrefixMatch, oneUniversalMatch, longExactMatch, multipleMatch} 205 ) 206 207 tests := []struct { 208 name string 209 host string 210 vHosts []*VirtualHost 211 want *VirtualHost 212 }{ 213 {name: "exact-match", host: "foo.bar.com", vHosts: vhs, want: oneExactMatch}, 214 {name: "suffix-match", host: "123.bar.com", vHosts: vhs, want: oneSuffixMatch}, 215 {name: "prefix-match", host: "foo.bar.org", vHosts: vhs, want: onePrefixMatch}, 216 {name: "universal-match", host: "abc.123", vHosts: vhs, want: oneUniversalMatch}, 217 {name: "long-exact-match", host: "v2.foo.bar.com", vHosts: vhs, want: longExactMatch}, 218 // Matches suffix "*.bar.com" and exact "pi.foo.bar.com". Takes exact. 219 {name: "multiple-match-exact", host: "pi.foo.bar.com", vHosts: vhs, want: multipleMatch}, 220 // Matches suffix "*.159" and prefix "foo.bar.*". Takes suffix. 221 {name: "multiple-match-suffix", host: "foo.bar.159", vHosts: vhs, want: multipleMatch}, 222 // Matches suffix "*.bar.com" and prefix "314.*". Takes suffix. 223 {name: "multiple-match-prefix", host: "314.bar.com", vHosts: vhs, want: oneSuffixMatch}, 224 } 225 for _, tt := range tests { 226 t.Run(tt.name, func(t *testing.T) { 227 if got := FindBestMatchingVirtualHost(tt.host, tt.vHosts); !cmp.Equal(got, tt.want, cmp.Comparer(proto.Equal)) { 228 t.Errorf("FindBestMatchingxdsclient.VirtualHost() = %v, want %v", got, tt.want) 229 } 230 }) 231 } 232 }