diff --git a/api/contexts/contexts.go b/api/contexts/contexts.go index 112a5b73b..17200ef94 100644 --- a/api/contexts/contexts.go +++ b/api/contexts/contexts.go @@ -1,11 +1,12 @@ package contexts +// Context is a type which is searchable via a unique identifier. type Context string const ( - Alloc Context = "allocs" - Eval Context = "evals" - Job Context = "jobs" - Node Context = "nodes" - All Context = "" + Allocs Context = "allocs" + Evals Context = "evals" + Jobs Context = "jobs" + Nodes Context = "nodes" + All Context = "" ) diff --git a/api/search.go b/api/search.go index 83c0495ec..c6cce5cd7 100644 --- a/api/search.go +++ b/api/search.go @@ -18,7 +18,7 @@ func (c *Client) Search() *Search { // context is not specified, matches for all contexts are returned. func (s *Search) PrefixSearch(prefix string, context c.Context) (*structs.SearchResponse, error) { var resp structs.SearchResponse - req := &structs.SearchRequest{Prefix: prefix, Context: string(context)} + req := &structs.SearchRequest{Prefix: prefix, Context: context} _, err := s.client.write("/v1/search", req, &resp, nil) if err != nil { diff --git a/api/search_test.go b/api/search_test.go index 9d822492a..d21364100 100644 --- a/api/search_test.go +++ b/api/search_test.go @@ -25,7 +25,7 @@ func TestSearch_List(t *testing.T) { assert.Nil(err) assert.NotEqual(0, resp.Index) - jobMatches := resp.Matches[contexts.jobs] + jobMatches := resp.Matches[contexts.Jobs] assert.Equal(1, len(jobMatches)) assert.Equal(id, jobMatches[0]) } diff --git a/command/agent/search_endpoint_test.go b/command/agent/search_endpoint_test.go index 6c63d8cf0..e42232c2e 100644 --- a/command/agent/search_endpoint_test.go +++ b/command/agent/search_endpoint_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "testing" + "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" a "github.com/stretchr/testify/assert" @@ -14,7 +15,7 @@ func TestHTTP_SearchWithIllegalMethod(t *testing.T) { assert := a.New(t) t.Parallel() httpTest(t, nil, func(s *TestAgent) { - req, err := http.NewRequest("DELETE", "/v1/resources", nil) + req, err := http.NewRequest("DELETE", "/v1/search", nil) assert.Nil(err) respW := httptest.NewRecorder() @@ -44,7 +45,7 @@ func TestHTTP_Search_POST(t *testing.T) { httpTest(t, nil, func(s *TestAgent) { createJobForTest(testJob, s, t) - data := structs.SearchRequest{Prefix: testJobPrefix, Context: "jobs"} + data := structs.SearchRequest{Prefix: testJobPrefix, Context: contexts.Jobs} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -57,12 +58,12 @@ func TestHTTP_Search_POST(t *testing.T) { assert.Equal(1, len(res.Matches)) - j := res.Matches["jobs"] + j := res.Matches[contexts.Jobs] assert.Equal(1, len(j)) assert.Equal(j[0], testJob) - assert.Equal(res.Truncations["job"], false) + assert.Equal(res.Truncations[contexts.Jobs], false) assert.NotEqual("0", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -76,7 +77,7 @@ func TestHTTP_Search_PUT(t *testing.T) { httpTest(t, nil, func(s *TestAgent) { createJobForTest(testJob, s, t) - data := structs.SearchRequest{Prefix: testJobPrefix, Context: "jobs"} + data := structs.SearchRequest{Prefix: testJobPrefix, Context: contexts.Jobs} req, err := http.NewRequest("PUT", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -89,12 +90,12 @@ func TestHTTP_Search_PUT(t *testing.T) { assert.Equal(1, len(res.Matches)) - j := res.Matches["jobs"] + j := res.Matches[contexts.Jobs] assert.Equal(1, len(j)) assert.Equal(j[0], testJob) - assert.Equal(res.Truncations["job"], false) + assert.Equal(res.Truncations[contexts.Jobs], false) assert.NotEqual("0", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -114,7 +115,7 @@ func TestHTTP_Search_MultipleJobs(t *testing.T) { createJobForTest(testJobB, s, t) createJobForTest(testJobC, s, t) - data := structs.SearchRequest{Prefix: testJobPrefix, Context: "jobs"} + data := structs.SearchRequest{Prefix: testJobPrefix, Context: contexts.Jobs} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -127,14 +128,14 @@ func TestHTTP_Search_MultipleJobs(t *testing.T) { assert.Equal(1, len(res.Matches)) - j := res.Matches["jobs"] + j := res.Matches[contexts.Jobs] assert.Equal(2, len(j)) assert.Contains(j, testJobA) assert.Contains(j, testJobB) assert.NotContains(j, testJobC) - assert.Equal(res.Truncations["job"], false) + assert.Equal(res.Truncations[contexts.Jobs], false) assert.NotEqual("0", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -152,7 +153,7 @@ func TestHTTP_Search_Evaluation(t *testing.T) { assert.Nil(err) prefix := eval1.ID[:len(eval1.ID)-2] - data := structs.SearchRequest{Prefix: prefix, Context: "evals"} + data := structs.SearchRequest{Prefix: prefix, Context: contexts.Evals} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -165,12 +166,12 @@ func TestHTTP_Search_Evaluation(t *testing.T) { assert.Equal(1, len(res.Matches)) - j := res.Matches["evals"] + j := res.Matches[contexts.Evals] assert.Equal(1, len(j)) assert.Contains(j, eval1.ID) assert.NotContains(j, eval2.ID) - assert.Equal(res.Truncations["evals"], false) + assert.Equal(res.Truncations[contexts.Evals], false) assert.Equal("9000", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -186,7 +187,7 @@ func TestHTTP_Search_Allocations(t *testing.T) { assert.Nil(err) prefix := alloc.ID[:len(alloc.ID)-2] - data := structs.SearchRequest{Prefix: prefix, Context: "allocs"} + data := structs.SearchRequest{Prefix: prefix, Context: contexts.Allocs} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -199,11 +200,11 @@ func TestHTTP_Search_Allocations(t *testing.T) { assert.Equal(1, len(res.Matches)) - a := res.Matches["allocs"] + a := res.Matches[contexts.Allocs] assert.Equal(1, len(a)) assert.Contains(a, alloc.ID) - assert.Equal(res.Truncations["allocs"], false) + assert.Equal(res.Truncations[contexts.Allocs], false) assert.Equal("7000", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -219,7 +220,7 @@ func TestHTTP_Search_Nodes(t *testing.T) { assert.Nil(err) prefix := node.ID[:len(node.ID)-2] - data := structs.SearchRequest{Prefix: prefix, Context: "nodes"} + data := structs.SearchRequest{Prefix: prefix, Context: contexts.Nodes} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -232,11 +233,11 @@ func TestHTTP_Search_Nodes(t *testing.T) { assert.Equal(1, len(res.Matches)) - n := res.Matches["nodes"] + n := res.Matches[contexts.Nodes] assert.Equal(1, len(n)) assert.Contains(n, node.ID) - assert.Equal(res.Truncations["nodes"], false) + assert.Equal(res.Truncations[contexts.Nodes], false) assert.Equal("6000", respW.HeaderMap.Get("X-Nomad-Index")) }) } @@ -246,7 +247,7 @@ func TestHTTP_Search_NoJob(t *testing.T) { t.Parallel() httpTest(t, nil, func(s *TestAgent) { - data := structs.SearchRequest{Prefix: "12345", Context: "jobs"} + data := structs.SearchRequest{Prefix: "12345", Context: contexts.Jobs} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -258,13 +259,13 @@ func TestHTTP_Search_NoJob(t *testing.T) { res := resp.(structs.SearchResponse) assert.Equal(1, len(res.Matches)) - assert.Equal(0, len(res.Matches["jobs"])) + assert.Equal(0, len(res.Matches[contexts.Jobs])) assert.Equal("0", respW.HeaderMap.Get("X-Nomad-Index")) }) } -func TestHTTP_Search_NoContext(t *testing.T) { +func TestHTTP_Search_AllContext(t *testing.T) { assert := a.New(t) testJobID := "aaaaaaaa-e8f7-fd38-c855-ab94ceb89706" @@ -279,7 +280,7 @@ func TestHTTP_Search_NoContext(t *testing.T) { err := state.UpsertEvals(8000, []*structs.Evaluation{eval1}) assert.Nil(err) - data := structs.SearchRequest{Prefix: testJobPrefix} + data := structs.SearchRequest{Prefix: testJobPrefix, Context: contexts.All} req, err := http.NewRequest("POST", "/v1/search", encodeReq(data)) assert.Nil(err) @@ -290,8 +291,8 @@ func TestHTTP_Search_NoContext(t *testing.T) { res := resp.(structs.SearchResponse) - matchedJobs := res.Matches["jobs"] - matchedEvals := res.Matches["evals"] + matchedJobs := res.Matches[contexts.Jobs] + matchedEvals := res.Matches[contexts.Evals] assert.Equal(1, len(matchedJobs)) assert.Equal(1, len(matchedEvals)) diff --git a/command/alloc_status.go b/command/alloc_status.go index a1300c76c..1265151ad 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -205,7 +205,7 @@ func (c *AllocStatusCommand) AutocompleteArgs() complete.Predictor { if err != nil { return []string{} } - return resp.Matches[api.contexts.Allocs] + return resp.Matches[contexts.Allocs] }) } diff --git a/nomad/search_endpoint.go b/nomad/search_endpoint.go index b9e5ce7f2..f526f1c38 100644 --- a/nomad/search_endpoint.go +++ b/nomad/search_endpoint.go @@ -4,6 +4,7 @@ import ( "fmt" memdb "github.com/hashicorp/go-memdb" + c "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/structs" ) @@ -14,13 +15,13 @@ const ( truncateLimit = 20 ) -// allContexts are the available contexts which searched to find matches for a -// given prefix +// allContexts are the available contexts which are searched to find matches +// for a given prefix var ( - allContexts = []string{"allocs", "nodes", "jobs", "evals"} + allContexts = []c.Context{c.Allocs, c.Jobs, c.Nodes, c.Evals} ) -// Search endpoint is used to lookup matches for a given prefix and context +// Search endpoint is used to look up matches for a given prefix and context type Search struct { srv *Server } @@ -67,15 +68,15 @@ func (s *Search) getMatches(iter memdb.ResultIterator, prefix string) ([]string, // getResourceIter takes a context and returns a memdb iterator specific to // that context -func getResourceIter(context, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) { +func getResourceIter(context c.Context, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) { switch context { - case "jobs": + case c.Jobs: return state.JobsByIDPrefix(ws, prefix) - case "evals": + case c.Evals: return state.EvalsByIDPrefix(ws, prefix) - case "allocs": + case c.Allocs: return state.AllocsByIDPrefix(ws, prefix) - case "nodes": + case c.Nodes: return state.NodesByIDPrefix(ws, prefix) default: return nil, fmt.Errorf("context must be one of %v; got %q", allContexts, context) @@ -84,8 +85,8 @@ func getResourceIter(context, prefix string, ws memdb.WatchSet, state *state.Sta // If the length of a prefix is odd, return a subset to the last even character // This only applies to UUIDs, jobs are excluded -func roundUUIDDownIfOdd(prefix, context string) string { - if context == "job" { +func roundUUIDDownIfOdd(prefix string, context c.Context) string { + if context == c.Jobs { return prefix } @@ -96,12 +97,12 @@ func roundUUIDDownIfOdd(prefix, context string) string { return prefix[:l-1] } -// List is used to list matches for a given prefix. Search returns jobs, -// evaluations, allocations, and/or nodes. -func (s *Search) List(args *structs.SearchRequest, +// PrefixSearch is used to list matches for a given prefix, and returns +// matching jobs, evaluations, allocations, and/or nodes. +func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.SearchResponse) error { - reply.Matches = make(map[string][]string) - reply.Truncations = make(map[string]bool) + reply.Matches = make(map[c.Context][]string) + reply.Truncations = make(map[c.Context]bool) // Setup the blocking query opts := blockingOptions{ @@ -109,11 +110,11 @@ func (s *Search) List(args *structs.SearchRequest, queryOpts: &structs.QueryOptions{}, run: func(ws memdb.WatchSet, state *state.StateStore) error { - iters := make(map[string]memdb.ResultIterator) + iters := make(map[c.Context]memdb.ResultIterator) contexts := allContexts - if args.Context != "" { - contexts = []string{args.Context} + if args.Context != c.All { + contexts = []c.Context{args.Context} } for _, e := range contexts { @@ -135,7 +136,7 @@ func (s *Search) List(args *structs.SearchRequest, // will be used as the index of the response. Otherwise, the // maximum index from all resources will be used. for _, e := range contexts { - index, err := state.Index(e) + index, err := state.Index(string(e)) if err != nil { return err } diff --git a/nomad/search_endpoint_test.go b/nomad/search_endpoint_test.go index 5a322d5dd..04f23bc8d 100644 --- a/nomad/search_endpoint_test.go +++ b/nomad/search_endpoint_test.go @@ -5,6 +5,7 @@ import ( "testing" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" + c "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/nomad/mock" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/testutil" @@ -25,7 +26,7 @@ func registerAndVerifyJob(s *Server, t *testing.T, prefix string, counter int) s return job.ID } -func TestSearch_List(t *testing.T) { +func TestSearch_PrefixSearch(t *testing.T) { assert := assert.New(t) prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" @@ -42,11 +43,11 @@ func TestSearch_List(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "jobs", + Context: c.Jobs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -56,7 +57,7 @@ func TestSearch_List(t *testing.T) { } // truncate should limit results to 20 -func TestSearch_List_Truncate(t *testing.T) { +func TestSearch_PrefixSearch_Truncate(t *testing.T) { assert := assert.New(t) prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" @@ -75,11 +76,11 @@ func TestSearch_List_Truncate(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "jobs", + Context: c.Jobs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -88,7 +89,7 @@ func TestSearch_List_Truncate(t *testing.T) { assert.Equal(uint64(jobIndex), resp.Index) } -func TestSearch_List_Evals(t *testing.T) { +func TestSearch_PrefixSearch_Evals(t *testing.T) { assert := assert.New(t) t.Parallel() s := testServer(t, func(c *Config) { @@ -106,11 +107,11 @@ func TestSearch_List_Evals(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "evals", + Context: c.Evals, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -121,7 +122,7 @@ func TestSearch_List_Evals(t *testing.T) { assert.Equal(uint64(2000), resp.Index) } -func TestSearch_List_Allocation(t *testing.T) { +func TestSearch_PrefixSearch_Allocation(t *testing.T) { assert := assert.New(t) t.Parallel() s := testServer(t, func(c *Config) { @@ -147,11 +148,11 @@ func TestSearch_List_Allocation(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "allocs", + Context: c.Allocs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -162,7 +163,7 @@ func TestSearch_List_Allocation(t *testing.T) { assert.Equal(uint64(90), resp.Index) } -func TestSearch_List_Node(t *testing.T) { +func TestSearch_PrefixSearch_Node(t *testing.T) { assert := assert.New(t) t.Parallel() s := testServer(t, func(c *Config) { @@ -184,11 +185,11 @@ func TestSearch_List_Node(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "nodes", + Context: c.Nodes, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -199,31 +200,7 @@ func TestSearch_List_Node(t *testing.T) { assert.Equal(uint64(100), resp.Index) } -func TestSearch_List_InvalidContext(t *testing.T) { - assert := assert.New(t) - - t.Parallel() - s := testServer(t, func(c *Config) { - c.NumSchedulers = 0 - }) - - defer s.Shutdown() - codec := rpcClient(t, s) - testutil.WaitForLeader(t, s.RPC) - - req := &structs.SearchRequest{ - Prefix: "anyPrefix", - Context: "invalid", - } - - var resp structs.SearchResponse - err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp) - assert.Equal(err.Error(), "context must be one of [allocs nodes jobs evals]; got \"invalid\"") - - assert.Equal(uint64(0), resp.Index) -} - -func TestSearch_List_NoContext(t *testing.T) { +func TestSearch_PrefixSearch_AllContext(t *testing.T) { assert := assert.New(t) t.Parallel() s := testServer(t, func(c *Config) { @@ -251,11 +228,11 @@ func TestSearch_List_NoContext(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "", + Context: c.All, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -269,7 +246,7 @@ func TestSearch_List_NoContext(t *testing.T) { } // Tests that the top 20 matches are returned when no prefix is set -func TestSearch_List_NoPrefix(t *testing.T) { +func TestSearch_PrefixSearch_NoPrefix(t *testing.T) { assert := assert.New(t) prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" @@ -287,11 +264,11 @@ func TestSearch_List_NoPrefix(t *testing.T) { req := &structs.SearchRequest{ Prefix: "", - Context: "jobs", + Context: c.Jobs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -302,7 +279,7 @@ func TestSearch_List_NoPrefix(t *testing.T) { // Tests that the zero matches are returned when a prefix has no matching // results -func TestSearch_List_NoMatches(t *testing.T) { +func TestSearch_PrefixSearch_NoMatches(t *testing.T) { assert := assert.New(t) prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" @@ -318,11 +295,11 @@ func TestSearch_List_NoMatches(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "jobs", + Context: c.Jobs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } @@ -332,7 +309,7 @@ func TestSearch_List_NoMatches(t *testing.T) { // Prefixes can only be looked up if their length is a power of two. For // prefixes which are an odd length, use the length-1 characters. -func TestSearch_List_RoundDownToEven(t *testing.T) { +func TestSearch_PrefixSearch_RoundDownToEven(t *testing.T) { assert := assert.New(t) id1 := "aaafaaaa-e8f7-fd38-c855-ab94ceb89" id2 := "aaafeaaa-e8f7-fd38-c855-ab94ceb89" @@ -352,11 +329,11 @@ func TestSearch_List_RoundDownToEven(t *testing.T) { req := &structs.SearchRequest{ Prefix: prefix, - Context: "jobs", + Context: c.Jobs, } var resp structs.SearchResponse - if err := msgpackrpc.CallWithCodec(codec, "Search.List", req, &resp); err != nil { + if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { t.Fatalf("err: %v", err) } diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index dcce4a92b..7693e5074 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -24,6 +24,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-version" + "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/helper/args" "github.com/mitchellh/copystructure" @@ -235,11 +236,11 @@ type NodeSpecificRequest struct { // the match list is truncated specific to each type of context. type SearchResponse struct { // Map of context types to ids which match a specified prefix - Matches map[string][]string + Matches map[contexts.Context][]string // Truncations indicates whether the matches for a particular context have // been truncated - Truncations map[string]bool + Truncations map[contexts.Context]bool QueryMeta } @@ -255,7 +256,7 @@ type SearchRequest struct { // Context is the type that can be matched against. A context can be a job, // node, evaluation, allocation, or empty (indicated every context should be // matched) - Context string + Context contexts.Context } // JobRegisterRequest is used for Job.Register endpoint