Skip to content

Commit

Permalink
api: add ?resources=true to /v1/{allocations,nodes}
Browse files Browse the repository at this point in the history
Fixes #9017

The ?resources=true query parameter includes resources in the object
stub listings. Specifically:

- For `/v1/nodes?resources=true` both the `NodeResources` and
  `ReservedResources` field are included.
- For `/v1/allocations?resources=true` the `AllocatedResources` field is
  included.
  • Loading branch information
schmichael committed Oct 9, 2020
1 parent 99b9a8d commit fcb15e7
Show file tree
Hide file tree
Showing 22 changed files with 227 additions and 43 deletions.
1 change: 1 addition & 0 deletions api/allocations.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ type AllocationListStub struct {
JobType string
JobVersion uint64
TaskGroup string
AllocatedResources *AllocatedResources `json:",omitempty"`
DesiredStatus string
DesiredDescription string
ClientStatus string
Expand Down
80 changes: 54 additions & 26 deletions api/allocations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import (
"reflect"
"sort"
"testing"

"time"

"github.com/hashicorp/nomad/api/internal/testutil"
"github.com/stretchr/testify/require"
)

func TestAllocations_List(t *testing.T) {
t.Parallel()
c, s := makeClient(t, nil, nil)
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.DevMode = true
})
defer s.Stop()
a := c.Allocations()

Expand All @@ -31,33 +33,28 @@ func TestAllocations_List(t *testing.T) {
t.Fatalf("expected 0 allocs, got: %d", n)
}

// TODO: do something that causes an allocation to actually happen
// so we can query for them.
return
// Create a job and attempt to register it
job := testJob()
resp, wm, err := c.Jobs().Register(job, nil)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotEmpty(t, resp.EvalID)
assertWriteMeta(t, wm)

//job := &Job{
//ID: stringToPtr("job1"),
//Name: stringToPtr("Job #1"),
//Type: stringToPtr(JobTypeService),
//}
//eval, _, err := c.Jobs().Register(job, nil)
//if err != nil {
//t.Fatalf("err: %s", err)
//}
// List the allocations again
qo := &QueryOptions{
WaitIndex: wm.LastIndex,
}
allocs, qm, err = a.List(qo)
require.NoError(t, err)
require.NotZero(t, qm.LastIndex)

//// List the allocations again
//allocs, qm, err = a.List(nil)
//if err != nil {
//t.Fatalf("err: %s", err)
//}
//if qm.LastIndex == 0 {
//t.Fatalf("bad index: %d", qm.LastIndex)
//}
// Check that we got the allocation back
require.Len(t, allocs, 1)
require.Equal(t, resp.EvalID, allocs[0].EvalID)

//// Check that we got the allocation back
//if len(allocs) == 0 || allocs[0].EvalID != eval {
//t.Fatalf("bad: %#v", allocs)
//}
// Resources should be unset by default
require.Nil(t, allocs[0].AllocatedResources)
}

func TestAllocations_PrefixList(t *testing.T) {
Expand Down Expand Up @@ -108,6 +105,37 @@ func TestAllocations_PrefixList(t *testing.T) {
//}
}

func TestAllocations_List_Resources(t *testing.T) {
t.Parallel()
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.DevMode = true
})
defer s.Stop()
a := c.Allocations()

// Create a job and register it
job := testJob()
resp, wm, err := c.Jobs().Register(job, nil)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotEmpty(t, resp.EvalID)
assertWriteMeta(t, wm)

// List the allocations
qo := &QueryOptions{
Params: map[string]string{"resources": "true"},
WaitIndex: wm.LastIndex,
}
allocs, qm, err := a.List(qo)
require.NoError(t, err)
require.NotZero(t, qm.LastIndex)

// Check that we got the allocation back with resources
require.Len(t, allocs, 1)
require.Equal(t, resp.EvalID, allocs[0].EvalID)
require.NotNil(t, allocs[0].AllocatedResources)
}

func TestAllocations_CreateIndexSort(t *testing.T) {
t.Parallel()
allocs := []*AllocationListStub{
Expand Down
2 changes: 2 additions & 0 deletions api/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,8 @@ type NodeListStub struct {
Status string
StatusDescription string
Drivers map[string]*DriverInfo
NodeResources *NodeResources `json:",omitempty"`
ReservedResources *NodeReservedResources `json:",omitempty"`
CreateIndex uint64
ModifyIndex uint64
}
Expand Down
39 changes: 39 additions & 0 deletions api/nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,45 @@ func TestNodes_PrefixList(t *testing.T) {
assertQueryMeta(t, qm)
}

// TestNodes_List_Resources asserts that ?resources=true includes allocated and
// reserved resources in the response.
func TestNodes_List_Resources(t *testing.T) {
t.Parallel()
c, s := makeClient(t, nil, func(c *testutil.TestServerConfig) {
c.DevMode = true
})
defer s.Stop()
nodes := c.Nodes()

var out []*NodeListStub
var err error

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)
}
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
})

// By default resources should *not* be included
require.Nil(t, out[0].NodeResources)
require.Nil(t, out[0].ReservedResources)

qo := &QueryOptions{
Params: map[string]string{"resources": "true"},
}
out, _, err = nodes.List(qo)
require.NoError(t, err)
require.NotNil(t, out[0].NodeResources)
require.NotNil(t, out[0].ReservedResources)
}

func TestNodes_Info(t *testing.T) {
t.Parallel()
startTime := time.Now().Unix()
Expand Down
2 changes: 1 addition & 1 deletion api/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func assertWriteMeta(t *testing.T, wm *WriteMeta) {
}

func testJob() *Job {
task := NewTask("task1", "exec").
task := NewTask("task1", "raw_exec").
SetConfig("command", "/bin/sleep").
Require(&Resources{
CPU: intToPtr(100),
Expand Down
9 changes: 9 additions & 0 deletions command/agent/alloc_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ func (s *HTTPServer) AllocsRequest(resp http.ResponseWriter, req *http.Request)
return nil, nil
}

// Parse resources field selection
if resources, err := parseResources(req); err != nil {
return nil, err
} else if resources {
args.Fields = &structs.AllocStubFields{
Resources: true,
}
}

var out structs.AllocListResponse
if err := s.agent.RPC("Alloc.List", &args, &out); err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions command/agent/csi_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,13 +344,13 @@ func structsCSIVolumeToApi(vol *structs.CSIVolume) *api.CSIVolume {

for _, a := range vol.WriteAllocs {
if a != nil {
out.Allocations = append(out.Allocations, structsAllocListStubToApi(a.Stub()))
out.Allocations = append(out.Allocations, structsAllocListStubToApi(a.Stub(nil)))
}
}

for _, a := range vol.ReadAllocs {
if a != nil {
out.Allocations = append(out.Allocations, structsAllocListStubToApi(a.Stub()))
out.Allocations = append(out.Allocations, structsAllocListStubToApi(a.Stub(nil)))
}
}

Expand Down
13 changes: 13 additions & 0 deletions command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,19 @@ func parseNamespace(req *http.Request, n *string) {
}
}

// parseResources is used to parse the ?resources parameter
func parseResources(req *http.Request) (bool, error) {
if resourcesStr := req.URL.Query().Get("resources"); resourcesStr != "" {
resources, err := strconv.ParseBool(resourcesStr)
if err != nil {
return false, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "resources", resourcesStr, err)
}
return resources, nil
}

return false, nil
}

// parseToken is used to parse the X-Nomad-Token param
func (s *HTTPServer) parseToken(req *http.Request, token *string) {
if other := req.Header.Get("X-Nomad-Token"); other != "" {
Expand Down
45 changes: 45 additions & 0 deletions command/agent/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,51 @@ func TestParseToken(t *testing.T) {
}
}

func TestParseResources(t *testing.T) {
t.Parallel()

cases := []struct {
Value string
Resources bool
Err bool // true if an error should be expected
}{
{
Value: "",
Resources: false,
},
{
Value: "true",
Resources: true,
},
{
Value: "false",
Resources: false,
},
{
Value: "1234",
Err: true,
},
}

for i := range cases {
tc := cases[i]
t.Run("Value-"+tc.Value, func(t *testing.T) {
testURL, err := url.Parse("http://localhost/foo?resources=" + tc.Value)
require.NoError(t, err)
req := &http.Request{
URL: testURL,
}

result, err := parseResources(req)
if tc.Err {
require.Error(t, err)
} else {
require.Equal(t, tc.Resources, result)
}
})
}
}

// TestHTTP_VerifyHTTPSClient asserts that a client certificate signed by the
// appropriate CA is required when VerifyHTTPSClient=true.
func TestHTTP_VerifyHTTPSClient(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions command/agent/node_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ func (s *HTTPServer) NodesRequest(resp http.ResponseWriter, req *http.Request) (
return nil, nil
}

// Parse resources field selection
if resources, err := parseResources(req); err != nil {
return nil, err
} else if resources {
args.Fields = &structs.NodeStubFields{
Resources: true,
}
}

var out structs.NodeListResponse
if err := s.agent.RPC("Node.List", &args, &out); err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion nomad/alloc_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes
break
}
alloc := raw.(*structs.Allocation)
allocs = append(allocs, alloc.Stub())
allocs = append(allocs, alloc.Stub(args.Fields))
}
reply.Allocations = allocs

Expand Down
2 changes: 1 addition & 1 deletion nomad/deployment_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ func (d *Deployment) Allocations(args *structs.DeploymentSpecificRequest, reply

stubs := make([]*structs.AllocListStub, 0, len(allocs))
for _, alloc := range allocs {
stubs = append(stubs, alloc.Stub())
stubs = append(stubs, alloc.Stub(nil))
}
reply.Allocations = stubs

Expand Down
2 changes: 1 addition & 1 deletion nomad/deploymentwatcher/deployment_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,7 @@ func (w *deploymentWatcher) getAllocsImpl(ws memdb.WatchSet, state *state.StateS
maxIndex := uint64(0)
stubs := make([]*structs.AllocListStub, 0, len(allocs))
for _, alloc := range allocs {
stubs = append(stubs, alloc.Stub())
stubs = append(stubs, alloc.Stub(nil))

if maxIndex < alloc.ModifyIndex {
maxIndex = alloc.ModifyIndex
Expand Down
2 changes: 1 addition & 1 deletion nomad/eval_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ func (e *Eval) Allocations(args *structs.EvalSpecificRequest,

reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
for _, alloc := range allocs {
reply.Allocations = append(reply.Allocations, alloc.Stub())
reply.Allocations = append(reply.Allocations, alloc.Stub(nil))
}
}

Expand Down
2 changes: 1 addition & 1 deletion nomad/job_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -1471,7 +1471,7 @@ func (j *Job) Allocations(args *structs.JobSpecificRequest,
if len(allocs) > 0 {
reply.Allocations = make([]*structs.AllocListStub, 0, len(allocs))
for _, alloc := range allocs {
reply.Allocations = append(reply.Allocations, alloc.Stub())
reply.Allocations = append(reply.Allocations, alloc.Stub(nil))
}
}

Expand Down
2 changes: 1 addition & 1 deletion nomad/node_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -1295,7 +1295,7 @@ func (n *Node) List(args *structs.NodeListRequest,
break
}
node := raw.(*structs.Node)
nodes = append(nodes, node.Stub())
nodes = append(nodes, node.Stub(args.Fields))
}
reply.Nodes = nodes

Expand Down
2 changes: 1 addition & 1 deletion nomad/state/state_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2422,7 +2422,7 @@ func (s *StateStore) CSIPluginDenormalize(ws memdb.WatchSet, plug *structs.CSIPl
if alloc == nil {
continue
}
plug.Allocations = append(plug.Allocations, alloc.Stub())
plug.Allocations = append(plug.Allocations, alloc.Stub(nil))
}

return plug, nil
Expand Down
Loading

0 comments on commit fcb15e7

Please sign in to comment.