github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/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 "time" 24 25 "github.com/pingcap/check" 26 "github.com/pingcap/log" 27 cerror "github.com/pingcap/ticdc/pkg/errors" 28 "github.com/pingcap/ticdc/pkg/orchestrator/util" 29 "github.com/pingcap/ticdc/pkg/util/testleak" 30 "go.uber.org/zap" 31 ) 32 33 type bankReactorState struct { 34 c *check.C 35 account []int 36 pendingPatch [][]DataPatch 37 index int 38 notFirstTick bool 39 } 40 41 const bankTestPrefix = "/ticdc/test/bank/" 42 43 func (b *bankReactorState) Update(key util.EtcdKey, value []byte, isInit bool) error { 44 b.c.Assert(strings.HasPrefix(key.String(), bankTestPrefix), check.IsTrue) 45 indexStr := key.String()[len(bankTestPrefix):] 46 b.account[b.atoi(indexStr)] = b.atoi(string(value)) 47 return nil 48 } 49 50 func (b *bankReactorState) GetPatches() [][]DataPatch { 51 pendingPatches := b.pendingPatch 52 b.pendingPatch = nil 53 return pendingPatches 54 } 55 56 func (b *bankReactorState) Check() { 57 var sum int 58 for _, money := range b.account { 59 sum += money 60 } 61 if sum != 0 { 62 log.Info("show account", zap.Int("index", b.index), zap.Int("sum", sum), zap.Ints("account", b.account)) 63 } 64 b.c.Assert(sum, check.Equals, 0, check.Commentf("not ft:%t", b.notFirstTick)) 65 } 66 67 func (b *bankReactorState) atoi(value string) int { 68 i, err := strconv.Atoi(value) 69 b.c.Assert(err, check.IsNil) 70 return i 71 } 72 73 func (b *bankReactorState) patchAccount(index int, fn func(int) int) DataPatch { 74 return &SingleDataPatch{ 75 Key: util.NewEtcdKey(fmt.Sprintf("%s%d", bankTestPrefix, index)), 76 Func: func(old []byte) (newValue []byte, changed bool, err error) { 77 oldMoney := b.atoi(string(old)) 78 newMoney := fn(oldMoney) 79 if oldMoney == newMoney { 80 return old, false, nil 81 } 82 log.Debug("change money", zap.Int("account", index), zap.Int("from", oldMoney), zap.Int("to", newMoney)) 83 return []byte(strconv.Itoa(newMoney)), true, nil 84 }, 85 } 86 } 87 88 func (b *bankReactorState) TransferRandomly(transferNumber int) { 89 for i := 0; i < transferNumber; i++ { 90 accountA := rand.Intn(len(b.account)) 91 accountB := rand.Intn(len(b.account)) 92 transferMoney := rand.Intn(100) 93 b.pendingPatch = append(b.pendingPatch, []DataPatch{ 94 b.patchAccount(accountA, func(money int) int { 95 return money - transferMoney 96 }), 97 b.patchAccount(accountB, func(money int) int { 98 return money + transferMoney 99 }), 100 }) 101 log.Debug("transfer money", zap.Int("accountA", accountA), zap.Int("accountB", accountB), zap.Int("money", transferMoney)) 102 } 103 } 104 105 type bankReactor struct { 106 accountNumber int 107 } 108 109 func (b *bankReactor) Tick(ctx context.Context, state ReactorState) (nextState ReactorState, err error) { 110 bankState := (state).(*bankReactorState) 111 bankState.Check() 112 // transfer 20% of account 113 bankState.TransferRandomly(rand.Intn(b.accountNumber/5 + 2)) 114 // there is a 20% chance of restarting etcd worker 115 if rand.Intn(10) < 2 { 116 err = cerror.ErrReactorFinished.GenWithStackByArgs() 117 } 118 bankState.notFirstTick = true 119 return state, err 120 } 121 122 func (s *etcdWorkerSuite) TestEtcdBank(c *check.C) { 123 defer testleak.AfterTest(c)() 124 totalAccountNumber := 25 125 workerNumber := 10 126 var wg sync.WaitGroup 127 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 128 defer cancel() 129 130 newClient, closer := setUpTest(c) 131 defer closer() 132 133 cli := newClient() 134 defer func() { 135 _ = cli.Unwrap().Close() 136 }() 137 138 for i := 0; i < totalAccountNumber; i++ { 139 _, err := cli.Put(ctx, fmt.Sprintf("%s%d", bankTestPrefix, i), "0") 140 c.Assert(err, check.IsNil) 141 } 142 143 for i := 0; i < workerNumber; i++ { 144 i := i 145 wg.Add(1) 146 go func() { 147 defer wg.Done() 148 for { 149 worker, err := NewEtcdWorker(cli, bankTestPrefix, &bankReactor{ 150 accountNumber: totalAccountNumber, 151 }, &bankReactorState{c: c, index: i, account: make([]int, totalAccountNumber)}) 152 c.Assert(err, check.IsNil) 153 err = worker.Run(ctx, nil, 100*time.Millisecond) 154 if err == nil || err.Error() == "etcdserver: request timed out" { 155 continue 156 } 157 c.Assert(err, check.ErrorMatches, ".*context deadline exceeded.*") 158 return 159 } 160 }() 161 } 162 wg.Wait() 163 }