From 4893acf6fae3a1950278d3b07d69f4dc80da65e1 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 6 Sep 2015 14:18:11 -0700 Subject: [PATCH] http/node: adding a few node endpoints --- command/agent/http.go | 3 + command/agent/node_endpoint.go | 107 +++++++++++++++++++ command/agent/node_endpoint_test.go | 157 ++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+) create mode 100644 command/agent/node_endpoint.go create mode 100644 command/agent/node_endpoint_test.go diff --git a/command/agent/http.go b/command/agent/http.go index b7311cf4e..2ed502309 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -66,6 +66,9 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) { s.mux.HandleFunc("/v1/jobs", s.wrap(s.JobsRequest)) s.mux.HandleFunc("/v1/job/", s.wrap(s.JobSpecificRequest)) + s.mux.HandleFunc("/v1/nodes", s.wrap(s.NodesRequest)) + s.mux.HandleFunc("/v1/node/", s.wrap(s.NodeSpecificRequest)) + if enableDebug { s.mux.HandleFunc("/debug/pprof/", pprof.Index) s.mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) diff --git a/command/agent/node_endpoint.go b/command/agent/node_endpoint.go new file mode 100644 index 000000000..f21240117 --- /dev/null +++ b/command/agent/node_endpoint.go @@ -0,0 +1,107 @@ +package agent + +import ( + "net/http" + "strings" + + "github.com/hashicorp/nomad/nomad/structs" +) + +func (s *HTTPServer) NodesRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + if req.Method != "GET" { + return nil, CodedError(405, ErrInvalidMethod) + } + + // TODO NODE LIST + return nil, nil +} + +func (s *HTTPServer) NodeSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + path := strings.TrimPrefix(req.URL.Path, "/v1/node/") + switch { + case strings.HasSuffix(path, "/evaluate"): + nodeName := strings.TrimSuffix(path, "/evaluate") + return s.nodeForceEvaluate(resp, req, nodeName) + case strings.HasSuffix(path, "/allocations"): + nodeName := strings.TrimSuffix(path, "/allocations") + return s.nodeAllocations(resp, req, nodeName) + case strings.HasSuffix(path, "/drain"): + nodeName := strings.TrimSuffix(path, "/drain") + return s.nodeToggleDrain(resp, req, nodeName) + default: + return s.nodeQuery(resp, req, path) + } +} + +func (s *HTTPServer) nodeForceEvaluate(resp http.ResponseWriter, req *http.Request, + nodeID string) (interface{}, error) { + if req.Method != "PUT" && req.Method != "POST" { + return nil, CodedError(405, ErrInvalidMethod) + } + args := structs.NodeEvaluateRequest{ + NodeID: nodeID, + } + s.parseRegion(req, &args.Region) + + var out structs.NodeUpdateResponse + if err := s.agent.RPC("Client.Evaluate", &args, &out); err != nil { + return nil, err + } + setIndex(resp, out.Index) + return out, nil +} + +func (s *HTTPServer) nodeAllocations(resp http.ResponseWriter, req *http.Request, + nodeID string) (interface{}, error) { + if req.Method != "GET" { + return nil, CodedError(405, ErrInvalidMethod) + } + args := structs.NodeSpecificRequest{ + NodeID: nodeID, + } + if s.parse(resp, req, &args.Region, &args.QueryOptions) { + return nil, nil + } + + var out structs.NodeAllocsResponse + if err := s.agent.RPC("Client.GetAllocs", &args, &out); err != nil { + return nil, err + } + + setMeta(resp, &out.QueryMeta) + return out.Allocs, nil +} + +func (s *HTTPServer) nodeToggleDrain(resp http.ResponseWriter, req *http.Request, + jobName string) (interface{}, error) { + if req.Method != "PUT" && req.Method != "POST" { + return nil, CodedError(405, ErrInvalidMethod) + } + + // TODO + return nil, nil +} + +func (s *HTTPServer) nodeQuery(resp http.ResponseWriter, req *http.Request, + nodeID string) (interface{}, error) { + if req.Method != "GET" { + return nil, CodedError(405, ErrInvalidMethod) + } + args := structs.NodeSpecificRequest{ + NodeID: nodeID, + } + if s.parse(resp, req, &args.Region, &args.QueryOptions) { + return nil, nil + } + + var out structs.SingleNodeResponse + if err := s.agent.RPC("Client.GetNode", &args, &out); err != nil { + return nil, err + } + + setMeta(resp, &out.QueryMeta) + if out.Node == nil { + return nil, CodedError(404, "node not found") + } + return out.Node, nil +} diff --git a/command/agent/node_endpoint_test.go b/command/agent/node_endpoint_test.go new file mode 100644 index 000000000..e5d5007c0 --- /dev/null +++ b/command/agent/node_endpoint_test.go @@ -0,0 +1,157 @@ +package agent + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/hashicorp/nomad/nomad/mock" + "github.com/hashicorp/nomad/nomad/structs" +) + +func TestHTTP_NodeForceEval(t *testing.T) { + httpTest(t, nil, func(s *TestServer) { + // Create the job + node := mock.Node() + args := structs.NodeRegisterRequest{ + Node: node, + WriteRequest: structs.WriteRequest{Region: "region1"}, + } + var resp structs.NodeUpdateResponse + if err := s.Agent.RPC("Client.Register", &args, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + // Directly manipulate the state + state := s.Agent.server.State() + alloc1 := mock.Alloc() + alloc1.NodeID = node.ID + err := state.UpdateAllocations(1000, []*structs.Allocation{alloc1}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Make the HTTP request + req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/evaluate", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + respW := httptest.NewRecorder() + + // Make the request + obj, err := s.Server.NodeSpecificRequest(respW, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Check for the index + if respW.HeaderMap.Get("X-Nomad-Index") == "" { + t.Fatalf("missing index") + } + + // Check the response + upd := obj.(structs.NodeUpdateResponse) + if len(upd.EvalIDs) == 0 { + t.Fatalf("bad: %v", upd) + } + }) +} + +func TestHTTP_NodeAllocations(t *testing.T) { + httpTest(t, nil, func(s *TestServer) { + // Create the job + node := mock.Node() + args := structs.NodeRegisterRequest{ + Node: node, + WriteRequest: structs.WriteRequest{Region: "region1"}, + } + var resp structs.NodeUpdateResponse + if err := s.Agent.RPC("Client.Register", &args, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + // Directly manipulate the state + state := s.Agent.server.State() + alloc1 := mock.Alloc() + alloc1.NodeID = node.ID + err := state.UpdateAllocations(1000, []*structs.Allocation{alloc1}) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Make the HTTP request + req, err := http.NewRequest("GET", "/v1/node/"+node.ID+"/allocations", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + respW := httptest.NewRecorder() + + // Make the request + obj, err := s.Server.NodeSpecificRequest(respW, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Check for the index + if respW.HeaderMap.Get("X-Nomad-Index") == "" { + t.Fatalf("missing index") + } + if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { + t.Fatalf("missing known leader") + } + if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { + t.Fatalf("missing last contact") + } + + // Check the node + allocs := obj.([]*structs.Allocation) + if len(allocs) != 1 || allocs[0].ID != alloc1.ID { + t.Fatalf("bad: %#v", allocs) + } + }) +} + +func TestHTTP_NodeQuery(t *testing.T) { + httpTest(t, nil, func(s *TestServer) { + // Create the job + node := mock.Node() + args := structs.NodeRegisterRequest{ + Node: node, + WriteRequest: structs.WriteRequest{Region: "region1"}, + } + var resp structs.NodeUpdateResponse + if err := s.Agent.RPC("Client.Register", &args, &resp); err != nil { + t.Fatalf("err: %v", err) + } + + // Make the HTTP request + req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil) + if err != nil { + t.Fatalf("err: %v", err) + } + respW := httptest.NewRecorder() + + // Make the request + obj, err := s.Server.NodeSpecificRequest(respW, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Check for the index + if respW.HeaderMap.Get("X-Nomad-Index") == "" { + t.Fatalf("missing index") + } + if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { + t.Fatalf("missing known leader") + } + if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { + t.Fatalf("missing last contact") + } + + // Check the node + n := obj.(*structs.Node) + if n.ID != node.ID { + t.Fatalf("bad: %#v", n) + } + }) +}