1/* 2 * 3 * Copyright 2017 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 19package grpc 20 21import ( 22 "context" 23 "errors" 24 "fmt" 25 "net" 26 "strings" 27 "testing" 28 "time" 29 30 "google.golang.org/grpc/balancer" 31 "google.golang.org/grpc/codes" 32 "google.golang.org/grpc/resolver" 33 "google.golang.org/grpc/resolver/manual" 34 "google.golang.org/grpc/serviceconfig" 35 "google.golang.org/grpc/status" 36) 37 38func (s) TestParseTarget(t *testing.T) { 39 for _, test := range []resolver.Target{ 40 {Scheme: "dns", Authority: "", Endpoint: "google.com"}, 41 {Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com"}, 42 {Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/?a=b"}, 43 {Scheme: "passthrough", Authority: "", Endpoint: "/unix/socket/address"}, 44 } { 45 str := test.Scheme + "://" + test.Authority + "/" + test.Endpoint 46 got := parseTarget(str) 47 if got != test { 48 t.Errorf("parseTarget(%q) = %+v, want %+v", str, got, test) 49 } 50 } 51} 52 53func (s) TestParseTargetString(t *testing.T) { 54 for _, test := range []struct { 55 targetStr string 56 want resolver.Target 57 }{ 58 {targetStr: "", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}}, 59 {targetStr: ":///", want: resolver.Target{Scheme: "", Authority: "", Endpoint: ""}}, 60 {targetStr: "a:///", want: resolver.Target{Scheme: "a", Authority: "", Endpoint: ""}}, 61 {targetStr: "://a/", want: resolver.Target{Scheme: "", Authority: "a", Endpoint: ""}}, 62 {targetStr: ":///a", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a"}}, 63 {targetStr: "a://b/", want: resolver.Target{Scheme: "a", Authority: "b", Endpoint: ""}}, 64 {targetStr: "a:///b", want: resolver.Target{Scheme: "a", Authority: "", Endpoint: "b"}}, 65 {targetStr: "://a/b", want: resolver.Target{Scheme: "", Authority: "a", Endpoint: "b"}}, 66 {targetStr: "a://b/c", want: resolver.Target{Scheme: "a", Authority: "b", Endpoint: "c"}}, 67 {targetStr: "dns:///google.com", want: resolver.Target{Scheme: "dns", Authority: "", Endpoint: "google.com"}}, 68 {targetStr: "dns://a.server.com/google.com", want: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com"}}, 69 {targetStr: "dns://a.server.com/google.com/?a=b", want: resolver.Target{Scheme: "dns", Authority: "a.server.com", Endpoint: "google.com/?a=b"}}, 70 71 {targetStr: "/", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "/"}}, 72 {targetStr: "google.com", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "google.com"}}, 73 {targetStr: "google.com/?a=b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "google.com/?a=b"}}, 74 {targetStr: "/unix/socket/address", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "/unix/socket/address"}}, 75 76 // If we can only parse part of the target. 77 {targetStr: "://", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "://"}}, 78 {targetStr: "unix://domain", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "unix://domain"}}, 79 {targetStr: "a:b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:b"}}, 80 {targetStr: "a/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a/b"}}, 81 {targetStr: "a:/b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a:/b"}}, 82 {targetStr: "a//b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a//b"}}, 83 {targetStr: "a://b", want: resolver.Target{Scheme: "", Authority: "", Endpoint: "a://b"}}, 84 } { 85 got := parseTarget(test.targetStr) 86 if got != test.want { 87 t.Errorf("parseTarget(%q) = %+v, want %+v", test.targetStr, got, test.want) 88 } 89 } 90} 91 92// The target string with unknown scheme should be kept unchanged and passed to 93// the dialer. 94func (s) TestDialParseTargetUnknownScheme(t *testing.T) { 95 for _, test := range []struct { 96 targetStr string 97 want string 98 }{ 99 {"/unix/socket/address", "/unix/socket/address"}, 100 101 // Special test for "unix:///". 102 {"unix:///unix/socket/address", "unix:///unix/socket/address"}, 103 104 // For known scheme. 105 {"passthrough://a.server.com/google.com", "google.com"}, 106 } { 107 dialStrCh := make(chan string, 1) 108 cc, err := Dial(test.targetStr, WithInsecure(), WithDialer(func(addr string, _ time.Duration) (net.Conn, error) { 109 select { 110 case dialStrCh <- addr: 111 default: 112 } 113 return nil, fmt.Errorf("test dialer, always error") 114 })) 115 if err != nil { 116 t.Fatalf("Failed to create ClientConn: %v", err) 117 } 118 got := <-dialStrCh 119 cc.Close() 120 if got != test.want { 121 t.Errorf("Dial(%q), dialer got %q, want %q", test.targetStr, got, test.want) 122 } 123 } 124} 125 126func testResolverErrorPolling(t *testing.T, badUpdate func(*manual.Resolver), goodUpdate func(*manual.Resolver), dopts ...DialOption) { 127 boIter := make(chan int) 128 resolverBackoff := func(v int) time.Duration { 129 boIter <- v 130 return 0 131 } 132 133 r, rcleanup := manual.GenerateAndRegisterManualResolver() 134 defer rcleanup() 135 rn := make(chan struct{}) 136 defer func() { close(rn) }() 137 r.ResolveNowCallback = func(resolver.ResolveNowOptions) { rn <- struct{}{} } 138 139 defaultDialOptions := []DialOption{ 140 WithInsecure(), 141 withResolveNowBackoff(resolverBackoff), 142 } 143 cc, err := Dial(r.Scheme()+":///test.server", append(defaultDialOptions, dopts...)...) 144 if err != nil { 145 t.Fatalf("Dial(_, _) = _, %v; want _, nil", err) 146 } 147 defer cc.Close() 148 badUpdate(r) 149 150 panicAfter := time.AfterFunc(5*time.Second, func() { panic("timed out polling resolver") }) 151 defer panicAfter.Stop() 152 153 // Ensure ResolveNow is called, then Backoff with the right parameter, several times 154 for i := 0; i < 7; i++ { 155 <-rn 156 if v := <-boIter; v != i { 157 t.Errorf("Backoff call %v uses value %v", i, v) 158 } 159 } 160 161 // UpdateState will block if ResolveNow is being called (which blocks on 162 // rn), so call it in a goroutine. 163 goodUpdate(r) 164 165 // Wait awhile to ensure ResolveNow and Backoff stop being called when the 166 // state is OK (i.e. polling was cancelled). 167 for { 168 t := time.NewTimer(50 * time.Millisecond) 169 select { 170 case <-rn: 171 // ClientConn is still calling ResolveNow 172 <-boIter 173 time.Sleep(5 * time.Millisecond) 174 continue 175 case <-t.C: 176 // ClientConn stopped calling ResolveNow; success 177 } 178 break 179 } 180} 181 182const happyBalancerName = "happy balancer" 183 184func init() { 185 // Register a balancer that never returns an error from 186 // UpdateClientConnState, and doesn't do anything else either. 187 fb := &funcBalancer{ 188 updateClientConnState: func(s balancer.ClientConnState) error { 189 return nil 190 }, 191 } 192 balancer.Register(&funcBalancerBuilder{name: happyBalancerName, instance: fb}) 193} 194 195// TestResolverErrorPolling injects resolver errors and verifies ResolveNow is 196// called with the appropriate backoff strategy being consulted between 197// ResolveNow calls. 198func (s) TestResolverErrorPolling(t *testing.T) { 199 testResolverErrorPolling(t, func(r *manual.Resolver) { 200 r.CC.ReportError(errors.New("res err")) 201 }, func(r *manual.Resolver) { 202 // UpdateState will block if ResolveNow is being called (which blocks on 203 // rn), so call it in a goroutine. 204 go r.CC.UpdateState(resolver.State{}) 205 }, 206 WithDefaultServiceConfig(fmt.Sprintf(`{ "loadBalancingConfig": [{"%v": {}}] }`, happyBalancerName))) 207} 208 209// TestServiceConfigErrorPolling injects a service config error and verifies 210// ResolveNow is called with the appropriate backoff strategy being consulted 211// between ResolveNow calls. 212func (s) TestServiceConfigErrorPolling(t *testing.T) { 213 testResolverErrorPolling(t, func(r *manual.Resolver) { 214 badsc := r.CC.ParseServiceConfig("bad config") 215 r.UpdateState(resolver.State{ServiceConfig: badsc}) 216 }, func(r *manual.Resolver) { 217 // UpdateState will block if ResolveNow is being called (which blocks on 218 // rn), so call it in a goroutine. 219 go r.CC.UpdateState(resolver.State{}) 220 }, 221 WithDefaultServiceConfig(fmt.Sprintf(`{ "loadBalancingConfig": [{"%v": {}}] }`, happyBalancerName))) 222} 223 224// TestResolverErrorInBuild makes the resolver.Builder call into the ClientConn 225// during the Build call. We use two separate mutexes in the code which make 226// sure there is no data race in this code path, and also that there is no 227// deadlock. 228func (s) TestResolverErrorInBuild(t *testing.T) { 229 r, rcleanup := manual.GenerateAndRegisterManualResolver() 230 defer rcleanup() 231 r.InitialState(resolver.State{ServiceConfig: &serviceconfig.ParseResult{Err: errors.New("resolver build err")}}) 232 233 cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) 234 if err != nil { 235 t.Fatalf("Dial(_, _) = _, %v; want _, nil", err) 236 } 237 defer cc.Close() 238 239 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 240 defer cancel() 241 var dummy int 242 const wantMsg = "error parsing service config" 243 const wantCode = codes.Unavailable 244 if err := cc.Invoke(ctx, "/foo/bar", &dummy, &dummy); status.Code(err) != wantCode || !strings.Contains(status.Convert(err).Message(), wantMsg) { 245 t.Fatalf("cc.Invoke(_, _, _, _) = %v; want status.Code()==%v, status.Message() contains %q", err, wantCode, wantMsg) 246 } 247} 248 249func (s) TestServiceConfigErrorRPC(t *testing.T) { 250 r, rcleanup := manual.GenerateAndRegisterManualResolver() 251 defer rcleanup() 252 253 cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) 254 if err != nil { 255 t.Fatalf("Dial(_, _) = _, %v; want _, nil", err) 256 } 257 defer cc.Close() 258 badsc := r.CC.ParseServiceConfig("bad config") 259 r.UpdateState(resolver.State{ServiceConfig: badsc}) 260 261 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 262 defer cancel() 263 var dummy int 264 const wantMsg = "error parsing service config" 265 const wantCode = codes.Unavailable 266 if err := cc.Invoke(ctx, "/foo/bar", &dummy, &dummy); status.Code(err) != wantCode || !strings.Contains(status.Convert(err).Message(), wantMsg) { 267 t.Fatalf("cc.Invoke(_, _, _, _) = %v; want status.Code()==%v, status.Message() contains %q", err, wantCode, wantMsg) 268 } 269} 270