go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/membership/cluster_test.go (about) 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 15 package membership 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "path" 21 "reflect" 22 "testing" 23 24 "github.com/coreos/etcd/pkg/mock/mockstore" 25 "github.com/coreos/etcd/pkg/testutil" 26 "github.com/coreos/etcd/pkg/types" 27 "github.com/coreos/etcd/raft/raftpb" 28 "github.com/coreos/etcd/store" 29 ) 30 31 func TestClusterMember(t *testing.T) { 32 membs := []*Member{ 33 newTestMember(1, nil, "node1", nil), 34 newTestMember(2, nil, "node2", nil), 35 } 36 tests := []struct { 37 id types.ID 38 match bool 39 }{ 40 {1, true}, 41 {2, true}, 42 {3, false}, 43 } 44 for i, tt := range tests { 45 c := newTestCluster(membs) 46 m := c.Member(tt.id) 47 if g := m != nil; g != tt.match { 48 t.Errorf("#%d: find member = %v, want %v", i, g, tt.match) 49 } 50 if m != nil && m.ID != tt.id { 51 t.Errorf("#%d: id = %x, want %x", i, m.ID, tt.id) 52 } 53 } 54 } 55 56 func TestClusterMemberByName(t *testing.T) { 57 membs := []*Member{ 58 newTestMember(1, nil, "node1", nil), 59 newTestMember(2, nil, "node2", nil), 60 } 61 tests := []struct { 62 name string 63 match bool 64 }{ 65 {"node1", true}, 66 {"node2", true}, 67 {"node3", false}, 68 } 69 for i, tt := range tests { 70 c := newTestCluster(membs) 71 m := c.MemberByName(tt.name) 72 if g := m != nil; g != tt.match { 73 t.Errorf("#%d: find member = %v, want %v", i, g, tt.match) 74 } 75 if m != nil && m.Name != tt.name { 76 t.Errorf("#%d: name = %v, want %v", i, m.Name, tt.name) 77 } 78 } 79 } 80 81 func TestClusterMemberIDs(t *testing.T) { 82 c := newTestCluster([]*Member{ 83 newTestMember(1, nil, "", nil), 84 newTestMember(4, nil, "", nil), 85 newTestMember(100, nil, "", nil), 86 }) 87 w := []types.ID{1, 4, 100} 88 g := c.MemberIDs() 89 if !reflect.DeepEqual(w, g) { 90 t.Errorf("IDs = %+v, want %+v", g, w) 91 } 92 } 93 94 func TestClusterPeerURLs(t *testing.T) { 95 tests := []struct { 96 mems []*Member 97 wurls []string 98 }{ 99 // single peer with a single address 100 { 101 mems: []*Member{ 102 newTestMember(1, []string{"http://192.0.2.1"}, "", nil), 103 }, 104 wurls: []string{"http://192.0.2.1"}, 105 }, 106 107 // single peer with a single address with a port 108 { 109 mems: []*Member{ 110 newTestMember(1, []string{"http://192.0.2.1:8001"}, "", nil), 111 }, 112 wurls: []string{"http://192.0.2.1:8001"}, 113 }, 114 115 // several members explicitly unsorted 116 { 117 mems: []*Member{ 118 newTestMember(2, []string{"http://192.0.2.3", "http://192.0.2.4"}, "", nil), 119 newTestMember(3, []string{"http://192.0.2.5", "http://192.0.2.6"}, "", nil), 120 newTestMember(1, []string{"http://192.0.2.1", "http://192.0.2.2"}, "", nil), 121 }, 122 wurls: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"}, 123 }, 124 125 // no members 126 { 127 mems: []*Member{}, 128 wurls: []string{}, 129 }, 130 131 // peer with no peer urls 132 { 133 mems: []*Member{ 134 newTestMember(3, []string{}, "", nil), 135 }, 136 wurls: []string{}, 137 }, 138 } 139 140 for i, tt := range tests { 141 c := newTestCluster(tt.mems) 142 urls := c.PeerURLs() 143 if !reflect.DeepEqual(urls, tt.wurls) { 144 t.Errorf("#%d: PeerURLs = %v, want %v", i, urls, tt.wurls) 145 } 146 } 147 } 148 149 func TestClusterClientURLs(t *testing.T) { 150 tests := []struct { 151 mems []*Member 152 wurls []string 153 }{ 154 // single peer with a single address 155 { 156 mems: []*Member{ 157 newTestMember(1, nil, "", []string{"http://192.0.2.1"}), 158 }, 159 wurls: []string{"http://192.0.2.1"}, 160 }, 161 162 // single peer with a single address with a port 163 { 164 mems: []*Member{ 165 newTestMember(1, nil, "", []string{"http://192.0.2.1:8001"}), 166 }, 167 wurls: []string{"http://192.0.2.1:8001"}, 168 }, 169 170 // several members explicitly unsorted 171 { 172 mems: []*Member{ 173 newTestMember(2, nil, "", []string{"http://192.0.2.3", "http://192.0.2.4"}), 174 newTestMember(3, nil, "", []string{"http://192.0.2.5", "http://192.0.2.6"}), 175 newTestMember(1, nil, "", []string{"http://192.0.2.1", "http://192.0.2.2"}), 176 }, 177 wurls: []string{"http://192.0.2.1", "http://192.0.2.2", "http://192.0.2.3", "http://192.0.2.4", "http://192.0.2.5", "http://192.0.2.6"}, 178 }, 179 180 // no members 181 { 182 mems: []*Member{}, 183 wurls: []string{}, 184 }, 185 186 // peer with no client urls 187 { 188 mems: []*Member{ 189 newTestMember(3, nil, "", []string{}), 190 }, 191 wurls: []string{}, 192 }, 193 } 194 195 for i, tt := range tests { 196 c := newTestCluster(tt.mems) 197 urls := c.ClientURLs() 198 if !reflect.DeepEqual(urls, tt.wurls) { 199 t.Errorf("#%d: ClientURLs = %v, want %v", i, urls, tt.wurls) 200 } 201 } 202 } 203 204 func TestClusterValidateAndAssignIDsBad(t *testing.T) { 205 tests := []struct { 206 clmembs []*Member 207 membs []*Member 208 }{ 209 { 210 // unmatched length 211 []*Member{ 212 newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil), 213 }, 214 []*Member{}, 215 }, 216 { 217 // unmatched peer urls 218 []*Member{ 219 newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil), 220 }, 221 []*Member{ 222 newTestMember(1, []string{"http://127.0.0.1:4001"}, "", nil), 223 }, 224 }, 225 { 226 // unmatched peer urls 227 []*Member{ 228 newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil), 229 newTestMember(2, []string{"http://127.0.0.2:2379"}, "", nil), 230 }, 231 []*Member{ 232 newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil), 233 newTestMember(2, []string{"http://127.0.0.2:4001"}, "", nil), 234 }, 235 }, 236 } 237 for i, tt := range tests { 238 ecl := newTestCluster(tt.clmembs) 239 lcl := newTestCluster(tt.membs) 240 if err := ValidateClusterAndAssignIDs(lcl, ecl); err == nil { 241 t.Errorf("#%d: unexpected update success", i) 242 } 243 } 244 } 245 246 func TestClusterValidateAndAssignIDs(t *testing.T) { 247 tests := []struct { 248 clmembs []*Member 249 membs []*Member 250 wids []types.ID 251 }{ 252 { 253 []*Member{ 254 newTestMember(1, []string{"http://127.0.0.1:2379"}, "", nil), 255 newTestMember(2, []string{"http://127.0.0.2:2379"}, "", nil), 256 }, 257 []*Member{ 258 newTestMember(3, []string{"http://127.0.0.1:2379"}, "", nil), 259 newTestMember(4, []string{"http://127.0.0.2:2379"}, "", nil), 260 }, 261 []types.ID{3, 4}, 262 }, 263 } 264 for i, tt := range tests { 265 lcl := newTestCluster(tt.clmembs) 266 ecl := newTestCluster(tt.membs) 267 if err := ValidateClusterAndAssignIDs(lcl, ecl); err != nil { 268 t.Errorf("#%d: unexpect update error: %v", i, err) 269 } 270 if !reflect.DeepEqual(lcl.MemberIDs(), tt.wids) { 271 t.Errorf("#%d: ids = %v, want %v", i, lcl.MemberIDs(), tt.wids) 272 } 273 } 274 } 275 276 func TestClusterValidateConfigurationChange(t *testing.T) { 277 cl := NewCluster("") 278 cl.SetStore(store.New()) 279 for i := 1; i <= 4; i++ { 280 attr := RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", i)}} 281 cl.AddMember(&Member{ID: types.ID(i), RaftAttributes: attr}) 282 } 283 cl.RemoveMember(4) 284 285 attr := RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 1)}} 286 ctx, err := json.Marshal(&Member{ID: types.ID(5), RaftAttributes: attr}) 287 if err != nil { 288 t.Fatal(err) 289 } 290 291 attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 5)}} 292 ctx5, err := json.Marshal(&Member{ID: types.ID(5), RaftAttributes: attr}) 293 if err != nil { 294 t.Fatal(err) 295 } 296 297 attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 3)}} 298 ctx2to3, err := json.Marshal(&Member{ID: types.ID(2), RaftAttributes: attr}) 299 if err != nil { 300 t.Fatal(err) 301 } 302 303 attr = RaftAttributes{PeerURLs: []string{fmt.Sprintf("http://127.0.0.1:%d", 5)}} 304 ctx2to5, err := json.Marshal(&Member{ID: types.ID(2), RaftAttributes: attr}) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 tests := []struct { 310 cc raftpb.ConfChange 311 werr error 312 }{ 313 { 314 raftpb.ConfChange{ 315 Type: raftpb.ConfChangeRemoveNode, 316 NodeID: 3, 317 }, 318 nil, 319 }, 320 { 321 raftpb.ConfChange{ 322 Type: raftpb.ConfChangeAddNode, 323 NodeID: 4, 324 }, 325 ErrIDRemoved, 326 }, 327 { 328 raftpb.ConfChange{ 329 Type: raftpb.ConfChangeRemoveNode, 330 NodeID: 4, 331 }, 332 ErrIDRemoved, 333 }, 334 { 335 raftpb.ConfChange{ 336 Type: raftpb.ConfChangeAddNode, 337 NodeID: 1, 338 }, 339 ErrIDExists, 340 }, 341 { 342 raftpb.ConfChange{ 343 Type: raftpb.ConfChangeAddNode, 344 NodeID: 5, 345 Context: ctx, 346 }, 347 ErrPeerURLexists, 348 }, 349 { 350 raftpb.ConfChange{ 351 Type: raftpb.ConfChangeRemoveNode, 352 NodeID: 5, 353 }, 354 ErrIDNotFound, 355 }, 356 { 357 raftpb.ConfChange{ 358 Type: raftpb.ConfChangeAddNode, 359 NodeID: 5, 360 Context: ctx5, 361 }, 362 nil, 363 }, 364 { 365 raftpb.ConfChange{ 366 Type: raftpb.ConfChangeUpdateNode, 367 NodeID: 5, 368 Context: ctx, 369 }, 370 ErrIDNotFound, 371 }, 372 // try to change the peer url of 2 to the peer url of 3 373 { 374 raftpb.ConfChange{ 375 Type: raftpb.ConfChangeUpdateNode, 376 NodeID: 2, 377 Context: ctx2to3, 378 }, 379 ErrPeerURLexists, 380 }, 381 { 382 raftpb.ConfChange{ 383 Type: raftpb.ConfChangeUpdateNode, 384 NodeID: 2, 385 Context: ctx2to5, 386 }, 387 nil, 388 }, 389 } 390 for i, tt := range tests { 391 err := cl.ValidateConfigurationChange(tt.cc) 392 if err != tt.werr { 393 t.Errorf("#%d: validateConfigurationChange error = %v, want %v", i, err, tt.werr) 394 } 395 } 396 } 397 398 func TestClusterGenID(t *testing.T) { 399 cs := newTestCluster([]*Member{ 400 newTestMember(1, nil, "", nil), 401 newTestMember(2, nil, "", nil), 402 }) 403 404 cs.genID() 405 if cs.ID() == 0 { 406 t.Fatalf("cluster.ID = %v, want not 0", cs.ID()) 407 } 408 previd := cs.ID() 409 410 cs.SetStore(mockstore.NewNop()) 411 cs.AddMember(newTestMember(3, nil, "", nil)) 412 cs.genID() 413 if cs.ID() == previd { 414 t.Fatalf("cluster.ID = %v, want not %v", cs.ID(), previd) 415 } 416 } 417 418 func TestNodeToMemberBad(t *testing.T) { 419 tests := []*store.NodeExtern{ 420 {Key: "/1234", Nodes: []*store.NodeExtern{ 421 {Key: "/1234/strange"}, 422 }}, 423 {Key: "/1234", Nodes: []*store.NodeExtern{ 424 {Key: "/1234/raftAttributes", Value: stringp("garbage")}, 425 }}, 426 {Key: "/1234", Nodes: []*store.NodeExtern{ 427 {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)}, 428 }}, 429 {Key: "/1234", Nodes: []*store.NodeExtern{ 430 {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)}, 431 {Key: "/1234/strange"}, 432 }}, 433 {Key: "/1234", Nodes: []*store.NodeExtern{ 434 {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)}, 435 {Key: "/1234/attributes", Value: stringp("garbage")}, 436 }}, 437 {Key: "/1234", Nodes: []*store.NodeExtern{ 438 {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)}, 439 {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)}, 440 {Key: "/1234/strange"}, 441 }}, 442 } 443 for i, tt := range tests { 444 if _, err := nodeToMember(tt); err == nil { 445 t.Errorf("#%d: unexpected nil error", i) 446 } 447 } 448 } 449 450 func TestClusterAddMember(t *testing.T) { 451 st := mockstore.NewRecorder() 452 c := newTestCluster(nil) 453 c.SetStore(st) 454 c.AddMember(newTestMember(1, nil, "node1", nil)) 455 456 wactions := []testutil.Action{ 457 { 458 Name: "Create", 459 Params: []interface{}{ 460 path.Join(StoreMembersPrefix, "1", "raftAttributes"), 461 false, 462 `{"peerURLs":null}`, 463 false, 464 store.TTLOptionSet{ExpireTime: store.Permanent}, 465 }, 466 }, 467 } 468 if g := st.Action(); !reflect.DeepEqual(g, wactions) { 469 t.Errorf("actions = %v, want %v", g, wactions) 470 } 471 } 472 473 func TestClusterMembers(t *testing.T) { 474 cls := &RaftCluster{ 475 members: map[types.ID]*Member{ 476 1: {ID: 1}, 477 20: {ID: 20}, 478 100: {ID: 100}, 479 5: {ID: 5}, 480 50: {ID: 50}, 481 }, 482 } 483 w := []*Member{ 484 {ID: 1}, 485 {ID: 5}, 486 {ID: 20}, 487 {ID: 50}, 488 {ID: 100}, 489 } 490 if g := cls.Members(); !reflect.DeepEqual(g, w) { 491 t.Fatalf("Members()=%#v, want %#v", g, w) 492 } 493 } 494 495 func TestClusterRemoveMember(t *testing.T) { 496 st := mockstore.NewRecorder() 497 c := newTestCluster(nil) 498 c.SetStore(st) 499 c.RemoveMember(1) 500 501 wactions := []testutil.Action{ 502 {Name: "Delete", Params: []interface{}{MemberStoreKey(1), true, true}}, 503 {Name: "Create", Params: []interface{}{RemovedMemberStoreKey(1), false, "", false, store.TTLOptionSet{ExpireTime: store.Permanent}}}, 504 } 505 if !reflect.DeepEqual(st.Action(), wactions) { 506 t.Errorf("actions = %v, want %v", st.Action(), wactions) 507 } 508 } 509 510 func TestClusterUpdateAttributes(t *testing.T) { 511 name := "etcd" 512 clientURLs := []string{"http://127.0.0.1:4001"} 513 tests := []struct { 514 mems []*Member 515 removed map[types.ID]bool 516 wmems []*Member 517 }{ 518 // update attributes of existing member 519 { 520 []*Member{ 521 newTestMember(1, nil, "", nil), 522 }, 523 nil, 524 []*Member{ 525 newTestMember(1, nil, name, clientURLs), 526 }, 527 }, 528 // update attributes of removed member 529 { 530 nil, 531 map[types.ID]bool{types.ID(1): true}, 532 nil, 533 }, 534 } 535 for i, tt := range tests { 536 c := newTestCluster(tt.mems) 537 c.removed = tt.removed 538 539 c.UpdateAttributes(types.ID(1), Attributes{Name: name, ClientURLs: clientURLs}) 540 if g := c.Members(); !reflect.DeepEqual(g, tt.wmems) { 541 t.Errorf("#%d: members = %+v, want %+v", i, g, tt.wmems) 542 } 543 } 544 } 545 546 func TestNodeToMember(t *testing.T) { 547 n := &store.NodeExtern{Key: "/1234", Nodes: []*store.NodeExtern{ 548 {Key: "/1234/attributes", Value: stringp(`{"name":"node1","clientURLs":null}`)}, 549 {Key: "/1234/raftAttributes", Value: stringp(`{"peerURLs":null}`)}, 550 }} 551 wm := &Member{ID: 0x1234, RaftAttributes: RaftAttributes{}, Attributes: Attributes{Name: "node1"}} 552 m, err := nodeToMember(n) 553 if err != nil { 554 t.Fatalf("unexpected nodeToMember error: %v", err) 555 } 556 if !reflect.DeepEqual(m, wm) { 557 t.Errorf("member = %+v, want %+v", m, wm) 558 } 559 } 560 561 func newTestCluster(membs []*Member) *RaftCluster { 562 c := &RaftCluster{members: make(map[types.ID]*Member), removed: make(map[types.ID]bool)} 563 for _, m := range membs { 564 c.members[m.ID] = m 565 } 566 return c 567 } 568 569 func stringp(s string) *string { return &s } 570 571 func TestIsReadyToAddNewMember(t *testing.T) { 572 tests := []struct { 573 members []*Member 574 want bool 575 }{ 576 { 577 // 0/3 members ready, should fail 578 []*Member{ 579 newTestMember(1, nil, "", nil), 580 newTestMember(2, nil, "", nil), 581 newTestMember(3, nil, "", nil), 582 }, 583 false, 584 }, 585 { 586 // 1/2 members ready, should fail 587 []*Member{ 588 newTestMember(1, nil, "1", nil), 589 newTestMember(2, nil, "", nil), 590 }, 591 false, 592 }, 593 { 594 // 1/3 members ready, should fail 595 []*Member{ 596 newTestMember(1, nil, "1", nil), 597 newTestMember(2, nil, "", nil), 598 newTestMember(3, nil, "", nil), 599 }, 600 false, 601 }, 602 { 603 // 1/1 members ready, should succeed (special case of 1-member cluster for recovery) 604 []*Member{ 605 newTestMember(1, nil, "1", nil), 606 }, 607 true, 608 }, 609 { 610 // 2/3 members ready, should fail 611 []*Member{ 612 newTestMember(1, nil, "1", nil), 613 newTestMember(2, nil, "2", nil), 614 newTestMember(3, nil, "", nil), 615 }, 616 false, 617 }, 618 { 619 // 3/3 members ready, should be fine to add one member and retain quorum 620 []*Member{ 621 newTestMember(1, nil, "1", nil), 622 newTestMember(2, nil, "2", nil), 623 newTestMember(3, nil, "3", nil), 624 }, 625 true, 626 }, 627 { 628 // 3/4 members ready, should be fine to add one member and retain quorum 629 []*Member{ 630 newTestMember(1, nil, "1", nil), 631 newTestMember(2, nil, "2", nil), 632 newTestMember(3, nil, "3", nil), 633 newTestMember(4, nil, "", nil), 634 }, 635 true, 636 }, 637 { 638 // empty cluster, it is impossible but should fail 639 []*Member{}, 640 false, 641 }, 642 } 643 for i, tt := range tests { 644 c := newTestCluster(tt.members) 645 if got := c.IsReadyToAddNewMember(); got != tt.want { 646 t.Errorf("%d: isReadyToAddNewMember returned %t, want %t", i, got, tt.want) 647 } 648 } 649 } 650 651 func TestIsReadyToRemoveMember(t *testing.T) { 652 tests := []struct { 653 members []*Member 654 removeID uint64 655 want bool 656 }{ 657 { 658 // 1/1 members ready, should fail 659 []*Member{ 660 newTestMember(1, nil, "1", nil), 661 }, 662 1, 663 false, 664 }, 665 { 666 // 0/3 members ready, should fail 667 []*Member{ 668 newTestMember(1, nil, "", nil), 669 newTestMember(2, nil, "", nil), 670 newTestMember(3, nil, "", nil), 671 }, 672 1, 673 false, 674 }, 675 { 676 // 1/2 members ready, should be fine to remove unstarted member 677 // (isReadyToRemoveMember() logic should return success, but operation itself would fail) 678 []*Member{ 679 newTestMember(1, nil, "1", nil), 680 newTestMember(2, nil, "", nil), 681 }, 682 2, 683 true, 684 }, 685 { 686 // 2/3 members ready, should fail 687 []*Member{ 688 newTestMember(1, nil, "1", nil), 689 newTestMember(2, nil, "2", nil), 690 newTestMember(3, nil, "", nil), 691 }, 692 2, 693 false, 694 }, 695 { 696 // 3/3 members ready, should be fine to remove one member and retain quorum 697 []*Member{ 698 newTestMember(1, nil, "1", nil), 699 newTestMember(2, nil, "2", nil), 700 newTestMember(3, nil, "3", nil), 701 }, 702 3, 703 true, 704 }, 705 { 706 // 3/4 members ready, should be fine to remove one member 707 []*Member{ 708 newTestMember(1, nil, "1", nil), 709 newTestMember(2, nil, "2", nil), 710 newTestMember(3, nil, "3", nil), 711 newTestMember(4, nil, "", nil), 712 }, 713 3, 714 true, 715 }, 716 { 717 // 3/4 members ready, should be fine to remove unstarted member 718 []*Member{ 719 newTestMember(1, nil, "1", nil), 720 newTestMember(2, nil, "2", nil), 721 newTestMember(3, nil, "3", nil), 722 newTestMember(4, nil, "", nil), 723 }, 724 4, 725 true, 726 }, 727 } 728 for i, tt := range tests { 729 c := newTestCluster(tt.members) 730 if got := c.IsReadyToRemoveMember(tt.removeID); got != tt.want { 731 t.Errorf("%d: isReadyToAddNewMember returned %t, want %t", i, got, tt.want) 732 } 733 } 734 }