Browse Source

Merge pull request #1118 from unihorn/133

etcdserver: add TestRecvSnapshot
Yicheng Qin 11 years ago
parent
commit
ddfcb67ce3
3 changed files with 101 additions and 19 deletions
  1. 81 4
      etcdserver/server_test.go
  2. 6 15
      raft/node_test.go
  3. 14 0
      testutil/testutil.go

+ 81 - 4
etcdserver/server_test.go

@@ -13,6 +13,7 @@ import (
 	"github.com/coreos/etcd/raft"
 	"github.com/coreos/etcd/raft"
 	"github.com/coreos/etcd/raft/raftpb"
 	"github.com/coreos/etcd/raft/raftpb"
 	"github.com/coreos/etcd/store"
 	"github.com/coreos/etcd/store"
+	"github.com/coreos/etcd/testutil"
 	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
 	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
 )
 )
 
 
@@ -508,9 +509,6 @@ func TestSnapshot(t *testing.T) {
 }
 }
 
 
 // Applied > SnapCount should trigger a SaveSnap event
 // Applied > SnapCount should trigger a SaveSnap event
-// TODO: receive a snapshot from raft leader should also be able
-// to trigger snapSave and also trigger a store.Recover.
-// We need fake node!
 func TestTriggerSnap(t *testing.T) {
 func TestTriggerSnap(t *testing.T) {
 	ctx := context.Background()
 	ctx := context.Background()
 	n := raft.StartNode(0xBAD0, []int64{0xBAD0}, 10, 1)
 	n := raft.StartNode(0xBAD0, []int64{0xBAD0}, 10, 1)
@@ -543,6 +541,63 @@ func TestTriggerSnap(t *testing.T) {
 	}
 	}
 }
 }
 
 
+// TestRecvSnapshot tests when it receives a snapshot from raft leader,
+// it should trigger storage.SaveSnap and also store.Recover.
+func TestRecvSnapshot(t *testing.T) {
+	n := newReadyNode()
+	st := &storeRecorder{}
+	p := &storageRecorder{}
+	s := &EtcdServer{
+		Store:   st,
+		Send:    func(_ []raftpb.Message) {},
+		Storage: p,
+		Node:    n,
+	}
+
+	s.Start()
+	n.readyc <- raft.Ready{Snapshot: raftpb.Snapshot{Index: 1}}
+	// make goroutines move forward to receive snapshot
+	testutil.ForceGosched()
+	s.Stop()
+
+	waction := []string{"Recovery"}
+	if g := st.Action(); !reflect.DeepEqual(g, waction) {
+		t.Errorf("store action = %v, want %v", g, waction)
+	}
+	waction = []string{"Save", "SaveSnap"}
+	if g := p.Action(); !reflect.DeepEqual(g, waction) {
+		t.Errorf("storage action = %v, want %v", g, waction)
+	}
+}
+
+// TestRecvSlowSnapshot tests that slow snapshot will not be applied
+// to store.
+func TestRecvSlowSnapshot(t *testing.T) {
+	n := newReadyNode()
+	st := &storeRecorder{}
+	s := &EtcdServer{
+		Store:   st,
+		Send:    func(_ []raftpb.Message) {},
+		Storage: &storageRecorder{},
+		Node:    n,
+	}
+
+	s.Start()
+	n.readyc <- raft.Ready{Snapshot: raftpb.Snapshot{Index: 1}}
+	// make goroutines move forward to receive snapshot
+	testutil.ForceGosched()
+	action := st.Action()
+
+	n.readyc <- raft.Ready{Snapshot: raftpb.Snapshot{Index: 1}}
+	// make goroutines move forward to receive snapshot
+	testutil.ForceGosched()
+	s.Stop()
+
+	if g := st.Action(); !reflect.DeepEqual(g, action) {
+		t.Errorf("store action = %v, want %v", g, action)
+	}
+}
+
 // TODO: test wait trigger correctness in multi-server case
 // TODO: test wait trigger correctness in multi-server case
 
 
 func TestGetBool(t *testing.T) {
 func TestGetBool(t *testing.T) {
@@ -626,7 +681,10 @@ func (s *storeRecorder) Save() ([]byte, error) {
 	s.record("Save")
 	s.record("Save")
 	return nil, nil
 	return nil, nil
 }
 }
-func (s *storeRecorder) Recovery(b []byte) error   { return nil }
+func (s *storeRecorder) Recovery(b []byte) error {
+	s.record("Recovery")
+	return nil
+}
 func (s *storeRecorder) TotalTransactions() uint64 { return 0 }
 func (s *storeRecorder) TotalTransactions() uint64 { return 0 }
 func (s *storeRecorder) JsonStats() []byte         { return nil }
 func (s *storeRecorder) JsonStats() []byte         { return nil }
 func (s *storeRecorder) DeleteExpiredKeys(cutoff time.Time) {
 func (s *storeRecorder) DeleteExpiredKeys(cutoff time.Time) {
@@ -687,6 +745,25 @@ func (p *storageRecorder) SaveSnap(st raftpb.Snapshot) {
 	p.record("SaveSnap")
 	p.record("SaveSnap")
 }
 }
 
 
+type readyNode struct {
+	readyc chan raft.Ready
+}
+
+func newReadyNode() *readyNode {
+	readyc := make(chan raft.Ready, 1)
+	return &readyNode{readyc: readyc}
+}
+func (n *readyNode) Tick()                                              {}
+func (n *readyNode) Campaign(ctx context.Context) error                 { return nil }
+func (n *readyNode) Propose(ctx context.Context, data []byte) error     { return nil }
+func (n *readyNode) Configure(ctx context.Context, data []byte) error   { return nil }
+func (n *readyNode) Step(ctx context.Context, msg raftpb.Message) error { return nil }
+func (n *readyNode) Ready() <-chan raft.Ready                           { return n.readyc }
+func (n *readyNode) Stop()                                              {}
+func (n *readyNode) Compact(d []byte)                                   {}
+func (n *readyNode) AddNode(id int64)                                   {}
+func (n *readyNode) RemoveNode(id int64)                                {}
+
 func TestGenID(t *testing.T) {
 func TestGenID(t *testing.T) {
 	// Sanity check that the GenID function has been seeded appropriately
 	// Sanity check that the GenID function has been seeded appropriately
 	// (math/rand is seeded with 1 by default)
 	// (math/rand is seeded with 1 by default)

+ 6 - 15
raft/node_test.go

@@ -2,11 +2,11 @@ package raft
 
 
 import (
 import (
 	"reflect"
 	"reflect"
-	"runtime"
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
 	"github.com/coreos/etcd/raft/raftpb"
 	"github.com/coreos/etcd/raft/raftpb"
+	"github.com/coreos/etcd/testutil"
 	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
 	"github.com/coreos/etcd/third_party/code.google.com/p/go.net/context"
 )
 )
 
 
@@ -96,7 +96,7 @@ func TestBlockProposal(t *testing.T) {
 		errc <- n.Propose(context.TODO(), []byte("somedata"))
 		errc <- n.Propose(context.TODO(), []byte("somedata"))
 	}()
 	}()
 
 
-	forceGosched()
+	testutil.ForceGosched()
 	select {
 	select {
 	case err := <-errc:
 	case err := <-errc:
 		t.Errorf("err = %v, want blocking", err)
 		t.Errorf("err = %v, want blocking", err)
@@ -104,7 +104,7 @@ func TestBlockProposal(t *testing.T) {
 	}
 	}
 
 
 	n.Campaign(context.TODO())
 	n.Campaign(context.TODO())
-	forceGosched()
+	testutil.ForceGosched()
 	select {
 	select {
 	case err := <-errc:
 	case err := <-errc:
 		if err != nil {
 		if err != nil {
@@ -216,7 +216,7 @@ func TestCompact(t *testing.T) {
 		Nodes: []int64{1},
 		Nodes: []int64{1},
 	}
 	}
 
 
-	forceGosched()
+	testutil.ForceGosched()
 	select {
 	select {
 	case <-n.Ready():
 	case <-n.Ready():
 	default:
 	default:
@@ -224,7 +224,7 @@ func TestCompact(t *testing.T) {
 	}
 	}
 
 
 	n.Compact(w.Data)
 	n.Compact(w.Data)
-	forceGosched()
+	testutil.ForceGosched()
 	select {
 	select {
 	case rd := <-n.Ready():
 	case rd := <-n.Ready():
 		if !reflect.DeepEqual(rd.Snapshot, w) {
 		if !reflect.DeepEqual(rd.Snapshot, w) {
@@ -233,7 +233,7 @@ func TestCompact(t *testing.T) {
 	default:
 	default:
 		t.Fatalf("unexpected compact failure: unable to create a snapshot")
 		t.Fatalf("unexpected compact failure: unable to create a snapshot")
 	}
 	}
-	forceGosched()
+	testutil.ForceGosched()
 	// TODO: this test the run updates the snapi correctly... should be tested
 	// TODO: this test the run updates the snapi correctly... should be tested
 	// separately with other kinds of updates
 	// separately with other kinds of updates
 	select {
 	select {
@@ -265,12 +265,3 @@ func TestIsStateEqual(t *testing.T) {
 		}
 		}
 	}
 	}
 }
 }
-
-// WARNING: This is a hack.
-// Remove this when we are able to block/check the status of the go-routines.
-func forceGosched() {
-	// possibility enough to sched upto 10 go routines.
-	for i := 0; i < 10000; i++ {
-		runtime.Gosched()
-	}
-}

+ 14 - 0
testutil/testutil.go

@@ -0,0 +1,14 @@
+package testutil
+
+import (
+	"runtime"
+)
+
+// WARNING: This is a hack.
+// Remove this when we are able to block/check the status of the go-routines.
+func ForceGosched() {
+	// possibility enough to sched upto 10 go routines.
+	for i := 0; i < 10000; i++ {
+		runtime.Gosched()
+	}
+}