mirror of https://github.com/dtm-labs/dtm.git
committed by
GitHub
21 changed files with 440 additions and 71 deletions
@ -0,0 +1,102 @@ |
|||
package dtmcli |
|||
|
|||
import ( |
|||
"context" |
|||
"fmt" |
|||
"strings" |
|||
|
|||
"github.com/dtm-labs/dtm/dtmcli/dtmimp" |
|||
"github.com/dtm-labs/dtm/dtmcli/logger" |
|||
"go.mongodb.org/mongo-driver/bson" |
|||
"go.mongodb.org/mongo-driver/mongo" |
|||
) |
|||
|
|||
// MongoCall sub-trans barrier for mongo. see http://dtm.pub/practice/barrier
|
|||
// experimental
|
|||
func (bb *BranchBarrier) MongoCall(mc *mongo.Client, busiCall func(mongo.SessionContext) error) (rerr error) { |
|||
bb.BarrierID = bb.BarrierID + 1 |
|||
bid := fmt.Sprintf("%02d", bb.BarrierID) |
|||
return mc.UseSession(context.Background(), func(sc mongo.SessionContext) (rerr error) { |
|||
rerr = sc.StartTransaction() |
|||
if rerr != nil { |
|||
return nil |
|||
} |
|||
defer dtmimp.DeferDo(&rerr, func() error { |
|||
return sc.CommitTransaction(sc) |
|||
}, func() error { |
|||
return sc.AbortTransaction(sc) |
|||
}) |
|||
originOp := map[string]string{ |
|||
BranchCancel: BranchTry, |
|||
BranchCompensate: BranchAction, |
|||
}[bb.Op] |
|||
|
|||
originAffected, oerr := mongoInsertBarrier(sc, mc, bb.TransType, bb.Gid, bb.BranchID, originOp, bid, bb.Op) |
|||
currentAffected, rerr := mongoInsertBarrier(sc, mc, bb.TransType, bb.Gid, bb.BranchID, bb.Op, bid, bb.Op) |
|||
logger.Debugf("originAffected: %d currentAffected: %d", originAffected, currentAffected) |
|||
if rerr == nil { |
|||
rerr = oerr |
|||
} |
|||
if (bb.Op == BranchCancel || bb.Op == BranchCompensate) && originAffected > 0 || // null compensate
|
|||
currentAffected == 0 { // repeated request or dangled request
|
|||
return |
|||
} |
|||
if rerr == nil { |
|||
rerr = busiCall(sc) |
|||
} |
|||
return |
|||
}) |
|||
} |
|||
|
|||
// MongoQueryPrepared query prepared for redis
|
|||
// experimental
|
|||
func (bb *BranchBarrier) MongoQueryPrepared(mc *mongo.Client) error { |
|||
_, err := mongoInsertBarrier(context.Background(), mc, bb.TransType, bb.Gid, "00", "msg", "01", "rollback") |
|||
var result bson.M |
|||
if err == nil { |
|||
fs := strings.Split(dtmimp.BarrierTableName, ".") |
|||
barrier := mc.Database(fs[0]).Collection(fs[1]) |
|||
err = barrier.FindOne(context.Background(), bson.D{ |
|||
{Key: "gid", Value: bb.Gid}, |
|||
{Key: "branch_id", Value: "00"}, |
|||
{Key: "op", Value: "msg"}, |
|||
{Key: "barrier_id", Value: "01"}, |
|||
}).Decode(&result) |
|||
} |
|||
var reason string |
|||
if err == nil { |
|||
reason, _ = result["reason"].(string) |
|||
} |
|||
if err == nil && reason == "rollback" { |
|||
return ErrFailure |
|||
} |
|||
return err |
|||
} |
|||
|
|||
func mongoInsertBarrier(sc context.Context, mc *mongo.Client, transType string, gid string, branchID string, op string, barrierID string, reason string) (int64, error) { |
|||
if op == "" { |
|||
return 0, nil |
|||
} |
|||
fs := strings.Split(dtmimp.BarrierTableName, ".") |
|||
barrier := mc.Database(fs[0]).Collection(fs[1]) |
|||
r := barrier.FindOne(sc, bson.D{ |
|||
{Key: "gid", Value: gid}, |
|||
{Key: "branch_id", Value: branchID}, |
|||
{Key: "op", Value: op}, |
|||
{Key: "barrier_id", Value: barrierID}, |
|||
}) |
|||
err := r.Err() |
|||
if err == mongo.ErrNoDocuments { |
|||
_, err = barrier.InsertOne(sc, |
|||
bson.D{ |
|||
{Key: "trans_type", Value: transType}, |
|||
{Key: "gid", Value: gid}, |
|||
{Key: "branch_id", Value: branchID}, |
|||
{Key: "op", Value: op}, |
|||
{Key: "barrier_id", Value: barrierID}, |
|||
{Key: "reason", Value: reason}, |
|||
}) |
|||
return 1, err |
|||
} |
|||
return 0, err |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
use busi |
|||
db.busi.insert({user_id: 1, balance: 10000}) |
|||
db.busi.insert({user_id: 2, balance: 10000}) |
|||
@ -0,0 +1,4 @@ |
|||
use dtm_barrier |
|||
db.barrier.drop() |
|||
db.barrier.createIndex({gid:1, branch_id:1, op: 1, barrier_id: 1}, {unique: true}) |
|||
//db.barrier.insert({gid:"123", branch_id:"01", op:"action", barrier_id:"01", reason:"action"});
|
|||
@ -0,0 +1,78 @@ |
|||
package test |
|||
|
|||
import ( |
|||
"errors" |
|||
"testing" |
|||
|
|||
"github.com/dtm-labs/dtm/dtmcli" |
|||
"github.com/dtm-labs/dtm/dtmcli/dtmimp" |
|||
"github.com/dtm-labs/dtm/test/busi" |
|||
"github.com/stretchr/testify/assert" |
|||
"go.mongodb.org/mongo-driver/mongo" |
|||
) |
|||
|
|||
func TestMsgMongoDoSucceed(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
gid := dtmimp.GetFuncName() |
|||
req := busi.GenTransReq(30, false, false) |
|||
msg := dtmcli.NewMsg(DtmServer, gid). |
|||
Add(busi.Busi+"/SagaMongoTransIn", req) |
|||
err := msg.DoAndSubmit(Busi+"/MongoQueryPrepared", func(bb *dtmcli.BranchBarrier) error { |
|||
return bb.MongoCall(busi.MongoGet(), func(sc mongo.SessionContext) error { |
|||
return busi.SagaMongoAdjustBalance(sc, sc.Client(), busi.TransOutUID, -30, "") |
|||
}) |
|||
}) |
|||
assert.Nil(t, err) |
|||
waitTransProcessed(msg.Gid) |
|||
assert.Equal(t, []string{StatusSucceed}, getBranchesStatus(msg.Gid)) |
|||
assert.Equal(t, StatusSucceed, getTransStatus(msg.Gid)) |
|||
assertNotSameBalance(t, before, "mongo") |
|||
} |
|||
|
|||
func TestMsgMongoDoBusiFailed(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
gid := dtmimp.GetFuncName() |
|||
req := busi.GenTransReq(30, false, false) |
|||
msg := dtmcli.NewMsg(DtmServer, gid). |
|||
Add(busi.Busi+"/SagaMongoTransIn", req) |
|||
err := msg.DoAndSubmit(Busi+"/MongoQueryPrepared", func(bb *dtmcli.BranchBarrier) error { |
|||
return errors.New("an error") |
|||
}) |
|||
assert.Error(t, err) |
|||
assertSameBalance(t, before, "mongo") |
|||
} |
|||
|
|||
func TestMsgMongoDoCommitFailed(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
gid := dtmimp.GetFuncName() |
|||
req := busi.GenTransReq(30, false, false) |
|||
msg := dtmcli.NewMsg(DtmServer, gid). |
|||
Add(busi.Busi+"/SagaMongoTransIn", req) |
|||
err := msg.DoAndSubmit(Busi+"/MongoQueryPrepared", func(bb *dtmcli.BranchBarrier) error { |
|||
return bb.MongoCall(busi.MongoGet(), func(sc mongo.SessionContext) error { |
|||
err := busi.SagaMongoAdjustBalance(sc, sc.Client(), busi.TransOutUID, -30, "") |
|||
assert.Nil(t, err) |
|||
return errors.New("commit failed") |
|||
}) |
|||
}) |
|||
assert.Error(t, err) |
|||
assertSameBalance(t, before, "mongo") |
|||
} |
|||
|
|||
func TestMsgMongoDoCommitAfterFailed(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
gid := dtmimp.GetFuncName() |
|||
req := busi.GenTransReq(30, false, false) |
|||
msg := dtmcli.NewMsg(DtmServer, gid). |
|||
Add(busi.Busi+"/SagaMongoTransIn", req) |
|||
err := msg.DoAndSubmit(Busi+"/MongoQueryPrepared", func(bb *dtmcli.BranchBarrier) error { |
|||
err := bb.MongoCall(busi.MongoGet(), func(sc mongo.SessionContext) error { |
|||
return busi.SagaMongoAdjustBalance(sc, sc.Client(), busi.TransOutUID, -30, "") |
|||
}) |
|||
assert.Nil(t, err) |
|||
return errors.New("an error") |
|||
}) |
|||
assert.Error(t, err) |
|||
waitTransProcessed(gid) |
|||
assertNotSameBalance(t, before, "mongo") |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/* |
|||
* Copyright (c) 2021 yedf. All rights reserved. |
|||
* Use of this source code is governed by a BSD-style |
|||
* license that can be found in the LICENSE file. |
|||
*/ |
|||
|
|||
package test |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/dtm-labs/dtm/dtmcli" |
|||
"github.com/dtm-labs/dtm/dtmcli/dtmimp" |
|||
"github.com/dtm-labs/dtm/test/busi" |
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
func TestSagaBarrierMongoNormal(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
saga := genSagaBarrierMongo(dtmimp.GetFuncName(), false) |
|||
err := saga.Submit() |
|||
assert.Nil(t, err) |
|||
waitTransProcessed(saga.Gid) |
|||
assert.Equal(t, []string{StatusPrepared, StatusSucceed, StatusPrepared, StatusSucceed}, getBranchesStatus(saga.Gid)) |
|||
assert.Equal(t, StatusSucceed, getTransStatus(saga.Gid)) |
|||
assertNotSameBalance(t, before, "mongo") |
|||
} |
|||
|
|||
func TestSagaBarrierMongoRollback(t *testing.T) { |
|||
before := getBeforeBalances("mongo") |
|||
saga := genSagaBarrierMongo(dtmimp.GetFuncName(), true) |
|||
err := saga.Submit() |
|||
assert.Nil(t, err) |
|||
waitTransProcessed(saga.Gid) |
|||
assert.Equal(t, StatusFailed, getTransStatus(saga.Gid)) |
|||
assert.Equal(t, []string{StatusSucceed, StatusSucceed, StatusSucceed, StatusFailed}, getBranchesStatus(saga.Gid)) |
|||
assertSameBalance(t, before, "mongo") |
|||
} |
|||
|
|||
func genSagaBarrierMongo(gid string, transInFailed bool) *dtmcli.Saga { |
|||
req := busi.GenTransReq(30, false, transInFailed) |
|||
req.Store = "mongo" |
|||
return dtmcli.NewSaga(DtmServer, gid). |
|||
Add(Busi+"/SagaMongoTransOut", Busi+"/SagaMongoTransOutCom", req). |
|||
Add(Busi+"/SagaMongoTransIn", Busi+"/SagaMongoTransInCom", req) |
|||
} |
|||
Loading…
Reference in new issue