github.com/portworx/kvdb@v0.0.0-20241107215734-a185a966f535/etcd/v3/kv_etcd_test.go (about)

     1  package etcdv3
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/portworx/kvdb"
    11  	"github.com/portworx/kvdb/etcd/common"
    12  	"github.com/portworx/kvdb/test"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
    16  	"go.etcd.io/etcd/server/v3/etcdserver"
    17  	"golang.org/x/net/context"
    18  	"google.golang.org/grpc"
    19  	"google.golang.org/grpc/codes"
    20  	"google.golang.org/grpc/status"
    21  )
    22  
    23  func TestAll(t *testing.T) {
    24  	test.Run(New, t, common.TestStart, common.TestStop)
    25  	// Run the basic tests with an authenticated etcd
    26  	// Uncomment if you have an auth enabled etcd setup. Checkout the test/kv.go for options
    27  	//test.RunAuth(New, t)
    28  	test.RunControllerTests(New, t)
    29  }
    30  
    31  func TestIsRetryNeeded(t *testing.T) {
    32  	fn := "TestIsRetryNeeded"
    33  	key := "test"
    34  	retryCount := 1
    35  
    36  	// context.DeadlineExceeded
    37  	retry, err := isRetryNeeded(context.DeadlineExceeded, fn, key, retryCount)
    38  	assert.EqualError(t, context.DeadlineExceeded, err.Error(), "Unexpcted error")
    39  	assert.True(t, retry, "Expected a retry")
    40  
    41  	// etcdserver.ErrTimeout
    42  	retry, err = isRetryNeeded(etcdserver.ErrTimeout, fn, key, retryCount)
    43  	assert.EqualError(t, etcdserver.ErrTimeout, err.Error(), "Unexpcted error")
    44  	assert.True(t, retry, "Expected a retry")
    45  
    46  	// etcdserver.ErrUnhealthy
    47  	retry, err = isRetryNeeded(etcdserver.ErrUnhealthy, fn, key, retryCount)
    48  	assert.EqualError(t, etcdserver.ErrUnhealthy, err.Error(), "Unexpcted error")
    49  	assert.True(t, retry, "Expected a retry")
    50  
    51  	// etcd is sending the error ErrTimeout over grpc instead of the actual ErrGRPCTimeout
    52  	// hence isRetryNeeded cannot do an actual error comparison
    53  	retry, err = isRetryNeeded(fmt.Errorf("etcdserver: request timed out"), fn, key, retryCount)
    54  	assert.True(t, retry, "Expected a retry")
    55  
    56  	// rpctypes.ErrGRPCTimeout
    57  	retry, err = isRetryNeeded(rpctypes.ErrGRPCTimeout, fn, key, retryCount)
    58  	assert.EqualError(t, rpctypes.ErrGRPCTimeout, err.Error(), "Unexpcted error")
    59  	assert.True(t, retry, "Expected a retry")
    60  
    61  	// rpctypes.ErrGRPCNoLeader
    62  	retry, err = isRetryNeeded(rpctypes.ErrGRPCNoLeader, fn, key, retryCount)
    63  	assert.EqualError(t, rpctypes.ErrGRPCNoLeader, err.Error(), "Unexpcted error")
    64  	assert.True(t, retry, "Expected a retry")
    65  
    66  	// rpctypes.ErrGRPCEmptyKey
    67  	retry, err = isRetryNeeded(rpctypes.ErrGRPCEmptyKey, fn, key, retryCount)
    68  	assert.EqualError(t, kvdb.ErrNotFound, err.Error(), "Unexpcted error")
    69  	assert.False(t, retry, "Expected no retry")
    70  
    71  	// etcd v3.2.x uses following grpc error format
    72  	grpcErr := grpc.Errorf(codes.Unavailable, "desc = some grpc error")
    73  	retry, err = isRetryNeeded(grpcErr, fn, key, retryCount)
    74  	assert.EqualError(t, grpcErr, err.Error(), "Unexpcted error")
    75  	assert.True(t, retry, "Expected a retry")
    76  
    77  	// etcd v3.3.x uses the following grpc error format
    78  	grpcErr = status.New(codes.Unavailable, "desc = some grpc error").Err()
    79  	retry, err = isRetryNeeded(grpcErr, fn, key, retryCount)
    80  	assert.EqualError(t, grpcErr, err.Error(), "Unexpcted error")
    81  	assert.True(t, retry, "Expected a retry")
    82  
    83  	// grpc error of ContextDeadlineExceeded
    84  	gErr := status.New(codes.DeadlineExceeded, "context deadline exceeded").Err()
    85  	retry, err = isRetryNeeded(gErr, fn, key, retryCount)
    86  	assert.EqualError(t, gErr, err.Error(), "Unexpcted error")
    87  	assert.True(t, retry, "Expected a retry")
    88  
    89  	retry, err = isRetryNeeded(kvdb.ErrNotFound, fn, key, retryCount)
    90  	assert.EqualError(t, kvdb.ErrNotFound, err.Error(), "Unexpcted error")
    91  	assert.False(t, retry, "Expected no retry")
    92  
    93  	retry, err = isRetryNeeded(kvdb.ErrValueMismatch, fn, key, retryCount)
    94  	assert.EqualError(t, kvdb.ErrValueMismatch, err.Error(), "Unexpcted error")
    95  	assert.False(t, retry, "Expected no retry")
    96  
    97  }
    98  
    99  func TestCasWithRestarts(t *testing.T) {
   100  	fmt.Println("casWithRestarts")
   101  	testFn := func(lockChan chan int, kv kvdb.Kvdb, kvPair *kvdb.KVPair, val string) {
   102  		_, err := kv.CompareAndSet(kvPair, kvdb.KVFlags(0), []byte(val))
   103  		assert.NoError(t, err, "CompareAndSet should succeed on an correct value")
   104  		lockChan <- 1
   105  	}
   106  	testWithRestarts(t, testFn)
   107  
   108  }
   109  
   110  func TestCadWithRestarts(t *testing.T) {
   111  	fmt.Println("cadWithRestarts")
   112  	testFn := func(lockChan chan int, kv kvdb.Kvdb, kvPair *kvdb.KVPair, val string) {
   113  		_, err := kv.CompareAndDelete(kvPair, kvdb.KVFlags(0))
   114  		assert.NoError(t, err, "CompareAndDelete should succeed on an correct value")
   115  		lockChan <- 1
   116  	}
   117  	testWithRestarts(t, testFn)
   118  }
   119  
   120  func testWithRestarts(t *testing.T, testFn func(chan int, kvdb.Kvdb, *kvdb.KVPair, string)) {
   121  	key := "foo/cadCasWithRestarts"
   122  	val := "great"
   123  	err := common.TestStart(true)
   124  	assert.NoError(t, err, "Unable to start kvdb")
   125  	kv := newKv(t, 0)
   126  
   127  	defer func() {
   128  		kv.DeleteTree(key)
   129  	}()
   130  
   131  	kvPair, err := kv.Put(key, []byte(val), 0)
   132  	assert.NoError(t, err, "Unxpected error in Put")
   133  
   134  	kvPair, err = kv.Get(key)
   135  	assert.NoError(t, err, "Failed in Get")
   136  	fmt.Println("stopping kvdb")
   137  	err = common.TestStop()
   138  	assert.NoError(t, err, "Unable to stop kvdb")
   139  
   140  	lockChan := make(chan int)
   141  	go func() {
   142  		testFn(lockChan, kv, kvPair, val)
   143  	}()
   144  
   145  	fmt.Println("starting kvdb")
   146  	err = common.TestStart(false)
   147  	assert.NoError(t, err, "Unable to start kvdb")
   148  	select {
   149  	case <-time.After(10 * time.Second):
   150  		assert.Fail(t, "Unable to take a lock whose session is expired")
   151  	case <-lockChan:
   152  	}
   153  
   154  }
   155  
   156  func TestKeys(t *testing.T) {
   157  	err := common.TestStart(true)
   158  	require.NoError(t, err, "Unable to start kvdb")
   159  	kv := newKv(t, 0)
   160  	require.NotNil(t, kv)
   161  
   162  	defer func() {
   163  		t.Log("Stopping kvdb")
   164  		common.TestStop()
   165  	}()
   166  
   167  	data := []struct {
   168  		key, val string
   169  	}{
   170  		{"foo/keys/key1", "val1"},
   171  		{"foo/keys/key2", "val2"},
   172  		{"foo/keys/key3d/key3", "val3"},
   173  		{"foo/keys/key3d/key3b", "val3b"},
   174  	}
   175  
   176  	// load the data
   177  	for i, td := range data {
   178  		_, err := kv.Put(td.key, []byte(td.val), 0)
   179  		assert.NoError(t, err, "Unxpected error in Put on test# %d : %v", i+1, td)
   180  	}
   181  
   182  	// check the Keys()
   183  	results := []struct {
   184  		input    string
   185  		expected []string
   186  	}{
   187  		{"", []string{"foo"}},
   188  		{"non-existent", []string{}},
   189  		{"foo", []string{"keys"}},
   190  		{"foo/keys", []string{"key1", "key2", "key3d"}},
   191  		{"foo/keys/non-existent", []string{}},
   192  		{"foo/keys/key3", []string{}},
   193  		{"foo/keys/key3d", []string{"key3", "key3b"}},
   194  	}
   195  	for i, td := range results {
   196  		res, err := kv.Keys(td.input, "/")
   197  		assert.NoError(t, err, "Unexpected error on test# %d : %v", i+1, td)
   198  		assert.Equal(t, td.expected, res, "Unexpected result on test# %d : %v", i+1, td)
   199  	}
   200  	for i, td := range results {
   201  		res, err := kv.Keys(td.input, "")
   202  		assert.NoError(t, err, "Unexpected error on test# %d : %v", i+1, td)
   203  		assert.Equal(t, td.expected, res, "Unexpected result on test# %d : %v", i+1, td)
   204  	}
   205  
   206  	results = results[1:] // the first test will not work on 3rd pass -- let's skip it
   207  	for i, td := range results {
   208  		res, err := kv.Keys(td.input+"/", "/")
   209  		assert.NoError(t, err, "Unexpected error on test# %d : %v", i+1, td)
   210  		assert.Equal(t, td.expected, res, "Unexpected result on test# %d : %v", i+1, td)
   211  	}
   212  
   213  	// using different separator ('e')
   214  	results = []struct {
   215  		input    string
   216  		expected []string
   217  	}{
   218  		{"", []string{"foo/k"}},
   219  		{"foo/keys/non-existent", []string{}},
   220  		{"foo/ke", []string{"ys/k"}},
   221  		{"foo/k", []string{"ys/k"}},
   222  		{"foo/keys/ke", []string{"y1", "y2", "y3d/k"}},
   223  		{"foo/keys/k", []string{"y1", "y2", "y3d/k"}},
   224  	}
   225  	for i, td := range results {
   226  		res, err := kv.Keys(td.input, "e")
   227  		assert.NoError(t, err, "Unexpected error on test# %d : %v", i+1, td)
   228  		assert.Equal(t, td.expected, res, "Unexpected result on test# %d : %v", i+1, td)
   229  	}
   230  }
   231  
   232  func TestUnlock(t *testing.T) {
   233  	err := common.TestStart(true)
   234  	require.NoError(t, err, "Unable to start kvdb")
   235  	t.Log("Started kvdb")
   236  
   237  	kv := newKv(t, 2*time.Second)
   238  	require.NotNil(t, kv)
   239  
   240  	defer func() {
   241  		t.Log("Stopping kvdb")
   242  		common.TestStop()
   243  	}()
   244  
   245  	data := []struct {
   246  		key, val string
   247  	}{
   248  		{"foo/keys/key1", "val1"},
   249  		{"foo/keys/key2", "val2"},
   250  		{"foo/keys/key3", "val3"},
   251  	}
   252  
   253  	// load the data
   254  	for i, td := range data {
   255  		_, err := kv.Put(td.key, []byte(td.val), 0)
   256  		require.NoError(t, err, "Unxpected error in Put on test# %d : %v", i+1, td)
   257  	}
   258  	t.Log("loaded data")
   259  
   260  	var wg sync.WaitGroup
   261  	for i := 0; i < 30; i++ {
   262  		key := fmt.Sprintf("foo/keys/key%d", i%3+1)
   263  		wg.Add(1)
   264  		go func() {
   265  			defer wg.Done()
   266  			kvp, err := kv.Lock(key)
   267  			require.NoError(t, err, "Failed to lock key %s", key)
   268  			t.Log("locked key", key)
   269  
   270  			dur := time.Duration(2000+rand.Intn(2000)) * time.Millisecond
   271  			time.Sleep(dur)
   272  
   273  			err = kv.Unlock(kvp)
   274  			require.NoError(t, err, "Failed to unlock key %s", key)
   275  			t.Log("unlocked key", key)
   276  
   277  			dur = time.Duration(rand.Intn(500)) * time.Millisecond
   278  			time.Sleep(dur)
   279  
   280  			// unlock again (double unlock is not recommended but can happen)
   281  			err = kv.Unlock(kvp)
   282  			require.NoError(t, err, "Failed to double unlock key %s", key)
   283  			t.Log("unlocked key again", key)
   284  		}()
   285  	}
   286  	wg.Wait()
   287  	// give some time for refreshLocks to finish
   288  	time.Sleep(time.Second)
   289  }
   290  
   291  func newKv(t *testing.T, lockRefreshDuration time.Duration) kvdb.Kvdb {
   292  	kv, err := New("pwx/test", nil, nil, func(err error, format string, args ...interface{}) {
   293  		t.Fatalf(format, args...)
   294  	})
   295  	if err != nil {
   296  		t.Fatalf(err.Error())
   297  	}
   298  	if lockRefreshDuration != 0 {
   299  		et := kv.(*etcdKV)
   300  		et.lockRefreshDuration = lockRefreshDuration
   301  	}
   302  	return kv
   303  }