open-nomad/nomad/search_endpoint.go

193 lines
5.2 KiB
Go
Raw Normal View History

package nomad
import (
2017-08-14 13:34:31 +00:00
"strings"
2017-12-20 20:17:28 +00:00
"time"
2017-12-20 20:17:28 +00:00
metrics "github.com/armon/go-metrics"
2017-08-10 19:24:11 +00:00
memdb "github.com/hashicorp/go-memdb"
2017-10-13 21:36:02 +00:00
"github.com/hashicorp/nomad/acl"
"github.com/hashicorp/nomad/nomad/state"
2017-08-14 13:34:31 +00:00
"github.com/hashicorp/nomad/nomad/structs"
)
2017-08-04 22:18:49 +00:00
const (
2017-08-14 13:34:31 +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
truncateLimit = 20
)
var (
2017-09-07 23:56:15 +00:00
// ossContexts are the oss contexts which are searched to find matches
// for a given prefix
ossContexts = []structs.Context{
structs.Allocs,
structs.Jobs,
structs.Nodes,
structs.Evals,
structs.Deployments,
}
2017-08-04 22:18:49 +00:00
)
// Search endpoint is used to look up matches for a given prefix and context
2017-08-10 19:24:11 +00:00
type Search struct {
srv *Server
}
// getMatches extracts matches for an iterator, and returns a list of ids for
// these matches.
2017-08-10 19:24:11 +00:00
func (s *Search) getMatches(iter memdb.ResultIterator, prefix string) ([]string, bool) {
var matches []string
for i := 0; i < truncateLimit; i++ {
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-14 13:34:31 +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
2017-08-18 20:06:25 +00:00
case *structs.Deployment:
id = raw.(*structs.Deployment).ID
2017-08-04 15:08:12 +00:00
default:
2017-09-07 23:56:15 +00:00
matchID, ok := getEnterpriseMatch(raw)
if !ok {
s.srv.logger.Printf("[ERR] nomad.resources: unexpected type for resources context: %T", t)
continue
}
id = matchID
}
2017-08-14 13:34:31 +00:00
if !strings.HasPrefix(id, prefix) {
2017-08-11 13:33:12 +00:00
continue
}
matches = append(matches, id)
}
2017-08-07 17:23:32 +00:00
return matches, iter.Next() != nil
}
// getResourceIter takes a context and returns a memdb iterator specific to
// that context
2017-10-13 21:36:02 +00:00
func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) {
switch context {
2017-08-14 13:34:31 +00:00
case structs.Jobs:
2017-09-07 23:56:15 +00:00
return state.JobsByIDPrefix(ws, namespace, prefix)
2017-08-14 13:34:31 +00:00
case structs.Evals:
2017-09-07 23:56:15 +00:00
return state.EvalsByIDPrefix(ws, namespace, prefix)
2017-08-14 13:34:31 +00:00
case structs.Allocs:
2017-09-07 23:56:15 +00:00
return state.AllocsByIDPrefix(ws, namespace, prefix)
2017-08-14 13:34:31 +00:00
case structs.Nodes:
return state.NodesByIDPrefix(ws, prefix)
2017-08-18 20:06:25 +00:00
case structs.Deployments:
2017-09-07 23:56:15 +00:00
return state.DeploymentsByIDPrefix(ws, namespace, prefix)
default:
2017-10-13 21:36:02 +00:00
return getEnterpriseResourceIter(context, aclObj, namespace, prefix, ws, state)
}
}
// If the length of a prefix is odd, return a subset to the last even character
2017-08-11 13:33:12 +00:00
// This only applies to UUIDs, jobs are excluded
2017-08-14 13:34:31 +00:00
func roundUUIDDownIfOdd(prefix string, context structs.Context) string {
if context == structs.Jobs {
return prefix
}
2017-08-29 16:21:18 +00:00
// We ignore the count of hyphens when calculating if the prefix is even:
// E.g "e3671fa4-21"
numHyphens := strings.Count(prefix, "-")
l := len(prefix) - numHyphens
2017-08-11 13:33:12 +00:00
if l%2 == 0 {
return prefix
2017-08-08 20:29:02 +00:00
}
return prefix[:len(prefix)-1]
2017-08-08 20:29:02 +00:00
}
// PrefixSearch is used to list matches for a given prefix, and returns
// matching jobs, evaluations, allocations, and/or nodes.
2017-10-10 22:04:23 +00:00
func (s *Search) PrefixSearch(args *structs.SearchRequest, reply *structs.SearchResponse) error {
2017-12-20 20:17:28 +00:00
if done, err := s.srv.forward("Search.PrefixSearch", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"nomad", "search", "prefix_search"}, time.Now())
2017-12-20 20:17:28 +00:00
2017-10-12 22:16:33 +00:00
aclObj, err := s.srv.ResolveToken(args.AuthToken)
2017-10-10 22:04:23 +00:00
if err != nil {
return err
}
namespace := args.RequestNamespace()
2017-10-10 22:04:23 +00:00
// Require either node:read or namespace:read-job
if !anySearchPerms(aclObj, namespace, args.Context) {
return structs.ErrPermissionDenied
2017-10-10 22:04:23 +00:00
}
2017-08-14 13:34:31 +00:00
reply.Matches = make(map[structs.Context][]string)
reply.Truncations = make(map[structs.Context]bool)
// Setup the blocking query
opts := blockingOptions{
queryMeta: &reply.QueryMeta,
2017-08-14 13:34:31 +00:00
queryOpts: &structs.QueryOptions{},
run: func(ws memdb.WatchSet, state *state.StateStore) error {
2017-08-14 13:34:31 +00:00
iters := make(map[structs.Context]memdb.ResultIterator)
contexts := searchContexts(aclObj, namespace, args.Context)
2017-08-04 22:18:49 +00:00
2017-08-14 13:34:31 +00:00
for _, ctx := range contexts {
2017-10-13 21:36:02 +00:00
iter, err := getResourceIter(ctx, aclObj, namespace, roundUUIDDownIfOdd(args.Prefix, args.Context), ws, state)
if err != nil {
e := err.Error()
switch {
// Searching other contexts with job names raises an error, which in
// this case we want to ignore.
case strings.Contains(e, "Invalid UUID: encoding/hex"):
case strings.Contains(e, "UUID have 36 characters"):
case strings.Contains(e, "must be even length"):
case strings.Contains(e, "UUID should have maximum of 4"):
default:
return err
}
} else {
iters[ctx] = iter
}
}
// Return matches for the given prefix
for k, v := range iters {
2017-08-10 19:24:11 +00:00
res, isTrunc := s.getMatches(v, args.Prefix)
reply.Matches[k] = res
reply.Truncations[k] = isTrunc
}
2017-08-04 15:08:12 +00:00
// Set the index for the context. If the context has been specified, it
// will be used as the index of the response. Otherwise, the
// maximum index from all resources will be used.
2017-08-14 13:34:31 +00:00
for _, ctx := range contexts {
2017-10-13 21:36:02 +00:00
index, err := state.Index(contextToIndex(ctx))
if err != nil {
return err
}
if index > reply.Index {
reply.Index = index
}
}
2017-08-10 19:24:11 +00:00
s.srv.setQueryMeta(&reply.QueryMeta)
return nil
}}
2017-08-10 19:24:11 +00:00
return s.srv.blockingRPC(&opts)
}