mirror of
https://github.com/etcd-io/etcd.git
synced 2024-09-27 06:25:44 +00:00
etcdserver: allow 1 learner in cluster
Hard-coded the maximum number of learners to 1.
This commit is contained in:
parent
c438f6db27
commit
aa4cda2f5c
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user