github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/fsm/snapshot_oss_test.go (about) 1 package fsm 2 3 import ( 4 "bytes" 5 "os" 6 "reflect" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/consul/acl" 11 "github.com/hashicorp/consul/agent/connect" 12 "github.com/hashicorp/consul/agent/consul/autopilot" 13 "github.com/hashicorp/consul/agent/consul/state" 14 "github.com/hashicorp/consul/agent/structs" 15 "github.com/hashicorp/consul/api" 16 "github.com/hashicorp/consul/lib" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19 ) 20 21 func TestFSM_SnapshotRestore_OSS(t *testing.T) { 22 t.Parallel() 23 24 assert := assert.New(t) 25 fsm, err := New(nil, os.Stderr) 26 if err != nil { 27 t.Fatalf("err: %v", err) 28 } 29 30 // Add some state 31 node1 := &structs.Node{ 32 ID: "610918a6-464f-fa9b-1a95-03bd6e88ed92", 33 Node: "foo", 34 Datacenter: "dc1", 35 Address: "127.0.0.1", 36 } 37 node2 := &structs.Node{ 38 ID: "40e4a748-2192-161a-0510-9bf59fe950b5", 39 Node: "baz", 40 Datacenter: "dc1", 41 Address: "127.0.0.2", 42 TaggedAddresses: map[string]string{ 43 "hello": "1.2.3.4", 44 }, 45 Meta: map[string]string{ 46 "testMeta": "testing123", 47 }, 48 } 49 assert.NoError(fsm.state.EnsureNode(1, node1)) 50 assert.NoError(fsm.state.EnsureNode(2, node2)) 51 52 // Add a service instance with Connect config. 53 connectConf := structs.ServiceConnect{ 54 Native: true, 55 Proxy: &structs.ServiceDefinitionConnectProxy{ 56 Command: []string{"foo", "bar"}, 57 ExecMode: "a", 58 Config: map[string]interface{}{ 59 "a": "qwer", 60 "b": 4.3, 61 }, 62 }, 63 } 64 fsm.state.EnsureService(3, "foo", &structs.NodeService{ 65 ID: "web", 66 Service: "web", 67 Tags: nil, 68 Address: "127.0.0.1", 69 Port: 80, 70 Connect: connectConf, 71 }) 72 fsm.state.EnsureService(4, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"primary"}, Address: "127.0.0.1", Port: 5000}) 73 fsm.state.EnsureService(5, "baz", &structs.NodeService{ID: "web", Service: "web", Tags: nil, Address: "127.0.0.2", Port: 80}) 74 fsm.state.EnsureService(6, "baz", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"secondary"}, Address: "127.0.0.2", Port: 5000}) 75 fsm.state.EnsureCheck(7, &structs.HealthCheck{ 76 Node: "foo", 77 CheckID: "web", 78 Name: "web connectivity", 79 Status: api.HealthPassing, 80 ServiceID: "web", 81 }) 82 fsm.state.KVSSet(8, &structs.DirEntry{ 83 Key: "/test", 84 Value: []byte("foo"), 85 }) 86 session := &structs.Session{ID: generateUUID(), Node: "foo"} 87 fsm.state.SessionCreate(9, session) 88 policy := structs.ACLPolicy{ 89 ID: structs.ACLPolicyGlobalManagementID, 90 Name: "global-management", 91 Description: "Builtin Policy that grants unlimited access", 92 Rules: structs.ACLPolicyGlobalManagement, 93 Syntax: acl.SyntaxCurrent, 94 } 95 policy.SetHash(true) 96 require.NoError(t, fsm.state.ACLPolicySet(1, &policy)) 97 98 token := &structs.ACLToken{ 99 AccessorID: "30fca056-9fbb-4455-b94a-bf0e2bc575d6", 100 SecretID: "cbe1c6fd-d865-4034-9d6d-64fef7fb46a9", 101 Description: "Bootstrap Token (Global Management)", 102 Policies: []structs.ACLTokenPolicyLink{ 103 { 104 ID: structs.ACLPolicyGlobalManagementID, 105 }, 106 }, 107 CreateTime: time.Now(), 108 Local: false, 109 // DEPRECATED (ACL-Legacy-Compat) - This is used so that the bootstrap token is still visible via the v1 acl APIs 110 Type: structs.ACLTokenTypeManagement, 111 } 112 require.NoError(t, fsm.state.ACLBootstrap(10, 0, token, false)) 113 114 fsm.state.KVSSet(11, &structs.DirEntry{ 115 Key: "/remove", 116 Value: []byte("foo"), 117 }) 118 fsm.state.KVSDelete(12, "/remove") 119 idx, _, err := fsm.state.KVSList(nil, "/remove") 120 if err != nil { 121 t.Fatalf("err: %s", err) 122 } 123 if idx != 12 { 124 t.Fatalf("bad index: %d", idx) 125 } 126 127 updates := structs.Coordinates{ 128 &structs.Coordinate{ 129 Node: "baz", 130 Coord: generateRandomCoordinate(), 131 }, 132 &structs.Coordinate{ 133 Node: "foo", 134 Coord: generateRandomCoordinate(), 135 }, 136 } 137 if err := fsm.state.CoordinateBatchUpdate(13, updates); err != nil { 138 t.Fatalf("err: %s", err) 139 } 140 141 query := structs.PreparedQuery{ 142 ID: generateUUID(), 143 Service: structs.ServiceQuery{ 144 Service: "web", 145 }, 146 RaftIndex: structs.RaftIndex{ 147 CreateIndex: 14, 148 ModifyIndex: 14, 149 }, 150 } 151 if err := fsm.state.PreparedQuerySet(14, &query); err != nil { 152 t.Fatalf("err: %s", err) 153 } 154 155 autopilotConf := &autopilot.Config{ 156 CleanupDeadServers: true, 157 LastContactThreshold: 100 * time.Millisecond, 158 MaxTrailingLogs: 222, 159 } 160 if err := fsm.state.AutopilotSetConfig(15, autopilotConf); err != nil { 161 t.Fatalf("err: %s", err) 162 } 163 164 // Intentions 165 ixn := structs.TestIntention(t) 166 ixn.ID = generateUUID() 167 ixn.RaftIndex = structs.RaftIndex{ 168 CreateIndex: 14, 169 ModifyIndex: 14, 170 } 171 assert.Nil(fsm.state.IntentionSet(14, ixn)) 172 173 // CA Roots 174 roots := []*structs.CARoot{ 175 connect.TestCA(t, nil), 176 connect.TestCA(t, nil), 177 } 178 for _, r := range roots[1:] { 179 r.Active = false 180 } 181 ok, err := fsm.state.CARootSetCAS(15, 0, roots) 182 assert.Nil(err) 183 assert.True(ok) 184 185 ok, err = fsm.state.CASetProviderState(16, &structs.CAConsulProviderState{ 186 ID: "asdf", 187 PrivateKey: "foo", 188 RootCert: "bar", 189 }) 190 assert.Nil(err) 191 assert.True(ok) 192 193 // CA Config 194 caConfig := &structs.CAConfiguration{ 195 ClusterID: "foo", 196 Provider: "consul", 197 Config: map[string]interface{}{ 198 "foo": "asdf", 199 "bar": 6.5, 200 }, 201 } 202 err = fsm.state.CASetConfig(17, caConfig) 203 assert.Nil(err) 204 205 // Snapshot 206 snap, err := fsm.Snapshot() 207 if err != nil { 208 t.Fatalf("err: %v", err) 209 } 210 defer snap.Release() 211 212 // Persist 213 buf := bytes.NewBuffer(nil) 214 sink := &MockSink{buf, false} 215 if err := snap.Persist(sink); err != nil { 216 t.Fatalf("err: %v", err) 217 } 218 219 // Try to restore on a new FSM 220 fsm2, err := New(nil, os.Stderr) 221 if err != nil { 222 t.Fatalf("err: %v", err) 223 } 224 225 // Do a restore 226 if err := fsm2.Restore(sink); err != nil { 227 t.Fatalf("err: %v", err) 228 } 229 230 // Verify the contents 231 _, nodes, err := fsm2.state.Nodes(nil) 232 if err != nil { 233 t.Fatalf("err: %s", err) 234 } 235 if len(nodes) != 2 { 236 t.Fatalf("bad: %v", nodes) 237 } 238 if nodes[0].ID != node2.ID || 239 nodes[0].Node != "baz" || 240 nodes[0].Datacenter != "dc1" || 241 nodes[0].Address != "127.0.0.2" || 242 len(nodes[0].Meta) != 1 || 243 nodes[0].Meta["testMeta"] != "testing123" || 244 len(nodes[0].TaggedAddresses) != 1 || 245 nodes[0].TaggedAddresses["hello"] != "1.2.3.4" { 246 t.Fatalf("bad: %v", nodes[0]) 247 } 248 if nodes[1].ID != node1.ID || 249 nodes[1].Node != "foo" || 250 nodes[1].Datacenter != "dc1" || 251 nodes[1].Address != "127.0.0.1" || 252 len(nodes[1].TaggedAddresses) != 0 { 253 t.Fatalf("bad: %v", nodes[1]) 254 } 255 256 _, fooSrv, err := fsm2.state.NodeServices(nil, "foo") 257 if err != nil { 258 t.Fatalf("err: %s", err) 259 } 260 if len(fooSrv.Services) != 2 { 261 t.Fatalf("Bad: %v", fooSrv) 262 } 263 if !lib.StrContains(fooSrv.Services["db"].Tags, "primary") { 264 t.Fatalf("Bad: %v", fooSrv) 265 } 266 if fooSrv.Services["db"].Port != 5000 { 267 t.Fatalf("Bad: %v", fooSrv) 268 } 269 connectSrv := fooSrv.Services["web"] 270 if !reflect.DeepEqual(connectSrv.Connect, connectConf) { 271 t.Fatalf("got: %v, want: %v", connectSrv.Connect, connectConf) 272 } 273 274 _, checks, err := fsm2.state.NodeChecks(nil, "foo") 275 if err != nil { 276 t.Fatalf("err: %s", err) 277 } 278 if len(checks) != 1 { 279 t.Fatalf("Bad: %v", checks) 280 } 281 282 // Verify key is set 283 _, d, err := fsm2.state.KVSGet(nil, "/test") 284 if err != nil { 285 t.Fatalf("err: %v", err) 286 } 287 if string(d.Value) != "foo" { 288 t.Fatalf("bad: %v", d) 289 } 290 291 // Verify session is restored 292 idx, s, err := fsm2.state.SessionGet(nil, session.ID) 293 if err != nil { 294 t.Fatalf("err: %v", err) 295 } 296 if s.Node != "foo" { 297 t.Fatalf("bad: %v", s) 298 } 299 if idx <= 1 { 300 t.Fatalf("bad index: %d", idx) 301 } 302 303 // Verify ACL Token is restored 304 _, a, err := fsm2.state.ACLTokenGetByAccessor(nil, token.AccessorID) 305 require.NoError(t, err) 306 require.Equal(t, token.AccessorID, a.AccessorID) 307 require.Equal(t, token.ModifyIndex, a.ModifyIndex) 308 309 // Verify the acl-token-bootstrap index was restored 310 canBootstrap, index, err := fsm2.state.CanBootstrapACLToken() 311 require.False(t, canBootstrap) 312 require.True(t, index > 0) 313 314 // Verify ACL Policy is restored 315 _, policy2, err := fsm2.state.ACLPolicyGetByID(nil, structs.ACLPolicyGlobalManagementID) 316 require.NoError(t, err) 317 require.Equal(t, policy.Name, policy2.Name) 318 319 // Verify tombstones are restored 320 func() { 321 snap := fsm2.state.Snapshot() 322 defer snap.Close() 323 stones, err := snap.Tombstones() 324 if err != nil { 325 t.Fatalf("err: %s", err) 326 } 327 stone := stones.Next().(*state.Tombstone) 328 if stone == nil { 329 t.Fatalf("missing tombstone") 330 } 331 if stone.Key != "/remove" || stone.Index != 12 { 332 t.Fatalf("bad: %v", stone) 333 } 334 if stones.Next() != nil { 335 t.Fatalf("unexpected extra tombstones") 336 } 337 }() 338 339 // Verify coordinates are restored 340 _, coords, err := fsm2.state.Coordinates(nil) 341 if err != nil { 342 t.Fatalf("err: %s", err) 343 } 344 if !reflect.DeepEqual(coords, updates) { 345 t.Fatalf("bad: %#v", coords) 346 } 347 348 // Verify queries are restored. 349 _, queries, err := fsm2.state.PreparedQueryList(nil) 350 if err != nil { 351 t.Fatalf("err: %s", err) 352 } 353 if len(queries) != 1 { 354 t.Fatalf("bad: %#v", queries) 355 } 356 if !reflect.DeepEqual(queries[0], &query) { 357 t.Fatalf("bad: %#v", queries[0]) 358 } 359 360 // Verify autopilot config is restored. 361 _, restoredConf, err := fsm2.state.AutopilotConfig() 362 if err != nil { 363 t.Fatalf("err: %s", err) 364 } 365 if !reflect.DeepEqual(restoredConf, autopilotConf) { 366 t.Fatalf("bad: %#v, %#v", restoredConf, autopilotConf) 367 } 368 369 // Verify intentions are restored. 370 _, ixns, err := fsm2.state.Intentions(nil) 371 assert.Nil(err) 372 assert.Len(ixns, 1) 373 assert.Equal(ixn, ixns[0]) 374 375 // Verify CA roots are restored. 376 _, roots, err = fsm2.state.CARoots(nil) 377 assert.Nil(err) 378 assert.Len(roots, 2) 379 380 // Verify provider state is restored. 381 _, state, err := fsm2.state.CAProviderState("asdf") 382 assert.Nil(err) 383 assert.Equal("foo", state.PrivateKey) 384 assert.Equal("bar", state.RootCert) 385 386 // Verify CA configuration is restored. 387 _, caConf, err := fsm2.state.CAConfig() 388 assert.Nil(err) 389 assert.Equal(caConfig, caConf) 390 391 // Snapshot 392 snap, err = fsm2.Snapshot() 393 if err != nil { 394 t.Fatalf("err: %v", err) 395 } 396 defer snap.Release() 397 398 // Persist 399 buf = bytes.NewBuffer(nil) 400 sink = &MockSink{buf, false} 401 if err := snap.Persist(sink); err != nil { 402 t.Fatalf("err: %v", err) 403 } 404 405 // Try to restore on the old FSM and make sure it abandons the old state 406 // store. 407 abandonCh := fsm.state.AbandonCh() 408 if err := fsm.Restore(sink); err != nil { 409 t.Fatalf("err: %v", err) 410 } 411 select { 412 case <-abandonCh: 413 default: 414 t.Fatalf("bad") 415 } 416 } 417 418 func TestFSM_BadRestore_OSS(t *testing.T) { 419 t.Parallel() 420 // Create an FSM with some state. 421 fsm, err := New(nil, os.Stderr) 422 if err != nil { 423 t.Fatalf("err: %v", err) 424 } 425 fsm.state.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}) 426 abandonCh := fsm.state.AbandonCh() 427 428 // Do a bad restore. 429 buf := bytes.NewBuffer([]byte("bad snapshot")) 430 sink := &MockSink{buf, false} 431 if err := fsm.Restore(sink); err == nil { 432 t.Fatalf("err: %v", err) 433 } 434 435 // Verify the contents didn't get corrupted. 436 _, nodes, err := fsm.state.Nodes(nil) 437 if err != nil { 438 t.Fatalf("err: %s", err) 439 } 440 if len(nodes) != 1 { 441 t.Fatalf("bad: %v", nodes) 442 } 443 if nodes[0].Node != "foo" || 444 nodes[0].Address != "127.0.0.1" || 445 len(nodes[0].TaggedAddresses) != 0 { 446 t.Fatalf("bad: %v", nodes[0]) 447 } 448 449 // Verify the old state store didn't get abandoned. 450 select { 451 case <-abandonCh: 452 t.Fatalf("bad") 453 default: 454 } 455 } 456 457 func TestFSM_BadSnapshot_NilCAConfig(t *testing.T) { 458 t.Parallel() 459 460 require := require.New(t) 461 462 // Create an FSM with no config entry. 463 fsm, err := New(nil, os.Stderr) 464 if err != nil { 465 t.Fatalf("err: %v", err) 466 } 467 468 // Snapshot 469 snap, err := fsm.Snapshot() 470 if err != nil { 471 t.Fatalf("err: %v", err) 472 } 473 defer snap.Release() 474 475 // Persist 476 buf := bytes.NewBuffer(nil) 477 sink := &MockSink{buf, false} 478 if err := snap.Persist(sink); err != nil { 479 t.Fatalf("err: %v", err) 480 } 481 482 // Try to restore on a new FSM 483 fsm2, err := New(nil, os.Stderr) 484 if err != nil { 485 t.Fatalf("err: %v", err) 486 } 487 488 // Do a restore 489 if err := fsm2.Restore(sink); err != nil { 490 t.Fatalf("err: %v", err) 491 } 492 493 // Make sure there's no entry in the CA config table. 494 state := fsm2.State() 495 idx, config, err := state.CAConfig() 496 require.NoError(err) 497 require.Equal(uint64(0), idx) 498 if config != nil { 499 t.Fatalf("config should be nil") 500 } 501 }