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 a23b9cdb400..6fb9fe9aa5f 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go @@ -634,7 +634,14 @@ func (s *store) Stats(ctx context.Context) (stats storage.Stats, err error) { return s.stats.Stats(ctx) } startTime := time.Now() - count, err := s.client.Kubernetes.Count(ctx, s.pathPrefix, kubernetes.CountOptions{}) + prefix, err := s.prepareKey(s.resourcePrefix) + if err != nil { + return storage.Stats{}, err + } + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + count, err := s.client.Kubernetes.Count(ctx, prefix, kubernetes.CountOptions{}) metrics.RecordEtcdRequest("listWithCount", s.groupResource, err, startTime) if err != nil { return storage.Stats{}, err @@ -652,7 +659,14 @@ func (s *store) SetKeysFunc(keys storage.KeysFunc) { func (s *store) getKeys(ctx context.Context) ([]string, error) { startTime := time.Now() - resp, err := s.client.KV.Get(ctx, s.pathPrefix, clientv3.WithPrefix(), clientv3.WithKeysOnly()) + prefix, err := s.prepareKey(s.resourcePrefix) + if err != nil { + return nil, err + } + if !strings.HasSuffix(prefix, "/") { + prefix += "/" + } + resp, err := s.client.KV.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithKeysOnly()) metrics.RecordEtcdRequest("listOnlyKeys", s.groupResource, err, startTime) if err != nil { return nil, err 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 72e361c317a..5ae973fcb22 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 @@ -28,6 +28,7 @@ import ( "time" "github.com/go-logr/logr" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" clientv3 "go.etcd.io/etcd/client/v3" "go.etcd.io/etcd/client/v3/kubernetes" @@ -588,6 +589,12 @@ func withPrefix(prefix string) setupOption { } } +func withResourcePrefix(prefix string) setupOption { + return func(options *setupOptions) { + options.resourcePrefix = prefix + } +} + func withLeaseConfig(leaseConfig LeaseManagerConfig) setupOption { return func(options *setupOptions) { options.leaseConfig = leaseConfig @@ -996,3 +1003,94 @@ func BenchmarkStatsCacheCleanKeys(b *testing.B) { b.Fatalf("Unexpected number of keys in stats, want: %d, got: %d", namespaceCount*podPerNamespaceCount, len(store.stats.keys)) } } + +func TestPrefixGetKeys(t *testing.T) { + ctx, store, c := testSetup(t, withPrefix("/registry"), withResourcePrefix("pods")) + _, err := c.KV.Put(ctx, "key", "a") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/key", "b") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/pods/key", "c") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/podskey", "d") + if err != nil { + t.Fatal(err) + } + + gotKeys, err := store.getKeys(ctx) + if err != nil { + t.Fatal(err) + } + + wantKeys := []string{"/registry/pods/key"} + if diff := cmp.Diff(wantKeys, gotKeys); diff != "" { + t.Errorf("getKeys diff:\n%s", diff) + } +} + +func TestPrefixStats(t *testing.T) { + tcs := []struct { + name string + estimate bool + expectStats storage.Stats + }{ + { + name: "SizeBasedListCostEstimate=false", + estimate: false, + expectStats: storage.Stats{ObjectCount: 1}, + }, + { + name: "SizeBasedListCostEstimate=true", + estimate: true, + expectStats: storage.Stats{ObjectCount: 1, EstimatedAverageObjectSizeBytes: 3}, + }, + } + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SizeBasedListCostEstimate, tc.estimate) + ctx, store, c := testSetup(t, withPrefix("/registry"), withResourcePrefix("pods")) + _, err := c.KV.Put(ctx, "key", "a") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/key", "ab") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/pods/key", "abc") + if err != nil { + t.Fatal(err) + } + + _, err = c.KV.Put(ctx, "/registry/podskey", "abcd") + if err != nil { + t.Fatal(err) + } + + listOut := &example.PodList{} + // Ignore error as decode is expected to fail + _ = store.GetList(ctx, "pods", storage.ListOptions{Predicate: storage.Everything, Recursive: true}, listOut) + + gotStats, err := store.Stats(ctx) + if err != nil { + t.Fatal(err) + } + + if diff := cmp.Diff(tc.expectStats, gotStats); diff != "" { + t.Errorf("Stats diff:\n%s", diff) + } + + }) + } +}