Merge pull request #91505 from jpbetz/rv-semantics
Consistent Resource Version Semantics for List Kubernetes-commit: 27aca3f7c2f06925dd1890cd8b0522b775b0e323
This commit is contained in:
commit
1723c05a3e
|
@ -668,11 +668,11 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/api",
|
||||
"Rev": "f849118f70f6"
|
||||
"Rev": "3b5342aabbeb"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/apimachinery",
|
||||
"Rev": "3b98a76529ae"
|
||||
"Rev": "640343330969"
|
||||
},
|
||||
{
|
||||
"ImportPath": "k8s.io/client-go",
|
||||
|
|
8
go.mod
8
go.mod
|
@ -41,8 +41,8 @@ require (
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.2.2
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.0.0-20200626130448-f849118f70f6
|
||||
k8s.io/apimachinery v0.0.0-20200626130251-3b98a76529ae
|
||||
k8s.io/api v0.0.0-20200627130740-3b5342aabbeb
|
||||
k8s.io/apimachinery v0.0.0-20200627130346-640343330969
|
||||
k8s.io/client-go v0.0.0-20200626130735-db5293afc7bf
|
||||
k8s.io/component-base v0.0.0-20200626131333-f52a9b7250e8
|
||||
k8s.io/klog/v2 v2.1.0
|
||||
|
@ -54,8 +54,8 @@ require (
|
|||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => k8s.io/api v0.0.0-20200626130448-f849118f70f6
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200626130251-3b98a76529ae
|
||||
k8s.io/api => k8s.io/api v0.0.0-20200627130740-3b5342aabbeb
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20200627130346-640343330969
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20200626130735-db5293afc7bf
|
||||
k8s.io/component-base => k8s.io/component-base v0.0.0-20200626131333-f52a9b7250e8
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -506,8 +506,8 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
|||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.0.0-20200626130448-f849118f70f6/go.mod h1:NKnRcQSY4TIqMb0OI3a9oDdDJflkQO6p9sY6iKIpvco=
|
||||
k8s.io/apimachinery v0.0.0-20200626130251-3b98a76529ae/go.mod h1:m5QoVMHU94aieNFPfLqf7WmU8HLVLm3JasG64COkSXQ=
|
||||
k8s.io/api v0.0.0-20200627130740-3b5342aabbeb/go.mod h1:vQNZO9B7m8N2djMqqgLa34Z2LCUrTXLWN98egCzDhrU=
|
||||
k8s.io/apimachinery v0.0.0-20200627130346-640343330969/go.mod h1:m5QoVMHU94aieNFPfLqf7WmU8HLVLm3JasG64COkSXQ=
|
||||
k8s.io/client-go v0.0.0-20200626130735-db5293afc7bf/go.mod h1:xWyNNU1CYxp5Qle29OSU49QrlzME3jkGOpv9FsbIFr4=
|
||||
k8s.io/component-base v0.0.0-20200626131333-f52a9b7250e8/go.mod h1:c8B2Gs7ipNKY0Hn4SBgCBsMo38vovG9B2E90kf+Fg1E=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
|
||||
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -198,6 +199,12 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
|||
return
|
||||
}
|
||||
|
||||
if errs := metainternalversionvalidation.ValidateListOptions(&listOptions); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// transform fields
|
||||
// TODO: DecodeParametersInto should do this.
|
||||
if listOptions.FieldSelector != nil {
|
||||
|
|
|
@ -19,6 +19,8 @@ package handlers
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
metainternalversionvalidation "k8s.io/apimachinery/pkg/apis/meta/internalversion/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -198,6 +200,12 @@ func ListResource(r rest.Lister, rw rest.Watcher, scope *RequestScope, forceWatc
|
|||
return
|
||||
}
|
||||
|
||||
if errs := metainternalversionvalidation.ValidateListOptions(&opts); len(errs) > 0 {
|
||||
err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "ListOptions"}, "", errs)
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// transform fields
|
||||
// TODO: DecodeParametersInto should do this.
|
||||
if opts.FieldSelector != nil {
|
||||
|
|
|
@ -1078,6 +1078,10 @@ func typeToJSON(typeName string) string {
|
|||
return "string"
|
||||
case "v1.DeletionPropagation", "*v1.DeletionPropagation":
|
||||
return "string"
|
||||
case "v1.ResourceVersionMatch", "*v1.ResourceVersionMatch":
|
||||
return "string"
|
||||
case "v1.IncludeObjectPolicy", "*v1.IncludeObjectPolicy":
|
||||
return "string"
|
||||
|
||||
// TODO: Fix these when go-restful supports a way to specify an array query param:
|
||||
// https://github.com/emicklei/go-restful/issues/225
|
||||
|
|
|
@ -322,7 +322,7 @@ func (e *Store) ListPredicate(ctx context.Context, p storage.SelectionPredicate,
|
|||
p.Continue = options.Continue
|
||||
list := e.NewListFunc()
|
||||
qualifiedResource := e.qualifiedResourceFromContext(ctx)
|
||||
storageOpts := storage.ListOptions{ResourceVersion: options.ResourceVersion, Predicate: p}
|
||||
storageOpts := storage.ListOptions{ResourceVersion: options.ResourceVersion, ResourceVersionMatch: options.ResourceVersionMatch, Predicate: p}
|
||||
if name, ok := p.MatchesSingle(); ok {
|
||||
if key, err := e.KeyFunc(ctx, name); err == nil {
|
||||
err := e.Storage.GetToList(ctx, key, storageOpts, list)
|
||||
|
|
|
@ -580,7 +580,7 @@ func (c *Cacher) GetToList(ctx context.Context, key string, opts storage.ListOpt
|
|||
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
|
||||
hasContinuation := pagingEnabled && len(pred.Continue) > 0
|
||||
hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0"
|
||||
if resourceVersion == "" || hasContinuation || hasLimit {
|
||||
if resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact {
|
||||
// If resourceVersion is not specified, serve it from underlying
|
||||
// storage (for backward compatibility). If a continuation is
|
||||
// requested, serve it from the underlying storage as well.
|
||||
|
@ -654,7 +654,7 @@ func (c *Cacher) List(ctx context.Context, key string, opts storage.ListOptions,
|
|||
pagingEnabled := utilfeature.DefaultFeatureGate.Enabled(features.APIListChunking)
|
||||
hasContinuation := pagingEnabled && len(pred.Continue) > 0
|
||||
hasLimit := pagingEnabled && pred.Limit > 0 && resourceVersion != "0"
|
||||
if resourceVersion == "" || hasContinuation || hasLimit {
|
||||
if resourceVersion == "" || hasContinuation || hasLimit || opts.ResourceVersionMatch == metav1.ResourceVersionMatchExact {
|
||||
// If resourceVersion is not specified, serve it from underlying
|
||||
// storage (for backward compatibility). If a continuation is
|
||||
// requested, serve it from the underlying storage as well.
|
||||
|
@ -1090,7 +1090,7 @@ func (lw *cacherListerWatcher) List(options metav1.ListOptions) (runtime.Object,
|
|||
Continue: options.Continue,
|
||||
}
|
||||
|
||||
if err := lw.storage.List(context.TODO(), lw.resourcePrefix, storage.ListOptions{Predicate: pred}, list); err != nil {
|
||||
if err := lw.storage.List(context.TODO(), lw.resourcePrefix, storage.ListOptions{ResourceVersionMatch: options.ResourceVersionMatch, Predicate: pred}, list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
|
|
|
@ -32,6 +32,8 @@ import (
|
|||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -62,10 +64,7 @@ func (d authenticatedDataString) AuthenticatedData() []byte {
|
|||
var _ value.Context = authenticatedDataString("")
|
||||
|
||||
type store struct {
|
||||
client *clientv3.Client
|
||||
// getOps contains additional options that should be passed
|
||||
// to all Get() calls.
|
||||
getOps []clientv3.OpOption
|
||||
client *clientv3.Client
|
||||
codec runtime.Codec
|
||||
versioner storage.Versioner
|
||||
transformer value.Transformer
|
||||
|
@ -115,13 +114,12 @@ func (s *store) Versioner() storage.Versioner {
|
|||
func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error {
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
callOpts := s.getOps
|
||||
getResp, err := s.client.KV.Get(ctx, key, callOpts...)
|
||||
getResp, err := s.client.KV.Get(ctx, key)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.ensureMinimumResourceVersion(opts.ResourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
if err = s.validateMinimumResourceVersion(opts.ResourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -252,7 +250,7 @@ func (s *store) GuaranteedUpdate(
|
|||
|
||||
getCurrentState := func() (*objState, error) {
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key, s.getOps...)
|
||||
getResp, err := s.client.KV.Get(ctx, key)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -382,10 +380,12 @@ func (s *store) GuaranteedUpdate(
|
|||
// GetToList implements storage.Interface.GetToList.
|
||||
func (s *store) GetToList(ctx context.Context, key string, listOpts storage.ListOptions, listObj runtime.Object) error {
|
||||
resourceVersion := listOpts.ResourceVersion
|
||||
match := listOpts.ResourceVersionMatch
|
||||
pred := listOpts.Predicate
|
||||
trace := utiltrace.New("GetToList etcd3",
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"resourceVersion", resourceVersion},
|
||||
utiltrace.Field{"resourceVersionMatch", match},
|
||||
utiltrace.Field{"limit", pred.Limit},
|
||||
utiltrace.Field{"continue", pred.Continue})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
@ -402,12 +402,21 @@ func (s *store) GetToList(ctx context.Context, key string, listOpts storage.List
|
|||
|
||||
key = path.Join(s.pathPrefix, key)
|
||||
startTime := time.Now()
|
||||
getResp, err := s.client.KV.Get(ctx, key, s.getOps...)
|
||||
var opts []clientv3.OpOption
|
||||
if len(resourceVersion) > 0 && match == metav1.ResourceVersionMatchExact {
|
||||
rv, err := s.versioner.ParseResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
|
||||
}
|
||||
opts = append(opts, clientv3.WithRev(int64(rv)))
|
||||
}
|
||||
|
||||
getResp, err := s.client.KV.Get(ctx, key, opts...)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(listPtr), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.ensureMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -515,10 +524,12 @@ func encodeContinue(key, keyPrefix string, resourceVersion int64) (string, error
|
|||
// List implements storage.Interface.List.
|
||||
func (s *store) List(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
||||
resourceVersion := opts.ResourceVersion
|
||||
match := opts.ResourceVersionMatch
|
||||
pred := opts.Predicate
|
||||
trace := utiltrace.New("List etcd3",
|
||||
utiltrace.Field{"key", key},
|
||||
utiltrace.Field{"resourceVersion", resourceVersion},
|
||||
utiltrace.Field{"resourceVersionMatch", match},
|
||||
utiltrace.Field{"limit", pred.Limit},
|
||||
utiltrace.Field{"continue", pred.Continue})
|
||||
defer trace.LogIfLong(500 * time.Millisecond)
|
||||
|
@ -552,6 +563,15 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
|||
|
||||
newItemFunc := getNewItemFunc(listObj, v)
|
||||
|
||||
var fromRV *uint64
|
||||
if len(resourceVersion) > 0 {
|
||||
parsedRV, err := s.versioner.ParseResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
|
||||
}
|
||||
fromRV = &parsedRV
|
||||
}
|
||||
|
||||
var returnedRV, continueRV int64
|
||||
var continueKey string
|
||||
switch {
|
||||
|
@ -577,20 +597,41 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
|||
returnedRV = continueRV
|
||||
}
|
||||
case s.pagingEnabled && pred.Limit > 0:
|
||||
if len(resourceVersion) > 0 {
|
||||
fromRV, err := s.versioner.ParseResourceVersion(resourceVersion)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %v", err))
|
||||
if fromRV != nil {
|
||||
switch match {
|
||||
case metav1.ResourceVersionMatchNotOlderThan:
|
||||
// The not older than constraint is checked after we get a response from etcd,
|
||||
// and returnedRV is then set to the revision we get from the etcd response.
|
||||
case metav1.ResourceVersionMatchExact:
|
||||
returnedRV = int64(*fromRV)
|
||||
options = append(options, clientv3.WithRev(returnedRV))
|
||||
case "": // legacy case
|
||||
if *fromRV > 0 {
|
||||
returnedRV = int64(*fromRV)
|
||||
options = append(options, clientv3.WithRev(returnedRV))
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown ResourceVersionMatch value: %v", match)
|
||||
}
|
||||
if fromRV > 0 {
|
||||
options = append(options, clientv3.WithRev(int64(fromRV)))
|
||||
}
|
||||
returnedRV = int64(fromRV)
|
||||
}
|
||||
|
||||
rangeEnd := clientv3.GetPrefixRangeEnd(keyPrefix)
|
||||
options = append(options, clientv3.WithRange(rangeEnd))
|
||||
default:
|
||||
if fromRV != nil {
|
||||
switch match {
|
||||
case metav1.ResourceVersionMatchNotOlderThan:
|
||||
// The not older than constraint is checked after we get a response from etcd,
|
||||
// and returnedRV is then set to the revision we get from the etcd response.
|
||||
case metav1.ResourceVersionMatchExact:
|
||||
returnedRV = int64(*fromRV)
|
||||
options = append(options, clientv3.WithRev(returnedRV))
|
||||
case "": // legacy case
|
||||
default:
|
||||
return fmt.Errorf("unknown ResourceVersionMatch value: %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
options = append(options, clientv3.WithPrefix())
|
||||
}
|
||||
|
||||
|
@ -605,7 +646,7 @@ func (s *store) List(ctx context.Context, key string, opts storage.ListOptions,
|
|||
if err != nil {
|
||||
return interpretListError(err, len(pred.Continue) > 0, continueKey, keyPrefix)
|
||||
}
|
||||
if err = s.ensureMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
if err = s.validateMinimumResourceVersion(resourceVersion, uint64(getResp.Header.Revision)); err != nil {
|
||||
return err
|
||||
}
|
||||
hasMore = getResp.More
|
||||
|
@ -822,9 +863,9 @@ func (s *store) ttlOpts(ctx context.Context, ttl int64) ([]clientv3.OpOption, er
|
|||
return []clientv3.OpOption{clientv3.WithLease(id)}, nil
|
||||
}
|
||||
|
||||
// ensureMinimumResourceVersion returns a 'too large resource' version error when the provided minimumResourceVersion is
|
||||
// validateMinimumResourceVersion returns a 'too large resource' version error when the provided minimumResourceVersion is
|
||||
// greater than the most recent actualRevision available from storage.
|
||||
func (s *store) ensureMinimumResourceVersion(minimumResourceVersion string, actualRevision uint64) error {
|
||||
func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, actualRevision uint64) error {
|
||||
if minimumResourceVersion == "" {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -373,6 +373,10 @@ func TestConditionalDelete(t *testing.T) {
|
|||
func TestGetToList(t *testing.T) {
|
||||
ctx, store, cluster := testSetup(t)
|
||||
defer cluster.Terminate(t)
|
||||
prevKey, prevStoredObj := testPropogateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "prev"}})
|
||||
|
||||
prevRV, _ := strconv.Atoi(prevStoredObj.ResourceVersion)
|
||||
|
||||
key, storedObj := testPropogateStore(ctx, t, store, &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
|
||||
|
||||
currentRV, _ := strconv.Atoi(storedObj.ResourceVersion)
|
||||
|
@ -382,6 +386,7 @@ func TestGetToList(t *testing.T) {
|
|||
pred storage.SelectionPredicate
|
||||
expectedOut []*example.Pod
|
||||
rv string
|
||||
rvMatch metav1.ResourceVersionMatch
|
||||
expectRVTooLarge bool
|
||||
}{{ // test GetToList on existing key
|
||||
key: key,
|
||||
|
@ -392,11 +397,41 @@ func TestGetToList(t *testing.T) {
|
|||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: "0",
|
||||
}, { // test GetToList on existing key with minimum resource version set to 0, match=minimum
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: "0",
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
}, { // test GetToList on existing key with minimum resource version set to current resource version
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: fmt.Sprintf("%d", currentRV),
|
||||
}, { // test GetToList on existing key with minimum resource version set to current resource version, match=minimum
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: fmt.Sprintf("%d", currentRV),
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
}, { // test GetToList on existing key with minimum resource version set to previous resource version, match=minimum
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: fmt.Sprintf("%d", prevRV),
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
}, { // test GetToList on existing key with resource version set to current resource version, match=exact
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{storedObj},
|
||||
rv: fmt.Sprintf("%d", currentRV),
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
}, { // test GetToList on existing key with resource version set to previous resource version, match=exact
|
||||
key: prevKey,
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{prevStoredObj},
|
||||
rv: fmt.Sprintf("%d", prevRV),
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
}, { // test GetToList on existing key with minimum resource version set too high
|
||||
key: key,
|
||||
pred: storage.Everything,
|
||||
|
@ -422,7 +457,7 @@ func TestGetToList(t *testing.T) {
|
|||
|
||||
for i, tt := range tests {
|
||||
out := &example.PodList{}
|
||||
err := store.GetToList(ctx, tt.key, storage.ListOptions{ResourceVersion: tt.rv, Predicate: tt.pred}, out)
|
||||
err := store.GetToList(ctx, tt.key, storage.ListOptions{ResourceVersion: tt.rv, ResourceVersionMatch: tt.rvMatch, Predicate: tt.pred}, out)
|
||||
|
||||
if tt.expectRVTooLarge {
|
||||
if err == nil || !storage.IsTooLargeResourceVersion(err) {
|
||||
|
@ -934,6 +969,7 @@ func TestList(t *testing.T) {
|
|||
name string
|
||||
disablePaging bool
|
||||
rv string
|
||||
rvMatch metav1.ResourceVersionMatch
|
||||
prefix string
|
||||
pred storage.SelectionPredicate
|
||||
expectedOut []*example.Pod
|
||||
|
@ -981,6 +1017,31 @@ func TestList(t *testing.T) {
|
|||
expectedOut: []*example.Pod{preset[0].storedObj},
|
||||
rv: "0",
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to 1, match=Exact",
|
||||
prefix: "/one-level/",
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{},
|
||||
rv: "1",
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
expectRV: "1",
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to 1, match=NotOlderThan",
|
||||
prefix: "/one-level/",
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{preset[0].storedObj},
|
||||
rv: "0",
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to 1, match=Invalid",
|
||||
prefix: "/one-level/",
|
||||
pred: storage.Everything,
|
||||
rv: "0",
|
||||
rvMatch: "Invalid",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to current resource version",
|
||||
prefix: "/one-level/",
|
||||
|
@ -988,6 +1049,23 @@ func TestList(t *testing.T) {
|
|||
expectedOut: []*example.Pod{preset[0].storedObj},
|
||||
rv: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to current resource version, match=Exact",
|
||||
prefix: "/one-level/",
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{preset[0].storedObj},
|
||||
rv: list.ResourceVersion,
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
expectRV: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List on existing key with resource version set to current resource version, match=NotOlderThan",
|
||||
prefix: "/one-level/",
|
||||
pred: storage.Everything,
|
||||
expectedOut: []*example.Pod{preset[0].storedObj},
|
||||
rv: list.ResourceVersion,
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
},
|
||||
{
|
||||
name: "test List on non-existing key",
|
||||
prefix: "/non-existing/",
|
||||
|
@ -1029,6 +1107,21 @@ func TestList(t *testing.T) {
|
|||
rv: list.ResourceVersion,
|
||||
expectRV: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List with limit at current resource version and match=Exact",
|
||||
prefix: "/two-level/",
|
||||
pred: storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: 1,
|
||||
},
|
||||
expectedOut: []*example.Pod{preset[1].storedObj},
|
||||
expectContinue: true,
|
||||
expectedRemainingItemCount: utilpointer.Int64Ptr(1),
|
||||
rv: list.ResourceVersion,
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
expectRV: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List with limit at resource version 0",
|
||||
prefix: "/two-level/",
|
||||
|
@ -1043,6 +1136,49 @@ func TestList(t *testing.T) {
|
|||
rv: "0",
|
||||
expectRV: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List with limit at resource version 0 match=NotOlderThan",
|
||||
prefix: "/two-level/",
|
||||
pred: storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: 1,
|
||||
},
|
||||
expectedOut: []*example.Pod{preset[1].storedObj},
|
||||
expectContinue: true,
|
||||
expectedRemainingItemCount: utilpointer.Int64Ptr(1),
|
||||
rv: "0",
|
||||
rvMatch: metav1.ResourceVersionMatchNotOlderThan,
|
||||
expectRV: list.ResourceVersion,
|
||||
},
|
||||
{
|
||||
name: "test List with limit at resource version 1 and match=Exact",
|
||||
prefix: "/two-level/",
|
||||
pred: storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: 1,
|
||||
},
|
||||
expectedOut: []*example.Pod{},
|
||||
expectContinue: false,
|
||||
rv: "1",
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
expectRV: "1",
|
||||
},
|
||||
{
|
||||
name: "test List with limit at old resource version and match=Exact",
|
||||
prefix: "/two-level/",
|
||||
pred: storage.SelectionPredicate{
|
||||
Label: labels.Everything(),
|
||||
Field: fields.Everything(),
|
||||
Limit: 1,
|
||||
},
|
||||
expectedOut: []*example.Pod{},
|
||||
expectContinue: false,
|
||||
rv: "1",
|
||||
rvMatch: metav1.ResourceVersionMatchExact,
|
||||
expectRV: "1",
|
||||
},
|
||||
{
|
||||
name: "test List with limit when paging disabled",
|
||||
disablePaging: true,
|
||||
|
@ -1201,7 +1337,7 @@ func TestList(t *testing.T) {
|
|||
}
|
||||
|
||||
out := &example.PodList{}
|
||||
storageOpts := storage.ListOptions{ResourceVersion: tt.rv, Predicate: tt.pred}
|
||||
storageOpts := storage.ListOptions{ResourceVersion: tt.rv, ResourceVersionMatch: tt.rvMatch, Predicate: tt.pred}
|
||||
var err error
|
||||
if tt.disablePaging {
|
||||
err = disablePagingStore.List(ctx, tt.prefix, storageOpts, out)
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
@ -189,17 +190,20 @@ type Interface interface {
|
|||
// Get unmarshals json found at key into objPtr. On a not found error, will either
|
||||
// return a zero object of the requested type, or an error, depending on 'opts.ignoreNotFound'.
|
||||
// Treats empty responses and nil response nodes exactly like a not found error.
|
||||
// The returned contents may be delayed according to the semantics of GetOptions.ResourceVersion.
|
||||
// The returned contents may be delayed, but it is guaranteed that they will
|
||||
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
||||
Get(ctx context.Context, key string, opts GetOptions, objPtr runtime.Object) error
|
||||
|
||||
// GetToList unmarshals json found at key and opaque it into *List api object
|
||||
// (an object that satisfies the runtime.IsList definition).
|
||||
// The returned contents may be delayed according to the semantics of ListOptions.ResourceVersion.
|
||||
// The returned contents may be delayed, but it is guaranteed that they will
|
||||
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
||||
GetToList(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error
|
||||
|
||||
// List unmarshalls jsons found at directory defined by key and opaque them
|
||||
// into *List api object (an object that satisfies runtime.IsList definition).
|
||||
// The returned contents may be delayed according to the semantics of ListOptions.ResourceVersion.
|
||||
// The returned contents may be delayed, but it is guaranteed that they will
|
||||
// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'.
|
||||
List(ctx context.Context, key string, opts ListOptions, listObj runtime.Object) error
|
||||
|
||||
// GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'ptrToType')
|
||||
|
@ -260,6 +264,9 @@ type ListOptions struct {
|
|||
// ResourceVersion. The newest available data is preferred, but any data not older than this
|
||||
// ResourceVersion may be served.
|
||||
ResourceVersion string
|
||||
// ResourceVersionMatch provides the rule for how the resource version constraint applies. If set
|
||||
// to the default value "" the legacy resource version semantic apply.
|
||||
ResourceVersionMatch metav1.ResourceVersionMatch
|
||||
// Predicate provides the selection rules for the list operation.
|
||||
Predicate SelectionPredicate
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue