2017-07-28 21:48:15 +00:00
|
|
|
package nomad
|
|
|
|
|
|
|
|
import (
|
2017-08-03 14:47:20 +00:00
|
|
|
"fmt"
|
2017-07-28 21:48:15 +00:00
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
"github.com/hashicorp/nomad/nomad/state"
|
|
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
|
|
)
|
|
|
|
|
2017-08-03 20:34:56 +00:00
|
|
|
// truncateLimit is the maximum number of matches that will be returned for a
|
|
|
|
// prefix for a specific context
|
2017-08-04 22:18:49 +00:00
|
|
|
const (
|
|
|
|
truncateLimit = 20
|
|
|
|
)
|
|
|
|
|
|
|
|
// allContexts are the available contexts which searched to find matches for a
|
|
|
|
// given prefix
|
|
|
|
var (
|
|
|
|
allContexts = []string{"allocs", "nodes", "jobs", "evals"}
|
|
|
|
)
|
2017-08-03 20:34:56 +00:00
|
|
|
|
|
|
|
// Resource endpoint is used to lookup matches for a given prefix and context
|
2017-07-28 21:48:15 +00:00
|
|
|
type Resources struct {
|
|
|
|
srv *Server
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:34:56 +00:00
|
|
|
// getMatches extracts matches for an iterator, and returns a list of ids for
|
|
|
|
// these matches.
|
2017-08-07 14:16:24 +00:00
|
|
|
func (r *Resources) getMatches(iter memdb.ResultIterator) ([]string, bool) {
|
2017-08-02 21:55:48 +00:00
|
|
|
var matches []string
|
|
|
|
isTruncated := false
|
|
|
|
|
2017-08-03 20:34:56 +00:00
|
|
|
for i := 0; i < truncateLimit; i++ {
|
2017-08-02 21:55:48 +00:00
|
|
|
raw := iter.Next()
|
|
|
|
if raw == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2017-08-04 15:08:12 +00:00
|
|
|
var id string
|
2017-08-07 14:16:24 +00:00
|
|
|
switch t := raw.(type) {
|
2017-08-04 15:08:12 +00:00
|
|
|
case *structs.Job:
|
|
|
|
id = raw.(*structs.Job).ID
|
|
|
|
case *structs.Evaluation:
|
|
|
|
id = raw.(*structs.Evaluation).ID
|
|
|
|
case *structs.Allocation:
|
|
|
|
id = raw.(*structs.Allocation).ID
|
|
|
|
case *structs.Node:
|
|
|
|
id = raw.(*structs.Node).ID
|
|
|
|
default:
|
2017-08-07 14:16:24 +00:00
|
|
|
r.srv.logger.Printf("[ERR] nomad.resources: unexpected type for resources context; %T \n", t)
|
2017-08-02 21:55:48 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
matches = append(matches, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
if iter.Next() != nil {
|
|
|
|
isTruncated = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return matches, isTruncated
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:34:56 +00:00
|
|
|
// getResourceIter takes a context and returns a memdb iterator specific to
|
|
|
|
// that context
|
2017-08-03 15:59:27 +00:00
|
|
|
func getResourceIter(context, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) {
|
|
|
|
switch context {
|
2017-08-04 14:18:46 +00:00
|
|
|
case "jobs":
|
2017-08-03 15:59:27 +00:00
|
|
|
return state.JobsByIDPrefix(ws, prefix)
|
2017-08-04 14:18:46 +00:00
|
|
|
case "evals":
|
2017-08-03 15:59:27 +00:00
|
|
|
return state.EvalsByIDPrefix(ws, prefix)
|
2017-08-04 14:18:46 +00:00
|
|
|
case "allocs":
|
2017-08-03 15:59:27 +00:00
|
|
|
return state.AllocsByIDPrefix(ws, prefix)
|
2017-08-04 14:18:46 +00:00
|
|
|
case "nodes":
|
2017-08-03 15:59:27 +00:00
|
|
|
return state.NodesByIDPrefix(ws, prefix)
|
|
|
|
default:
|
2017-08-04 22:18:49 +00:00
|
|
|
return nil, fmt.Errorf("context must be one of %v; got %q", allContexts, context)
|
2017-08-03 15:59:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-03 20:34:56 +00:00
|
|
|
// List is used to list the resouces registered in the system that matches the
|
|
|
|
// given prefix. Resources are jobs, evaluations, allocations, and/or nodes.
|
2017-08-04 22:18:49 +00:00
|
|
|
func (r *Resources) List(args *structs.ResourceListRequest,
|
|
|
|
reply *structs.ResourceListResponse) error {
|
2017-08-02 21:55:48 +00:00
|
|
|
reply.Matches = make(map[string][]string)
|
|
|
|
reply.Truncations = make(map[string]bool)
|
2017-07-28 21:48:15 +00:00
|
|
|
|
|
|
|
// Setup the blocking query
|
|
|
|
opts := blockingOptions{
|
|
|
|
queryMeta: &reply.QueryMeta,
|
2017-08-02 15:57:19 +00:00
|
|
|
queryOpts: &structs.QueryOptions{},
|
2017-07-28 21:48:15 +00:00
|
|
|
run: func(ws memdb.WatchSet, state *state.StateStore) error {
|
|
|
|
|
2017-08-03 15:59:27 +00:00
|
|
|
iters := make(map[string]memdb.ResultIterator)
|
|
|
|
|
2017-08-04 22:18:49 +00:00
|
|
|
contexts := allContexts
|
2017-08-03 15:59:27 +00:00
|
|
|
if args.Context != "" {
|
2017-08-04 22:18:49 +00:00
|
|
|
contexts = []string{args.Context}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, e := range contexts {
|
|
|
|
iter, err := getResourceIter(e, args.Prefix, ws, state)
|
2017-08-03 15:59:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-08-04 22:18:49 +00:00
|
|
|
iters[e] = iter
|
2017-07-28 21:48:15 +00:00
|
|
|
}
|
|
|
|
|
2017-08-04 14:18:46 +00:00
|
|
|
// Return matches for the given prefix
|
2017-08-03 15:59:27 +00:00
|
|
|
for k, v := range iters {
|
2017-08-07 14:16:24 +00:00
|
|
|
res, isTrunc := r.getMatches(v)
|
2017-08-03 15:59:27 +00:00
|
|
|
reply.Matches[k] = res
|
|
|
|
reply.Truncations[k] = isTrunc
|
2017-08-02 21:55:48 +00:00
|
|
|
}
|
2017-08-03 14:28:38 +00:00
|
|
|
|
2017-08-04 15:08:12 +00:00
|
|
|
// Set the index for the context. If the context has been specified, it
|
|
|
|
// is the only non-empty match set, and the index is set for it.
|
2017-08-04 20:14:41 +00:00
|
|
|
// If the context was not specified, we set the index to be the max index
|
2017-08-07 14:16:24 +00:00
|
|
|
// from available matches.
|
2017-08-04 20:14:41 +00:00
|
|
|
reply.Index = 0
|
2017-08-04 15:08:12 +00:00
|
|
|
for k, v := range reply.Matches {
|
2017-08-04 20:14:41 +00:00
|
|
|
if len(v) != 0 { // make sure matches exist for this context
|
2017-08-04 15:08:12 +00:00
|
|
|
index, err := state.Index(k)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-08-03 22:39:56 +00:00
|
|
|
}
|
2017-08-04 20:14:41 +00:00
|
|
|
if index > reply.Index {
|
|
|
|
reply.Index = index
|
|
|
|
}
|
2017-08-03 22:39:56 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-03 15:12:14 +00:00
|
|
|
|
2017-08-03 22:39:56 +00:00
|
|
|
r.srv.setQueryMeta(&reply.QueryMeta)
|
2017-07-28 21:48:15 +00:00
|
|
|
return nil
|
|
|
|
}}
|
|
|
|
return r.srv.blockingRPC(&opts)
|
|
|
|
}
|