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 }