package consul import ( "bytes" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/raft" "os" "testing" ) type MockSink struct { *bytes.Buffer cancel bool } func (m *MockSink) ID() string { return "Mock" } func (m *MockSink) Cancel() error { m.cancel = true return nil } func (m *MockSink) Close() error { return nil } func makeLog(buf []byte) *raft.Log { return &raft.Log{ Index: 1, Term: 1, Type: raft.LogCommand, Data: buf, } } func TestFSM_RegisterNode(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", } buf, err := structs.Encode(structs.RegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered if idx, found, _ := fsm.state.GetNode("foo"); !found { t.Fatalf("not found!") } else if idx != 1 { t.Fatalf("bad index: %d", idx) } // Verify service registered _, services := fsm.state.NodeServices("foo") if len(services.Services) != 0 { t.Fatalf("Services: %v", services) } } func TestFSM_RegisterNode_Service(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ ID: "db", Service: "db", Tag: "master", Port: 8000, }, Check: &structs.HealthCheck{ Node: "foo", CheckID: "db", Name: "db connectivity", Status: structs.HealthPassing, ServiceID: "db", }, } buf, err := structs.Encode(structs.RegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered if _, found, _ := fsm.state.GetNode("foo"); !found { t.Fatalf("not found!") } // Verify service registered _, services := fsm.state.NodeServices("foo") if _, ok := services.Services["db"]; !ok { t.Fatalf("not registered!") } // Verify check _, checks := fsm.state.NodeChecks("foo") if checks[0].CheckID != "db" { t.Fatalf("not registered!") } } func TestFSM_DeregisterService(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ ID: "db", Service: "db", Tag: "master", Port: 8000, }, } buf, err := structs.Encode(structs.RegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } dereg := structs.DeregisterRequest{ Datacenter: "dc1", Node: "foo", ServiceID: "db", } buf, err = structs.Encode(structs.DeregisterRequestType, dereg) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered if _, found, _ := fsm.state.GetNode("foo"); !found { t.Fatalf("not found!") } // Verify service not registered _, services := fsm.state.NodeServices("foo") if _, ok := services.Services["db"]; ok { t.Fatalf("db registered!") } } func TestFSM_DeregisterCheck(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Check: &structs.HealthCheck{ Node: "foo", CheckID: "mem", Name: "memory util", Status: structs.HealthPassing, }, } buf, err := structs.Encode(structs.RegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } dereg := structs.DeregisterRequest{ Datacenter: "dc1", Node: "foo", CheckID: "mem", } buf, err = structs.Encode(structs.DeregisterRequestType, dereg) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered if _, found, _ := fsm.state.GetNode("foo"); !found { t.Fatalf("not found!") } // Verify check not registered _, checks := fsm.state.NodeChecks("foo") if len(checks) != 0 { t.Fatalf("check registered!") } } func TestFSM_DeregisterNode(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.RegisterRequest{ Datacenter: "dc1", Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ ID: "db", Service: "db", Tag: "master", Port: 8000, }, Check: &structs.HealthCheck{ Node: "foo", CheckID: "db", Name: "db connectivity", Status: structs.HealthPassing, ServiceID: "db", }, } buf, err := structs.Encode(structs.RegisterRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } dereg := structs.DeregisterRequest{ Datacenter: "dc1", Node: "foo", } buf, err = structs.Encode(structs.DeregisterRequestType, dereg) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify we are registered if _, found, _ := fsm.state.GetNode("foo"); found { t.Fatalf("found!") } // Verify service not registered _, services := fsm.state.NodeServices("foo") if services != nil { t.Fatalf("Services: %v", services) } // Verify checks not registered _, checks := fsm.state.NodeChecks("foo") if len(checks) != 0 { t.Fatalf("Services: %v", services) } } func TestFSM_SnapshotRestore(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() // Add some state fsm.state.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) fsm.state.EnsureNode(2, structs.Node{"baz", "127.0.0.2"}) fsm.state.EnsureService(3, "foo", &structs.NodeService{"web", "web", "", 80}) fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", "primary", 5000}) fsm.state.EnsureService(5, "baz", &structs.NodeService{"web", "web", "", 80}) fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", "secondary", 5000}) fsm.state.EnsureCheck(7, &structs.HealthCheck{ Node: "foo", CheckID: "web", Name: "web connectivity", Status: structs.HealthPassing, ServiceID: "web", }) fsm.state.KVSSet(8, &structs.DirEntry{ Key: "/test", Value: []byte("foo"), }) // Snapshot snap, err := fsm.Snapshot() if err != nil { t.Fatalf("err: %v", err) } defer snap.Release() // Persist buf := bytes.NewBuffer(nil) sink := &MockSink{buf, false} if err := snap.Persist(sink); err != nil { t.Fatalf("err: %v", err) } // Try to restore on a new FSM fsm2, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm2.Close() // Do a restore if err := fsm2.Restore(sink); err != nil { t.Fatalf("err: %v", err) } // Verify the contents _, nodes := fsm2.state.Nodes() if len(nodes) != 2 { t.Fatalf("Bad: %v", nodes) } _, fooSrv := fsm2.state.NodeServices("foo") if len(fooSrv.Services) != 2 { t.Fatalf("Bad: %v", fooSrv) } if fooSrv.Services["db"].Tag != "primary" { t.Fatalf("Bad: %v", fooSrv) } if fooSrv.Services["db"].Port != 5000 { t.Fatalf("Bad: %v", fooSrv) } _, checks := fsm2.state.NodeChecks("foo") if len(checks) != 1 { t.Fatalf("Bad: %v", checks) } // Verify key is set _, d, err := fsm.state.KVSGet("/test") if err != nil { t.Fatalf("err: %v", err) } if string(d.Value) != "foo" { t.Fatalf("bad: %v", d) } } func TestFSM_KVSSet(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.KVSRequest{ Datacenter: "dc1", Op: structs.KVSSet, DirEnt: structs.DirEntry{ Key: "/test/path", Flags: 0, Value: []byte("test"), }, } buf, err := structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify key is set _, d, err := fsm.state.KVSGet("/test/path") if err != nil { t.Fatalf("err: %v", err) } if d == nil { t.Fatalf("missing") } } func TestFSM_KVSDelete(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.KVSRequest{ Datacenter: "dc1", Op: structs.KVSSet, DirEnt: structs.DirEntry{ Key: "/test/path", Flags: 0, Value: []byte("test"), }, } buf, err := structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Run the delete req.Op = structs.KVSDelete buf, err = structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify key is not set _, d, err := fsm.state.KVSGet("/test/path") if err != nil { t.Fatalf("err: %v", err) } if d != nil { t.Fatalf("key present") } } func TestFSM_KVSDeleteTree(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.KVSRequest{ Datacenter: "dc1", Op: structs.KVSSet, DirEnt: structs.DirEntry{ Key: "/test/path", Flags: 0, Value: []byte("test"), }, } buf, err := structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Run the delete tree req.Op = structs.KVSDeleteTree req.DirEnt.Key = "/test" buf, err = structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify key is not set _, d, err := fsm.state.KVSGet("/test/path") if err != nil { t.Fatalf("err: %v", err) } if d != nil { t.Fatalf("key present") } } func TestFSM_KVSCheckAndSet(t *testing.T) { fsm, err := NewFSM(os.Stderr) if err != nil { t.Fatalf("err: %v", err) } defer fsm.Close() req := structs.KVSRequest{ Datacenter: "dc1", Op: structs.KVSSet, DirEnt: structs.DirEntry{ Key: "/test/path", Flags: 0, Value: []byte("test"), }, } buf, err := structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp := fsm.Apply(makeLog(buf)) if resp != nil { t.Fatalf("resp: %v", resp) } // Verify key is set _, d, err := fsm.state.KVSGet("/test/path") if err != nil { t.Fatalf("err: %v", err) } if d == nil { t.Fatalf("key missing") } // Run the check-and-set req.Op = structs.KVSCAS req.DirEnt.ModifyIndex = d.ModifyIndex req.DirEnt.Value = []byte("zip") buf, err = structs.Encode(structs.KVSRequestType, req) if err != nil { t.Fatalf("err: %v", err) } resp = fsm.Apply(makeLog(buf)) if resp.(bool) != true { t.Fatalf("resp: %v", resp) } // Verify key is updated _, d, err = fsm.state.KVSGet("/test/path") if err != nil { t.Fatalf("err: %v", err) } if string(d.Value) != "zip" { t.Fatalf("bad: %v", d) } }