2022-10-28 22:17:31 +00:00
|
|
|
package partition
|
|
|
|
|
|
|
|
import (
|
2022-11-03 22:26:49 +00:00
|
|
|
"context"
|
2022-11-04 22:41:43 +00:00
|
|
|
"crypto/sha256"
|
2022-10-28 22:17:31 +00:00
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/rancher/apiserver/pkg/types"
|
2022-11-03 22:26:49 +00:00
|
|
|
"github.com/rancher/steve/pkg/accesscontrol"
|
2022-10-28 22:17:31 +00:00
|
|
|
"github.com/rancher/wrangler/pkg/schemas"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2022-10-21 23:41:08 +00:00
|
|
|
"k8s.io/apimachinery/pkg/watch"
|
2022-11-03 22:26:49 +00:00
|
|
|
"k8s.io/apiserver/pkg/authentication/user"
|
|
|
|
"k8s.io/apiserver/pkg/endpoints/request"
|
2022-10-28 22:17:31 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestList(t *testing.T) {
|
|
|
|
tests := []struct {
|
2022-11-04 22:41:43 +00:00
|
|
|
name string
|
|
|
|
apiOps []*types.APIRequest
|
|
|
|
access []map[string]string
|
|
|
|
partitions map[string][]Partition
|
|
|
|
objects map[string]*unstructured.UnstructuredList
|
|
|
|
want []types.APIObjectList
|
|
|
|
wantCache []mockCache
|
|
|
|
disableCache bool
|
|
|
|
wantListCalls []map[string]int
|
2022-10-28 22:17:31 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "basic",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("", "user1"),
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
2022-10-21 23:41:08 +00:00
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
2022-10-28 22:17:31 +00:00
|
|
|
"all": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-28 22:17:31 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "limit and continue",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("limit=1", "user1"),
|
|
|
|
newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith")))))), "user1"),
|
|
|
|
newRequest(fmt.Sprintf("limit=1&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin")))))), "user1"),
|
2022-12-07 22:39:05 +00:00
|
|
|
newRequest("limit=-1", "user1"),
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
2022-12-07 22:39:05 +00:00
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
2022-10-21 23:41:08 +00:00
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
2022-10-28 22:17:31 +00:00
|
|
|
"all": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-28 22:17:31 +00:00
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("granny-smith"))))),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-28 22:17:31 +00:00
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"p":"all","c":"%s","l":1}`, base64.StdEncoding.EncodeToString([]byte("crispin"))))),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-28 22:17:31 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
2022-12-07 22:39:05 +00:00
|
|
|
{
|
|
|
|
Count: 3,
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("", "user1"),
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
2022-10-21 23:41:08 +00:00
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
2022-10-28 22:17:31 +00:00
|
|
|
"pink": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 2,
|
2022-10-28 22:17:31 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition with limit and continue",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("limit=3", "user1"),
|
|
|
|
newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`))), "user1"),
|
|
|
|
newRequest(fmt.Sprintf("limit=3&continue=%s", base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`))), "user1"),
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "pink",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "red",
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
2022-10-21 23:41:08 +00:00
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
2022-10-28 22:17:31 +00:00
|
|
|
"pink": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("honeycrisp").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
"red": {
|
2022-10-21 23:41:08 +00:00
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("red-delicious").Unstructured,
|
2022-10-28 22:17:31 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 3,
|
2022-10-28 22:17:31 +00:00
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"green","o":1,"l":3}`)),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("honeycrisp").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 3,
|
2022-10-28 22:17:31 +00:00
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(`{"p":"red","l":3}`)),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
newApple("golden-delicious").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-28 22:17:31 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("red-delicious").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-10-24 21:17:14 +00:00
|
|
|
{
|
|
|
|
name: "with filters",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("filter=data.color=green", "user1"),
|
|
|
|
newRequest("filter=data.color=green&filter=metadata.name=bramley", "user1"),
|
2022-10-24 21:17:14 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
2022-10-24 21:17:14 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 2,
|
2022-10-24 21:17:14 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 1,
|
2022-10-24 21:17:14 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition with filters",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("filter=data.category=baking", "user1"),
|
2022-10-24 21:17:14 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-24 21:17:14 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "pink",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
2022-10-24 21:17:14 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"pink": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").with(map[string]string{"category": "eating"}).Unstructured,
|
|
|
|
newApple("honeycrisp").with(map[string]string{"category": "eating,baking"}).Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").with(map[string]string{"category": "baking"}).Unstructured,
|
|
|
|
newApple("bramley").with(map[string]string{"category": "eating"}).Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").with(map[string]string{"category": "baking"}).Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 3,
|
2022-10-24 21:17:14 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("honeycrisp").with(map[string]string{"category": "eating,baking"}).toObj(),
|
|
|
|
newApple("granny-smith").with(map[string]string{"category": "baking"}).toObj(),
|
|
|
|
newApple("crispin").with(map[string]string{"category": "baking"}).toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-10-27 21:11:17 +00:00
|
|
|
{
|
|
|
|
name: "with sorting",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("sort=metadata.name", "user1"),
|
|
|
|
newRequest("sort=-metadata.name", "user1"),
|
2022-10-27 21:11:17 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
2022-10-27 21:11:17 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 4,
|
2022-10-27 21:11:17 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 4,
|
2022-10-27 21:11:17 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-11-14 22:07:23 +00:00
|
|
|
{
|
|
|
|
name: "sorting with secondary sort",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("sort=data.color,metadata.name,", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("honeycrisp").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 3,
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("honeycrisp").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "sorting with missing primary sort is unsorted",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("sort=,metadata.name", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("honeycrisp").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 3,
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("honeycrisp").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "sorting with missing secondary sort is single-column sorted",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("sort=metadata.name,", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("honeycrisp").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 3,
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
newApple("honeycrisp").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-10-27 21:11:17 +00:00
|
|
|
{
|
|
|
|
name: "multi-partition sort=metadata.name",
|
|
|
|
apiOps: []*types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
newRequest("sort=metadata.name", "user1"),
|
2022-10-27 21:11:17 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
2022-10-27 21:11:17 +00:00
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
2022-10-27 21:11:17 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"pink": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
2022-11-01 20:00:22 +00:00
|
|
|
Count: 2,
|
2022-10-27 21:11:17 +00:00
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-11-04 22:41:43 +00:00
|
|
|
{
|
|
|
|
name: "pagination",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=42", "user1"),
|
|
|
|
newRequest("pagesize=1&page=3&revision=42", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"all": 1},
|
|
|
|
{"all": 1},
|
|
|
|
{"all": 1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "access-change pagination",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=42", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleB",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"all": 1},
|
|
|
|
{"all": 2},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pagination with cache disabled",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=42", "user1"),
|
|
|
|
newRequest("pagesize=1&page=3&revision=42", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{},
|
|
|
|
disableCache: true,
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"all": 1},
|
|
|
|
{"all": 2},
|
|
|
|
{"all": 3},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition pagesize=1",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=102", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"pink": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "101",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "102",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "103",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"green": 1, "yellow": 1},
|
|
|
|
{"green": 1, "yellow": 1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "pagesize=1 & limit=2 & continue",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1&limit=2", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&limit=2", "user1"), // does not use cache
|
|
|
|
newRequest("pagesize=1&page=2&revision=42&limit=2", "user1"), // uses cache
|
|
|
|
newRequest("pagesize=1&page=3&revision=42&limit=2", "user1"), // next page from cache
|
|
|
|
newRequest(fmt.Sprintf("pagesize=1&revision=42&limit=2&continue=%s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`)))))), "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("red-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Continue: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: "",
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: "",
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: "",
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: "",
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: "",
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"continue": base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 2,
|
|
|
|
resume: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf(`{"r":"42","p":"all","c":"%s","l":2}`, base64.StdEncoding.EncodeToString([]byte(`crispin`))))),
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("red-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"all": 2},
|
|
|
|
{"all": 4},
|
|
|
|
{"all": 4},
|
|
|
|
{"all": 4},
|
|
|
|
{"all": 5},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-user pagination",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1", "user2"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=42", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=42", "user2"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user2": "roleB",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user2": "roleB",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"user2": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"all": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "42",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("fuji").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "42",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "42",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"all": 1},
|
|
|
|
{"all": 2},
|
|
|
|
{"all": 2},
|
|
|
|
{"all": 2},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition multi-user pagination",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1", "user2"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=102", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=103", "user2"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user2": "roleB",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user2": "roleB",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"user2": {
|
|
|
|
mockPartition{
|
|
|
|
name: "yellow",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"pink": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "101",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "102",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "103",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "103",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("crispin").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "103",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("golden-delicious").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
cacheKey{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "103",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "103",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user2", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "103",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"green": 1, "yellow": 0},
|
|
|
|
{"green": 1, "yellow": 1},
|
|
|
|
{"green": 1, "yellow": 1},
|
|
|
|
{"green": 1, "yellow": 1},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "multi-partition access-change pagination",
|
|
|
|
apiOps: []*types.APIRequest{
|
|
|
|
newRequest("pagesize=1", "user1"),
|
|
|
|
newRequest("pagesize=1&page=2&revision=102", "user1"),
|
|
|
|
},
|
|
|
|
access: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleB",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "green",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
objects: map[string]*unstructured.UnstructuredList{
|
|
|
|
"pink": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "101",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"green": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "102",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"yellow": {
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "103",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("crispin").Unstructured,
|
|
|
|
newApple("golden-delicious").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
want: []types.APIObjectList{
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("granny-smith").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Count: 2,
|
|
|
|
Pages: 2,
|
|
|
|
Revision: "102",
|
|
|
|
Objects: []types.APIObject{
|
|
|
|
newApple("bramley").toObj(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantCache: []mockCache{
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
cacheKey{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: map[cacheKey]*unstructured.UnstructuredList{
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleA"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
chunkSize: 100000,
|
|
|
|
pageSize: 1,
|
|
|
|
accessID: getAccessID("user1", "roleB"),
|
|
|
|
resourcePath: "/apples",
|
|
|
|
revision: "102",
|
|
|
|
}: {
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
newApple("bramley").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
wantListCalls: []map[string]int{
|
|
|
|
{"green": 1},
|
|
|
|
{"green": 2},
|
|
|
|
},
|
|
|
|
},
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}}
|
2022-12-08 01:57:14 +00:00
|
|
|
stores := map[string]UnstructuredStore{}
|
2022-11-04 22:41:43 +00:00
|
|
|
for _, partitions := range test.partitions {
|
|
|
|
for _, p := range partitions {
|
|
|
|
stores[p.Name()] = &mockStore{
|
|
|
|
contents: test.objects[p.Name()],
|
|
|
|
}
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-04 22:41:43 +00:00
|
|
|
asl := &mockAccessSetLookup{userRoles: test.access}
|
|
|
|
if test.disableCache {
|
|
|
|
t.Setenv("CATTLE_REQUEST_CACHE_DISABLED", "Y")
|
|
|
|
}
|
2022-11-03 22:26:49 +00:00
|
|
|
store := NewStore(mockPartitioner{
|
|
|
|
stores: stores,
|
|
|
|
partitions: test.partitions,
|
|
|
|
}, asl)
|
2022-10-28 22:17:31 +00:00
|
|
|
for i, req := range test.apiOps {
|
|
|
|
got, gotErr := store.List(req, schema)
|
|
|
|
assert.Nil(t, gotErr)
|
|
|
|
assert.Equal(t, test.want[i], got)
|
2022-11-04 22:41:43 +00:00
|
|
|
if test.disableCache {
|
|
|
|
assert.Nil(t, store.listCache)
|
|
|
|
}
|
|
|
|
if len(test.wantCache) > 0 {
|
|
|
|
assert.Equal(t, len(test.wantCache[i].contents), len(store.listCache.Keys()))
|
|
|
|
for k, v := range test.wantCache[i].contents {
|
|
|
|
cachedVal, _ := store.listCache.Get(k)
|
|
|
|
assert.Equal(t, v, cachedVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(test.wantListCalls) > 0 {
|
|
|
|
for name, _ := range store.Partitioner.(mockPartitioner).stores {
|
2022-12-08 01:57:14 +00:00
|
|
|
assert.Equal(t, test.wantListCalls[i][name], store.Partitioner.(mockPartitioner).stores[name].(*mockStore).called)
|
2022-11-04 22:41:43 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-08 01:57:14 +00:00
|
|
|
func TestListByRevision(t *testing.T) {
|
|
|
|
|
|
|
|
schema := &types.APISchema{Schema: &schemas.Schema{ID: "apple"}}
|
|
|
|
asl := &mockAccessSetLookup{userRoles: []map[string]string{
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"user1": "roleA",
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
store := NewStore(mockPartitioner{
|
|
|
|
stores: map[string]UnstructuredStore{
|
|
|
|
"all": &mockVersionedStore{
|
|
|
|
versions: []mockStore{
|
|
|
|
{
|
|
|
|
contents: &unstructured.UnstructuredList{
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
contents: &unstructured.UnstructuredList{
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"resourceVersion": "2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Items: []unstructured.Unstructured{
|
|
|
|
newApple("fuji").Unstructured,
|
|
|
|
newApple("granny-smith").Unstructured,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
partitions: map[string][]Partition{
|
|
|
|
"user1": {
|
|
|
|
mockPartition{
|
|
|
|
name: "all",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, asl)
|
|
|
|
req := newRequest("", "user1")
|
|
|
|
t.Setenv("CATTLE_REQUEST_CACHE_DISABLED", "Y")
|
|
|
|
|
|
|
|
got, gotErr := store.List(req, schema)
|
|
|
|
assert.Nil(t, gotErr)
|
|
|
|
wantVersion := "2"
|
|
|
|
assert.Equal(t, wantVersion, got.Revision)
|
|
|
|
|
|
|
|
req = newRequest("revision=1", "user1")
|
|
|
|
got, gotErr = store.List(req, schema)
|
|
|
|
assert.Nil(t, gotErr)
|
|
|
|
wantVersion = "1"
|
|
|
|
assert.Equal(t, wantVersion, got.Revision)
|
|
|
|
}
|
|
|
|
|
2022-10-28 22:17:31 +00:00
|
|
|
type mockPartitioner struct {
|
2022-12-08 01:57:14 +00:00
|
|
|
stores map[string]UnstructuredStore
|
2022-11-04 22:41:43 +00:00
|
|
|
partitions map[string][]Partition
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m mockPartitioner) Lookup(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) (Partition, error) {
|
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m mockPartitioner) All(apiOp *types.APIRequest, schema *types.APISchema, verb, id string) ([]Partition, error) {
|
2022-11-04 22:41:43 +00:00
|
|
|
user, _ := request.UserFrom(apiOp.Request.Context())
|
|
|
|
return m.partitions[user.GetName()], nil
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-21 23:41:08 +00:00
|
|
|
func (m mockPartitioner) Store(apiOp *types.APIRequest, partition Partition) (UnstructuredStore, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
return m.stores[partition.Name()], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockPartition struct {
|
|
|
|
name string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m mockPartition) Name() string {
|
|
|
|
return m.name
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockStore struct {
|
2022-10-21 23:41:08 +00:00
|
|
|
contents *unstructured.UnstructuredList
|
2022-10-28 22:17:31 +00:00
|
|
|
partition mockPartition
|
2022-11-04 22:41:43 +00:00
|
|
|
called int
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) {
|
2022-11-04 22:41:43 +00:00
|
|
|
m.called++
|
2022-10-28 22:17:31 +00:00
|
|
|
query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery)
|
|
|
|
l := query.Get("limit")
|
|
|
|
if l == "" {
|
2022-12-16 16:12:57 +00:00
|
|
|
return m.contents, nil, nil
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
i := 0
|
|
|
|
if c := query.Get("continue"); c != "" {
|
|
|
|
start, _ := base64.StdEncoding.DecodeString(c)
|
2022-10-21 23:41:08 +00:00
|
|
|
for j, obj := range m.contents.Items {
|
|
|
|
if string(start) == obj.GetName() {
|
2022-10-28 22:17:31 +00:00
|
|
|
i = j
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lInt, _ := strconv.Atoi(l)
|
2022-10-21 23:41:08 +00:00
|
|
|
contents := m.contents.DeepCopy()
|
|
|
|
if len(contents.Items) > i+lInt {
|
|
|
|
contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName())))
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
2022-10-21 23:41:08 +00:00
|
|
|
if i > len(contents.Items) {
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
2022-10-21 23:41:08 +00:00
|
|
|
if i+lInt > len(contents.Items) {
|
|
|
|
contents.Items = contents.Items[i:]
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
2022-10-21 23:41:08 +00:00
|
|
|
contents.Items = contents.Items[i : i+lInt]
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockStore) ByID(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockStore) Create(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject) (*unstructured.Unstructured, []types.Warning, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockStore) Update(apiOp *types.APIRequest, schema *types.APISchema, data types.APIObject, id string) (*unstructured.Unstructured, []types.Warning, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockStore) Delete(apiOp *types.APIRequest, schema *types.APISchema, id string) (*unstructured.Unstructured, []types.Warning, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2022-10-21 23:41:08 +00:00
|
|
|
func (m *mockStore) Watch(apiOp *types.APIRequest, schema *types.APISchema, w types.WatchRequest) (chan watch.Event, error) {
|
2022-10-28 22:17:31 +00:00
|
|
|
panic("not implemented")
|
|
|
|
}
|
|
|
|
|
2022-12-08 01:57:14 +00:00
|
|
|
type mockVersionedStore struct {
|
|
|
|
mockStore
|
|
|
|
versions []mockStore
|
|
|
|
}
|
|
|
|
|
2022-12-16 16:12:57 +00:00
|
|
|
func (m *mockVersionedStore) List(apiOp *types.APIRequest, schema *types.APISchema) (*unstructured.UnstructuredList, []types.Warning, error) {
|
2022-12-08 01:57:14 +00:00
|
|
|
m.called++
|
|
|
|
query, _ := url.ParseQuery(apiOp.Request.URL.RawQuery)
|
|
|
|
rv := len(m.versions) - 1
|
|
|
|
if query.Get("resourceVersion") != "" {
|
|
|
|
rv, _ = strconv.Atoi(query.Get("resourceVersion"))
|
|
|
|
rv--
|
|
|
|
}
|
|
|
|
l := query.Get("limit")
|
|
|
|
if l == "" {
|
2022-12-16 16:12:57 +00:00
|
|
|
return m.versions[rv].contents, nil, nil
|
2022-12-08 01:57:14 +00:00
|
|
|
}
|
|
|
|
i := 0
|
|
|
|
if c := query.Get("continue"); c != "" {
|
|
|
|
start, _ := base64.StdEncoding.DecodeString(c)
|
|
|
|
for j, obj := range m.versions[rv].contents.Items {
|
|
|
|
if string(start) == obj.GetName() {
|
|
|
|
i = j
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lInt, _ := strconv.Atoi(l)
|
|
|
|
contents := m.versions[rv].contents.DeepCopy()
|
|
|
|
if len(contents.Items) > i+lInt {
|
|
|
|
contents.SetContinue(base64.StdEncoding.EncodeToString([]byte(contents.Items[i+lInt].GetName())))
|
|
|
|
}
|
|
|
|
if i > len(contents.Items) {
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-12-08 01:57:14 +00:00
|
|
|
}
|
|
|
|
if i+lInt > len(contents.Items) {
|
|
|
|
contents.Items = contents.Items[i:]
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-12-08 01:57:14 +00:00
|
|
|
}
|
|
|
|
contents.Items = contents.Items[i : i+lInt]
|
2022-12-16 16:12:57 +00:00
|
|
|
return contents, nil, nil
|
2022-12-08 01:57:14 +00:00
|
|
|
}
|
|
|
|
|
2022-11-04 22:41:43 +00:00
|
|
|
type mockCache struct {
|
|
|
|
contents map[cacheKey]*unstructured.UnstructuredList
|
|
|
|
}
|
|
|
|
|
2022-10-28 22:17:31 +00:00
|
|
|
var colorMap = map[string]string{
|
|
|
|
"fuji": "pink",
|
|
|
|
"honeycrisp": "pink",
|
|
|
|
"granny-smith": "green",
|
|
|
|
"bramley": "green",
|
|
|
|
"crispin": "yellow",
|
|
|
|
"golden-delicious": "yellow",
|
|
|
|
"red-delicious": "red",
|
|
|
|
}
|
|
|
|
|
2022-11-03 22:26:49 +00:00
|
|
|
func newRequest(query, username string) *types.APIRequest {
|
2022-10-28 22:17:31 +00:00
|
|
|
return &types.APIRequest{
|
2022-11-03 22:26:49 +00:00
|
|
|
Request: (&http.Request{
|
2022-10-28 22:17:31 +00:00
|
|
|
URL: &url.URL{
|
|
|
|
Scheme: "https",
|
|
|
|
Host: "rancher",
|
|
|
|
Path: "/apples",
|
|
|
|
RawQuery: query,
|
|
|
|
},
|
2022-11-03 22:26:49 +00:00
|
|
|
}).WithContext(request.WithUser(context.Background(), &user.DefaultInfo{
|
|
|
|
Name: username,
|
|
|
|
Groups: []string{"system:authenticated"},
|
|
|
|
})),
|
2022-10-28 22:17:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type apple struct {
|
|
|
|
unstructured.Unstructured
|
|
|
|
}
|
|
|
|
|
|
|
|
func newApple(name string) apple {
|
|
|
|
return apple{unstructured.Unstructured{
|
|
|
|
Object: map[string]interface{}{
|
|
|
|
"kind": "apple",
|
|
|
|
"metadata": map[string]interface{}{
|
|
|
|
"name": name,
|
|
|
|
},
|
|
|
|
"data": map[string]interface{}{
|
|
|
|
"color": colorMap[name],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a apple) toObj() types.APIObject {
|
|
|
|
return types.APIObject{
|
|
|
|
Type: "apple",
|
|
|
|
ID: a.Object["metadata"].(map[string]interface{})["name"].(string),
|
|
|
|
Object: &a.Unstructured,
|
|
|
|
}
|
|
|
|
}
|
2022-10-24 21:17:14 +00:00
|
|
|
|
|
|
|
func (a apple) with(data map[string]string) apple {
|
|
|
|
for k, v := range data {
|
|
|
|
a.Object["data"].(map[string]interface{})[k] = v
|
|
|
|
}
|
|
|
|
return a
|
|
|
|
}
|
2022-11-03 22:26:49 +00:00
|
|
|
|
2022-11-04 22:41:43 +00:00
|
|
|
type mockAccessSetLookup struct {
|
|
|
|
accessID string
|
|
|
|
userRoles []map[string]string
|
|
|
|
}
|
2022-11-03 22:26:49 +00:00
|
|
|
|
2022-11-04 22:41:43 +00:00
|
|
|
func (m *mockAccessSetLookup) AccessFor(user user.Info) *accesscontrol.AccessSet {
|
|
|
|
userName := user.GetName()
|
|
|
|
access := getAccessID(userName, m.userRoles[0][userName])
|
|
|
|
m.userRoles = m.userRoles[1:]
|
2022-11-03 22:26:49 +00:00
|
|
|
return &accesscontrol.AccessSet{
|
2022-11-04 22:41:43 +00:00
|
|
|
ID: access,
|
2022-11-03 22:26:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockAccessSetLookup) PurgeUserData(_ string) {
|
|
|
|
panic("not implemented")
|
|
|
|
}
|
2022-11-04 22:41:43 +00:00
|
|
|
|
|
|
|
func getAccessID(user, role string) string {
|
|
|
|
h := sha256.Sum256([]byte(user + role))
|
|
|
|
return string(h[:])
|
|
|
|
}
|