From fbce8503170578c785e02c1ef4ef8bb0a449ad86 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 28 Apr 2014 14:52:30 -0700 Subject: [PATCH] agent: Adding nodes UI endpoint --- command/agent/http.go | 4 +++ command/agent/ui_endpoint.go | 47 ++++++++++++++++++++++++++++++- command/agent/ui_endpoint_test.go | 40 ++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index fd60d6e8c8..47fb42f853 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -102,7 +102,11 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { // Enable the UI + special endpoints if s.uiDir != "" { + // Static file serving done from /ui/ s.mux.Handle("/ui/", http.StripPrefix("/ui/", http.FileServer(http.Dir(s.uiDir)))) + + // API's are under /internal/ui/ to avoid conflict + s.mux.HandleFunc("/v1/internal/ui/nodes/", s.wrap(s.UINodes)) } } diff --git a/command/agent/ui_endpoint.go b/command/agent/ui_endpoint.go index 6bc163dff9..8f7f875ac8 100644 --- a/command/agent/ui_endpoint.go +++ b/command/agent/ui_endpoint.go @@ -1,3 +1,48 @@ package agent -import () +import ( + "github.com/hashicorp/consul/consul/structs" + "net/http" + "strings" +) + +// UINodes is used to list the nodes in a given datacenter. We return a +// UINodeList which provides overview information for all the nodes +func (s *HTTPServer) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Verify we have some DC, or use the default + dc := strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/nodes/") + if dc == "" { + dc = s.agent.config.Datacenter + } + + // Try to ge ta node dump + var dump structs.NodeDump + if err := s.getNodeDump(resp, dc, &dump); err != nil { + return nil, err + } + + return dump, nil +} + +// getNodeDump is used to get a dump of all node data. We make a best effort by +// reading stale data in the case of an availability outage. +func (s *HTTPServer) getNodeDump(resp http.ResponseWriter, dc string, dump *structs.NodeDump) error { + args := structs.DCSpecificRequest{Datacenter: dc} + var out structs.IndexedNodeDump + defer setMeta(resp, &out.QueryMeta) + +START: + if err := s.agent.RPC("Internal.NodeDump", &args, &out); err != nil { + // Retry the request allowing stale data if no leader. The UI should continue + // to function even during an outage + if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !args.AllowStale { + args.AllowStale = true + goto START + } + return err + } + + // Set the result + *dump = out.Dump + return nil +} diff --git a/command/agent/ui_endpoint_test.go b/command/agent/ui_endpoint_test.go index 48de9209ae..56fe223053 100644 --- a/command/agent/ui_endpoint_test.go +++ b/command/agent/ui_endpoint_test.go @@ -2,6 +2,7 @@ package agent import ( "bytes" + "github.com/hashicorp/consul/consul/structs" "io" "io/ioutil" "net/http" @@ -9,6 +10,7 @@ import ( "os" "path/filepath" "testing" + "time" ) func TestUiIndex(t *testing.T) { @@ -28,13 +30,17 @@ func TestUiIndex(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } + req.URL.Scheme = "http" + req.URL.Host = srv.listener.Addr().String() // Make the request - resp := httptest.NewRecorder() - srv.UiIndex(resp, req) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatalf("err: %v", err) + } // Verify teh response - if resp.Code != 200 { + if resp.StatusCode != 200 { t.Fatalf("bad: %v", resp) } @@ -45,3 +51,31 @@ func TestUiIndex(t *testing.T) { t.Fatalf("bad: %s", out.Bytes()) } } + +func TestUiNodes(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for leader + time.Sleep(100 * time.Millisecond) + + req, err := http.NewRequest("GET", "/v1/internal/ui/nodes/dc1", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + obj, err := srv.UINodes(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + assertIndex(t, resp) + + // Should be 1 node for the server + nodes := obj.(structs.NodeDump) + if len(nodes) != 1 { + t.Fatalf("bad: %v", obj) + } +}