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  }