diff --git a/nomad/acl_endpoint.go b/nomad/acl_endpoint.go index 8f3031952..c5df70e29 100644 --- a/nomad/acl_endpoint.go +++ b/nomad/acl_endpoint.go @@ -665,12 +665,12 @@ func (a *ACL) ListTokens(args *structs.ACLTokenListRequest, reply *structs.ACLTo var opts paginator.StructsTokenizerOptions if prefix := args.QueryOptions.Prefix; prefix != "" { - iter, err = state.ACLTokenByAccessorIDPrefix(ws, prefix) + iter, err = state.ACLTokenByAccessorIDPrefix(ws, prefix, sort) opts = paginator.StructsTokenizerOptions{ WithID: true, } } else if args.GlobalOnly { - iter, err = state.ACLTokensByGlobal(ws, true) + iter, err = state.ACLTokensByGlobal(ws, true, sort) opts = paginator.StructsTokenizerOptions{ WithID: true, } diff --git a/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index 65b0ea4b7..f06e1cb24 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -80,7 +80,7 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes return err } else { if prefix := args.QueryOptions.Prefix; prefix != "" { - iter, err = state.AllocsByIDPrefix(ws, namespace, prefix) + iter, err = state.AllocsByIDPrefix(ws, namespace, prefix, sort) opts = paginator.StructsTokenizerOptions{ WithID: true, } diff --git a/nomad/deployment_endpoint.go b/nomad/deployment_endpoint.go index d0217f424..84632f5a5 100644 --- a/nomad/deployment_endpoint.go +++ b/nomad/deployment_endpoint.go @@ -414,7 +414,7 @@ func (d *Deployment) List(args *structs.DeploymentListRequest, reply *structs.De var opts paginator.StructsTokenizerOptions if prefix := args.QueryOptions.Prefix; prefix != "" { - iter, err = store.DeploymentsByIDPrefix(ws, namespace, prefix) + iter, err = store.DeploymentsByIDPrefix(ws, namespace, prefix, sort) opts = paginator.StructsTokenizerOptions{ WithID: true, } diff --git a/nomad/eval_endpoint.go b/nomad/eval_endpoint.go index daec216d2..b3e4d371e 100644 --- a/nomad/eval_endpoint.go +++ b/nomad/eval_endpoint.go @@ -419,7 +419,7 @@ func (e *Eval) List(args *structs.EvalListRequest, reply *structs.EvalListRespon var opts paginator.StructsTokenizerOptions if prefix := args.QueryOptions.Prefix; prefix != "" { - iter, err = store.EvalsByIDPrefix(ws, namespace, prefix) + iter, err = store.EvalsByIDPrefix(ws, namespace, prefix, sort) opts = paginator.StructsTokenizerOptions{ WithID: true, } diff --git a/nomad/leader.go b/nomad/leader.go index 7facb66c7..5a50995a4 100644 --- a/nomad/leader.go +++ b/nomad/leader.go @@ -1524,13 +1524,13 @@ ERR_WAIT: // diffACLTokens is used to perform a two-way diff between the local // tokens and the remote tokens to determine which tokens need to // be deleted or updated. -func diffACLTokens(state *state.StateStore, minIndex uint64, remoteList []*structs.ACLTokenListStub) (delete []string, update []string) { +func diffACLTokens(store *state.StateStore, minIndex uint64, remoteList []*structs.ACLTokenListStub) (delete []string, update []string) { // Construct a set of the local and remote policies local := make(map[string][]byte) remote := make(map[string]struct{}) // Add all the local global tokens - iter, err := state.ACLTokensByGlobal(nil, true) + iter, err := store.ACLTokensByGlobal(nil, true, state.SortDefault) if err != nil { panic("failed to iterate local tokens") } diff --git a/nomad/node_endpoint_test.go b/nomad/node_endpoint_test.go index 1070892b0..c91dafc1a 100644 --- a/nomad/node_endpoint_test.go +++ b/nomad/node_endpoint_test.go @@ -2063,11 +2063,11 @@ func TestClientEndpoint_GetClientAllocs_Blocking(t *testing.T) { alloc := mock.Alloc() alloc.NodeID = node.ID alloc.ModifyTime = now - state := s1.fsm.State() - state.UpsertJobSummary(99, mock.JobSummary(alloc.JobID)) + store := s1.fsm.State() + store.UpsertJobSummary(99, mock.JobSummary(alloc.JobID)) start := time.Now() time.AfterFunc(100*time.Millisecond, func() { - err := state.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc}) + err := store.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc}) if err != nil { t.Fatalf("err: %v", err) } @@ -2101,7 +2101,7 @@ func TestClientEndpoint_GetClientAllocs_Blocking(t *testing.T) { t.Fatalf("bad: %#v", resp2.Allocs) } - iter, err := state.AllocsByIDPrefix(nil, structs.DefaultNamespace, alloc.ID) + iter, err := store.AllocsByIDPrefix(nil, structs.DefaultNamespace, alloc.ID, state.SortDefault) if err != nil { t.Fatalf("err: %v", err) } @@ -2133,8 +2133,8 @@ func TestClientEndpoint_GetClientAllocs_Blocking(t *testing.T) { allocUpdate.NodeID = alloc.NodeID allocUpdate.ID = alloc.ID allocUpdate.ClientStatus = structs.AllocClientStatusRunning - state.UpsertJobSummary(199, mock.JobSummary(allocUpdate.JobID)) - err := state.UpsertAllocs(structs.MsgTypeTestSetup, 200, []*structs.Allocation{allocUpdate}) + store.UpsertJobSummary(199, mock.JobSummary(allocUpdate.JobID)) + err := store.UpsertAllocs(structs.MsgTypeTestSetup, 200, []*structs.Allocation{allocUpdate}) if err != nil { t.Fatalf("err: %v", err) } diff --git a/nomad/search_endpoint.go b/nomad/search_endpoint.go index 73cfb47e2..90bb3e420 100644 --- a/nomad/search_endpoint.go +++ b/nomad/search_endpoint.go @@ -355,26 +355,26 @@ func sortSet(matches []fuzzyMatch) { // getResourceIter takes a context and returns a memdb iterator specific to // that context -func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix string, ws memdb.WatchSet, state *state.StateStore) (memdb.ResultIterator, error) { +func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix string, ws memdb.WatchSet, store *state.StateStore) (memdb.ResultIterator, error) { switch context { case structs.Jobs: - return state.JobsByIDPrefix(ws, namespace, prefix) + return store.JobsByIDPrefix(ws, namespace, prefix) case structs.Evals: - return state.EvalsByIDPrefix(ws, namespace, prefix) + return store.EvalsByIDPrefix(ws, namespace, prefix, state.SortDefault) case structs.Allocs: - return state.AllocsByIDPrefix(ws, namespace, prefix) + return store.AllocsByIDPrefix(ws, namespace, prefix, state.SortDefault) case structs.Nodes: - return state.NodesByIDPrefix(ws, prefix) + return store.NodesByIDPrefix(ws, prefix) case structs.Deployments: - return state.DeploymentsByIDPrefix(ws, namespace, prefix) + return store.DeploymentsByIDPrefix(ws, namespace, prefix, state.SortDefault) case structs.Plugins: - return state.CSIPluginsByIDPrefix(ws, prefix) + return store.CSIPluginsByIDPrefix(ws, prefix) case structs.ScalingPolicies: - return state.ScalingPoliciesByIDPrefix(ws, namespace, prefix) + return store.ScalingPoliciesByIDPrefix(ws, namespace, prefix) case structs.Volumes: - return state.CSIVolumesByIDPrefix(ws, namespace, prefix) + return store.CSIVolumesByIDPrefix(ws, namespace, prefix) case structs.Namespaces: - iter, err := state.NamespacesByNamePrefix(ws, prefix) + iter, err := store.NamespacesByNamePrefix(ws, prefix) if err != nil { return nil, err } @@ -383,7 +383,7 @@ func getResourceIter(context structs.Context, aclObj *acl.ACL, namespace, prefix } return memdb.NewFilterIterator(iter, nsCapFilter(aclObj)), nil default: - return getEnterpriseResourceIter(context, aclObj, namespace, prefix, ws, state) + return getEnterpriseResourceIter(context, aclObj, namespace, prefix, ws, store) } } diff --git a/nomad/state/state_store.go b/nomad/state/state_store.go index 60bf36fc1..42f2ebf1c 100644 --- a/nomad/state/state_store.go +++ b/nomad/state/state_store.go @@ -617,11 +617,19 @@ func (s *StateStore) DeploymentsByNamespaceOrdered(ws memdb.WatchSet, namespace return it, nil } -func (s *StateStore) DeploymentsByIDPrefix(ws memdb.WatchSet, namespace, deploymentID string) (memdb.ResultIterator, error) { +func (s *StateStore) DeploymentsByIDPrefix(ws memdb.WatchSet, namespace, deploymentID string, sort SortOption) (memdb.ResultIterator, error) { txn := s.db.ReadTxn() + var iter memdb.ResultIterator + var err error + // Walk the entire deployments table - iter, err := txn.Get("deployment", "id_prefix", deploymentID) + switch sort { + case SortReverse: + iter, err = txn.GetReverse("deployment", "id_prefix", deploymentID) + default: + iter, err = txn.Get("deployment", "id_prefix", deploymentID) + } if err != nil { return nil, err } @@ -3171,11 +3179,19 @@ func (s *StateStore) EvalByID(ws memdb.WatchSet, id string) (*structs.Evaluation // EvalsByIDPrefix is used to lookup evaluations by prefix in a particular // namespace -func (s *StateStore) EvalsByIDPrefix(ws memdb.WatchSet, namespace, id string) (memdb.ResultIterator, error) { +func (s *StateStore) EvalsByIDPrefix(ws memdb.WatchSet, namespace, id string, sort SortOption) (memdb.ResultIterator, error) { txn := s.db.ReadTxn() + var iter memdb.ResultIterator + var err error + // Get an iterator over all evals by the id prefix - iter, err := txn.Get("evals", "id_prefix", id) + switch sort { + case SortReverse: + iter, err = txn.GetReverse("evals", "id_prefix", id) + default: + iter, err = txn.Get("evals", "id_prefix", id) + } if err != nil { return nil, fmt.Errorf("eval lookup failed: %v", err) } @@ -3631,10 +3647,18 @@ func (s *StateStore) allocByIDImpl(txn Txn, ws memdb.WatchSet, id string) (*stru } // AllocsByIDPrefix is used to lookup allocs by prefix -func (s *StateStore) AllocsByIDPrefix(ws memdb.WatchSet, namespace, id string) (memdb.ResultIterator, error) { +func (s *StateStore) AllocsByIDPrefix(ws memdb.WatchSet, namespace, id string, sort SortOption) (memdb.ResultIterator, error) { txn := s.db.ReadTxn() - iter, err := txn.Get("allocs", "id_prefix", id) + var iter memdb.ResultIterator + var err error + + switch sort { + case SortReverse: + iter, err = txn.GetReverse("allocs", "id_prefix", id) + default: + iter, err = txn.Get("allocs", "id_prefix", id) + } if err != nil { return nil, fmt.Errorf("alloc lookup failed: %v", err) } @@ -5535,13 +5559,22 @@ func (s *StateStore) ACLTokenBySecretID(ws memdb.WatchSet, secretID string) (*st } // ACLTokenByAccessorIDPrefix is used to lookup tokens by prefix -func (s *StateStore) ACLTokenByAccessorIDPrefix(ws memdb.WatchSet, prefix string) (memdb.ResultIterator, error) { +func (s *StateStore) ACLTokenByAccessorIDPrefix(ws memdb.WatchSet, prefix string, sort SortOption) (memdb.ResultIterator, error) { txn := s.db.ReadTxn() - iter, err := txn.Get("acl_token", "id_prefix", prefix) + var iter memdb.ResultIterator + var err error + + switch sort { + case SortReverse: + iter, err = txn.GetReverse("acl_token", "id_prefix", prefix) + default: + iter, err = txn.Get("acl_token", "id_prefix", prefix) + } if err != nil { return nil, fmt.Errorf("acl token lookup failed: %v", err) } + ws.Add(iter.WatchCh()) return iter, nil } @@ -5568,14 +5601,23 @@ func (s *StateStore) ACLTokens(ws memdb.WatchSet, sort SortOption) (memdb.Result } // ACLTokensByGlobal returns an iterator over all the tokens filtered by global value -func (s *StateStore) ACLTokensByGlobal(ws memdb.WatchSet, globalVal bool) (memdb.ResultIterator, error) { +func (s *StateStore) ACLTokensByGlobal(ws memdb.WatchSet, globalVal bool, sort SortOption) (memdb.ResultIterator, error) { txn := s.db.ReadTxn() + var iter memdb.ResultIterator + var err error + // Walk the entire table - iter, err := txn.Get("acl_token", "global", globalVal) + switch sort { + case SortReverse: + iter, err = txn.GetReverse("acl_token", "global", globalVal) + default: + iter, err = txn.Get("acl_token", "global", globalVal) + } if err != nil { return nil, err } + ws.Add(iter.WatchCh()) return iter, nil } diff --git a/nomad/state/state_store_test.go b/nomad/state/state_store_test.go index e9b58b7b8..441344a28 100644 --- a/nomad/state/state_store_test.go +++ b/nomad/state/state_store_test.go @@ -694,16 +694,7 @@ func TestStateStore_DeploymentsByIDPrefix(t *testing.T) { deploy.ID = "11111111-662e-d0ab-d1c9-3e434af7bdb4" err := state.UpsertDeployment(1000, deploy) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Create a watchset so we can test that getters don't cause it to fire - ws := memdb.NewWatchSet() - iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, deploy.ID) - if err != nil { - t.Fatalf("err: %v", err) - } + require.NoError(t, err) gatherDeploys := func(iter memdb.ResultIterator) []*structs.Deployment { var deploys []*structs.Deployment @@ -718,60 +709,67 @@ func TestStateStore_DeploymentsByIDPrefix(t *testing.T) { return deploys } - deploys := gatherDeploys(iter) - if len(deploys) != 1 { - t.Fatalf("err: %v", err) - } + t.Run("first deployment", func(t *testing.T) { + // Create a watchset so we can test that getters don't cause it to fire + ws := memdb.NewWatchSet() + iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, deploy.ID, SortDefault) + require.NoError(t, err) - if watchFired(ws) { - t.Fatalf("bad") - } + deploys := gatherDeploys(iter) + require.Len(t, deploys, 1) + require.False(t, watchFired(ws)) + }) - iter, err = state.DeploymentsByIDPrefix(ws, deploy.Namespace, "11") - if err != nil { - t.Fatalf("err: %v", err) - } + t.Run("using prefix", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, "11", SortDefault) + require.NoError(t, err) - deploys = gatherDeploys(iter) - if len(deploys) != 1 { - t.Fatalf("err: %v", err) - } + deploys := gatherDeploys(iter) + require.Len(t, deploys, 1) + require.False(t, watchFired(ws)) + }) deploy = mock.Deployment() deploy.ID = "11222222-662e-d0ab-d1c9-3e434af7bdb4" err = state.UpsertDeployment(1001, deploy) - if err != nil { - t.Fatalf("err: %v", err) - } + require.NoError(t, err) - if !watchFired(ws) { - t.Fatalf("bad") - } + t.Run("more than one", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, "11", SortDefault) + require.NoError(t, err) - ws = memdb.NewWatchSet() - iter, err = state.DeploymentsByIDPrefix(ws, deploy.Namespace, "11") - if err != nil { - t.Fatalf("err: %v", err) - } + deploys := gatherDeploys(iter) + require.Len(t, deploys, 2) + }) - deploys = gatherDeploys(iter) - if len(deploys) != 2 { - t.Fatalf("err: %v", err) - } + t.Run("filter to one", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, "1111", SortDefault) + require.NoError(t, err) - iter, err = state.DeploymentsByIDPrefix(ws, deploy.Namespace, "1111") - if err != nil { - t.Fatalf("err: %v", err) - } + deploys := gatherDeploys(iter) + require.Len(t, deploys, 1) + require.False(t, watchFired(ws)) + }) - deploys = gatherDeploys(iter) - if len(deploys) != 1 { - t.Fatalf("err: %v", err) - } + t.Run("reverse order", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.DeploymentsByIDPrefix(ws, deploy.Namespace, "11", SortReverse) + require.NoError(t, err) - if watchFired(ws) { - t.Fatalf("bad") - } + got := []string{} + for _, d := range gatherDeploys(iter) { + got = append(got, d.ID) + } + expected := []string{ + "11222222-662e-d0ab-d1c9-3e434af7bdb4", + "11111111-662e-d0ab-d1c9-3e434af7bdb4", + } + require.Equal(t, expected, got) + require.False(t, watchFired(ws)) + }) } func TestStateStore_UpsertNode_Node(t *testing.T) { @@ -3874,12 +3872,6 @@ func TestStateStore_EvalsByIDPrefix(t *testing.T) { t.Fatalf("err: %v", err) } - ws := memdb.NewWatchSet() - iter, err := state.EvalsByIDPrefix(ws, structs.DefaultNamespace, "aaaa") - if err != nil { - t.Fatalf("err: %v", err) - } - gatherEvals := func(iter memdb.ResultIterator) []*structs.Evaluation { var evals []*structs.Evaluation for { @@ -3892,32 +3884,57 @@ func TestStateStore_EvalsByIDPrefix(t *testing.T) { return evals } - out := gatherEvals(iter) - if len(out) != 5 { - t.Fatalf("bad: expected five evaluations, got: %#v", out) - } + t.Run("list by prefix", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.EvalsByIDPrefix(ws, structs.DefaultNamespace, "aaaa", SortDefault) + require.NoError(t, err) - sort.Sort(EvalIDSort(evals)) - - for index, eval := range out { - if ids[index] != eval.ID { - t.Fatalf("bad: got unexpected id: %s", eval.ID) + got := []string{} + for _, e := range gatherEvals(iter) { + got = append(got, e.ID) } - } - iter, err = state.EvalsByIDPrefix(ws, structs.DefaultNamespace, "b-a7bfb") - if err != nil { - t.Fatalf("err: %v", err) - } + expected := []string{ + "aaaaaaaa-7bfb-395d-eb95-0685af2176b2", + "aaaaaaab-7bfb-395d-eb95-0685af2176b2", + "aaaaaabb-7bfb-395d-eb95-0685af2176b2", + "aaaaabbb-7bfb-395d-eb95-0685af2176b2", + "aaaabbbb-7bfb-395d-eb95-0685af2176b2", + } + require.Len(t, got, 5, "expected five evaluations") + require.Equal(t, expected, got) // Must be in this order. + }) - out = gatherEvals(iter) - if len(out) != 0 { - t.Fatalf("bad: unexpected zero evaluations, got: %#v", out) - } + t.Run("invalid prefix", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.EvalsByIDPrefix(ws, structs.DefaultNamespace, "b-a7bfb", SortDefault) + require.NoError(t, err) - if watchFired(ws) { - t.Fatalf("bad") - } + out := gatherEvals(iter) + require.Len(t, out, 0, "expected zero evaluations") + require.False(t, watchFired(ws)) + }) + + t.Run("reverse order", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.EvalsByIDPrefix(ws, structs.DefaultNamespace, "aaaa", SortReverse) + require.NoError(t, err) + + got := []string{} + for _, e := range gatherEvals(iter) { + got = append(got, e.ID) + } + + expected := []string{ + "aaaabbbb-7bfb-395d-eb95-0685af2176b2", + "aaaaabbb-7bfb-395d-eb95-0685af2176b2", + "aaaaaabb-7bfb-395d-eb95-0685af2176b2", + "aaaaaaab-7bfb-395d-eb95-0685af2176b2", + "aaaaaaaa-7bfb-395d-eb95-0685af2176b2", + } + require.Len(t, got, 5, "expected five evaluations") + require.Equal(t, expected, got) // Must be in this order. + }) } func TestStateStore_UpdateAllocsFromClient(t *testing.T) { @@ -5362,15 +5379,7 @@ func TestStateStore_AllocsByIDPrefix(t *testing.T) { } err := state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, allocs) - if err != nil { - t.Fatalf("err: %v", err) - } - - ws := memdb.NewWatchSet() - iter, err := state.AllocsByIDPrefix(ws, structs.DefaultNamespace, "aaaa") - if err != nil { - t.Fatalf("err: %v", err) - } + require.NoError(t, err) gatherAllocs := func(iter memdb.ResultIterator) []*structs.Allocation { var allocs []*structs.Allocation @@ -5384,32 +5393,61 @@ func TestStateStore_AllocsByIDPrefix(t *testing.T) { return allocs } - out := gatherAllocs(iter) - if len(out) != 5 { - t.Fatalf("bad: expected five allocations, got: %#v", out) - } + t.Run("allocs by prefix", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.AllocsByIDPrefix(ws, structs.DefaultNamespace, "aaaa", SortDefault) + require.NoError(t, err) - sort.Sort(AllocIDSort(allocs)) + out := gatherAllocs(iter) + require.Len(t, out, 5, "expected five allocations") - for index, alloc := range out { - if ids[index] != alloc.ID { - t.Fatalf("bad: got unexpected id: %s", alloc.ID) + got := []string{} + for _, a := range out { + got = append(got, a.ID) } - } + expected := []string{ + "aaaaaaaa-7bfb-395d-eb95-0685af2176b2", + "aaaaaaab-7bfb-395d-eb95-0685af2176b2", + "aaaaaabb-7bfb-395d-eb95-0685af2176b2", + "aaaaabbb-7bfb-395d-eb95-0685af2176b2", + "aaaabbbb-7bfb-395d-eb95-0685af2176b2", + } + require.Equal(t, expected, got) + require.False(t, watchFired(ws)) + }) - iter, err = state.AllocsByIDPrefix(ws, structs.DefaultNamespace, "b-a7bfb") - if err != nil { - t.Fatalf("err: %v", err) - } + t.Run("invalid prefix", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.AllocsByIDPrefix(ws, structs.DefaultNamespace, "b-a7bfb", SortDefault) + require.NoError(t, err) - out = gatherAllocs(iter) - if len(out) != 0 { - t.Fatalf("bad: unexpected zero allocations, got: %#v", out) - } + out := gatherAllocs(iter) + require.Len(t, out, 0) + require.False(t, watchFired(ws)) + }) - if watchFired(ws) { - t.Fatalf("bad") - } + t.Run("reverse", func(t *testing.T) { + ws := memdb.NewWatchSet() + iter, err := state.AllocsByIDPrefix(ws, structs.DefaultNamespace, "aaaa", SortReverse) + require.NoError(t, err) + + out := gatherAllocs(iter) + require.Len(t, out, 5, "expected five allocations") + + got := []string{} + for _, a := range out { + got = append(got, a.ID) + } + expected := []string{ + "aaaabbbb-7bfb-395d-eb95-0685af2176b2", + "aaaaabbb-7bfb-395d-eb95-0685af2176b2", + "aaaaaabb-7bfb-395d-eb95-0685af2176b2", + "aaaaaaab-7bfb-395d-eb95-0685af2176b2", + "aaaaaaaa-7bfb-395d-eb95-0685af2176b2", + } + require.Equal(t, expected, got) + require.False(t, watchFired(ws)) + }) } func TestStateStore_Allocs(t *testing.T) { @@ -7717,36 +7755,54 @@ func TestStateStore_ACLTokenByAccessorIDPrefix(t *testing.T) { for _, prefix := range prefixes { tk := mock.ACLToken() tk.AccessorID = prefix + tk.AccessorID[4:] - if err := state.UpsertACLTokens(structs.MsgTypeTestSetup, baseIndex, []*structs.ACLToken{tk}); err != nil { - t.Fatalf("err: %v", err) - } + err := state.UpsertACLTokens(structs.MsgTypeTestSetup, baseIndex, []*structs.ACLToken{tk}) + require.NoError(t, err) baseIndex++ } - // Scan by prefix - iter, err := state.ACLTokenByAccessorIDPrefix(nil, "aa") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Ensure we see both tokens - count := 0 - out := []string{} - for { - raw := iter.Next() - if raw == nil { - break + gatherTokens := func(iter memdb.ResultIterator) []*structs.ACLToken { + var tokens []*structs.ACLToken + for { + raw := iter.Next() + if raw == nil { + break + } + tokens = append(tokens, raw.(*structs.ACLToken)) } - count++ - out = append(out, raw.(*structs.ACLToken).AccessorID[:4]) + return tokens } - if count != 2 { - t.Fatalf("bad: %d %v", count, out) - } - sort.Strings(out) - expect := []string{"aaaa", "aabb"} - assert.Equal(t, expect, out) + t.Run("scan by prefix", func(t *testing.T) { + iter, err := state.ACLTokenByAccessorIDPrefix(nil, "aa", SortDefault) + require.NoError(t, err) + + // Ensure we see both tokens + out := gatherTokens(iter) + require.Len(t, out, 2) + + got := []string{} + for _, t := range out { + got = append(got, t.AccessorID[:4]) + } + expect := []string{"aaaa", "aabb"} + require.Equal(t, expect, got) + }) + + t.Run("reverse order", func(t *testing.T) { + iter, err := state.ACLTokenByAccessorIDPrefix(nil, "aa", SortReverse) + require.NoError(t, err) + + // Ensure we see both tokens + out := gatherTokens(iter) + require.Len(t, out, 2) + + got := []string{} + for _, t := range out { + got = append(got, t.AccessorID[:4]) + } + expect := []string{"aabb", "aaaa"} + require.Equal(t, expect, got) + }) } func TestStateStore_ACLTokensByGlobal(t *testing.T) { @@ -7754,32 +7810,51 @@ func TestStateStore_ACLTokensByGlobal(t *testing.T) { state := testStateStore(t) tk1 := mock.ACLToken() + tk1.AccessorID = "aaaa" + tk1.AccessorID[4:] + tk2 := mock.ACLToken() + tk2.AccessorID = "aabb" + tk2.AccessorID[4:] + tk3 := mock.ACLToken() - tk4 := mock.ACLToken() + tk3.AccessorID = "bbbb" + tk3.AccessorID[4:] tk3.Global = true - if err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{tk1, tk2, tk3, tk4}); err != nil { - t.Fatalf("err: %v", err) - } + tk4 := mock.ACLToken() + tk4.AccessorID = "ffff" + tk4.AccessorID[4:] - iter, err := state.ACLTokensByGlobal(nil, true) - if err != nil { - t.Fatalf("err: %v", err) - } + err := state.UpsertACLTokens(structs.MsgTypeTestSetup, 1000, []*structs.ACLToken{tk1, tk2, tk3, tk4}) + require.NoError(t, err) - // Ensure we see the one global policies - count := 0 - for { - raw := iter.Next() - if raw == nil { - break + gatherTokens := func(iter memdb.ResultIterator) []*structs.ACLToken { + var tokens []*structs.ACLToken + for { + raw := iter.Next() + if raw == nil { + break + } + tokens = append(tokens, raw.(*structs.ACLToken)) } - count++ - } - if count != 1 { - t.Fatalf("bad: %d", count) + return tokens } + + t.Run("only global tokens", func(t *testing.T) { + iter, err := state.ACLTokensByGlobal(nil, true, SortDefault) + require.NoError(t, err) + + got := gatherTokens(iter) + require.Len(t, got, 1) + require.Equal(t, tk3.AccessorID, got[0].AccessorID) + }) + + t.Run("reverse order", func(t *testing.T) { + iter, err := state.ACLTokensByGlobal(nil, false, SortReverse) + require.NoError(t, err) + + expected := []*structs.ACLToken{tk4, tk2, tk1} + got := gatherTokens(iter) + require.Len(t, got, 3) + require.Equal(t, expected, got) + }) } func TestStateStore_OneTimeTokens(t *testing.T) { diff --git a/website/content/api-docs/acl-tokens.mdx b/website/content/api-docs/acl-tokens.mdx index acdf03f7a..02663e319 100644 --- a/website/content/api-docs/acl-tokens.mdx +++ b/website/content/api-docs/acl-tokens.mdx @@ -70,11 +70,19 @@ The table below shows this endpoint's support for ### Parameters +- `global` `(bool: false)` - If true, only return ACL tokens that are + replicated globally to all regions. + - `prefix` `(string: "")` - Specifies a string to filter ACL tokens based on an accessor 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. +- `reverse` `(bool: false)` - Specifies the list of returned ACL tokens should + be sorted in the reverse order. By default ACL tokens are returned sorted in + chronological order (older ACL tokens first), or in lexicographical order by + their ID if the `prefix` or `global` query parameters are used. + ### Sample Request ```shell-session diff --git a/website/content/api-docs/allocations.mdx b/website/content/api-docs/allocations.mdx index e39eb7f4c..21303d274 100644 --- a/website/content/api-docs/allocations.mdx +++ b/website/content/api-docs/allocations.mdx @@ -43,6 +43,11 @@ The table below shows this endpoint's support for a large number of allocations may set `task_states=false` to significantly reduce the size of the response. +- `reverse` `(bool: false)` - Specifies the list of returned allocations should + be sorted in the reverse order. By default allocations are returned sorted in + chronological order (older evaluations first), or in lexicographical order by + their ID if the `prefix` query parameter is used. + ### Sample Request ```shell-session diff --git a/website/content/api-docs/deployments.mdx b/website/content/api-docs/deployments.mdx index d9cc238d4..c8f359b0f 100644 --- a/website/content/api-docs/deployments.mdx +++ b/website/content/api-docs/deployments.mdx @@ -50,9 +50,10 @@ The table below shows this endpoint's support for results. Consider using pagination or a query parameter to reduce resource used to serve the request. -- `ascending` `(bool: false)` - Specifies the list of returned deployments should - be sorted in chronological order (oldest evaluations first). By default deployments - are returned sorted in reverse chronological order (newest deployments first). +- `reverse` `(bool: false)` - Specifies the list of returned deployments should + be sorted in the reverse order. By default deployments are returned sorted in + chronological order (older deployments first), or in lexicographical order + by their ID if the `prefix` query parameter is used. ### Sample Request diff --git a/website/content/api-docs/evaluations.mdx b/website/content/api-docs/evaluations.mdx index 37a49228a..9a33d469b 100644 --- a/website/content/api-docs/evaluations.mdx +++ b/website/content/api-docs/evaluations.mdx @@ -57,9 +57,10 @@ The table below shows this endpoint's support for Specifying `*` will return all evaluations across all authorized namespaces. This parameter is used before any `filter` expression is applied. -- `ascending` `(bool: false)` - Specifies the list of returned evaluations should - be sorted in chronological order (oldest evaluations first). By default evaluations - are returned sorted in reverse chronological order (newest evaluations first). +- `reverse` `(bool: false)` - Specifies the list of returned evaluations should + be sorted in the reverse order. By default evaluations are returned sorted in + chronological order (older evaluations first), or in lexicographical order by + their ID if the `prefix` query parameter is used. ### Sample Request