HTTP and API
This commit is contained in:
parent
0fba0101b6
commit
8289cc3c6f
28
api/nodes.go
28
api/nodes.go
|
@ -4,6 +4,8 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
)
|
||||
|
||||
// Nodes is used to query node-related API endpoints
|
||||
|
@ -65,6 +67,32 @@ func (n *Nodes) UpdateDrain(nodeID string, spec *DrainSpec, q *WriteOptions) (*W
|
|||
return wm, nil
|
||||
}
|
||||
|
||||
// NodeUpdateEligibilityRequest is used to update the drain specification for a node.
|
||||
type NodeUpdateEligibilityRequest struct {
|
||||
// NodeID is the node to update the drain specification for.
|
||||
NodeID string
|
||||
Eligibility string
|
||||
}
|
||||
|
||||
// ToggleEligibility is used to update the scheduling eligibility of the node
|
||||
func (n *Nodes) ToggleEligibility(nodeID string, eligible bool, q *WriteOptions) (*WriteMeta, error) {
|
||||
e := structs.NodeSchedulingEligible
|
||||
if !eligible {
|
||||
e = structs.NodeSchedulingIneligible
|
||||
}
|
||||
|
||||
req := &NodeUpdateEligibilityRequest{
|
||||
NodeID: nodeID,
|
||||
Eligibility: e,
|
||||
}
|
||||
|
||||
wm, err := n.client.write("/v1/node/"+nodeID+"/eligibility", req, nil, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Allocations is used to return the allocations associated with a node.
|
||||
func (n *Nodes) Allocations(nodeID string, q *QueryOptions) ([]*Allocation, *QueryMeta, error) {
|
||||
var resp []*Allocation
|
||||
|
|
|
@ -212,6 +212,72 @@ func TestNodes_ToggleDrain(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNodes_ToggleEligibility(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.DevMode = true
|
||||
})
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Wait for node registration and get the ID
|
||||
var nodeID string
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, _, err := nodes.List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n := len(out); n != 1 {
|
||||
return false, fmt.Errorf("expected 1 node, got: %d", n)
|
||||
}
|
||||
nodeID = out[0].ID
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Check for eligibility
|
||||
out, _, err := nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.SchedulingEligibility != structs.NodeSchedulingEligible {
|
||||
t.Fatalf("node should be eligible")
|
||||
}
|
||||
|
||||
// Toggle it off
|
||||
wm, err := nodes.ToggleEligibility(nodeID, false, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Check again
|
||||
out, _, err = nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.SchedulingEligibility != structs.NodeSchedulingIneligible {
|
||||
t.Fatalf("bad eligibility: %v vs %v", out.SchedulingEligibility, structs.NodeSchedulingIneligible)
|
||||
}
|
||||
|
||||
// Toggle on
|
||||
wm, err = nodes.ToggleEligibility(nodeID, true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertWriteMeta(t, wm)
|
||||
|
||||
// Check again
|
||||
out, _, err = nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.SchedulingEligibility != structs.NodeSchedulingEligible {
|
||||
t.Fatalf("bad eligibility: %v vs %v", out.SchedulingEligibility, structs.NodeSchedulingEligible)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_Allocations(t *testing.T) {
|
||||
t.Parallel()
|
||||
c, s := makeClient(t, nil, nil)
|
||||
|
|
|
@ -44,6 +44,9 @@ func (s *HTTPServer) NodeSpecificRequest(resp http.ResponseWriter, req *http.Req
|
|||
case strings.HasSuffix(path, "/drain"):
|
||||
nodeName := strings.TrimSuffix(path, "/drain")
|
||||
return s.nodeToggleDrain(resp, req, nodeName)
|
||||
case strings.HasSuffix(path, "/eligibility"):
|
||||
nodeName := strings.TrimSuffix(path, "/eligibility")
|
||||
return s.nodeToggleEligibility(resp, req, nodeName)
|
||||
case strings.HasSuffix(path, "/purge"):
|
||||
nodeName := strings.TrimSuffix(path, "/purge")
|
||||
return s.nodePurge(resp, req, nodeName)
|
||||
|
@ -149,6 +152,26 @@ func (s *HTTPServer) nodeToggleDrain(resp http.ResponseWriter, req *http.Request
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) nodeToggleEligibility(resp http.ResponseWriter, req *http.Request,
|
||||
nodeID string) (interface{}, error) {
|
||||
if req.Method != "PUT" && req.Method != "POST" {
|
||||
return nil, CodedError(405, ErrInvalidMethod)
|
||||
}
|
||||
|
||||
var drainRequest structs.NodeUpdateEligibilityRequest
|
||||
if err := decodeBody(req, &drainRequest); err != nil {
|
||||
return nil, CodedError(400, err.Error())
|
||||
}
|
||||
s.parseWriteRequest(req, &drainRequest.WriteRequest)
|
||||
|
||||
var out structs.GenericResponse
|
||||
if err := s.agent.RPC("Node.UpdateEligibility", &drainRequest, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setIndex(resp, out.Index)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *HTTPServer) nodeQuery(resp http.ResponseWriter, req *http.Request,
|
||||
nodeID string) (interface{}, error) {
|
||||
if req.Method != "GET" {
|
||||
|
|
|
@ -302,6 +302,57 @@ func TestHTTP_NodeDrain(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestHTTP_NodeEligble(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
// Create the node
|
||||
node := mock.Node()
|
||||
args := structs.NodeRegisterRequest{
|
||||
Node: node,
|
||||
WriteRequest: structs.WriteRequest{Region: "global"},
|
||||
}
|
||||
var resp structs.NodeUpdateResponse
|
||||
require.Nil(s.Agent.RPC("Node.Register", &args, &resp))
|
||||
|
||||
drainReq := api.NodeUpdateEligibilityRequest{
|
||||
NodeID: node.ID,
|
||||
Eligibility: structs.NodeSchedulingIneligible,
|
||||
}
|
||||
|
||||
// Make the HTTP request
|
||||
buf := encodeReq(drainReq)
|
||||
req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
|
||||
require.Nil(err)
|
||||
respW := httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
_, err = s.Server.NodeSpecificRequest(respW, req)
|
||||
require.Nil(err)
|
||||
|
||||
// Check for the index
|
||||
require.NotZero(respW.HeaderMap.Get("X-Nomad-Index"))
|
||||
|
||||
// Check that the node has been updated
|
||||
state := s.Agent.server.State()
|
||||
out, err := state.NodeByID(nil, node.ID)
|
||||
require.Nil(err)
|
||||
require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility)
|
||||
|
||||
// Make the HTTP request to set something invalid
|
||||
drainReq.Eligibility = "foo"
|
||||
buf = encodeReq(drainReq)
|
||||
req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf)
|
||||
require.Nil(err)
|
||||
respW = httptest.NewRecorder()
|
||||
|
||||
// Make the request
|
||||
_, err = s.Server.NodeSpecificRequest(respW, req)
|
||||
require.NotNil(err)
|
||||
require.Contains(err.Error(), "invalid")
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTTP_NodePurge(t *testing.T) {
|
||||
t.Parallel()
|
||||
httpTest(t, nil, func(s *TestAgent) {
|
||||
|
|
|
@ -494,6 +494,12 @@ func (n *Node) UpdateEligibility(args *structs.NodeUpdateEligibilityRequest,
|
|||
return fmt.Errorf("can not set node's scheduling eligibility to eligible while it is draining")
|
||||
}
|
||||
|
||||
switch args.Eligibility {
|
||||
case structs.NodeSchedulingEligible, structs.NodeSchedulingIneligible:
|
||||
default:
|
||||
return fmt.Errorf("invalid scheduling eligibility %q", args.Eligibility)
|
||||
}
|
||||
|
||||
// Commit this update via Raft
|
||||
outErr, index, err := n.srv.raftApply(structs.NodeUpdateEligibilityRequestType, args)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue