// Applied > SnapCount should trigger a SaveSnap event func TestTriggerSnap(t *testing.T) { snapc := 10 st := store.NewRecorder() p := &storageRecorder{} srv := &EtcdServer{ cfg: &ServerConfig{TickMs: 1}, snapCount: uint64(snapc), r: raftNode{ Node: newNodeCommitter(), raftStorage: raft.NewMemoryStorage(), storage: p, transport: rafthttp.NewNopTransporter(), }, store: st, reqIDGen: idutil.NewGenerator(0, time.Time{}), } srv.start() for i := 0; i < snapc+1; i++ { srv.Do(context.Background(), pb.Request{Method: "PUT"}) } wcnt := 2 + snapc gaction, _ := p.Wait(wcnt) srv.Stop() // each operation is recorded as a Save // (SnapCount+1) * Puts + SaveSnap = (SnapCount+1) * Save + SaveSnap if len(gaction) != wcnt { t.Fatalf("len(action) = %d, want %d", len(gaction), wcnt) } if !reflect.DeepEqual(gaction[wcnt-1], testutil.Action{Name: "SaveSnap"}) { t.Errorf("action = %s, want SaveSnap", gaction[wcnt-1]) } }
// snapshot should snapshot the store and cut the persistent func TestSnapshot(t *testing.T) { s := raft.NewMemoryStorage() s.Append([]raftpb.Entry{{Index: 1}}) st := store.NewRecorder() p := &storageRecorder{} srv := &EtcdServer{ cfg: &ServerConfig{}, r: raftNode{ Node: newNodeNop(), raftStorage: s, storage: p, }, store: st, } srv.snapshot(1, raftpb.ConfState{Nodes: []uint64{1}}) gaction, _ := st.Wait(2) if len(gaction) != 2 { t.Fatalf("len(action) = %d, want 1", len(gaction)) } if !reflect.DeepEqual(gaction[0], testutil.Action{Name: "Clone"}) { t.Errorf("action = %s, want Clone", gaction[0]) } if !reflect.DeepEqual(gaction[1], testutil.Action{Name: "SaveNoCopy"}) { t.Errorf("action = %s, want SaveNoCopy", gaction[1]) } gaction = p.Action() if len(gaction) != 1 { t.Fatalf("len(action) = %d, want 1", len(gaction)) } if !reflect.DeepEqual(gaction[0], testutil.Action{Name: "SaveSnap"}) { t.Errorf("action = %s, want SaveSnap", gaction[0]) } }
// TestDoLocalAction tests requests which do not need to go through raft to be applied, // and are served through local data. func TestDoLocalAction(t *testing.T) { tests := []struct { req pb.Request wresp Response werr error wactions []testutil.Action }{ { pb.Request{Method: "GET", ID: 1, Wait: true}, Response{Watcher: store.NewNopWatcher()}, nil, []testutil.Action{{Name: "Watch"}}, }, { pb.Request{Method: "GET", ID: 1}, Response{Event: &store.Event{}}, nil, []testutil.Action{ { Name: "Get", Params: []interface{}{"", false, false}, }, }, }, { pb.Request{Method: "HEAD", ID: 1}, Response{Event: &store.Event{}}, nil, []testutil.Action{ { Name: "Get", Params: []interface{}{"", false, false}, }, }, }, { pb.Request{Method: "BADMETHOD", ID: 1}, Response{}, ErrUnknownMethod, []testutil.Action{}, }, } for i, tt := range tests { st := store.NewRecorder() srv := &EtcdServer{ store: st, reqIDGen: idutil.NewGenerator(0, time.Time{}), } resp, err := srv.Do(context.TODO(), tt.req) if err != tt.werr { t.Fatalf("#%d: err = %+v, want %+v", i, err, tt.werr) } if !reflect.DeepEqual(resp, tt.wresp) { t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp) } gaction := st.Action() if !reflect.DeepEqual(gaction, tt.wactions) { t.Errorf("#%d: action = %+v, want %+v", i, gaction, tt.wactions) } } }
func TestClusterRemoveMember(t *testing.T) { st := store.NewRecorder() c := newTestCluster(nil) c.SetStore(st) c.RemoveMember(1) wactions := []testutil.Action{ {Name: "Delete", Params: []interface{}{memberStoreKey(1), true, true}}, {Name: "Create", Params: []interface{}{removedMemberStoreKey(1), false, "", false, store.Permanent}}, } if !reflect.DeepEqual(st.Action(), wactions) { t.Errorf("actions = %v, want %v", st.Action(), wactions) } }
func TestApplyRequestOnAdminMemberAttributes(t *testing.T) { cl := newTestCluster([]*Member{{ID: 1}}) srv := &EtcdServer{ store: store.NewRecorder(), cluster: cl, } req := pb.Request{ Method: "PUT", ID: 1, Path: path.Join(storeMembersPrefix, strconv.FormatUint(1, 16), attributesSuffix), Val: `{"Name":"abc","ClientURLs":["http://127.0.0.1:2379"]}`, } srv.applyRequest(req) w := Attributes{Name: "abc", ClientURLs: []string{"http://127.0.0.1:2379"}} if g := cl.Member(1).Attributes; !reflect.DeepEqual(g, w) { t.Errorf("attributes = %v, want %v", g, w) } }
// TestApplySnapshotAndCommittedEntries tests that server applies snapshot // first and then committed entries. func TestApplySnapshotAndCommittedEntries(t *testing.T) { n := newNopReadyNode() st := store.NewRecorder() cl := newCluster("abc") cl.SetStore(store.New()) storage := raft.NewMemoryStorage() s := &EtcdServer{ cfg: &ServerConfig{}, r: raftNode{ Node: n, storage: &storageRecorder{}, raftStorage: storage, transport: rafthttp.NewNopTransporter(), }, store: st, cluster: cl, } s.start() req := &pb.Request{Method: "QGET"} n.readyc <- raft.Ready{ Snapshot: raftpb.Snapshot{Metadata: raftpb.SnapshotMetadata{Index: 1}}, CommittedEntries: []raftpb.Entry{ {Index: 2, Data: pbutil.MustMarshal(req)}, }, } // make goroutines move forward to receive snapshot actions, _ := st.Wait(2) s.Stop() if len(actions) != 2 { t.Fatalf("len(action) = %d, want 2", len(actions)) } if actions[0].Name != "Recovery" { t.Errorf("actions[0] = %s, want %s", actions[0].Name, "Recovery") } if actions[1].Name != "Get" { t.Errorf("actions[1] = %s, want %s", actions[1].Name, "Get") } }
func TestClusterAddMember(t *testing.T) { st := store.NewRecorder() c := newTestCluster(nil) c.SetStore(st) c.AddMember(newTestMember(1, nil, "node1", nil)) wactions := []testutil.Action{ { Name: "Create", Params: []interface{}{ path.Join(storeMembersPrefix, "1", "raftAttributes"), false, `{"peerURLs":null}`, false, store.Permanent, }, }, } if g := st.Action(); !reflect.DeepEqual(g, wactions) { t.Errorf("actions = %v, want %v", g, wactions) } }
// 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 := newNopReadyNode() st := store.NewRecorder() p := &storageRecorder{} cl := newCluster("abc") cl.SetStore(store.New()) s := &EtcdServer{ cfg: &ServerConfig{}, r: raftNode{ Node: n, transport: rafthttp.NewNopTransporter(), storage: p, raftStorage: raft.NewMemoryStorage(), }, store: st, cluster: cl, } s.start() n.readyc <- raft.Ready{Snapshot: raftpb.Snapshot{Metadata: raftpb.SnapshotMetadata{Index: 1}}} // wait for actions happened on the storage for len(p.Action()) == 0 { time.Sleep(10 * time.Millisecond) } s.Stop() wactions := []testutil.Action{{Name: "Recovery"}} if g := st.Action(); !reflect.DeepEqual(g, wactions) { t.Errorf("store action = %v, want %v", g, wactions) } wactions = []testutil.Action{{Name: "SaveSnap"}, {Name: "Save"}} if g := p.Action(); !reflect.DeepEqual(g, wactions) { t.Errorf("storage action = %v, want %v", g, wactions) } }
func TestDoProposal(t *testing.T) { tests := []pb.Request{ {Method: "POST", ID: 1}, {Method: "PUT", ID: 1}, {Method: "DELETE", ID: 1}, {Method: "GET", ID: 1, Quorum: true}, } for i, tt := range tests { st := store.NewRecorder() srv := &EtcdServer{ cfg: &ServerConfig{TickMs: 1}, r: raftNode{ Node: newNodeCommitter(), storage: &storageRecorder{}, raftStorage: raft.NewMemoryStorage(), transport: rafthttp.NewNopTransporter(), }, store: st, reqIDGen: idutil.NewGenerator(0, time.Time{}), } srv.start() resp, err := srv.Do(context.Background(), tt) srv.Stop() action := st.Action() if len(action) != 1 { t.Errorf("#%d: len(action) = %d, want 1", i, len(action)) } if err != nil { t.Fatalf("#%d: err = %v, want nil", i, err) } wresp := Response{Event: &store.Event{}} if !reflect.DeepEqual(resp, wresp) { t.Errorf("#%d: resp = %v, want %v", i, resp, wresp) } } }
func TestApplyRequest(t *testing.T) { tests := []struct { req pb.Request wresp Response wactions []testutil.Action }{ // POST ==> Create { pb.Request{Method: "POST", ID: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Create", Params: []interface{}{"", false, "", true, time.Time{}}, }, }, }, // POST ==> Create, with expiration { pb.Request{Method: "POST", ID: 1, Expiration: 1337}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Create", Params: []interface{}{"", false, "", true, time.Unix(0, 1337)}, }, }, }, // POST ==> Create, with dir { pb.Request{Method: "POST", ID: 1, Dir: true}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Create", Params: []interface{}{"", true, "", true, time.Time{}}, }, }, }, // PUT ==> Set { pb.Request{Method: "PUT", ID: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Set", Params: []interface{}{"", false, "", time.Time{}}, }, }, }, // PUT ==> Set, with dir { pb.Request{Method: "PUT", ID: 1, Dir: true}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Set", Params: []interface{}{"", true, "", time.Time{}}, }, }, }, // PUT with PrevExist=true ==> Update { pb.Request{Method: "PUT", ID: 1, PrevExist: pbutil.Boolp(true)}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Update", Params: []interface{}{"", "", time.Time{}}, }, }, }, // PUT with PrevExist=false ==> Create { pb.Request{Method: "PUT", ID: 1, PrevExist: pbutil.Boolp(false)}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Create", Params: []interface{}{"", false, "", false, time.Time{}}, }, }, }, // PUT with PrevExist=true *and* PrevIndex set ==> CompareAndSwap { pb.Request{Method: "PUT", ID: 1, PrevExist: pbutil.Boolp(true), PrevIndex: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndSwap", Params: []interface{}{"", "", uint64(1), "", time.Time{}}, }, }, }, // PUT with PrevExist=false *and* PrevIndex set ==> Create { pb.Request{Method: "PUT", ID: 1, PrevExist: pbutil.Boolp(false), PrevIndex: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Create", Params: []interface{}{"", false, "", false, time.Time{}}, }, }, }, // PUT with PrevIndex set ==> CompareAndSwap { pb.Request{Method: "PUT", ID: 1, PrevIndex: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndSwap", Params: []interface{}{"", "", uint64(1), "", time.Time{}}, }, }, }, // PUT with PrevValue set ==> CompareAndSwap { pb.Request{Method: "PUT", ID: 1, PrevValue: "bar"}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndSwap", Params: []interface{}{"", "bar", uint64(0), "", time.Time{}}, }, }, }, // PUT with PrevIndex and PrevValue set ==> CompareAndSwap { pb.Request{Method: "PUT", ID: 1, PrevIndex: 1, PrevValue: "bar"}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndSwap", Params: []interface{}{"", "bar", uint64(1), "", time.Time{}}, }, }, }, // DELETE ==> Delete { pb.Request{Method: "DELETE", ID: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Delete", Params: []interface{}{"", false, false}, }, }, }, // DELETE with PrevIndex set ==> CompareAndDelete { pb.Request{Method: "DELETE", ID: 1, PrevIndex: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndDelete", Params: []interface{}{"", "", uint64(1)}, }, }, }, // DELETE with PrevValue set ==> CompareAndDelete { pb.Request{Method: "DELETE", ID: 1, PrevValue: "bar"}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndDelete", Params: []interface{}{"", "bar", uint64(0)}, }, }, }, // DELETE with PrevIndex *and* PrevValue set ==> CompareAndDelete { pb.Request{Method: "DELETE", ID: 1, PrevIndex: 5, PrevValue: "bar"}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "CompareAndDelete", Params: []interface{}{"", "bar", uint64(5)}, }, }, }, // QGET ==> Get { pb.Request{Method: "QGET", ID: 1}, Response{Event: &store.Event{}}, []testutil.Action{ { Name: "Get", Params: []interface{}{"", false, false}, }, }, }, // SYNC ==> DeleteExpiredKeys { pb.Request{Method: "SYNC", ID: 1}, Response{}, []testutil.Action{ { Name: "DeleteExpiredKeys", Params: []interface{}{time.Unix(0, 0)}, }, }, }, { pb.Request{Method: "SYNC", ID: 1, Time: 12345}, Response{}, []testutil.Action{ { Name: "DeleteExpiredKeys", Params: []interface{}{time.Unix(0, 12345)}, }, }, }, // Unknown method - error { pb.Request{Method: "BADMETHOD", ID: 1}, Response{err: ErrUnknownMethod}, []testutil.Action{}, }, } for i, tt := range tests { st := store.NewRecorder() srv := &EtcdServer{store: st} resp := srv.applyRequest(tt.req) if !reflect.DeepEqual(resp, tt.wresp) { t.Errorf("#%d: resp = %+v, want %+v", i, resp, tt.wresp) } gaction := st.Action() if !reflect.DeepEqual(gaction, tt.wactions) { t.Errorf("#%d: action = %#v, want %#v", i, gaction, tt.wactions) } } }