k8s.io/apiserver@v0.31.1/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, client := testSetup(t)
   166  	storagetesting.RunTestGetListNonRecursive(ctx, t, compactStorage(client), store)
   167  }
   168  
   169  func TestGetListRecursivePrefix(t *testing.T) {
   170  	ctx, store, _ := testSetup(t)
   171  	storagetesting.RunTestGetListRecursivePrefix(ctx, t, store)
   172  }
   173  
   174  type storeWithPrefixTransformer struct {
   175  	*store
   176  }
   177  
   178  func (s *storeWithPrefixTransformer) UpdatePrefixTransformer(modifier storagetesting.PrefixTransformerModifier) func() {
   179  	originalTransformer := s.transformer.(*storagetesting.PrefixTransformer)
   180  	transformer := *originalTransformer
   181  	s.transformer = modifier(&transformer)
   182  	s.watcher.transformer = modifier(&transformer)
   183  	return func() {
   184  		s.transformer = originalTransformer
   185  		s.watcher.transformer = originalTransformer
   186  	}
   187  }
   188  
   189  func TestGuaranteedUpdate(t *testing.T) {
   190  	ctx, store, etcdClient := testSetup(t)
   191  	storagetesting.RunTestGuaranteedUpdate(ctx, t, &storeWithPrefixTransformer{store}, checkStorageInvariants(etcdClient, store.codec))
   192  }
   193  
   194  func TestGuaranteedUpdateWithTTL(t *testing.T) {
   195  	ctx, store, _ := testSetup(t)
   196  	storagetesting.RunTestGuaranteedUpdateWithTTL(ctx, t, store)
   197  }
   198  
   199  func TestGuaranteedUpdateChecksStoredData(t *testing.T) {
   200  	ctx, store, _ := testSetup(t)
   201  	storagetesting.RunTestGuaranteedUpdateChecksStoredData(ctx, t, &storeWithPrefixTransformer{store})
   202  }
   203  
   204  func TestGuaranteedUpdateWithConflict(t *testing.T) {
   205  	ctx, store, _ := testSetup(t)
   206  	storagetesting.RunTestGuaranteedUpdateWithConflict(ctx, t, store)
   207  }
   208  
   209  func TestGuaranteedUpdateWithSuggestionAndConflict(t *testing.T) {
   210  	ctx, store, _ := testSetup(t)
   211  	storagetesting.RunTestGuaranteedUpdateWithSuggestionAndConflict(ctx, t, store)
   212  }
   213  
   214  func TestTransformationFailure(t *testing.T) {
   215  	ctx, store, _ := testSetup(t)
   216  	storagetesting.RunTestTransformationFailure(ctx, t, &storeWithPrefixTransformer{store})
   217  }
   218  
   219  func TestList(t *testing.T) {
   220  	ctx, store, client := testSetup(t)
   221  	storagetesting.RunTestList(ctx, t, store, compactStorage(client), false)
   222  }
   223  
   224  func TestConsistentList(t *testing.T) {
   225  	ctx, store, client := testSetup(t)
   226  	storagetesting.RunTestConsistentList(ctx, t, store, compactStorage(client), false, true)
   227  }
   228  
   229  func checkStorageCallsInvariants(transformer *storagetesting.PrefixTransformer, recorder *clientRecorder) storagetesting.CallsValidation {
   230  	return func(t *testing.T, pageSize, estimatedProcessedObjects uint64) {
   231  		if reads := transformer.GetReadsAndReset(); reads != estimatedProcessedObjects {
   232  			t.Errorf("unexpected reads: %d, expected: %d", reads, estimatedProcessedObjects)
   233  		}
   234  		estimatedGetCalls := uint64(1)
   235  		if pageSize != 0 {
   236  			// We expect that kube-apiserver will be increasing page sizes
   237  			// if not full pages are received, so we should see significantly less
   238  			// than 1000 pages (which would be result of talking to etcd with page size
   239  			// copied from pred.Limit).
   240  			// The expected number of calls is n+1 where n is the smallest n so that:
   241  			// pageSize + pageSize * 2 + pageSize * 4 + ... + pageSize * 2^n >= podCount.
   242  			// For pageSize = 1, podCount = 1000, we get n+1 = 10, 2 ^ 10 = 1024.
   243  			currLimit := pageSize
   244  			for sum := uint64(1); sum < estimatedProcessedObjects; {
   245  				currLimit *= 2
   246  				if currLimit > maxLimit {
   247  					currLimit = maxLimit
   248  				}
   249  				sum += currLimit
   250  				estimatedGetCalls++
   251  			}
   252  		}
   253  		if reads := recorder.GetReadsAndReset(); reads != estimatedGetCalls {
   254  			t.Errorf("unexpected reads: %d", reads)
   255  		}
   256  	}
   257  }
   258  
   259  func TestListContinuation(t *testing.T) {
   260  	ctx, store, etcdClient := testSetup(t, withRecorder())
   261  	validation := checkStorageCallsInvariants(
   262  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   263  	storagetesting.RunTestListContinuation(ctx, t, store, validation)
   264  }
   265  
   266  func TestListPaginationRareObject(t *testing.T) {
   267  	ctx, store, etcdClient := testSetup(t, withRecorder())
   268  	validation := checkStorageCallsInvariants(
   269  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   270  	storagetesting.RunTestListPaginationRareObject(ctx, t, store, validation)
   271  }
   272  
   273  func TestListContinuationWithFilter(t *testing.T) {
   274  	ctx, store, etcdClient := testSetup(t, withRecorder())
   275  	validation := checkStorageCallsInvariants(
   276  		store.transformer.(*storagetesting.PrefixTransformer), etcdClient.KV.(*clientRecorder))
   277  	storagetesting.RunTestListContinuationWithFilter(ctx, t, store, validation)
   278  }
   279  
   280  func compactStorage(etcdClient *clientv3.Client) storagetesting.Compaction {
   281  	return func(ctx context.Context, t *testing.T, resourceVersion string) {
   282  		versioner := storage.APIObjectVersioner{}
   283  		rv, err := versioner.ParseResourceVersion(resourceVersion)
   284  		if err != nil {
   285  			t.Fatal(err)
   286  		}
   287  		if _, _, err = compact(ctx, etcdClient, 0, int64(rv)); err != nil {
   288  			t.Fatalf("Unable to compact, %v", err)
   289  		}
   290  	}
   291  }
   292  
   293  func TestListInconsistentContinuation(t *testing.T) {
   294  	ctx, store, client := testSetup(t)
   295  	storagetesting.RunTestListInconsistentContinuation(ctx, t, store, compactStorage(client))
   296  }
   297  
   298  func TestListResourceVersionMatch(t *testing.T) {
   299  	ctx, store, _ := testSetup(t)
   300  	storagetesting.RunTestListResourceVersionMatch(ctx, t, &storeWithPrefixTransformer{store})
   301  }
   302  
   303  func TestCount(t *testing.T) {
   304  	ctx, store, _ := testSetup(t)
   305  	storagetesting.RunTestCount(ctx, t, store)
   306  }
   307  
   308  // =======================================================================
   309  // Implementation-specific tests are following.
   310  // The following tests are exercising the details of the implementation
   311  // not the actual user-facing contract of storage interface.
   312  // As such, they may focus e.g. on non-functional aspects like performance
   313  // impact.
   314  // =======================================================================
   315  
   316  func TestPrefix(t *testing.T) {
   317  	testcases := map[string]string{
   318  		"custom/prefix":     "/custom/prefix/",
   319  		"/custom//prefix//": "/custom/prefix/",
   320  		"/registry":         "/registry/",
   321  	}
   322  	for configuredPrefix, effectivePrefix := range testcases {
   323  		_, store, _ := testSetup(t, withPrefix(configuredPrefix))
   324  		if store.pathPrefix != effectivePrefix {
   325  			t.Errorf("configured prefix of %s, expected effective prefix of %s, got %s", configuredPrefix, effectivePrefix, store.pathPrefix)
   326  		}
   327  	}
   328  }
   329  
   330  func Test_growSlice(t *testing.T) {
   331  	type args struct {
   332  		initialCapacity int
   333  		initialLen      int
   334  		v               reflect.Value
   335  		maxCapacity     int
   336  		sizes           []int
   337  	}
   338  	tests := []struct {
   339  		name string
   340  		args args
   341  		cap  int
   342  		len  int
   343  	}{
   344  		{
   345  			name: "empty",
   346  			args: args{v: reflect.ValueOf([]example.Pod{})},
   347  			cap:  0,
   348  		},
   349  		{
   350  			name: "no sizes",
   351  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10},
   352  			cap:  10,
   353  		},
   354  		{
   355  			name: "above maxCapacity",
   356  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{1, 12}},
   357  			cap:  10,
   358  		},
   359  		{
   360  			name: "takes max",
   361  			args: args{v: reflect.ValueOf([]example.Pod{}), maxCapacity: 10, sizes: []int{8, 4}},
   362  			cap:  8,
   363  		},
   364  		{
   365  			name: "with existing capacity above max",
   366  			args: args{initialCapacity: 12, maxCapacity: 10, sizes: []int{8, 4}},
   367  			cap:  12,
   368  		},
   369  		{
   370  			name: "with existing capacity below max",
   371  			args: args{initialCapacity: 5, maxCapacity: 10, sizes: []int{8, 4}},
   372  			cap:  8,
   373  		},
   374  		{
   375  			name: "with existing capacity and length above max",
   376  			args: args{initialCapacity: 12, initialLen: 5, maxCapacity: 10, sizes: []int{8, 4}},
   377  			cap:  12,
   378  			len:  5,
   379  		},
   380  		{
   381  			name: "with existing capacity and length below max",
   382  			args: args{initialCapacity: 5, initialLen: 3, maxCapacity: 10, sizes: []int{8, 4}},
   383  			cap:  8,
   384  			len:  3,
   385  		},
   386  	}
   387  	for _, tt := range tests {
   388  		t.Run(tt.name, func(t *testing.T) {
   389  			if tt.args.initialCapacity > 0 {
   390  				val := make([]example.Pod, tt.args.initialLen, tt.args.initialCapacity)
   391  				for i := 0; i < tt.args.initialLen; i++ {
   392  					val[i].Name = fmt.Sprintf("test-%d", i)
   393  				}
   394  				tt.args.v = reflect.ValueOf(val)
   395  			}
   396  			// reflection requires that the value be addressable in order to call set,
   397  			// so we must ensure the value we created is available on the heap (not a problem
   398  			// for normal usage)
   399  			if !tt.args.v.CanAddr() {
   400  				x := reflect.New(tt.args.v.Type())
   401  				x.Elem().Set(tt.args.v)
   402  				tt.args.v = x.Elem()
   403  			}
   404  			growSlice(tt.args.v, tt.args.maxCapacity, tt.args.sizes...)
   405  			if tt.cap != tt.args.v.Cap() {
   406  				t.Errorf("Unexpected capacity: got=%d want=%d", tt.args.v.Cap(), tt.cap)
   407  			}
   408  			if tt.len != tt.args.v.Len() {
   409  				t.Errorf("Unexpected length: got=%d want=%d", tt.args.v.Len(), tt.len)
   410  			}
   411  			for i := 0; i < tt.args.v.Len(); i++ {
   412  				nameWanted := fmt.Sprintf("test-%d", i)
   413  				val := tt.args.v.Index(i).Interface()
   414  				pod, ok := val.(example.Pod)
   415  				if !ok || pod.Name != nameWanted {
   416  					t.Errorf("Unexpected element value: got=%s, want=%s", pod.Name, nameWanted)
   417  				}
   418  			}
   419  		})
   420  	}
   421  }
   422  
   423  func TestLeaseMaxObjectCount(t *testing.T) {
   424  	ctx, store, _ := testSetup(t, withLeaseConfig(LeaseManagerConfig{
   425  		ReuseDurationSeconds: defaultLeaseReuseDurationSeconds,
   426  		MaxObjectCount:       2,
   427  	}))
   428  
   429  	obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}
   430  	out := &example.Pod{}
   431  
   432  	testCases := []struct {
   433  		key                 string
   434  		expectAttachedCount int64
   435  	}{
   436  		{
   437  			key:                 "testkey1",
   438  			expectAttachedCount: 1,
   439  		},
   440  		{
   441  			key:                 "testkey2",
   442  			expectAttachedCount: 2,
   443  		},
   444  		{
   445  			key: "testkey3",
   446  			// We assume each time has 1 object attached to the lease
   447  			// so after granting a new lease, the recorded count is set to 1
   448  			expectAttachedCount: 1,
   449  		},
   450  	}
   451  
   452  	for _, tc := range testCases {
   453  		err := store.Create(ctx, tc.key, obj, out, 120)
   454  		if err != nil {
   455  			t.Fatalf("Set failed: %v", err)
   456  		}
   457  		if store.leaseManager.leaseAttachedObjectCount != tc.expectAttachedCount {
   458  			t.Errorf("Lease manager recorded count %v should be %v", store.leaseManager.leaseAttachedObjectCount, tc.expectAttachedCount)
   459  		}
   460  	}
   461  }
   462  
   463  // ===================================================
   464  // Test-setup related function are following.
   465  // ===================================================
   466  
   467  func newTestLeaseManagerConfig() LeaseManagerConfig {
   468  	cfg := NewDefaultLeaseManagerConfig()
   469  	// As 30s is the default timeout for testing in global configuration,
   470  	// we cannot wait longer than that in a single time: change it to 1s
   471  	// for testing purposes. See wait.ForeverTestTimeout
   472  	cfg.ReuseDurationSeconds = 1
   473  	return cfg
   474  }
   475  
   476  func newTestTransformer() value.Transformer {
   477  	return storagetesting.NewPrefixTransformer([]byte(defaultTestPrefix), false)
   478  }
   479  
   480  type clientRecorder struct {
   481  	reads uint64
   482  	clientv3.KV
   483  }
   484  
   485  func (r *clientRecorder) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
   486  	atomic.AddUint64(&r.reads, 1)
   487  	return r.KV.Get(ctx, key, opts...)
   488  }
   489  
   490  func (r *clientRecorder) GetReadsAndReset() uint64 {
   491  	return atomic.SwapUint64(&r.reads, 0)
   492  }
   493  
   494  type setupOptions struct {
   495  	client         func(testing.TB) *clientv3.Client
   496  	codec          runtime.Codec
   497  	newFunc        func() runtime.Object
   498  	newListFunc    func() runtime.Object
   499  	prefix         string
   500  	resourcePrefix string
   501  	groupResource  schema.GroupResource
   502  	transformer    value.Transformer
   503  	leaseConfig    LeaseManagerConfig
   504  
   505  	recorderEnabled bool
   506  }
   507  
   508  type setupOption func(*setupOptions)
   509  
   510  func withClientConfig(config *embed.Config) setupOption {
   511  	return func(options *setupOptions) {
   512  		options.client = func(t testing.TB) *clientv3.Client {
   513  			return testserver.RunEtcd(t, config)
   514  		}
   515  	}
   516  }
   517  
   518  func withPrefix(prefix string) setupOption {
   519  	return func(options *setupOptions) {
   520  		options.prefix = prefix
   521  	}
   522  }
   523  
   524  func withLeaseConfig(leaseConfig LeaseManagerConfig) setupOption {
   525  	return func(options *setupOptions) {
   526  		options.leaseConfig = leaseConfig
   527  	}
   528  }
   529  
   530  func withRecorder() setupOption {
   531  	return func(options *setupOptions) {
   532  		options.recorderEnabled = true
   533  	}
   534  }
   535  
   536  func withDefaults(options *setupOptions) {
   537  	options.client = func(t testing.TB) *clientv3.Client {
   538  		return testserver.RunEtcd(t, nil)
   539  	}
   540  	options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion)
   541  	options.newFunc = newPod
   542  	options.newListFunc = newPodList
   543  	options.prefix = ""
   544  	options.resourcePrefix = "/pods"
   545  	options.groupResource = schema.GroupResource{Resource: "pods"}
   546  	options.transformer = newTestTransformer()
   547  	options.leaseConfig = newTestLeaseManagerConfig()
   548  }
   549  
   550  var _ setupOption = withDefaults
   551  
   552  func testSetup(t testing.TB, opts ...setupOption) (context.Context, *store, *clientv3.Client) {
   553  	setupOpts := setupOptions{}
   554  	opts = append([]setupOption{withDefaults}, opts...)
   555  	for _, opt := range opts {
   556  		opt(&setupOpts)
   557  	}
   558  	client := setupOpts.client(t)
   559  	if setupOpts.recorderEnabled {
   560  		client.KV = &clientRecorder{KV: client.KV}
   561  	}
   562  	store := newStore(
   563  		client,
   564  		setupOpts.codec,
   565  		setupOpts.newFunc,
   566  		setupOpts.newListFunc,
   567  		setupOpts.prefix,
   568  		setupOpts.resourcePrefix,
   569  		setupOpts.groupResource,
   570  		setupOpts.transformer,
   571  		setupOpts.leaseConfig,
   572  	)
   573  	ctx := context.Background()
   574  	return ctx, store, client
   575  }
   576  
   577  func TestValidateKey(t *testing.T) {
   578  	validKeys := []string{
   579  		"/foo/bar/baz/a.b.c/",
   580  		"/foo",
   581  		"foo/bar/baz",
   582  		"/foo/bar..baz/",
   583  		"/foo/bar..",
   584  		"foo",
   585  		"foo/bar",
   586  		"/foo/bar/",
   587  	}
   588  	invalidKeys := []string{
   589  		"/foo/bar/../a.b.c/",
   590  		"..",
   591  		"/..",
   592  		"../",
   593  		"/foo/bar/..",
   594  		"../foo/bar",
   595  		"/../foo",
   596  		"/foo/bar/../",
   597  		".",
   598  		"/.",
   599  		"./",
   600  		"/./",
   601  		"/foo/.",
   602  		"./bar",
   603  		"/foo/./bar/",
   604  	}
   605  	const (
   606  		pathPrefix   = "/first/second"
   607  		expectPrefix = pathPrefix + "/"
   608  	)
   609  	_, store, _ := testSetup(t, withPrefix(pathPrefix))
   610  
   611  	for _, key := range validKeys {
   612  		k, err := store.prepareKey(key)
   613  		if err != nil {
   614  			t.Errorf("key %q should be valid; unexpected error: %v", key, err)
   615  		} else if !strings.HasPrefix(k, expectPrefix) {
   616  			t.Errorf("key %q should have prefix %q", k, expectPrefix)
   617  		}
   618  	}
   619  
   620  	for _, key := range invalidKeys {
   621  		_, err := store.prepareKey(key)
   622  		if err == nil {
   623  			t.Errorf("key %q should be invalid", key)
   624  		}
   625  	}
   626  }
   627  
   628  func TestInvalidKeys(t *testing.T) {
   629  	const invalidKey = "/foo/bar/../baz"
   630  	expectedError := fmt.Sprintf("invalid key: %q", invalidKey)
   631  
   632  	expectInvalidKey := func(methodName string, err error) {
   633  		if err == nil {
   634  			t.Errorf("[%s] expected invalid key error; got nil", methodName)
   635  		} else if err.Error() != expectedError {
   636  			t.Errorf("[%s] expected invalid key error; got %v", methodName, err)
   637  		}
   638  	}
   639  
   640  	ctx, store, _ := testSetup(t)
   641  	expectInvalidKey("Create", store.Create(ctx, invalidKey, nil, nil, 0))
   642  	expectInvalidKey("Delete", store.Delete(ctx, invalidKey, nil, nil, nil, nil))
   643  	_, watchErr := store.Watch(ctx, invalidKey, storage.ListOptions{})
   644  	expectInvalidKey("Watch", watchErr)
   645  	expectInvalidKey("Get", store.Get(ctx, invalidKey, storage.GetOptions{}, nil))
   646  	expectInvalidKey("GetList", store.GetList(ctx, invalidKey, storage.ListOptions{}, nil))
   647  	expectInvalidKey("GuaranteedUpdate", store.GuaranteedUpdate(ctx, invalidKey, nil, true, nil, nil, nil))
   648  	_, countErr := store.Count(invalidKey)
   649  	expectInvalidKey("Count", countErr)
   650  }
   651  
   652  func TestResolveGetListRev(t *testing.T) {
   653  	_, store, _ := testSetup(t)
   654  	testCases := []struct {
   655  		name          string
   656  		continueKey   string
   657  		continueRV    int64
   658  		rv            string
   659  		rvMatch       metav1.ResourceVersionMatch
   660  		recursive     bool
   661  		expectedError string
   662  		limit         int64
   663  		expectedRev   int64
   664  	}{
   665  		{
   666  			name:          "specifying resource versionwhen using continue",
   667  			continueKey:   "continue",
   668  			continueRV:    100,
   669  			rv:            "200",
   670  			expectedError: "specifying resource version is not allowed when using continue",
   671  		},
   672  		{
   673  			name:          "invalid resource version",
   674  			rv:            "invalid",
   675  			expectedError: "invalid resource version",
   676  		},
   677  		{
   678  			name:          "unknown ResourceVersionMatch value",
   679  			rv:            "200",
   680  			rvMatch:       "unknown",
   681  			expectedError: "unknown ResourceVersionMatch value",
   682  		},
   683  		{
   684  			name:        "use continueRV",
   685  			continueKey: "continue",
   686  			continueRV:  100,
   687  			rv:          "0",
   688  			expectedRev: 100,
   689  		},
   690  		{
   691  			name:        "use continueRV with empty rv",
   692  			continueKey: "continue",
   693  			continueRV:  100,
   694  			rv:          "",
   695  			expectedRev: 100,
   696  		},
   697  		{
   698  			name:        "continueRV = 0",
   699  			continueKey: "continue",
   700  			continueRV:  0,
   701  			rv:          "",
   702  			expectedRev: 0,
   703  		},
   704  		{
   705  			name:        "continueRV < 0",
   706  			continueKey: "continue",
   707  			continueRV:  -1,
   708  			rv:          "",
   709  			expectedRev: 0,
   710  		},
   711  		{
   712  			name:        "default",
   713  			expectedRev: 0,
   714  		},
   715  		{
   716  			name:        "rev resolve to 0 if ResourceVersionMatchNotOlderThan",
   717  			rv:          "200",
   718  			rvMatch:     metav1.ResourceVersionMatchNotOlderThan,
   719  			expectedRev: 0,
   720  		},
   721  		{
   722  			name:        "specified rev if ResourceVersionMatchExact",
   723  			rv:          "200",
   724  			rvMatch:     metav1.ResourceVersionMatchExact,
   725  			expectedRev: 200,
   726  		},
   727  		{
   728  			name:        "rev resolve to 0 if not recursive",
   729  			rv:          "200",
   730  			limit:       1,
   731  			expectedRev: 0,
   732  		},
   733  		{
   734  			name:        "rev resolve to 0 if limit unspecified",
   735  			rv:          "200",
   736  			recursive:   true,
   737  			expectedRev: 0,
   738  		},
   739  		{
   740  			name:        "specified rev if recursive with limit",
   741  			rv:          "200",
   742  			recursive:   true,
   743  			limit:       1,
   744  			expectedRev: 200,
   745  		},
   746  	}
   747  	for _, tt := range testCases {
   748  		tt := tt
   749  		t.Run(tt.name, func(t *testing.T) {
   750  			storageOpts := storage.ListOptions{
   751  				ResourceVersion:      tt.rv,
   752  				ResourceVersionMatch: tt.rvMatch,
   753  				Predicate: storage.SelectionPredicate{
   754  					Limit: tt.limit,
   755  				},
   756  				Recursive: tt.recursive,
   757  			}
   758  			rev, err := store.resolveGetListRev(tt.continueKey, tt.continueRV, storageOpts)
   759  			if len(tt.expectedError) > 0 {
   760  				if err == nil || !strings.Contains(err.Error(), tt.expectedError) {
   761  					t.Fatalf("expected error: %s, but got: %v", tt.expectedError, err)
   762  				}
   763  				return
   764  			}
   765  			if err != nil {
   766  				t.Fatalf("resolveRevForGetList failed: %v", err)
   767  			}
   768  			if rev != tt.expectedRev {
   769  				t.Errorf("%s: expecting rev = %d, but get %d", tt.name, tt.expectedRev, rev)
   770  			}
   771  		})
   772  	}
   773  }
   774  
   775  func BenchmarkStore_GetList(b *testing.B) {
   776  	generateBigPod := func(index int, total int, expect int) runtime.Object {
   777  		l := map[string]string{}
   778  		if index%(total/expect) == 0 {
   779  			l["foo"] = "bar"
   780  		}
   781  		terminationGracePeriodSeconds := int64(42)
   782  		activeDeadlineSeconds := int64(42)
   783  		pod := &examplev1.Pod{
   784  			ObjectMeta: metav1.ObjectMeta{
   785  				Labels: l,
   786  			},
   787  			Spec: examplev1.PodSpec{
   788  				RestartPolicy:                 examplev1.RestartPolicy("Always"),
   789  				TerminationGracePeriodSeconds: &terminationGracePeriodSeconds,
   790  				ActiveDeadlineSeconds:         &activeDeadlineSeconds,
   791  				NodeSelector:                  map[string]string{},
   792  				ServiceAccountName:            "demo-sa",
   793  			},
   794  		}
   795  		pod.Name = fmt.Sprintf("object-%d", index)
   796  		data := make([]byte, 1024*2, 1024*2) // 2k labels
   797  		rand.Read(data)
   798  		pod.Spec.NodeSelector["key"] = string(data)
   799  		return pod
   800  	}
   801  	testCases := []struct {
   802  		name              string
   803  		objectNum         int
   804  		expectNum         int
   805  		selector          labels.Selector
   806  		newObjectFunc     func(index int, total int, expect int) runtime.Object
   807  		newListObjectFunc func() runtime.Object
   808  	}{
   809  		{
   810  			name:              "pick 50 pods out of 5000 pod",
   811  			objectNum:         5000,
   812  			expectNum:         50,
   813  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   814  			newObjectFunc:     generateBigPod,
   815  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   816  		},
   817  		{
   818  			name:              "pick 500 pods out of 5000 pod",
   819  			objectNum:         5000,
   820  			expectNum:         500,
   821  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   822  			newObjectFunc:     generateBigPod,
   823  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   824  		},
   825  		{
   826  			name:              "pick 1000 pods out of 5000 pod",
   827  			objectNum:         5000,
   828  			expectNum:         1000,
   829  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   830  			newObjectFunc:     generateBigPod,
   831  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   832  		},
   833  		{
   834  			name:              "pick 2500 pods out of 5000 pod",
   835  			objectNum:         5000,
   836  			expectNum:         2500,
   837  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   838  			newObjectFunc:     generateBigPod,
   839  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   840  		},
   841  		{
   842  			name:              "pick 5000 pods out of 5000 pod",
   843  			objectNum:         5000,
   844  			expectNum:         5000,
   845  			selector:          labels.SelectorFromSet(map[string]string{"foo": "bar"}),
   846  			newObjectFunc:     generateBigPod,
   847  			newListObjectFunc: func() runtime.Object { return &examplev1.PodList{} },
   848  		},
   849  	}
   850  	for _, tc := range testCases {
   851  		b.Run(tc.name, func(b *testing.B) {
   852  			// booting etcd instance
   853  			ctx, store, etcdClient := testSetup(b)
   854  			defer etcdClient.Close()
   855  
   856  			// make fake objects..
   857  			dir := "/testing"
   858  			originalRevision := ""
   859  			for i := 0; i < tc.objectNum; i++ {
   860  				obj := tc.newObjectFunc(i, tc.objectNum, tc.expectNum)
   861  				o := obj.(metav1.Object)
   862  				key := fmt.Sprintf("/testing/testkey/%s", o.GetName())
   863  				out := tc.newObjectFunc(i, tc.objectNum, tc.expectNum)
   864  				if err := store.Create(ctx, key, obj, out, 0); err != nil {
   865  					b.Fatalf("Set failed: %v", err)
   866  				}
   867  				originalRevision = out.(metav1.Object).GetResourceVersion()
   868  			}
   869  
   870  			// prepare result and pred
   871  			pred := storage.SelectionPredicate{
   872  				Label: tc.selector,
   873  				Field: fields.Everything(),
   874  				GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
   875  					pod, ok := obj.(*examplev1.Pod)
   876  					if !ok {
   877  						return nil, nil, fmt.Errorf("not a pod")
   878  					}
   879  					return pod.ObjectMeta.Labels, fields.Set{
   880  						"metadata.name": pod.Name,
   881  					}, nil
   882  				},
   883  			}
   884  
   885  			// now we start benchmarking
   886  			b.ResetTimer()
   887  			for i := 0; i < b.N; i++ {
   888  				list := tc.newListObjectFunc()
   889  				if err := store.GetList(ctx, dir, storage.ListOptions{Predicate: pred, Recursive: true}, list); err != nil {
   890  					b.Errorf("Unexpected List error: %v", err)
   891  				}
   892  				listObject := list.(*examplev1.PodList)
   893  				if originalRevision != listObject.GetResourceVersion() {
   894  					b.Fatalf("original revision (%s) did not match final revision after linearized reads (%s)", originalRevision, listObject.GetResourceVersion())
   895  				}
   896  				if len(listObject.Items) != tc.expectNum {
   897  					b.Fatalf("expect (%d) items but got (%d)", tc.expectNum, len(listObject.Items))
   898  				}
   899  			}
   900  		})
   901  	}
   902  }