From 3679c0adb2d2f774eb60c1da735ec095fe6c5b8e Mon Sep 17 00:00:00 2001 From: Doug Fawley Date: Wed, 18 Sep 2019 13:16:20 -0700 Subject: [PATCH] add tests --- balancer_conn_wrappers_test.go | 124 +++++++++++++++++++++++++++++++++ resolver/manual/manual.go | 13 +++- resolver_conn_wrapper.go | 9 ++- resolver_conn_wrapper_test.go | 54 ++++++++++++++ 4 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 balancer_conn_wrappers_test.go diff --git a/balancer_conn_wrappers_test.go b/balancer_conn_wrappers_test.go new file mode 100644 index 00000000000..3739eab881d --- /dev/null +++ b/balancer_conn_wrappers_test.go @@ -0,0 +1,124 @@ +/* + * + * Copyright 2019 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package grpc + +import ( + "testing" + "time" + + "google.golang.org/grpc/balancer" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/resolver" + "google.golang.org/grpc/resolver/manual" +) + +var _ balancer.V2Balancer = &funcBalancer{} + +type funcBalancer struct { + updateClientConnState func(s balancer.ClientConnState) error +} + +func (*funcBalancer) HandleSubConnStateChange(balancer.SubConn, connectivity.State) { + panic("unimplemented") // v1 API +} +func (*funcBalancer) HandleResolvedAddrs([]resolver.Address, error) { + panic("unimplemented") // v1 API +} +func (b *funcBalancer) UpdateClientConnState(s balancer.ClientConnState) error { + return b.updateClientConnState(s) +} +func (*funcBalancer) ResolverError(error) { + panic("unimplemented") // resolver never reports error +} +func (*funcBalancer) UpdateSubConnState(balancer.SubConn, balancer.SubConnState) { + panic("unimplemented") // we never have sub-conns +} +func (*funcBalancer) Close() {} + +type funcBalancerBuilder struct { + name string + instance *funcBalancer +} + +func (b *funcBalancerBuilder) Build(balancer.ClientConn, balancer.BuildOptions) balancer.Balancer { + return b.instance +} +func (b *funcBalancerBuilder) Name() string { return b.name } + +// TestResolverErrorPolling injects balancer errors and verifies ResolveNow is +// called on the resolver with the appropriate backoff strategy being consulted +// between ResolveNow calls. +func (s) TestBalancerErrorResolverPolling(t *testing.T) { + defer func(o func(int) time.Duration) { resolverBackoff = o }(resolverBackoff) + + boTime := time.Duration(0) + boIter := make(chan int) + resolverBackoff = func(v int) time.Duration { + t := boTime + boIter <- v + return t + } + + r, rcleanup := manual.GenerateAndRegisterManualResolver() + defer rcleanup() + rn := make(chan struct{}) + defer func() { close(rn) }() + r.ResolveNowCallback = func(resolver.ResolveNowOption) { rn <- struct{}{} } + + uccsErr := make(chan error) + fb := &funcBalancer{ + updateClientConnState: func(s balancer.ClientConnState) error { + return <-uccsErr + }, + } + balancer.Register(&funcBalancerBuilder{name: "BalancerErrorResolverPolling", instance: fb}) + + cc, err := Dial(r.Scheme()+":///test.server", WithInsecure(), WithBalancerName("BalancerErrorResolverPolling")) + if err != nil { + t.Fatalf("Dial(_, _) = _, %v; want _, nil", err) + } + defer cc.Close() + go func() { uccsErr <- balancer.ErrBadResolverState }() + r.CC.UpdateState(resolver.State{}) + timer := time.AfterFunc(5*time.Second, func() { panic("timed out polling resolver") }) + // Ensure ResolveNow is called, then Backoff with the right parameter, several times + for i := 0; i < 7; i++ { + <-rn + if v := <-boIter; v != i { + t.Errorf("Backoff call %v uses value %v", i, v) + } + } + boTime = 50 * time.Millisecond + <-rn + <-boIter + go func() { uccsErr <- nil }() + r.CC.UpdateState(resolver.State{}) + boTime = 0 + timer.Stop() + // Wait awhile to ensure ResolveNow and Backoff are not called when the + // state is OK (i.e. polling was cancelled). + timer = time.NewTimer(60 * time.Millisecond) + select { + case <-boIter: + t.Errorf("Received Backoff call after successful resolver state") + case <-rn: + t.Errorf("Received ResolveNow after successful resolver state") + case <-timer.C: + } +} diff --git a/resolver/manual/manual.go b/resolver/manual/manual.go index 6b8d0e8a19c..766df467ca5 100644 --- a/resolver/manual/manual.go +++ b/resolver/manual/manual.go @@ -30,14 +30,19 @@ import ( // NewBuilderWithScheme creates a new test resolver builder with the given scheme. func NewBuilderWithScheme(scheme string) *Resolver { return &Resolver{ - scheme: scheme, + ResolveNowCallback: func(resolver.ResolveNowOption) {}, + scheme: scheme, } } // Resolver is also a resolver builder. // It's build() function always returns itself. type Resolver struct { - scheme string + // ResolveNowCallback is called when the ResolveNow method is called on the + // resolver. Must not be nil. Must not be changed after the resolver may + // be built. + ResolveNowCallback func(resolver.ResolveNowOption) + scheme string // Fields actually belong to the resolver. CC resolver.ClientConn @@ -65,7 +70,9 @@ func (r *Resolver) Scheme() string { } // ResolveNow is a noop for Resolver. -func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {} +func (r *Resolver) ResolveNow(o resolver.ResolveNowOption) { + r.ResolveNowCallback(o) +} // Close is a noop for Resolver. func (*Resolver) Close() {} diff --git a/resolver_conn_wrapper.go b/resolver_conn_wrapper.go index 36ae2ca8d47..c1470f2341a 100644 --- a/resolver_conn_wrapper.go +++ b/resolver_conn_wrapper.go @@ -117,7 +117,7 @@ func (ccr *ccResolverWrapper) close() { ccr.mu.Unlock() } -var resolverBackoff = backoff.Exponential{MaxDelay: 2 * time.Minute} +var resolverBackoff = backoff.Exponential{MaxDelay: 2 * time.Minute}.Backoff // poll begins or ends asynchronous polling of the resolver based on whether // err is ErrBadResolverState. @@ -141,7 +141,7 @@ func (ccr *ccResolverWrapper) poll(err error) { go func() { for i := 0; ; i++ { ccr.resolveNow(resolver.ResolveNowOption{}) - t := time.NewTimer(resolverBackoff.Backoff(i)) + t := time.NewTimer(resolverBackoff(i)) select { case <-p: t.Stop() @@ -151,6 +151,11 @@ func (ccr *ccResolverWrapper) poll(err error) { t.Stop() return case <-t.C: + select { + case <-p: + return + default: + } // Timer expired; re-resolve. } } diff --git a/resolver_conn_wrapper_test.go b/resolver_conn_wrapper_test.go index 4a16d7a88c9..1760f517f67 100644 --- a/resolver_conn_wrapper_test.go +++ b/resolver_conn_wrapper_test.go @@ -19,12 +19,14 @@ package grpc import ( + "errors" "fmt" "net" "testing" "time" "google.golang.org/grpc/resolver" + "google.golang.org/grpc/resolver/manual" ) func (s) TestParseTarget(t *testing.T) { @@ -114,3 +116,55 @@ func (s) TestDialParseTargetUnknownScheme(t *testing.T) { } } } + +// TestResolverErrorPolling injects resolver errors and verifies ResolveNow is +// called with the appropriate backoff strategy being consulted between +// ResolveNow calls. +func (s) TestResolverErrorPolling(t *testing.T) { + defer func(o func(int) time.Duration) { resolverBackoff = o }(resolverBackoff) + + boTime := time.Duration(0) + boIter := make(chan int) + resolverBackoff = func(v int) time.Duration { + t := boTime + boIter <- v + return t + } + + r, rcleanup := manual.GenerateAndRegisterManualResolver() + defer rcleanup() + rn := make(chan struct{}) + defer func() { close(rn) }() + r.ResolveNowCallback = func(resolver.ResolveNowOption) { rn <- struct{}{} } + + cc, err := Dial(r.Scheme()+":///test.server", WithInsecure()) + if err != nil { + t.Fatalf("Dial(_, _) = _, %v; want _, nil", err) + } + defer cc.Close() + r.CC.ReportError(errors.New("res err")) + timer := time.AfterFunc(5*time.Second, func() { panic("timed out polling resolver") }) + // Ensure ResolveNow is called, then Backoff with the right parameter, several times + for i := 0; i < 7; i++ { + <-rn + if v := <-boIter; v != i { + t.Errorf("Backoff call %v uses value %v", i, v) + } + } + boTime = 50 * time.Millisecond + <-rn + <-boIter + r.CC.UpdateState(resolver.State{}) + boTime = 0 + timer.Stop() + // Wait awhile to ensure ResolveNow and Backoff are not called when the + // state is OK (i.e. polling was cancelled). + timer = time.NewTimer(60 * time.Millisecond) + select { + case <-boIter: + t.Errorf("Received Backoff call after successful resolver state") + case <-rn: + t.Errorf("Received ResolveNow after successful resolver state") + case <-timer.C: + } +}