github.com/matrixorigin/matrixone@v0.7.0/pkg/logservice/client_test.go (about)

     1  // Copyright 2021 - 2022 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package logservice
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"math"
    21  	"math/rand"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/cockroachdb/errors"
    26  	"github.com/lni/dragonboat/v4"
    27  	"github.com/lni/goutils/leaktest"
    28  	"github.com/lni/vfs"
    29  	"github.com/matrixorigin/matrixone/pkg/common/moerr"
    30  	"github.com/matrixorigin/matrixone/pkg/common/morpc"
    31  	"github.com/matrixorigin/matrixone/pkg/common/runtime"
    32  	"github.com/matrixorigin/matrixone/pkg/logutil"
    33  	pb "github.com/matrixorigin/matrixone/pkg/pb/logservice"
    34  	"github.com/matrixorigin/matrixone/pkg/testutil"
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  var testLogger = logutil.GetGlobalLogger().Named("logservice-test")
    40  
    41  var getClientConfig = func(readOnly bool) ClientConfig {
    42  	return ClientConfig{
    43  		ReadOnly:         readOnly,
    44  		LogShardID:       1,
    45  		DNReplicaID:      2,
    46  		ServiceAddresses: []string{testServiceAddress},
    47  		MaxMessageSize:   defaultMaxMessageSize,
    48  	}
    49  }
    50  
    51  func runClientTest(
    52  	t *testing.T,
    53  	readOnly bool,
    54  	cCfgFn func(bool) ClientConfig,
    55  	fn func(*testing.T, *Service, ClientConfig, Client)) {
    56  	runtime.SetupProcessLevelRuntime(runtime.DefaultRuntime())
    57  
    58  	defer leaktest.AfterTest(t)()
    59  	cfg := getServiceTestConfig()
    60  	defer vfs.ReportLeakedFD(cfg.FS, t)
    61  	service, err := NewService(cfg,
    62  		testutil.NewFS(),
    63  		WithBackendFilter(func(msg morpc.Message, backendAddr string) bool {
    64  			return true
    65  		}),
    66  	)
    67  	require.NoError(t, err)
    68  	defer func() {
    69  		assert.NoError(t, service.Close())
    70  	}()
    71  
    72  	init := make(map[uint64]string)
    73  	init[2] = service.ID()
    74  	assert.NoError(t, service.store.startReplica(1, 2, init, false))
    75  
    76  	if cCfgFn == nil {
    77  		cCfgFn = getClientConfig
    78  	}
    79  	scfg := cCfgFn(readOnly)
    80  
    81  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    82  	defer cancel()
    83  	c, err := NewClient(ctx, scfg)
    84  	require.NoError(t, err)
    85  	defer func() {
    86  		assert.NoError(t, c.Close())
    87  	}()
    88  
    89  	fn(t, service, scfg, c)
    90  }
    91  
    92  func TestClientConfigIsValidated(t *testing.T) {
    93  	cfg := ClientConfig{}
    94  	cc, err := NewClient(context.TODO(), cfg)
    95  	assert.Nil(t, cc)
    96  	assert.True(t, moerr.IsMoErrCode(err, moerr.ErrBadConfig))
    97  }
    98  
    99  func TestClientCanBeReset(t *testing.T) {
   100  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   101  		client := c.(*managedClient)
   102  		client.resetClient()
   103  		assert.Nil(t, client.client)
   104  	}
   105  	runClientTest(t, false, nil, fn)
   106  }
   107  
   108  func TestPrepareClient(t *testing.T) {
   109  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   110  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   111  		defer cancel()
   112  		client := c.(*managedClient)
   113  		assert.NoError(t, client.prepareClient(ctx))
   114  		client.resetClient()
   115  		assert.Nil(t, client.client)
   116  		assert.NoError(t, client.prepareClient(ctx))
   117  		assert.NotNil(t, client.client)
   118  	}
   119  	runClientTest(t, false, nil, fn)
   120  }
   121  
   122  func TestLogShardNotFoundErrorIsConsideredAsTempError(t *testing.T) {
   123  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   124  		require.NoError(t, s.store.stopReplica(1, 2))
   125  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   126  		defer cancel()
   127  		_, err := c.GetTSOTimestamp(ctx, 100)
   128  		assert.True(t, isTempError(err))
   129  		client := c.(*managedClient)
   130  		assert.True(t, client.isRetryableError(err))
   131  	}
   132  	runClientTest(t, false, nil, fn)
   133  }
   134  
   135  func TestClientCanBeCreated(t *testing.T) {
   136  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   137  	}
   138  	runClientTest(t, false, nil, fn)
   139  	runClientTest(t, true, nil, fn)
   140  }
   141  
   142  func TestClientCanBeConnectedByReverseProxy(t *testing.T) {
   143  	defer leaktest.AfterTest(t)()
   144  	cfg := getServiceTestConfig()
   145  	defer vfs.ReportLeakedFD(cfg.FS, t)
   146  	service, err := NewService(cfg,
   147  		testutil.NewFS(),
   148  		WithBackendFilter(func(msg morpc.Message, backendAddr string) bool {
   149  			return true
   150  		}),
   151  	)
   152  	require.NoError(t, err)
   153  	defer func() {
   154  		assert.NoError(t, service.Close())
   155  	}()
   156  
   157  	init := make(map[uint64]string)
   158  	init[2] = service.ID()
   159  	assert.NoError(t, service.store.startReplica(1, 2, init, false))
   160  
   161  	scfg := ClientConfig{
   162  		LogShardID:       1,
   163  		DNReplicaID:      2,
   164  		ServiceAddresses: []string{"localhost:53032"}, // unreachable
   165  		DiscoveryAddress: testServiceAddress,
   166  	}
   167  
   168  	done := false
   169  	for i := 0; i < 1000; i++ {
   170  		si, ok, err := GetShardInfo(testServiceAddress, 1)
   171  		if err != nil || !ok {
   172  			time.Sleep(10 * time.Millisecond)
   173  			continue
   174  		}
   175  		done = true
   176  		require.NoError(t, err)
   177  		assert.True(t, ok)
   178  		assert.Equal(t, uint64(2), si.ReplicaID)
   179  		addr, ok := si.Replicas[si.ReplicaID]
   180  		assert.True(t, ok)
   181  		assert.Equal(t, testServiceAddress, addr)
   182  		break
   183  	}
   184  	if !done {
   185  		t.Fatalf("failed to get shard info")
   186  	}
   187  
   188  	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
   189  	defer cancel()
   190  	c, err := NewClient(ctx, scfg)
   191  	require.NoError(t, err)
   192  	defer func() {
   193  		assert.NoError(t, c.Close())
   194  	}()
   195  }
   196  
   197  func TestClientGetTSOTimestamp(t *testing.T) {
   198  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   199  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   200  		defer cancel()
   201  		v, err := c.GetTSOTimestamp(ctx, 100)
   202  		require.NoError(t, err)
   203  		assert.Equal(t, uint64(1), v)
   204  
   205  		v, err = c.GetTSOTimestamp(ctx, 1000)
   206  		require.NoError(t, err)
   207  		assert.Equal(t, uint64(101), v)
   208  
   209  		v, err = c.GetTSOTimestamp(ctx, 100)
   210  		require.NoError(t, err)
   211  		assert.Equal(t, uint64(1101), v)
   212  	}
   213  	runClientTest(t, false, nil, fn)
   214  }
   215  
   216  func TestClientAppend(t *testing.T) {
   217  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   218  		rec := c.GetLogRecord(16)
   219  		rand.Read(rec.Payload())
   220  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   221  		defer cancel()
   222  		lsn, err := c.Append(ctx, rec)
   223  		require.NoError(t, err)
   224  		assert.Equal(t, uint64(4), lsn)
   225  
   226  		lsn, err = c.Append(ctx, rec)
   227  		require.NoError(t, err)
   228  		assert.Equal(t, uint64(5), lsn)
   229  
   230  		cmd := make([]byte, 16+headerSize+8)
   231  		cmd = getAppendCmd(cmd, cfg.DNReplicaID+1)
   232  		_, err = c.Append(ctx, pb.LogRecord{Data: cmd})
   233  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrNotLeaseHolder))
   234  	}
   235  	runClientTest(t, false, nil, fn)
   236  }
   237  
   238  // FIXME: actually enforce allowed allocation
   239  func TestClientAppendAlloc(t *testing.T) {
   240  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   241  		rec := c.GetLogRecord(16)
   242  		rand.Read(rec.Payload())
   243  		ac := testing.AllocsPerRun(1000, func() {
   244  			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   245  			defer cancel()
   246  			_, err := c.Append(ctx, rec)
   247  			require.NoError(t, err)
   248  		})
   249  		testLogger.Info(fmt.Sprintf("ac: %f", ac))
   250  	}
   251  	runClientTest(t, false, nil, fn)
   252  }
   253  
   254  func TestClientRead(t *testing.T) {
   255  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   256  		rec := c.GetLogRecord(16)
   257  		rand.Read(rec.Payload())
   258  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   259  		defer cancel()
   260  		lsn, err := c.Append(ctx, rec)
   261  		require.NoError(t, err)
   262  		assert.Equal(t, uint64(4), lsn)
   263  
   264  		rec2 := c.GetLogRecord(16)
   265  		rand.Read(rec2.Payload())
   266  		lsn, err = c.Append(ctx, rec2)
   267  		require.NoError(t, err)
   268  		assert.Equal(t, uint64(5), lsn)
   269  
   270  		// FIXME: returned records should contain correct Index value
   271  		recs, lsn, err := c.Read(ctx, 4, math.MaxUint64)
   272  		require.NoError(t, err)
   273  		assert.Equal(t, uint64(4), lsn)
   274  		require.Equal(t, 2, len(recs))
   275  		assert.Equal(t, rec.Data, recs[0].Data)
   276  		assert.Equal(t, rec2.Data, recs[1].Data)
   277  
   278  		_, _, err = c.Read(ctx, 6, math.MaxUint64)
   279  		assert.True(t, errors.Is(err, dragonboat.ErrInvalidRange))
   280  	}
   281  	runClientTest(t, false, nil, fn)
   282  }
   283  
   284  func TestClientTruncate(t *testing.T) {
   285  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   286  		rec := c.GetLogRecord(16)
   287  		rand.Read(rec.Payload())
   288  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   289  		defer cancel()
   290  		lsn, err := c.Append(ctx, rec)
   291  		require.NoError(t, err)
   292  		assert.Equal(t, uint64(4), lsn)
   293  
   294  		require.NoError(t, c.Truncate(ctx, 4))
   295  		lsn, err = c.GetTruncatedLsn(ctx)
   296  		assert.NoError(t, err)
   297  		assert.Equal(t, Lsn(4), lsn)
   298  
   299  		err = c.Truncate(ctx, 3)
   300  		assert.True(t, moerr.IsMoErrCode(err, moerr.ErrInvalidTruncateLsn))
   301  	}
   302  	runClientTest(t, false, nil, fn)
   303  }
   304  
   305  func TestReadOnlyClientRejectWriteRequests(t *testing.T) {
   306  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   307  		rec := c.GetLogRecord(16)
   308  		rand.Read(rec.Payload())
   309  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   310  		defer cancel()
   311  		_, err := c.Append(ctx, rec)
   312  		require.True(t, moerr.IsMoErrCode(err, moerr.ErrInvalidInput))
   313  		err = c.Truncate(ctx, 4)
   314  		require.True(t, moerr.IsMoErrCode(err, moerr.ErrInvalidInput))
   315  	}
   316  	runClientTest(t, true, nil, fn)
   317  }
   318  
   319  func TestClientSendWithMsgSize(t *testing.T) {
   320  	cFn := func(readOnly bool) ClientConfig {
   321  		return ClientConfig{
   322  			ReadOnly:         readOnly,
   323  			LogShardID:       1,
   324  			DNReplicaID:      2,
   325  			ServiceAddresses: []string{testServiceAddress},
   326  			MaxMessageSize:   testServerMaxMsgSize,
   327  		}
   328  	}
   329  
   330  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   331  		rec := c.GetLogRecord(testServerMaxMsgSize + 80)
   332  		rand.Read(rec.Payload())
   333  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   334  		defer cancel()
   335  		// client only writes message whose size less than 190
   336  		_, err := c.Append(ctx, rec)
   337  		require.Error(t, err)
   338  	}
   339  	runClientTest(t, false, cFn, fn)
   340  }
   341  
   342  func TestServerReceiveWithMsgSize(t *testing.T) {
   343  	fn := func(t *testing.T, s *Service, cfg ClientConfig, c Client) {
   344  		rec := c.GetLogRecord(16)
   345  		rand.Read(rec.Payload())
   346  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   347  		defer cancel()
   348  		_, err := c.Append(ctx, rec)
   349  		require.NoError(t, err)
   350  
   351  		rec = c.GetLogRecord(defaultMaxMessageSize + 20)
   352  		rand.Read(rec.Payload())
   353  		ctx, cancel = context.WithTimeout(context.Background(), time.Second)
   354  		defer cancel()
   355  		_, err = c.Append(ctx, rec)
   356  		require.Error(t, err)
   357  	}
   358  	runClientTest(t, false, nil, fn)
   359  }