github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/apply/task_test.go (about) 1 // Copyright 2019 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package apply_test 12 13 import ( 14 "context" 15 "fmt" 16 "testing" 17 18 "github.com/cockroachdb/cockroach/pkg/kv/kvserver/apply" 19 "github.com/cockroachdb/errors" 20 "github.com/stretchr/testify/require" 21 "go.etcd.io/etcd/raft/raftpb" 22 ) 23 24 // logging is used for Example. 25 var logging bool 26 27 func setLogging(on bool) func() { 28 bef := logging 29 logging = on 30 return func() { 31 logging = bef 32 } 33 } 34 35 type cmd struct { 36 index uint64 37 nonTrivial bool 38 nonLocal bool 39 shouldReject bool 40 41 acked bool 42 finished bool 43 } 44 45 type checkedCmd struct { 46 *cmd 47 rejected bool 48 } 49 50 type appliedCmd struct { 51 *checkedCmd 52 } 53 54 func (c *cmd) Index() uint64 { return c.index } 55 func (c *cmd) IsTrivial() bool { return !c.nonTrivial } 56 func (c *cmd) IsLocal() bool { return !c.nonLocal } 57 func (c *checkedCmd) Rejected() bool { return c.rejected } 58 func (c *checkedCmd) CanAckBeforeApplication() bool { return true } 59 func (c *checkedCmd) AckSuccess() error { 60 c.acked = true 61 if logging { 62 fmt.Printf(" acknowledging command %d before application\n", c.Index()) 63 } 64 return nil 65 } 66 func (c *appliedCmd) FinishAndAckOutcome(context.Context) error { 67 c.finished = true 68 if c.acked { 69 if logging { 70 fmt.Printf(" finishing command %d; rejected=%t\n", c.Index(), c.Rejected()) 71 } 72 } else { 73 if logging { 74 fmt.Printf(" finishing and acknowledging command %d; rejected=%t\n", c.Index(), c.Rejected()) 75 } 76 c.acked = true 77 } 78 return nil 79 } 80 81 type cmdSlice []*cmd 82 type checkedCmdSlice []*checkedCmd 83 type appliedCmdSlice []*appliedCmd 84 85 func (s *cmdSlice) Valid() bool { return len(*s) > 0 } 86 func (s *cmdSlice) Next() { *s = (*s)[1:] } 87 func (s *cmdSlice) NewList() apply.CommandList { return new(cmdSlice) } 88 func (s *cmdSlice) NewCheckedList() apply.CheckedCommandList { return new(checkedCmdSlice) } 89 func (s *cmdSlice) Close() {} 90 func (s *cmdSlice) Cur() apply.Command { return (*s)[0] } 91 func (s *cmdSlice) Append(c apply.Command) { *s = append(*s, c.(*cmd)) } 92 93 func (s *checkedCmdSlice) Valid() bool { return len(*s) > 0 } 94 func (s *checkedCmdSlice) Next() { *s = (*s)[1:] } 95 func (s *checkedCmdSlice) NewAppliedList() apply.AppliedCommandList { return new(appliedCmdSlice) } 96 func (s *checkedCmdSlice) Close() {} 97 func (s *checkedCmdSlice) CurChecked() apply.CheckedCommand { return (*s)[0] } 98 func (s *checkedCmdSlice) AppendChecked(c apply.CheckedCommand) { *s = append(*s, c.(*checkedCmd)) } 99 100 func (s *appliedCmdSlice) Valid() bool { return len(*s) > 0 } 101 func (s *appliedCmdSlice) Next() { *s = (*s)[1:] } 102 func (s *appliedCmdSlice) Close() {} 103 func (s *appliedCmdSlice) CurApplied() apply.AppliedCommand { return (*s)[0] } 104 func (s *appliedCmdSlice) AppendApplied(c apply.AppliedCommand) { *s = append(*s, c.(*appliedCmd)) } 105 106 var _ apply.Command = &cmd{} 107 var _ apply.CheckedCommand = &checkedCmd{} 108 var _ apply.AppliedCommand = &appliedCmd{} 109 var _ apply.CommandList = &cmdSlice{} 110 var _ apply.CheckedCommandList = &checkedCmdSlice{} 111 var _ apply.AppliedCommandList = &appliedCmdSlice{} 112 113 type testStateMachine struct { 114 batches [][]uint64 115 applied []uint64 116 appliedSideEffects []uint64 117 batchOpen bool 118 } 119 120 func getTestStateMachine() *testStateMachine { 121 return new(testStateMachine) 122 } 123 124 func (sm *testStateMachine) NewBatch(ephemeral bool) apply.Batch { 125 if sm.batchOpen { 126 panic("batch not closed") 127 } 128 sm.batchOpen = true 129 return &testBatch{sm: sm, ephemeral: ephemeral} 130 } 131 func (sm *testStateMachine) ApplySideEffects( 132 cmdI apply.CheckedCommand, 133 ) (apply.AppliedCommand, error) { 134 cmd := cmdI.(*checkedCmd) 135 sm.appliedSideEffects = append(sm.appliedSideEffects, cmd.index) 136 if logging { 137 fmt.Printf(" applying side-effects of command %d\n", cmd.Index()) 138 } 139 acmd := appliedCmd{checkedCmd: cmd} 140 return &acmd, nil 141 } 142 143 type testBatch struct { 144 sm *testStateMachine 145 ephemeral bool 146 staged []uint64 147 } 148 149 func (b *testBatch) Stage(cmdI apply.Command) (apply.CheckedCommand, error) { 150 cmd := cmdI.(*cmd) 151 b.staged = append(b.staged, cmd.index) 152 ccmd := checkedCmd{cmd: cmd, rejected: cmd.shouldReject} 153 return &ccmd, nil 154 } 155 func (b *testBatch) ApplyToStateMachine(_ context.Context) error { 156 if b.ephemeral { 157 return errors.New("can't commit an ephemeral batch") 158 } 159 b.sm.batches = append(b.sm.batches, b.staged) 160 b.sm.applied = append(b.sm.applied, b.staged...) 161 if logging { 162 fmt.Printf(" applying batch with commands=%v\n", b.staged) 163 } 164 return nil 165 } 166 func (b *testBatch) Close() { 167 b.sm.batchOpen = false 168 } 169 170 type testDecoder struct { 171 nonTrivial map[uint64]bool 172 nonLocal map[uint64]bool 173 shouldReject map[uint64]bool 174 175 cmds []*cmd 176 } 177 178 func newTestDecoder() *testDecoder { 179 return &testDecoder{ 180 nonTrivial: make(map[uint64]bool), 181 nonLocal: make(map[uint64]bool), 182 shouldReject: make(map[uint64]bool), 183 } 184 } 185 186 func (d *testDecoder) DecodeAndBind(_ context.Context, ents []raftpb.Entry) (bool, error) { 187 d.cmds = make([]*cmd, len(ents)) 188 for i, ent := range ents { 189 idx := ent.Index 190 cmd := &cmd{ 191 index: idx, 192 nonTrivial: d.nonTrivial[idx], 193 nonLocal: d.nonLocal[idx], 194 shouldReject: d.shouldReject[idx], 195 } 196 d.cmds[i] = cmd 197 if logging { 198 fmt.Printf(" decoding command %d; local=%t\n", cmd.Index(), cmd.IsLocal()) 199 } 200 } 201 return true, nil 202 } 203 func (d *testDecoder) NewCommandIter() apply.CommandIterator { 204 it := cmdSlice(d.cmds) 205 return &it 206 } 207 func (d *testDecoder) Reset() {} 208 209 func makeEntries(num int) []raftpb.Entry { 210 ents := make([]raftpb.Entry, num) 211 for i := range ents { 212 ents[i].Index = uint64(i + 1) 213 } 214 return ents 215 } 216 217 func TestApplyCommittedEntries(t *testing.T) { 218 ctx := context.Background() 219 ents := makeEntries(6) 220 221 sm := getTestStateMachine() 222 dec := newTestDecoder() 223 dec.nonTrivial[3] = true 224 dec.nonTrivial[4] = true 225 dec.nonTrivial[6] = true 226 227 // Use an apply.Task to apply all commands. 228 appT := apply.MakeTask(sm, dec) 229 defer appT.Close() 230 require.NoError(t, appT.Decode(ctx, ents)) 231 require.NoError(t, appT.ApplyCommittedEntries(ctx)) 232 233 // Assert that all commands were applied in the correct batches. 234 exp := testStateMachine{ 235 batches: [][]uint64{{1, 2}, {3}, {4}, {5}, {6}}, 236 applied: []uint64{1, 2, 3, 4, 5, 6}, 237 appliedSideEffects: []uint64{1, 2, 3, 4, 5, 6}, 238 } 239 require.Equal(t, exp, *sm) 240 241 // Assert that all commands were acknowledged and finished. 242 for _, cmd := range dec.cmds { 243 require.True(t, cmd.acked) 244 require.True(t, cmd.finished) 245 } 246 } 247 248 func TestApplyCommittedEntriesWithBatchSize(t *testing.T) { 249 ctx := context.Background() 250 ents := makeEntries(7) 251 252 sm := getTestStateMachine() 253 dec := newTestDecoder() 254 dec.nonTrivial[4] = true 255 256 // Use an apply.Task to apply all commands with a batch size limit. 257 appT := apply.MakeTask(sm, dec) 258 appT.SetMaxBatchSize(2) 259 defer appT.Close() 260 require.NoError(t, appT.Decode(ctx, ents)) 261 require.NoError(t, appT.ApplyCommittedEntries(ctx)) 262 263 // Assert that all commands were applied in the correct batches. 264 exp := testStateMachine{ 265 batches: [][]uint64{{1, 2}, {3}, {4}, {5, 6}, {7}}, 266 applied: []uint64{1, 2, 3, 4, 5, 6, 7}, 267 appliedSideEffects: []uint64{1, 2, 3, 4, 5, 6, 7}, 268 } 269 require.Equal(t, exp, *sm) 270 271 // Assert that all commands were acknowledged and finished. 272 for _, cmd := range dec.cmds { 273 require.True(t, cmd.acked) 274 require.True(t, cmd.finished) 275 } 276 } 277 278 func TestAckCommittedEntriesBeforeApplication(t *testing.T) { 279 ctx := context.Background() 280 ents := makeEntries(9) 281 282 sm := getTestStateMachine() 283 dec := newTestDecoder() 284 dec.nonTrivial[6] = true 285 dec.nonTrivial[7] = true 286 dec.nonTrivial[9] = true 287 dec.nonLocal[2] = true 288 dec.shouldReject[3] = true 289 290 // Use an apply.Task to ack all commands before applying them. 291 appT := apply.MakeTask(sm, dec) 292 defer appT.Close() 293 require.NoError(t, appT.Decode(ctx, ents)) 294 require.NoError(t, appT.AckCommittedEntriesBeforeApplication(ctx, 10 /* maxIndex */)) 295 296 // Assert that the state machine was not updated. 297 require.Equal(t, testStateMachine{}, *sm) 298 299 // Assert that some commands were acknowledged early and that none were finished. 300 for _, cmd := range dec.cmds { 301 var exp bool 302 switch cmd.index { 303 case 1, 4, 5: 304 exp = true // local and successful 305 case 2: 306 exp = false // remote 307 case 3: 308 exp = false // local and rejected 309 default: 310 exp = false // after first non-trivial cmd 311 } 312 require.Equal(t, exp, cmd.acked) 313 require.False(t, cmd.finished) 314 } 315 316 // Try again with a lower maximum log index. 317 appT.Close() 318 ents = makeEntries(5) 319 320 dec = newTestDecoder() 321 dec.nonLocal[2] = true 322 dec.shouldReject[3] = true 323 324 appT = apply.MakeTask(sm, dec) 325 require.NoError(t, appT.Decode(ctx, ents)) 326 require.NoError(t, appT.AckCommittedEntriesBeforeApplication(ctx, 4 /* maxIndex */)) 327 328 // Assert that the state machine was not updated. 329 require.Equal(t, testStateMachine{}, *sm) 330 331 // Assert that some commands were acknowledged early and that none were finished. 332 for _, cmd := range dec.cmds { 333 var exp bool 334 switch cmd.index { 335 case 1, 4: 336 exp = true // local and successful 337 case 2: 338 exp = false // remote 339 case 3: 340 exp = false // local and rejected 341 case 5: 342 exp = false // index too high 343 default: 344 t.Fatalf("unexpected index %d", cmd.index) 345 } 346 require.Equal(t, exp, cmd.acked) 347 require.False(t, cmd.finished) 348 } 349 }