1package raft
2
3import (
4	"fmt"
5	"reflect"
6	"strings"
7	"testing"
8)
9
10var sampleConfiguration = Configuration{
11	Servers: []Server{
12		{
13			Suffrage: Nonvoter,
14			ID:       ServerID("id0"),
15			Address:  ServerAddress("addr0"),
16		},
17		{
18			Suffrage: Voter,
19			ID:       ServerID("id1"),
20			Address:  ServerAddress("addr1"),
21		},
22		{
23			Suffrage: Staging,
24			ID:       ServerID("id2"),
25			Address:  ServerAddress("addr2"),
26		},
27	},
28}
29
30func TestConfiguration_Configuration_Clone(t *testing.T) {
31	cloned := sampleConfiguration.Clone()
32	if !reflect.DeepEqual(sampleConfiguration, cloned) {
33		t.Fatalf("mismatch %v %v", sampleConfiguration, cloned)
34	}
35	cloned.Servers[1].ID = "scribble"
36	if sampleConfiguration.Servers[1].ID == "scribble" {
37		t.Fatalf("cloned configuration shouldn't alias Servers")
38	}
39}
40
41func TestConfiguration_configurations_Clone(t *testing.T) {
42	configuration := configurations{
43		committed:      sampleConfiguration,
44		committedIndex: 1,
45		latest:         sampleConfiguration,
46		latestIndex:    2,
47	}
48	cloned := configuration.Clone()
49	if !reflect.DeepEqual(configuration, cloned) {
50		t.Fatalf("mismatch %v %v", configuration, cloned)
51	}
52	cloned.committed.Servers[1].ID = "scribble"
53	cloned.latest.Servers[1].ID = "scribble"
54	if configuration.committed.Servers[1].ID == "scribble" ||
55		configuration.latest.Servers[1].ID == "scribble" {
56		t.Fatalf("cloned configuration shouldn't alias Servers")
57	}
58}
59
60func TestConfiguration_hasVote(t *testing.T) {
61	if hasVote(sampleConfiguration, "id0") {
62		t.Fatalf("id0 should not have vote")
63	}
64	if !hasVote(sampleConfiguration, "id1") {
65		t.Fatalf("id1 should have vote")
66	}
67	if hasVote(sampleConfiguration, "id2") {
68		t.Fatalf("id2 should not have vote")
69	}
70	if hasVote(sampleConfiguration, "someotherid") {
71		t.Fatalf("someotherid should not have vote")
72	}
73}
74
75func TestConfiguration_checkConfiguration(t *testing.T) {
76	var configuration Configuration
77	if checkConfiguration(configuration) == nil {
78		t.Fatalf("empty configuration should be error")
79	}
80
81	configuration.Servers = append(configuration.Servers, Server{
82		Suffrage: Nonvoter,
83		ID:       ServerID("id0"),
84		Address:  ServerAddress("addr0"),
85	})
86	if checkConfiguration(configuration) == nil {
87		t.Fatalf("lack of voter should be error")
88	}
89
90	configuration.Servers = append(configuration.Servers, Server{
91		Suffrage: Voter,
92		ID:       ServerID("id1"),
93		Address:  ServerAddress("addr1"),
94	})
95	if err := checkConfiguration(configuration); err != nil {
96		t.Fatalf("should be OK: %v", err)
97	}
98
99	configuration.Servers[1].ID = "id0"
100	err := checkConfiguration(configuration)
101	if err == nil {
102		t.Fatalf("duplicate ID should be error")
103	}
104	if !strings.Contains(err.Error(), "duplicate ID") {
105		t.Fatalf("unexpected error: %v", err)
106	}
107	configuration.Servers[1].ID = "id1"
108
109	configuration.Servers[1].Address = "addr0"
110	err = checkConfiguration(configuration)
111	if err == nil {
112		t.Fatalf("duplicate address should be error")
113	}
114	if !strings.Contains(err.Error(), "duplicate address") {
115		t.Fatalf("unexpected error: %v", err)
116	}
117}
118
119var singleServer = Configuration{
120	Servers: []Server{
121		{
122			Suffrage: Voter,
123			ID:       ServerID("id1"),
124			Address:  ServerAddress("addr1x"),
125		},
126	},
127}
128
129var oneOfEach = Configuration{
130	Servers: []Server{
131		{
132			Suffrage: Voter,
133			ID:       ServerID("id1"),
134			Address:  ServerAddress("addr1x"),
135		},
136		{
137			Suffrage: Staging,
138			ID:       ServerID("id2"),
139			Address:  ServerAddress("addr2x"),
140		},
141		{
142			Suffrage: Nonvoter,
143			ID:       ServerID("id3"),
144			Address:  ServerAddress("addr3x"),
145		},
146	},
147}
148
149var voterPair = Configuration{
150	Servers: []Server{
151		{
152			Suffrage: Voter,
153			ID:       ServerID("id1"),
154			Address:  ServerAddress("addr1x"),
155		},
156		{
157			Suffrage: Voter,
158			ID:       ServerID("id2"),
159			Address:  ServerAddress("addr2x"),
160		},
161	},
162}
163
164var nextConfigurationTests = []struct {
165	current  Configuration
166	command  ConfigurationChangeCommand
167	serverID int
168	next     string
169}{
170	// AddStaging: was missing.
171	{Configuration{}, AddStaging, 1, "{[{Voter id1 addr1}]}"},
172	{singleServer, AddStaging, 2, "{[{Voter id1 addr1x} {Voter id2 addr2}]}"},
173	// AddStaging: was Voter.
174	{singleServer, AddStaging, 1, "{[{Voter id1 addr1}]}"},
175	// AddStaging: was Staging.
176	{oneOfEach, AddStaging, 2, "{[{Voter id1 addr1x} {Voter id2 addr2} {Nonvoter id3 addr3x}]}"},
177	// AddStaging: was Nonvoter.
178	{oneOfEach, AddStaging, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Voter id3 addr3}]}"},
179
180	// AddNonvoter: was missing.
181	{singleServer, AddNonvoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2}]}"},
182	// AddNonvoter: was Voter.
183	{singleServer, AddNonvoter, 1, "{[{Voter id1 addr1}]}"},
184	// AddNonvoter: was Staging.
185	{oneOfEach, AddNonvoter, 2, "{[{Voter id1 addr1x} {Staging id2 addr2} {Nonvoter id3 addr3x}]}"},
186	// AddNonvoter: was Nonvoter.
187	{oneOfEach, AddNonvoter, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3}]}"},
188
189	// DemoteVoter: was missing.
190	{singleServer, DemoteVoter, 2, "{[{Voter id1 addr1x}]}"},
191	// DemoteVoter: was Voter.
192	{voterPair, DemoteVoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2x}]}"},
193	// DemoteVoter: was Staging.
194	{oneOfEach, DemoteVoter, 2, "{[{Voter id1 addr1x} {Nonvoter id2 addr2x} {Nonvoter id3 addr3x}]}"},
195	// DemoteVoter: was Nonvoter.
196	{oneOfEach, DemoteVoter, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3x}]}"},
197
198	// RemoveServer: was missing.
199	{singleServer, RemoveServer, 2, "{[{Voter id1 addr1x}]}"},
200	// RemoveServer: was Voter.
201	{voterPair, RemoveServer, 2, "{[{Voter id1 addr1x}]}"},
202	// RemoveServer: was Staging.
203	{oneOfEach, RemoveServer, 2, "{[{Voter id1 addr1x} {Nonvoter id3 addr3x}]}"},
204	// RemoveServer: was Nonvoter.
205	{oneOfEach, RemoveServer, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x}]}"},
206
207	// Promote: was missing.
208	{singleServer, Promote, 2, "{[{Voter id1 addr1x}]}"},
209	// Promote: was Voter.
210	{singleServer, Promote, 1, "{[{Voter id1 addr1x}]}"},
211	// Promote: was Staging.
212	{oneOfEach, Promote, 2, "{[{Voter id1 addr1x} {Voter id2 addr2x} {Nonvoter id3 addr3x}]}"},
213	// Promote: was Nonvoter.
214	{oneOfEach, Promote, 3, "{[{Voter id1 addr1x} {Staging id2 addr2x} {Nonvoter id3 addr3x}]}"},
215}
216
217func TestConfiguration_nextConfiguration_table(t *testing.T) {
218	for i, tt := range nextConfigurationTests {
219		req := configurationChangeRequest{
220			command:       tt.command,
221			serverID:      ServerID(fmt.Sprintf("id%d", tt.serverID)),
222			serverAddress: ServerAddress(fmt.Sprintf("addr%d", tt.serverID)),
223		}
224		next, err := nextConfiguration(tt.current, 1, req)
225		if err != nil {
226			t.Errorf("nextConfiguration %d should have succeeded, got %v", i, err)
227			continue
228		}
229		if fmt.Sprintf("%v", next) != tt.next {
230			t.Errorf("nextConfiguration %d returned %v, expected %s", i, next, tt.next)
231			continue
232		}
233	}
234}
235
236func TestConfiguration_nextConfiguration_prevIndex(t *testing.T) {
237	// Stale prevIndex.
238	req := configurationChangeRequest{
239		command:       AddStaging,
240		serverID:      ServerID("id1"),
241		serverAddress: ServerAddress("addr1"),
242		prevIndex:     1,
243	}
244	_, err := nextConfiguration(singleServer, 2, req)
245	if err == nil || !strings.Contains(err.Error(), "changed") {
246		t.Fatalf("nextConfiguration should have failed due to intervening configuration change")
247	}
248
249	// Current prevIndex.
250	req = configurationChangeRequest{
251		command:       AddStaging,
252		serverID:      ServerID("id2"),
253		serverAddress: ServerAddress("addr2"),
254		prevIndex:     2,
255	}
256	_, err = nextConfiguration(singleServer, 2, req)
257	if err != nil {
258		t.Fatalf("nextConfiguration should have succeeded, got %v", err)
259	}
260
261	// Zero prevIndex.
262	req = configurationChangeRequest{
263		command:       AddStaging,
264		serverID:      ServerID("id3"),
265		serverAddress: ServerAddress("addr3"),
266		prevIndex:     0,
267	}
268	_, err = nextConfiguration(singleServer, 2, req)
269	if err != nil {
270		t.Fatalf("nextConfiguration should have succeeded, got %v", err)
271	}
272}
273
274func TestConfiguration_nextConfiguration_checkConfiguration(t *testing.T) {
275	req := configurationChangeRequest{
276		command:       AddNonvoter,
277		serverID:      ServerID("id1"),
278		serverAddress: ServerAddress("addr1"),
279	}
280	_, err := nextConfiguration(Configuration{}, 1, req)
281	if err == nil || !strings.Contains(err.Error(), "at least one voter") {
282		t.Fatalf("nextConfiguration should have failed for not having a voter")
283	}
284}
285
286func TestConfiguration_encodeDecodePeers(t *testing.T) {
287	// Set up configuration.
288	var configuration Configuration
289	for i := 0; i < 3; i++ {
290		address := NewInmemAddr()
291		configuration.Servers = append(configuration.Servers, Server{
292			Suffrage: Voter,
293			ID:       ServerID(address),
294			Address:  ServerAddress(address),
295		})
296	}
297
298	// Encode into the old format.
299	_, trans := NewInmemTransport("")
300	buf := encodePeers(configuration, trans)
301
302	// Decode from old format, as if reading an old log entry.
303	decoded := decodePeers(buf, trans)
304	if !reflect.DeepEqual(configuration, decoded) {
305		t.Fatalf("mismatch %v %v", configuration, decoded)
306	}
307}
308
309func TestConfiguration_encodeDecodeConfiguration(t *testing.T) {
310	decoded := DecodeConfiguration(EncodeConfiguration(sampleConfiguration))
311	if !reflect.DeepEqual(sampleConfiguration, decoded) {
312		t.Fatalf("mismatch %v %v", sampleConfiguration, decoded)
313	}
314}
315