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 }