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