diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go index 75a6c3b58b88d..f97bc3833e767 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go @@ -819,7 +819,7 @@ func growSlice(v reflect.Value, maxCapacity int, sizes ...int) { return } if v.Len() > 0 { - extra := reflect.MakeSlice(v.Type(), 0, max) + extra := reflect.MakeSlice(v.Type(), v.Len(), max) reflect.Copy(extra, v) v.Set(extra) } else { diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go index fedbcce3e41f9..6ace3f68bc85c 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go @@ -2240,6 +2240,7 @@ func Test_decodeContinue(t *testing.T) { func Test_growSlice(t *testing.T) { type args struct { initialCapacity int + initialLen int v reflect.Value maxCapacity int sizes []int @@ -2248,6 +2249,7 @@ func Test_growSlice(t *testing.T) { name string args args cap int + len int }{ { name: "empty", @@ -2279,13 +2281,29 @@ func Test_growSlice(t *testing.T) { args: args{initialCapacity: 5, maxCapacity: 10, sizes: []int{8, 4}}, cap: 8, }, + { + name: "with existing capacity and length above max", + args: args{initialCapacity: 12, initialLen: 5, maxCapacity: 10, sizes: []int{8, 4}}, + cap: 12, + len: 5, + }, + { + name: "with existing capacity and length below max", + args: args{initialCapacity: 5, initialLen: 3, maxCapacity: 10, sizes: []int{8, 4}}, + cap: 8, + len: 3, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.args.initialCapacity > 0 { - tt.args.v = reflect.ValueOf(make([]example.Pod, 0, tt.args.initialCapacity)) + val := make([]example.Pod, tt.args.initialLen, tt.args.initialCapacity) + for i := 0; i < tt.args.initialLen; i++ { + val[i].Name = fmt.Sprintf("test-%d", i) + } + tt.args.v = reflect.ValueOf(val) } - // reflection requires that the value be addressible in order to call set, + // reflection requires that the value be addressable in order to call set, // so we must ensure the value we created is available on the heap (not a problem // for normal usage) if !tt.args.v.CanAddr() { @@ -2297,6 +2315,17 @@ func Test_growSlice(t *testing.T) { if tt.cap != tt.args.v.Cap() { t.Errorf("Unexpected capacity: got=%d want=%d", tt.args.v.Cap(), tt.cap) } + if tt.len != tt.args.v.Len() { + t.Errorf("Unexpected length: got=%d want=%d", tt.args.v.Len(), tt.len) + } + for i := 0; i < tt.args.v.Len(); i++ { + nameWanted := fmt.Sprintf("test-%d", i) + val := tt.args.v.Index(i).Interface() + pod, ok := val.(example.Pod) + if !ok || pod.Name != nameWanted { + t.Errorf("Unexpected element value: got=%s, want=%s", pod.Name, nameWanted) + } + } }) } } diff --git a/test/integration/apiserver/apiserver_test.go b/test/integration/apiserver/apiserver_test.go index 37b7ffed5d3dd..93ec187733c70 100644 --- a/test/integration/apiserver/apiserver_test.go +++ b/test/integration/apiserver/apiserver_test.go @@ -731,6 +731,69 @@ func TestAPIListChunking(t *testing.T) { } } +func TestAPIListChunkingWithLabelSelector(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIListChunking, true)() + s, clientSet, closeFn := setup(t) + defer closeFn() + + ns := framework.CreateTestingNamespace("list-paging-with-label-selector", s, t) + defer framework.DeleteTestingNamespace(ns, s, t) + + rsClient := clientSet.AppsV1().ReplicaSets(ns.Name) + + for i := 0; i < 10; i++ { + rs := newRS(ns.Name) + rs.Name = fmt.Sprintf("test-%d", i) + odd := i%2 != 0 + rs.Labels = map[string]string{"odd-index": strconv.FormatBool(odd)} + if _, err := rsClient.Create(context.TODO(), rs, metav1.CreateOptions{}); err != nil { + t.Fatal(err) + } + } + + calls := 0 + firstRV := "" + p := &pager.ListPager{ + PageSize: 1, + PageFn: pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) { + calls++ + list, err := rsClient.List(context.TODO(), opts) + if err != nil { + return nil, err + } + if calls == 1 { + firstRV = list.ResourceVersion + } + return list, err + }), + } + listObj, _, err := p.List(context.Background(), metav1.ListOptions{LabelSelector: "odd-index=true", Limit: 3}) + if err != nil { + t.Fatal(err) + } + if calls != 2 { + t.Errorf("unexpected list invocations: %d", calls) + } + list := listObj.(metav1.ListInterface) + if len(list.GetContinue()) != 0 { + t.Errorf("unexpected continue: %s", list.GetContinue()) + } + if list.GetResourceVersion() != firstRV { + t.Errorf("unexpected resource version: %s instead of %s", list.GetResourceVersion(), firstRV) + } + var names []string + if err := meta.EachListItem(listObj, func(obj runtime.Object) error { + rs := obj.(*apps.ReplicaSet) + names = append(names, rs.Name) + return nil + }); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(names, []string{"test-1", "test-3", "test-5", "test-7", "test-9"}) { + t.Errorf("unexpected items: %#v", list) + } +} + func makeSecret(name string) *v1.Secret { return &v1.Secret{ ObjectMeta: metav1.ObjectMeta{