diff --git a/consul/state_store.go b/consul/state_store.go index f19bea8617..334371c60f 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -241,6 +241,8 @@ func (s *StateStore) initialize() error { "NodeChecks": MDBTables{s.checkTable}, "ServiceChecks": MDBTables{s.checkTable}, "CheckServiceNodes": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, + "NodeInfo": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, + "NodeDump": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, "KVSGet": MDBTables{s.kvsTable}, "KVSList": MDBTables{s.kvsTable}, } @@ -743,6 +745,99 @@ func (s *StateStore) parseCheckServiceNodes(tx *MDBTxn, res []interface{}, err e return nodes } +// NodeInfo is used to generate the full info about a node. +func (s *StateStore) NodeInfo(node string) (uint64, *structs.NodeInfo) { + tables := s.queryTables["NodeInfo"] + tx, err := tables.StartTxn(true) + if err != nil { + panic(fmt.Errorf("Failed to start txn: %v", err)) + } + defer tx.Abort() + + idx, err := tables.LastIndexTxn(tx) + if err != nil { + panic(fmt.Errorf("Failed to get last index: %v", err)) + } + + res, err := s.nodeTable.GetTxn(tx, "id", node) + dump := s.parseNodeInfo(tx, res, err) + var n *structs.NodeInfo + if len(dump) > 0 { + n = dump[0] + } + return idx, n +} + +// NodeDump is used to generate the NodeInfo for all nodes. This is very expensive, +// and should generally be avoided for programatic access. +func (s *StateStore) NodeDump() (uint64, structs.NodeDump) { + tables := s.queryTables["NodeDump"] + tx, err := tables.StartTxn(true) + if err != nil { + panic(fmt.Errorf("Failed to start txn: %v", err)) + } + defer tx.Abort() + + idx, err := tables.LastIndexTxn(tx) + if err != nil { + panic(fmt.Errorf("Failed to get last index: %v", err)) + } + + res, err := s.nodeTable.GetTxn(tx, "id") + return idx, s.parseNodeInfo(tx, res, err) +} + +// parseNodeInfo is used to scan over the results of a node +// iteration and generate a NodeDump +func (s *StateStore) parseNodeInfo(tx *MDBTxn, res []interface{}, err error) structs.NodeDump { + dump := make(structs.NodeDump, 0, len(res)) + if err != nil { + s.logger.Printf("[ERR] consul.state: Failed to get nodes: %v", err) + return dump + } + + for _, r := range res { + // Copy the address and node + node := r.(*structs.Node) + info := &structs.NodeInfo{ + Node: node.Node, + Address: node.Address, + } + + // Get any services of the node + res, err = s.serviceTable.GetTxn(tx, "id", node.Node) + if err != nil { + s.logger.Printf("[ERR] consul.state: Failed to get node services: %v", err) + } + info.Services = make([]*structs.NodeService, 0, len(res)) + for _, r := range res { + service := r.(*structs.ServiceNode) + srv := &structs.NodeService{ + ID: service.ServiceID, + Service: service.ServiceName, + Tags: service.ServiceTags, + Port: service.ServicePort, + } + info.Services = append(info.Services, srv) + } + + // Get any checks of the node + res, err = s.checkTable.GetTxn(tx, "node", node.Node) + if err != nil { + s.logger.Printf("[ERR] consul.state: Failed to get node checks: %v", err) + } + info.Checks = make([]*structs.HealthCheck, 0, len(res)) + for _, r := range res { + chk := r.(*structs.HealthCheck) + info.Checks = append(info.Checks, chk) + } + + // Add the node info + dump = append(dump, info) + } + return dump +} + // KVSSet is used to create or update a KV entry func (s *StateStore) KVSSet(index uint64, d *structs.DirEntry) error { // Start a new txn diff --git a/consul/state_store_test.go b/consul/state_store_test.go index dd46adf6ae..813c39ae27 100644 --- a/consul/state_store_test.go +++ b/consul/state_store_test.go @@ -1068,6 +1068,108 @@ func TestSS_Register_Deregister_Query(t *testing.T) { } } +func TestNodeInfo(t *testing.T) { + store, err := testStateStore() + if err != nil { + t.Fatalf("err: %v", err) + } + defer store.Close() + + if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { + t.Fatalf("err: %v", err) + } + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { + t.Fatalf("err: %v") + } + check := &structs.HealthCheck{ + Node: "foo", + CheckID: "db", + Name: "Can connect", + Status: structs.HealthPassing, + ServiceID: "db1", + } + if err := store.EnsureCheck(3, check); err != nil { + t.Fatalf("err: %v") + } + check = &structs.HealthCheck{ + Node: "foo", + CheckID: SerfCheckID, + Name: SerfCheckName, + Status: structs.HealthPassing, + } + if err := store.EnsureCheck(4, check); err != nil { + t.Fatalf("err: %v") + } + + idx, info := store.NodeInfo("foo") + if idx != 4 { + t.Fatalf("bad: %v", idx) + } + if info == nil { + t.Fatalf("Bad: %v", info) + } + + if info.Node != "foo" { + t.Fatalf("Bad: %v", info) + } + if info.Services[0].ID != "db1" { + t.Fatalf("Bad: %v", info) + } + if len(info.Checks) != 2 { + t.Fatalf("Bad: %v", info) + } + if info.Checks[0].CheckID != "db" { + t.Fatalf("Bad: %v", info) + } + if info.Checks[1].CheckID != SerfCheckID { + t.Fatalf("Bad: %v", info) + } +} + +func TestNodeDump(t *testing.T) { + store, err := testStateStore() + if err != nil { + t.Fatalf("err: %v", err) + } + defer store.Close() + + if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { + t.Fatalf("err: %v", err) + } + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { + t.Fatalf("err: %v") + } + if err := store.EnsureNode(3, structs.Node{"baz", "127.0.0.2"}); err != nil { + t.Fatalf("err: %v", err) + } + if err := store.EnsureService(4, "baz", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { + t.Fatalf("err: %v") + } + + idx, dump := store.NodeDump() + if idx != 4 { + t.Fatalf("bad: %v", idx) + } + if len(dump) != 2 { + t.Fatalf("Bad: %v", dump) + } + + info := dump[0] + if info.Node != "baz" { + t.Fatalf("Bad: %v", info) + } + if info.Services[0].ID != "db1" { + t.Fatalf("Bad: %v", info) + } + info = dump[1] + if info.Node != "foo" { + t.Fatalf("Bad: %v", info) + } + if info.Services[0].ID != "db1" { + t.Fatalf("Bad: %v", info) + } +} + func TestKVSSet_Get(t *testing.T) { store, err := testStateStore() if err != nil { diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 1ebfae6a4e..ce79c690d9 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -220,6 +220,21 @@ type CheckServiceNode struct { } type CheckServiceNodes []CheckServiceNode +// NodeInfo is used to dump all associated information about +// a node. This is currently used for the UI only, as it is +// rather expensive to generate. +type NodeInfo struct { + Node string + Address string + Services []*NodeService + Checks []*HealthCheck +} + +// NodeDump is used to dump all the nodes with all their +// associated data. This is currently used for the UI only, +// as it is rather expensive to generate. +type NodeDump []*NodeInfo + type IndexedNodes struct { Nodes Nodes QueryMeta