github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/orchestrator/etcd_worker_bank_test.go (about) 1 // Copyright 2021 PingCAP, Inc. 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 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package orchestrator 15 16 import ( 17 "context" 18 "fmt" 19 "math/rand" 20 "strconv" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/pingcap/failpoint" 27 "github.com/pingcap/log" 28 "github.com/pingcap/tiflow/pkg/errors" 29 "github.com/pingcap/tiflow/pkg/etcd" 30 "github.com/pingcap/tiflow/pkg/migrate" 31 "github.com/pingcap/tiflow/pkg/orchestrator/util" 32 "github.com/stretchr/testify/require" 33 "go.uber.org/zap" 34 ) 35 36 type bankReactorState struct { 37 t *testing.T 38 account []int 39 pendingPatch [][]DataPatch 40 index int 41 notFirstTick bool 42 } 43 44 const bankTestPrefix = "/ticdc/test/bank/" 45 46 func (b *bankReactorState) UpdatePendingChange() { 47 } 48 49 func (b *bankReactorState) Update(key util.EtcdKey, value []byte, isInit bool) error { 50 require.True(b.t, strings.HasPrefix(key.String(), bankTestPrefix)) 51 indexStr := key.String()[len(bankTestPrefix):] 52 b.account[b.atoi(indexStr)] = b.atoi(string(value)) 53 return nil 54 } 55 56 func (b *bankReactorState) GetPatches() [][]DataPatch { 57 pendingPatches := b.pendingPatch 58 b.pendingPatch = nil 59 return pendingPatches 60 } 61 62 func (b *bankReactorState) Check() { 63 var sum int 64 for _, money := range b.account { 65 sum += money 66 } 67 if sum != 0 { 68 log.Info("show account", zap.Int("index", b.index), zap.Int("sum", sum), zap.Ints("account", b.account)) 69 } 70 require.Equal(b.t, sum, 0, fmt.Sprintf("not ft:%t", b.notFirstTick)) 71 } 72 73 func (b *bankReactorState) atoi(value string) int { 74 i, err := strconv.Atoi(value) 75 require.Nil(b.t, err) 76 return i 77 } 78 79 func (b *bankReactorState) patchAccount(index int, fn func(int) int) DataPatch { 80 return &SingleDataPatch{ 81 Key: util.NewEtcdKey(fmt.Sprintf("%s%d", bankTestPrefix, index)), 82 Func: func(old []byte) (newValue []byte, changed bool, err error) { 83 oldMoney := b.atoi(string(old)) 84 newMoney := fn(oldMoney) 85 if oldMoney == newMoney { 86 return old, false, nil 87 } 88 log.Debug("change money", zap.Int("account", index), zap.Int("from", oldMoney), zap.Int("to", newMoney)) 89 return []byte(strconv.Itoa(newMoney)), true, nil 90 }, 91 } 92 } 93 94 func (b *bankReactorState) TransferRandomly(transferNumber int) { 95 for i := 0; i < transferNumber; i++ { 96 accountA := rand.Intn(len(b.account)) 97 accountB := rand.Intn(len(b.account)) 98 transferMoney := rand.Intn(100) 99 b.pendingPatch = append(b.pendingPatch, []DataPatch{ 100 b.patchAccount(accountA, func(money int) int { 101 return money - transferMoney 102 }), 103 b.patchAccount(accountB, func(money int) int { 104 return money + transferMoney 105 }), 106 }) 107 log.Debug("transfer money", zap.Int("accountA", accountA), zap.Int("accountB", accountB), zap.Int("money", transferMoney)) 108 } 109 } 110 111 type bankReactor struct { 112 accountNumber int 113 } 114 115 func (b *bankReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) { 116 bankState := (state).(*bankReactorState) 117 bankState.Check() 118 // transfer 20% of account 119 bankState.TransferRandomly(rand.Intn(b.accountNumber/5 + 2)) 120 // there is a 20% chance of restarting etcd worker 121 if rand.Intn(10) < 2 { 122 err = errors.ErrReactorFinished.GenWithStackByArgs() 123 } 124 bankState.notFirstTick = true 125 return state, err 126 } 127 128 func TestEtcdBank(t *testing.T) { 129 _ = failpoint.Enable("github.com/pingcap/tiflow/pkg/orchestrator/InjectProgressRequestAfterCommit", "10%return(true)") 130 defer func() { 131 _ = failpoint.Disable("github.com/pingcap/tiflow/pkg/orchestrator/InjectProgressRequestAfterCommit") 132 }() 133 134 totalAccountNumber := 25 135 workerNumber := 10 136 var wg sync.WaitGroup 137 138 newClient, closer := setUpTest(t) 139 defer closer() 140 141 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 142 cli := newClient() 143 cdcCli, err := etcd.NewCDCEtcdClient(ctx, cli.Unwrap(), "default") 144 require.Nil(t, err) 145 146 defer func() { 147 _ = cli.Unwrap().Close() 148 }() 149 150 defer cancel() 151 for i := 0; i < totalAccountNumber; i++ { 152 _, err := cli.Put(ctx, fmt.Sprintf("%s%d", bankTestPrefix, i), "0") 153 require.Nil(t, err) 154 } 155 156 for i := 0; i < workerNumber; i++ { 157 i := i 158 wg.Add(1) 159 go func() { 160 defer wg.Done() 161 for { 162 worker, err := NewEtcdWorker(cdcCli, bankTestPrefix, &bankReactor{ 163 accountNumber: totalAccountNumber, 164 }, &bankReactorState{t: t, index: i, account: make([]int, totalAccountNumber)}, 165 &migrate.NoOpMigrator{}) 166 require.Nil(t, err) 167 err = worker.Run(ctx, nil, 100*time.Millisecond, "owner") 168 if err == nil || err.Error() == "etcdserver: request timed out" { 169 continue 170 } 171 require.Contains(t, err.Error(), "context deadline exceeded") 172 return 173 } 174 }() 175 } 176 wg.Wait() 177 }