Protect remainingItemCount behind a feature flag. Also updating the API doc

This commit is contained in:
Chao Xu 2019-05-31 15:32:26 -07:00
parent 9577cbfb9f
commit 1ad5cb5bb1
5 changed files with 65 additions and 27 deletions

View File

@ -82,11 +82,18 @@ type ListMeta struct {
// message.
Continue string `json:"continue,omitempty" protobuf:"bytes,3,opt,name=continue"`
// RemainingItemCount is the number of subsequent items in the list which are not included in this
// remainingItemCount is the number of subsequent items in the list which are not included in this
// list response. If the list request contained label or field selectors, then the number of
// remaining items is unknown and this field will be unset. If the list is complete (either
// because it is unpaginated or because this is the last page), then there are no more remaining
// items and this field will also be unset. Servers older than v1.15 do not set this field.
// remaining items is unknown and the field will be left unset and omitted during serialization.
// If the list is complete (either because it is not chunking or because this is the last chunk),
// then there are no more remaining items and this field will be left unset and omitted during
// serialization.
// Servers older than v1.15 do not set this field.
// The intended use of the remainingItemCount is *estimating* the size of a collection. Clients
// should not rely on the remainingItemCount to be set or to be exact.
//
// This field is alpha and can be changed or removed without notice.
//
// +optional
RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"`
}

View File

@ -86,6 +86,13 @@ const (
// committing.
DryRun featuregate.Feature = "DryRun"
// owner: @caesarxuchao
// alpha: v1.15
//
// Allow apiservers to show a count of remaining items in the response
// to a chunking list request.
RemainingItemCount featuregate.Feature = "RemainingItemCount"
// owner: @apelisse, @lavalamp
// alpha: v1.14
//
@ -140,6 +147,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
APIResponseCompression: {Default: false, PreRelease: featuregate.Alpha},
APIListChunking: {Default: true, PreRelease: featuregate.Beta},
DryRun: {Default: true, PreRelease: featuregate.Beta},
RemainingItemCount: {Default: false, PreRelease: featuregate.Alpha},
ServerSideApply: {Default: false, PreRelease: featuregate.Alpha},
StorageVersionHash: {Default: false, PreRelease: featuregate.Alpha},
WinOverlay: {Default: false, PreRelease: featuregate.Alpha},

View File

@ -36,10 +36,12 @@ import (
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/etcd"
"k8s.io/apiserver/pkg/storage/etcd/metrics"
"k8s.io/apiserver/pkg/storage/value"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utiltrace "k8s.io/utils/trace"
)
@ -622,9 +624,11 @@ func (s *store) List(ctx context.Context, key, resourceVersion string, pred stor
// getResp.Count counts in objects that do not match the pred.
// Instead of returning inaccurate count for non-empty selectors, we return nil.
// Only set remainingItemCount if the predicate is empty.
if pred.Empty() {
c := int64(getResp.Count - pred.Limit)
remainingItemCount = &c
if utilfeature.DefaultFeatureGate.Enabled(features.RemainingItemCount) {
if pred.Empty() {
c := int64(getResp.Count - pred.Limit)
remainingItemCount = &c
}
}
return s.versioner.UpdateList(listObj, uint64(returnedRV), next, remainingItemCount)
}

View File

@ -43,10 +43,13 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/apis/example"
examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/etcd"
storagetests "k8s.io/apiserver/pkg/storage/tests"
"k8s.io/apiserver/pkg/storage/value"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
utilpointer "k8s.io/utils/pointer"
)
@ -751,6 +754,7 @@ func TestTransformationFailure(t *testing.T) {
}
func TestList(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.RemainingItemCount, true)()
codec := apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer cluster.Terminate(t)

View File

@ -30,12 +30,18 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage/storagebackend"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/test/e2e/framework"
e2elog "k8s.io/kubernetes/test/e2e/framework/log"
)
func shouldCheckRemainingItem() bool {
return utilfeature.DefaultFeatureGate.Enabled(features.RemainingItemCount)
}
const numberOfTotalResources = 400
var _ = SIGDescribe("Servers with support for API chunking", func() {
@ -89,11 +95,13 @@ var _ = SIGDescribe("Servers with support for API chunking", func() {
lastRV = list.ResourceVersion
}
gomega.Expect(list.ResourceVersion).To(gomega.Equal(lastRV))
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
if shouldCheckRemainingItem() {
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
}
}
for _, item := range list.Items {
gomega.Expect(item.Name).To(gomega.Equal(fmt.Sprintf("template-%04d", found)))
@ -127,11 +135,13 @@ var _ = SIGDescribe("Servers with support for API chunking", func() {
framework.ExpectNoError(err, "failed to list pod templates in namespace: %s, given limit: %d", ns, opts.Limit)
firstToken := list.Continue
firstRV := list.ResourceVersion
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items)).To(gomega.BeNumerically("==", numberOfTotalResources))
if shouldCheckRemainingItem() {
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items)).To(gomega.BeNumerically("==", numberOfTotalResources))
}
}
e2elog.Logf("Retrieved %d/%d results with rv %s and continue %s", len(list.Items), opts.Limit, list.ResourceVersion, firstToken)
@ -167,11 +177,14 @@ var _ = SIGDescribe("Servers with support for API chunking", func() {
gomega.Expect(list.ResourceVersion).ToNot(gomega.Equal(firstRV))
gomega.Expect(len(list.Items)).To(gomega.BeNumerically("==", opts.Limit))
found := int(oneTenth)
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
if shouldCheckRemainingItem() {
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
}
}
for _, item := range list.Items {
gomega.Expect(item.Name).To(gomega.Equal(fmt.Sprintf("template-%04d", found)))
@ -184,11 +197,13 @@ var _ = SIGDescribe("Servers with support for API chunking", func() {
for {
list, err := client.List(opts)
framework.ExpectNoError(err, "failed to list pod templates in namespace: %s, given limit: %d", ns, opts.Limit)
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
if shouldCheckRemainingItem() {
if list.GetContinue() == "" {
gomega.Expect(list.GetRemainingItemCount()).To(gomega.BeNil())
} else {
gomega.Expect(list.GetRemainingItemCount()).ToNot(gomega.BeNil())
gomega.Expect(int(*list.GetRemainingItemCount()) + len(list.Items) + found).To(gomega.BeNumerically("==", numberOfTotalResources))
}
}
e2elog.Logf("Retrieved %d/%d results with rv %s and continue %s", len(list.Items), opts.Limit, list.ResourceVersion, list.Continue)
gomega.Expect(len(list.Items)).To(gomega.BeNumerically("<=", opts.Limit))