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