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. // message.
Continue string `json:"continue,omitempty" protobuf:"bytes,3,opt,name=continue"` 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 // 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 // remaining items is unknown and the field will be left unset and omitted during serialization.
// because it is unpaginated or because this is the last page), then there are no more remaining // If the list is complete (either because it is not chunking or because this is the last chunk),
// items and this field will also be unset. Servers older than v1.15 do not set this field. // 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 // +optional
RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"` RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"`
} }

View File

@ -86,6 +86,13 @@ const (
// committing. // committing.
DryRun featuregate.Feature = "DryRun" 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 // owner: @apelisse, @lavalamp
// alpha: v1.14 // alpha: v1.14
// //
@ -140,6 +147,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
APIResponseCompression: {Default: false, PreRelease: featuregate.Alpha}, APIResponseCompression: {Default: false, PreRelease: featuregate.Alpha},
APIListChunking: {Default: true, PreRelease: featuregate.Beta}, APIListChunking: {Default: true, PreRelease: featuregate.Beta},
DryRun: {Default: true, PreRelease: featuregate.Beta}, DryRun: {Default: true, PreRelease: featuregate.Beta},
RemainingItemCount: {Default: false, PreRelease: featuregate.Alpha},
ServerSideApply: {Default: false, PreRelease: featuregate.Alpha}, ServerSideApply: {Default: false, PreRelease: featuregate.Alpha},
StorageVersionHash: {Default: false, PreRelease: featuregate.Alpha}, StorageVersionHash: {Default: false, PreRelease: featuregate.Alpha},
WinOverlay: {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/conversion"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/etcd" "k8s.io/apiserver/pkg/storage/etcd"
"k8s.io/apiserver/pkg/storage/etcd/metrics" "k8s.io/apiserver/pkg/storage/etcd/metrics"
"k8s.io/apiserver/pkg/storage/value" "k8s.io/apiserver/pkg/storage/value"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utiltrace "k8s.io/utils/trace" 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. // getResp.Count counts in objects that do not match the pred.
// Instead of returning inaccurate count for non-empty selectors, we return nil. // Instead of returning inaccurate count for non-empty selectors, we return nil.
// Only set remainingItemCount if the predicate is empty. // Only set remainingItemCount if the predicate is empty.
if pred.Empty() { if utilfeature.DefaultFeatureGate.Enabled(features.RemainingItemCount) {
c := int64(getResp.Count - pred.Limit) if pred.Empty() {
remainingItemCount = &c c := int64(getResp.Count - pred.Limit)
remainingItemCount = &c
}
} }
return s.versioner.UpdateList(listObj, uint64(returnedRV), next, remainingItemCount) return s.versioner.UpdateList(listObj, uint64(returnedRV), next, remainingItemCount)
} }

View File

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

View File

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