From d0c1b3fa3876fdf9ba4ca378d77dff79b5d08719 Mon Sep 17 00:00:00 2001 From: Jingyi Hu Date: Mon, 22 Apr 2019 19:08:03 -0700 Subject: [PATCH] etcdserver: learner return Unavailable for unsupported RPC Make learner return code.Unavailable when the request is not supported by learner. Client balancer will retry a different endpoint. --- clientv3/integration/kv_test.go | 46 ++++++++++++++++++++++++++ etcdserver/api/v3rpc/interceptor.go | 1 - etcdserver/api/v3rpc/rpctypes/error.go | 3 +- integration/cluster.go | 4 +++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index b71b98d06..45120d9bc 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -1051,3 +1051,49 @@ func TestKVForLearner(t *testing.T) { } } } + +// TestBalancerSupportLearner verifies that balancer's retry and failover mechanism supports cluster with learner member +func TestBalancerSupportLearner(t *testing.T) { + defer testutil.AfterTest(t) + + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) + defer clus.Terminate(t) + + // we have to add and launch learner member after initial cluster was created, because + // bootstrapping a cluster with learner member is not supported. + clus.AddAndLaunchLearnerMember(t) + + learners, err := clus.GetLearnerMembers() + if err != nil { + t.Fatalf("failed to get the learner members in cluster: %v", err) + } + if len(learners) != 1 { + t.Fatalf("added 1 learner to cluster, got %d", len(learners)) + } + + // clus.Members[3] is the newly added learner member, which was appended to clus.Members + learnerEp := clus.Members[3].GRPCAddr() + cfg := clientv3.Config{ + Endpoints: []string{learnerEp}, + DialTimeout: 5 * time.Second, + DialOptions: []grpc.DialOption{grpc.WithBlock()}, + } + cli, err := clientv3.New(cfg) + if err != nil { + t.Fatalf("failed to create clientv3: %v", err) + } + defer cli.Close() + + // wait until learner member is ready + <-clus.Members[3].ReadyNotify() + + if _, err := cli.Get(context.Background(), "foo"); err == nil { + t.Fatalf("expect Get request to learner to fail, got no error") + } + + eps := []string{learnerEp, clus.Members[0].GRPCAddr()} + cli.SetEndpoints(eps...) + if _, err := cli.Get(context.Background(), "foo"); err != nil { + t.Errorf("expect no error (balancer should retry when request to learner fails), got error: %v", err) + } +} diff --git a/etcdserver/api/v3rpc/interceptor.go b/etcdserver/api/v3rpc/interceptor.go index e6fe0a7f0..9882968c7 100644 --- a/etcdserver/api/v3rpc/interceptor.go +++ b/etcdserver/api/v3rpc/interceptor.go @@ -48,7 +48,6 @@ func newUnaryInterceptor(s *etcdserver.EtcdServer) grpc.UnaryServerInterceptor { return nil, rpctypes.ErrGRPCNotCapable } - // TODO: add test in clientv3/integration to verify behavior if s.IsLearner() && !isRPCSupportedForLearner(req) { return nil, rpctypes.ErrGPRCNotSupportedForLearner } diff --git a/etcdserver/api/v3rpc/rpctypes/error.go b/etcdserver/api/v3rpc/rpctypes/error.go index a3cc87486..12021daf6 100644 --- a/etcdserver/api/v3rpc/rpctypes/error.go +++ b/etcdserver/api/v3rpc/rpctypes/error.go @@ -71,7 +71,7 @@ var ( ErrGRPCTimeoutDueToConnectionLost = status.New(codes.Unavailable, "etcdserver: request timed out, possibly due to connection lost").Err() ErrGRPCUnhealthy = status.New(codes.Unavailable, "etcdserver: unhealthy cluster").Err() ErrGRPCCorrupt = status.New(codes.DataLoss, "etcdserver: corrupt cluster").Err() - ErrGPRCNotSupportedForLearner = status.New(codes.FailedPrecondition, "etcdserver: rpc not supported for learner").Err() + ErrGPRCNotSupportedForLearner = status.New(codes.Unavailable, "etcdserver: rpc not supported for learner").Err() ErrGRPCBadLeaderTransferee = status.New(codes.FailedPrecondition, "etcdserver: bad leader transferee").Err() errStringToError = map[string]error{ @@ -126,6 +126,7 @@ var ( ErrorDesc(ErrGRPCTimeoutDueToConnectionLost): ErrGRPCTimeoutDueToConnectionLost, ErrorDesc(ErrGRPCUnhealthy): ErrGRPCUnhealthy, ErrorDesc(ErrGRPCCorrupt): ErrGRPCCorrupt, + ErrorDesc(ErrGPRCNotSupportedForLearner): ErrGPRCNotSupportedForLearner, ErrorDesc(ErrGRPCBadLeaderTransferee): ErrGRPCBadLeaderTransferee, } ) diff --git a/integration/cluster.go b/integration/cluster.go index 9a3c8d46a..fb6967a28 100644 --- a/integration/cluster.go +++ b/integration/cluster.go @@ -1166,6 +1166,10 @@ func (m *member) RecoverPartition(t testing.TB, others ...*member) { } } +func (m *member) ReadyNotify() <-chan struct{} { + return m.s.ReadyNotify() +} + func MustNewHTTPClient(t testing.TB, eps []string, tls *transport.TLSInfo) client.Client { cfgtls := transport.TLSInfo{} if tls != nil {