etcdserver: allow 1 learner in cluster

Hard-coded the maximum number of learners to 1.
This commit is contained in:
Jingyi Hu 2019-04-26 18:16:51 -07:00
parent c438f6db27
commit aa4cda2f5c
5 changed files with 62 additions and 4 deletions

View File

@ -259,3 +259,43 @@ func TestMemberPromoteForLearner(t *testing.T) {
t.Errorf("learner promoted, expect 0 learner, got %d", numberOfLearners) t.Errorf("learner promoted, expect 0 learner, got %d", numberOfLearners)
} }
} }
// TestMaxLearnerInCluster verifies that the maximum number of learners allowed in a cluster is 1
func TestMaxLearnerInCluster(t *testing.T) {
defer testutil.AfterTest(t)
// 1. start with a cluster with 3 voting member and 0 learner member
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
defer clus.Terminate(t)
// 2. adding a learner member should succeed
resp1, err := clus.Client(0).MemberAddAsLearner(context.Background(), []string{"http://127.0.0.1:1234"})
if err != nil {
t.Fatalf("failed to add learner member %v", err)
}
numberOfLearners := 0
for _, m := range resp1.Members {
if m.IsLearner {
numberOfLearners++
}
}
if numberOfLearners != 1 {
t.Fatalf("Added 1 learner node to cluster, got %d", numberOfLearners)
}
// 3. cluster has 3 voting member and 1 learner, adding another learner should fail
_, err = clus.Client(0).MemberAddAsLearner(context.Background(), []string{"http://127.0.0.1:2345"})
if err == nil {
t.Fatalf("expect member add to fail, got no error")
}
expectedErrKeywords := "too many learner members in cluster"
if !strings.Contains(err.Error(), expectedErrKeywords) {
t.Fatalf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
}
// 4. cluster has 3 voting member and 1 learner, adding a voting member should succeed
_, err = clus.Client(0).MemberAdd(context.Background(), []string{"http://127.0.0.1:3456"})
if err != nil {
t.Errorf("failed to add member %v", err)
}
}

View File

@ -40,6 +40,8 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
const maxLearners = 1
// RaftCluster is a list of Members that belong to the same raft cluster // RaftCluster is a list of Members that belong to the same raft cluster
type RaftCluster struct { type RaftCluster struct {
lg *zap.Logger lg *zap.Logger
@ -292,16 +294,15 @@ func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error {
plog.Panicf("unmarshal confChangeContext should never fail: %v", err) plog.Panicf("unmarshal confChangeContext should never fail: %v", err)
} }
} }
// A ConfChangeAddNode to a existing learner node promotes it to a voting member.
if confChangeContext.IsPromote { if confChangeContext.IsPromote { // promoting a learner member to voting member
if members[id] == nil { if members[id] == nil {
return ErrIDNotFound return ErrIDNotFound
} }
if !members[id].IsLearner { if !members[id].IsLearner {
return ErrMemberNotLearner return ErrMemberNotLearner
} }
} else { } else { // adding a new member
// add a learner or a follower case
if members[id] != nil { if members[id] != nil {
return ErrIDExists return ErrIDExists
} }
@ -317,6 +318,18 @@ func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error {
return ErrPeerURLexists return ErrPeerURLexists
} }
} }
if confChangeContext.Member.IsLearner { // the new member is a learner
numLearners := 0
for _, m := range members {
if m.IsLearner {
numLearners++
}
}
if numLearners+1 > maxLearners {
return ErrTooManyLearners
}
}
} }
case raftpb.ConfChangeRemoveNode: case raftpb.ConfChangeRemoveNode:
if members[id] == nil { if members[id] == nil {

View File

@ -27,6 +27,7 @@ var (
ErrPeerURLexists = errors.New("membership: peerURL exists") ErrPeerURLexists = errors.New("membership: peerURL exists")
ErrMemberNotLearner = errors.New("membership: can only promote a learner member") ErrMemberNotLearner = errors.New("membership: can only promote a learner member")
ErrLearnerNotReady = errors.New("membership: can only promote a learner member which is in sync with leader") ErrLearnerNotReady = errors.New("membership: can only promote a learner member which is in sync with leader")
ErrTooManyLearners = errors.New("membership: too many learner members in cluster")
) )
func isKeyNotFound(err error) bool { func isKeyNotFound(err error) bool {

View File

@ -42,6 +42,7 @@ var (
ErrGRPCMemberNotFound = status.New(codes.NotFound, "etcdserver: member not found").Err() ErrGRPCMemberNotFound = status.New(codes.NotFound, "etcdserver: member not found").Err()
ErrGRPCMemberNotLearner = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member").Err() ErrGRPCMemberNotLearner = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member").Err()
ErrGRPCLearnerNotReady = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member which is in sync with leader").Err() ErrGRPCLearnerNotReady = status.New(codes.FailedPrecondition, "etcdserver: can only promote a learner member which is in sync with leader").Err()
ErrGRPCTooManyLearners = status.New(codes.FailedPrecondition, "etcdserver: too many learner members in cluster").Err()
ErrGRPCRequestTooLarge = status.New(codes.InvalidArgument, "etcdserver: request is too large").Err() ErrGRPCRequestTooLarge = status.New(codes.InvalidArgument, "etcdserver: request is too large").Err()
ErrGRPCRequestTooManyRequests = status.New(codes.ResourceExhausted, "etcdserver: too many requests").Err() ErrGRPCRequestTooManyRequests = status.New(codes.ResourceExhausted, "etcdserver: too many requests").Err()
@ -97,6 +98,7 @@ var (
ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound, ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound,
ErrorDesc(ErrGRPCMemberNotLearner): ErrGRPCMemberNotLearner, ErrorDesc(ErrGRPCMemberNotLearner): ErrGRPCMemberNotLearner,
ErrorDesc(ErrGRPCLearnerNotReady): ErrGRPCLearnerNotReady, ErrorDesc(ErrGRPCLearnerNotReady): ErrGRPCLearnerNotReady,
ErrorDesc(ErrGRPCTooManyLearners): ErrGRPCTooManyLearners,
ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge, ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge,
ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests, ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests,
@ -154,6 +156,7 @@ var (
ErrMemberNotFound = Error(ErrGRPCMemberNotFound) ErrMemberNotFound = Error(ErrGRPCMemberNotFound)
ErrMemberNotLearner = Error(ErrGRPCMemberNotLearner) ErrMemberNotLearner = Error(ErrGRPCMemberNotLearner)
ErrMemberLearnerNotReady = Error(ErrGRPCLearnerNotReady) ErrMemberLearnerNotReady = Error(ErrGRPCLearnerNotReady)
ErrTooManyLearners = Error(ErrGRPCTooManyLearners)
ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge) ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge)
ErrTooManyRequests = Error(ErrGRPCRequestTooManyRequests) ErrTooManyRequests = Error(ErrGRPCRequestTooManyRequests)

View File

@ -37,6 +37,7 @@ var toGRPCErrorMap = map[error]error{
membership.ErrPeerURLexists: rpctypes.ErrGRPCPeerURLExist, membership.ErrPeerURLexists: rpctypes.ErrGRPCPeerURLExist,
membership.ErrMemberNotLearner: rpctypes.ErrGRPCMemberNotLearner, membership.ErrMemberNotLearner: rpctypes.ErrGRPCMemberNotLearner,
membership.ErrLearnerNotReady: rpctypes.ErrGRPCLearnerNotReady, membership.ErrLearnerNotReady: rpctypes.ErrGRPCLearnerNotReady,
membership.ErrTooManyLearners: rpctypes.ErrGRPCTooManyLearners,
etcdserver.ErrNotEnoughStartedMembers: rpctypes.ErrMemberNotEnoughStarted, etcdserver.ErrNotEnoughStartedMembers: rpctypes.ErrMemberNotEnoughStarted,
mvcc.ErrCompacted: rpctypes.ErrGRPCCompacted, mvcc.ErrCompacted: rpctypes.ErrGRPCCompacted,