Skip to content

Commit

Permalink
add filter support for /v1/evaluations
Browse files Browse the repository at this point in the history
  • Loading branch information
lgfa29 committed Feb 15, 2022
1 parent da0212f commit 2f5e78c
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 9 deletions.
8 changes: 8 additions & 0 deletions api/evaluations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ func TestEvaluations_List(t *testing.T) {
if len(result) != 1 {
t.Fatalf("expected no evals after last one but got %v", result[0])
}

// Query evaluations using a filter.
results, _, err = e.List(&QueryOptions{
Filter: `TriggeredBy == "job-register"`,
})
if len(result) != 1 {
t.Fatalf("expected 1 eval, got %d", len(result))
}
}

func TestEvaluations_PrefixList(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions command/agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,9 @@ func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Reque
} else if strings.HasSuffix(errMsg, structs.ErrJobRegistrationDisabled.Error()) {
errMsg = structs.ErrJobRegistrationDisabled.Error()
code = 403
} else if strings.HasSuffix(errMsg, structs.ErrIncompatibleFiltering.Error()) {
errMsg = structs.ErrIncompatibleFiltering.Error()
code = 400
}
}

Expand Down
9 changes: 8 additions & 1 deletion command/eval_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ Eval List Options:
-page-token
Where to start pagination.
-filter
Specifies the expression used to filter the queries results prior to
returning the data.
-job
Only show evaluations for this job ID.
Expand All @@ -61,6 +65,7 @@ func (c *EvalListCommand) AutocompleteFlags() complete.Flags {
"-json": complete.PredictNothing,
"-t": complete.PredictAnything,
"-verbose": complete.PredictNothing,
"-filter": complete.PredictAnything,
"-job": complete.PredictAnything,
"-status": complete.PredictAnything,
"-per-page": complete.PredictAnything,
Expand Down Expand Up @@ -88,7 +93,7 @@ func (c *EvalListCommand) Name() string { return "eval list" }
func (c *EvalListCommand) Run(args []string) int {
var monitor, verbose, json bool
var perPage int
var tmpl, pageToken, filterJobID, filterStatus string
var tmpl, pageToken, filter, filterJobID, filterStatus string

flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }
Expand All @@ -98,6 +103,7 @@ func (c *EvalListCommand) Run(args []string) int {
flags.StringVar(&tmpl, "t", "", "")
flags.IntVar(&perPage, "per-page", 0, "")
flags.StringVar(&pageToken, "page-token", "", "")
flags.StringVar(&filter, "filter", "", "")
flags.StringVar(&filterJobID, "job", "", "")
flags.StringVar(&filterStatus, "status", "", "")

Expand All @@ -120,6 +126,7 @@ func (c *EvalListCommand) Run(args []string) int {
}

opts := &api.QueryOptions{
Filter: filter,
PerPage: int32(perPage),
NextToken: pageToken,
Params: map[string]string{},
Expand Down
22 changes: 20 additions & 2 deletions nomad/eval_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nomad

import (
"fmt"
"net/http"
"time"

metrics "github.com/armon/go-metrics"
Expand Down Expand Up @@ -397,6 +398,14 @@ func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListRespon
return structs.ErrPermissionDenied
}

if args.Filter != "" {
// Check for incompatible filtering.
hasLegacyFilter := args.FilterJobID != "" || args.FilterEvalStatus != ""
if hasLegacyFilter {
return structs.ErrIncompatibleFiltering
}
}

// Setup the blocking query
opts := blockingOptions{
queryOpts: &args.QueryOptions,
Expand Down Expand Up @@ -425,13 +434,22 @@ func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListRespon
})

var evals []*structs.Evaluation
paginator := state.NewPaginator(iter, args.QueryOptions,
paginator, err := state.NewPaginator(iter, args.QueryOptions,
func(raw interface{}) {
eval := raw.(*structs.Evaluation)
evals = append(evals, eval)
})
if err != nil {
return structs.NewErrRPCCodedf(
http.StatusBadRequest, "failed to create result paginator: %v", err)
}

nextToken, err := paginator.Page()
if err != nil {
return structs.NewErrRPCCodedf(
http.StatusBadRequest, "failed to read result page: %v", err)
}

nextToken := paginator.Page()
reply.QueryMeta.NextToken = nextToken
reply.Evaluations = evals

Expand Down
61 changes: 59 additions & 2 deletions nomad/eval_endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ func TestEvalEndpoint_List_PaginationFiltering(t *testing.T) {
}

aclToken := mock.CreatePolicyAndToken(t, state, 1100, "test-valid-read",
mock.NamespacePolicy(structs.DefaultNamespace, "read", nil)).
mock.NamespacePolicy("*", "read", nil)).
SecretID

cases := []struct {
Expand All @@ -1060,9 +1060,11 @@ func TestEvalEndpoint_List_PaginationFiltering(t *testing.T) {
nextToken string
filterJobID string
filterStatus string
filter string
pageSize int32
expectedNextToken string
expectedIDs []string
expectedError string
}{
{
name: "test01 size-2 page-1 default NS",
Expand Down Expand Up @@ -1194,6 +1196,52 @@ func TestEvalEndpoint_List_PaginationFiltering(t *testing.T) {
nextToken: "aaaaaa11-3350-4b4b-d185-0e1992ed43e9",
expectedIDs: []string{},
},
{
name: "test14 go-bexpr filter",
filter: `Status == "blocked"`,
nextToken: "",
expectedIDs: []string{"aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"},
},
{
name: "test15 go-bexpr filter with pagination",
filter: `JobID == "example"`,
pageSize: 2,
expectedNextToken: "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9",
expectedIDs: []string{
"aaaa1111-3350-4b4b-d185-0e1992ed43e9",
"aaaaaa22-3350-4b4b-d185-0e1992ed43e9",
},
},
{
name: "test16 go-bexpr filter namespace",
namespace: "non-default",
filter: `ID contains "aaa"`,
expectedIDs: []string{
"aaaaaa33-3350-4b4b-d185-0e1992ed43e9",
},
},
{
name: "test17 go-bexpr wrong namespace",
namespace: "default",
filter: `Namespace == "non-default"`,
expectedIDs: []string{},
},
{
name: "test18 incompatible filtering",
filter: `JobID == "example"`,
filterStatus: "complete",
expectedError: structs.ErrIncompatibleFiltering.Error(),
},
{
name: "test19 go-bexpr invalid expression",
filter: `NotValid`,
expectedError: "failed to read filter expression",
},
{
name: "test20 go-bexpr invalid field",
filter: `InvalidField == "value"`,
expectedError: "error finding value in datum",
},
}

for _, tc := range cases {
Expand All @@ -1208,11 +1256,20 @@ func TestEvalEndpoint_List_PaginationFiltering(t *testing.T) {
Prefix: tc.prefix,
PerPage: tc.pageSize,
NextToken: tc.nextToken,
Filter: tc.filter,
},
}
req.AuthToken = aclToken
var resp structs.EvalListResponse
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Eval.List", req, &resp))
err := msgpackrpc.CallWithCodec(codec, "Eval.List", req, &resp)
if tc.expectedError == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedError)
return
}

gotIDs := []string{}
for _, eval := range resp.Evaluations {
gotIDs = append(gotIDs, eval.ID)
Expand Down
2 changes: 2 additions & 0 deletions nomad/structs/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const (
errUnknownNomadVersion = "Unable to determine Nomad version"
errNodeLacksRpc = "Node does not support RPC; requires 0.8 or later"
errMissingAllocID = "Missing allocation ID"
errIncompatibleFiltering = "Invalid request filtering, filter option can't be used with other filters"

// Prefix based errors that are used to check if the error is of a given
// type. These errors should be created with the associated constructor.
Expand Down Expand Up @@ -53,6 +54,7 @@ var (
ErrUnknownNomadVersion = errors.New(errUnknownNomadVersion)
ErrNodeLacksRpc = errors.New(errNodeLacksRpc)
ErrMissingAllocID = errors.New(errMissingAllocID)
ErrIncompatibleFiltering = errors.New(errIncompatibleFiltering)

ErrUnknownNode = errors.New(ErrUnknownNodePrefix)

Expand Down
13 changes: 9 additions & 4 deletions website/content/api-docs/evaluations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The table below shows this endpoint's support for
- `prefix` `(string: "")`- Specifies a string to filter evaluations based on an
ID prefix. Because the value is decoded to bytes, the prefix must have an
even number of hexadecimal characters (0-9a-f). This is specified as a query
string parameter.
string parameter and and is used before any `filter` is applied.

- `next_token` `(string: "")` - This endpoint supports paging. The
`next_token` parameter accepts a string which is the `ID` field of
Expand All @@ -42,16 +42,21 @@ The table below shows this endpoint's support for
used as the `last_token` of the next request to fetch additional
pages.

- `filter` `(string: "")` - Specifies the expression used to filter the queries
results prior to returning the data. The filter is applied over all resulting
entries so it may be slow to return for large requests. Consider using
pagination or an indexed query parameter if possible.

- `job` `(string: "")` - Filter the list of evaluations to a specific
job ID.

- `status` `(string: "")` - Filter the list of evaluations to a
specific evaluation status (one of `blocked`, `pending`, `complete`,
`failed`, or `canceled`).

- `namespace` `(string: "default")` - Specifies the target
namespace. Specifying `*` will return all evaluations across all
authorized namespaces.
- `namespace` `(string: "default")` - Specifies the target namespace.
Specifying `*` will return all evaluations across all authorized namespaces.
This parameter is used before any `filter` is applied.

- `ascending` `(bool: false)` - Specifies the list of returned evaluations should
be sorted in chronological order (oldest evaluations first). By default evaluations
Expand Down
2 changes: 2 additions & 0 deletions website/content/docs/commands/eval/list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ capability for the requested namespace.
- `-verbose`: Show full information.
- `-per-page`: How many results to show per page.
- `-page-token`: Where to start pagination.
- `-filter`: Specifies the expression used to filter the queries results prior
to returning the data.
- `-job`: Only show evaluations for this job ID.
- `-status`: Only show evaluations with this status.
- `-json`: Output the evaluation in its JSON format.
Expand Down

0 comments on commit 2f5e78c

Please sign in to comment.