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  }