github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/state/raft/storage_test.go (about) 1 package raft_test 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 "time" 12 13 "code.cloudfoundry.org/clock/fakeclock" 14 "github.com/docker/swarmkit/api" 15 "github.com/docker/swarmkit/manager/state/raft" 16 "github.com/docker/swarmkit/manager/state/raft/storage" 17 raftutils "github.com/docker/swarmkit/manager/state/raft/testutils" 18 "github.com/docker/swarmkit/manager/state/store" 19 "github.com/docker/swarmkit/testutils" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestRaftSnapshot(t *testing.T) { 25 t.Parallel() 26 27 // Bring up a 3 node cluster 28 nodes, clockSource := raftutils.NewRaftCluster(t, tc, &api.RaftConfig{SnapshotInterval: 9, LogEntriesForSlowFollowers: 0}) 29 defer raftutils.TeardownCluster(nodes) 30 31 nodeIDs := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10", "id11", "id12"} 32 values := make([]*api.Node, len(nodeIDs)) 33 snapshotFilenames := make(map[uint64]string, 4) 34 35 // Propose 3 values 36 var err error 37 for i, nodeID := range nodeIDs[:3] { 38 values[i], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeID) 39 assert.NoError(t, err, "failed to propose value") 40 } 41 42 // None of the nodes should have snapshot files yet 43 for _, node := range nodes { 44 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 45 assert.NoError(t, err) 46 assert.Len(t, dirents, 0) 47 } 48 49 // Check all nodes have all the data. 50 // This also acts as a synchronization point so that the next value we 51 // propose will arrive as a separate message to the raft state machine, 52 // and it is guaranteed to have the right cluster settings when 53 // deciding whether to create a new snapshot. 54 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs[:3], values) 55 56 // Propose a 4th value 57 values[3], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[3]) 58 assert.NoError(t, err, "failed to propose value") 59 60 // All nodes should now have a snapshot file 61 for nodeID, node := range nodes { 62 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 63 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 64 if err != nil { 65 return err 66 } 67 if len(dirents) != 1 { 68 return fmt.Errorf("expected 1 snapshot, found %d", len(dirents)) 69 } 70 snapshotFilenames[nodeID] = dirents[0].Name() 71 return nil 72 })) 73 } 74 75 // Add a node to the cluster 76 raftutils.AddRaftNode(t, clockSource, nodes, tc) 77 78 // It should get a copy of the snapshot 79 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 80 dirents, err := ioutil.ReadDir(filepath.Join(nodes[4].StateDir, "snap-v3-encrypted")) 81 if err != nil { 82 return err 83 } 84 if len(dirents) != 1 { 85 return fmt.Errorf("expected 1 snapshot, found %d on new node", len(dirents)) 86 } 87 snapshotFilenames[4] = dirents[0].Name() 88 return nil 89 })) 90 91 // It should know about the other nodes 92 stripMembers := func(memberList map[uint64]*api.RaftMember) map[uint64]*api.RaftMember { 93 raftNodes := make(map[uint64]*api.RaftMember) 94 for k, v := range memberList { 95 raftNodes[k] = &api.RaftMember{ 96 RaftID: v.RaftID, 97 Addr: v.Addr, 98 } 99 } 100 return raftNodes 101 } 102 assert.Equal(t, stripMembers(nodes[1].GetMemberlist()), stripMembers(nodes[4].GetMemberlist())) 103 104 // All nodes should have all the data 105 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs[:4], values) 106 107 // Propose more values to provoke a second snapshot 108 for i := 4; i != len(nodeIDs); i++ { 109 values[i], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[i]) 110 assert.NoError(t, err, "failed to propose value") 111 } 112 113 // All nodes should have a snapshot under a *different* name 114 for nodeID, node := range nodes { 115 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 116 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 117 if err != nil { 118 return err 119 } 120 if len(dirents) != 1 { 121 return fmt.Errorf("expected 1 snapshot, found %d on node %d", len(dirents), nodeID) 122 } 123 if dirents[0].Name() == snapshotFilenames[nodeID] { 124 return fmt.Errorf("snapshot %s did not get replaced on node %d", snapshotFilenames[nodeID], nodeID) 125 } 126 return nil 127 })) 128 } 129 130 // All nodes should have all the data 131 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 132 } 133 134 func TestRaftSnapshotRestart(t *testing.T) { 135 t.Parallel() 136 137 // Bring up a 3 node cluster 138 nodes, clockSource := raftutils.NewRaftCluster(t, tc, &api.RaftConfig{SnapshotInterval: 10, LogEntriesForSlowFollowers: 0}) 139 defer raftutils.TeardownCluster(nodes) 140 141 nodeIDs := []string{"id1", "id2", "id3", "id4", "id5", "id6", "id7"} 142 values := make([]*api.Node, len(nodeIDs)) 143 144 // Propose 3 values 145 var err error 146 for i, nodeID := range nodeIDs[:3] { 147 values[i], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeID) 148 assert.NoError(t, err, "failed to propose value") 149 } 150 151 // Take down node 3 152 nodes[3].Server.Stop() 153 nodes[3].ShutdownRaft() 154 155 // Propose a 4th value before the snapshot 156 values[3], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[3]) 157 assert.NoError(t, err, "failed to propose value") 158 159 // Remaining nodes shouldn't have snapshot files yet 160 for _, node := range []*raftutils.TestNode{nodes[1], nodes[2]} { 161 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 162 assert.NoError(t, err) 163 assert.Len(t, dirents, 0) 164 } 165 166 // Add a node to the cluster before the snapshot. This is the event 167 // that triggers the snapshot. 168 nodes[4] = raftutils.NewJoinNode(t, clockSource, nodes[1].Address, tc) 169 raftutils.WaitForCluster(t, clockSource, map[uint64]*raftutils.TestNode{1: nodes[1], 2: nodes[2], 4: nodes[4]}) 170 171 // Remaining nodes should now have a snapshot file 172 for nodeIdx, node := range []*raftutils.TestNode{nodes[1], nodes[2]} { 173 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 174 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 175 if err != nil { 176 return err 177 } 178 if len(dirents) != 1 { 179 return fmt.Errorf("expected 1 snapshot, found %d on node %d", len(dirents), nodeIdx+1) 180 } 181 return nil 182 })) 183 } 184 raftutils.CheckValuesOnNodes(t, clockSource, map[uint64]*raftutils.TestNode{1: nodes[1], 2: nodes[2]}, nodeIDs[:4], values[:4]) 185 186 // Propose a 5th value 187 values[4], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[4]) 188 require.NoError(t, err) 189 190 // Add another node to the cluster 191 nodes[5] = raftutils.NewJoinNode(t, clockSource, nodes[1].Address, tc) 192 raftutils.WaitForCluster(t, clockSource, map[uint64]*raftutils.TestNode{1: nodes[1], 2: nodes[2], 4: nodes[4], 5: nodes[5]}) 193 194 // New node should get a copy of the snapshot 195 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 196 dirents, err := ioutil.ReadDir(filepath.Join(nodes[5].StateDir, "snap-v3-encrypted")) 197 if err != nil { 198 return err 199 } 200 if len(dirents) != 1 { 201 return fmt.Errorf("expected 1 snapshot, found %d on new node", len(dirents)) 202 } 203 return nil 204 })) 205 206 dirents, err := ioutil.ReadDir(filepath.Join(nodes[5].StateDir, "snap-v3-encrypted")) 207 assert.NoError(t, err) 208 assert.Len(t, dirents, 1) 209 raftutils.CheckValuesOnNodes(t, clockSource, map[uint64]*raftutils.TestNode{1: nodes[1], 2: nodes[2]}, nodeIDs[:5], values[:5]) 210 211 // It should know about the other nodes, including the one that was just added 212 stripMembers := func(memberList map[uint64]*api.RaftMember) map[uint64]*api.RaftMember { 213 raftNodes := make(map[uint64]*api.RaftMember) 214 for k, v := range memberList { 215 raftNodes[k] = &api.RaftMember{ 216 RaftID: v.RaftID, 217 Addr: v.Addr, 218 } 219 } 220 return raftNodes 221 } 222 assert.Equal(t, stripMembers(nodes[1].GetMemberlist()), stripMembers(nodes[4].GetMemberlist())) 223 224 // Restart node 3 225 nodes[3] = raftutils.RestartNode(t, clockSource, nodes[3], false) 226 raftutils.WaitForCluster(t, clockSource, nodes) 227 228 // Node 3 should know about other nodes, including the new one 229 assert.Len(t, nodes[3].GetMemberlist(), 5) 230 assert.Equal(t, stripMembers(nodes[1].GetMemberlist()), stripMembers(nodes[3].GetMemberlist())) 231 232 // Propose yet another value, to make sure the rejoined node is still 233 // receiving new logs 234 values[5], err = raftutils.ProposeValue(t, raftutils.Leader(nodes), DefaultProposalTime, nodeIDs[5]) 235 require.NoError(t, err) 236 237 // All nodes should have all the data 238 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs[:6], values[:6]) 239 240 // Restart node 3 again. It should load the snapshot. 241 nodes[3].Server.Stop() 242 nodes[3].ShutdownRaft() 243 nodes[3] = raftutils.RestartNode(t, clockSource, nodes[3], false) 244 raftutils.WaitForCluster(t, clockSource, nodes) 245 246 assert.Len(t, nodes[3].GetMemberlist(), 5) 247 assert.Equal(t, stripMembers(nodes[1].GetMemberlist()), stripMembers(nodes[3].GetMemberlist())) 248 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs[:6], values[:6]) 249 250 // Propose again. Just to check consensus after this latest restart. 251 values[6], err = raftutils.ProposeValue(t, raftutils.Leader(nodes), DefaultProposalTime, nodeIDs[6]) 252 require.NoError(t, err) 253 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 254 } 255 256 func TestRaftSnapshotForceNewCluster(t *testing.T) { 257 t.Parallel() 258 259 // Bring up a 3 node cluster 260 nodes, clockSource := raftutils.NewRaftCluster(t, tc, &api.RaftConfig{SnapshotInterval: 10, LogEntriesForSlowFollowers: 0}) 261 defer raftutils.TeardownCluster(nodes) 262 263 nodeIDs := []string{"id1", "id2", "id3", "id4", "id5"} 264 265 // Propose 3 values. 266 for _, nodeID := range nodeIDs[:3] { 267 _, err := raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeID) 268 assert.NoError(t, err, "failed to propose value") 269 } 270 271 // Remove one of the original nodes 272 273 // Use gRPC instead of calling handler directly because of 274 // authorization check. 275 cc, err := dial(nodes[1], nodes[1].Address) 276 assert.NoError(t, err) 277 raftClient := api.NewRaftMembershipClient(cc) 278 defer cc.Close() 279 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 280 resp, err := raftClient.Leave(ctx, &api.LeaveRequest{Node: &api.RaftMember{RaftID: nodes[2].Config.ID}}) 281 cancel() 282 assert.NoError(t, err, "error sending message to leave the raft") 283 assert.NotNil(t, resp, "leave response message is nil") 284 285 raftutils.ShutdownNode(nodes[2]) 286 delete(nodes, 2) 287 288 // Nodes shouldn't have snapshot files yet 289 for _, node := range nodes { 290 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 291 assert.NoError(t, err) 292 assert.Len(t, dirents, 0) 293 } 294 295 // Trigger a snapshot, with a 4th proposal 296 _, err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[3]) 297 assert.NoError(t, err, "failed to propose value") 298 299 // Nodes should now have a snapshot file 300 for nodeIdx, node := range nodes { 301 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 302 dirents, err := ioutil.ReadDir(filepath.Join(node.StateDir, "snap-v3-encrypted")) 303 if err != nil { 304 return err 305 } 306 if len(dirents) != 1 { 307 return fmt.Errorf("expected 1 snapshot, found %d on node %d", len(dirents), nodeIdx+1) 308 } 309 return nil 310 })) 311 } 312 313 // Join another node 314 nodes[4] = raftutils.NewJoinNode(t, clockSource, nodes[1].Address, tc) 315 raftutils.WaitForCluster(t, clockSource, nodes) 316 317 // Only restart the first node with force-new-cluster option 318 nodes[1].Server.Stop() 319 nodes[1].ShutdownRaft() 320 nodes[1] = raftutils.RestartNode(t, clockSource, nodes[1], true) 321 raftutils.WaitForCluster(t, clockSource, map[uint64]*raftutils.TestNode{1: nodes[1]}) 322 323 // The memberlist should contain exactly one node (self) 324 memberlist := nodes[1].GetMemberlist() 325 require.Len(t, memberlist, 1) 326 327 // Propose a 5th value 328 _, err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeIDs[4]) 329 require.NoError(t, err) 330 } 331 332 func TestGCWAL(t *testing.T) { 333 t.Parallel() 334 335 // Additional log entries from cluster setup, leader election 336 extraLogEntries := 5 337 // Number of large entries to propose 338 proposals := 8 339 340 // Bring up a 3 node cluster 341 nodes, clockSource := raftutils.NewRaftCluster(t, tc, &api.RaftConfig{SnapshotInterval: uint64(proposals + extraLogEntries), LogEntriesForSlowFollowers: 0}) 342 343 for i := 0; i != proposals; i++ { 344 _, err := proposeLargeValue(t, nodes[1], DefaultProposalTime, fmt.Sprintf("id%d", i)) 345 assert.NoError(t, err, "failed to propose value") 346 } 347 348 time.Sleep(250 * time.Millisecond) 349 350 // Snapshot should have been triggered just as the WAL rotated, so 351 // both WAL files should be preserved 352 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 353 dirents, err := ioutil.ReadDir(filepath.Join(nodes[1].StateDir, "snap-v3-encrypted")) 354 if err != nil { 355 return err 356 } 357 if len(dirents) != 1 { 358 return fmt.Errorf("expected 1 snapshot, found %d", len(dirents)) 359 } 360 361 dirents, err = ioutil.ReadDir(filepath.Join(nodes[1].StateDir, "wal-v3-encrypted")) 362 if err != nil { 363 return err 364 } 365 var walCount int 366 for _, f := range dirents { 367 if strings.HasSuffix(f.Name(), ".wal") { 368 walCount++ 369 } 370 } 371 if walCount != 2 { 372 return fmt.Errorf("expected 2 WAL files, found %d", walCount) 373 } 374 return nil 375 })) 376 377 raftutils.TeardownCluster(nodes) 378 379 // Repeat this test, but trigger the snapshot after the WAL has rotated 380 proposals++ 381 nodes, clockSource = raftutils.NewRaftCluster(t, tc, &api.RaftConfig{SnapshotInterval: uint64(proposals + extraLogEntries), LogEntriesForSlowFollowers: 0}) 382 defer raftutils.TeardownCluster(nodes) 383 384 for i := 0; i != proposals; i++ { 385 _, err := proposeLargeValue(t, nodes[1], DefaultProposalTime, fmt.Sprintf("id%d", i)) 386 assert.NoError(t, err, "failed to propose value") 387 } 388 389 time.Sleep(250 * time.Millisecond) 390 391 // This time only one WAL file should be saved. 392 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 393 dirents, err := ioutil.ReadDir(filepath.Join(nodes[1].StateDir, "snap-v3-encrypted")) 394 if err != nil { 395 return err 396 } 397 398 if len(dirents) != 1 { 399 return fmt.Errorf("expected 1 snapshot, found %d", len(dirents)) 400 } 401 402 dirents, err = ioutil.ReadDir(filepath.Join(nodes[1].StateDir, "wal-v3-encrypted")) 403 if err != nil { 404 return err 405 } 406 var walCount int 407 for _, f := range dirents { 408 if strings.HasSuffix(f.Name(), ".wal") { 409 walCount++ 410 } 411 } 412 if walCount != 1 { 413 return fmt.Errorf("expected 1 WAL file, found %d", walCount) 414 } 415 return nil 416 })) 417 418 // Restart the whole cluster 419 for _, node := range nodes { 420 node.Server.Stop() 421 node.ShutdownRaft() 422 } 423 424 raftutils.AdvanceTicks(clockSource, 5) 425 426 i := 0 427 for k, node := range nodes { 428 nodes[k] = raftutils.RestartNode(t, clockSource, node, false) 429 i++ 430 } 431 raftutils.WaitForCluster(t, clockSource, nodes) 432 433 // Is the data intact after restart? 434 for _, node := range nodes { 435 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 436 var err error 437 node.MemoryStore().View(func(tx store.ReadTx) { 438 var allNodes []*api.Node 439 allNodes, err = store.FindNodes(tx, store.All) 440 if err != nil { 441 return 442 } 443 if len(allNodes) != proposals { 444 err = fmt.Errorf("expected %d nodes, got %d", proposals, len(allNodes)) 445 return 446 } 447 }) 448 return err 449 })) 450 } 451 452 // It should still be possible to propose values 453 _, err := raftutils.ProposeValue(t, raftutils.Leader(nodes), DefaultProposalTime, "newnode") 454 assert.NoError(t, err, "failed to propose value") 455 456 for _, node := range nodes { 457 assert.NoError(t, testutils.PollFunc(clockSource, func() error { 458 var err error 459 node.MemoryStore().View(func(tx store.ReadTx) { 460 var allNodes []*api.Node 461 allNodes, err = store.FindNodes(tx, store.All) 462 if err != nil { 463 return 464 } 465 if len(allNodes) != proposals+1 { 466 err = fmt.Errorf("expected %d nodes, got %d", proposals, len(allNodes)) 467 return 468 } 469 }) 470 return err 471 })) 472 } 473 } 474 475 // proposeLargeValue proposes a 10kb value to a raft test cluster 476 func proposeLargeValue(t *testing.T, raftNode *raftutils.TestNode, time time.Duration, nodeID ...string) (*api.Node, error) { 477 nodeIDStr := "id1" 478 if len(nodeID) != 0 { 479 nodeIDStr = nodeID[0] 480 } 481 a := make([]byte, 10000) 482 for i := 0; i != len(a); i++ { 483 a[i] = 'a' 484 } 485 node := &api.Node{ 486 ID: nodeIDStr, 487 Spec: api.NodeSpec{ 488 Annotations: api.Annotations{ 489 Name: nodeIDStr, 490 Labels: map[string]string{ 491 "largestring": string(a), 492 }, 493 }, 494 }, 495 } 496 497 storeActions := []api.StoreAction{ 498 { 499 Action: api.StoreActionKindCreate, 500 Target: &api.StoreAction_Node{ 501 Node: node, 502 }, 503 }, 504 } 505 506 ctx, cancel := context.WithTimeout(context.Background(), time) 507 508 err := raftNode.ProposeValue(ctx, storeActions, func() { 509 err := raftNode.MemoryStore().ApplyStoreActions(storeActions) 510 assert.NoError(t, err, "error applying actions") 511 }) 512 cancel() 513 if err != nil { 514 return nil, err 515 } 516 517 return node, nil 518 } 519 520 // This test rotates the encryption key and waits for the expected thing to happen 521 func TestRaftEncryptionKeyRotationWait(t *testing.T) { 522 t.Parallel() 523 nodes := make(map[uint64]*raftutils.TestNode) 524 var clockSource *fakeclock.FakeClock 525 526 raftConfig := raft.DefaultRaftConfig() 527 nodes[1], clockSource = raftutils.NewInitNode(t, tc, &raftConfig) 528 defer raftutils.TeardownCluster(nodes) 529 530 nodeIDs := []string{"id1", "id2", "id3"} 531 values := make([]*api.Node, len(nodeIDs)) 532 533 // Propose 3 values 534 var err error 535 for i, nodeID := range nodeIDs[:3] { 536 values[i], err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, nodeID) 537 require.NoError(t, err, "failed to propose value") 538 } 539 540 snapDir := filepath.Join(nodes[1].StateDir, "snap-v3-encrypted") 541 542 startingKeys := nodes[1].KeyRotator.GetKeys() 543 544 // rotate the encryption key 545 nodes[1].KeyRotator.QueuePendingKey([]byte("key2")) 546 nodes[1].KeyRotator.RotationNotify() <- struct{}{} 547 548 // the rotation should trigger a snapshot, which should notify the rotator when it's done 549 require.NoError(t, testutils.PollFunc(clockSource, func() error { 550 snapshots, err := storage.ListSnapshots(snapDir) 551 if err != nil { 552 return err 553 } 554 if len(snapshots) != 1 { 555 return fmt.Errorf("expected 1 snapshot, found %d on new node", len(snapshots)) 556 } 557 if nodes[1].KeyRotator.NeedsRotation() { 558 return fmt.Errorf("rotation never finished") 559 } 560 return nil 561 })) 562 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 563 564 // Propose a 4th value 565 nodeIDs = append(nodeIDs, "id4") 566 v, err := raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, "id4") 567 require.NoError(t, err, "failed to propose value") 568 values = append(values, v) 569 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 570 571 nodes[1].Server.Stop() 572 nodes[1].ShutdownRaft() 573 574 // Try to restart node 1. Without the new unlock key, it can't actually start 575 n, ctx := raftutils.CopyNode(t, clockSource, nodes[1], false, raftutils.NewSimpleKeyRotator(startingKeys)) 576 require.Error(t, n.Node.JoinAndStart(ctx), 577 "should not have been able to restart since we can't read snapshots") 578 579 // with the right key, it can start, even if the right key is only the pending key 580 newKeys := startingKeys 581 newKeys.PendingDEK = []byte("key2") 582 nodes[1].KeyRotator = raftutils.NewSimpleKeyRotator(newKeys) 583 nodes[1] = raftutils.RestartNode(t, clockSource, nodes[1], false) 584 585 raftutils.WaitForCluster(t, clockSource, nodes) 586 587 // as soon as we joined, it should have finished rotating the key 588 require.False(t, nodes[1].KeyRotator.NeedsRotation()) 589 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 590 591 // break snapshotting, and ensure that key rotation never finishes 592 tempSnapDir := filepath.Join(nodes[1].StateDir, "snap-backup") 593 require.NoError(t, os.Rename(snapDir, tempSnapDir)) 594 require.NoError(t, ioutil.WriteFile(snapDir, []byte("this is no longer a directory"), 0644)) 595 596 nodes[1].KeyRotator.QueuePendingKey([]byte("key3")) 597 nodes[1].KeyRotator.RotationNotify() <- struct{}{} 598 599 time.Sleep(250 * time.Millisecond) 600 601 // rotation has not been finished, because we cannot take a snapshot 602 require.True(t, nodes[1].KeyRotator.NeedsRotation()) 603 604 // Propose a 5th value, so we have WALs written with the new key 605 nodeIDs = append(nodeIDs, "id5") 606 v, err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, "id5") 607 require.NoError(t, err, "failed to propose value") 608 values = append(values, v) 609 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 610 611 nodes[1].Server.Stop() 612 nodes[1].ShutdownRaft() 613 614 // restore the snapshot dir 615 require.NoError(t, os.RemoveAll(snapDir)) 616 require.NoError(t, os.Rename(tempSnapDir, snapDir)) 617 618 // Now the wals are a mix of key2 and key3 - we can't actually start with either key 619 singleKey := raft.EncryptionKeys{CurrentDEK: []byte("key2")} 620 n, ctx = raftutils.CopyNode(t, clockSource, nodes[1], false, raftutils.NewSimpleKeyRotator(singleKey)) 621 require.Error(t, n.Node.JoinAndStart(ctx), 622 "should not have been able to restart since we can't read all the WALs, even if we can read the snapshot") 623 singleKey = raft.EncryptionKeys{CurrentDEK: []byte("key3")} 624 n, ctx = raftutils.CopyNode(t, clockSource, nodes[1], false, raftutils.NewSimpleKeyRotator(singleKey)) 625 require.Error(t, n.Node.JoinAndStart(ctx), 626 "should not have been able to restart since we can't read all the WALs, and also not the snapshot") 627 628 nodes[1], ctx = raftutils.CopyNode(t, clockSource, nodes[1], false, 629 raftutils.NewSimpleKeyRotator(raft.EncryptionKeys{ 630 CurrentDEK: []byte("key2"), 631 PendingDEK: []byte("key3"), 632 })) 633 require.NoError(t, nodes[1].Node.JoinAndStart(ctx)) 634 635 // we can load, but we still need a snapshot because rotation hasn't finished 636 snapshots, err := storage.ListSnapshots(snapDir) 637 require.NoError(t, err) 638 require.Len(t, snapshots, 1, "expected 1 snapshot") 639 require.True(t, nodes[1].KeyRotator.NeedsRotation()) 640 currSnapshot := snapshots[0] 641 642 // start the node - everything should fix itself 643 go nodes[1].Node.Run(ctx) 644 raftutils.WaitForCluster(t, clockSource, nodes) 645 646 require.NoError(t, testutils.PollFunc(clockSource, func() error { 647 snapshots, err := storage.ListSnapshots(snapDir) 648 if err != nil { 649 return err 650 } 651 if len(snapshots) != 1 { 652 return fmt.Errorf("expected 1 snapshots, found %d on new node", len(snapshots)) 653 } 654 if snapshots[0] == currSnapshot { 655 return fmt.Errorf("new snapshot not done yet") 656 } 657 if nodes[1].KeyRotator.NeedsRotation() { 658 return fmt.Errorf("rotation never finished") 659 } 660 currSnapshot = snapshots[0] 661 return nil 662 })) 663 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 664 665 // If we can't update the keys, we wait for the next snapshot to do so 666 nodes[1].KeyRotator.SetUpdateFunc(func() error { return fmt.Errorf("nope!") }) 667 nodes[1].KeyRotator.QueuePendingKey([]byte("key4")) 668 nodes[1].KeyRotator.RotationNotify() <- struct{}{} 669 670 require.NoError(t, testutils.PollFunc(clockSource, func() error { 671 snapshots, err := storage.ListSnapshots(snapDir) 672 if err != nil { 673 return err 674 } 675 if len(snapshots) != 1 { 676 return fmt.Errorf("expected 1 snapshots, found %d on new node", len(snapshots)) 677 } 678 if snapshots[0] == currSnapshot { 679 return fmt.Errorf("new snapshot not done yet") 680 } 681 currSnapshot = snapshots[0] 682 return nil 683 })) 684 require.True(t, nodes[1].KeyRotator.NeedsRotation()) 685 686 // Fix updating the key rotator, and propose a 6th value - this should trigger the key 687 // rotation to finish 688 nodes[1].KeyRotator.SetUpdateFunc(nil) 689 nodeIDs = append(nodeIDs, "id6") 690 v, err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, "id6") 691 require.NoError(t, err, "failed to propose value") 692 values = append(values, v) 693 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 694 695 require.NoError(t, testutils.PollFunc(clockSource, func() error { 696 if nodes[1].KeyRotator.NeedsRotation() { 697 return fmt.Errorf("rotation never finished") 698 } 699 return nil 700 })) 701 702 // no new snapshot 703 snapshots, err = storage.ListSnapshots(snapDir) 704 require.NoError(t, err) 705 require.Len(t, snapshots, 1) 706 require.Equal(t, currSnapshot, snapshots[0]) 707 708 // Even if something goes wrong with getting keys, and needs rotation returns a false positive, 709 // if there's no PendingDEK nothing happens. 710 711 fakeTrue := true 712 nodes[1].KeyRotator.SetNeedsRotation(&fakeTrue) 713 nodes[1].KeyRotator.RotationNotify() <- struct{}{} 714 715 // propose another value 716 nodeIDs = append(nodeIDs, "id7") 717 v, err = raftutils.ProposeValue(t, nodes[1], DefaultProposalTime, "id7") 718 require.NoError(t, err, "failed to propose value") 719 values = append(values, v) 720 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 721 722 // no new snapshot 723 snapshots, err = storage.ListSnapshots(snapDir) 724 require.NoError(t, err) 725 require.Len(t, snapshots, 1) 726 require.Equal(t, currSnapshot, snapshots[0]) 727 728 // and when we restart, we can restart with the original key (the WAL written for the new proposed value) 729 // is written with the old key 730 nodes[1].Server.Stop() 731 nodes[1].ShutdownRaft() 732 733 nodes[1].KeyRotator = raftutils.NewSimpleKeyRotator(raft.EncryptionKeys{ 734 CurrentDEK: []byte("key4"), 735 }) 736 nodes[1] = raftutils.RestartNode(t, clockSource, nodes[1], false) 737 raftutils.WaitForCluster(t, clockSource, nodes) 738 raftutils.CheckValuesOnNodes(t, clockSource, nodes, nodeIDs, values) 739 } 740 741 // This test rotates the encryption key and restarts the node - the intent is try to trigger 742 // race conditions if there is more than one node and hence consensus may take longer. 743 func TestRaftEncryptionKeyRotationStress(t *testing.T) { 744 t.Parallel() 745 746 // Bring up a 3 nodes cluster 747 nodes, clockSource := raftutils.NewRaftCluster(t, tc) 748 defer raftutils.TeardownCluster(nodes) 749 leader := nodes[1] 750 751 // constantly propose values 752 done, stop, restart, clusterReady := make(chan struct{}), make(chan struct{}), make(chan struct{}), make(chan struct{}) 753 go func() { 754 counter := len(nodes) 755 for { 756 select { 757 case <-stop: 758 close(done) 759 return 760 case <-restart: 761 // the node restarts may trigger a leadership change, so wait until the cluster has 3 762 // nodes again and a leader is selected before proposing more values 763 <-clusterReady 764 leader = raftutils.Leader(nodes) 765 default: 766 counter += 1 767 raftutils.ProposeValue(t, leader, DefaultProposalTime, fmt.Sprintf("id%d", counter)) 768 } 769 } 770 }() 771 772 for i := 0; i < 30; i++ { 773 // rotate the encryption key 774 nodes[3].KeyRotator.QueuePendingKey([]byte(fmt.Sprintf("newKey%d", i))) 775 nodes[3].KeyRotator.RotationNotify() <- struct{}{} 776 777 require.NoError(t, testutils.PollFunc(clockSource, func() error { 778 if nodes[3].KeyRotator.GetKeys().PendingDEK == nil { 779 return nil 780 } 781 return fmt.Errorf("not done rotating yet") 782 })) 783 784 // restart the node and wait for everything to settle and a leader to be elected 785 nodes[3].Server.Stop() 786 nodes[3].ShutdownRaft() 787 restart <- struct{}{} 788 nodes[3] = raftutils.RestartNode(t, clockSource, nodes[3], false) 789 raftutils.AdvanceTicks(clockSource, 1) 790 791 raftutils.WaitForCluster(t, clockSource, nodes) 792 clusterReady <- struct{}{} 793 } 794 795 close(stop) 796 <-done 797 }