github.com/matrixorigin/matrixone@v0.7.0/pkg/txn/service/service_testutil.go (about) 1 // Copyright 2021 - 2022 Matrix Origin 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 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package service 16 17 import ( 18 "context" 19 "fmt" 20 "math" 21 "sync" 22 "sync/atomic" 23 "testing" 24 "time" 25 26 "github.com/matrixorigin/matrixone/pkg/common/runtime" 27 "github.com/matrixorigin/matrixone/pkg/logservice" 28 "github.com/matrixorigin/matrixone/pkg/logutil" 29 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 30 "github.com/matrixorigin/matrixone/pkg/pb/timestamp" 31 "github.com/matrixorigin/matrixone/pkg/pb/txn" 32 "github.com/matrixorigin/matrixone/pkg/txn/clock" 33 "github.com/matrixorigin/matrixone/pkg/txn/rpc" 34 "github.com/matrixorigin/matrixone/pkg/txn/storage" 35 "github.com/matrixorigin/matrixone/pkg/txn/storage/mem" 36 "go.uber.org/zap" 37 "go.uber.org/zap/zapcore" 38 ) 39 40 // NewTestTxnService create a test TxnService for test 41 func NewTestTxnService(t *testing.T, shard uint64, sender rpc.TxnSender, clock clock.Clock) TxnService { 42 return NewTestTxnServiceWithLog(t, shard, sender, clock, nil) 43 } 44 45 // NewTestTxnServiceWithLog is similar to NewTestTxnService, used to recovery tests 46 func NewTestTxnServiceWithLog(t *testing.T, 47 shard uint64, 48 sender rpc.TxnSender, 49 clock clock.Clock, 50 log logservice.Client) TxnService { 51 return NewTestTxnServiceWithLogAndZombie(t, 52 shard, 53 sender, 54 clock, 55 log, 56 time.Minute) 57 } 58 59 // NewTestTxnServiceWithLogAndZombie is similar to NewTestTxnService, but with more args 60 func NewTestTxnServiceWithLogAndZombie(t *testing.T, 61 shard uint64, 62 sender rpc.TxnSender, 63 clock clock.Clock, 64 log logservice.Client, 65 zombie time.Duration) TxnService { 66 rt := runtime.NewRuntime( 67 metadata.ServiceType_DN, 68 "dn-uuid", 69 logutil.GetPanicLoggerWithLevel(zapcore.DebugLevel).With(zap.String("case", t.Name())), 70 runtime.WithClock(clock)) 71 return NewTxnService(rt, 72 NewTestDNShard(shard), 73 NewTestTxnStorage(log, clock), 74 sender, 75 zombie).(*service) 76 } 77 78 // NewTestTxnStorage create a TxnStorage used to recovery tests 79 func NewTestTxnStorage(log logservice.Client, clock clock.Clock) storage.TxnStorage { 80 if log == nil { 81 log = mem.NewMemLog() 82 } 83 return mem.NewKVTxnStorage(1, log, clock) 84 } 85 86 // NewTestDNShard create a test DNShard 87 func NewTestDNShard(id uint64) metadata.DNShard { 88 return metadata.DNShard{ 89 DNShardRecord: metadata.DNShardRecord{ 90 ShardID: id, 91 LogShardID: id, 92 }, 93 ReplicaID: id, 94 Address: fmt.Sprintf("dn-%d", id), 95 } 96 } 97 98 // NewTestClock create test clock with start timestamp 99 func NewTestClock(start int64) clock.Clock { 100 ts := start 101 return clock.NewHLCClock(func() int64 { 102 return atomic.AddInt64(&ts, 1) 103 }, math.MaxInt64) 104 } 105 106 // NewTestSpecClock create test clock with timestamp factory 107 func NewTestSpecClock(fn func() int64) clock.Clock { 108 return clock.NewHLCClock(fn, math.MaxInt64) 109 } 110 111 // TestSender test TxnSender for sending messages between TxnServices 112 type TestSender struct { 113 router map[string]rpc.TxnRequestHandleFunc 114 filter func(*txn.TxnRequest) bool 115 116 mu struct { 117 sync.Mutex 118 cancels []context.CancelFunc 119 } 120 } 121 122 // NewTestSender create test TxnSender 123 func NewTestSender(services ...TxnService) *TestSender { 124 s := &TestSender{ 125 router: make(map[string]rpc.TxnRequestHandleFunc), 126 } 127 for _, ts := range services { 128 s.AddTxnService(ts) 129 } 130 return s 131 } 132 133 // AddTxnService add txnservice into test TxnSender 134 func (s *TestSender) AddTxnService(ts TxnService) { 135 s.router[s.getRouteKey(txn.TxnMethod_Read, ts.Shard())] = ts.Read 136 s.router[s.getRouteKey(txn.TxnMethod_Write, ts.Shard())] = ts.Write 137 s.router[s.getRouteKey(txn.TxnMethod_Commit, ts.Shard())] = ts.Commit 138 s.router[s.getRouteKey(txn.TxnMethod_Rollback, ts.Shard())] = ts.Rollback 139 s.router[s.getRouteKey(txn.TxnMethod_Prepare, ts.Shard())] = ts.Prepare 140 s.router[s.getRouteKey(txn.TxnMethod_GetStatus, ts.Shard())] = ts.GetStatus 141 s.router[s.getRouteKey(txn.TxnMethod_CommitDNShard, ts.Shard())] = ts.CommitDNShard 142 s.router[s.getRouteKey(txn.TxnMethod_RollbackDNShard, ts.Shard())] = ts.RollbackDNShard 143 s.router[s.getRouteKey(txn.TxnMethod_DEBUG, ts.Shard())] = ts.Debug 144 } 145 146 func (s *TestSender) setFilter(filter func(*txn.TxnRequest) bool) { 147 s.filter = filter 148 } 149 150 // Send TxnSender send 151 func (s *TestSender) Send(ctx context.Context, requests []txn.TxnRequest) (*rpc.SendResult, error) { 152 ctx, cancel := context.WithTimeout(ctx, time.Minute) 153 s.mu.Lock() 154 s.mu.cancels = append(s.mu.cancels, cancel) 155 s.mu.Unlock() 156 157 responses := make([]txn.TxnResponse, 0, len(requests)) 158 for _, req := range requests { 159 if s.filter != nil && !s.filter(&req) { 160 continue 161 } 162 163 resp := txn.TxnResponse{} 164 h := s.router[s.getRouteKey(req.Method, req.GetTargetDN())] 165 if err := h(ctx, &req, &resp); err != nil { 166 return nil, err 167 } 168 responses = append(responses, resp) 169 } 170 return &rpc.SendResult{Responses: responses}, nil 171 } 172 173 // Close close the test TxnSender 174 func (s *TestSender) Close() error { 175 s.mu.Lock() 176 defer s.mu.Unlock() 177 for _, cancel := range s.mu.cancels { 178 cancel() 179 } 180 return nil 181 } 182 183 func (s *TestSender) getRouteKey(method txn.TxnMethod, shard metadata.DNShard) string { 184 return fmt.Sprintf("%d-%s", shard.ShardID, method.String()) 185 } 186 187 // NewTestTxn create a transaction, specifying both the transaction snapshot time and the DNShard 188 // for the transaction operation. 189 func NewTestTxn(txnID byte, ts int64, shards ...uint64) txn.TxnMeta { 190 txnMeta := txn.TxnMeta{ 191 ID: []byte{txnID}, 192 Status: txn.TxnStatus_Active, 193 SnapshotTS: NewTestTimestamp(ts), 194 } 195 for _, shard := range shards { 196 txnMeta.DNShards = append(txnMeta.DNShards, NewTestDNShard(shard)) 197 } 198 return txnMeta 199 } 200 201 // NewTestTimestamp create a test timestamp and set only the PhysicalTime field 202 func NewTestTimestamp(ts int64) timestamp.Timestamp { 203 return timestamp.Timestamp{PhysicalTime: ts} 204 } 205 206 // NewTestWriteRequest create a Write request, using GetTestKey and GetTestValue as the KV data for 207 // the test. 208 func NewTestWriteRequest(k byte, wTxn txn.TxnMeta, toShard uint64) txn.TxnRequest { 209 key := GetTestKey(k) 210 value := GetTestValue(k, wTxn) 211 req := mem.NewSetTxnRequest([][]byte{key}, [][]byte{value}) 212 req.Txn = wTxn 213 req.CNRequest.Target = NewTestDNShard(toShard) 214 return req 215 } 216 217 // NewTestReadRequest create a read request, using GetTestKey as the KV data for the test. 218 func NewTestReadRequest(k byte, rTxn txn.TxnMeta, toShard uint64) txn.TxnRequest { 219 key := GetTestKey(k) 220 req := mem.NewGetTxnRequest([][]byte{key}) 221 req.Txn = rTxn 222 req.CNRequest.Target = NewTestDNShard(toShard) 223 return req 224 } 225 226 // NewTestCommitRequest create a commit request 227 func NewTestCommitRequest(wTxn txn.TxnMeta) txn.TxnRequest { 228 return txn.TxnRequest{ 229 Method: txn.TxnMethod_Commit, 230 Txn: wTxn, 231 CommitRequest: &txn.TxnCommitRequest{}, 232 } 233 } 234 235 // NewTestCommitShardRequest create a commit DNShard request 236 func NewTestCommitShardRequest(wTxn txn.TxnMeta) txn.TxnRequest { 237 return txn.TxnRequest{ 238 Method: txn.TxnMethod_CommitDNShard, 239 Txn: wTxn, 240 CommitDNShardRequest: &txn.TxnCommitDNShardRequest{ 241 DNShard: wTxn.DNShards[0], 242 }, 243 } 244 } 245 246 // NewTestRollbackShardRequest create a rollback DNShard request 247 func NewTestRollbackShardRequest(wTxn txn.TxnMeta) txn.TxnRequest { 248 return txn.TxnRequest{ 249 Method: txn.TxnMethod_RollbackDNShard, 250 Txn: wTxn, 251 RollbackDNShardRequest: &txn.TxnRollbackDNShardRequest{ 252 DNShard: wTxn.DNShards[0], 253 }, 254 } 255 } 256 257 // NewTestRollbackRequest create a rollback request 258 func NewTestRollbackRequest(wTxn txn.TxnMeta) txn.TxnRequest { 259 return txn.TxnRequest{ 260 Method: txn.TxnMethod_Rollback, 261 Txn: wTxn, 262 RollbackRequest: &txn.TxnRollbackRequest{}, 263 } 264 } 265 266 // NewTestPrepareRequest create a prepare request 267 func NewTestPrepareRequest(wTxn txn.TxnMeta, shard uint64) txn.TxnRequest { 268 return txn.TxnRequest{ 269 Method: txn.TxnMethod_Prepare, 270 Txn: wTxn, 271 PrepareRequest: &txn.TxnPrepareRequest{ 272 DNShard: NewTestDNShard(shard), 273 }, 274 } 275 } 276 277 // NewTestGetStatusRequest create a get status request 278 func NewTestGetStatusRequest(wTxn txn.TxnMeta, shard uint64) txn.TxnRequest { 279 return txn.TxnRequest{ 280 Method: txn.TxnMethod_GetStatus, 281 Txn: wTxn, 282 GetStatusRequest: &txn.TxnGetStatusRequest{ 283 DNShard: NewTestDNShard(shard), 284 }, 285 } 286 } 287 288 // GetTestKey encode test key 289 func GetTestKey(k byte) []byte { 290 return []byte{k} 291 } 292 293 // GetTestValue encode test value based on the key and txn's snapshot timestamp 294 func GetTestValue(k byte, wTxn txn.TxnMeta) []byte { 295 return []byte(fmt.Sprintf("%d-%d-%d", k, wTxn.ID[0], wTxn.SnapshotTS.PhysicalTime)) 296 }