github.com/m3db/m3@v1.5.0/src/dbnode/client/write_test.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package client
    22  
    23  import (
    24  	"errors"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/m3db/m3/src/cluster/shard"
    29  	tterrors "github.com/m3db/m3/src/dbnode/network/server/tchannelthrift/errors"
    30  	"github.com/m3db/m3/src/dbnode/topology"
    31  	"github.com/m3db/m3/src/x/checked"
    32  	xerrors "github.com/m3db/m3/src/x/errors"
    33  
    34  	"github.com/golang/mock/gomock"
    35  	"github.com/stretchr/testify/assert"
    36  	"github.com/stretchr/testify/require"
    37  )
    38  
    39  // shard state tests
    40  
    41  func testWriteSuccess(t *testing.T, state shard.State, success bool) {
    42  	var writeWg sync.WaitGroup
    43  
    44  	wState, s, host := writeTestSetup(t, &writeWg)
    45  	setShardStates(t, s, host, state)
    46  	wState.completionFn(host, nil)
    47  
    48  	if success {
    49  		assert.Equal(t, int32(1), wState.success)
    50  	} else {
    51  		assert.Equal(t, int32(0), wState.success)
    52  	}
    53  
    54  	writeTestTeardown(wState, &writeWg)
    55  }
    56  
    57  func TestWriteToAvailableShards(t *testing.T) {
    58  	testWriteSuccess(t, shard.Available, true)
    59  }
    60  
    61  func TestWriteToInitializingShards(t *testing.T) {
    62  	testWriteSuccess(t, shard.Initializing, false)
    63  }
    64  
    65  func TestWriteToLeavingShards(t *testing.T) {
    66  	testWriteSuccess(t, shard.Leaving, false)
    67  }
    68  
    69  // retryability test
    70  
    71  type errTestFn func(error) bool
    72  
    73  func retryabilityCheck(t *testing.T, wState *writeState, testFn errTestFn) {
    74  	require.True(t, len(wState.errors) == 1)
    75  	assert.True(t, testFn(wState.errors[0]))
    76  }
    77  
    78  func simpleRetryableTest(t *testing.T, passedErr error, customHost topology.Host, testFn errTestFn) {
    79  	var writeWg sync.WaitGroup
    80  
    81  	wState, _, host := writeTestSetup(t, &writeWg)
    82  	if customHost != nil {
    83  		host = customHost
    84  	}
    85  	wState.completionFn(host, passedErr)
    86  	retryabilityCheck(t, wState, testFn)
    87  	writeTestTeardown(wState, &writeWg)
    88  }
    89  
    90  func TestNonRetryableError(t *testing.T) {
    91  	simpleRetryableTest(t, xerrors.NewNonRetryableError(errors.New("")), nil, xerrors.IsNonRetryableError)
    92  }
    93  
    94  func TestBadRequestError(t *testing.T) {
    95  	simpleRetryableTest(t, tterrors.NewBadRequestError(errors.New("")), nil, IsBadRequestError)
    96  }
    97  
    98  func TestRetryableError(t *testing.T) {
    99  	simpleRetryableTest(t, xerrors.NewRetryableError(errors.New("")), nil, xerrors.IsRetryableError)
   100  }
   101  
   102  func TestBadHostID(t *testing.T) {
   103  	simpleRetryableTest(t, nil, fakeHost{id: "not a real host"}, xerrors.IsRetryableError)
   104  }
   105  
   106  func TestBadShardID(t *testing.T) {
   107  	var writeWg sync.WaitGroup
   108  
   109  	wState, _, host := writeTestSetup(t, &writeWg)
   110  	o := wState.op.(*writeOperation)
   111  	o.shardID = writeOperationZeroed.shardID
   112  	wState.completionFn(host, nil)
   113  	retryabilityCheck(t, wState, xerrors.IsRetryableError)
   114  	writeTestTeardown(wState, &writeWg)
   115  }
   116  
   117  func TestShardNotAvailable(t *testing.T) {
   118  	var writeWg sync.WaitGroup
   119  
   120  	wState, s, host := writeTestSetup(t, &writeWg)
   121  	setShardStates(t, s, host, shard.Initializing)
   122  	wState.completionFn(host, nil)
   123  	retryabilityCheck(t, wState, xerrors.IsRetryableError)
   124  	writeTestTeardown(wState, &writeWg)
   125  }
   126  
   127  func TestShardLeavingWithShardsLeavingCountTowardsConsistency(t *testing.T) {
   128  	var writeWg sync.WaitGroup
   129  
   130  	wState, s, host := writeTestSetup(t, &writeWg)
   131  	wState.shardsLeavingCountTowardsConsistency = true
   132  	setShardStates(t, s, host, shard.Leaving)
   133  	wState.completionFn(host, nil)
   134  	assert.Equal(t, int32(1), wState.success)
   135  	writeTestTeardown(wState, &writeWg)
   136  }
   137  
   138  // utils
   139  
   140  func getWriteState(s *session, w writeStub) *writeState {
   141  	wState := s.pools.writeState.Get()
   142  	s.state.RLock()
   143  	wState.consistencyLevel = s.state.writeLevel
   144  	wState.topoMap = s.state.topoMap
   145  	s.state.RUnlock()
   146  	o := s.pools.writeOperation.Get()
   147  	o.shardID = 0 // Any valid shardID
   148  	wState.op = o
   149  	wState.nsID = w.ns
   150  	wState.tsID = w.id
   151  	var clonedAnnotation checked.Bytes
   152  	if len(w.annotation) > 0 {
   153  		clonedAnnotation = s.pools.checkedBytes.Get(len(w.annotation))
   154  		clonedAnnotation.IncRef()
   155  		clonedAnnotation.AppendAll(w.annotation)
   156  	}
   157  	wState.annotation = clonedAnnotation
   158  	return wState
   159  }
   160  
   161  func setShardStates(t *testing.T, s *session, host topology.Host, state shard.State) {
   162  	s.state.RLock()
   163  	hostShardSet, ok := s.state.topoMap.LookupHostShardSet(host.ID())
   164  	s.state.RUnlock()
   165  	require.True(t, ok)
   166  
   167  	for _, hostShard := range hostShardSet.ShardSet().All() {
   168  		hostShard.SetState(state)
   169  	}
   170  }
   171  
   172  type fakeHost struct{ id string }
   173  
   174  func (f fakeHost) ID() string      { return f.id }
   175  func (f fakeHost) Address() string { return "" }
   176  func (f fakeHost) String() string  { return "" }
   177  
   178  func writeTestSetup(t *testing.T, writeWg *sync.WaitGroup) (*writeState, *session, topology.Host) {
   179  	ctrl := gomock.NewController(t)
   180  	defer ctrl.Finish()
   181  
   182  	s := newDefaultTestSession(t).(*session)
   183  	w := newWriteStub()
   184  
   185  	var completionFn completionFn
   186  	enqueueWg := mockHostQueues(ctrl, s, sessionTestReplicas, []testEnqueueFn{func(idx int, op op) {
   187  		completionFn = op.CompletionFn()
   188  	}})
   189  
   190  	require.NoError(t, s.Open())
   191  	defer func() {
   192  		require.NoError(t, s.Close())
   193  	}()
   194  
   195  	host := s.state.topoMap.Hosts()[0] // any host
   196  
   197  	wState := getWriteState(s, w)
   198  	wState.incRef() // for the test
   199  	wState.incRef() // allow introspection
   200  
   201  	// Begin write
   202  	writeWg.Add(1)
   203  	go func() {
   204  		s.Write(w.ns, w.id, w.t, w.value, w.unit, w.annotation)
   205  		writeWg.Done()
   206  	}()
   207  
   208  	// Callbacks
   209  
   210  	enqueueWg.Wait()
   211  	require.True(t, s.state.topoMap.Replicas() == sessionTestReplicas)
   212  	for i := 0; i < s.state.topoMap.Replicas(); i++ {
   213  		completionFn(host, nil) // maintain session state
   214  	}
   215  
   216  	return wState, s, host
   217  }
   218  
   219  func writeTestTeardown(wState *writeState, writeWg *sync.WaitGroup) {
   220  	wState.decRef() // end introspection
   221  	writeWg.Wait()  // wait for write to complete
   222  }