From 125f3c3f9a014febf8df073e78a03c4cdb490aa0 Mon Sep 17 00:00:00 2001 From: Chris Ayoub Date: Tue, 29 Mar 2022 13:38:21 -0400 Subject: [PATCH] clientv3: filter learners members during autosync This change is to ensure that all members returned during the client's AutoSync are started and are not learners, which are not valid etcd members to make requests to. --- client/v3/client.go | 4 +++- client/v3/client_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/client/v3/client.go b/client/v3/client.go index 971fea607..747dc6d9c 100644 --- a/client/v3/client.go +++ b/client/v3/client.go @@ -186,7 +186,9 @@ func (c *Client) Sync(ctx context.Context) error { } var eps []string for _, m := range mresp.Members { - eps = append(eps, m.ClientURLs...) + if len(m.Name) != 0 && !m.IsLearner { + eps = append(eps, m.ClientURLs...) + } } c.SetEndpoints(eps...) return nil diff --git a/client/v3/client_test.go b/client/v3/client_test.go index b8c62a95b..da6f39de6 100644 --- a/client/v3/client_test.go +++ b/client/v3/client_test.go @@ -244,6 +244,24 @@ func TestAuthTokenBundleNoOverwrite(t *testing.T) { } } +func TestSyncFiltersMembers(t *testing.T) { + c, _ := NewClient(t, Config{Endpoints: []string{"http://254.0.0.1:12345"}}) + defer c.Close() + c.Cluster = &mockCluster{ + []*etcdserverpb.Member{ + {ID: 0, Name: "", ClientURLs: []string{"http://254.0.0.1:12345"}, IsLearner: false}, + {ID: 1, Name: "isStarted", ClientURLs: []string{"http://254.0.0.2:12345"}, IsLearner: true}, + {ID: 2, Name: "isStartedAndNotLearner", ClientURLs: []string{"http://254.0.0.3:12345"}, IsLearner: false}, + }, + } + c.Sync(context.Background()) + + endpoints := c.Endpoints() + if len(endpoints) != 1 || endpoints[0] != "http://254.0.0.3:12345" { + t.Error("Client.Sync uses learner and/or non-started member client URLs") + } +} + type mockAuthServer struct { *etcdserverpb.UnimplementedAuthServer } @@ -251,3 +269,31 @@ type mockAuthServer struct { func (mockAuthServer) Authenticate(context.Context, *etcdserverpb.AuthenticateRequest) (*etcdserverpb.AuthenticateResponse, error) { return &etcdserverpb.AuthenticateResponse{Token: "mock-token"}, nil } + +type mockCluster struct { + members []*etcdserverpb.Member +} + +func (mc *mockCluster) MemberList(ctx context.Context) (*MemberListResponse, error) { + return &MemberListResponse{Members: mc.members}, nil +} + +func (mc *mockCluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { + return nil, nil +} + +func (mc *mockCluster) MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { + return nil, nil +} + +func (mc *mockCluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) { + return nil, nil +} + +func (mc *mockCluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) { + return nil, nil +} + +func (mc *mockCluster) MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error) { + return nil, nil +}