open-nomad/nomad/state/paginator.go
Tim Gross 624ecab901
evaluations list pagination and filtering (#11648)
API queries can request pagination using the `NextToken` and `PerPage`
fields of `QueryOptions`, when supported by the underlying API.

Add a `NextToken` field to the `structs.QueryMeta` so that we have a
common field across RPCs to tell the caller where to resume paging
from on their next API call. Include this field on the `api.QueryMeta`
as well so that it's available for future versions of List HTTP APIs
that wrap the response with `QueryMeta` rather than returning a simple
list of structs. In the meantime callers can get the `X-Nomad-NextToken`.

Add pagination to the `Eval.List` RPC by checking for pagination token
and page size in `QueryOptions`. This will allow resuming from the
last ID seen so long as the query parameters and the state store
itself are unchanged between requests.

Add filtering by job ID or evaluation status over the results we get
out of the state store.

Parse the query parameters of the `Eval.List` API into the arguments
expected for filtering in the RPC call.
2021-12-10 13:43:03 -05:00

89 lines
1.9 KiB
Go

package state
import (
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/nomad/structs"
)
// Paginator is an iterator over a memdb.ResultIterator that returns
// only the expected number of pages.
type Paginator struct {
iter memdb.ResultIterator
perPage int32
itemCount int32
seekingToken string
nextToken string
nextTokenFound bool
// appendFunc is the function the caller should use to append raw
// entries to the results set. The object is guaranteed to be
// non-nil.
appendFunc func(interface{})
}
func NewPaginator(iter memdb.ResultIterator, opts structs.QueryOptions, appendFunc func(interface{})) *Paginator {
return &Paginator{
iter: iter,
perPage: opts.PerPage,
seekingToken: opts.NextToken,
nextTokenFound: opts.NextToken == "",
appendFunc: appendFunc,
}
}
// Page populates a page by running the append function
// over all results. Returns the next token
func (p *Paginator) Page() string {
DONE:
for {
raw, andThen := p.next()
switch andThen {
case paginatorInclude:
p.appendFunc(raw)
case paginatorSkip:
continue
case paginatorComplete:
break DONE
}
}
return p.nextToken
}
func (p *Paginator) next() (interface{}, paginatorState) {
raw := p.iter.Next()
if raw == nil {
p.nextToken = ""
return nil, paginatorComplete
}
// have we found the token we're seeking (if any)?
id := raw.(IDGetter).GetID()
p.nextToken = id
if !p.nextTokenFound && id < p.seekingToken {
return nil, paginatorSkip
}
p.nextTokenFound = true
// have we produced enough results for this page?
p.itemCount++
if p.perPage != 0 && p.itemCount > p.perPage {
return raw, paginatorComplete
}
return raw, paginatorInclude
}
// IDGetter must be implemented for the results of any iterator we
// want to paginate
type IDGetter interface {
GetID() string
}
type paginatorState int
const (
paginatorInclude paginatorState = iota
paginatorSkip
paginatorComplete
)