diff --git a/consul/catalog_endpoint.go b/consul/catalog_endpoint.go index 065e9ec0bd..75ff84ebbb 100644 --- a/consul/catalog_endpoint.go +++ b/consul/catalog_endpoint.go @@ -63,3 +63,23 @@ func (c *Catalog) ListDatacenters(args *struct{}, reply *[]string) error { *reply = dcs return nil } + +// ListNodes is used to query the nodes in a DC +func (c *Catalog) ListNodes(dc string, reply *rpc.Nodes) error { + if done, err := c.srv.forward("Catalog.ListNodes", dc, dc, reply); done { + return err + } + + // Get the current nodes + state := c.srv.fsm.State() + rawNodes := state.Nodes() + + // Format the response + nodes := rpc.Nodes(make([]rpc.Node, len(rawNodes)/2)) + for i := 0; i < len(rawNodes); i += 2 { + nodes[i] = rpc.Node{rawNodes[i], rawNodes[i+1]} + } + + *reply = nodes + return nil +} diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go index 3be0645523..b6e07fd2be 100644 --- a/consul/catalog_endpoint_test.go +++ b/consul/catalog_endpoint_test.go @@ -183,3 +183,37 @@ func TestCatalogListDatacenters(t *testing.T) { t.Fatalf("bad: %v", out) } } + +func TestCatalogListNodes(t *testing.T) { + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + var out rpc.Nodes + err := client.Call("Catalog.ListNodes", "dc1", &out) + if err == nil || err.Error() != "No cluster leader" { + t.Fatalf("err: %v", err) + } + + // Wait for leader + time.Sleep(100 * time.Millisecond) + + // Just add a node + s1.fsm.State().EnsureNode("foo", "127.0.0.1") + + if err := client.Call("Catalog.ListNodes", "dc1", &out); err != nil { + t.Fatalf("err: %v", err) + } + + if len(out) != 1 { + t.Fatalf("bad: %v", out) + } + if out[0].Node != "foo" { + t.Fatalf("bad: %v", out) + } + if out[0].Address != "127.0.0.1" { + t.Fatalf("bad: %v", out) + } +} diff --git a/consul/fsm.go b/consul/fsm.go index eb3ff1b62b..ddd6335fb0 100644 --- a/consul/fsm.go +++ b/consul/fsm.go @@ -34,6 +34,11 @@ func NewFSM() (*consulFSM, error) { return fsm, nil } +// State is used to return a handle to the current state +func (c *consulFSM) State() *StateStore { + return c.state +} + func (c *consulFSM) Apply(buf []byte) interface{} { switch rpc.MessageType(buf[0]) { case rpc.RegisterRequestType: diff --git a/rpc/structs.go b/rpc/structs.go index fdad669e3d..487e254446 100644 --- a/rpc/structs.go +++ b/rpc/structs.go @@ -39,6 +39,13 @@ type DeregisterRequest struct { ServiceName string } +// Used to return information about a node +type Node struct { + Node string + Address string +} +type Nodes []Node + // Decode is used to decode a MsgPack encoded object func Decode(buf []byte, out interface{}) error { var handle codec.MsgpackHandle