This is an automated email from the ASF dual-hosted git repository.
zfeng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-seata-go-samples.git
The following commit(s) were added to refs/heads/main by this push:
new bb42752 Saga end-to-end verification (#74)
bb42752 is described below
commit bb42752c5f27e065e488cfbeb077cbe1e06b31a7
Author: FengZhang <[email protected]>
AuthorDate: Sun Dec 21 19:53:08 2025 +0800
Saga end-to-end verification (#74)
* support saga e2e test
* fix go mod
* fix gomod
* fix err
* update go version for readme
* fix import sdk
* fix version err
* remove json
---
.licenserc.yaml | 1 +
README.md | 2 +-
at/grpc/cmd/client/main.go | 1 +
at/grpc/cmd/server/main.go | 3 +-
at/grpc/pb/at_grpc.pb.go | 5 +-
at/grpc/pb/at_grpc_grpc.pb.go | 1 +
at/grpc/service/service.go | 5 +-
go.mod | 46 +--
go.sum | 75 +++--
goimports.sh | 8 +-
saga/e2e/Makefile | 33 +++
saga/e2e/README.md | 126 ++++++++
saga/e2e/README_zh.md | 134 +++++++++
goimports.sh => saga/e2e/config.yaml | 28 +-
saga/e2e/dbcheck/main.go | 329 +++++++++++++++++++++
goimports.sh => saga/e2e/docker-compose.yml | 36 ++-
saga/e2e/main.go | 291 ++++++++++++++++++
goimports.sh => saga/e2e/migrate.sh | 21 +-
goimports.sh => saga/e2e/run.sh | 18 +-
saga/e2e/run_all.sh | 144 +++++++++
goimports.sh => saga/e2e/run_compensation.sh | 21 +-
saga/e2e/scenario_comp_balance.go | 29 ++
saga/e2e/scenario_comp_inventory.go | 29 ++
saga/e2e/scenario_success.go | 28 ++
goimports.sh => saga/e2e/seatago.yaml | 40 ++-
saga/e2e/sql/mysql_saga_schema.sql | 78 +++++
.../statelang/reduce_inventory_and_balance.json | 75 +++++
goimports.sh => saga/e2e/up_and_run.sh | 30 +-
28 files changed, 1495 insertions(+), 142 deletions(-)
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 46b83c6..16ee2ba 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -60,6 +60,7 @@ header: # `header` section is configurations for source codes
license header.
- 'DISCLAIMER'
- 'NOTICE'
- '.github'
+ - '**/*.json'
comment: on-failure
language:
diff --git a/README.md b/README.md
index dc28f50..34bad31 100644
--- a/README.md
+++ b/README.md
@@ -128,7 +128,7 @@ go work use ./seata-go-samples
Now, the content of go.work file is as follows.
```text
-go 1.19
+go 1.20
use (
./seata-go
diff --git a/at/grpc/cmd/client/main.go b/at/grpc/cmd/client/main.go
index 0558db4..f168e20 100644
--- a/at/grpc/cmd/client/main.go
+++ b/at/grpc/cmd/client/main.go
@@ -21,6 +21,7 @@ package main
import (
"context"
"flag"
+
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
__ "seata.apache.org/seata-go-samples/at/grpc/pb"
diff --git a/at/grpc/cmd/server/main.go b/at/grpc/cmd/server/main.go
index d67b597..51046ab 100644
--- a/at/grpc/cmd/server/main.go
+++ b/at/grpc/cmd/server/main.go
@@ -21,7 +21,8 @@ package main
import (
"fmt"
"net"
- "seata.apache.org/seata-go-samples/at/grpc/pb"
+
+ __ "seata.apache.org/seata-go-samples/at/grpc/pb"
"seata.apache.org/seata-go/pkg/client"
"google.golang.org/grpc"
diff --git a/at/grpc/pb/at_grpc.pb.go b/at/grpc/pb/at_grpc.pb.go
index c063010..c5837fc 100644
--- a/at/grpc/pb/at_grpc.pb.go
+++ b/at/grpc/pb/at_grpc.pb.go
@@ -23,11 +23,12 @@
package __
import (
+ reflect "reflect"
+ sync "sync"
+
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
wrapperspb "google.golang.org/protobuf/types/known/wrapperspb"
- reflect "reflect"
- sync "sync"
)
const (
diff --git a/at/grpc/pb/at_grpc_grpc.pb.go b/at/grpc/pb/at_grpc_grpc.pb.go
index 5c4ee43..c2c9886 100644
--- a/at/grpc/pb/at_grpc_grpc.pb.go
+++ b/at/grpc/pb/at_grpc_grpc.pb.go
@@ -25,6 +25,7 @@ package __
import (
context "context"
+
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
diff --git a/at/grpc/service/service.go b/at/grpc/service/service.go
index 39077bf..50cc655 100644
--- a/at/grpc/service/service.go
+++ b/at/grpc/service/service.go
@@ -21,10 +21,11 @@ import (
"context"
"database/sql"
"fmt"
- "google.golang.org/protobuf/types/known/wrapperspb"
- "seata.apache.org/seata-go-samples/at/grpc/pb"
"time"
+ "google.golang.org/protobuf/types/known/wrapperspb"
+ __ "seata.apache.org/seata-go-samples/at/grpc/pb"
+
sql2 "seata.apache.org/seata-go/pkg/datasource/sql"
)
diff --git a/go.mod b/go.mod
index 9ffc9dd..c1b7e23 100644
--- a/go.mod
+++ b/go.mod
@@ -7,28 +7,35 @@ require (
github.com/gin-gonic/gin v1.9.1
github.com/go-sql-driver/mysql v1.7.0
github.com/parnurzeal/gorequest v0.2.16
- google.golang.org/grpc v1.56.3
- google.golang.org/protobuf v1.30.0
+ google.golang.org/grpc v1.57.0
+ google.golang.org/protobuf v1.31.0
gorm.io/driver/mysql v1.4.5
gorm.io/gorm v1.24.3
- seata.apache.org/seata-go v1.2.1-0.20240604133652-ad092d5eb331
+ seata.apache.org/seata-go v1.2.1-0.20251220113411-b18bcb019b65
)
-// For local testing only.
-//replace seata.apache.org/seata-go => ../seata-go
+require (
+ cloud.google.com/go/compute v1.20.1 // indirect
+ github.com/antlr/antlr4/runtime/Go/antlr/v4
v4.0.0-20230305170008-8188dc5388df // indirect
+ github.com/google/cel-go v0.18.0 // indirect
+ github.com/robertkrimen/otto v0.4.0 // indirect
+ github.com/stoewer/go-strcase v1.2.0 // indirect
+ golang.org/x/exp v0.0.0-20220827204233-334a2380cb91 // indirect
+ google.golang.org/genproto/googleapis/api
v0.0.0-20230803162519-f966b187b2e5 // indirect
+ google.golang.org/genproto/googleapis/rpc
v0.0.0-20230803162519-f966b187b2e5 // indirect
+ gopkg.in/sourcemap.v1 v1.0.5 // indirect
+)
require (
- cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/Workiva/go-datastructures v1.0.52 // indirect
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 //
indirect
github.com/alibaba/sentinel-golang v1.0.4 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704 // indirect
- github.com/apache/dubbo-getty v1.4.10 // indirect
+ github.com/apache/dubbo-getty v1.5.0 // indirect
github.com/apache/dubbo-go-hessian2 v1.12.2 // indirect
- github.com/arana-db/parser v0.2.5 // indirect
- github.com/benbjohnson/clock v1.1.0 // indirect
+ github.com/arana-db/parser v0.2.17 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.2.0 // indirect
github.com/bluele/gcache v0.0.2 // indirect
@@ -139,24 +146,25 @@ require (
go.opentelemetry.io/otel/sdk v1.10.0 // indirect
go.opentelemetry.io/otel/trace v1.11.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
- go.uber.org/multierr v1.8.0 // indirect
- go.uber.org/zap v1.21.0 // indirect
+ go.uber.org/multierr v1.10.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.3.0 // indirect
- golang.org/x/crypto v0.17.0 // indirect
- golang.org/x/net v0.17.0 // indirect
- golang.org/x/oauth2 v0.7.0 // indirect
- golang.org/x/sync v0.1.0 // indirect
- golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/net v0.23.0 // indirect
+ golang.org/x/oauth2 v0.8.0 // indirect
+ golang.org/x/sync v0.11.0 // indirect
+ golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.1.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 //
indirect
+ google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e //
indirect
gopkg.in/ini.v1 v1.66.2 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
+ gopkg.in/yaml.v3 v3.0.1
moul.io/http2curl v1.0.0 // indirect
vimagination.zapto.org/byteio v0.0.0-20200222190125-d27cba0f0b10 //
indirect
)
-// replace github.com/knadh/koanf => github.com/knadh/koanf/v2 v2.1.2
+// For local testing only.
+//replace seata.apache.org/seata-go => ../incubator-seata-go
diff --git a/go.sum b/go.sum
index 74fd412..837ed75 100644
--- a/go.sum
+++ b/go.sum
@@ -115,8 +115,8 @@ cloud.google.com/go/compute v1.12.0/go.mod
h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x
cloud.google.com/go/compute v1.12.1/go.mod
h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
cloud.google.com/go/compute v1.13.0/go.mod
h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE=
cloud.google.com/go/compute v1.14.0/go.mod
h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo=
-cloud.google.com/go/compute v1.19.1
h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
-cloud.google.com/go/compute v1.19.1/go.mod
h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
+cloud.google.com/go/compute v1.20.1
h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
+cloud.google.com/go/compute v1.20.1/go.mod
h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.1.0/go.mod
h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU=
cloud.google.com/go/compute/metadata v0.2.0/go.mod
h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.1/go.mod
h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
@@ -412,7 +412,7 @@ github.com/afex/hystrix-go
v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod
h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agiledragon/gomonkey v2.0.2+incompatible
h1:eXKi9/piiC3cjJD1658mEE2o3NjkJ5vDLgYjCQu0Xlw=
github.com/agiledragon/gomonkey v2.0.2+incompatible/go.mod
h1:2NGfXu1a80LLr2cmWXGBDaHEjb1idR6+FVlX5T3D9hw=
-github.com/agiledragon/gomonkey/v2 v2.9.0
h1:PDiKKybR596O6FHW+RVSG0Z7uGCBNbmbUXh3uCNQ7Hc=
+github.com/agiledragon/gomonkey/v2 v2.12.0
h1:ek0dYu9K1rSV+TgkW5LvNNPRWyDZVIxGMCFI6Pz9o38=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod
h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod
h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod
h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -425,17 +425,19 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.18/go.mod
h1:v8ESoHo4SyHmuB4b1tJqDH
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704
h1:PpfENOj/vPfhhy9N2OFRjpue0hjM5XqAp2thFmkXXIk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1704/go.mod
h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/antihax/optional v1.0.0/go.mod
h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df
h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18=
+github.com/antlr/antlr4/runtime/Go/antlr/v4
v4.0.0-20230305170008-8188dc5388df/go.mod
h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/apache/dubbo-getty v1.4.9/go.mod
h1:6qmrqBSPGs3B35zwEuGhEYNVsx1nfGT/xzV2yOt2amM=
-github.com/apache/dubbo-getty v1.4.10
h1:ZmkpHJa/qgS0evX2tTNqNCz6rClI/9Wwp7ctyMml82w=
-github.com/apache/dubbo-getty v1.4.10/go.mod
h1:V64WqLIxksEgNu5aBJBOxNIvpOZyfUJ7J/DXBlKSUoA=
+github.com/apache/dubbo-getty v1.5.0
h1:40RMjEoSfTuoG5EwKbGgfhjd7m6Zc7qBfOFgQv1jHCI=
+github.com/apache/dubbo-getty v1.5.0/go.mod
h1:V64WqLIxksEgNu5aBJBOxNIvpOZyfUJ7J/DXBlKSUoA=
github.com/apache/dubbo-go-hessian2 v1.9.1/go.mod
h1:xQUjE7F8PX49nm80kChFvepA/AvqAZ0oh/UaB6+6pBE=
github.com/apache/dubbo-go-hessian2 v1.9.3/go.mod
h1:xQUjE7F8PX49nm80kChFvepA/AvqAZ0oh/UaB6+6pBE=
github.com/apache/dubbo-go-hessian2 v1.12.2
h1:2/56JRPng2lnLziJF3fqmSgsg28Yt1a5YZ5RX+jHDGs=
github.com/apache/dubbo-go-hessian2 v1.12.2/go.mod
h1:QP9Tc0w/B/mDopjusebo/c7GgEfl6Lz8jeuFg8JA6yw=
github.com/apache/thrift v0.12.0/go.mod
h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod
h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/arana-db/parser v0.2.5
h1:X7SZUjs52nNkX+PL3wrJVd7+BL4VALIXahX+Bx+pmOQ=
-github.com/arana-db/parser v0.2.5/go.mod
h1:/XA29bplweWSEAjgoM557ZCzhBilSawUlHcZFjOeDAc=
+github.com/arana-db/parser v0.2.17
h1:4wNfSgza2N3pjpwR5jmWLvu4L6Sme6EtoLuZOgwWlsU=
+github.com/arana-db/parser v0.2.17/go.mod
h1:/XA29bplweWSEAjgoM557ZCzhBilSawUlHcZFjOeDAc=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod
h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod
h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.9/go.mod
h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
@@ -455,7 +457,6 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url
v1.3.2/go.mod h1:72H
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod
h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod
h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/smithy-go v1.8.0/go.mod
h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
-github.com/benbjohnson/clock v1.1.0
h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod
h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod
h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod
h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -737,6 +738,8 @@ github.com/gonum/stat
v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4g
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod
h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod
h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/cel-go v0.18.0
h1:u74MPiEC8mejBrkXqrTWT102g5IFEUjxOngzQIijMzU=
+github.com/google/cel-go v0.18.0/go.mod
h1:PVAybmSnWkNMUZR/tEWFUiJ1Np4Hz0MHsZJcgC4zln4=
github.com/google/go-cmp v0.2.0/go.mod
h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod
h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@@ -1008,6 +1011,7 @@ github.com/mattn/go-isatty v0.0.16/go.mod
h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.19
h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod
h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod
h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-sqlite3 v1.14.19
h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod
h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4
h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod
h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -1188,6 +1192,8 @@ github.com/rcrowley/go-metrics
v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod
h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod
h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rhnvrm/simples3 v0.6.1/go.mod
h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
+github.com/robertkrimen/otto v0.4.0
h1:/c0GRrK1XDPcgIasAsnlpBT5DelIeB9U/Z/JCQsgr7E=
+github.com/robertkrimen/otto v0.4.0/go.mod
h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw=
github.com/robfig/cron/v3 v3.0.1
h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod
h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod
h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
@@ -1241,6 +1247,8 @@ github.com/spf13/pflag v1.0.3/go.mod
h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod
h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod
h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stoewer/go-strcase v1.2.0
h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
+github.com/stoewer/go-strcase v1.2.0/go.mod
h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod
h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod
h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod
h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@@ -1376,15 +1384,16 @@ go.uber.org/atomic v1.10.0
h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod
h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.10/go.mod
h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11/go.mod
h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
-go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/goleak v1.1.12/go.mod
h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/multierr v1.1.0/go.mod
h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod
h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod
h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod
h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod
h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
-go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod
h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
+go.uber.org/multierr v1.10.0/go.mod
h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod
h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
@@ -1394,8 +1403,9 @@ go.uber.org/zap v1.16.0/go.mod
h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
-go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod
h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
@@ -1417,8 +1427,8 @@ golang.org/x/crypto
v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0/go.mod
h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod
h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod
h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod
h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1433,8 +1443,9 @@ golang.org/x/exp
v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod
h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod
h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod
h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5
h1:FR+oGxGfbQu1d+jglI3rCkjAjUnhRSZcUxr+DqlDLNo=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod
h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91
h1:tnebWN09GYg9OLPss1KXj8txwZc6X6uMr6VFdcGNbHw=
+golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod
h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod
h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod
h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod
h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -1537,8 +1548,8 @@ golang.org/x/net v0.2.0/go.mod
h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
-golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod
h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod
h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1565,8 +1576,8 @@ golang.org/x/oauth2
v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod
h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod
h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.6.0/go.mod
h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
-golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
-golang.org/x/oauth2 v0.7.0/go.mod
h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
+golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
+golang.org/x/oauth2 v0.8.0/go.mod
h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1581,8 +1592,9 @@ golang.org/x/sync
v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod
h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod
h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod
h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1695,8 +1707,8 @@ golang.org/x/sys v0.2.0/go.mod
h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod
h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod
h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
@@ -1997,8 +2009,12 @@ google.golang.org/genproto
v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZV
google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod
h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg=
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod
h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod
h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
-google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod
h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
+google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e
h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg=
+google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod
h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108=
+google.golang.org/genproto/googleapis/api v0.0.0-20230803162519-f966b187b2e5
h1:nIgk/EEq3/YlnmVVXVnm14rC2oxgs1o0ong4sD/rd44=
+google.golang.org/genproto/googleapis/api
v0.0.0-20230803162519-f966b187b2e5/go.mod
h1:5DZzOUPCLYL3mNkQ0ms0F3EuUNZ7py1Bqeq6sxzI7/Q=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5
h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
+google.golang.org/genproto/googleapis/rpc
v0.0.0-20230803162519-f966b187b2e5/go.mod
h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
google.golang.org/grpc v1.8.0/go.mod
h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.14.0/go.mod
h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod
h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@@ -2047,8 +2063,8 @@ google.golang.org/grpc v1.50.0/go.mod
h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCD
google.golang.org/grpc v1.50.1/go.mod
h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.51.0/go.mod
h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
google.golang.org/grpc v1.52.0/go.mod
h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY=
-google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc=
-google.golang.org/grpc v1.56.3/go.mod
h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
+google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
+google.golang.org/grpc v1.57.0/go.mod
h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod
h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod
h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod
h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -2065,8 +2081,9 @@ google.golang.org/protobuf v1.26.0/go.mod
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod
h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.30.0
h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0
h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod
h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod
h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod
h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -2087,6 +2104,8 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod
h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24
gopkg.in/natefinch/lumberjack.v2 v2.2.1
h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod
h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/resty.v1 v1.12.0/go.mod
h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
+gopkg.in/sourcemap.v1 v1.0.5/go.mod
h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod
h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod
h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod
h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
@@ -2137,8 +2156,8 @@ rsc.io/binaryregexp v0.2.0/go.mod
h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-seata.apache.org/seata-go v1.2.1-0.20240604133652-ad092d5eb331
h1:Pbes9LbfpN10s/Kt/ioEkqBg4l3hykoZjprZE0VuTdU=
-seata.apache.org/seata-go v1.2.1-0.20240604133652-ad092d5eb331/go.mod
h1:lzLjtX6x5zXDflnQ53jo/Gsbqu6gu8+ZaaQTHz6inYk=
+seata.apache.org/seata-go v1.2.1-0.20251220113411-b18bcb019b65
h1:2d4nvNnAnszr4SmSZa1VSLZsnzu2jHR0jtRAPH8R1cI=
+seata.apache.org/seata-go v1.2.1-0.20251220113411-b18bcb019b65/go.mod
h1:BH35FqKInt+3tV/Wp0OS2HfJswKdgv59MVJJ6KWxirY=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/goimports.sh b/goimports.sh
old mode 100644
new mode 100755
index 4c30f1c..137f786
--- a/goimports.sh
+++ b/goimports.sh
@@ -16,14 +16,14 @@
#
# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+go install golang.org/x/tools/cmd/[email protected]
+goimports -local seata.apache.org/seata-go -w .
# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
+go install github.com/apache/skywalking-eyes/cmd/[email protected]
license-eye header fix
# check dependency licence is valid
license-eye dependency check
# format go.mod
-go mod tidy
+go mod tidy
\ No newline at end of file
diff --git a/saga/e2e/Makefile b/saga/e2e/Makefile
new file mode 100644
index 0000000..8a38616
--- /dev/null
+++ b/saga/e2e/Makefile
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+.PHONY: run migrate
+
+SEATA_CONF ?= saga/e2e/seatago.yaml
+ENGINE_CONF ?= saga/e2e/config.yaml
+
+run:
+ go run ./saga/e2e -seataConf=$(SEATA_CONF) -engineConf=$(ENGINE_CONF)
+
+# Requires mysql client; provide MYSQL_HOST, MYSQL_PORT, MYSQL_USER,
MYSQL_PWD, MYSQL_DB
+migrate:
+ @[ -n "$(MYSQL_HOST)" ] || (echo "MYSQL_HOST is required" && exit 1)
+ @[ -n "$(MYSQL_PORT)" ] || (echo "MYSQL_PORT is required" && exit 1)
+ @[ -n "$(MYSQL_USER)" ] || (echo "MYSQL_USER is required" && exit 1)
+ @[ -n "$(MYSQL_PWD)" ] || (echo "MYSQL_PWD is required" && exit 1)
+ @[ -n "$(MYSQL_DB)" ] || (echo "MYSQL_DB is required" && exit 1)
+ @echo "Applying schema to $(MYSQL_HOST):$(MYSQL_PORT)/$(MYSQL_DB)..."
+ @mysql -h"$(MYSQL_HOST)" -P"$(MYSQL_PORT)" -u"$(MYSQL_USER)"
-p"$(MYSQL_PWD)" "$(MYSQL_DB)" < sql/mysql_saga_schema.sql
+ @echo "Schema applied."
diff --git a/saga/e2e/README.md b/saga/e2e/README.md
new file mode 100644
index 0000000..dfa399c
--- /dev/null
+++ b/saga/e2e/README.md
@@ -0,0 +1,126 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+
+# Saga E2E (MySQL) — End‑to‑End Guide
+
+Chinese version: see `README_zh.md`.
+
+This example demonstrates a Java‑aligned Seata SAGA flow in Go, with MySQL
persistence and Seata Server (TC) integration. It includes one‑click scripts,
DB validation tooling, and compensation semantics compatible with the Java
reference.
+
+## What it starts
+
+- MySQL 8.0 — stores SAGA state
+- Seata Server 1.6.1 — TC for global coordination
+- A demo SAGA: ReduceInventory → ReduceBalance (with compensations)
+
+## Quick start (one‑click)
+
+Prerequisites: Go 1.20+, Docker, and docker-compose available on the host.
+
+```
+saga/e2e/run_all.sh --up \
+ --seata saga/e2e/seatago.yaml \
+ --engine saga/e2e/config.yaml
+```
+
+## Use local seata-go while developing
+
+The sample repository currently depends on a released version of seata-go. When
+you need to exercise the E2E flow against your working tree at
+`../incubator-seata-go`, temporarily add a replace directive before running:
+
+```
+go mod edit -replace seata.apache.org/seata-go=../incubator-seata-go
+go mod tidy # optional, only if dependencies changed
+```
+
+After the test run, drop the replace so the module file stays clean:
+
+```
+go mod edit -dropreplace seata.apache.org/seata-go
+```
+
+When using Go 1.18+, you can alternatively create a temporary workspace:
+
+```
+go work init
+go work use .
+go work use ../incubator-seata-go
+go run ./saga/e2e
+rm go.work
+```
+
+Knobs:
+- `WAIT_TIMEOUT` (default 60), `WAIT_INTERVAL` (2) TCP readiness
+- `WAIT_READY_MARGIN` (default 15) extra wait after ports are open
+
+Expected:
+- `DB validation (success) OK`
+- `DB validation (compensate-balance) OK`
+- `DB validation (compensate-inventory) OK`
+- `[+] All e2e scenarios finished`
+
+## Configuration
+
+- Seata client (`saga/e2e/seatago.yaml`)
+ - `application-id`, `tx-service-group`
+ - `service.grouplist.default`: `host:port` of Seata Server
+
+- Saga engine (`saga/e2e/config.yaml`)
+ - `store_enabled: true`, `store_type: mysql`
+ - `store_dsn: user:pass@tcp(127.0.0.1:3306)/seata_saga?parseTime=true`
+ - `tc_enabled: true`
+ - `state_machine_resources: [saga/e2e/statelang/*.json]`
+
+## Run individual scenarios
+
+- Start + run (fresh): `saga/e2e/up_and_run.sh`
+- Success only: `saga/e2e/run.sh [seatago.yaml] [config.yaml]`
+- Compensation: `saga/e2e/run_compensation.sh [seatago.yaml] [config.yaml]
[compensate-balance|compensate-inventory]`
+
+## DB validation
+
+```
+go run ./saga/e2e/dbcheck \
+ -engine saga/e2e/config.yaml \
+ -xid <XID> \
+ -scenario success|compensate-balance|compensate-inventory
+```
+
+Checks machine row end state and per‑state rows with compensation linkage.
+
+## Schema migration
+
+```
+MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 \
+MYSQL_USER=root MYSQL_PWD=secret MYSQL_DB=seata_saga \
+saga/e2e/migrate.sh
+```
+
+## Troubleshooting
+
+- Seata unreachable → check `service.grouplist.default`
+- Branch register fails → ensure `application-id#tx-service-group` mapping
exists in TC
+- MySQL initial EOF/bad connection → increase `WAIT_READY_MARGIN`
+- Optimistic‑lock 0‑row finish on success → benign; handled idempotently
(debug only)
+- TC GlobalReport timeouts → logged, do not fail local finish; DB validation
still passes
+
+## Pointers
+
+- StateLang JSON: `statelang/reduce_inventory_and_balance.json`
+- Engine: `pkg/saga/statemachine/engine/pcext/*`, store:
`pkg/saga/statemachine/store/db/statelog.go`
+- Scripts: `run_all.sh`, `up_and_run.sh`, `run.sh`, `run_compensation.sh`
diff --git a/saga/e2e/README_zh.md b/saga/e2e/README_zh.md
new file mode 100644
index 0000000..d93687b
--- /dev/null
+++ b/saga/e2e/README_zh.md
@@ -0,0 +1,134 @@
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache License, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+
+# Saga E2E(MySQL)— 端到端使用说明
+
+本示例在 `saga/e2e` 目录下,提供一键脚本与 DB 校验工具。
+
+## 一键运行(推荐)
+
+前置条件:本机需安装 Go 1.20+、Docker 与 docker-compose。
+
+使用 docker‑compose 重建/启动 MySQL 与 Seata Server,等待就绪后依次运行 3 个场景并进行 DB 校验:
+
+```
+saga/e2e/run_all.sh --up \
+ --seata saga/e2e/seatago.yaml \
+ --engine saga/e2e/config.yaml
+```
+
+## 使用本地 seata-go 进行调试
+
+sample 仓库当前依赖发布版的 seata-go。如需在调试时直接复用
+`../incubator-seata-go` 的最新代码,可在运行前临时添加 replace:
+
+```
+go mod edit -replace seata.apache.org/seata-go=../incubator-seata-go
+go mod tidy # 依赖有变更时执行
+```
+
+执行完成后建议移除 replace,保持 go.mod 干净:
+
+```
+go mod edit -dropreplace seata.apache.org/seata-go
+```
+
+如果使用 Go 1.18+,也可以临时创建 workspace:
+
+```
+go work init
+go work use .
+go work use ../incubator-seata-go
+go run ./saga/e2e
+rm go.work
+```
+
+说明:
+- 脚本会 `docker-compose down -v` 清理旧容器与卷,再 `up -d --force-recreate` 全新拉起
+- 自动等待 MySQL、Seata Server 端口就绪,并额外等待一段时间,避免刚启动时 MySQL 的 EOF 抖动
+- 依次运行 3 个场景并进行 DB 校验:
+ - success:前向执行成功
+ - compensate-balance:余额扣减失败触发补偿,最终 Fail(补偿 SU)
+ - compensate-inventory:库存首步失败,无补偿,最终 Fail
+
+可调参数(环境变量):
+- `WAIT_TIMEOUT`(默认 60):端口等待超时秒数
+- `WAIT_INTERVAL`(默认 2):端口轮询间隔秒数
+- `WAIT_READY_MARGIN`(默认 15):端口可达后的额外等待秒数
+
+预期输出:
+- `DB validation (success) OK`
+- `DB validation (compensate-balance) OK`
+- `DB validation (compensate-inventory) OK`
+- `[+] All e2e scenarios finished`
+
+## 配置
+
+- Seata 客户端(`seatago.yaml`)
+ - `application-id`、`tx-service-group`
+ - `service.grouplist.default`:Seata Server 地址(`ip:port`)
+
+- Saga 引擎(`config.yaml`)
+ - `store_enabled: true`、`store_type: mysql`
+ - `store_dsn: user:pass@tcp(127.0.0.1:3306)/seata_saga?parseTime=true`
+ - `tc_enabled: true`
+ - `state_machine_resources: [saga/e2e/statelang/*.json]`
+
+## 单场景运行
+
+- 启动并运行(重建容器):`saga/e2e/up_and_run.sh`
+- 仅运行成功场景:`saga/e2e/run.sh [seatago.yaml] [config.yaml]`
+- 运行补偿场景:`saga/e2e/run_compensation.sh [seatago.yaml] [config.yaml]
[compensate-balance|compensate-inventory]`
+
+## DB 校验工具
+
+用于在运行结束后对最终状态进行校验:
+
+```
+go run ./saga/e2e/dbcheck \
+ -engine saga/e2e/config.yaml \
+ -xid <XID> \
+ -scenario success|compensate-balance|compensate-inventory
+```
+
+校验点包括:
+- `seata_state_machine_inst`:最终
`status`、`compensation_status`、`is_running`、`gmt_end`
+- `seata_state_inst`:关键前向/补偿状态与 `state_id_compensated_for` 关联
+
+## 初始化数据库表(非 docker‑compose 场景)
+
+```
+MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 \
+MYSQL_USER=root MYSQL_PWD=secret MYSQL_DB=seata_saga \
+saga/e2e/migrate.sh
+```
+
+以上脚本会执行 `sql/mysql_saga_schema.sql` 创建相关表。
+
+## 常见问题
+
+- 无法连接 Seata Server:检查 `seatago.yaml` 的 `service.grouplist.default` 与网络连通性。
+- BranchRegister 失败或 branchId 非法:确认资源标识 `applicationId#txServiceGroup` 与 TC 的
vgroup 映射一致。
+- MySQL 刚启动时 EOF/“bad connection”:增大 `WAIT_READY_MARGIN`(如 20)。
+- 成功场景“乐观锁 0 行更新”提示:幂等/并发下的正常现象,已降为 debug,不影响最终结果。
+- TC GlobalReport 超时:仅记录日志,不影响本地最终态;DB 校验仍应通过(与 Java 行为一致)。
+
+## 参考路径
+
+- 状态机 JSON:`statelang/reduce_inventory_and_balance.json`
+-
引擎/持久化关键路径:`pkg/saga/statemachine/engine/pcext/*`、`pkg/saga/statemachine/store/db/statelog.go`
+- 脚本:`run_all.sh`、`up_and_run.sh`、`run.sh`、`run_compensation.sh`
diff --git a/goimports.sh b/saga/e2e/config.yaml
similarity index 61%
copy from goimports.sh
copy to saga/e2e/config.yaml
index 4c30f1c..0261527 100644
--- a/goimports.sh
+++ b/saga/e2e/config.yaml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +12,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+# Engine runtime config (loaded by DefaultStateMachineConfig)
+
+trans_operation_timeout: 60000
+service_invoke_timeout: 5000
+rm_report_success_enable: true
+saga_branch_register_enable: true
+
+# Register JSON resources (update path if you move files)
+state_machine_resources:
+ - saga/e2e/statelang/*.json
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+# Enable MySQL store (fill in your DSN)
+store_enabled: true
+store_type: mysql
+store_dsn: "root:secret@tcp(127.0.0.1:3306)/seata_saga?parseTime=true"
-# format go.mod
-go mod tidy
+# Enable TC integration
+tc_enabled: true
diff --git a/saga/e2e/dbcheck/main.go b/saga/e2e/dbcheck/main.go
new file mode 100644
index 0000000..21a0f17
--- /dev/null
+++ b/saga/e2e/dbcheck/main.go
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "database/sql"
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+
+ _ "github.com/go-sql-driver/mysql"
+ "gopkg.in/yaml.v3"
+)
+
+type engineConf struct {
+ StoreEnabled bool `yaml:"store_enabled"`
+ StoreType string `yaml:"store_type"`
+ StoreDSN string `yaml:"store_dsn"`
+}
+
+func loadEngineConf(path string) (*engineConf, error) {
+ b, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ var c engineConf
+ if err := yaml.Unmarshal(b, &c); err != nil {
+ return nil, err
+ }
+ if !c.StoreEnabled || c.StoreType == "" || c.StoreDSN == "" {
+ return nil, errors.New("engineConf missing
store_enabled/store_type/store_dsn")
+ }
+ return &c, nil
+}
+
+func expectSuccess(row smRow) error {
+ var errs []string
+ if row.Status != "SU" {
+ errs = append(errs, fmt.Sprintf("status=%s want=SU",
row.Status))
+ }
+ if row.CompStatus.Valid && row.CompStatus.String != "" {
+ errs = append(errs, fmt.Sprintf("compensation_status=%s
want=''", row.CompStatus.String))
+ }
+ if row.IsRunning != 0 {
+ errs = append(errs, fmt.Sprintf("is_running=%d want=0",
row.IsRunning))
+ }
+ if !row.GmtEnd.Valid {
+ errs = append(errs, "gmt_end is NULL")
+ }
+ if row.Excep.Valid && len(row.Excep.String) > 0 {
+ errs = append(errs, "excep not empty")
+ }
+ if len(errs) > 0 {
+ return errors.New(strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+func expectCompensateFail(row smRow) error {
+ var errs []string
+ if row.Status != "FA" {
+ errs = append(errs, fmt.Sprintf("status=%s want=FA",
row.Status))
+ }
+ if !row.CompStatus.Valid || row.CompStatus.String != "SU" {
+ want := "<NULL>"
+ if row.CompStatus.Valid {
+ want = row.CompStatus.String
+ }
+ errs = append(errs, fmt.Sprintf("compensation_status=%s
want=SU", want))
+ }
+ if row.IsRunning != 0 {
+ errs = append(errs, fmt.Sprintf("is_running=%d want=0",
row.IsRunning))
+ }
+ if !row.GmtEnd.Valid {
+ errs = append(errs, "gmt_end is NULL")
+ }
+ // excep may be empty for Fail end state; don't enforce
+ if len(errs) > 0 {
+ return errors.New(strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+type smRow struct {
+ ID string
+ Status string
+ CompStatus sql.NullString
+ IsRunning int
+ GmtEnd sql.NullTime
+ Excep sql.NullString
+}
+
+type stRow struct {
+ Name string
+ Type string
+ Status string
+ CompFor sql.NullString
+}
+
+func fetchStateRows(db *sql.DB, xid string) ([]stRow, error) {
+ q := `SELECT name, type, status, state_id_compensated_for
+ FROM seata_state_inst WHERE machine_inst_id=? ORDER BY gmt_started
ASC`
+ rows, err := db.Query(q, xid)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var out []stRow
+ for rows.Next() {
+ var r stRow
+ if err := rows.Scan(&r.Name, &r.Type, &r.Status, &r.CompFor);
err != nil {
+ return nil, err
+ }
+ out = append(out, r)
+ }
+ return out, rows.Err()
+}
+
+func contains(rows []stRow, pred func(stRow) bool) bool {
+ for _, r := range rows {
+ if pred(r) {
+ return true
+ }
+ }
+ return false
+}
+
+func validateStatesSuccess(rows []stRow) error {
+ var errs []string
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "ReduceInventory" && r.Status == "SU" &&
!(r.CompFor.Valid && r.CompFor.String != "")
+ }) {
+ errs = append(errs, "missing SU ReduceInventory forward state")
+ }
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "ReduceBalance" && r.Status == "SU" &&
!(r.CompFor.Valid && r.CompFor.String != "")
+ }) {
+ errs = append(errs, "missing SU ReduceBalance forward state")
+ }
+ // no compensation states expected
+ if contains(rows, func(r stRow) bool { return r.CompFor.Valid &&
r.CompFor.String != "" }) {
+ errs = append(errs, "unexpected compensation state present")
+ }
+ // Succeed end state is not persisted as state_inst in Go impl; rely on
machine_inst status instead
+ if len(errs) > 0 {
+ return errors.New(strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+func validateStatesCompBalance(rows []stRow) error {
+ var errs []string
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "ReduceInventory" && r.Status == "SU" &&
!(r.CompFor.Valid && r.CompFor.String != "")
+ }) {
+ errs = append(errs, "missing SU ReduceInventory forward state")
+ }
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "ReduceBalance" && r.Status == "FA" &&
!(r.CompFor.Valid && r.CompFor.String != "")
+ }) {
+ errs = append(errs, "missing FA ReduceBalance forward state")
+ }
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "CompensateReduceInventory" && r.Status ==
"SU" && r.CompFor.Valid && r.CompFor.String != ""
+ }) {
+ // Allow type-based match in case of name differences
+ if !contains(rows, func(r stRow) bool {
+ return strings.HasPrefix(r.Name, "Compensate") &&
r.Status == "SU" && r.CompFor.Valid && r.CompFor.String != ""
+ }) {
+ errs = append(errs, "missing SU compensation state for
ReduceInventory")
+ }
+ }
+ // Fail end state is not persisted as state_inst in Go impl; rely on
machine_inst status instead
+ if len(errs) > 0 {
+ return errors.New(strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+func validateStatesCompInventory(rows []stRow) error {
+ var errs []string
+ if !contains(rows, func(r stRow) bool {
+ return r.Name == "ReduceInventory" && r.Status == "FA" &&
!(r.CompFor.Valid && r.CompFor.String != "")
+ }) {
+ errs = append(errs, "missing FA ReduceInventory forward state")
+ }
+ // no compensation states expected
+ if contains(rows, func(r stRow) bool { return r.CompFor.Valid &&
r.CompFor.String != "" }) {
+ // If any compensation exists, include a snapshot for debugging
+ var names []string
+ for _, r := range rows {
+ if r.CompFor.Valid && r.CompFor.String != "" {
+ names = append(names, r.Name)
+ }
+ }
+ sort.Strings(names)
+ errs = append(errs, fmt.Sprintf("unexpected compensation states
present: %s", strings.Join(names, ",")))
+ }
+ // Fail end state row not required; machine status covers it
+ if len(errs) > 0 {
+ return errors.New(strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+func main() {
+ var engineConfPath, xid, scenario string
+ flag.StringVar(&engineConfPath, "engine", "", "engine config path")
+ flag.StringVar(&xid, "xid", "", "XID to validate")
+ flag.StringVar(&scenario, "scenario", "success",
"success|compensate-balance|compensate-inventory")
+ flag.Parse()
+ if engineConfPath == "" || xid == "" {
+ fmt.Fprintln(os.Stderr, "missing --engine or --xid")
+ os.Exit(2)
+ }
+
+ cfg, err := loadEngineConf(engineConfPath)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+ if cfg.StoreType != "mysql" {
+ fmt.Fprintln(os.Stderr, "only mysql is supported in dbcheck")
+ os.Exit(2)
+ }
+ db, err := sql.Open("mysql", cfg.StoreDSN)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+ defer db.Close()
+ if err := db.Ping(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(2)
+ }
+
+ // query state_machine_inst row
+ q := `SELECT id, status, compensation_status, is_running, gmt_end,
excep FROM seata_state_machine_inst WHERE id=?`
+ var row smRow
+ if err := db.QueryRow(q, xid).Scan(&row.ID, &row.Status,
&row.CompStatus, &row.IsRunning, &row.GmtEnd, &row.Excep); err != nil {
+ fmt.Fprintf(os.Stderr, "query sm row failed: %v\n", err)
+ os.Exit(2)
+ }
+ states, err := fetchStateRows(db, xid)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "query state rows failed: %v\n", err)
+ os.Exit(2)
+ }
+ // Debug visibility: show how many rows we saw for this XID
+ fmt.Printf("Found %d state rows for XID=%s\n", len(states), xid)
+ if len(states) > 0 {
+ var names []string
+ for _, r := range states {
+ names = append(names, r.Name+"/"+r.Status)
+ }
+ fmt.Printf("States: %s\n", strings.Join(names, ", "))
+ }
+
+ switch scenario {
+ case "success":
+ if err := expectSuccess(row); err != nil {
+ fmt.Fprintf(os.Stderr, "validation failed (machine):
%v\n", err)
+ os.Exit(1)
+ }
+ if err := validateStatesSuccess(states); err != nil {
+ fmt.Fprintf(os.Stderr, "validation failed (states):
%v\n", err)
+ os.Exit(1)
+ }
+ fmt.Println("DB validation (success) OK")
+ return
+ case "compensate-balance":
+ if err := expectCompensateFail(row); err != nil {
+ fmt.Fprintf(os.Stderr, "validation failed (machine):
%v\n", err)
+ os.Exit(1)
+ }
+ if err := validateStatesCompBalance(states); err != nil {
+ fmt.Fprintf(os.Stderr, "validation failed (states):
%v\n", err)
+ os.Exit(1)
+ }
+ fmt.Println("DB validation (compensate-balance) OK")
+ return
+ case "compensate-inventory":
+ // machine expectation: forward fail without compensation
+ var errs []string
+ if row.Status != "FA" {
+ errs = append(errs, fmt.Sprintf("status=%s want=FA",
row.Status))
+ }
+ if row.CompStatus.Valid && row.CompStatus.String != "" {
+ errs = append(errs, fmt.Sprintf("compensation_status=%s
want=''", row.CompStatus.String))
+ }
+ if row.IsRunning != 0 {
+ errs = append(errs, fmt.Sprintf("is_running=%d want=0",
row.IsRunning))
+ }
+ if !row.GmtEnd.Valid {
+ errs = append(errs, "gmt_end is NULL")
+ }
+ if len(errs) > 0 {
+ fmt.Fprintf(os.Stderr, "validation failed (machine):
%s\n", strings.Join(errs, "; "))
+ os.Exit(1)
+ }
+ if err := validateStatesCompInventory(states); err != nil {
+ fmt.Fprintf(os.Stderr, "validation failed (states):
%v\n", err)
+ os.Exit(1)
+ }
+ fmt.Println("DB validation (compensate-inventory) OK")
+ return
+ default:
+ fmt.Fprintf(os.Stderr, "unknown scenario: %s\n", scenario)
+ os.Exit(2)
+ }
+}
diff --git a/goimports.sh b/saga/e2e/docker-compose.yml
similarity index 58%
copy from goimports.sh
copy to saga/e2e/docker-compose.yml
index 4c30f1c..944e79e 100644
--- a/goimports.sh
+++ b/saga/e2e/docker-compose.yml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +12,28 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+services:
+ mysql:
+ image: mysql:8.0
+ container_name: saga_mysql
+ restart: unless-stopped
+ environment:
+ MYSQL_ROOT_PASSWORD: secret
+ MYSQL_DATABASE: seata_saga
+ ports:
+ - "3306:3306"
+ volumes:
+ - ./sql/mysql_saga_schema.sql:/docker-entrypoint-initdb.d/1_saga.sql:ro
-# format go.mod
-go mod tidy
+ seata-server:
+ image: seataio/seata-server:1.6.1
+ container_name: seata_server
+ restart: unless-stopped
+ environment:
+ - SEATA_IP=0.0.0.0
+ - SEATA_PORT=8091
+ ports:
+ - "8091:8091"
+ depends_on:
+ - mysql
diff --git a/saga/e2e/main.go b/saga/e2e/main.go
new file mode 100644
index 0000000..a29645f
--- /dev/null
+++ b/saga/e2e/main.go
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "context"
+ "database/sql"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ _ "github.com/go-sql-driver/mysql"
+ "gopkg.in/yaml.v3"
+
+ "seata.apache.org/seata-go/pkg/client"
+ engcfg "seata.apache.org/seata-go/pkg/saga/statemachine/engine/config"
+ "seata.apache.org/seata-go/pkg/saga/statemachine/engine/core"
+ "seata.apache.org/seata-go/pkg/saga/statemachine/engine/invoker"
+)
+
+// InventoryAction (DB-backed) implements reduce/compensate with explicit
params
+type InventoryAction struct{ db *sql.DB }
+
+func NewInventoryAction(db *sql.DB) *InventoryAction { return
&InventoryAction{db: db} }
+
+// Reduce(businessKey, productId, count) -> (bool, error)
+func (a *InventoryAction) Reduce(businessKey string, productId string, count
int) (bool, error) {
+ if count <= 0 {
+ count = 1
+ }
+ res, err := a.db.Exec("UPDATE e2e_inventory SET stock = stock - ? WHERE
product_id = ? AND stock >= ?", count, productId, count)
+ if err != nil {
+ return false, err
+ }
+ if n, _ := res.RowsAffected(); n == 0 {
+ return false, fmt.Errorf("INVENTORY_NOT_ENOUGH")
+ }
+ fmt.Printf("InventoryAction.Reduce: biz=%s, product=%s, count=%d\n",
businessKey, productId, count)
+ return true, nil
+}
+
+func (a *InventoryAction) CompensateReduce(businessKey string, productId
string, count int) (bool, error) {
+ if count <= 0 {
+ count = 1
+ }
+ if _, err := a.db.Exec("UPDATE e2e_inventory SET stock = stock + ?
WHERE product_id = ?", count, productId); err != nil {
+ return false, err
+ }
+ fmt.Printf("InventoryAction.CompensateReduce: biz=%s, product=%s,
count=%d\n", businessKey, productId, count)
+ return true, nil
+}
+
+// BalanceAction (DB-backed) implements reduce/compensate with explicit params
+type BalanceAction struct{ db *sql.DB }
+
+func NewBalanceAction(db *sql.DB) *BalanceAction { return &BalanceAction{db:
db} }
+
+// Reduce(businessKey, userId, amount) -> (bool, error)
+func (b *BalanceAction) Reduce(businessKey string, userId string, amount int)
(bool, error) {
+ if amount <= 0 {
+ amount = 1
+ }
+ res, err := b.db.Exec("UPDATE e2e_balance SET amount = amount - ? WHERE
user_id = ? AND amount >= ?", amount, userId, amount)
+ if err != nil {
+ return false, err
+ }
+ if n, _ := res.RowsAffected(); n == 0 {
+ return false, fmt.Errorf("BALANCE_NOT_ENOUGH")
+ }
+ fmt.Printf("BalanceAction.Reduce: biz=%s, user=%s, amount=%d\n",
businessKey, userId, amount)
+ return true, nil
+}
+
+func (b *BalanceAction) CompensateReduce(businessKey string, userId string,
amount int) (bool, error) {
+ if amount <= 0 {
+ amount = 1
+ }
+ if _, err := b.db.Exec("UPDATE e2e_balance SET amount = amount + ?
WHERE user_id = ?", amount, userId); err != nil {
+ return false, err
+ }
+ fmt.Printf("BalanceAction.CompensateReduce: biz=%s, user=%s,
amount=%d\n", businessKey, userId, amount)
+ return true, nil
+}
+
+func main() {
+ var seataConf string
+ var engineConf string
+ flag.StringVar(&seataConf, "seataConf", "saga/e2e/seatago.yaml", "path
to seata-go client yaml")
+ flag.StringVar(&engineConf, "engineConf", "saga/e2e/config.yaml", "path
to saga engine config")
+ flag.Parse()
+
+ client.InitPath(seataConf)
+ if err := checkSeataConnectivity(seataConf); err != nil {
+ fmt.Fprintf(os.Stderr, "Seata server connectivity check failed:
%v\n", err)
+ os.Exit(1)
+ }
+
+ eng, err := core.NewProcessCtrlStateMachineEngine()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "create state machine engine failed:
%v\n", err)
+ os.Exit(1)
+ }
+ cfgIface := eng.GetStateMachineConfig()
+ if cfg, ok := cfgIface.(*engcfg.DefaultStateMachineConfig); ok {
+ if err := cfg.LoadConfig(engineConf); err != nil {
+ fmt.Fprintf(os.Stderr, "load engine config failed:
%v\n", err)
+ os.Exit(1)
+ }
+ if wd, err := os.Getwd(); err == nil {
+ absPattern := filepath.Join(wd,
"saga/e2e/statelang/*.json")
+ _ = cfg.RegisterStateMachineDef([]string{absPattern})
+ }
+ if err := cfg.Init(); err != nil {
+ fmt.Fprintf(os.Stderr, "init engine config failed:
%v\n", err)
+ os.Exit(1)
+ }
+ } else {
+ fmt.Fprintf(os.Stderr, "unexpected state machine config type:
%T\n", cfgIface)
+ os.Exit(1)
+ }
+
+ // Open business DB and seed data
+ bizDB, err := openBusinessDB(engineConf)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "open business db failed: %v\n", err)
+ os.Exit(1)
+ }
+ if err := seedBusinessData(bizDB); err != nil {
+ fmt.Fprintf(os.Stderr, "seed business data failed: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Register local services (DB-backed)
+ if lv := cfgIface.ServiceInvokerManager().ServiceInvoker("local"); lv
!= nil {
+ if lsi, ok := lv.(*invoker.LocalServiceInvoker); ok {
+ lsi.RegisterService("inventoryAction",
NewInventoryAction(bizDB))
+ lsi.RegisterService("balanceAction",
NewBalanceAction(bizDB))
+ }
+ }
+
+ // Run three scenarios sequentially, driven by parameters (no orders
table)
+ scenarios := []struct {
+ name string
+ params map[string]any
+ }{
+ {"success", successParams()},
+ {"compensate-balance", compensateBalanceParams()},
+ {"compensate-inventory", compensateInventoryParams()},
+ }
+ for _, sc := range scenarios {
+ inst, err := eng.Start(context.Background(),
"ReduceInventoryAndBalance", "", sc.params)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "start saga failed (%s): %v\n",
sc.name, err)
+ os.Exit(1)
+ }
+
fmt.Println("======================================================")
+ fmt.Printf("SCENARIO %s XID=%s status=%s compStatus=%s\n",
sc.name, inst.ID(), inst.Status(), inst.CompensationStatus())
+
fmt.Println("======================================================")
+
+ fmt.Println("")
+ fmt.Println("")
+ fmt.Println("")
+ time.Sleep(time.Second * 2)
+ }
+}
+
+type runtimeStoreConf struct {
+ StoreEnabled bool `yaml:"store_enabled"`
+ StoreType string `yaml:"store_type"`
+ StoreDSN string `yaml:"store_dsn"`
+ TCEnabled bool `yaml:"tc_enabled"`
+}
+
+// checkSeataConnectivity parses grouplist from seatago.yaml and dials the
first address
+func checkSeataConnectivity(seataConf string) error {
+ raw, err := os.ReadFile(seataConf)
+ if err != nil {
+ return err
+ }
+ type seataYaml struct {
+ Seata struct {
+ Service struct {
+ GroupList map[string]string `yaml:"grouplist"`
+ } `yaml:"service"`
+ } `yaml:"seata"`
+ }
+ var cfg seataYaml
+ if err := yaml.Unmarshal(raw, &cfg); err != nil {
+ return err
+ }
+ for _, addr := range cfg.Seata.Service.GroupList {
+ target := addr
+ // allow multiple via ","
+ if strings.Contains(addr, ",") {
+ parts := strings.Split(addr, ",")
+ target = strings.TrimSpace(parts[0])
+ }
+ if target == "" {
+ continue
+ }
+ conn, err := net.DialTimeout("tcp", target, 2_000_000_000)
+ if err != nil {
+ return fmt.Errorf("dial %s failed: %w", target, err)
+ }
+ _ = conn.Close()
+ return nil
+ }
+ return fmt.Errorf("no seata service.grouplist address found in %s",
seataConf)
+}
+
+// openBusinessDB opens a DB connection using engine store_dsn from YAML.
+func openBusinessDB(engineConf string) (*sql.DB, error) {
+ raw, err := os.ReadFile(engineConf)
+ if err != nil {
+ return nil, err
+ }
+ var r runtimeStoreConf
+ if err := yaml.Unmarshal(raw, &r); err != nil {
+ return nil, err
+ }
+ if !r.StoreEnabled || r.StoreType == "" || r.StoreDSN == "" {
+ return nil, fmt.Errorf("engine store not configured")
+ }
+ driver := r.StoreType
+ if driver == "sqlite" || driver == "sqlite3" {
+ driver = "sqlite3"
+ }
+ db, err := sql.Open(driver, r.StoreDSN)
+ if err != nil {
+ return nil, err
+ }
+ if err := db.Ping(); err != nil {
+ db.Close()
+ return nil, err
+ }
+ return db, nil
+}
+
+// seedBusinessData creates business tables and deterministic rows to drive
three scenarios
+func seedBusinessData(db *sql.DB) error {
+ // tables
+ _, err := db.Exec(`CREATE TABLE IF NOT EXISTS e2e_inventory (
+ product_id VARCHAR(64) PRIMARY KEY,
+ stock INT NOT NULL
+)`)
+ if err != nil {
+ return err
+ }
+ _, err = db.Exec(`CREATE TABLE IF NOT EXISTS e2e_balance (
+ user_id VARCHAR(64) PRIMARY KEY,
+ amount INT NOT NULL
+)`)
+ if err != nil {
+ return err
+ }
+ // upserts (MySQL syntax)
+ // inventories
+ if _, err := db.Exec(`INSERT INTO e2e_inventory(product_id, stock)
VALUES
+ ('p_s', 100), ('p_b', 100), ('p_i', 100)
+ ON DUPLICATE KEY UPDATE stock=VALUES(stock)`); err != nil {
+ return err
+ }
+ // balances
+ if _, err := db.Exec(`INSERT INTO e2e_balance(user_id, amount) VALUES
+ ('u_s', 1000), ('u_b', 50), ('u_i', 1000)
+ ON DUPLICATE KEY UPDATE amount=VALUES(amount)`); err != nil {
+ return err
+ }
+ return nil
+}
+
+// no time helpers needed
diff --git a/goimports.sh b/saga/e2e/migrate.sh
old mode 100644
new mode 100755
similarity index 62%
copy from goimports.sh
copy to saga/e2e/migrate.sh
index 4c30f1c..793f902
--- a/goimports.sh
+++ b/saga/e2e/migrate.sh
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +13,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+set -euo pipefail
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+if [ -z "${MYSQL_HOST:-}" ] || [ -z "${MYSQL_PORT:-}" ] || [ -z
"${MYSQL_USER:-}" ] || [ -z "${MYSQL_PWD:-}" ] || [ -z "${MYSQL_DB:-}" ]; then
+ echo "Please set MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PWD, MYSQL_DB"
+ exit 1
+fi
-# format go.mod
-go mod tidy
+echo "Applying schema to $MYSQL_HOST:$MYSQL_PORT/$MYSQL_DB ..."
+mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PWD"
"$MYSQL_DB" < saga/e2e/sql/mysql_saga_schema.sql
+echo "Schema applied."
diff --git a/goimports.sh b/saga/e2e/run.sh
old mode 100644
new mode 100755
similarity index 72%
copy from goimports.sh
copy to saga/e2e/run.sh
index 4c30f1c..ea2b43f
--- a/goimports.sh
+++ b/saga/e2e/run.sh
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +13,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+set -euo pipefail
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+SEATA_CONF=${1:-saga/e2e/seatago.yaml}
+ENGINE_CONF=${2:-saga/e2e/config.yaml}
-# format go.mod
-go mod tidy
+echo "Running saga e2e with seataConf=$SEATA_CONF engineConf=$ENGINE_CONF"
+go run ./saga/e2e -seataConf="$SEATA_CONF" -engineConf="$ENGINE_CONF"
diff --git a/saga/e2e/run_all.sh b/saga/e2e/run_all.sh
new file mode 100755
index 0000000..6933e86
--- /dev/null
+++ b/saga/e2e/run_all.sh
@@ -0,0 +1,144 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -euo pipefail
+
+# One-click e2e: optional docker-compose up + readiness wait, then run all
scenarios.
+
+DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
+REPO_ROOT="$DIR/../.."
+
+SEATA_CONF=${SEATA_CONF:-"$DIR/seatago.yaml"}
+ENGINE_CONF=${ENGINE_CONF:-"$DIR/config.yaml"}
+DO_UP=${DO_UP:-"false"}
+# Wait settings (seconds)
+WAIT_TIMEOUT=${WAIT_TIMEOUT:-60}
+WAIT_INTERVAL=${WAIT_INTERVAL:-2}
+# Extra margin after MySQL becomes reachable (to avoid initial EOF on fresh
start)
+WAIT_READY_MARGIN=${WAIT_READY_MARGIN:-15}
+
+usage() {
+ cat <<EOF
+Usage: $(basename "$0") [--up] [--seata <seatago.yaml>] [--engine
<config.yaml>]
+
+Options:
+ --up Start docker-compose (MySQL + Seata Server) before
running
+ --seata <file> Path to seatago.yaml (default: $SEATA_CONF)
+ --engine <file> Path to engine config (default: $ENGINE_CONF)
+
+Runs three scenarios in order:
+ 1) success
+ 2) compensate-balance
+ 3) compensate-inventory
+EOF
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --up) DO_UP="true"; shift ;;
+ --seata) SEATA_CONF="$2"; shift 2 ;;
+ --engine) ENGINE_CONF="$2"; shift 2 ;;
+ -h|--help) usage; exit 0 ;;
+ *) echo "Unknown arg: $1"; usage; exit 1 ;;
+ esac
+done
+
+require() { command -v "$1" >/dev/null 2>&1 || { echo "Missing required
command: $1"; exit 1; }; }
+
+echo "[+] Repo root: $REPO_ROOT"
+cd "$REPO_ROOT"
+
+require go
+
+[[ -f "$SEATA_CONF" ]] || { echo "seatago.yaml not found: $SEATA_CONF"; exit
1; }
+[[ -f "$ENGINE_CONF" ]] || { echo "engine config not found: $ENGINE_CONF";
exit 1; }
+
+if [[ "$DO_UP" == "true" ]]; then
+ require docker-compose || require docker
+ echo "[+] Resetting docker-compose services (MySQL + Seata Server) ..."
+ # Stop and remove old containers, networks and volumes to ensure a clean
start
+ docker-compose -f "$DIR/docker-compose.yml" down -v --remove-orphans || true
+ docker-compose -f "$DIR/docker-compose.yml" rm -f -s -v || true
+ echo "[+] Starting docker-compose services (fresh) ..."
+ docker-compose -f "$DIR/docker-compose.yml" up -d --force-recreate
+fi
+
+wait_for_tcp() {
+ local host="$1"; local port="$2"; local name="$3"; local
timeout="${4:-$WAIT_TIMEOUT}"; local interval="${5:-$WAIT_INTERVAL}"
+ echo "[+] Waiting for $name at $host:$port (timeout=${timeout}s) ..."
+ local start_ts=$(date +%s)
+ while true; do
+ if command -v nc >/dev/null 2>&1; then
+ if nc -z "$host" "$port" 2>/dev/null; then echo "[+] $name is
reachable"; return 0; fi
+ else
+ (exec 3<>/dev/tcp/$host/$port) 2>/dev/null && { exec 3>&- || true; echo
"[+] $name is reachable"; return 0; }
+ fi
+ local now=$(date +%s)
+ if (( now - start_ts >= timeout )); then
+ echo "[-] Timeout waiting for $name at $host:$port"; return 1
+ fi
+ sleep "$interval"
+ done
+}
+
+# Parse Seata target from seatago.yaml
+SEATA_ADDR=$(awk '
+ /grouplist:/ { gl=1; next }
+ gl==1 && /^[^[:space:]]/ { gl=0 }
+ gl==1 && /default:/ {
+ line=$0; gsub(/"/,"",line); sub(/.*default:[[:space:]]*/,"",line); print
line; exit;
+ }
+' "$SEATA_CONF" 2>/dev/null || true)
+[[ -n "$SEATA_ADDR" ]] || { echo "[-] Could not extract Seata
service.grouplist.default from $SEATA_CONF"; exit 1; }
+SEATA_HOST=${SEATA_ADDR%%:*}
+SEATA_PORT=${SEATA_ADDR##*:}
+
+# Parse MySQL from engine store_dsn (root:pwd@tcp(127.0.0.1:3306)/db)
+MYSQL_ADDR=$(sed -nE 's/.*store_dsn:[[:space:]]*"?[^\(]*tcp\(([^)]*)\).*/\1/p'
"$ENGINE_CONF" | head -n1)
+MYSQL_HOST=${MYSQL_ADDR%%:*}
+MYSQL_PORT=${MYSQL_ADDR##*:}
+
+if [[ "$DO_UP" == "true" ]]; then
+ if [[ -n "${MYSQL_HOST:-}" && -n "${MYSQL_PORT:-}" ]]; then
+ wait_for_tcp "$MYSQL_HOST" "$MYSQL_PORT" "MySQL" || exit 1
+ fi
+ wait_for_tcp "$SEATA_HOST" "$SEATA_PORT" "Seata" || exit 1
+ echo "[+] Extra wait ${WAIT_READY_MARGIN}s for services to finish
initialization ..."
+ sleep "$WAIT_READY_MARGIN"
+else
+ wait_for_tcp "$SEATA_HOST" "$SEATA_PORT" "Seata" || exit 1
+fi
+
+echo "[+] Running all scenarios via single process ..."
+OUT=$(go run ./saga/e2e -seataConf="$SEATA_CONF" -engineConf="$ENGINE_CONF" |
tee /dev/stderr)
+
+XID1=$(echo "$OUT" | awk -F '[ =,]+' '/SCENARIO success XID=/{print $4}' |
tail -n1)
+[[ -n "$XID1" ]] || { echo "[-] could not extract XID for success"; exit 1; }
+echo "[+] Validating DB for success (XID=$XID1) ..."
+go run ./saga/e2e/dbcheck -engine "$ENGINE_CONF" -xid "$XID1" -scenario success
+
+XID2=$(echo "$OUT" | awk -F '[ =,]+' '/SCENARIO compensate-balance XID=/{print
$4}' | tail -n1)
+[[ -n "$XID2" ]] || { echo "[-] could not extract XID for compensate-balance";
exit 1; }
+echo "[+] Validating DB for compensate-balance (XID=$XID2) ..."
+go run ./saga/e2e/dbcheck -engine "$ENGINE_CONF" -xid "$XID2" -scenario
compensate-balance
+
+XID3=$(echo "$OUT" | awk -F '[ =,]+' '/SCENARIO compensate-inventory
XID=/{print $4}' | tail -n1)
+[[ -n "$XID3" ]] || { echo "[-] could not extract XID for
compensate-inventory"; exit 1; }
+echo "[+] Validating DB for compensate-inventory (XID=$XID3) ..."
+go run ./saga/e2e/dbcheck -engine "$ENGINE_CONF" -xid "$XID3" -scenario
compensate-inventory
+
+echo "[+] All e2e scenarios finished"
+exit 0
diff --git a/goimports.sh b/saga/e2e/run_compensation.sh
old mode 100644
new mode 100755
similarity index 69%
copy from goimports.sh
copy to saga/e2e/run_compensation.sh
index 4c30f1c..9b26dec
--- a/goimports.sh
+++ b/saga/e2e/run_compensation.sh
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +13,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+set -euo pipefail
+
+DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+SEATA_CONF=${1:-$DIR/seatago.yaml}
+ENGINE_CONF=${2:-$DIR/config.yaml}
+SCENARIO=${3:-compensate-balance}
-# format go.mod
-go mod tidy
+echo "Running compensation scenario: $SCENARIO"
+go run ./saga/e2e -seataConf="$SEATA_CONF" -engineConf="$ENGINE_CONF"
-scenario="$SCENARIO"
diff --git a/saga/e2e/scenario_comp_balance.go
b/saga/e2e/scenario_comp_balance.go
new file mode 100644
index 0000000..a831440
--- /dev/null
+++ b/saga/e2e/scenario_comp_balance.go
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+func compensateBalanceParams() map[string]any {
+ // balance insufficient: u_b has 50, amount 100 -> fail -> compensate
inventory
+ return map[string]any{
+ "businessKey": "bk_comp_bal",
+ "productId": "p_b",
+ "count": 10,
+ "userId": "u_b",
+ "amount": 100,
+ }
+}
diff --git a/saga/e2e/scenario_comp_inventory.go
b/saga/e2e/scenario_comp_inventory.go
new file mode 100644
index 0000000..85d4e27
--- /dev/null
+++ b/saga/e2e/scenario_comp_inventory.go
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+func compensateInventoryParams() map[string]any {
+ // inventory insufficient: p_i has 100, count 200 -> fail -> no
compensation
+ return map[string]any{
+ "businessKey": "bk_comp_inv",
+ "productId": "p_i",
+ "count": 200,
+ "userId": "u_i",
+ "amount": 100,
+ }
+}
diff --git a/saga/e2e/scenario_success.go b/saga/e2e/scenario_success.go
new file mode 100644
index 0000000..0fbf1c4
--- /dev/null
+++ b/saga/e2e/scenario_success.go
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+func successParams() map[string]any {
+ return map[string]any{
+ "businessKey": "bk_success",
+ "productId": "p_s",
+ "count": 10,
+ "userId": "u_s",
+ "amount": 100,
+ }
+}
diff --git a/goimports.sh b/saga/e2e/seatago.yaml
similarity index 58%
copy from goimports.sh
copy to saga/e2e/seatago.yaml
index 4c30f1c..95e73ab 100644
--- a/goimports.sh
+++ b/saga/e2e/seatago.yaml
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +12,34 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+# Seata-go client config (placeholders; update to your environment)
+
+seata:
+ enabled: true
+ application-id: "saga-e2e-app"
+ tx-service-group: "default_tx_group"
+
+ client:
+ rm:
+ saga-branch-register-enable: true
+ tm: {}
+
+ service:
+ vgroup-mapping:
+ default_tx_group: "default"
+ grouplist:
+ default: "127.0.0.1:8091"
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+ transport:
+ type: TCP
+ server: NIO
+ heartbeat: true
-# format go.mod
-go mod tidy
+ getty:
+ reconnect-interval: 1
+ connection-num: 1
+ session:
+ compress-encoding: false
+ tcp-no-delay: true
+ keep-alive-period: 180
diff --git a/saga/e2e/sql/mysql_saga_schema.sql
b/saga/e2e/sql/mysql_saga_schema.sql
new file mode 100644
index 0000000..ab2b1da
--- /dev/null
+++ b/saga/e2e/sql/mysql_saga_schema.sql
@@ -0,0 +1,78 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements. See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License. You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+-- Saga MySQL schema (apply to your database)
+
+CREATE TABLE IF NOT EXISTS `seata_state_machine_def` (
+ `id` varchar(128) NOT NULL,
+ `tenant_id` varchar(32) DEFAULT NULL,
+ `app_name` varchar(64) DEFAULT NULL,
+ `name` varchar(128) NOT NULL,
+ `status` varchar(16) DEFAULT NULL,
+ `gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
+ `ver` varchar(16) DEFAULT NULL,
+ `type` varchar(32) DEFAULT NULL,
+ `content` mediumtext,
+ `recover_strategy` varchar(32) DEFAULT NULL,
+ `comment_` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `idx_smdef_name_tenant` (`name`,`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS `seata_state_machine_inst` (
+ `id` varchar(128) NOT NULL,
+ `machine_id` varchar(128) NOT NULL,
+ `tenant_id` varchar(32) DEFAULT NULL,
+ `parent_id` varchar(256) DEFAULT NULL,
+ `gmt_started` datetime DEFAULT CURRENT_TIMESTAMP,
+ `gmt_end` datetime DEFAULT NULL,
+ `status` varchar(16) DEFAULT NULL,
+ `compensation_status` varchar(16) DEFAULT NULL,
+ `is_running` tinyint(1) DEFAULT 0,
+ `gmt_updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ `business_key` varchar(128) DEFAULT NULL,
+ `start_params` mediumtext,
+ `end_params` mediumtext,
+ `excep` blob,
+ PRIMARY KEY (`id`),
+ KEY `idx_sminst_machine` (`machine_id`),
+ KEY `idx_sminst_parent` (`parent_id`),
+ KEY `idx_sminst_bizkey_tenant` (`business_key`,`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+CREATE TABLE IF NOT EXISTS `seata_state_inst` (
+ `id` varchar(128) NOT NULL,
+ `machine_inst_id` varchar(128) NOT NULL,
+ `name` varchar(128) NOT NULL,
+ `type` varchar(32) NOT NULL,
+ `gmt_started` datetime DEFAULT CURRENT_TIMESTAMP,
+ `service_name` varchar(255) DEFAULT NULL,
+ `service_method` varchar(255) DEFAULT NULL,
+ `service_type` varchar(32) DEFAULT NULL,
+ `is_for_update` tinyint(1) DEFAULT 0,
+ `input_params` mediumtext,
+ `status` varchar(16) DEFAULT NULL,
+ `business_key` varchar(128) DEFAULT NULL,
+ `state_id_compensated_for` varchar(128) DEFAULT NULL,
+ `state_id_retried_for` varchar(128) DEFAULT NULL,
+ `output_params` mediumtext,
+ `excep` blob,
+ `gmt_end` datetime DEFAULT NULL,
+ `gmt_updated` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_stinst_machine` (`machine_inst_id`),
+ KEY `idx_stinst_name` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
diff --git a/saga/e2e/statelang/reduce_inventory_and_balance.json
b/saga/e2e/statelang/reduce_inventory_and_balance.json
new file mode 100644
index 0000000..45d9155
--- /dev/null
+++ b/saga/e2e/statelang/reduce_inventory_and_balance.json
@@ -0,0 +1,75 @@
+{
+ "Name": "ReduceInventoryAndBalance",
+ "Comment": "Reduce inventory then balance with compensation (Go e2e;
Java-like actions)",
+ "StartState": "ReduceInventory",
+ "Version": "1.0",
+ "Persist": true,
+ "States": {
+ "ReduceInventory": {
+ "Type": "ServiceTask",
+ "ServiceType": "local",
+ "ServiceName": "inventoryAction",
+ "ServiceMethod": "Reduce",
+ "IsPersist": true,
+ "CompensateState": "CompensateReduceInventory",
+ "Input": [
+ "$CEL.elContext['context']['businessKey']",
+ "$CEL.elContext['context']['productId']",
+ "$CEL.elContext['context']['count']"
+ ],
+ "Catch": [
+ {"Exceptions": ["ERROR", "NOT_ENOUGH", "INVENTORY_NOT_ENOUGH"],
"Next": "CompensationTrigger"}
+ ],
+ "Next": "ReduceBalance"
+ },
+ "ReduceBalance": {
+ "Type": "ServiceTask",
+ "ServiceType": "local",
+ "ServiceName": "balanceAction",
+ "ServiceMethod": "Reduce",
+ "IsPersist": true,
+ "CompensateState": "CompensateReduceBalance",
+ "Input": [
+ "$CEL.elContext['context']['businessKey']",
+ "$CEL.elContext['context']['userId']",
+ "$CEL.elContext['context']['amount']"
+ ],
+ "Catch": [
+ {"Exceptions": ["ERROR", "reduce balance failed",
"BALANCE_NOT_ENOUGH"], "Next": "CompensationTrigger"}
+ ],
+ "Next": "Success"
+ },
+ "CompensationTrigger": {
+ "Type": "CompensationTrigger",
+ "Next": "Fail"
+ },
+ "CompensateReduceInventory": {
+ "Type": "ServiceTask",
+ "ServiceType": "local",
+ "ServiceName": "inventoryAction",
+ "ServiceMethod": "CompensateReduce",
+ "IsPersist": true,
+ "Input": [
+ "$CEL.elContext['context']['businessKey']",
+ "$CEL.elContext['context']['productId']",
+ "$CEL.elContext['context']['count']"
+ ],
+ "Next": "Success"
+ },
+ "CompensateReduceBalance": {
+ "Type": "ServiceTask",
+ "ServiceType": "local",
+ "ServiceName": "balanceAction",
+ "ServiceMethod": "CompensateReduce",
+ "IsPersist": true,
+ "Input": [
+ "$CEL.elContext['context']['businessKey']",
+ "$CEL.elContext['context']['userId']",
+ "$CEL.elContext['context']['amount']"
+ ],
+ "Next": "Success"
+ },
+ "Success": {"Type": "Succeed"},
+ "Fail": {"Type": "Fail", "Comment": "Ended with compensation or error"}
+ }
+}
diff --git a/goimports.sh b/saga/e2e/up_and_run.sh
old mode 100644
new mode 100755
similarity index 51%
copy from goimports.sh
copy to saga/e2e/up_and_run.sh
index 4c30f1c..c1730cf
--- a/goimports.sh
+++ b/saga/e2e/up_and_run.sh
@@ -1,4 +1,4 @@
-#
+#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -13,17 +13,23 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-# format go imports style
-go install -v golang.org/x/tools/cmd/goimports
-goimports -w .
+set -euo pipefail
+
+DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
+
+echo "Resetting docker-compose services (MySQL + Seata Server)..."
+docker-compose -f "$DIR/docker-compose.yml" down -v --remove-orphans || true
+docker-compose -f "$DIR/docker-compose.yml" rm -f -s -v || true
+echo "Starting docker-compose services (fresh)..."
+docker-compose -f "$DIR/docker-compose.yml" up -d --force-recreate
-# format licence style
-go install github.com/apache/skywalking-eyes/cmd/license-eye@latest
-license-eye header fix
-# check dependency licence is valid
-license-eye dependency check
+echo "Waiting for MySQL (3306) and Seata Server (8091) to be ready..."
+# simple wait loop
+for i in {1..60}; do
+ nc -z 127.0.0.1 3306 && nc -z 127.0.0.1 8091 && break || true
+ sleep 2
+done
-# format go.mod
-go mod tidy
+echo "Running saga e2e example"
+go run ./saga/e2e -seataConf="$DIR/seatago.yaml" -engineConf="$DIR/config.yaml"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]