github.com/projecteru2/core@v0.0.0-20240321043226-06bcc1c23f58/store/etcdv3/meta/etcd_test.go (about) 1 package meta 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "sync" 8 "testing" 9 10 "github.com/projecteru2/core/store/etcdv3/meta/mocks" 11 "github.com/projecteru2/core/types" 12 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 "go.etcd.io/etcd/api/v3/etcdserverpb" 16 "go.etcd.io/etcd/api/v3/mvccpb" 17 clientv3 "go.etcd.io/etcd/client/v3" 18 ) 19 20 func TestGetOneError(t *testing.T) { 21 e := NewMockedETCD(t) 22 expErr := fmt.Errorf("exp") 23 e.cliv3.(*mocks.ETCDClientV3).On("Get", mock.Anything, mock.Anything).Return(nil, expErr) 24 kv, err := e.GetOne(context.Background(), "foo") 25 require.Equal(t, expErr, err) 26 require.Nil(t, kv) 27 } 28 29 func TestGetOneFailedAsRespondMore(t *testing.T) { 30 e := NewMockedETCD(t) 31 expResp := &clientv3.GetResponse{Count: 2} 32 e.cliv3.(*mocks.ETCDClientV3).On("Get", mock.Anything, mock.Anything).Return(expResp, nil) 33 kv, err := e.GetOne(context.Background(), "foo") 34 require.Error(t, err) 35 require.Nil(t, kv) 36 } 37 38 func TestGetMultiWithNoKeys(t *testing.T) { 39 e := NewEmbeddedETCD(t) 40 kvs, err := e.GetMulti(context.Background(), []string{}) 41 require.NoError(t, err) 42 require.Equal(t, 0, len(kvs)) 43 } 44 45 func TestGetMultiFailedAsBatchGetError(t *testing.T) { 46 e := NewMockedETCD(t) 47 expErr := fmt.Errorf("exp") 48 expTxn := &mocks.Txn{} 49 expTxn.On("If", mock.Anything).Return(expTxn) 50 expTxn.On("Then", mock.Anything).Return(expTxn) 51 expTxn.On("Else", mock.Anything).Return(expTxn) 52 expTxn.On("Commit").Return(nil, expErr) 53 e.cliv3.(*mocks.ETCDClientV3).On("Txn", mock.Anything).Return(expTxn) 54 kvs, err := e.GetMulti(context.Background(), []string{"foo"}) 55 require.Equal(t, expErr, err) 56 require.Nil(t, kvs) 57 } 58 59 func TestGrant(t *testing.T) { 60 e := NewMockedETCD(t) 61 expErr := fmt.Errorf("exp") 62 e.cliv3.(*mocks.ETCDClientV3).On("Grant", mock.Anything, mock.Anything).Return(nil, expErr) 63 resp, err := e.Grant(context.Background(), 1) 64 require.Equal(t, expErr, err) 65 require.Nil(t, resp) 66 } 67 68 func TestBindStatusFailedAsGrantError(t *testing.T) { 69 e, etcd, assert := testKeepAliveETCD(t) 70 defer assert() 71 expErr := fmt.Errorf("exp") 72 etcd.On("Grant", mock.Anything, mock.Anything).Return(nil, expErr) 73 require.Equal(t, expErr, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 74 } 75 76 func TestBindStatusFailedAsCommitError(t *testing.T) { 77 e, etcd, assert := testKeepAliveETCD(t) 78 defer assert() 79 80 expErr := fmt.Errorf("exp") 81 txn := &mocks.Txn{} 82 defer txn.AssertExpectations(t) 83 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 84 txn.On("If", mock.Anything).Return(txn) 85 txn.On("Then", mock.Anything).Return(txn) 86 txn.On("Commit").Return(nil, expErr) 87 88 etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil) 89 etcd.On("Txn", mock.Anything).Return(txn) 90 require.Equal(t, expErr, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 91 } 92 93 func TestBindStatusButEntityTxnUnsuccessful(t *testing.T) { 94 e, etcd, assert := testKeepAliveETCD(t) 95 defer assert() 96 97 entityTxn := &clientv3.TxnResponse{Succeeded: false} 98 txn := &mocks.Txn{} 99 defer txn.AssertExpectations(t) 100 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 101 txn.On("If", mock.Anything).Return(txn) 102 txn.On("Then", mock.Anything).Return(txn) 103 txn.On("Commit").Return(entityTxn, nil) 104 105 etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil) 106 etcd.On("Txn", mock.Anything).Return(txn) 107 require.Equal(t, types.ErrInvaildCount, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 108 } 109 110 func TestBindStatusButStatusTxnUnsuccessful(t *testing.T) { 111 e, etcd, assert := testKeepAliveETCD(t) 112 defer assert() 113 114 entityTxn := &clientv3.TxnResponse{ 115 Succeeded: true, 116 Responses: []*etcdserverpb.ResponseOp{ 117 { 118 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 119 // statusTxn 120 ResponseTxn: &etcdserverpb.TxnResponse{Succeeded: false}, 121 }, 122 }, 123 }, 124 } 125 txn := &mocks.Txn{} 126 defer txn.AssertExpectations(t) 127 txn.On("If", mock.Anything).Return(txn) 128 txn.On("Then", mock.Anything).Return(txn) 129 txn.On("Commit").Return(entityTxn, nil) 130 131 etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil) 132 etcd.On("Txn", mock.Anything).Return(txn) 133 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 134 require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 135 } 136 137 func TestBindStatusWithZeroTTL(t *testing.T) { 138 e, etcd, assert := testKeepAliveETCD(t) 139 defer assert() 140 141 entityTxn := &clientv3.TxnResponse{ 142 Succeeded: true, 143 Responses: []*etcdserverpb.ResponseOp{ 144 { 145 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 146 // statusTxn 147 ResponseTxn: &etcdserverpb.TxnResponse{Succeeded: true}, 148 }, 149 }, 150 }, 151 } 152 txn := &mocks.Txn{} 153 defer txn.AssertExpectations(t) 154 txn.On("If", mock.Anything).Return(txn) 155 txn.On("Then", mock.Anything).Return(txn) 156 txn.On("Else", mock.Anything).Return(txn) 157 txn.On("Commit").Return(entityTxn, nil) 158 159 etcd.On("Txn", mock.Anything).Return(txn) 160 161 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 162 require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 0)) 163 } 164 165 func TestBindStatusButValueTxnUnsuccessful(t *testing.T) { 166 e, etcd, assert := testKeepAliveETCD(t) 167 defer assert() 168 169 statusTxn := &etcdserverpb.TxnResponse{ 170 Succeeded: true, 171 Responses: []*etcdserverpb.ResponseOp{ 172 { 173 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 174 // valueTxn 175 ResponseTxn: &etcdserverpb.TxnResponse{Succeeded: false}, 176 }, 177 }, 178 }, 179 } 180 entityTxn := &clientv3.TxnResponse{ 181 Succeeded: true, 182 Responses: []*etcdserverpb.ResponseOp{ 183 { 184 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 185 // statusTxn 186 ResponseTxn: statusTxn, 187 }, 188 }, 189 }, 190 } 191 txn := &mocks.Txn{} 192 defer txn.AssertExpectations(t) 193 txn.On("If", mock.Anything).Return(txn) 194 txn.On("Then", mock.Anything).Return(txn) 195 txn.On("Commit").Return(entityTxn, nil) 196 197 etcd.On("Txn", mock.Anything).Return(txn) 198 etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil) 199 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 200 require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 201 } 202 203 func TestBindStatus(t *testing.T) { 204 e, etcd, assert := testKeepAliveETCD(t) 205 defer assert() 206 207 leaseID := int64(1235) 208 valueTxn := &etcdserverpb.TxnResponse{ 209 Succeeded: true, 210 Responses: []*etcdserverpb.ResponseOp{ 211 { 212 Response: &etcdserverpb.ResponseOp_ResponseRange{ 213 ResponseRange: &etcdserverpb.RangeResponse{ 214 Kvs: []*mvccpb.KeyValue{ 215 {Lease: leaseID}, 216 }, 217 }, 218 }, 219 }, 220 }, 221 } 222 statusTxn := &etcdserverpb.TxnResponse{ 223 Succeeded: true, 224 Responses: []*etcdserverpb.ResponseOp{ 225 { 226 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 227 ResponseTxn: valueTxn, 228 }, 229 }, 230 }, 231 } 232 entityTxn := &clientv3.TxnResponse{ 233 Succeeded: true, 234 Responses: []*etcdserverpb.ResponseOp{ 235 { 236 Response: &etcdserverpb.ResponseOp_ResponseTxn{ 237 // statusTxn 238 ResponseTxn: statusTxn, 239 }, 240 }, 241 }, 242 } 243 txn := &mocks.Txn{} 244 defer txn.AssertExpectations(t) 245 txn.On("If", mock.Anything).Return(txn) 246 txn.On("Then", mock.Anything).Return(txn) 247 txn.On("Commit").Return(entityTxn, nil) 248 249 etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil) 250 etcd.On("Txn", mock.Anything).Return(txn) 251 etcd.On("Get", mock.Anything, mock.Anything).Return(&clientv3.GetResponse{}, nil) 252 require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 1)) 253 } 254 255 func testKeepAliveETCD(t *testing.T) (*ETCD, *mocks.ETCDClientV3, func()) { 256 e := NewMockedETCD(t) 257 etcd, ok := e.cliv3.(*mocks.ETCDClientV3) 258 require.True(t, ok) 259 return e, etcd, func() { etcd.AssertExpectations(t) } 260 } 261 262 func NewMockedETCD(t *testing.T) *ETCD { 263 e := NewEmbeddedETCD(t) 264 e.cliv3 = &mocks.ETCDClientV3{} 265 return e 266 } 267 268 func NewEmbeddedETCD(t *testing.T) *ETCD { 269 config := types.EtcdConfig{ 270 Machines: []string{"127.0.0.1:2379"}, 271 Prefix: "/eru-test", 272 LockPrefix: "/eru-test-lock", 273 } 274 e, err := NewETCD(config, t) 275 require.NoError(t, err) 276 return e 277 } 278 279 func TestETCD(t *testing.T) { 280 m := NewEmbeddedETCD(t) 281 ctx := context.Background() 282 283 // CreateLock 284 _, err := m.CreateLock("test", 5) 285 require.NoError(t, err) 286 // Get 287 resp, err := m.Get(ctx, "test") 288 require.NoError(t, err) 289 require.Equal(t, resp.Count, int64(0)) 290 // Put 291 _, err = m.Put(ctx, "test/1", "a") 292 m.Put(ctx, "test/2", "a") 293 require.NoError(t, err) 294 // Get again 295 resp, err = m.Get(ctx, "test/1") 296 require.NoError(t, err) 297 require.Equal(t, resp.Count, int64(len(resp.Kvs))) 298 // GetOne 299 _, err = m.GetOne(ctx, "test", clientv3.WithPrefix()) 300 require.Error(t, err) 301 ev, err := m.GetOne(ctx, "test/1") 302 require.NoError(t, err) 303 require.Equal(t, string(ev.Value), "a") 304 // Delete 305 _, err = m.Delete(ctx, "test/2") 306 require.NoError(t, err) 307 m.Put(ctx, "d1", "a") 308 m.Put(ctx, "d2", "a") 309 m.Put(ctx, "d3", "a") 310 // BatchDelete 311 r, err := m.BatchDelete(ctx, []string{"d1", "d2", "d3"}) 312 require.NoError(t, err) 313 require.True(t, r.Succeeded) 314 // Create 315 r, err = m.Create(ctx, "test/2", "a") 316 require.NoError(t, err) 317 require.True(t, r.Succeeded) 318 // CreateFail 319 r, err = m.Create(ctx, "test/2", "a") 320 require.Error(t, err) 321 require.False(t, r.Succeeded) 322 // BatchCreate 323 data := map[string]string{ 324 "k1": "a1", 325 "k2": "a2", 326 } 327 r, err = m.BatchCreate(ctx, data) 328 require.NoError(t, err) 329 require.True(t, r.Succeeded) 330 // BatchCreateFailed 331 r, err = m.BatchCreate(ctx, data) 332 require.Error(t, err) 333 require.False(t, r.Succeeded) 334 // Update 335 r, err = m.Update(ctx, "test/2", "b") 336 require.NoError(t, err) 337 require.True(t, r.Succeeded) 338 // UpdateFail 339 r, err = m.Update(ctx, "test/3", "b") 340 require.EqualError(t, err, "key not exists") 341 require.False(t, r.Succeeded) 342 // BatchUpdate 343 data = map[string]string{ 344 "k1": "b1", 345 "k2": "b2", 346 } 347 r, err = m.BatchUpdate(ctx, data) 348 require.NoError(t, err) 349 require.True(t, r.Succeeded) 350 // BatchUpdate 351 data = map[string]string{ 352 "k1": "c1", 353 "k3": "b2", 354 } 355 r, err = m.BatchUpdate(ctx, data) 356 require.EqualError(t, err, "key not exists") 357 require.False(t, r.Succeeded) 358 // Watch 359 ctx2, cancel := context.WithCancel(ctx) 360 ch := m.watch(ctx2, "watchkey", clientv3.WithPrefix()) 361 go func() { 362 for r := range ch { 363 require.NotEmpty(t, r.Events) 364 require.Equal(t, len(r.Events), 1) 365 require.Equal(t, r.Events[0].Type, clientv3.EventTypePut) 366 require.Equal(t, string(r.Events[0].Kv.Value), "b") 367 } 368 }() 369 m.Create(ctx, "watchkey/1", "b") 370 cancel() 371 372 // BatchCreateAndDecr error 373 data = map[string]string{ 374 "bcad_k1": "v1", 375 "bcad_k2": "v1", 376 } 377 err = m.BatchCreateAndDecr(context.Background(), data, "bcad_process") 378 require.EqualError(t, err, "bcad_process: key not exists") 379 380 // BatchCreateAndDecr error 381 _, err = m.Put(context.Background(), "bcad_process", "a") 382 require.NoError(t, err) 383 err = m.BatchCreateAndDecr(context.Background(), data, "bcad_process") 384 require.EqualError(t, err, "strconv.Atoi: parsing \"a\": invalid syntax") 385 386 // BatchCreateAndDecr success 387 _, err = m.Put(context.Background(), "bcad_process", "20") 388 require.NoError(t, err) 389 err = m.BatchCreateAndDecr(context.Background(), data, "bcad_process") 390 require.NoError(t, err) 391 resp, err = m.Get(context.Background(), "bcad_process") 392 require.NoError(t, err) 393 processCnt, err := strconv.Atoi(string(resp.Kvs[0].Value)) 394 require.NoError(t, err) 395 require.EqualValues(t, 19, processCnt) 396 397 // BatchCreateAndDecr concurrency 398 _, err = m.Put(context.Background(), "bcad_process", "200") 399 require.NoError(t, err) 400 wg := sync.WaitGroup{} 401 for i := 0; i < 200; i++ { 402 wg.Add(1) 403 go func() { 404 defer wg.Done() 405 m.BatchCreateAndDecr(context.Background(), data, "bcad_process") 406 }() 407 } 408 wg.Wait() 409 resp, err = m.Get(context.Background(), "bcad_process") 410 require.NoError(t, err) 411 processCnt, err = strconv.Atoi(string(resp.Kvs[0].Value)) 412 require.NoError(t, err) 413 require.EqualValues(t, 0, processCnt) 414 415 // doBatchOp error 416 _, err = m.doBatchOp(context.Background(), nil) 417 require.EqualError(t, err, "no txn ops") 418 419 // doBatchOp: many groups 420 txnes := []ETCDTxn{} 421 for i := 0; i < 999; i++ { 422 txnes = append(txnes, ETCDTxn{Then: []clientv3.Op{clientv3.OpGet("a")}}) 423 } 424 txnResp, err := m.doBatchOp(context.Background(), txnes) 425 require.NoError(t, err) 426 require.True(t, txnResp.Succeeded) 427 require.EqualValues(t, 999, len(txnResp.Responses)) 428 429 // doBatchOp: many then 430 txnes = []ETCDTxn{{}, {}} 431 for i := 0; i < 999; i++ { 432 txnes[0].Then = append(txnes[0].Then, clientv3.OpGet("a")) 433 txnes[1].Then = append(txnes[1].Then, clientv3.OpGet("a"), clientv3.OpGet("b")) 434 } 435 txnResp, err = m.doBatchOp(context.Background(), txnes) 436 require.NoError(t, err) 437 require.True(t, txnResp.Succeeded) 438 require.EqualValues(t, 999*3, len(txnResp.Responses)) 439 440 // doBatchOp: empty 441 txnes = []ETCDTxn{{If: []clientv3.Cmp{ 442 clientv3.Compare(clientv3.Value("a"), "=", string("123")), 443 }}} 444 txnResp, err = m.doBatchOp(context.Background(), txnes) 445 require.NoError(t, err) 446 require.False(t, txnResp.Succeeded) 447 require.EqualValues(t, 0, len(txnResp.Responses)) 448 449 // GetMulti error 450 _, err = m.GetMulti(context.Background(), []string{"a", "b"}) 451 require.EqualError(t, err, "key: a: bad `Count` value, entity count invalid") 452 453 // GetMulti success 454 m.Put(context.Background(), "a", "b") 455 m.Put(context.Background(), "b", "c") 456 kvs, err := m.GetMulti(context.Background(), []string{"a", "b"}) 457 require.NoError(t, err) 458 require.EqualValues(t, 2, len(kvs)) 459 460 // batchPut: cmpValue branch 461 data = map[string]string{ 462 "aa": "bb", 463 "cc": "dd", 464 } 465 limit := map[string]map[string]string{ 466 "aa": {cmpValue: "!="}, 467 "cc": {cmpValue: "!="}, 468 } 469 m.Put(context.Background(), "aa", "aa") 470 m.Put(context.Background(), "cc", "cc") 471 txnResp, err = m.batchPut(context.Background(), data, limit) 472 require.NoError(t, err) 473 require.True(t, txnResp.Succeeded) 474 }