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