k8s.io/apiserver@v0.29.3/pkg/storage/etcd3/store_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package etcd3
    18  
    19  import (
    20  	"context"
    21  	"crypto/rand"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"reflect"
    26  	"strings"
    27  	"sync/atomic"
    28  	"testing"
    29  
    30  	clientv3 "go.etcd.io/etcd/client/v3"
    31  	"go.etcd.io/etcd/server/v3/embed"
    32  	"google.golang.org/grpc/grpclog"
    33  
    34  	"k8s.io/apimachinery/pkg/api/apitesting"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/fields"
    37  	"k8s.io/apimachinery/pkg/labels"
    38  	"k8s.io/apimachinery/pkg/runtime"
    39  	"k8s.io/apimachinery/pkg/runtime/schema"
    40  	"k8s.io/apimachinery/pkg/runtime/serializer"
    41  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    42  	"k8s.io/apiserver/pkg/apis/example"
    43  	examplev1 "k8s.io/apiserver/pkg/apis/example/v1"
    44  	"k8s.io/apiserver/pkg/storage"
    45  	"k8s.io/apiserver/pkg/storage/etcd3/testserver"
    46  	storagetesting "k8s.io/apiserver/pkg/storage/testing"
    47  	"k8s.io/apiserver/pkg/storage/value"
    48  )
    49  
    50  var scheme = runtime.NewScheme()
    51  var codecs = serializer.NewCodecFactory(scheme)
    52  
    53  const defaultTestPrefix = "test!"
    54  
    55  func init() {
    56  	metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
    57  	utilruntime.Must(example.AddToScheme(scheme))
    58  	utilruntime.Must(examplev1.AddToScheme(scheme))
    59  
    60  	grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, os.Stderr))
    61  }
    62  
    63  func newPod() runtime.Object {
    64  	return &example.Pod{}
    65  }
    66  
    67  func newPodList() runtime.Object {
    68  	return &example.PodList{}
    69  }
    70  
    71  func checkStorageInvariants(etcdClient *clientv3.Client, codec runtime.Codec) storagetesting.KeyValidation {
    72  	return func(ctx context.Context, t *testing.T, key string) {
    73  		getResp, err := etcdClient.KV.Get(ctx, key)
    74  		if err != nil {
    75  			t.Fatalf("etcdClient.KV.Get failed: %v", err)
    76  		}
    77  		if len(getResp.Kvs) == 0 {
    78  			t.Fatalf("expecting non empty result on key: %s", key)
    79  		}
    80  		decoded, err := runtime.Decode(codec, getResp.Kvs[0].Value[len(defaultTestPrefix):])
    81  		if err != nil {
    82  			t.Fatalf("expecting successful decode of object from %v\n%v", err, string(getResp.Kvs[0].Value))
    83  		}
    84  		obj := decoded.(*example.Pod)
    85  		if obj.ResourceVersion != "" {
    86  			t.Errorf("stored object should have empty resource version")
    87  		}
    88  		if obj.SelfLink != "" {
    89  			t.Errorf("stored output should have empty selfLink")
    90  		}
    91  	}
    92  }
    93  
    94  func TestCreate(t *testing.T) {
    95  	ctx, store, etcdClient := testSetup(t)
    96  	storagetesting.RunTestCreate(ctx, t, store, checkStorageInvariants(etcdClient, store.codec))
    97  }
    98  
    99  func TestCreateWithTTL(t *testing.T) {
   100  	ctx, store, _ := testSetup(t)
   101  	storagetesting.RunTestCreateWithTTL(ctx, t, store)
   102  }
   103  
   104  func TestCreateWithKeyExist(t *testing.T) {
   105  	ctx, store, _ := testSetup(t)
   106  	storagetesting.RunTestCreateWithKeyExist(ctx, t, store)
   107  }
   108  
   109  func TestGet(t *testing.T) {
   110  	ctx, store, _ := testSetup(t)
   111  	storagetesting.RunTestGet(ctx, t, store)
   112  }
   113  
   114  func TestUnconditionalDelete(t *testing.T) {
   115  	ctx, store, _ := testSetup(t)
   116  	storagetesting.RunTestUnconditionalDelete(ctx, t, store)
   117  }
   118  
   119  func TestConditionalDelete(t *testing.T) {
   120  	ctx, store, _ := testSetup(t)
   121  	storagetesting.RunTestConditionalDelete(ctx, t, store)
   122  }
   123  
   124  func TestDeleteWithSuggestion(t *testing.T) {
   125  	ctx, store, _ := testSetup(t)
   126  	storagetesting.RunTestDeleteWithSuggestion(ctx, t, store)
   127  }
   128  
   129  func TestDeleteWithSuggestionAndConflict(t *testing.T) {
   130  	ctx, store, _ := testSetup(t)
   131  	storagetesting.RunTestDeleteWithSuggestionAndConflict(ctx, t, store)
   132  }
   133  
   134  func TestDeleteWithSuggestionOfDeletedObject(t *testing.T) {
   135  	ctx, store, _ := testSetup(t)
   136  	storagetesting.RunTestDeleteWithSuggestionOfDeletedObject(ctx, t, store)
   137  }
   138  
   139  func TestValidateDeletionWithSuggestion(t *testing.T) {
   140  	ctx, store, _ := testSetup(t)
   141  	storagetesting.RunTestValidateDeletionWithSuggestion(ctx, t, store)
   142  }
   143  
   144  func TestValidateDeletionWithOnlySuggestionValid(t *testing.T) {
   145  	ctx, store, _ := testSetup(t)
   146  	storagetesting.RunTestValidateDeletionWithOnlySuggestionValid(ctx, t, store)
   147  }
   148  
   149  func TestDeleteWithConflict(t *testing.T) {
   150  	ctx, store, _ := testSetup(t)
   151  	storagetesting.RunTestDeleteWithConflict(ctx, t, store)
   152  }
   153  
   154  func TestPreconditionalDeleteWithSuggestion(t *testing.T) {
   155  	ctx, store, _ := testSetup(t)
   156  	storagetesting.RunTestPreconditionalDeleteWithSuggestion(ctx, t, store)
   157  }
   158  
   159  func TestPreconditionalDeleteWithSuggestionPass(t *testing.T) {
   160  	ctx, store, _ := testSetup(t)
   161  	storagetesting.RunTestPreconditionalDeleteWithOnlySuggestionPass(ctx, t, store)
   162  }
   163  
   164  func TestGetListNonRecursive(t *testing.T) {
   165  	ctx, store, _ := testSetup(t)
   166  	storagetesting.RunTestGetListNonRecursive(ctx, t, store)
   167  }
   168  
   169  type storeWithPrefixTransformer struct {
   170  	*store
   171  }
   172  
   173  func (s *storeWithPrefixTransformer) UpdatePrefixTransformer(modifier storagetesting.PrefixTransformerModifier) func() {
   174  	originalTransformer := s.transformer.(*storagetesting.PrefixTransformer)
   175  	transformer := *originalTransformer
   176  	s.transformer = modifier(&transformer)
   177  	s.watcher.transformer = modifier(&transformer)
   178  	return func() {
   179  		s.transformer = originalTransformer
   180  		s.watcher.transformer = originalTransformer
   181  	}
   182  }
   183  
   184  func TestGuaranteedUpdate(t *testing.T) {
   185  	ctx, store, etcdClient := testSetup(t)
   186  	storagetesting.RunTestGuaranteedUpdate(ctx, t, &storeWithPrefixTransformer{store}, checkStorageInvariants(etcdClient, store.codec))
   187  }
   188  
   189  func TestGuaranteedUpdateWithTTL(t *testing.T) {
   190  	ctx, store, _ := testSetup(t)
   191  	storagetesting.RunTestGuaranteedUpdateWithTTL(ctx, t, store)
   192  }
   193  
   194  func TestGuaranteedUpdateChecksStoredData(t *testing.T) {
   195  	ctx, store, _ := testSetup(t)
   196  	storagetesting.RunTestGuaranteedUpdateChecksStoredData(ctx, t, &storeWithPrefixTransformer{store})
   197  }
   198  
   199  func TestGuaranteedUpdateWithConflict(t *testing.T) {
   200  	ctx, store, _ := testSetup(t)
   201  	storagetesting.RunTestGuaranteedUpdateWithConflict(ctx, t, store)
   202  }
   203  
   204  func TestGuaranteedUpdateWithSuggestionAndConflict(t *testing.T) {
   205  	ctx, store, _ := testSetup(t)
   206  	storagetesting.RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx, t, store)
   207  }
   208  
   209  func TestTransformationFailure(t *testing.T) {
   210  	ctx, store, _ := testSetup(t)
   211  	storagetesting.RunTestTransformationFailure(ctx, t, &storeWithPrefixTransformer{store})
   212  }
   213  
   214  func TestList(t *testing.T) {
   215  	ctx, store, client := testSetup(t)
   216  	storagetesting.RunTestList(ctx, t, store, compactStorage(client), false)
   217  }
   218  
   219  func checkStorageCallsInvariants(transformer *storagetesting.PrefixTransformer, recorder *clientRecorder) storagetesting.CallsValidation {
   220  	return func(t *testing.T, pageSize, estimatedProcessedObjects uint64) {
   221  		if reads := transformer.GetReadsAndReset(); reads != estimatedProcessedObjects {
   222  			t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects)
   223  		}
   224  		estimatedGetCalls := uint64(1)
   225  		if pageSize != 0 {
   226  			// We expect that kube-apiserver will be increasing page sizes
   227  			// if not full pages are received, so we should see significantly less
   228  			// than 1000 pages (which would be result of talking to etcd with page size
   229  			// copied from pred.Limit).
   230  			// The expected number of calls is n+1 where n is the smallest n so that:
   231  			// pageSize + pageSize * 2 + pageSize * 4 + ... + pageSize * 2^n >= podCount.
   232  			// For pageSize = 1, podCount = 1000, we get n+1 = 10, 2 ^ 10 = 1024.
   233  			currLimit := pageSize
   234  			for sum := uint64(1); sum < estimatedProcessedObjects; {
   235  				currLimit *= 2
   236  				if currLimit > maxLimit {
   237  					currLimit = maxLimit
   238  				}
   239  				sum += currLimit
   240  				estimatedGetCalls++
   241  			}
   242  		}
   243  		if reads := recorder.GetReadsAndReset(); reads != estimatedGetCalls {
   244  			t.Errorf("unexpected reads: %d", reads)
   245  		}
   246  	}
   247  }
   248  
   249  func TestListContinuation(t *testing.T) {
   250  	ctx, store, etcdClient := testSetup(t, withRecorder())
   251  	validation := checkStorageCallsInvariants(
   252  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   253  	storagetesting.RunTestListContinuation(ctx, t, store, validation)
   254  }
   255  
   256  func TestListPaginationRareObject(t *testing.T) {
   257  	ctx, store, etcdClient := testSetup(t, withRecorder())
   258  	validation := checkStorageCallsInvariants(
   259  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   260  	storagetesting.RunTestListPaginationRareObject(ctx, t, store, validation)
   261  }
   262  
   263  func TestListContinuationWithFilter(t *testing.T) {
   264  	ctx, store, etcdClient := testSetup(t, withRecorder())
   265  	validation := checkStorageCallsInvariants(
   266  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   267  	storagetesting.RunTestListContinuationWithFilter(ctx, t, store, validation)
   268  }
   269  
   270  func compactStorage(etcdClient *clientv3.Client) storagetesting.Compaction {
   271  	return func(ctx context.Context, t *testing.T, resourceVersion string) {
   272  		versioner := storage.APIObjectVersioner{}
   273  		rv, err := versioner.ParseResourceVersion(resourceVersion)
   274  		if err != nil {
   275  			t.Fatal(err)
   276  		}
   277  		if _, _, err = compact(ctx, etcdClient, 0, int64(rv)); err != nil {
   278  			t.Fatalf("Unable to compact, %v", err)
   279  		}
   280  	}
   281  }
   282  
   283  func TestListInconsistentContinuation(t *testing.T) {
   284  	ctx, store, client := testSetup(t)
   285  	storagetesting.RunTestListInconsistentContinuation(ctx, t, store, compactStorage(client))
   286  }
   287  
   288  func TestConsistentList(t *testing.T) {
   289  	ctx, store, _ := testSetup(t)
   290  	storagetesting.RunTestConsistentList(ctx, t, &storeWithPrefixTransformer{store})
   291  }
   292  
   293  func TestCount(t *testing.T) {
   294  	ctx, store, _ := testSetup(t)
   295  	storagetesting.RunTestCount(ctx, t, store)
   296  }
   297  
   298  // =======================================================================
   299  // Implementation-specific tests are following.
   300  // The following tests are exercising the details of the implementation
   301  // not the actual user-facing contract of storage interface.
   302  // As such, they may focus e.g. on non-functional aspects like performance
   303  // impact.
   304  // =======================================================================
   305  
   306  func TestPrefix(t *testing.T) {
   307  	testcases := map[string]string{
   308  		"custom/prefix":     "/custom/prefix/",
   309  		"/custom//prefix//": "/custom/prefix/",
   310  		"/registry":         "/registry/",
   311  	}
   312  	for configuredPrefix, effectivePrefix := range testcases {
   313  		_, store, _ := testSetup(t, withPrefix(configuredPrefix))
   314  		if store.pathPrefix != effectivePrefix {
   315  			t.Errorf("configured prefix of %s, expected effective prefix of %s, got %s", configuredPrefix, effectivePrefix, store.pathPrefix)
   316  		}
   317  	}
   318  }
   319  
   320  func Test_growSlice(t *testing.T) {
   321  	type args struct {
   322  		initialCapacity int
   323  		initialLen      int
   324  		v               reflect.Value
   325  		maxCapacity     int
   326  		sizes           []int
   327  	}
   328  	tests := []struct {
   329  		name string
   330  		args args
   331  		cap  int
   332  		len  int
   333  	}{
   334  		{
   335  			name: "empty",
   336  			args: args{v: reflect.ValueOf([]example.Pod{})},
   337  			cap:  0,
   338  		},
   339  		{
   340  			name: "no sizes",
   341  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10},
   342  			cap:  10,
   343  		},
   344  		{
   345  			name: "above maxCapacity",
   346  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{1, 12}},
   347  			cap:  10,
   348  		},
   349  		{
   350  			name: "takes max",
   351  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{8, 4}},
   352  			cap:  8,
   353  		},
   354  		{
   355  			name: "with existing capacity above max",
   356  			args: args{initialCapacity: 12, maxCapacity: 10, sizes: []int{8, 4}},
   357  			cap:  12,
   358  		},
   359  		{
   360  			name: "with existing capacity below max",
   361  			args: args{initialCapacity: 5, maxCapacity: 10, sizes: []int{8, 4}},
   362  			cap:  8,
   363  		},
   364  		{
   365  			name: "with existing capacity and length above max",
   366  			args: args{initialCapacity: 12, initialLen: 5, maxCapacity: 10, sizes: []int{8, 4}},
   367  			cap:  12,
   368  			len:  5,
   369  		},
   370  		{
   371  			name: "with existing capacity and length below max",
   372  			args: args{initialCapacity: 5, initialLen: 3, maxCapacity: 10, sizes: []int{8, 4}},
   373  			cap:  8,
   374  			len:  3,
   375  		},
   376  	}
   377  	for _, tt := range tests {
   378  		t.Run(tt.name, func(t *testing.T) {
   379  			if tt.args.initialCapacity > 0 {
   380  				val := make([]example.Pod, tt.args.initialLen, tt.args.initialCapacity)
   381  				for i := 0; i < tt.args.initialLen; i++ {
   382  					val[i].Name = fmt.Sprintf("test-%d", i)
   383  				}
   384  				tt.args.v = reflect.ValueOf(val)
   385  			}
   386  			// reflection requires that the value be addressable in order to call set,
   387  			// so we must ensure the value we created is available on the heap (not a problem
   388  			// for normal usage)
   389  			if !tt.args.v.CanAddr() {
   390  				x := reflect.New(tt.args.v.Type())
   391  				x.Elem().Set(tt.args.v)
   392  				tt.args.v = x.Elem()
   393  			}
   394  			growSlice(tt.args.v, tt.args.maxCapacity, tt.args.sizes...)
   395  			if tt.cap != tt.args.v.Cap() {
   396  				t.Errorf("Unexpected capacity: got=%d want=%d", tt.args.v.Cap(), tt.cap)
   397  			}
   398  			if tt.len != tt.args.v.Len() {
   399  				t.Errorf("Unexpected length: got=%d want=%d", tt.args.v.Len(), tt.len)
   400  			}
   401  			for i := 0; i < tt.args.v.Len(); i++ {
   402  				nameWanted := fmt.Sprintf("test-%d", i)
   403  				val := tt.args.v.Index(i).Interface()
   404  				pod, ok := val.(example.Pod)
   405  				if !ok || pod.Name != nameWanted {
   406  					t.Errorf("Unexpected element value: got=%s, want=%s", pod.Name, nameWanted)
   407  				}
   408  			}
   409  		})
   410  	}
   411  }
   412  
   413  func TestLeaseMaxObjectCount(t *testing.T) {
   414  	ctx, store, _ := testSetup(t, withLeaseConfig(LeaseManagerConfig{
   415  		ReuseDurationSeconds: defaultLeaseReuseDurationSeconds,
   416  		MaxObjectCount:       2,
   417  	}))
   418  
   419  	obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
   420  	out := &example.Pod{}
   421  
   422  	testCases := []struct {
   423  		key                 string
   424  		expectAttachedCount int64
   425  	}{
   426  		{
   427  			key:                 "testkey1",
   428  			expectAttachedCount: 1,
   429  		},
   430  		{
   431  			key:                 "testkey2",
   432  			expectAttachedCount: 2,
   433  		},
   434  		{
   435  			key: "testkey3",
   436  			// We assume each time has 1 object attached to the lease
   437  			// so after granting a new lease, the recorded count is set to 1
   438  			expectAttachedCount: 1,
   439  		},
   440  	}
   441  
   442  	for _, tc := range testCases {
   443  		err := store.Create(ctx, tc.key, obj, out, 120)
   444  		if err != nil {
   445  			t.Fatalf("Set failed: %v", err)
   446  		}
   447  		if store.leaseManager.leaseAttachedObjectCount != tc.expectAttachedCount {
   448  			t.Errorf("Lease manager recorded count %v should be %v", store.leaseManager.leaseAttachedObjectCount, tc.expectAttachedCount)
   449  		}
   450  	}
   451  }
   452  
   453  // ===================================================
   454  // Test-setup related function are following.
   455  // ===================================================
   456  
   457  func newTestLeaseManagerConfig() LeaseManagerConfig {
   458  	cfg := NewDefaultLeaseManagerConfig()
   459  	// As 30s is the default timeout for testing in global configuration,
   460  	// we cannot wait longer than that in a single time: change it to 1s
   461  	// for testing purposes. See wait.ForeverTestTimeout
   462  	cfg.ReuseDurationSeconds = 1
   463  	return cfg
   464  }
   465  
   466  func newTestTransformer() value.Transformer {
   467  	return storagetesting.NewPrefixTransformer([]byte(defaultTestPrefix), false)
   468  }
   469  
   470  type clientRecorder struct {
   471  	reads uint64
   472  	clientv3.KV
   473  }
   474  
   475  func (r *clientRecorder) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
   476  	atomic.AddUint64(&r.reads, 1)
   477  	return r.KV.Get(ctx, key, opts...)
   478  }
   479  
   480  func (r *clientRecorder) GetReadsAndReset() uint64 {
   481  	return atomic.SwapUint64(&r.reads, 0)
   482  }
   483  
   484  type setupOptions struct {
   485  	client         func(testing.TB) *clientv3.Client
   486  	codec          runtime.Codec
   487  	newFunc        func() runtime.Object
   488  	newListFunc    func() runtime.Object
   489  	prefix         string
   490  	resourcePrefix string
   491  	groupResource  schema.GroupResource
   492  	transformer    value.Transformer
   493  	leaseConfig    LeaseManagerConfig
   494  
   495  	recorderEnabled bool
   496  }
   497  
   498  type setupOption func(*setupOptions)
   499  
   500  func withClientConfig(config *embed.Config) setupOption {
   501  	return func(options *setupOptions) {
   502  		options.client = func(t testing.TB) *clientv3.Client {
   503  			return testserver.RunEtcd(t, config)
   504  		}
   505  	}
   506  }
   507  
   508  func withPrefix(prefix string) setupOption {
   509  	return func(options *setupOptions) {
   510  		options.prefix = prefix
   511  	}
   512  }
   513  
   514  func withLeaseConfig(leaseConfig LeaseManagerConfig) setupOption {
   515  	return func(options *setupOptions) {
   516  		options.leaseConfig = leaseConfig
   517  	}
   518  }
   519  
   520  func withRecorder() setupOption {
   521  	return func(options *setupOptions) {
   522  		options.recorderEnabled = true
   523  	}
   524  }
   525  
   526  func withDefaults(options *setupOptions) {
   527  	options.client = func(t testing.TB) *clientv3.Client {
   528  		return testserver.RunEtcd(t, nil)
   529  	}
   530  	options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
   531  	options.newFunc = newPod
   532  	options.newListFunc = newPodList
   533  	options.prefix = ""
   534  	options.resourcePrefix = "/pods"
   535  	options.groupResource = schema.GroupResource{Resource: "pods"}
   536  	options.transformer = newTestTransformer()
   537  	options.leaseConfig = newTestLeaseManagerConfig()
   538  }
   539  
   540  var _ setupOption = withDefaults
   541  
   542  func testSetup(t testing.TB, opts ...setupOption) (context.Context, *store, *clientv3.Client) {
   543  	setupOpts := setupOptions{}
   544  	opts = append([]setupOption{withDefaults}, opts...)
   545  	for _, opt := range opts {
   546  		opt(&setupOpts)
   547  	}
   548  	client := setupOpts.client(t)
   549  	if setupOpts.recorderEnabled {
   550  		client.KV = &clientRecorder{KV: client.KV}
   551  	}
   552  	store := newStore(
   553  		client,
   554  		setupOpts.codec,
   555  		setupOpts.newFunc,
   556  		setupOpts.newListFunc,
   557  		setupOpts.prefix,
   558  		setupOpts.resourcePrefix,
   559  		setupOpts.groupResource,
   560  		setupOpts.transformer,
   561  		setupOpts.leaseConfig,
   562  	)
   563  	ctx := context.Background()
   564  	return ctx, store, client
   565  }
   566  
   567  func TestValidateKey(t *testing.T) {
   568  	validKeys := []string{
   569  		"/foo/bar/baz/a.b.c/",
   570  		"/foo",
   571  		"foo/bar/baz",
   572  		"/foo/bar..baz/",
   573  		"/foo/bar..",
   574  		"foo",
   575  		"foo/bar",
   576  		"/foo/bar/",
   577  	}
   578  	invalidKeys := []string{
   579  		"/foo/bar/../a.b.c/",
   580  		"..",
   581  		"/..",
   582  		"../",
   583  		"/foo/bar/..",
   584  		"../foo/bar",
   585  		"/../foo",
   586  		"/foo/bar/../",
   587  		".",
   588  		"/.",
   589  		"./",
   590  		"/./",
   591  		"/foo/.",
   592  		"./bar",
   593  		"/foo/./bar/",
   594  	}
   595  	const (
   596  		pathPrefix   = "/first/second"
   597  		expectPrefix = pathPrefix + "/"
   598  	)
   599  	_, store, _ := testSetup(t, withPrefix(pathPrefix))
   600  
   601  	for _, key := range validKeys {
   602  		k, err := store.prepareKey(key)
   603  		if err != nil {
   604  			t.Errorf("key %q should be valid; unexpected error: %v", key, err)
   605  		} else if !strings.HasPrefix(k, expectPrefix) {
   606  			t.Errorf("key %q should have prefix %q", k, expectPrefix)
   607  		}
   608  	}
   609  
   610  	for _, key := range invalidKeys {
   611  		_, err := store.prepareKey(key)
   612  		if err == nil {
   613  			t.Errorf("key %q should be invalid", key)
   614  		}
   615  	}
   616  }
   617  
   618  func TestInvalidKeys(t *testing.T) {
   619  	const invalidKey = "/foo/bar/../baz"
   620  	expectedError := fmt.Sprintf("invalid key: %q", invalidKey)
   621  
   622  	expectInvalidKey := func(methodName string, err error) {
   623  		if err == nil {
   624  			t.Errorf("[%s] expected invalid key error; got nil", methodName)
   625  		} else if err.Error() != expectedError {
   626  			t.Errorf("[%s] expected invalid key error; got %v", methodName, err)
   627  		}
   628  	}
   629  
   630  	ctx, store, _ := testSetup(t)
   631  	expectInvalidKey("Create", store.Create(ctx, invalidKey, nil, nil, 0))
   632  	expectInvalidKey("Delete", store.Delete(ctx, invalidKey, nil, nil, nil, nil))
   633  	_, watchErr := store.Watch(ctx, invalidKey, storage.ListOptions{})
   634  	expectInvalidKey("Watch", watchErr)
   635  	expectInvalidKey("Get", store.Get(ctx, invalidKey, storage.GetOptions{}, nil))
   636  	expectInvalidKey("GetList", store.GetList(ctx, invalidKey, storage.ListOptions{}, nil))
   637  	expectInvalidKey("GuaranteedUpdate", store.GuaranteedUpdate(ctx, invalidKey, nil, true, nil, nil, nil))
   638  	_, countErr := store.Count(invalidKey)
   639  	expectInvalidKey("Count", countErr)
   640  }
   641  
   642  func BenchmarkStore_GetList(b *testing.B) {
   643  	generateBigPod := func(index int, total int, expect int) runtime.Object {
   644  		l := map[string]string{}
   645  		if index%(total/expect) == 0 {
   646  			l["foo"] = "bar"
   647  		}
   648  		terminationGracePeriodSeconds := int64(42)
   649  		activeDeadlineSeconds := int64(42)
   650  		pod := &examplev1.Pod{
   651  			ObjectMeta: metav1.ObjectMeta{
   652  				Labels: l,
   653  			},
   654  			Spec: examplev1.PodSpec{
   655  				RestartPolicy:                 examplev1.RestartPolicy("Always"),
   656  				TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
   657  				ActiveDeadlineSeconds:         &activeDeadlineSeconds,
   658  				NodeSelector:                  map[string]string{},
   659  				ServiceAccountName:            "demo-sa",
   660  			},
   661  		}
   662  		pod.Name = fmt.Sprintf("object-%d", index)
   663  		data := make([]byte, 1024*2, 1024*2) // 2k labels
   664  		rand.Read(data)
   665  		pod.Spec.NodeSelector["key"] = string(data)
   666  		return pod
   667  	}
   668  	testCases := []struct {
   669  		name              string
   670  		objectNum         int
   671  		expectNum         int
   672  		selector          labels.Selector
   673  		newObjectFunc     func(index int, total int, expect int) runtime.Object
   674  		newListObjectFunc func() runtime.Object
   675  	}{
   676  		{
   677  			name:              "pick 50 pods out of 5000 pod",
   678  			objectNum:         5000,
   679  			expectNum:         50,
   680  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   681  			newObjectFunc:     generateBigPod,
   682  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   683  		},
   684  		{
   685  			name:              "pick 500 pods out of 5000 pod",
   686  			objectNum:         5000,
   687  			expectNum:         500,
   688  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   689  			newObjectFunc:     generateBigPod,
   690  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   691  		},
   692  		{
   693  			name:              "pick 1000 pods out of 5000 pod",
   694  			objectNum:         5000,
   695  			expectNum:         1000,
   696  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   697  			newObjectFunc:     generateBigPod,
   698  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   699  		},
   700  		{
   701  			name:              "pick 2500 pods out of 5000 pod",
   702  			objectNum:         5000,
   703  			expectNum:         2500,
   704  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   705  			newObjectFunc:     generateBigPod,
   706  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   707  		},
   708  		{
   709  			name:              "pick 5000 pods out of 5000 pod",
   710  			objectNum:         5000,
   711  			expectNum:         5000,
   712  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   713  			newObjectFunc:     generateBigPod,
   714  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   715  		},
   716  	}
   717  	for _, tc := range testCases {
   718  		b.Run(tc.name, func(b *testing.B) {
   719  			// booting etcd instance
   720  			ctx, store, etcdClient := testSetup(b)
   721  			defer etcdClient.Close()
   722  
   723  			// make fake objects..
   724  			dir := "/testing"
   725  			originalRevision := ""
   726  			for i := 0; i < tc.objectNum; i++ {
   727  				obj := tc.newObjectFunc(i, tc.objectNum, tc.expectNum)
   728  				o := obj.(metav1.Object)
   729  				key := fmt.Sprintf("/testing/testkey/%s", o.GetName())
   730  				out := tc.newObjectFunc(i, tc.objectNum, tc.expectNum)
   731  				if err := store.Create(ctx, key, obj, out, 0); err != nil {
   732  					b.Fatalf("Set failed: %v", err)
   733  				}
   734  				originalRevision = out.(metav1.Object).GetResourceVersion()
   735  			}
   736  
   737  			// prepare result and pred
   738  			pred := storage.SelectionPredicate{
   739  				Label: tc.selector,
   740  				Field: fields.Everything(),
   741  				GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
   742  					pod, ok := obj.(*examplev1.Pod)
   743  					if !ok {
   744  						return nil, nil, fmt.Errorf("not a pod")
   745  					}
   746  					return pod.ObjectMeta.Labels, fields.Set{
   747  						"metadata.name": pod.Name,
   748  					}, nil
   749  				},
   750  			}
   751  
   752  			// now we start benchmarking
   753  			b.ResetTimer()
   754  			for i := 0; i < b.N; i++ {
   755  				list := tc.newListObjectFunc()
   756  				if err := store.GetList(ctx, dir, storage.ListOptions{Predicate: pred, Recursive: true}, list); err != nil {
   757  					b.Errorf("Unexpected List error: %v", err)
   758  				}
   759  				listObject := list.(*examplev1.PodList)
   760  				if originalRevision != listObject.GetResourceVersion() {
   761  					b.Fatalf("original revision (%s) did not match final revision after linearized reads (%s)", originalRevision, listObject.GetResourceVersion())
   762  				}
   763  				if len(listObject.Items) != tc.expectNum {
   764  					b.Fatalf("expect (%d) items but got (%d)", tc.expectNum, len(listObject.Items))
   765  				}
   766  			}
   767  		})
   768  	}
   769  }