Merge pull request #39 from hashicorp/f-more-api
Fill missing API endpoint methods
This commit is contained in:
commit
635b7c74f9
83
api/nodes.go
Normal file
83
api/nodes.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Nodes is used to query node-related API endpoints
|
||||
type Nodes struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Nodes returns a handle on the node endpoints.
|
||||
func (c *Client) Nodes() *Nodes {
|
||||
return &Nodes{client: c}
|
||||
}
|
||||
|
||||
// List is used to list out all of the nodes
|
||||
func (n *Nodes) List(q *QueryOptions) ([]*Node, *QueryMeta, error) {
|
||||
var resp []*Node
|
||||
qm, err := n.client.query("/v1/nodes", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// Info is used to query a specific node by its ID.
|
||||
func (n *Nodes) Info(nodeID string, q *QueryOptions) (*Node, *QueryMeta, error) {
|
||||
var resp Node
|
||||
qm, err := n.client.query("/v1/node/"+nodeID, &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// ToggleDrain is used to toggle drain mode on/off for a given node.
|
||||
func (n *Nodes) ToggleDrain(nodeID string, drain bool, q *WriteOptions) (*WriteMeta, error) {
|
||||
drainArg := strconv.FormatBool(drain)
|
||||
wm, err := n.client.write("/v1/node/"+nodeID+"/drain?enable="+drainArg, nil, 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
|
||||
qm, err := n.client.query("/v1/node/"+nodeID+"/allocations", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// ForceEvaluate is used to force-evaluate an existing node.
|
||||
func (n *Nodes) ForceEvaluate(nodeID string, q *WriteOptions) (string, *WriteMeta, error) {
|
||||
var resp nodeEvalResponse
|
||||
wm, err := n.client.write("/v1/node/"+nodeID+"/evaluate", nil, &resp, q)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.EvalID, wm, nil
|
||||
}
|
||||
|
||||
// Node is used to deserialize a node entry.
|
||||
type Node struct {
|
||||
ID string
|
||||
Datacenter string
|
||||
Name string
|
||||
NodeClass string
|
||||
Drain bool
|
||||
Status string
|
||||
StatusDescription string
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// nodeEvalResponse is used to decode a force-eval.
|
||||
type nodeEvalResponse struct {
|
||||
EvalID string
|
||||
}
|
195
api/nodes_test.go
Normal file
195
api/nodes_test.go
Normal file
|
@ -0,0 +1,195 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
)
|
||||
|
||||
func TestNodes_List(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
var qm *QueryMeta
|
||||
var out []*Node
|
||||
var err error
|
||||
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
out, qm, 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)
|
||||
}
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Check that we got valid QueryMeta.
|
||||
assertQueryMeta(t, qm)
|
||||
}
|
||||
|
||||
func TestNodes_Info(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Retrieving a non-existent node returns error
|
||||
_, _, err := nodes.Info("nope", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// Get the node ID
|
||||
var nodeID, dc 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
|
||||
dc = out[0].Datacenter
|
||||
return true, nil
|
||||
}, func(err error) {
|
||||
t.Fatalf("err: %s", err)
|
||||
})
|
||||
|
||||
// Querying for existing nodes returns properly
|
||||
result, qm, err := nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
assertQueryMeta(t, qm)
|
||||
|
||||
// Check that the result is what we expect
|
||||
if result.ID != nodeID || result.Datacenter != dc {
|
||||
t.Fatalf("expected %s (%s), got: %s (%s)",
|
||||
nodeID, dc,
|
||||
result.ID, result.Datacenter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_ToggleDrain(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
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 drain mode
|
||||
out, _, err := nodes.Info(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out.Drain {
|
||||
t.Fatalf("drain mode should be off")
|
||||
}
|
||||
|
||||
// Toggle it on
|
||||
wm, err := nodes.ToggleDrain(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.Drain {
|
||||
t.Fatalf("drain mode should be on")
|
||||
}
|
||||
|
||||
// Toggle off again
|
||||
wm, err = nodes.ToggleDrain(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.Drain {
|
||||
t.Fatalf("drain mode should be off")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_Allocations(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Looking up by a non-existent node returns nothing. We
|
||||
// don't check the index here because it's possible the node
|
||||
// has already registered, in which case we will get a non-
|
||||
// zero result anyways.
|
||||
allocs, _, err := nodes.Allocations("nope", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if n := len(allocs); n != 0 {
|
||||
t.Fatalf("expected 0 allocs, got: %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodes_ForceEvaluate(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodes := c.Nodes()
|
||||
|
||||
// Force-eval on a non-existent node fails
|
||||
_, _, err := nodes.ForceEvaluate("nope", nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
||||
t.Fatalf("expected not found error, got: %#v", err)
|
||||
}
|
||||
|
||||
// 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)
|
||||
})
|
||||
|
||||
// Try force-eval again. We don't check the WriteMeta because
|
||||
// there are no allocations to process, so we would get an index
|
||||
// of zero. Same goes for the eval ID.
|
||||
_, _, err = nodes.ForceEvaluate(nodeID, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
32
api/status.go
Normal file
32
api/status.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package api
|
||||
|
||||
// Status is used to query the status-related endpoints.
|
||||
type Status struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// Status returns a handle on the status endpoints.
|
||||
func (c *Client) Status() *Status {
|
||||
return &Status{client: c}
|
||||
}
|
||||
|
||||
// Leader is used to query for the current cluster leader.
|
||||
func (s *Status) Leader() (string, error) {
|
||||
var resp string
|
||||
_, err := s.client.query("/v1/status/leader", &resp, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Peers is used to query the addresses of the server peers
|
||||
// in the cluster.
|
||||
func (s *Status) Peers() ([]string, error) {
|
||||
var resp []string
|
||||
_, err := s.client.query("/v1/status/peers", &resp, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
41
api/status_test.go
Normal file
41
api/status_test.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/testutil"
|
||||
)
|
||||
|
||||
func TestStatus_Leader(t *testing.T) {
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
status := c.Status()
|
||||
|
||||
// Query for leader status should return a result
|
||||
out, err := status.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out == "" {
|
||||
t.Fatalf("expected leader, got: %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatus_Leader_NoLeader(t *testing.T) {
|
||||
// Start a server without bootstrap mode. This prevents
|
||||
// the leadership from being acquired.
|
||||
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
|
||||
c.Server.Bootstrap = false
|
||||
})
|
||||
defer s.Stop()
|
||||
status := c.Status()
|
||||
|
||||
// Query for leader status should return nothing.
|
||||
out, err := status.Leader()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if out != "" {
|
||||
t.Fatalf("expected no leader, got: %q", out)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue