1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package etcdserver
16
17import (
18	"encoding/json"
19	"reflect"
20	"testing"
21	"time"
22
23	"github.com/coreos/etcd/etcdserver/membership"
24	"github.com/coreos/etcd/pkg/mock/mockstorage"
25	"github.com/coreos/etcd/pkg/pbutil"
26	"github.com/coreos/etcd/pkg/types"
27	"github.com/coreos/etcd/raft"
28	"github.com/coreos/etcd/raft/raftpb"
29	"github.com/coreos/etcd/rafthttp"
30)
31
32func TestGetIDs(t *testing.T) {
33	addcc := &raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 2}
34	addEntry := raftpb.Entry{Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(addcc)}
35	removecc := &raftpb.ConfChange{Type: raftpb.ConfChangeRemoveNode, NodeID: 2}
36	removeEntry := raftpb.Entry{Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc)}
37	normalEntry := raftpb.Entry{Type: raftpb.EntryNormal}
38	updatecc := &raftpb.ConfChange{Type: raftpb.ConfChangeUpdateNode, NodeID: 2}
39	updateEntry := raftpb.Entry{Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(updatecc)}
40
41	tests := []struct {
42		confState *raftpb.ConfState
43		ents      []raftpb.Entry
44
45		widSet []uint64
46	}{
47		{nil, []raftpb.Entry{}, []uint64{}},
48		{&raftpb.ConfState{Nodes: []uint64{1}},
49			[]raftpb.Entry{}, []uint64{1}},
50		{&raftpb.ConfState{Nodes: []uint64{1}},
51			[]raftpb.Entry{addEntry}, []uint64{1, 2}},
52		{&raftpb.ConfState{Nodes: []uint64{1}},
53			[]raftpb.Entry{addEntry, removeEntry}, []uint64{1}},
54		{&raftpb.ConfState{Nodes: []uint64{1}},
55			[]raftpb.Entry{addEntry, normalEntry}, []uint64{1, 2}},
56		{&raftpb.ConfState{Nodes: []uint64{1}},
57			[]raftpb.Entry{addEntry, normalEntry, updateEntry}, []uint64{1, 2}},
58		{&raftpb.ConfState{Nodes: []uint64{1}},
59			[]raftpb.Entry{addEntry, removeEntry, normalEntry}, []uint64{1}},
60	}
61
62	for i, tt := range tests {
63		var snap raftpb.Snapshot
64		if tt.confState != nil {
65			snap.Metadata.ConfState = *tt.confState
66		}
67		idSet := getIDs(&snap, tt.ents)
68		if !reflect.DeepEqual(idSet, tt.widSet) {
69			t.Errorf("#%d: idset = %#v, want %#v", i, idSet, tt.widSet)
70		}
71	}
72}
73
74func TestCreateConfigChangeEnts(t *testing.T) {
75	m := membership.Member{
76		ID:             types.ID(1),
77		RaftAttributes: membership.RaftAttributes{PeerURLs: []string{"http://localhost:2380"}},
78	}
79	ctx, err := json.Marshal(m)
80	if err != nil {
81		t.Fatal(err)
82	}
83	addcc1 := &raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1, Context: ctx}
84	removecc2 := &raftpb.ConfChange{Type: raftpb.ConfChangeRemoveNode, NodeID: 2}
85	removecc3 := &raftpb.ConfChange{Type: raftpb.ConfChangeRemoveNode, NodeID: 3}
86	tests := []struct {
87		ids         []uint64
88		self        uint64
89		term, index uint64
90
91		wents []raftpb.Entry
92	}{
93		{
94			[]uint64{1},
95			1,
96			1, 1,
97
98			[]raftpb.Entry{},
99		},
100		{
101			[]uint64{1, 2},
102			1,
103			1, 1,
104
105			[]raftpb.Entry{{Term: 1, Index: 2, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc2)}},
106		},
107		{
108			[]uint64{1, 2},
109			1,
110			2, 2,
111
112			[]raftpb.Entry{{Term: 2, Index: 3, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc2)}},
113		},
114		{
115			[]uint64{1, 2, 3},
116			1,
117			2, 2,
118
119			[]raftpb.Entry{
120				{Term: 2, Index: 3, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc2)},
121				{Term: 2, Index: 4, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc3)},
122			},
123		},
124		{
125			[]uint64{2, 3},
126			2,
127			2, 2,
128
129			[]raftpb.Entry{
130				{Term: 2, Index: 3, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc3)},
131			},
132		},
133		{
134			[]uint64{2, 3},
135			1,
136			2, 2,
137
138			[]raftpb.Entry{
139				{Term: 2, Index: 3, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc2)},
140				{Term: 2, Index: 4, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(removecc3)},
141				{Term: 2, Index: 5, Type: raftpb.EntryConfChange, Data: pbutil.MustMarshal(addcc1)},
142			},
143		},
144	}
145
146	for i, tt := range tests {
147		gents := createConfigChangeEnts(tt.ids, tt.self, tt.term, tt.index)
148		if !reflect.DeepEqual(gents, tt.wents) {
149			t.Errorf("#%d: ents = %v, want %v", i, gents, tt.wents)
150		}
151	}
152}
153
154func TestStopRaftWhenWaitingForApplyDone(t *testing.T) {
155	n := newNopReadyNode()
156	r := newRaftNode(raftNodeConfig{
157		Node:        n,
158		storage:     mockstorage.NewStorageRecorder(""),
159		raftStorage: raft.NewMemoryStorage(),
160		transport:   rafthttp.NewNopTransporter(),
161	})
162	srv := &EtcdServer{r: *r}
163	srv.r.start(nil)
164	n.readyc <- raft.Ready{}
165	select {
166	case <-srv.r.applyc:
167	case <-time.After(time.Second):
168		t.Fatalf("failed to receive apply struct")
169	}
170
171	srv.r.stopped <- struct{}{}
172	select {
173	case <-srv.r.done:
174	case <-time.After(time.Second):
175		t.Fatalf("failed to stop raft loop")
176	}
177}
178
179// TestConfgChangeBlocksApply ensures apply blocks if committed entries contain config-change.
180func TestConfgChangeBlocksApply(t *testing.T) {
181	n := newNopReadyNode()
182
183	r := newRaftNode(raftNodeConfig{
184		Node:        n,
185		storage:     mockstorage.NewStorageRecorder(""),
186		raftStorage: raft.NewMemoryStorage(),
187		transport:   rafthttp.NewNopTransporter(),
188	})
189	srv := &EtcdServer{r: *r}
190
191	srv.r.start(&raftReadyHandler{updateLeadership: func(bool) {}})
192	defer srv.r.Stop()
193
194	n.readyc <- raft.Ready{
195		SoftState:        &raft.SoftState{RaftState: raft.StateFollower},
196		CommittedEntries: []raftpb.Entry{{Type: raftpb.EntryConfChange}},
197	}
198	ap := <-srv.r.applyc
199
200	continueC := make(chan struct{})
201	go func() {
202		n.readyc <- raft.Ready{}
203		<-srv.r.applyc
204		close(continueC)
205	}()
206
207	select {
208	case <-continueC:
209		t.Fatalf("unexpected execution: raft routine should block waiting for apply")
210	case <-time.After(time.Second):
211	}
212
213	// finish apply, unblock raft routine
214	<-ap.notifyc
215
216	select {
217	case <-continueC:
218	case <-time.After(time.Second):
219		t.Fatalf("unexpected blocking on execution")
220	}
221}
222