integration: update TestMemberPromote test
Update TestMemberPromote to include both learner not-ready and learner ready test cases. Removed unit test TestPromoteMember, it requires underlying raft node to be started and running. The member promote is covered by the integration test.release-3.4
parent
3f94385fc6
commit
6bf609b96d
|
@ -19,6 +19,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"go.etcd.io/etcd/integration"
|
"go.etcd.io/etcd/integration"
|
||||||
"go.etcd.io/etcd/pkg/testutil"
|
"go.etcd.io/etcd/pkg/testutil"
|
||||||
|
@ -214,13 +215,19 @@ func TestMemberAddForLearner(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMemberPromoteForNotReadyLearner(t *testing.T) {
|
func TestMemberPromote(t *testing.T) {
|
||||||
defer testutil.AfterTest(t)
|
defer testutil.AfterTest(t)
|
||||||
|
|
||||||
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
|
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
|
||||||
defer clus.Terminate(t)
|
defer clus.Terminate(t)
|
||||||
// first client is talked to leader because cluster size is 1
|
|
||||||
capi := clus.Client(0)
|
// member promote request can be sent to any server in cluster,
|
||||||
|
// the request will be auto-forwarded to leader on server-side.
|
||||||
|
// This test explicitly includes the server-side forwarding by
|
||||||
|
// sending the request to follower.
|
||||||
|
leaderIdx := clus.WaitLeader(t)
|
||||||
|
followerIdx := (leaderIdx + 1) % 3
|
||||||
|
capi := clus.Client(followerIdx)
|
||||||
|
|
||||||
urls := []string{"http://127.0.0.1:1234"}
|
urls := []string{"http://127.0.0.1:1234"}
|
||||||
memberAddResp, err := capi.MemberAddAsLearner(context.Background(), urls)
|
memberAddResp, err := capi.MemberAddAsLearner(context.Background(), urls)
|
||||||
|
@ -243,14 +250,45 @@ func TestMemberPromoteForNotReadyLearner(t *testing.T) {
|
||||||
t.Fatalf("Added 1 learner node to cluster, got %d", numberOfLearners)
|
t.Fatalf("Added 1 learner node to cluster, got %d", numberOfLearners)
|
||||||
}
|
}
|
||||||
|
|
||||||
// since we do not start learner, learner must be not ready.
|
// learner is not started yet. Expect learner progress check to fail.
|
||||||
|
// As the result, member promote request will fail.
|
||||||
_, err = capi.MemberPromote(context.Background(), learnerID)
|
_, err = capi.MemberPromote(context.Background(), learnerID)
|
||||||
expectedErrKeywords := "can only promote a learner member which is in sync with leader"
|
expectedErrKeywords := "can only promote a learner member which is in sync with leader"
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expecting promote not ready learner to fail, got no error")
|
t.Fatalf("expecting promote not ready learner to fail, got no error")
|
||||||
}
|
}
|
||||||
if !strings.Contains(err.Error(), expectedErrKeywords) {
|
if !strings.Contains(err.Error(), expectedErrKeywords) {
|
||||||
t.Errorf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
|
t.Fatalf("expecting error to contain %s, got %s", expectedErrKeywords, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and launch learner member based on the response of V3 Member Add API.
|
||||||
|
// (the response has information on peer urls of the existing members in cluster)
|
||||||
|
learnerMember := clus.MustNewMember(t, memberAddResp)
|
||||||
|
clus.Members = append(clus.Members, learnerMember)
|
||||||
|
if err := learnerMember.Launch(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry until promote succeed or timeout
|
||||||
|
timeout := time.After(5 * time.Second)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
case <-timeout:
|
||||||
|
t.Errorf("failed all attempts to promote learner member, last error: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = capi.MemberPromote(context.Background(), learnerID)
|
||||||
|
// successfully promoted learner
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if member promote fails due to learner not ready, retry.
|
||||||
|
// otherwise fails the test.
|
||||||
|
if !strings.Contains(err.Error(), expectedErrKeywords) {
|
||||||
|
t.Fatalf("unexpected error when promoting learner member: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1340,54 +1340,6 @@ func TestRemoveMember(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestPromoteMember tests PromoteMember can propose and perform learner node promotion.
|
|
||||||
func TestPromoteMember(t *testing.T) {
|
|
||||||
n := newNodeConfChangeCommitterRecorder()
|
|
||||||
n.readyc <- raft.Ready{
|
|
||||||
SoftState: &raft.SoftState{RaftState: raft.StateLeader},
|
|
||||||
}
|
|
||||||
cl := newTestCluster(nil)
|
|
||||||
st := v2store.New()
|
|
||||||
cl.SetStore(v2store.New())
|
|
||||||
cl.AddMember(&membership.Member{
|
|
||||||
ID: 1234,
|
|
||||||
RaftAttributes: membership.RaftAttributes{
|
|
||||||
IsLearner: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
r := newRaftNode(raftNodeConfig{
|
|
||||||
lg: zap.NewExample(),
|
|
||||||
Node: n,
|
|
||||||
raftStorage: raft.NewMemoryStorage(),
|
|
||||||
storage: mockstorage.NewStorageRecorder(""),
|
|
||||||
transport: newNopTransporter(),
|
|
||||||
})
|
|
||||||
s := &EtcdServer{
|
|
||||||
lgMu: new(sync.RWMutex),
|
|
||||||
lg: zap.NewExample(),
|
|
||||||
r: *r,
|
|
||||||
v2store: st,
|
|
||||||
cluster: cl,
|
|
||||||
reqIDGen: idutil.NewGenerator(0, time.Time{}),
|
|
||||||
SyncTicker: &time.Ticker{},
|
|
||||||
}
|
|
||||||
s.start()
|
|
||||||
_, err := s.PromoteMember(context.TODO(), 1234)
|
|
||||||
gaction := n.Action()
|
|
||||||
s.Stop()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("PromoteMember error: %v", err)
|
|
||||||
}
|
|
||||||
wactions := []testutil.Action{{Name: "ProposeConfChange:ConfChangeAddNode"}, {Name: "ApplyConfChange:ConfChangeAddNode"}}
|
|
||||||
if !reflect.DeepEqual(gaction, wactions) {
|
|
||||||
t.Errorf("action = %v, want %v", gaction, wactions)
|
|
||||||
}
|
|
||||||
if cl.Member(1234).IsLearner {
|
|
||||||
t.Errorf("member with id 1234 is not promoted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestUpdateMember tests RemoveMember can propose and perform node update.
|
// TestUpdateMember tests RemoveMember can propose and perform node update.
|
||||||
func TestUpdateMember(t *testing.T) {
|
func TestUpdateMember(t *testing.T) {
|
||||||
n := newNodeConfChangeCommitterRecorder()
|
n := newNodeConfChangeCommitterRecorder()
|
||||||
|
|
|
@ -1396,3 +1396,18 @@ func (p SortableProtoMemberSliceByPeerURLs) Less(i, j int) bool {
|
||||||
return p[i].PeerURLs[0] < p[j].PeerURLs[0]
|
return p[i].PeerURLs[0] < p[j].PeerURLs[0]
|
||||||
}
|
}
|
||||||
func (p SortableProtoMemberSliceByPeerURLs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
func (p SortableProtoMemberSliceByPeerURLs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
|
// MustNewMember creates a new member instance based on the response of V3 Member Add API.
|
||||||
|
func (c *ClusterV3) MustNewMember(t testing.TB, resp *clientv3.MemberAddResponse) *member {
|
||||||
|
m := c.mustNewMember(t)
|
||||||
|
m.isLearner = resp.Member.IsLearner
|
||||||
|
m.NewCluster = false
|
||||||
|
|
||||||
|
m.InitialPeerURLsMap = types.URLsMap{}
|
||||||
|
for _, mm := range c.Members {
|
||||||
|
m.InitialPeerURLsMap[mm.Name] = mm.PeerURLs
|
||||||
|
}
|
||||||
|
m.InitialPeerURLsMap[m.Name] = types.MustNewURLs(resp.Member.PeerURLs)
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue