From acf67a163012b3061ed3f0f489385190f1566063 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 28 Apr 2014 15:09:46 -0700 Subject: [PATCH] agent: Adding node UI endpoint --- command/agent/http.go | 3 +- command/agent/ui_endpoint.go | 64 +++++++++++++++++++++++++------ command/agent/ui_endpoint_test.go | 30 +++++++++++++++ 3 files changed, 84 insertions(+), 13 deletions(-) diff --git a/command/agent/http.go b/command/agent/http.go index 47fb42f853..61c58aede5 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -106,7 +106,8 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { 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)) + s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes)) + s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo)) } } diff --git a/command/agent/ui_endpoint.go b/command/agent/ui_endpoint.go index 8f7f875ac8..92d4e1147f 100644 --- a/command/agent/ui_endpoint.go +++ b/command/agent/ui_endpoint.go @@ -7,36 +7,76 @@ import ( ) // UINodes is used to list the nodes in a given datacenter. We return a -// UINodeList which provides overview information for all the nodes +// NodeDump 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 - } + // Get the datacenter + var dc string + s.parseDC(req, &dc) // Try to ge ta node dump var dump structs.NodeDump - if err := s.getNodeDump(resp, dc, &dump); err != nil { + if err := s.getNodeDump(resp, dc, "", &dump); err != nil { return nil, err } return dump, nil } +// UINodeInfo is used to get info on a single node in a given datacenter. We return a +// NodeInfo which provides overview information for the node +func (s *HTTPServer) UINodeInfo(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + // Get the datacenter + var dc string + s.parseDC(req, &dc) + + // Verify we have some DC, or use the default + node := strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/node/") + if node == "" { + resp.WriteHeader(400) + resp.Write([]byte("Missing node name")) + return nil, nil + } + + // Try to get a node dump + var dump structs.NodeDump + if err := s.getNodeDump(resp, dc, node, &dump); err != nil { + return nil, err + } + + // Return only the first entry + if len(dump) > 0 { + return dump[0], nil + } + return nil, 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} +func (s *HTTPServer) getNodeDump(resp http.ResponseWriter, dc, node string, dump *structs.NodeDump) error { + var args interface{} + var method string + var allowStale *bool + + if node == "" { + raw := structs.DCSpecificRequest{Datacenter: dc} + method = "Internal.NodeDump" + allowStale = &raw.AllowStale + args = &raw + } else { + raw := &structs.NodeSpecificRequest{Datacenter: dc, Node: node} + method = "Internal.NodeInfo" + allowStale = &raw.AllowStale + args = &raw + } var out structs.IndexedNodeDump defer setMeta(resp, &out.QueryMeta) START: - if err := s.agent.RPC("Internal.NodeDump", &args, &out); err != nil { + if err := s.agent.RPC(method, 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 + if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !*allowStale { + *allowStale = true goto START } return err diff --git a/command/agent/ui_endpoint_test.go b/command/agent/ui_endpoint_test.go index 56fe223053..20aaf13ddc 100644 --- a/command/agent/ui_endpoint_test.go +++ b/command/agent/ui_endpoint_test.go @@ -2,6 +2,7 @@ package agent import ( "bytes" + "fmt" "github.com/hashicorp/consul/consul/structs" "io" "io/ioutil" @@ -79,3 +80,32 @@ func TestUiNodes(t *testing.T) { t.Fatalf("bad: %v", obj) } } + +func TestUiNodeInfo(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", + fmt.Sprintf("/v1/internal/ui/node/%s", srv.agent.config.NodeName), nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + obj, err := srv.UINodeInfo(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + assertIndex(t, resp) + + // Should be 1 node for the server + node := obj.(*structs.NodeInfo) + if node.Node != srv.agent.config.NodeName { + t.Fatalf("bad: %v", node) + } +}