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