From 0562e9f457017cbb92c9eeab31ce615d5e8e2cea Mon Sep 17 00:00:00 2001 From: funkye Date: Sat, 21 Sep 2024 21:39:18 +0800 Subject: [PATCH 01/54] optimize: sync 2.2.0 to 2.x branch (#6872) --- .github/workflows/test-druid.yml | 14 + .github/workflows/test-ubuntu.yml | 162 ++ .github/workflows/test.yml | 45 +- build/pom.xml | 2 +- changes/en-us/2.2.0.md | 159 ++ changes/en-us/2.x.md | 134 -- changes/zh-cn/2.2.0.md | 163 ++ changes/zh-cn/2.x.md | 136 +- .../extend/TestConfigFromExtendSPI.java | 3 + .../seata/config/nacos/NacosMockTest.java | 9 + console/pom.xml | 1 - distribution/LICENSE | 5 +- distribution/NOTICE.md | 1666 ++++++++--------- 13 files changed, 1330 insertions(+), 1169 deletions(-) create mode 100644 .github/workflows/test-ubuntu.yml create mode 100644 changes/en-us/2.2.0.md create mode 100644 changes/zh-cn/2.2.0.md diff --git a/.github/workflows/test-druid.yml b/.github/workflows/test-druid.yml index 9bc33c4f466..75763a58474 100644 --- a/.github/workflows/test-druid.yml +++ b/.github/workflows/test-druid.yml @@ -6,6 +6,20 @@ on: jobs: test-druid: + services: + redis: + image: redis:7.2 + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + nacos: + image: nacos/nacos-server:v2.4.2 + ports: + - 8848:8848 + env: + MODE: standalone + SPRING_SECURITY_ENABLED: false + options: --health-cmd="curl -f http://localhost:8848/nacos" --health-interval=10s --health-timeout=5s --health-retries=3 --health-start-period=30s name: "test-druid" runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml new file mode 100644 index 00000000000..90c754b06c8 --- /dev/null +++ b/.github/workflows/test-ubuntu.yml @@ -0,0 +1,162 @@ +name: "test-ubuntu" + +on: + push: + branches: [ test*, "*.*.*" ] +jobs: + # job 1 + test: + name: "test" + services: + redis: + image: redis:7.2 + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + nacos: + image: nacos/nacos-server:v2.4.2 + ports: + - 8848:8848 + env: + MODE: standalone + SPRING_SECURITY_ENABLED: false + options: --health-cmd="curl -f http://localhost:8848/nacos" --health-interval=10s --health-timeout=5s --health-retries=3 --health-start-period=30s + runs-on: "${{ matrix.os }}-latest" + strategy: + max-parallel: 3 + fail-fast: false + matrix: + java: [ 8, 11, 17, 21 ] + os: [ + ubuntu, + ] + springboot: [ + 2.7.18 -D spring-framework.version=5.3.31, + 2.6.15 -D spring-framework.version=5.3.27, + 2.5.15 -D spring-framework.version=5.3.27, + 2.4.13 -D spring-framework.version=5.3.13, + 2.3.12.RELEASE -D spring-framework.version=5.2.15.RELEASE, + 2.2.13.RELEASE -D spring-framework.version=5.2.12.RELEASE, + #2.1.18.RELEASE, + #2.0.9.RELEASE, + ] + steps: + # step 1 + - name: "Checkout" + uses: actions/checkout@v3 + # step 2 + - name: "Use Python 3.x" + uses: actions/setup-python@v2 + with: + python-version: '3.12' + # step 3 + - name: "Set up Java JDK" + uses: actions/setup-java@v3.12.0 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + # step 4 + ## step 4.1: for Ubuntu and MacOS + - name: "Test with Maven on '${{ matrix.os }}' OS" + run: | + ./mvnw -T 4C clean test -P args-for-client-test -Dspring-boot.version=${{ matrix.springboot }} -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + + # job 2 + test-springboot3x: + name: "test-springboot3.x" + services: + redis: + image: redis:7.2 + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + nacos: + image: nacos/nacos-server:v2.4.2 + ports: + - 8848:8848 + env: + MODE: standalone + SPRING_SECURITY_ENABLED: false + options: --health-cmd="curl -f http://localhost:8848/nacos" --health-interval=10s --health-timeout=5s --health-retries=3 --health-start-period=30s + runs-on: "${{ matrix.os }}-latest" + strategy: + max-parallel: 3 + fail-fast: false + matrix: + java: [ 17, 21 ] + os: [ + ubuntu, + ] + springboot: [ + 3.2.0 -D spring-framework.version=6.1.1, + 3.1.6 -D spring-framework.version=6.0.14, + 3.0.13 -D spring-framework.version=6.0.14, + ] + steps: + # step 1 + - name: "Checkout" + uses: actions/checkout@v3 + # step 2 + - name: "Use Python 3.x" + uses: actions/setup-python@v2 + with: + python-version: '3.12' + # step 3 + - name: "Set up Java JDK" + uses: actions/setup-java@v3.12.0 + with: + distribution: 'zulu' + java-version: ${{ matrix.java }} + # step 4 + ## step 4.1: for Ubuntu and MacOS + - name: "Test with Maven on '${{ matrix.os }}' OS" + if: matrix.os != 'windows' + run: | + ./mvnw -T 4C clean test -P args-for-client-test -Dspring-boot.version=${{ matrix.springboot }} -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + ## step 4.2: for Windows + - name: "Build with Maven on 'windows' OS (Skip tests)" + if: matrix.os == 'windows' + run: | # Skip tests, because too many errors in unit-test. + ./mvnw.cmd -version; + ./mvnw.cmd clean install -P args-for-client-test -DskipTests -D spring-boot.version=${{ matrix.springboot }} -e -B -D org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + + # job 3 + test-arm64: + name: "test-arm64" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + springboot: [ + #2.7.18 -Dspring-framework.version=5.3.31, # The maven-compiler-plugin will throw an error for an unknown reason. + #2.6.15 -Dspring-framework.version=5.3.27, # The maven-compiler-plugin will throw an error for an unknown reason. + #2.5.15 -Dspring-framework.version=5.3.27, # The maven-compiler-plugin will throw an error for an unknown reason. + 2.4.13 -Dspring-framework.version=5.3.13, + 2.3.12.RELEASE -Dspring-framework.version=5.2.15.RELEASE, + 2.2.13.RELEASE -Dspring-framework.version=5.2.12.RELEASE, + #2.1.18.RELEASE, + #2.0.9.RELEASE, + ] + steps: + # step 1 + - name: "Checkout" + uses: actions/checkout@v3 + # step 2 + - name: "Set up QEMU" + id: qemu + uses: docker/setup-qemu-action@v3 + # step 3 + - name: "Build with Maven on 'arm64v8/ubuntu:20.04' OS (Skip tests)" + run: | + docker run --rm -v ${{ github.workspace }}:/ws:rw --workdir=/ws \ + arm64v8/ubuntu:20.04 \ + bash -exc 'apt-get update -y && \ + apt-get install maven -y && \ + apt-get install -y python3 python3-pip python3-distutils && \ + apt-get install -y build-essential && \ + mvn -version && \ + mvn -T 4C clean install \ + -Dspring-boot.version=${{ matrix.springboot }} \ + -Prelease-seata \ + -DskipTests \ + -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b07abb53067..af757ebecba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,11 +9,11 @@ jobs: name: "test" runs-on: "${{ matrix.os }}-latest" strategy: + max-parallel: 3 fail-fast: false matrix: java: [ 8, 11, 17, 21 ] os: [ - ubuntu, macos, windows, # Skip tests, because too many errors in unit-test. ] @@ -69,11 +69,11 @@ jobs: name: "test-springboot3.x" runs-on: "${{ matrix.os }}-latest" strategy: + max-parallel: 3 fail-fast: false matrix: java: [ 17, 21 ] os: [ - ubuntu, macos, windows, # Skip tests, because too many errors in unit-test. ] @@ -118,44 +118,3 @@ jobs: run: | # Skip tests, because too many errors in unit-test. ./mvnw.cmd -version; ./mvnw.cmd clean install -P args-for-client-test -DskipTests -D spring-boot.version=${{ matrix.springboot }} -e -B -D org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; - - # job 3 - test-arm64: - name: "test-arm64" - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - springboot: [ - #2.7.18 -Dspring-framework.version=5.3.31, # The maven-compiler-plugin will throw an error for an unknown reason. - #2.6.15 -Dspring-framework.version=5.3.27, # The maven-compiler-plugin will throw an error for an unknown reason. - #2.5.15 -Dspring-framework.version=5.3.27, # The maven-compiler-plugin will throw an error for an unknown reason. - 2.4.13 -Dspring-framework.version=5.3.13, - 2.3.12.RELEASE -Dspring-framework.version=5.2.15.RELEASE, - 2.2.13.RELEASE -Dspring-framework.version=5.2.12.RELEASE, - #2.1.18.RELEASE, - #2.0.9.RELEASE, - ] - steps: - # step 1 - - name: "Checkout" - uses: actions/checkout@v3 - # step 2 - - name: "Set up QEMU" - id: qemu - uses: docker/setup-qemu-action@v3 - # step 3 - - name: "Build with Maven on 'arm64v8/ubuntu:20.04' OS (Skip tests)" - run: | - docker run --rm -v ${{ github.workspace }}:/ws:rw --workdir=/ws \ - arm64v8/ubuntu:20.04 \ - bash -exc 'apt-get update -y && \ - apt-get install maven -y && \ - apt-get install -y python3 python3-pip python3-distutils && \ - apt-get install -y build-essential && \ - mvn -version && \ - mvn -T 4C clean install \ - -Dspring-boot.version=${{ matrix.springboot }} \ - -Prelease-seata \ - -DskipTests \ - -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn' diff --git a/build/pom.xml b/build/pom.xml index 87f476eb361..e7f0815ab6e 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -71,7 +71,7 @@ - 2.2.0-SNAPSHOT + 2.2.0 1.8 diff --git a/changes/en-us/2.2.0.md b/changes/en-us/2.2.0.md new file mode 100644 index 00000000000..b18658cf518 --- /dev/null +++ b/changes/en-us/2.2.0.md @@ -0,0 +1,159 @@ +Add changes here for all PR submitted to the 2.x branch. + + + +### feature: +- [[#6536](https://github.com/apache/incubator-seata/pull/6536)] support naming server client +- [[#6226](https://github.com/apache/incubator-seata/pull/6226)] multi-version seata protocol support +- [[#6537](https://github.com/apache/incubator-seata/pull/6537)] support Namingserver +- [[#6538](https://github.com/apache/incubator-seata/pull/6538)] Integration of naming server on the Seata server side +- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] add TCC three-phase hooks + +### bugfix: +- [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix @Async annotation not working in ClusterWatcherManager +- [[#6624](https://github.com/apache/incubator-seata/pull/6624)] fix Alibaba Dubbo convert error +- [[#6627](https://github.com/apache/incubator-seata/pull/6627)] fix the issue of xaEnded not being reset +- [[#6626](https://github.com/apache/incubator-seata/pull/6626)] fix hsf ConsumerModel convert error +- [[#6816](https://github.com/apache/incubator-seata/pull/6816)] Fix NPE when getting branchType in a non-global transaction context +- [[#6642](https://github.com/apache/incubator-seata/pull/6642)] codecov token not found +- [[#6661](https://github.com/apache/incubator-seata/pull/6661)] fix `tableMeta` cache scheduled refresh issue +- [[#6486](https://github.com/apache/incubator-seata/pull/6486)] fix mysql undo log update sql data more than max allowed packet +- [[#6668](https://github.com/apache/incubator-seata/pull/6668)] thread safety issue when adding and removing instances +- [[#6678](https://github.com/apache/incubator-seata/pull/6678)] fix the same record has different lowkeys due to mixed case of table names yesterday +- [[#6697](https://github.com/apache/incubator-seata/pull/6697)] v0 ByteBuf should not decode by super class +- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] fix readonly branch commit errors in Oracle XA transactions +- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] fix dameng rollback info un compress fail +- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] fix dameng delete undo fail +- [[#6511](https://github.com/apache/incubator-seata/pull/6511)] fix the failure of rollbacking back of the delete SQL at AT mode when using SQLServer +- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] fix support serialization for dm.jdbc.driver.DmdbTimestamp +- [[#6757](https://github.com/apache/incubator-seata/pull/6757)] the bug where multiple nodes cannot be retrieved from the naming server +- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] fix tcc fence deadLock +- [[#6778](https://github.com/apache/incubator-seata/pull/6778)] fix namingserver node term +- [[#6765](https://github.com/apache/incubator-seata/pull/6765)] fix MySQL driver loading by replacing custom classloader with system classloader for better compatibility and simplified process +- [[#6781](https://github.com/apache/incubator-seata/pull/6781)] the issue where the TC occasionally fails to go offline from the NamingServer +- [[#6785](https://github.com/apache/incubator-seata/pull/6785)] fix prometheus fail to return seata metrics data when using Nacos +- [[#6797](https://github.com/apache/incubator-seata/pull/6797)] fall back to any of available cluster address when query cluster address is empty +- [[#6800](https://github.com/apache/incubator-seata/pull/6800)] make exception message generic for all database drivers +- [[#6759](https://github.com/apache/incubator-seata/pull/6759)] fix the error of active refresh failure of cross-database table metadata +- [[#6812](https://github.com/apache/incubator-seata/pull/6812)] bugfix: change group and node offline status are not pushed in real time +- [[#6817](https://github.com/apache/incubator-seata/pull/6817)] bugfix: fix namingserver changVgroup failed +- [[#6820](https://github.com/apache/incubator-seata/pull/6820)] Fix file path error in the Dockerfile +- [[#6825](https://github.com/apache/incubator-seata/pull/6825)] Fix the issue of XA mode transaction timeout and inability to roll back in Postgres +- [[#6833](https://github.com/apache/incubator-seata/pull/6833)] SQLIntegrityConstraintViolationException capture incorrectly when inserting a globallock +- [[#6835](https://github.com/apache/incubator-seata/pull/6835)] Fix the issue of missing request body of post method in HttpClientUtil +- [[#6845](https://github.com/apache/incubator-seata/pull/6845)] fix rocksDB opens the same file multiple times +- [[#6840](https://github.com/apache/incubator-seata/pull/6840)] Fix the issue of unsafe deserialization in ProcessorYaml.java +- [[#6843](https://github.com/apache/incubator-seata/pull/6843)] Fix 403 error when sending a POST request from the console +- [[#6850](https://github.com/apache/incubator-seata/pull/6850)] raft mode is backward compatible with version 2.0 +- [[#6855](https://github.com/apache/incubator-seata/pull/6855)] after scaling down a Raft cluster, the metadata still contains the removed node +- [[#6859](https://github.com/apache/incubator-seata/pull/6859)] remove duplicated dependency in pom + + +### optimize: +- [[#6499](https://github.com/apache/incubator-seata/pull/6499)] split the task thread pool for committing and rollbacking statuses +- [[#6208](https://github.com/apache/incubator-seata/pull/6208)] optimize : load SeataSerializer by version +- [[#6209](https://github.com/apache/incubator-seata/pull/6209)] Eliminate RpcMessage and Encoder/Decoder dependencies +- [[#6634](https://github.com/apache/incubator-seata/pull/6634)] select channel handles based on protocol versions +- [[#6523](https://github.com/apache/incubator-seata/pull/6523)] upgrade alibaba/druid version to 1.2.20 +- [[#6566](https://github.com/apache/incubator-seata/pull/6566)] Add support for configuring exposeProxy in GlobalTransactionScanner +- [[#6534](https://github.com/apache/incubator-seata/pull/6534)] optimize: send async response +- [[#6640](https://github.com/apache/incubator-seata/pull/6640)] modify codecov config +- [[#6640](https://github.com/apache/incubator-seata/pull/6648)] add license header +- [[#6666](https://github.com/apache/incubator-seata/pull/6666)] add ExceptionUtil class for unwarp error msg +- [[#6654](https://github.com/apache/incubator-seata/pull/6654)] add Namingserver package module +- [[#6667](https://github.com/apache/incubator-seata/pull/6667)] optimize Namingserver log output +- [[#6687](https://github.com/apache/incubator-seata/pull/6687)] delete static code built on the frontend +- [[#6700](https://github.com/apache/incubator-seata/pull/6700)] remove sdk version checking +- [[#6727](https://github.com/apache/incubator-seata/pull/6727)] deserialize performance optimize +- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] add the default console and security config on application.example.yml and application.raft.example.yml +- [[#6651](https://github.com/apache/incubator-seata/pull/6651)] add license header for proto file +- [[#6653](https://github.com/apache/incubator-seata/pull/6653)] optimize multiple licenses and remove license urls +- [[#6655](https://github.com/apache/incubator-seata/pull/6655)] update front-end license +- [[#6652](https://github.com/apache/incubator-seata/pull/6673)] add license header for spring config file +- [[#6674](https://github.com/apache/incubator-seata/pull/6674)] update source license +- [[#6650](https://github.com/apache/incubator-seata/pull/6650)] add license header for SPI file +- [[#6741](https://github.com/apache/incubator-seata/pull/6741)] upgrade tomcat-embed-core to 9.0.90 +- [[#6742](https://github.com/apache/incubator-seata/pull/6742)] upgrade npmjs version in console +- [[#6743](https://github.com/apache/incubator-seata/pull/6743)] upgrade npmjs version in saga +- [[#6746](https://github.com/apache/incubator-seata/pull/6746)] optimize compatible dependencies +- [[#6745](https://github.com/apache/incubator-seata/pull/6745)] fix node-gyp build error on arm64 and macos +- [[#6749](https://github.com/apache/incubator-seata/pull/6749)] optimize WebSecurityConfig csrf +- [[#6748](https://github.com/apache/incubator-seata/pull/6748)] optimize ConsistentHashLoadBalance Algorithm +- [[#6747](https://github.com/apache/incubator-seata/pull/6747)] optimize fastjson deserialization +- [[#6755](https://github.com/apache/incubator-seata/pull/6755)] optimize namingserver code logic +- [[#6763](https://github.com/apache/incubator-seata/pull/6763)] optimize NacosConfiguration singleton reload +- [[#6761](https://github.com/apache/incubator-seata/pull/6761)] optimize the namingserver code to improve readability +- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] report the tcc fence transaction isolation level +- [[#6770](https://github.com/apache/incubator-seata/pull/6770)] Automatic deletion of namingserver vgroup through Caffeine map +- [[#6780](https://github.com/apache/incubator-seata/pull/6780)] optimize the reflection operation in class `SerializerServiceLoader` +- [[#6784](https://github.com/apache/incubator-seata/pull/6784)] upgrade axios to 1.7.4 +- [[#6787](https://github.com/apache/incubator-seata/pull/6787)] upgrade elliptic to 6.5.7 +- [[#6783](https://github.com/apache/incubator-seata/pull/6783)] rename the server naming/v1 api to vgroup/v1 +- [[#6793](https://github.com/apache/incubator-seata/pull/6793)] fix npmjs conflicts +- [[#6793](https://github.com/apache/incubator-seata/pull/6795)] optimize the initialization logic for server meta +- [[#6794](https://github.com/apache/incubator-seata/pull/6794)] optimize NacosMockTest UT case +- [[#6806](https://github.com/apache/incubator-seata/pull/6806)] optimize `tableMeta` cache scheduled refresh issue +- [[#6808](https://github.com/apache/incubator-seata/pull/6808)] change version to 2.2.0-SNAPSHOT +- [[#6819](https://github.com/apache/incubator-seata/pull/6819)] merge the packaging processes of namingserver and seata-server +- [[#6827](https://github.com/apache/incubator-seata/pull/6827)] rename namingserver registry type +- [[#6836](https://github.com/apache/incubator-seata/pull/6836)] add independent nacos for the CI process +- [[#6841](https://github.com/apache/incubator-seata/pull/6841)] update the LICENSE and NOTICE files and standardize dependency versions +- [[#6823](https://github.com/apache/incubator-seata/pull/6823)] fix typo +- [[#6779](https://github.com/apache/incubator-seata/pull/6779)] use curator instead of zkclient in config model +- [[#6831](https://github.com/apache/incubator-seata/pull/6831)] use curator instead of zkclient in registry model +- [[#6852](https://github.com/apache/incubator-seata/pull/6852)] optimize raft metadata api +- [[#6863](https://github.com/apache/incubator-seata/pull/6863)] update NOTICE.md + +### refactor: + +### security: + +### test: +- [[#6533](https://github.com/apache/incubator-seata/pull/6533)] increase integration-tx-api module unit test coverage +- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] add unit test for sql-parser-core +- [[#6647](https://github.com/apache/incubator-seata/pull/6647)] improve the test case coverage of saga module to 70% +- [[#6695](https://github.com/apache/incubator-seata/pull/6695)] old version(< 0.7.1) client test case for multi-version protocol +- [[#6752](https://github.com/apache/incubator-seata/pull/6752)] Improve the test case coverage of metrics module +- [[#6764](https://github.com/apache/incubator-seata/pull/6764)] add Apollo mock test case +- [[#6750](https://github.com/apache/incubator-seata/pull/6750)] increase spring autoconfigure module unit test converage +- [[#6773](https://github.com/apache/incubator-seata/pull/6773)] fix the wrong code coverage from codecov icon in default branch +- [[#6821](https://github.com/apache/incubator-seata/pull/6821)] fix the test case assertions +- [[#6803](https://github.com/apache/incubator-seata/pull/6803)] optimize: compilation and packaging for the ARM64 architecture + + +Thanks to these contributors for their code commits. Please report an unintended omission. + + +- [slievrly](https://github.com/slievrly) +- [tuwenlin](https://github.com/tuwenlin) +- [YeonCheolGit](https://github.com/YeonCheolGit) +- [liuqiufeng](https://github.com/liuqiufeng) +- [God-Gan](https://github.com/God-Gan) +- [Bughue](https://github.com/Bughue) +- [funky-eyes](https://github.com/funky-eyes) +- [tanyaofei](https://github.com/tanyaofei) +- [traitsisgiorgos](https://github.com/traitsisgiorgos) +- [wanghongzhou](https://github.com/wanghongzhou) +- [ggbocoder](https://github.com/ggbocoder) +- [azatyamanaev](https://github.com/azatyamanaev) +- [xjlgod](https://github.com/xjlgod) +- [xingfudeshi](https://github.com/xingfudeshi) +- [wuwen5](https://github.com/wuwen5) +- [jsbxyyx](https://github.com/jsbxyyx) +- [iAmClever](https://github.com/iAmClever) +- [GoodBoyCoder](https://github.com/GoodBoyCoder) +- [liuqiufeng](https://github.com/liuqiufeng) +- [caohdgege](https://github.com/caohdgege) +- [TakeActionNow2019](https://github.com/TakeActionNow2019) +- [imashimaro](https://github.com/hmj776521114) +- [lyl2008dsg](https://github.com/lyl2008dsg) +- [lightClouds917](https://github.com/lightClouds917) +- [l81893521](https://github.com/l81893521) +- [laywin](https://github.com/laywin) +- [xiaoxiangyeyu0](https://github.com/xiaoxiangyeyu0) +- [LegGasai](https://github.com/LegGasai) +- [yangli-stu](https://github.com/yangli-stu) +- [heliang666s](https://github.com/heliang666s) + + +Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index b18658cf518..eee100a62d4 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -3,157 +3,23 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: -- [[#6536](https://github.com/apache/incubator-seata/pull/6536)] support naming server client -- [[#6226](https://github.com/apache/incubator-seata/pull/6226)] multi-version seata protocol support -- [[#6537](https://github.com/apache/incubator-seata/pull/6537)] support Namingserver -- [[#6538](https://github.com/apache/incubator-seata/pull/6538)] Integration of naming server on the Seata server side -- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] add TCC three-phase hooks ### bugfix: -- [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix @Async annotation not working in ClusterWatcherManager -- [[#6624](https://github.com/apache/incubator-seata/pull/6624)] fix Alibaba Dubbo convert error -- [[#6627](https://github.com/apache/incubator-seata/pull/6627)] fix the issue of xaEnded not being reset -- [[#6626](https://github.com/apache/incubator-seata/pull/6626)] fix hsf ConsumerModel convert error -- [[#6816](https://github.com/apache/incubator-seata/pull/6816)] Fix NPE when getting branchType in a non-global transaction context -- [[#6642](https://github.com/apache/incubator-seata/pull/6642)] codecov token not found -- [[#6661](https://github.com/apache/incubator-seata/pull/6661)] fix `tableMeta` cache scheduled refresh issue -- [[#6486](https://github.com/apache/incubator-seata/pull/6486)] fix mysql undo log update sql data more than max allowed packet -- [[#6668](https://github.com/apache/incubator-seata/pull/6668)] thread safety issue when adding and removing instances -- [[#6678](https://github.com/apache/incubator-seata/pull/6678)] fix the same record has different lowkeys due to mixed case of table names yesterday -- [[#6697](https://github.com/apache/incubator-seata/pull/6697)] v0 ByteBuf should not decode by super class -- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] fix readonly branch commit errors in Oracle XA transactions -- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] fix dameng rollback info un compress fail -- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] fix dameng delete undo fail -- [[#6511](https://github.com/apache/incubator-seata/pull/6511)] fix the failure of rollbacking back of the delete SQL at AT mode when using SQLServer -- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] fix support serialization for dm.jdbc.driver.DmdbTimestamp -- [[#6757](https://github.com/apache/incubator-seata/pull/6757)] the bug where multiple nodes cannot be retrieved from the naming server -- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] fix tcc fence deadLock -- [[#6778](https://github.com/apache/incubator-seata/pull/6778)] fix namingserver node term -- [[#6765](https://github.com/apache/incubator-seata/pull/6765)] fix MySQL driver loading by replacing custom classloader with system classloader for better compatibility and simplified process -- [[#6781](https://github.com/apache/incubator-seata/pull/6781)] the issue where the TC occasionally fails to go offline from the NamingServer -- [[#6785](https://github.com/apache/incubator-seata/pull/6785)] fix prometheus fail to return seata metrics data when using Nacos -- [[#6797](https://github.com/apache/incubator-seata/pull/6797)] fall back to any of available cluster address when query cluster address is empty -- [[#6800](https://github.com/apache/incubator-seata/pull/6800)] make exception message generic for all database drivers -- [[#6759](https://github.com/apache/incubator-seata/pull/6759)] fix the error of active refresh failure of cross-database table metadata -- [[#6812](https://github.com/apache/incubator-seata/pull/6812)] bugfix: change group and node offline status are not pushed in real time -- [[#6817](https://github.com/apache/incubator-seata/pull/6817)] bugfix: fix namingserver changVgroup failed -- [[#6820](https://github.com/apache/incubator-seata/pull/6820)] Fix file path error in the Dockerfile -- [[#6825](https://github.com/apache/incubator-seata/pull/6825)] Fix the issue of XA mode transaction timeout and inability to roll back in Postgres -- [[#6833](https://github.com/apache/incubator-seata/pull/6833)] SQLIntegrityConstraintViolationException capture incorrectly when inserting a globallock -- [[#6835](https://github.com/apache/incubator-seata/pull/6835)] Fix the issue of missing request body of post method in HttpClientUtil -- [[#6845](https://github.com/apache/incubator-seata/pull/6845)] fix rocksDB opens the same file multiple times -- [[#6840](https://github.com/apache/incubator-seata/pull/6840)] Fix the issue of unsafe deserialization in ProcessorYaml.java -- [[#6843](https://github.com/apache/incubator-seata/pull/6843)] Fix 403 error when sending a POST request from the console -- [[#6850](https://github.com/apache/incubator-seata/pull/6850)] raft mode is backward compatible with version 2.0 -- [[#6855](https://github.com/apache/incubator-seata/pull/6855)] after scaling down a Raft cluster, the metadata still contains the removed node -- [[#6859](https://github.com/apache/incubator-seata/pull/6859)] remove duplicated dependency in pom ### optimize: -- [[#6499](https://github.com/apache/incubator-seata/pull/6499)] split the task thread pool for committing and rollbacking statuses -- [[#6208](https://github.com/apache/incubator-seata/pull/6208)] optimize : load SeataSerializer by version -- [[#6209](https://github.com/apache/incubator-seata/pull/6209)] Eliminate RpcMessage and Encoder/Decoder dependencies -- [[#6634](https://github.com/apache/incubator-seata/pull/6634)] select channel handles based on protocol versions -- [[#6523](https://github.com/apache/incubator-seata/pull/6523)] upgrade alibaba/druid version to 1.2.20 -- [[#6566](https://github.com/apache/incubator-seata/pull/6566)] Add support for configuring exposeProxy in GlobalTransactionScanner -- [[#6534](https://github.com/apache/incubator-seata/pull/6534)] optimize: send async response -- [[#6640](https://github.com/apache/incubator-seata/pull/6640)] modify codecov config -- [[#6640](https://github.com/apache/incubator-seata/pull/6648)] add license header -- [[#6666](https://github.com/apache/incubator-seata/pull/6666)] add ExceptionUtil class for unwarp error msg -- [[#6654](https://github.com/apache/incubator-seata/pull/6654)] add Namingserver package module -- [[#6667](https://github.com/apache/incubator-seata/pull/6667)] optimize Namingserver log output -- [[#6687](https://github.com/apache/incubator-seata/pull/6687)] delete static code built on the frontend -- [[#6700](https://github.com/apache/incubator-seata/pull/6700)] remove sdk version checking -- [[#6727](https://github.com/apache/incubator-seata/pull/6727)] deserialize performance optimize -- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] add the default console and security config on application.example.yml and application.raft.example.yml -- [[#6651](https://github.com/apache/incubator-seata/pull/6651)] add license header for proto file -- [[#6653](https://github.com/apache/incubator-seata/pull/6653)] optimize multiple licenses and remove license urls -- [[#6655](https://github.com/apache/incubator-seata/pull/6655)] update front-end license -- [[#6652](https://github.com/apache/incubator-seata/pull/6673)] add license header for spring config file -- [[#6674](https://github.com/apache/incubator-seata/pull/6674)] update source license -- [[#6650](https://github.com/apache/incubator-seata/pull/6650)] add license header for SPI file -- [[#6741](https://github.com/apache/incubator-seata/pull/6741)] upgrade tomcat-embed-core to 9.0.90 -- [[#6742](https://github.com/apache/incubator-seata/pull/6742)] upgrade npmjs version in console -- [[#6743](https://github.com/apache/incubator-seata/pull/6743)] upgrade npmjs version in saga -- [[#6746](https://github.com/apache/incubator-seata/pull/6746)] optimize compatible dependencies -- [[#6745](https://github.com/apache/incubator-seata/pull/6745)] fix node-gyp build error on arm64 and macos -- [[#6749](https://github.com/apache/incubator-seata/pull/6749)] optimize WebSecurityConfig csrf -- [[#6748](https://github.com/apache/incubator-seata/pull/6748)] optimize ConsistentHashLoadBalance Algorithm -- [[#6747](https://github.com/apache/incubator-seata/pull/6747)] optimize fastjson deserialization -- [[#6755](https://github.com/apache/incubator-seata/pull/6755)] optimize namingserver code logic -- [[#6763](https://github.com/apache/incubator-seata/pull/6763)] optimize NacosConfiguration singleton reload -- [[#6761](https://github.com/apache/incubator-seata/pull/6761)] optimize the namingserver code to improve readability -- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] report the tcc fence transaction isolation level -- [[#6770](https://github.com/apache/incubator-seata/pull/6770)] Automatic deletion of namingserver vgroup through Caffeine map -- [[#6780](https://github.com/apache/incubator-seata/pull/6780)] optimize the reflection operation in class `SerializerServiceLoader` -- [[#6784](https://github.com/apache/incubator-seata/pull/6784)] upgrade axios to 1.7.4 -- [[#6787](https://github.com/apache/incubator-seata/pull/6787)] upgrade elliptic to 6.5.7 -- [[#6783](https://github.com/apache/incubator-seata/pull/6783)] rename the server naming/v1 api to vgroup/v1 -- [[#6793](https://github.com/apache/incubator-seata/pull/6793)] fix npmjs conflicts -- [[#6793](https://github.com/apache/incubator-seata/pull/6795)] optimize the initialization logic for server meta -- [[#6794](https://github.com/apache/incubator-seata/pull/6794)] optimize NacosMockTest UT case -- [[#6806](https://github.com/apache/incubator-seata/pull/6806)] optimize `tableMeta` cache scheduled refresh issue -- [[#6808](https://github.com/apache/incubator-seata/pull/6808)] change version to 2.2.0-SNAPSHOT -- [[#6819](https://github.com/apache/incubator-seata/pull/6819)] merge the packaging processes of namingserver and seata-server -- [[#6827](https://github.com/apache/incubator-seata/pull/6827)] rename namingserver registry type -- [[#6836](https://github.com/apache/incubator-seata/pull/6836)] add independent nacos for the CI process -- [[#6841](https://github.com/apache/incubator-seata/pull/6841)] update the LICENSE and NOTICE files and standardize dependency versions -- [[#6823](https://github.com/apache/incubator-seata/pull/6823)] fix typo -- [[#6779](https://github.com/apache/incubator-seata/pull/6779)] use curator instead of zkclient in config model -- [[#6831](https://github.com/apache/incubator-seata/pull/6831)] use curator instead of zkclient in registry model -- [[#6852](https://github.com/apache/incubator-seata/pull/6852)] optimize raft metadata api -- [[#6863](https://github.com/apache/incubator-seata/pull/6863)] update NOTICE.md ### refactor: ### security: ### test: -- [[#6533](https://github.com/apache/incubator-seata/pull/6533)] increase integration-tx-api module unit test coverage -- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] add unit test for sql-parser-core -- [[#6647](https://github.com/apache/incubator-seata/pull/6647)] improve the test case coverage of saga module to 70% -- [[#6695](https://github.com/apache/incubator-seata/pull/6695)] old version(< 0.7.1) client test case for multi-version protocol -- [[#6752](https://github.com/apache/incubator-seata/pull/6752)] Improve the test case coverage of metrics module -- [[#6764](https://github.com/apache/incubator-seata/pull/6764)] add Apollo mock test case -- [[#6750](https://github.com/apache/incubator-seata/pull/6750)] increase spring autoconfigure module unit test converage -- [[#6773](https://github.com/apache/incubator-seata/pull/6773)] fix the wrong code coverage from codecov icon in default branch -- [[#6821](https://github.com/apache/incubator-seata/pull/6821)] fix the test case assertions -- [[#6803](https://github.com/apache/incubator-seata/pull/6803)] optimize: compilation and packaging for the ARM64 architecture Thanks to these contributors for their code commits. Please report an unintended omission. - [slievrly](https://github.com/slievrly) -- [tuwenlin](https://github.com/tuwenlin) -- [YeonCheolGit](https://github.com/YeonCheolGit) -- [liuqiufeng](https://github.com/liuqiufeng) -- [God-Gan](https://github.com/God-Gan) -- [Bughue](https://github.com/Bughue) -- [funky-eyes](https://github.com/funky-eyes) -- [tanyaofei](https://github.com/tanyaofei) -- [traitsisgiorgos](https://github.com/traitsisgiorgos) -- [wanghongzhou](https://github.com/wanghongzhou) -- [ggbocoder](https://github.com/ggbocoder) -- [azatyamanaev](https://github.com/azatyamanaev) -- [xjlgod](https://github.com/xjlgod) -- [xingfudeshi](https://github.com/xingfudeshi) -- [wuwen5](https://github.com/wuwen5) -- [jsbxyyx](https://github.com/jsbxyyx) -- [iAmClever](https://github.com/iAmClever) -- [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [liuqiufeng](https://github.com/liuqiufeng) -- [caohdgege](https://github.com/caohdgege) -- [TakeActionNow2019](https://github.com/TakeActionNow2019) -- [imashimaro](https://github.com/hmj776521114) -- [lyl2008dsg](https://github.com/lyl2008dsg) -- [lightClouds917](https://github.com/lightClouds917) -- [l81893521](https://github.com/l81893521) -- [laywin](https://github.com/laywin) -- [xiaoxiangyeyu0](https://github.com/xiaoxiangyeyu0) -- [LegGasai](https://github.com/LegGasai) -- [yangli-stu](https://github.com/yangli-stu) -- [heliang666s](https://github.com/heliang666s) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.2.0.md b/changes/zh-cn/2.2.0.md new file mode 100644 index 00000000000..3a951142ef8 --- /dev/null +++ b/changes/zh-cn/2.2.0.md @@ -0,0 +1,163 @@ +所有提交到 2.x 分支的 PR 请在此处登记。 + + + +### feature: +- [[#6536](https://github.com/apache/incubator-seata/pull/6536)] 支持 naming server客户端 +- [[#6226](https://github.com/apache/incubator-seata/pull/6226)] 支持seata私有协议多版本兼容 +- [[#6537](https://github.com/apache/incubator-seata/pull/6537)] 支持 Namingserver +- [[#6538](https://github.com/apache/incubator-seata/pull/6538)] seata server端集成naming server +- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] 添加tcc三阶段钩子函数,方便用户拓展业务逻辑(比如多数据源情况下可以用于切换数据源) + +### bugfix: +- [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix @Async注解ClusterWatcherManager中不生效的问题 +- [[#6624](https://github.com/apache/incubator-seata/pull/6624)] 修复 Alibaba Dubbo 转换错误 +- [[#6627](https://github.com/apache/incubator-seata/pull/6627)] 修复 xaEnded 没有重置 +- [[#6626](https://github.com/apache/incubator-seata/pull/6626)] 修复 hsf ConsumerModel 转换错误 +- [[#6816](https://github.com/apache/incubator-seata/pull/6816)] 修复在非全局事务上下文中获取branchType时的NPE问题 +- [[#6640](https://github.com/apache/incubator-seata/pull/6640)] 优化codecov相关配置 +- [[#6642](https://github.com/apache/incubator-seata/pull/6642)] 修复codecov token找不到导致无法提交单测覆盖度报告 +- [[#6661](https://github.com/apache/incubator-seata/pull/6661)] 修复`tableMeta`缓存定时刷新失效问题 +- [[#6486](https://github.com/apache/incubator-seata/pull/6486)] 修复在mysql数据库下undo_log sql数据超过最大包大小错误 +- [[#6668](https://github.com/apache/incubator-seata/pull/6668)] 解决namingserver同一个集群下instance添加和删除时的线程安全问题 +- [[#6678](https://github.com/apache/incubator-seata/pull/6678)] 修复由于表名大小写问题导致的相同记录生成不同RowKey的问题 +- [[#6697](https://github.com/apache/incubator-seata/pull/6697)] v0版本的ByteBuf不应由父类先解码 +- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] 修复Oracle XA事务中只读分支提交出错的问题 +- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] 修复达梦数据库的getRollbackInfo没有解压缩的问题 +- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] 修复达梦数据库的delete sql回滚失败的问题 +- [[#6511](https://github.com/apache/incubator-seata/pull/6511)] 修复在使用SQLServer时,AT模式delete语句无法正常回滚的问题 +- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] 修复达梦数据库的对dm.jdbc.driver.DmdbTimestamp的支持 +- [[#6757](https://github.com/apache/incubator-seata/pull/6757)] 修复client通过namingserver只能获取到一个tc节点的bug +- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] 修复tcc fence死锁 +- [[#6778](https://github.com/apache/incubator-seata/pull/6778)] 修复namingserver的节点term为0问题 +- [[#6765](https://github.com/apache/incubator-seata/pull/6765)] 改进MySQL驱动加载机制,将自定义类加载器替换为系统类加载器,更兼容简化流程 +- [[#6781](https://github.com/apache/incubator-seata/pull/6781)] 修复tc下线时,由于定时任务没有先关闭,导致下线后还会被注册上,需要靠namingserver的健康检查来下线的bug +- [[#6785](https://github.com/apache/incubator-seata/pull/6785)] 修复 prometheus 在与 Nacos 集成时无法返回 seata metrics 数据的问题 +- [[#6797](https://github.com/apache/incubator-seata/pull/6797)] 当查询的集群地址为空时,获取可用的任意集群地址 +- [[#6800](https://github.com/apache/incubator-seata/pull/6800)] 使异常消息对所有数据库驱动程序通用 +- [[#6812](https://github.com/apache/incubator-seata/pull/6812)] 修复切换事务分组和节点下线时namingserver没有实时感知和推送的bug +- [[#6759](https://github.com/apache/incubator-seata/pull/6759)] 修复跨库表主动刷新`tableMeta`的异常问题 +- [[#6817](https://github.com/apache/incubator-seata/pull/6817)] 修复namingserver切换事务分组失效的问题 +- [[#6820](https://github.com/apache/incubator-seata/pull/6820)] 修复Dockerfile得文件结构错误 +- [[#6825](https://github.com/apache/incubator-seata/pull/6825)] 修复Postgres的XA模式事务超时无法回滚问题 +- [[#6833](https://github.com/apache/incubator-seata/pull/6833)] 插入全局锁时 SQLIntegrityConstraintViolationException 捕获不正确 +- [[#6835](https://github.com/apache/incubator-seata/pull/6835)] 修复HttpClientUtil中post方法请求体缺失的问题 +- [[#6845](https://github.com/apache/incubator-seata/pull/6845)] 修复rocksdb open相同文件多次的问题 +- [[#6840](https://github.com/apache/incubator-seata/pull/6840)] 修复ProcessorYaml中不安全的反序列化 +- [[#6843](https://github.com/apache/incubator-seata/pull/6843)] 修复从控制台发送POST请求时出现的403错误 +- [[#6850](https://github.com/apache/incubator-seata/pull/6850)] raft模式向下兼容2.0版本 +- [[#6855](https://github.com/apache/incubator-seata/pull/6855)] 修复raft缩容后元数据中残留该节点的问题(需先升级到2.2再进行缩容) +- [[#6859](https://github.com/apache/incubator-seata/pull/6859)] 移除重复的依赖 + +### optimize: +- [[#6499](https://github.com/apache/incubator-seata/pull/6499)] 拆分 committing 和 rollbacking 状态的任务线程池 +- [[#6208](https://github.com/apache/incubator-seata/pull/6208)] 支持多版本的Seata序列化 +- [[#6209](https://github.com/apache/incubator-seata/pull/6209)] 解开 RpcMessage 和 Encoder/Decoder 的互相依赖 +- [[#6634](https://github.com/apache/incubator-seata/pull/6634)] 根据协议版本指定channel handle +- [[#6523](https://github.com/apache/incubator-seata/pull/6523)] 升级 alibaba/druid 的版本到1.2.20 +- [[#6566](https://github.com/apache/incubator-seata/pull/6566)] 支持GlobalTransactionScanner类中exposeProxy属性的配置 +- [[#6534](https://github.com/apache/incubator-seata/pull/6534)] 优化: 发送异步响应 +- [[#6534](https://github.com/apache/incubator-seata/pull/6648)] 增加license header信息 +- [[#6666](https://github.com/apache/incubator-seata/pull/6666)] 添加ExceptionUtil工具类用于解包装异常 +- [[#6654](https://github.com/apache/incubator-seata/pull/6654)] 增加Namingserver打包功能 +- [[#6667](https://github.com/apache/incubator-seata/pull/6667)] 优化Namingserver日志输出 +- [[#6687](https://github.com/apache/incubator-seata/pull/6687)] 删除前端构建的静态代码 +- [[#6700](https://github.com/apache/incubator-seata/pull/6700)] 去掉sdk版本检查 +- [[#6727](https://github.com/apache/incubator-seata/pull/6727)] 反序列化性能优化 +- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] 为application.example.yml与application.raft.example.yml添加默认安全配置 +- [[#6651](https://github.com/apache/incubator-seata/pull/6651)] 为 proto 文件添加 license header +- [[#6653](https://github.com/apache/incubator-seata/pull/6653)] 优化多 license和移除license url +- [[#6655](https://github.com/apache/incubator-seata/pull/6655)] 更新前端 license +- [[#6652](https://github.com/apache/incubator-seata/pull/6652)] 为spring 配置文件添加 license header +- [[#6656](https://github.com/apache/incubator-seata/pull/6656)] 更新源码中的 license +- [[#6650](https://github.com/apache/incubator-seata/pull/6650)] 为 SPI配置文件添加 license header +- [[#6741](https://github.com/apache/incubator-seata/pull/6741)] 升级 tomcat-embed-core 至 9.0.90 版本 +- [[#6742](https://github.com/apache/incubator-seata/pull/6742)] 升级 console 模块 npmjs 版本 +- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] 为application.example.yml与application.raft.example.yml添加默认安全配置 +- [[#6743](https://github.com/apache/incubator-seata/pull/6743)] 升级saga模块npmjs版本 +- [[#6746](https://github.com/apache/incubator-seata/pull/6746)] 优化 compatible 模块依赖 +- [[#6745](https://github.com/apache/incubator-seata/pull/6745)] 修复 node-gyp 在 arm64 和 macos 构建失败问题 +- [[#6749](https://github.com/apache/incubator-seata/pull/6749)] 优化 WebSecurityConfig csrf 处理 +- [[#6748](https://github.com/apache/incubator-seata/pull/6748)] 优化 ConsistentHashLoadBalance 算法 +- [[#6747](https://github.com/apache/incubator-seata/pull/6747)] 优化 fastjson 反序列化 +- [[#6755](https://github.com/apache/incubator-seata/pull/6755)] 优化namingserver代码逻辑 +- [[#6763](https://github.com/apache/incubator-seata/pull/6763)] 优化 NacosConfiguration 单例加载 +- [[#6761](https://github.com/apache/incubator-seata/pull/6761)] 提升namingserver manager代码可读性 +- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] 上报tcc fence事务隔离级别 +- [[#6770](https://github.com/apache/incubator-seata/pull/6770)] 通过caffeine map支持namingserver事务分组的过期删除 +- [[#6780](https://github.com/apache/incubator-seata/pull/6780)] 优化类 `SerializerServiceLoader` 中的反射操作 +- [[#6784](https://github.com/apache/incubator-seata/pull/6784)] 升级 axios 至 1.7.4 版本 +- [[#6787](https://github.com/apache/incubator-seata/pull/6787)] 升级 elliptic 至 6.5.7 版本 +- [[#6783](https://github.com/apache/incubator-seata/pull/6783)] 将server事务分组修改接口改为/vgroup/v1 +- [[#6793](https://github.com/apache/incubator-seata/pull/6793)] 修复 npmjs 依赖冲突问题 +- [[#6794](https://github.com/apache/incubator-seata/pull/6794)] 优化 NacosMockTest 单测问题 +- [[#6793](https://github.com/apache/incubator-seata/pull/6795)] 独立server的meta信息初始化逻辑 +- [[#6806](https://github.com/apache/incubator-seata/pull/6806)] 优化`tableMeta`缓存定时刷新问题 +- [[#6808](https://github.com/apache/incubator-seata/pull/6808)] 修改版本号为2.2.0-SNAPSHOT +- [[#6819](https://github.com/apache/incubator-seata/pull/6819)] namingserver与server的合并打包 +- [[#6827](https://github.com/apache/incubator-seata/pull/6827)] 重命名namingserver注册类型改为seata +- [[#6836](https://github.com/apache/incubator-seata/pull/6836)] 为CI流程增加独立nacos +- [[#6841](https://github.com/apache/incubator-seata/pull/6841)] 更新license和notice文件并统一依赖版本 +- [[#6823](https://github.com/apache/incubator-seata/pull/6823)] 修正DefaultGlobalTransaction日志的错别字 +- [[#6779](https://github.com/apache/incubator-seata/pull/6779)] 在config模块中使用curator替代zkclient +- [[#6831](https://github.com/apache/incubator-seata/pull/6831)] 在registry模块中使用curator替代zkclient +- [[#6803](https://github.com/apache/incubator-seata/pull/6803)] 优化 ARM64 架构的编译打包 +- [[#6852](https://github.com/apache/incubator-seata/pull/6852)] 优化raft接口 +- [[#6863](https://github.com/apache/incubator-seata/pull/6863)] 更新notice.md + + +### refactor: + + +### security: + +### test: +- [[#6533](https://github.com/apache/incubator-seata/pull/6533)] 增加 Integration-TX-API 模块单元测试覆盖范围 +- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] 添加sql-parser-core模块测试用例 +- [[#6647](https://github.com/apache/incubator-seata/pull/6647)] 增加saga模块的测试用例覆盖率 +- [[#6695](https://github.com/apache/incubator-seata/pull/6695)] 多版本协议的旧版本(< 0.7.1)客户端测试用例 +- [[#6752](https://github.com/apache/incubator-seata/pull/6752)] 增加metrics模块测试用例覆盖率 +- [[#6764](https://github.com/apache/incubator-seata/pull/6764)] 增加 Apollo Mock 测试用例 +- [[#6750](https://github.com/apache/incubator-seata/pull/6750)] 提升spring autoconfigure模块单测覆盖率 +- [[#6773](https://github.com/apache/incubator-seata/pull/6773)] 修复codecov图标显示错误的代码覆盖率 +- [[#6821](https://github.com/apache/incubator-seata/pull/6821)] 修复单元测试断言 + + +非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 + + +- [slievrly](https://github.com/slievrly) +- [tuwenlin](https://github.com/tuwenlin) +- [YeonCheolGit](https://github.com/YeonCheolGit) +- [liuqiufeng](https://github.com/liuqiufeng) +- [God-Gan](https://github.com/God-Gan) +- [Bughue](https://github.com/Bughue) +- [funky-eyes](https://github.com/funky-eyes) +- [tanyaofei](https://github.com/tanyaofei) +- [traitsisgiorgos](https://github.com/traitsisgiorgos) +- [wanghongzhou](https://github.com/wanghongzhou) +- [ggbocoder](https://github.com/ggbocoder) +- [azatyamanaev](https://github.com/azatyamanaev) +- [xjlgod](https://github.com/xjlgod) +- [xingfudeshi](https://github.com/xingfudeshi) +- [wuwen5](https://github.com/wuwen5) +- [jsbxyyx](https://github.com/jsbxyyx) +- [iAmClever](https://github.com/iAmClever) +- [GoodBoyCoder](https://github.com/GoodBoyCoder) +- [liuqiufeng](https://github.com/liuqiufeng) +- [caohdgege](https://github.com/caohdgege) +- [TakeActionNow2019](https://github.com/TakeActionNow2019) +- [imashimaro](https://github.com/hmj776521114) +- [lyl2008dsg](https://github.com/lyl2008dsg) +- [lightClouds917](https://github.com/lightClouds917) +- [l81893521](https://github.com/l81893521) +- [laywin](https://github.com/laywin) +- [xiaoxiangyeyu0](https://github.com/xiaoxiangyeyu0) +- [LegGasai](https://github.com/LegGasai) +- [yangli-stu](https://github.com/yangli-stu) +- [heliang666s](https://github.com/heliang666s) + + + +同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 + diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 3a951142ef8..a193a3f7f4e 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -3,107 +3,11 @@ ### feature: -- [[#6536](https://github.com/apache/incubator-seata/pull/6536)] 支持 naming server客户端 -- [[#6226](https://github.com/apache/incubator-seata/pull/6226)] 支持seata私有协议多版本兼容 -- [[#6537](https://github.com/apache/incubator-seata/pull/6537)] 支持 Namingserver -- [[#6538](https://github.com/apache/incubator-seata/pull/6538)] seata server端集成naming server -- [[#6766](https://github.com/apache/incubator-seata/pull/6766)] 添加tcc三阶段钩子函数,方便用户拓展业务逻辑(比如多数据源情况下可以用于切换数据源) ### bugfix: -- [[#6592](https://github.com/apache/incubator-seata/pull/6592)] fix @Async注解ClusterWatcherManager中不生效的问题 -- [[#6624](https://github.com/apache/incubator-seata/pull/6624)] 修复 Alibaba Dubbo 转换错误 -- [[#6627](https://github.com/apache/incubator-seata/pull/6627)] 修复 xaEnded 没有重置 -- [[#6626](https://github.com/apache/incubator-seata/pull/6626)] 修复 hsf ConsumerModel 转换错误 -- [[#6816](https://github.com/apache/incubator-seata/pull/6816)] 修复在非全局事务上下文中获取branchType时的NPE问题 -- [[#6640](https://github.com/apache/incubator-seata/pull/6640)] 优化codecov相关配置 -- [[#6642](https://github.com/apache/incubator-seata/pull/6642)] 修复codecov token找不到导致无法提交单测覆盖度报告 -- [[#6661](https://github.com/apache/incubator-seata/pull/6661)] 修复`tableMeta`缓存定时刷新失效问题 -- [[#6486](https://github.com/apache/incubator-seata/pull/6486)] 修复在mysql数据库下undo_log sql数据超过最大包大小错误 -- [[#6668](https://github.com/apache/incubator-seata/pull/6668)] 解决namingserver同一个集群下instance添加和删除时的线程安全问题 -- [[#6678](https://github.com/apache/incubator-seata/pull/6678)] 修复由于表名大小写问题导致的相同记录生成不同RowKey的问题 -- [[#6697](https://github.com/apache/incubator-seata/pull/6697)] v0版本的ByteBuf不应由父类先解码 -- [[#6707](https://github.com/apache/incubator-seata/pull/6707)] 修复Oracle XA事务中只读分支提交出错的问题 -- [[#6711](https://github.com/apache/incubator-seata/pull/6711)] 修复达梦数据库的getRollbackInfo没有解压缩的问题 -- [[#6714](https://github.com/apache/incubator-seata/pull/6714)] 修复达梦数据库的delete sql回滚失败的问题 -- [[#6511](https://github.com/apache/incubator-seata/pull/6511)] 修复在使用SQLServer时,AT模式delete语句无法正常回滚的问题 -- [[#6701](https://github.com/apache/incubator-seata/pull/6728)] 修复达梦数据库的对dm.jdbc.driver.DmdbTimestamp的支持 -- [[#6757](https://github.com/apache/incubator-seata/pull/6757)] 修复client通过namingserver只能获取到一个tc节点的bug -- [[#6769](https://github.com/apache/incubator-seata/pull/6769)] 修复tcc fence死锁 -- [[#6778](https://github.com/apache/incubator-seata/pull/6778)] 修复namingserver的节点term为0问题 -- [[#6765](https://github.com/apache/incubator-seata/pull/6765)] 改进MySQL驱动加载机制,将自定义类加载器替换为系统类加载器,更兼容简化流程 -- [[#6781](https://github.com/apache/incubator-seata/pull/6781)] 修复tc下线时,由于定时任务没有先关闭,导致下线后还会被注册上,需要靠namingserver的健康检查来下线的bug -- [[#6785](https://github.com/apache/incubator-seata/pull/6785)] 修复 prometheus 在与 Nacos 集成时无法返回 seata metrics 数据的问题 -- [[#6797](https://github.com/apache/incubator-seata/pull/6797)] 当查询的集群地址为空时,获取可用的任意集群地址 -- [[#6800](https://github.com/apache/incubator-seata/pull/6800)] 使异常消息对所有数据库驱动程序通用 -- [[#6812](https://github.com/apache/incubator-seata/pull/6812)] 修复切换事务分组和节点下线时namingserver没有实时感知和推送的bug -- [[#6759](https://github.com/apache/incubator-seata/pull/6759)] 修复跨库表主动刷新`tableMeta`的异常问题 -- [[#6817](https://github.com/apache/incubator-seata/pull/6817)] 修复namingserver切换事务分组失效的问题 -- [[#6820](https://github.com/apache/incubator-seata/pull/6820)] 修复Dockerfile得文件结构错误 -- [[#6825](https://github.com/apache/incubator-seata/pull/6825)] 修复Postgres的XA模式事务超时无法回滚问题 -- [[#6833](https://github.com/apache/incubator-seata/pull/6833)] 插入全局锁时 SQLIntegrityConstraintViolationException 捕获不正确 -- [[#6835](https://github.com/apache/incubator-seata/pull/6835)] 修复HttpClientUtil中post方法请求体缺失的问题 -- [[#6845](https://github.com/apache/incubator-seata/pull/6845)] 修复rocksdb open相同文件多次的问题 -- [[#6840](https://github.com/apache/incubator-seata/pull/6840)] 修复ProcessorYaml中不安全的反序列化 -- [[#6843](https://github.com/apache/incubator-seata/pull/6843)] 修复从控制台发送POST请求时出现的403错误 -- [[#6850](https://github.com/apache/incubator-seata/pull/6850)] raft模式向下兼容2.0版本 -- [[#6855](https://github.com/apache/incubator-seata/pull/6855)] 修复raft缩容后元数据中残留该节点的问题(需先升级到2.2再进行缩容) -- [[#6859](https://github.com/apache/incubator-seata/pull/6859)] 移除重复的依赖 + ### optimize: -- [[#6499](https://github.com/apache/incubator-seata/pull/6499)] 拆分 committing 和 rollbacking 状态的任务线程池 -- [[#6208](https://github.com/apache/incubator-seata/pull/6208)] 支持多版本的Seata序列化 -- [[#6209](https://github.com/apache/incubator-seata/pull/6209)] 解开 RpcMessage 和 Encoder/Decoder 的互相依赖 -- [[#6634](https://github.com/apache/incubator-seata/pull/6634)] 根据协议版本指定channel handle -- [[#6523](https://github.com/apache/incubator-seata/pull/6523)] 升级 alibaba/druid 的版本到1.2.20 -- [[#6566](https://github.com/apache/incubator-seata/pull/6566)] 支持GlobalTransactionScanner类中exposeProxy属性的配置 -- [[#6534](https://github.com/apache/incubator-seata/pull/6534)] 优化: 发送异步响应 -- [[#6534](https://github.com/apache/incubator-seata/pull/6648)] 增加license header信息 -- [[#6666](https://github.com/apache/incubator-seata/pull/6666)] 添加ExceptionUtil工具类用于解包装异常 -- [[#6654](https://github.com/apache/incubator-seata/pull/6654)] 增加Namingserver打包功能 -- [[#6667](https://github.com/apache/incubator-seata/pull/6667)] 优化Namingserver日志输出 -- [[#6687](https://github.com/apache/incubator-seata/pull/6687)] 删除前端构建的静态代码 -- [[#6700](https://github.com/apache/incubator-seata/pull/6700)] 去掉sdk版本检查 -- [[#6727](https://github.com/apache/incubator-seata/pull/6727)] 反序列化性能优化 -- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] 为application.example.yml与application.raft.example.yml添加默认安全配置 -- [[#6651](https://github.com/apache/incubator-seata/pull/6651)] 为 proto 文件添加 license header -- [[#6653](https://github.com/apache/incubator-seata/pull/6653)] 优化多 license和移除license url -- [[#6655](https://github.com/apache/incubator-seata/pull/6655)] 更新前端 license -- [[#6652](https://github.com/apache/incubator-seata/pull/6652)] 为spring 配置文件添加 license header -- [[#6656](https://github.com/apache/incubator-seata/pull/6656)] 更新源码中的 license -- [[#6650](https://github.com/apache/incubator-seata/pull/6650)] 为 SPI配置文件添加 license header -- [[#6741](https://github.com/apache/incubator-seata/pull/6741)] 升级 tomcat-embed-core 至 9.0.90 版本 -- [[#6742](https://github.com/apache/incubator-seata/pull/6742)] 升级 console 模块 npmjs 版本 -- [[#6732](https://github.com/apache/incubator-seata/pull/6732)] 为application.example.yml与application.raft.example.yml添加默认安全配置 -- [[#6743](https://github.com/apache/incubator-seata/pull/6743)] 升级saga模块npmjs版本 -- [[#6746](https://github.com/apache/incubator-seata/pull/6746)] 优化 compatible 模块依赖 -- [[#6745](https://github.com/apache/incubator-seata/pull/6745)] 修复 node-gyp 在 arm64 和 macos 构建失败问题 -- [[#6749](https://github.com/apache/incubator-seata/pull/6749)] 优化 WebSecurityConfig csrf 处理 -- [[#6748](https://github.com/apache/incubator-seata/pull/6748)] 优化 ConsistentHashLoadBalance 算法 -- [[#6747](https://github.com/apache/incubator-seata/pull/6747)] 优化 fastjson 反序列化 -- [[#6755](https://github.com/apache/incubator-seata/pull/6755)] 优化namingserver代码逻辑 -- [[#6763](https://github.com/apache/incubator-seata/pull/6763)] 优化 NacosConfiguration 单例加载 -- [[#6761](https://github.com/apache/incubator-seata/pull/6761)] 提升namingserver manager代码可读性 -- [[#6768](https://github.com/apache/incubator-seata/pull/6768)] 上报tcc fence事务隔离级别 -- [[#6770](https://github.com/apache/incubator-seata/pull/6770)] 通过caffeine map支持namingserver事务分组的过期删除 -- [[#6780](https://github.com/apache/incubator-seata/pull/6780)] 优化类 `SerializerServiceLoader` 中的反射操作 -- [[#6784](https://github.com/apache/incubator-seata/pull/6784)] 升级 axios 至 1.7.4 版本 -- [[#6787](https://github.com/apache/incubator-seata/pull/6787)] 升级 elliptic 至 6.5.7 版本 -- [[#6783](https://github.com/apache/incubator-seata/pull/6783)] 将server事务分组修改接口改为/vgroup/v1 -- [[#6793](https://github.com/apache/incubator-seata/pull/6793)] 修复 npmjs 依赖冲突问题 -- [[#6794](https://github.com/apache/incubator-seata/pull/6794)] 优化 NacosMockTest 单测问题 -- [[#6793](https://github.com/apache/incubator-seata/pull/6795)] 独立server的meta信息初始化逻辑 -- [[#6806](https://github.com/apache/incubator-seata/pull/6806)] 优化`tableMeta`缓存定时刷新问题 -- [[#6808](https://github.com/apache/incubator-seata/pull/6808)] 修改版本号为2.2.0-SNAPSHOT -- [[#6819](https://github.com/apache/incubator-seata/pull/6819)] namingserver与server的合并打包 -- [[#6827](https://github.com/apache/incubator-seata/pull/6827)] 重命名namingserver注册类型改为seata -- [[#6836](https://github.com/apache/incubator-seata/pull/6836)] 为CI流程增加独立nacos -- [[#6841](https://github.com/apache/incubator-seata/pull/6841)] 更新license和notice文件并统一依赖版本 -- [[#6823](https://github.com/apache/incubator-seata/pull/6823)] 修正DefaultGlobalTransaction日志的错别字 -- [[#6779](https://github.com/apache/incubator-seata/pull/6779)] 在config模块中使用curator替代zkclient -- [[#6831](https://github.com/apache/incubator-seata/pull/6831)] 在registry模块中使用curator替代zkclient -- [[#6803](https://github.com/apache/incubator-seata/pull/6803)] 优化 ARM64 架构的编译打包 -- [[#6852](https://github.com/apache/incubator-seata/pull/6852)] 优化raft接口 -- [[#6863](https://github.com/apache/incubator-seata/pull/6863)] 更新notice.md ### refactor: @@ -112,50 +16,12 @@ ### security: ### test: -- [[#6533](https://github.com/apache/incubator-seata/pull/6533)] 增加 Integration-TX-API 模块单元测试覆盖范围 -- [[#6608](https://github.com/apache/incubator-seata/pull/6608)] 添加sql-parser-core模块测试用例 -- [[#6647](https://github.com/apache/incubator-seata/pull/6647)] 增加saga模块的测试用例覆盖率 -- [[#6695](https://github.com/apache/incubator-seata/pull/6695)] 多版本协议的旧版本(< 0.7.1)客户端测试用例 -- [[#6752](https://github.com/apache/incubator-seata/pull/6752)] 增加metrics模块测试用例覆盖率 -- [[#6764](https://github.com/apache/incubator-seata/pull/6764)] 增加 Apollo Mock 测试用例 -- [[#6750](https://github.com/apache/incubator-seata/pull/6750)] 提升spring autoconfigure模块单测覆盖率 -- [[#6773](https://github.com/apache/incubator-seata/pull/6773)] 修复codecov图标显示错误的代码覆盖率 -- [[#6821](https://github.com/apache/incubator-seata/pull/6821)] 修复单元测试断言 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 - [slievrly](https://github.com/slievrly) -- [tuwenlin](https://github.com/tuwenlin) -- [YeonCheolGit](https://github.com/YeonCheolGit) -- [liuqiufeng](https://github.com/liuqiufeng) -- [God-Gan](https://github.com/God-Gan) -- [Bughue](https://github.com/Bughue) -- [funky-eyes](https://github.com/funky-eyes) -- [tanyaofei](https://github.com/tanyaofei) -- [traitsisgiorgos](https://github.com/traitsisgiorgos) -- [wanghongzhou](https://github.com/wanghongzhou) -- [ggbocoder](https://github.com/ggbocoder) -- [azatyamanaev](https://github.com/azatyamanaev) -- [xjlgod](https://github.com/xjlgod) -- [xingfudeshi](https://github.com/xingfudeshi) -- [wuwen5](https://github.com/wuwen5) -- [jsbxyyx](https://github.com/jsbxyyx) -- [iAmClever](https://github.com/iAmClever) -- [GoodBoyCoder](https://github.com/GoodBoyCoder) -- [liuqiufeng](https://github.com/liuqiufeng) -- [caohdgege](https://github.com/caohdgege) -- [TakeActionNow2019](https://github.com/TakeActionNow2019) -- [imashimaro](https://github.com/hmj776521114) -- [lyl2008dsg](https://github.com/lyl2008dsg) -- [lightClouds917](https://github.com/lightClouds917) -- [l81893521](https://github.com/l81893521) -- [laywin](https://github.com/laywin) -- [xiaoxiangyeyu0](https://github.com/xiaoxiangyeyu0) -- [LegGasai](https://github.com/LegGasai) -- [yangli-stu](https://github.com/yangli-stu) -- [heliang666s](https://github.com/heliang666s) diff --git a/config/seata-config-nacos/src/test/java/io/seata/config/extend/TestConfigFromExtendSPI.java b/config/seata-config-nacos/src/test/java/io/seata/config/extend/TestConfigFromExtendSPI.java index 80bde364814..55e76441861 100644 --- a/config/seata-config-nacos/src/test/java/io/seata/config/extend/TestConfigFromExtendSPI.java +++ b/config/seata-config-nacos/src/test/java/io/seata/config/extend/TestConfigFromExtendSPI.java @@ -37,6 +37,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; public class TestConfigFromExtendSPI { @@ -59,6 +61,7 @@ public static void setup() throws NacosException { } @Test + @EnabledOnOs(OS.LINUX) public void testGetConfigProperties() throws Exception { Assertions.assertNotNull(configService); Configuration configuration = ConfigurationFactory.getInstance(); diff --git a/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosMockTest.java b/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosMockTest.java index 5c3a9d5402f..f246ba33ea8 100644 --- a/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosMockTest.java +++ b/config/seata-config-nacos/src/test/java/org/apache/seata/config/nacos/NacosMockTest.java @@ -37,6 +37,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; public class NacosMockTest { private static ConfigService configService; @@ -64,6 +67,7 @@ public static void setup() throws NacosException { } @Test + @EnabledOnOs(OS.LINUX) public void getInstance() { Assertions.assertNotNull(configService); Assertions.assertNotNull(NacosConfiguration.getInstance()); @@ -71,6 +75,7 @@ public void getInstance() { } @Test + @EnabledOnOs(OS.LINUX) public void getConfig() { Configuration configuration = ConfigurationFactory.getInstance(); String configStrValue = configuration.getConfig(SUB_NACOS_DATAID); @@ -135,6 +140,7 @@ public void getConfig() { } @Test + @EnabledOnOs(OS.LINUX) public void putConfigIfAbsent() { Configuration configuration = ConfigurationFactory.getInstance(); Assertions.assertThrows(UndeclaredThrowableException.class, () -> { @@ -143,6 +149,7 @@ public void putConfigIfAbsent() { } @Test + @EnabledOnOs(OS.LINUX) public void removeConfig() { Configuration configuration = ConfigurationFactory.getInstance(); boolean removed = configuration.removeConfig(NACOS_DATAID); @@ -150,6 +157,7 @@ public void removeConfig() { } @Test + @EnabledOnOs(OS.LINUX) public void putConfig() { Configuration configuration = ConfigurationFactory.getInstance(); boolean added = configuration.putConfig(SUB_NACOS_DATAID, "TEST"); @@ -159,6 +167,7 @@ public void putConfig() { } @Test + @EnabledOnOs(OS.LINUX) public void testConfigListener() throws NacosException, InterruptedException { Configuration configuration = ConfigurationFactory.getInstance(); configuration.putConfig(NACOS_DATAID, "KEY=TEST"); diff --git a/console/pom.xml b/console/pom.xml index 23610df3a07..16fba6a55ee 100644 --- a/console/pom.xml +++ b/console/pom.xml @@ -194,7 +194,6 @@ generate-resources install - https://registry.npmmirror.com/ diff --git a/distribution/LICENSE b/distribution/LICENSE index 9aa892f8160..f4605ffca41 100644 --- a/distribution/LICENSE +++ b/distribution/LICENSE @@ -528,7 +528,7 @@ ISC licenses anymatch 3.1.3 ISC browser-stdout 1.3.1 ISC browserify-sign 4.2.3 ISC - cliui 7.0.4 ISC + cliui 8.0.1 ISC css-color-keywords 1.0.0 ISC electron-to-chromium 1.4.681 ISC fs.realpath 1.0.0 ISC @@ -665,6 +665,9 @@ MIT licenses bignumber.js 9.1.2 MIT bn.js 5.2.1 MIT brace-expansion 1.1.11 MIT + braces 3.0.2 MIT + braces 3.0.3 MIT + braces 2.3.1 MIT brorand 1.1.0 MIT browserify-aes 1.2.0 MIT browserify-rsa 4.1.0 MIT diff --git a/distribution/NOTICE.md b/distribution/NOTICE.md index f333c756545..cc71344309e 100644 --- a/distribution/NOTICE.md +++ b/distribution/NOTICE.md @@ -3,434 +3,413 @@ Please copy database driver dependencies, such as `mysql-connector-java.jar`, to ```aidl . +├── DISCLAIMER ├── LICENSE ├── NOTICE -├── NOTICE.md -├── bin -│   ├── seata-namingserver-setup.sh -│   ├── seata-namingserver.bat -│   ├── seata-namingserver.sh -│   ├── seata-server.bat -│   ├── seata-server.sh -│   └── seata-setup.sh -├── docker -│   ├── namingserver -│   │   └── Dockerfile -│   └── server -│   └── Dockerfile -├── pom.xml -├── release-seata.xml -└── target - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin - │   ├── DISCLAIMER - │   ├── LICENSE - │   ├── NOTICE - │   ├── seata-namingserver - │   │   ├── Dockerfile - │   │   ├── bin - │   │   │   ├── seata-namingserver-setup.sh - │   │   │   ├── seata-namingserver.bat - │   │   │   └── seata-namingserver.sh - │   │   ├── conf - │   │   │   ├── application.yml - │   │   │   ├── logback - │   │   │   │   ├── console-appender.xml - │   │   │   │   └── file-appender.xml - │   │   │   └── logback-spring.xml - │   │   ├── lib - │   │   │   ├── caffeine-2.9.3.jar - │   │   │   ├── checker-qual-3.37.0.jar - │   │   │   ├── commons-codec-1.15.jar - │   │   │   ├── commons-compiler-3.1.10.jar - │   │   │   ├── commons-lang-2.6.jar - │   │   │   ├── commons-lang3-3.12.0.jar - │   │   │   ├── error_prone_annotations-2.21.1.jar - │   │   │   ├── httpclient-4.5.14.jar - │   │   │   ├── httpcore-4.4.16.jar - │   │   │   ├── jackson-annotations-2.13.5.jar - │   │   │   ├── jackson-core-2.13.5.jar - │   │   │   ├── jackson-databind-2.13.5.jar - │   │   │   ├── jackson-datatype-jdk8-2.13.5.jar - │   │   │   ├── jackson-datatype-jsr310-2.13.5.jar - │   │   │   ├── jackson-module-parameter-names-2.13.5.jar - │   │   │   ├── jakarta.annotation-api-1.3.5.jar - │   │   │   ├── janino-3.1.10.jar - │   │   │   ├── jul-to-slf4j-1.7.36.jar - │   │   │   ├── logback-classic-1.2.12.jar - │   │   │   ├── logback-core-1.2.12.jar - │   │   │   ├── netty-all-4.1.101.Final.jar - │   │   │   ├── netty-buffer-4.1.101.Final.jar - │   │   │   ├── netty-codec-4.1.101.Final.jar - │   │   │   ├── netty-codec-dns-4.1.101.Final.jar - │   │   │   ├── netty-codec-haproxy-4.1.101.Final.jar - │   │   │   ├── netty-codec-http-4.1.101.Final.jar - │   │   │   ├── netty-codec-http2-4.1.101.Final.jar - │   │   │   ├── netty-codec-memcache-4.1.101.Final.jar - │   │   │   ├── netty-codec-mqtt-4.1.101.Final.jar - │   │   │   ├── netty-codec-redis-4.1.101.Final.jar - │   │   │   ├── netty-codec-smtp-4.1.101.Final.jar - │   │   │   ├── netty-codec-socks-4.1.101.Final.jar - │   │   │   ├── netty-codec-stomp-4.1.101.Final.jar - │   │   │   ├── netty-codec-xml-4.1.101.Final.jar - │   │   │   ├── netty-common-4.1.101.Final.jar - │   │   │   ├── netty-handler-4.1.101.Final.jar - │   │   │   ├── netty-handler-proxy-4.1.101.Final.jar - │   │   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar - │   │   │   ├── netty-resolver-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar - │   │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar - │   │   │   ├── netty-transport-4.1.101.Final.jar - │   │   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar - │   │   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar - │   │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar - │   │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar - │   │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar - │   │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar - │   │   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar - │   │   │   ├── netty-transport-rxtx-4.1.101.Final.jar - │   │   │   ├── netty-transport-sctp-4.1.101.Final.jar - │   │   │   ├── netty-transport-udt-4.1.101.Final.jar - │   │   │   ├── seata-common-2.2.0-SNAPSHOT.jar - │   │   │   ├── slf4j-api-1.7.36.jar - │   │   │   ├── snakeyaml-2.0.jar - │   │   │   ├── spring-aop-5.3.39.jar - │   │   │   ├── spring-beans-5.3.39.jar - │   │   │   ├── spring-boot-2.7.18.jar - │   │   │   ├── spring-boot-autoconfigure-2.7.18.jar - │   │   │   ├── spring-boot-starter-2.7.18.jar - │   │   │   ├── spring-boot-starter-json-2.7.18.jar - │   │   │   ├── spring-boot-starter-logging-2.7.18.jar - │   │   │   ├── spring-boot-starter-tomcat-2.7.18.jar - │   │   │   ├── spring-boot-starter-web-2.7.18.jar - │   │   │   ├── spring-context-5.3.39.jar - │   │   │   ├── spring-core-5.3.39.jar - │   │   │   ├── spring-expression-5.3.39.jar - │   │   │   ├── spring-jcl-5.3.39.jar - │   │   │   ├── spring-web-5.3.39.jar - │   │   │   ├── spring-webmvc-5.3.39.jar - │   │   │   ├── tomcat-annotations-api-9.0.83.jar - │   │   │   ├── tomcat-embed-core-9.0.90.jar - │   │   │   ├── tomcat-embed-el-9.0.90.jar - │   │   │   └── tomcat-embed-websocket-9.0.90.jar - │   │   └── target - │   │   └── seata-namingserver.jar - │   └── seata-server - │   ├── Dockerfile - │   ├── bin - │   │   ├── seata-server.bat - │   │   ├── seata-server.sh - │   │   └── seata-setup.sh - │   ├── conf - │   │   ├── application.example.yml - │   │   ├── application.raft.example.yml - │   │   ├── application.yml - │   │   ├── logback - │   │   │   ├── console-appender.xml - │   │   │   ├── file-appender.xml - │   │   │   ├── kafka-appender.xml - │   │   │   ├── logstash-appender.xml - │   │   │   └── metric-appender.xml - │   │   └── logback-spring.xml - │   ├── ext - │   │   └── apm-skywalking - │   │   ├── plugins - │   │   │   ├── apm-jdbc-commons-8.6.0.jar - │   │   │   ├── apm-mysql-5.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-6.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-8.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-commons-8.6.0.jar - │   │   │   └── apm-seata-skywalking-plugin-2.2.0-SNAPSHOT.jar - │   │   └── skywalking-agent.jar - │   ├── lib - │   │   ├── DmJdbcDriver18-8.1.2.192.jar - │   │   ├── HikariCP-4.0.3.jar - │   │   ├── animal-sniffer-annotations-1.24.jar - │   │   ├── annotations-4.1.1.4.jar - │   │   ├── ant-1.10.12.jar - │   │   ├── ant-launcher-1.10.12.jar - │   │   ├── aopalliance-1.0.jar - │   │   ├── apollo-client-2.0.1.jar - │   │   ├── apollo-core-2.0.1.jar - │   │   ├── archaius-core-0.7.6.jar - │   │   ├── asm-6.0.jar - │   │   ├── audience-annotations-0.12.0.jar - │   │   ├── bolt-1.6.7.jar - │   │   ├── checker-qual-3.37.0.jar - │   │   ├── commons-codec-1.15.jar - │   │   ├── commons-compiler-3.1.10.jar - │   │   ├── commons-configuration-1.10.jar - │   │   ├── commons-dbcp2-2.9.0.jar - │   │   ├── commons-io-2.8.0.jar - │   │   ├── commons-jxpath-1.3.jar - │   │   ├── commons-lang-2.6.jar - │   │   ├── commons-logging-1.2.jar - │   │   ├── commons-math-2.2.jar - │   │   ├── commons-pool-1.6.jar - │   │   ├── commons-pool2-2.11.1.jar - │   │   ├── compactmap-2.0.jar - │   │   ├── config-1.2.1.jar - │   │   ├── consul-api-1.4.2.jar - │   │   ├── curator-client-5.1.0.jar - │   │   ├── curator-framework-5.1.0.jar - │   │   ├── curator-recipes-5.1.0.jar - │   │   ├── curator-test-5.1.0.jar - │   │   ├── dexx-collections-0.2.jar - │   │   ├── disruptor-3.3.7.jar - │   │   ├── druid-1.2.20.jar - │   │   ├── error_prone_annotations-2.21.1.jar - │   │   ├── eureka-client-1.10.18.jar - │   │   ├── failsafe-2.3.3.jar - │   │   ├── failureaccess-1.0.1.jar - │   │   ├── fastjson-1.2.83.jar - │   │   ├── grpc-api-1.66.0.jar - │   │   ├── grpc-context-1.66.0.jar - │   │   ├── grpc-core-1.66.0.jar - │   │   ├── grpc-grpclb-1.27.1.jar - │   │   ├── grpc-netty-1.66.0.jar - │   │   ├── grpc-protobuf-1.66.0.jar - │   │   ├── grpc-protobuf-lite-1.66.0.jar - │   │   ├── grpc-stub-1.66.0.jar - │   │   ├── grpc-util-1.66.0.jar - │   │   ├── gson-2.9.1.jar - │   │   ├── guava-32.1.3-jre.jar - │   │   ├── guice-5.0.1.jar - │   │   ├── h2-2.1.214.jar - │   │   ├── hamcrest-2.2.jar - │   │   ├── hamcrest-core-2.2.jar - │   │   ├── hessian-4.0.3.jar - │   │   ├── hessian-4.0.63.jar - │   │   ├── httpasyncclient-4.1.5.jar - │   │   ├── httpclient-4.5.14.jar - │   │   ├── httpcore-4.4.16.jar - │   │   ├── httpcore-nio-4.4.16.jar - │   │   ├── j2objc-annotations-2.8.jar - │   │   ├── jackson-annotations-2.13.5.jar - │   │   ├── jackson-core-2.13.5.jar - │   │   ├── jackson-core-asl-1.9.13.jar - │   │   ├── jackson-databind-2.13.5.jar - │   │   ├── jackson-datatype-jdk8-2.13.5.jar - │   │   ├── jackson-datatype-jsr310-2.13.5.jar - │   │   ├── jackson-mapper-asl-1.9.13.jar - │   │   ├── jackson-module-parameter-names-2.13.5.jar - │   │   ├── jakarta.annotation-api-1.3.5.jar - │   │   ├── janino-3.1.10.jar - │   │   ├── javax.inject-1.jar - │   │   ├── javax.servlet-api-4.0.1.jar - │   │   ├── jcommander-1.82.jar - │   │   ├── jctools-core-2.1.1.jar - │   │   ├── jdbc - │   │   │   └── NOTICE.md - │   │   ├── jedis-3.8.0.jar - │   │   ├── jersey-apache-client4-1.19.1.jar - │   │   ├── jersey-client-1.19.1.jar - │   │   ├── jersey-core-1.19.1.jar - │   │   ├── jetcd-common-0.5.0.jar - │   │   ├── jetcd-core-0.5.0.jar - │   │   ├── jetcd-resolver-0.5.0.jar - │   │   ├── jettison-1.5.4.jar - │   │   ├── jjwt-api-0.10.5.jar - │   │   ├── jjwt-impl-0.10.5.jar - │   │   ├── jjwt-jackson-0.10.5.jar - │   │   ├── jna-5.5.0.jar - │   │   ├── joda-time-2.3.jar - │   │   ├── jraft-core-1.3.14.jar - │   │   ├── jsr305-3.0.2.jar - │   │   ├── jsr311-api-1.1.1.jar - │   │   ├── jul-to-slf4j-1.7.36.jar - │   │   ├── junit-4.13.2.jar - │   │   ├── kafka-clients-3.6.1.jar - │   │   ├── kryo-5.4.0.jar - │   │   ├── kryo-serializers-0.45.jar - │   │   ├── logback-classic-1.2.12.jar - │   │   ├── logback-core-1.2.12.jar - │   │   ├── logback-kafka-appender-0.2.0-RC2.jar - │   │   ├── logstash-logback-encoder-6.5.jar - │   │   ├── lz4-java-1.7.1.jar - │   │   ├── metrics-core-4.2.22.jar - │   │   ├── minlog-1.3.1.jar - │   │   ├── mxparser-1.2.2.jar - │   │   ├── nacos-api-1.4.6.jar - │   │   ├── nacos-client-1.4.6.jar - │   │   ├── nacos-common-1.4.6.jar - │   │   ├── netflix-eventbus-0.3.0.jar - │   │   ├── netflix-infix-0.3.0.jar - │   │   ├── netty-all-4.1.101.Final.jar - │   │   ├── netty-buffer-4.1.101.Final.jar - │   │   ├── netty-codec-4.1.101.Final.jar - │   │   ├── netty-codec-dns-4.1.101.Final.jar - │   │   ├── netty-codec-haproxy-4.1.101.Final.jar - │   │   ├── netty-codec-http-4.1.101.Final.jar - │   │   ├── netty-codec-http2-4.1.101.Final.jar - │   │   ├── netty-codec-memcache-4.1.101.Final.jar - │   │   ├── netty-codec-mqtt-4.1.101.Final.jar - │   │   ├── netty-codec-redis-4.1.101.Final.jar - │   │   ├── netty-codec-smtp-4.1.101.Final.jar - │   │   ├── netty-codec-socks-4.1.101.Final.jar - │   │   ├── netty-codec-stomp-4.1.101.Final.jar - │   │   ├── netty-codec-xml-4.1.101.Final.jar - │   │   ├── netty-common-4.1.101.Final.jar - │   │   ├── netty-handler-4.1.101.Final.jar - │   │   ├── netty-handler-proxy-4.1.101.Final.jar - │   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar - │   │   ├── netty-resolver-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar - │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar - │   │   ├── netty-transport-4.1.101.Final.jar - │   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar - │   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar - │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar - │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar - │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar - │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar - │   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar - │   │   ├── netty-transport-rxtx-4.1.101.Final.jar - │   │   ├── netty-transport-sctp-4.1.101.Final.jar - │   │   ├── netty-transport-udt-4.1.101.Final.jar - │   │   ├── objenesis-3.2.jar - │   │   ├── perfmark-api-0.27.0.jar - │   │   ├── postgresql-42.3.8.jar - │   │   ├── proto-google-common-protos-2.41.0.jar - │   │   ├── protobuf-java-3.25.4.jar - │   │   ├── protobuf-java-util-3.11.0.jar - │   │   ├── reflectasm-1.11.9.jar - │   │   ├── registry-client-all-6.3.0.jar - │   │   ├── rocksdbjni-8.8.1.jar - │   │   ├── seata-common-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-bzip2-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-deflater-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-gzip-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-lz4-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-zip-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-zstd-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-apollo-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-consul-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-etcd3-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-nacos-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-spring-cloud-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-zk-2.2.0-SNAPSHOT.jar - │   │   ├── seata-console-2.2.0-SNAPSHOT.jar - │   │   ├── seata-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-consul-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-custom-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-etcd3-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-eureka-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-nacos-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-namingserver-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-redis-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-sofa-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-zk-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-api-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-exporter-prometheus-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-registry-compact-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-hessian-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-kryo-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-protobuf-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-seata-2.2.0-SNAPSHOT.jar - │   │   ├── seata-spring-autoconfigure-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-spring-autoconfigure-server-2.2.0-SNAPSHOT.jar - │   │   ├── servo-core-0.12.21.jar - │   │   ├── simpleclient-0.15.0.jar - │   │   ├── simpleclient_common-0.15.0.jar - │   │   ├── simpleclient_httpserver-0.15.0.jar - │   │   ├── simpleclient_tracer_common-0.15.0.jar - │   │   ├── simpleclient_tracer_otel-0.15.0.jar - │   │   ├── simpleclient_tracer_otel_agent-0.15.0.jar - │   │   ├── slf4j-api-1.7.36.jar - │   │   ├── snakeyaml-2.0.jar - │   │   ├── snappy-java-1.1.10.5.jar - │   │   ├── sofa-common-tools-1.0.12.jar - │   │   ├── spring-aop-5.3.39.jar - │   │   ├── spring-beans-5.3.39.jar - │   │   ├── spring-boot-2.7.18.jar - │   │   ├── spring-boot-autoconfigure-2.7.18.jar - │   │   ├── spring-boot-starter-2.7.18.jar - │   │   ├── spring-boot-starter-json-2.7.18.jar - │   │   ├── spring-boot-starter-logging-2.7.18.jar - │   │   ├── spring-boot-starter-security-2.7.18.jar - │   │   ├── spring-boot-starter-tomcat-2.7.18.jar - │   │   ├── spring-boot-starter-web-2.7.18.jar - │   │   ├── spring-context-5.3.39.jar - │   │   ├── spring-core-5.3.39.jar - │   │   ├── spring-expression-5.3.39.jar - │   │   ├── spring-jcl-5.3.39.jar - │   │   ├── spring-security-config-5.7.11.jar - │   │   ├── spring-security-core-5.7.11.jar - │   │   ├── spring-security-crypto-5.7.11.jar - │   │   ├── spring-security-web-5.7.11.jar - │   │   ├── spring-test-5.3.39.jar - │   │   ├── spring-web-5.3.39.jar - │   │   ├── spring-webmvc-5.3.39.jar - │   │   ├── tomcat-annotations-api-9.0.83.jar - │   │   ├── tomcat-embed-core-9.0.90.jar - │   │   ├── tomcat-embed-el-9.0.90.jar - │   │   ├── tomcat-embed-websocket-9.0.90.jar - │   │   ├── xstream-1.4.20.jar - │   │   ├── zookeeper-3.7.2.jar - │   │   ├── zookeeper-jute-3.7.2.jar - │   │   └── zstd-jni-1.5.0-4.jar - │   ├── script - │   │   ├── config-center - │   │   │   ├── README.md - │   │   │   ├── apollo - │   │   │   │   ├── apollo-config-interactive.sh - │   │   │   │   └── apollo-config.sh - │   │   │   ├── config.txt - │   │   │   ├── consul - │   │   │   │   ├── consul-config-interactive.sh - │   │   │   │   └── consul-config.sh - │   │   │   ├── etcd3 - │   │   │   │   ├── etcd3-config-interactive.sh - │   │   │   │   └── etcd3-config.sh - │   │   │   ├── nacos - │   │   │   │   ├── nacos-config-interactive.py - │   │   │   │   ├── nacos-config-interactive.sh - │   │   │   │   ├── nacos-config.py - │   │   │   │   └── nacos-config.sh - │   │   │   └── zk - │   │   │   ├── zk-config-interactive.sh - │   │   │   └── zk-config.sh - │   │   ├── logstash - │   │   │   └── config - │   │   │   ├── logstash-kafka.conf - │   │   │   └── logstash-logback.conf - │   │   └── server - │   │   ├── db - │   │   │   ├── dm.sql - │   │   │   ├── mysql.sql - │   │   │   ├── oracle.sql - │   │   │   ├── postgresql.sql - │   │   │   └── sqlserver.sql - │   │   ├── docker-compose - │   │   │   └── docker-compose.yaml - │   │   ├── helm - │   │   │   └── seata-server - │   │   │   ├── Chart.yaml - │   │   │   ├── templates - │   │   │   │   ├── NOTES.txt - │   │   │   │   ├── _helpers.tpl - │   │   │   │   ├── deployment.yaml - │   │   │   │   ├── service.yaml - │   │   │   │   └── tests - │   │   │   │   └── test-connection.yaml - │   │   │   └── values.yaml - │   │   └── kubernetes - │   │   └── seata-server.yaml - │   └── target - │   └── seata-server.jar - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin.tar.gz - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin.zip +├── seata-namingserver +│   ├── Dockerfile +│   ├── bin +│   │   ├── seata-namingserver-setup.sh +│   │   ├── seata-namingserver.bat +│   │   └── seata-namingserver.sh +│   ├── conf +│   │   ├── application.yml +│   │   ├── logback +│   │   │   ├── console-appender.xml +│   │   │   └── file-appender.xml +│   │   └── logback-spring.xml +│   ├── lib +│   │   ├── caffeine-2.9.3.jar +│   │   ├── checker-qual-3.37.0.jar +│   │   ├── commons-codec-1.15.jar +│   │   ├── commons-compiler-3.1.10.jar +│   │   ├── commons-lang-2.6.jar +│   │   ├── commons-lang3-3.12.0.jar +│   │   ├── error_prone_annotations-2.21.1.jar +│   │   ├── httpclient-4.5.14.jar +│   │   ├── httpcore-4.4.16.jar +│   │   ├── jackson-annotations-2.13.5.jar +│   │   ├── jackson-core-2.13.5.jar +│   │   ├── jackson-databind-2.13.5.jar +│   │   ├── jackson-datatype-jdk8-2.13.5.jar +│   │   ├── jackson-datatype-jsr310-2.13.5.jar +│   │   ├── jackson-module-parameter-names-2.13.5.jar +│   │   ├── jakarta.annotation-api-1.3.5.jar +│   │   ├── janino-3.1.10.jar +│   │   ├── jul-to-slf4j-1.7.36.jar +│   │   ├── logback-classic-1.2.12.jar +│   │   ├── logback-core-1.2.12.jar +│   │   ├── netty-all-4.1.101.Final.jar +│   │   ├── netty-buffer-4.1.101.Final.jar +│   │   ├── netty-codec-4.1.101.Final.jar +│   │   ├── netty-codec-dns-4.1.101.Final.jar +│   │   ├── netty-codec-haproxy-4.1.101.Final.jar +│   │   ├── netty-codec-http-4.1.101.Final.jar +│   │   ├── netty-codec-http2-4.1.101.Final.jar +│   │   ├── netty-codec-memcache-4.1.101.Final.jar +│   │   ├── netty-codec-mqtt-4.1.101.Final.jar +│   │   ├── netty-codec-redis-4.1.101.Final.jar +│   │   ├── netty-codec-smtp-4.1.101.Final.jar +│   │   ├── netty-codec-socks-4.1.101.Final.jar +│   │   ├── netty-codec-stomp-4.1.101.Final.jar +│   │   ├── netty-codec-xml-4.1.101.Final.jar +│   │   ├── netty-common-4.1.101.Final.jar +│   │   ├── netty-handler-4.1.101.Final.jar +│   │   ├── netty-handler-proxy-4.1.101.Final.jar +│   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar +│   │   ├── netty-resolver-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar +│   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar +│   │   ├── netty-transport-4.1.101.Final.jar +│   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar +│   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar +│   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar +│   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar +│   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar +│   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar +│   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar +│   │   ├── netty-transport-rxtx-4.1.101.Final.jar +│   │   ├── netty-transport-sctp-4.1.101.Final.jar +│   │   ├── netty-transport-udt-4.1.101.Final.jar +│   │   ├── seata-common-2.2.0.jar +│   │   ├── slf4j-api-1.7.36.jar +│   │   ├── snakeyaml-2.0.jar +│   │   ├── spring-aop-5.3.39.jar +│   │   ├── spring-beans-5.3.39.jar +│   │   ├── spring-boot-2.7.18.jar +│   │   ├── spring-boot-autoconfigure-2.7.18.jar +│   │   ├── spring-boot-starter-2.7.18.jar +│   │   ├── spring-boot-starter-json-2.7.18.jar +│   │   ├── spring-boot-starter-logging-2.7.18.jar +│   │   ├── spring-boot-starter-tomcat-2.7.18.jar +│   │   ├── spring-boot-starter-web-2.7.18.jar +│   │   ├── spring-context-5.3.39.jar +│   │   ├── spring-core-5.3.39.jar +│   │   ├── spring-expression-5.3.39.jar +│   │   ├── spring-jcl-5.3.39.jar +│   │   ├── spring-web-5.3.39.jar +│   │   ├── spring-webmvc-5.3.39.jar +│   │   ├── tomcat-annotations-api-9.0.83.jar +│   │   ├── tomcat-embed-core-9.0.90.jar +│   │   ├── tomcat-embed-el-9.0.90.jar +│   │   └── tomcat-embed-websocket-9.0.90.jar +│   └── target +│   └── seata-namingserver.jar +└── seata-server + ├── Dockerfile + ├── bin + │   ├── seata-server.bat + │   ├── seata-server.sh + │   └── seata-setup.sh + ├── conf + │   ├── application.example.yml + │   ├── application.raft.example.yml + │   ├── application.yml + │   ├── logback + │   │   ├── console-appender.xml + │   │   ├── file-appender.xml + │   │   ├── kafka-appender.xml + │   │   ├── logstash-appender.xml + │   │   └── metric-appender.xml + │   └── logback-spring.xml + ├── ext + │   └── apm-skywalking + │   ├── plugins + │   │   ├── apm-jdbc-commons-8.6.0.jar + │   │   ├── apm-mysql-5.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-6.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-8.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-commons-8.6.0.jar + │   │   └── apm-seata-skywalking-plugin-2.2.0.jar + │   └── skywalking-agent.jar + ├── lib + │   ├── DmJdbcDriver18-8.1.2.192.jar + │   ├── HikariCP-4.0.3.jar + │   ├── animal-sniffer-annotations-1.24.jar + │   ├── annotations-4.1.1.4.jar + │   ├── ant-1.10.12.jar + │   ├── ant-launcher-1.10.12.jar + │   ├── aopalliance-1.0.jar + │   ├── apollo-client-2.0.1.jar + │   ├── apollo-core-2.0.1.jar + │   ├── archaius-core-0.7.6.jar + │   ├── asm-6.0.jar + │   ├── audience-annotations-0.12.0.jar + │   ├── bolt-1.6.7.jar + │   ├── checker-qual-3.37.0.jar + │   ├── commons-codec-1.15.jar + │   ├── commons-compiler-3.1.10.jar + │   ├── commons-configuration-1.10.jar + │   ├── commons-dbcp2-2.9.0.jar + │   ├── commons-io-2.8.0.jar + │   ├── commons-jxpath-1.3.jar + │   ├── commons-lang-2.6.jar + │   ├── commons-logging-1.2.jar + │   ├── commons-math-2.2.jar + │   ├── commons-pool-1.6.jar + │   ├── commons-pool2-2.11.1.jar + │   ├── compactmap-2.0.jar + │   ├── config-1.2.1.jar + │   ├── consul-api-1.4.2.jar + │   ├── curator-client-5.1.0.jar + │   ├── curator-framework-5.1.0.jar + │   ├── curator-recipes-5.1.0.jar + │   ├── curator-test-5.1.0.jar + │   ├── dexx-collections-0.2.jar + │   ├── disruptor-3.3.7.jar + │   ├── druid-1.2.20.jar + │   ├── error_prone_annotations-2.21.1.jar + │   ├── eureka-client-1.10.18.jar + │   ├── failsafe-2.3.3.jar + │   ├── failureaccess-1.0.1.jar + │   ├── fastjson-1.2.83.jar + │   ├── grpc-api-1.66.0.jar + │   ├── grpc-context-1.66.0.jar + │   ├── grpc-core-1.66.0.jar + │   ├── grpc-grpclb-1.27.1.jar + │   ├── grpc-netty-1.66.0.jar + │   ├── grpc-protobuf-1.66.0.jar + │   ├── grpc-protobuf-lite-1.66.0.jar + │   ├── grpc-stub-1.66.0.jar + │   ├── grpc-util-1.66.0.jar + │   ├── gson-2.9.1.jar + │   ├── guava-32.1.3-jre.jar + │   ├── guice-5.0.1.jar + │   ├── h2-2.1.214.jar + │   ├── hamcrest-2.2.jar + │   ├── hamcrest-core-2.2.jar + │   ├── hessian-4.0.3.jar + │   ├── hessian-4.0.63.jar + │   ├── httpasyncclient-4.1.5.jar + │   ├── httpclient-4.5.14.jar + │   ├── httpcore-4.4.16.jar + │   ├── httpcore-nio-4.4.16.jar + │   ├── j2objc-annotations-2.8.jar + │   ├── jackson-annotations-2.13.5.jar + │   ├── jackson-core-2.13.5.jar + │   ├── jackson-core-asl-1.9.13.jar + │   ├── jackson-databind-2.13.5.jar + │   ├── jackson-datatype-jdk8-2.13.5.jar + │   ├── jackson-datatype-jsr310-2.13.5.jar + │   ├── jackson-mapper-asl-1.9.13.jar + │   ├── jackson-module-parameter-names-2.13.5.jar + │   ├── jakarta.annotation-api-1.3.5.jar + │   ├── janino-3.1.10.jar + │   ├── javax.inject-1.jar + │   ├── javax.servlet-api-4.0.1.jar + │   ├── jcommander-1.82.jar + │   ├── jctools-core-2.1.1.jar + │   ├── jdbc + │   │   └── NOTICE.md + │   ├── jedis-3.8.0.jar + │   ├── jersey-apache-client4-1.19.1.jar + │   ├── jersey-client-1.19.1.jar + │   ├── jersey-core-1.19.1.jar + │   ├── jetcd-common-0.5.0.jar + │   ├── jetcd-core-0.5.0.jar + │   ├── jetcd-resolver-0.5.0.jar + │   ├── jettison-1.5.4.jar + │   ├── jjwt-api-0.10.5.jar + │   ├── jjwt-impl-0.10.5.jar + │   ├── jjwt-jackson-0.10.5.jar + │   ├── jna-5.5.0.jar + │   ├── joda-time-2.3.jar + │   ├── jraft-core-1.3.14.jar + │   ├── jsr305-3.0.2.jar + │   ├── jsr311-api-1.1.1.jar + │   ├── jul-to-slf4j-1.7.36.jar + │   ├── junit-4.13.2.jar + │   ├── kafka-clients-3.6.1.jar + │   ├── kryo-5.4.0.jar + │   ├── kryo-serializers-0.45.jar + │   ├── logback-classic-1.2.12.jar + │   ├── logback-core-1.2.12.jar + │   ├── logback-kafka-appender-0.2.0-RC2.jar + │   ├── logstash-logback-encoder-6.5.jar + │   ├── lz4-java-1.7.1.jar + │   ├── metrics-core-4.2.22.jar + │   ├── minlog-1.3.1.jar + │   ├── mxparser-1.2.2.jar + │   ├── nacos-api-1.4.6.jar + │   ├── nacos-client-1.4.6.jar + │   ├── nacos-common-1.4.6.jar + │   ├── netflix-eventbus-0.3.0.jar + │   ├── netflix-infix-0.3.0.jar + │   ├── netty-all-4.1.101.Final.jar + │   ├── netty-buffer-4.1.101.Final.jar + │   ├── netty-codec-4.1.101.Final.jar + │   ├── netty-codec-dns-4.1.101.Final.jar + │   ├── netty-codec-haproxy-4.1.101.Final.jar + │   ├── netty-codec-http-4.1.101.Final.jar + │   ├── netty-codec-http2-4.1.101.Final.jar + │   ├── netty-codec-memcache-4.1.101.Final.jar + │   ├── netty-codec-mqtt-4.1.101.Final.jar + │   ├── netty-codec-redis-4.1.101.Final.jar + │   ├── netty-codec-smtp-4.1.101.Final.jar + │   ├── netty-codec-socks-4.1.101.Final.jar + │   ├── netty-codec-stomp-4.1.101.Final.jar + │   ├── netty-codec-xml-4.1.101.Final.jar + │   ├── netty-common-4.1.101.Final.jar + │   ├── netty-handler-4.1.101.Final.jar + │   ├── netty-handler-proxy-4.1.101.Final.jar + │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar + │   ├── netty-resolver-4.1.101.Final.jar + │   ├── netty-resolver-dns-4.1.101.Final.jar + │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar + │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar + │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar + │   ├── netty-transport-4.1.101.Final.jar + │   ├── netty-transport-classes-epoll-4.1.101.Final.jar + │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar + │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar + │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar + │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar + │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar + │   ├── netty-transport-native-unix-common-4.1.101.Final.jar + │   ├── netty-transport-rxtx-4.1.101.Final.jar + │   ├── netty-transport-sctp-4.1.101.Final.jar + │   ├── netty-transport-udt-4.1.101.Final.jar + │   ├── objenesis-3.2.jar + │   ├── perfmark-api-0.27.0.jar + │   ├── postgresql-42.3.8.jar + │   ├── proto-google-common-protos-2.41.0.jar + │   ├── protobuf-java-3.25.4.jar + │   ├── protobuf-java-util-3.11.0.jar + │   ├── reflectasm-1.11.9.jar + │   ├── registry-client-all-6.3.0.jar + │   ├── rocksdbjni-8.8.1.jar + │   ├── seata-common-2.2.0.jar + │   ├── seata-compressor-all-2.2.0.jar + │   ├── seata-compressor-bzip2-2.2.0.jar + │   ├── seata-compressor-deflater-2.2.0.jar + │   ├── seata-compressor-gzip-2.2.0.jar + │   ├── seata-compressor-lz4-2.2.0.jar + │   ├── seata-compressor-zip-2.2.0.jar + │   ├── seata-compressor-zstd-2.2.0.jar + │   ├── seata-config-all-2.2.0.jar + │   ├── seata-config-apollo-2.2.0.jar + │   ├── seata-config-consul-2.2.0.jar + │   ├── seata-config-core-2.2.0.jar + │   ├── seata-config-etcd3-2.2.0.jar + │   ├── seata-config-nacos-2.2.0.jar + │   ├── seata-config-spring-cloud-2.2.0.jar + │   ├── seata-config-zk-2.2.0.jar + │   ├── seata-console-2.2.0.jar + │   ├── seata-core-2.2.0.jar + │   ├── seata-discovery-all-2.2.0.jar + │   ├── seata-discovery-consul-2.2.0.jar + │   ├── seata-discovery-core-2.2.0.jar + │   ├── seata-discovery-custom-2.2.0.jar + │   ├── seata-discovery-etcd3-2.2.0.jar + │   ├── seata-discovery-eureka-2.2.0.jar + │   ├── seata-discovery-nacos-2.2.0.jar + │   ├── seata-discovery-namingserver-2.2.0.jar + │   ├── seata-discovery-redis-2.2.0.jar + │   ├── seata-discovery-sofa-2.2.0.jar + │   ├── seata-discovery-zk-2.2.0.jar + │   ├── seata-metrics-all-2.2.0.jar + │   ├── seata-metrics-api-2.2.0.jar + │   ├── seata-metrics-core-2.2.0.jar + │   ├── seata-metrics-exporter-prometheus-2.2.0.jar + │   ├── seata-metrics-registry-compact-2.2.0.jar + │   ├── seata-serializer-all-2.2.0.jar + │   ├── seata-serializer-hessian-2.2.0.jar + │   ├── seata-serializer-kryo-2.2.0.jar + │   ├── seata-serializer-protobuf-2.2.0.jar + │   ├── seata-serializer-seata-2.2.0.jar + │   ├── seata-spring-autoconfigure-core-2.2.0.jar + │   ├── seata-spring-autoconfigure-server-2.2.0.jar + │   ├── servo-core-0.12.21.jar + │   ├── simpleclient-0.15.0.jar + │   ├── simpleclient_common-0.15.0.jar + │   ├── simpleclient_httpserver-0.15.0.jar + │   ├── simpleclient_tracer_common-0.15.0.jar + │   ├── simpleclient_tracer_otel-0.15.0.jar + │   ├── simpleclient_tracer_otel_agent-0.15.0.jar + │   ├── slf4j-api-1.7.36.jar + │   ├── snakeyaml-2.0.jar + │   ├── snappy-java-1.1.10.5.jar + │   ├── sofa-common-tools-1.0.12.jar + │   ├── spring-aop-5.3.39.jar + │   ├── spring-beans-5.3.39.jar + │   ├── spring-boot-2.7.18.jar + │   ├── spring-boot-autoconfigure-2.7.18.jar + │   ├── spring-boot-starter-2.7.18.jar + │   ├── spring-boot-starter-json-2.7.18.jar + │   ├── spring-boot-starter-logging-2.7.18.jar + │   ├── spring-boot-starter-security-2.7.18.jar + │   ├── spring-boot-starter-tomcat-2.7.18.jar + │   ├── spring-boot-starter-web-2.7.18.jar + │   ├── spring-context-5.3.39.jar + │   ├── spring-core-5.3.39.jar + │   ├── spring-expression-5.3.39.jar + │   ├── spring-jcl-5.3.39.jar + │   ├── spring-security-config-5.7.11.jar + │   ├── spring-security-core-5.7.11.jar + │   ├── spring-security-crypto-5.7.11.jar + │   ├── spring-security-web-5.7.11.jar + │   ├── spring-test-5.3.39.jar + │   ├── spring-web-5.3.39.jar + │   ├── spring-webmvc-5.3.39.jar + │   ├── tomcat-annotations-api-9.0.83.jar + │   ├── tomcat-embed-core-9.0.90.jar + │   ├── tomcat-embed-el-9.0.90.jar + │   ├── tomcat-embed-websocket-9.0.90.jar + │   ├── xstream-1.4.20.jar + │   ├── zookeeper-3.7.2.jar + │   ├── zookeeper-jute-3.7.2.jar + │   └── zstd-jni-1.5.0-4.jar + ├── script + │   ├── config-center + │   │   ├── README.md + │   │   ├── apollo + │   │   │   ├── apollo-config-interactive.sh + │   │   │   └── apollo-config.sh + │   │   ├── config.txt + │   │   ├── consul + │   │   │   ├── consul-config-interactive.sh + │   │   │   └── consul-config.sh + │   │   ├── etcd3 + │   │   │   ├── etcd3-config-interactive.sh + │   │   │   └── etcd3-config.sh + │   │   ├── nacos + │   │   │   ├── nacos-config-interactive.py + │   │   │   ├── nacos-config-interactive.sh + │   │   │   ├── nacos-config.py + │   │   │   └── nacos-config.sh + │   │   └── zk + │   │   ├── zk-config-interactive.sh + │   │   └── zk-config.sh + │   ├── logstash + │   │   └── config + │   │   ├── logstash-kafka.conf + │   │   └── logstash-logback.conf + │   └── server + │   ├── db + │   │   ├── dm.sql + │   │   ├── mysql.sql + │   │   ├── oracle.sql + │   │   ├── postgresql.sql + │   │   └── sqlserver.sql + │   ├── docker-compose + │   │   └── docker-compose.yaml + │   ├── helm + │   │   └── seata-server + │   │   ├── Chart.yaml + │   │   ├── templates + │   │   │   ├── NOTES.txt + │   │   │   ├── _helpers.tpl + │   │   │   ├── deployment.yaml + │   │   │   ├── service.yaml + │   │   │   └── tests + │   │   │   └── test-connection.yaml + │   │   └── values.yaml + │   └── kubernetes + │   └── seata-server.yaml + └── target + └── seata-server.jar ``` @@ -440,434 +419,413 @@ Please copy database driver dependencies, such as `mysql-connector-java.jar`, to 请将数据库driver相关依赖例如:`mysql-connector-java.jar`,拷贝到此目录下。目录结构示例如下: ```aidl . +├── DISCLAIMER ├── LICENSE ├── NOTICE -├── NOTICE.md -├── bin -│   ├── seata-namingserver-setup.sh -│   ├── seata-namingserver.bat -│   ├── seata-namingserver.sh -│   ├── seata-server.bat -│   ├── seata-server.sh -│   └── seata-setup.sh -├── docker -│   ├── namingserver -│   │   └── Dockerfile -│   └── server -│   └── Dockerfile -├── pom.xml -├── release-seata.xml -└── target - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin - │   ├── DISCLAIMER - │   ├── LICENSE - │   ├── NOTICE - │   ├── seata-namingserver - │   │   ├── Dockerfile - │   │   ├── bin - │   │   │   ├── seata-namingserver-setup.sh - │   │   │   ├── seata-namingserver.bat - │   │   │   └── seata-namingserver.sh - │   │   ├── conf - │   │   │   ├── application.yml - │   │   │   ├── logback - │   │   │   │   ├── console-appender.xml - │   │   │   │   └── file-appender.xml - │   │   │   └── logback-spring.xml - │   │   ├── lib - │   │   │   ├── caffeine-2.9.3.jar - │   │   │   ├── checker-qual-3.37.0.jar - │   │   │   ├── commons-codec-1.15.jar - │   │   │   ├── commons-compiler-3.1.10.jar - │   │   │   ├── commons-lang-2.6.jar - │   │   │   ├── commons-lang3-3.12.0.jar - │   │   │   ├── error_prone_annotations-2.21.1.jar - │   │   │   ├── httpclient-4.5.14.jar - │   │   │   ├── httpcore-4.4.16.jar - │   │   │   ├── jackson-annotations-2.13.5.jar - │   │   │   ├── jackson-core-2.13.5.jar - │   │   │   ├── jackson-databind-2.13.5.jar - │   │   │   ├── jackson-datatype-jdk8-2.13.5.jar - │   │   │   ├── jackson-datatype-jsr310-2.13.5.jar - │   │   │   ├── jackson-module-parameter-names-2.13.5.jar - │   │   │   ├── jakarta.annotation-api-1.3.5.jar - │   │   │   ├── janino-3.1.10.jar - │   │   │   ├── jul-to-slf4j-1.7.36.jar - │   │   │   ├── logback-classic-1.2.12.jar - │   │   │   ├── logback-core-1.2.12.jar - │   │   │   ├── netty-all-4.1.101.Final.jar - │   │   │   ├── netty-buffer-4.1.101.Final.jar - │   │   │   ├── netty-codec-4.1.101.Final.jar - │   │   │   ├── netty-codec-dns-4.1.101.Final.jar - │   │   │   ├── netty-codec-haproxy-4.1.101.Final.jar - │   │   │   ├── netty-codec-http-4.1.101.Final.jar - │   │   │   ├── netty-codec-http2-4.1.101.Final.jar - │   │   │   ├── netty-codec-memcache-4.1.101.Final.jar - │   │   │   ├── netty-codec-mqtt-4.1.101.Final.jar - │   │   │   ├── netty-codec-redis-4.1.101.Final.jar - │   │   │   ├── netty-codec-smtp-4.1.101.Final.jar - │   │   │   ├── netty-codec-socks-4.1.101.Final.jar - │   │   │   ├── netty-codec-stomp-4.1.101.Final.jar - │   │   │   ├── netty-codec-xml-4.1.101.Final.jar - │   │   │   ├── netty-common-4.1.101.Final.jar - │   │   │   ├── netty-handler-4.1.101.Final.jar - │   │   │   ├── netty-handler-proxy-4.1.101.Final.jar - │   │   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar - │   │   │   ├── netty-resolver-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar - │   │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar - │   │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar - │   │   │   ├── netty-transport-4.1.101.Final.jar - │   │   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar - │   │   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar - │   │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar - │   │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar - │   │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar - │   │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar - │   │   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar - │   │   │   ├── netty-transport-rxtx-4.1.101.Final.jar - │   │   │   ├── netty-transport-sctp-4.1.101.Final.jar - │   │   │   ├── netty-transport-udt-4.1.101.Final.jar - │   │   │   ├── seata-common-2.2.0-SNAPSHOT.jar - │   │   │   ├── slf4j-api-1.7.36.jar - │   │   │   ├── snakeyaml-2.0.jar - │   │   │   ├── spring-aop-5.3.39.jar - │   │   │   ├── spring-beans-5.3.39.jar - │   │   │   ├── spring-boot-2.7.18.jar - │   │   │   ├── spring-boot-autoconfigure-2.7.18.jar - │   │   │   ├── spring-boot-starter-2.7.18.jar - │   │   │   ├── spring-boot-starter-json-2.7.18.jar - │   │   │   ├── spring-boot-starter-logging-2.7.18.jar - │   │   │   ├── spring-boot-starter-tomcat-2.7.18.jar - │   │   │   ├── spring-boot-starter-web-2.7.18.jar - │   │   │   ├── spring-context-5.3.39.jar - │   │   │   ├── spring-core-5.3.39.jar - │   │   │   ├── spring-expression-5.3.39.jar - │   │   │   ├── spring-jcl-5.3.39.jar - │   │   │   ├── spring-web-5.3.39.jar - │   │   │   ├── spring-webmvc-5.3.39.jar - │   │   │   ├── tomcat-annotations-api-9.0.83.jar - │   │   │   ├── tomcat-embed-core-9.0.90.jar - │   │   │   ├── tomcat-embed-el-9.0.90.jar - │   │   │   └── tomcat-embed-websocket-9.0.90.jar - │   │   └── target - │   │   └── seata-namingserver.jar - │   └── seata-server - │   ├── Dockerfile - │   ├── bin - │   │   ├── seata-server.bat - │   │   ├── seata-server.sh - │   │   └── seata-setup.sh - │   ├── conf - │   │   ├── application.example.yml - │   │   ├── application.raft.example.yml - │   │   ├── application.yml - │   │   ├── logback - │   │   │   ├── console-appender.xml - │   │   │   ├── file-appender.xml - │   │   │   ├── kafka-appender.xml - │   │   │   ├── logstash-appender.xml - │   │   │   └── metric-appender.xml - │   │   └── logback-spring.xml - │   ├── ext - │   │   └── apm-skywalking - │   │   ├── plugins - │   │   │   ├── apm-jdbc-commons-8.6.0.jar - │   │   │   ├── apm-mysql-5.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-6.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-8.x-plugin-8.6.0.jar - │   │   │   ├── apm-mysql-commons-8.6.0.jar - │   │   │   └── apm-seata-skywalking-plugin-2.2.0-SNAPSHOT.jar - │   │   └── skywalking-agent.jar - │   ├── lib - │   │   ├── DmJdbcDriver18-8.1.2.192.jar - │   │   ├── HikariCP-4.0.3.jar - │   │   ├── animal-sniffer-annotations-1.24.jar - │   │   ├── annotations-4.1.1.4.jar - │   │   ├── ant-1.10.12.jar - │   │   ├── ant-launcher-1.10.12.jar - │   │   ├── aopalliance-1.0.jar - │   │   ├── apollo-client-2.0.1.jar - │   │   ├── apollo-core-2.0.1.jar - │   │   ├── archaius-core-0.7.6.jar - │   │   ├── asm-6.0.jar - │   │   ├── audience-annotations-0.12.0.jar - │   │   ├── bolt-1.6.7.jar - │   │   ├── checker-qual-3.37.0.jar - │   │   ├── commons-codec-1.15.jar - │   │   ├── commons-compiler-3.1.10.jar - │   │   ├── commons-configuration-1.10.jar - │   │   ├── commons-dbcp2-2.9.0.jar - │   │   ├── commons-io-2.8.0.jar - │   │   ├── commons-jxpath-1.3.jar - │   │   ├── commons-lang-2.6.jar - │   │   ├── commons-logging-1.2.jar - │   │   ├── commons-math-2.2.jar - │   │   ├── commons-pool-1.6.jar - │   │   ├── commons-pool2-2.11.1.jar - │   │   ├── compactmap-2.0.jar - │   │   ├── config-1.2.1.jar - │   │   ├── consul-api-1.4.2.jar - │   │   ├── curator-client-5.1.0.jar - │   │   ├── curator-framework-5.1.0.jar - │   │   ├── curator-recipes-5.1.0.jar - │   │   ├── curator-test-5.1.0.jar - │   │   ├── dexx-collections-0.2.jar - │   │   ├── disruptor-3.3.7.jar - │   │   ├── druid-1.2.20.jar - │   │   ├── error_prone_annotations-2.21.1.jar - │   │   ├── eureka-client-1.10.18.jar - │   │   ├── failsafe-2.3.3.jar - │   │   ├── failureaccess-1.0.1.jar - │   │   ├── fastjson-1.2.83.jar - │   │   ├── grpc-api-1.66.0.jar - │   │   ├── grpc-context-1.66.0.jar - │   │   ├── grpc-core-1.66.0.jar - │   │   ├── grpc-grpclb-1.27.1.jar - │   │   ├── grpc-netty-1.66.0.jar - │   │   ├── grpc-protobuf-1.66.0.jar - │   │   ├── grpc-protobuf-lite-1.66.0.jar - │   │   ├── grpc-stub-1.66.0.jar - │   │   ├── grpc-util-1.66.0.jar - │   │   ├── gson-2.9.1.jar - │   │   ├── guava-32.1.3-jre.jar - │   │   ├── guice-5.0.1.jar - │   │   ├── h2-2.1.214.jar - │   │   ├── hamcrest-2.2.jar - │   │   ├── hamcrest-core-2.2.jar - │   │   ├── hessian-4.0.3.jar - │   │   ├── hessian-4.0.63.jar - │   │   ├── httpasyncclient-4.1.5.jar - │   │   ├── httpclient-4.5.14.jar - │   │   ├── httpcore-4.4.16.jar - │   │   ├── httpcore-nio-4.4.16.jar - │   │   ├── j2objc-annotations-2.8.jar - │   │   ├── jackson-annotations-2.13.5.jar - │   │   ├── jackson-core-2.13.5.jar - │   │   ├── jackson-core-asl-1.9.13.jar - │   │   ├── jackson-databind-2.13.5.jar - │   │   ├── jackson-datatype-jdk8-2.13.5.jar - │   │   ├── jackson-datatype-jsr310-2.13.5.jar - │   │   ├── jackson-mapper-asl-1.9.13.jar - │   │   ├── jackson-module-parameter-names-2.13.5.jar - │   │   ├── jakarta.annotation-api-1.3.5.jar - │   │   ├── janino-3.1.10.jar - │   │   ├── javax.inject-1.jar - │   │   ├── javax.servlet-api-4.0.1.jar - │   │   ├── jcommander-1.82.jar - │   │   ├── jctools-core-2.1.1.jar - │   │   ├── jdbc - │   │   │   └── mysql-connector-java-8.0.28.jar - │   │   │   └── NOTICE.md - │   │   ├── jedis-3.8.0.jar - │   │   ├── jersey-apache-client4-1.19.1.jar - │   │   ├── jersey-client-1.19.1.jar - │   │   ├── jersey-core-1.19.1.jar - │   │   ├── jetcd-common-0.5.0.jar - │   │   ├── jetcd-core-0.5.0.jar - │   │   ├── jetcd-resolver-0.5.0.jar - │   │   ├── jettison-1.5.4.jar - │   │   ├── jjwt-api-0.10.5.jar - │   │   ├── jjwt-impl-0.10.5.jar - │   │   ├── jjwt-jackson-0.10.5.jar - │   │   ├── jna-5.5.0.jar - │   │   ├── joda-time-2.3.jar - │   │   ├── jraft-core-1.3.14.jar - │   │   ├── jsr305-3.0.2.jar - │   │   ├── jsr311-api-1.1.1.jar - │   │   ├── jul-to-slf4j-1.7.36.jar - │   │   ├── junit-4.13.2.jar - │   │   ├── kafka-clients-3.6.1.jar - │   │   ├── kryo-5.4.0.jar - │   │   ├── kryo-serializers-0.45.jar - │   │   ├── logback-classic-1.2.12.jar - │   │   ├── logback-core-1.2.12.jar - │   │   ├── logback-kafka-appender-0.2.0-RC2.jar - │   │   ├── logstash-logback-encoder-6.5.jar - │   │   ├── lz4-java-1.7.1.jar - │   │   ├── metrics-core-4.2.22.jar - │   │   ├── minlog-1.3.1.jar - │   │   ├── mxparser-1.2.2.jar - │   │   ├── nacos-api-1.4.6.jar - │   │   ├── nacos-client-1.4.6.jar - │   │   ├── nacos-common-1.4.6.jar - │   │   ├── netflix-eventbus-0.3.0.jar - │   │   ├── netflix-infix-0.3.0.jar - │   │   ├── netty-all-4.1.101.Final.jar - │   │   ├── netty-buffer-4.1.101.Final.jar - │   │   ├── netty-codec-4.1.101.Final.jar - │   │   ├── netty-codec-dns-4.1.101.Final.jar - │   │   ├── netty-codec-haproxy-4.1.101.Final.jar - │   │   ├── netty-codec-http-4.1.101.Final.jar - │   │   ├── netty-codec-http2-4.1.101.Final.jar - │   │   ├── netty-codec-memcache-4.1.101.Final.jar - │   │   ├── netty-codec-mqtt-4.1.101.Final.jar - │   │   ├── netty-codec-redis-4.1.101.Final.jar - │   │   ├── netty-codec-smtp-4.1.101.Final.jar - │   │   ├── netty-codec-socks-4.1.101.Final.jar - │   │   ├── netty-codec-stomp-4.1.101.Final.jar - │   │   ├── netty-codec-xml-4.1.101.Final.jar - │   │   ├── netty-common-4.1.101.Final.jar - │   │   ├── netty-handler-4.1.101.Final.jar - │   │   ├── netty-handler-proxy-4.1.101.Final.jar - │   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar - │   │   ├── netty-resolver-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar - │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar - │   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar - │   │   ├── netty-transport-4.1.101.Final.jar - │   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar - │   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar - │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar - │   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar - │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar - │   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar - │   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar - │   │   ├── netty-transport-rxtx-4.1.101.Final.jar - │   │   ├── netty-transport-sctp-4.1.101.Final.jar - │   │   ├── netty-transport-udt-4.1.101.Final.jar - │   │   ├── objenesis-3.2.jar - │   │   ├── perfmark-api-0.27.0.jar - │   │   ├── postgresql-42.3.8.jar - │   │   ├── proto-google-common-protos-2.41.0.jar - │   │   ├── protobuf-java-3.25.4.jar - │   │   ├── protobuf-java-util-3.11.0.jar - │   │   ├── reflectasm-1.11.9.jar - │   │   ├── registry-client-all-6.3.0.jar - │   │   ├── rocksdbjni-8.8.1.jar - │   │   ├── seata-common-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-bzip2-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-deflater-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-gzip-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-lz4-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-zip-2.2.0-SNAPSHOT.jar - │   │   ├── seata-compressor-zstd-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-apollo-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-consul-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-etcd3-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-nacos-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-spring-cloud-2.2.0-SNAPSHOT.jar - │   │   ├── seata-config-zk-2.2.0-SNAPSHOT.jar - │   │   ├── seata-console-2.2.0-SNAPSHOT.jar - │   │   ├── seata-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-consul-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-custom-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-etcd3-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-eureka-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-nacos-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-namingserver-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-redis-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-sofa-2.2.0-SNAPSHOT.jar - │   │   ├── seata-discovery-zk-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-api-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-exporter-prometheus-2.2.0-SNAPSHOT.jar - │   │   ├── seata-metrics-registry-compact-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-all-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-hessian-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-kryo-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-protobuf-2.2.0-SNAPSHOT.jar - │   │   ├── seata-serializer-seata-2.2.0-SNAPSHOT.jar - │   │   ├── seata-spring-autoconfigure-core-2.2.0-SNAPSHOT.jar - │   │   ├── seata-spring-autoconfigure-server-2.2.0-SNAPSHOT.jar - │   │   ├── servo-core-0.12.21.jar - │   │   ├── simpleclient-0.15.0.jar - │   │   ├── simpleclient_common-0.15.0.jar - │   │   ├── simpleclient_httpserver-0.15.0.jar - │   │   ├── simpleclient_tracer_common-0.15.0.jar - │   │   ├── simpleclient_tracer_otel-0.15.0.jar - │   │   ├── simpleclient_tracer_otel_agent-0.15.0.jar - │   │   ├── slf4j-api-1.7.36.jar - │   │   ├── snakeyaml-2.0.jar - │   │   ├── snappy-java-1.1.10.5.jar - │   │   ├── sofa-common-tools-1.0.12.jar - │   │   ├── spring-aop-5.3.39.jar - │   │   ├── spring-beans-5.3.39.jar - │   │   ├── spring-boot-2.7.18.jar - │   │   ├── spring-boot-autoconfigure-2.7.18.jar - │   │   ├── spring-boot-starter-2.7.18.jar - │   │   ├── spring-boot-starter-json-2.7.18.jar - │   │   ├── spring-boot-starter-logging-2.7.18.jar - │   │   ├── spring-boot-starter-security-2.7.18.jar - │   │   ├── spring-boot-starter-tomcat-2.7.18.jar - │   │   ├── spring-boot-starter-web-2.7.18.jar - │   │   ├── spring-context-5.3.39.jar - │   │   ├── spring-core-5.3.39.jar - │   │   ├── spring-expression-5.3.39.jar - │   │   ├── spring-jcl-5.3.39.jar - │   │   ├── spring-security-config-5.7.11.jar - │   │   ├── spring-security-core-5.7.11.jar - │   │   ├── spring-security-crypto-5.7.11.jar - │   │   ├── spring-security-web-5.7.11.jar - │   │   ├── spring-test-5.3.39.jar - │   │   ├── spring-web-5.3.39.jar - │   │   ├── spring-webmvc-5.3.39.jar - │   │   ├── tomcat-annotations-api-9.0.83.jar - │   │   ├── tomcat-embed-core-9.0.90.jar - │   │   ├── tomcat-embed-el-9.0.90.jar - │   │   ├── tomcat-embed-websocket-9.0.90.jar - │   │   ├── xstream-1.4.20.jar - │   │   ├── zookeeper-3.7.2.jar - │   │   ├── zookeeper-jute-3.7.2.jar - │   │   └── zstd-jni-1.5.0-4.jar - │   ├── script - │   │   ├── config-center - │   │   │   ├── README.md - │   │   │   ├── apollo - │   │   │   │   ├── apollo-config-interactive.sh - │   │   │   │   └── apollo-config.sh - │   │   │   ├── config.txt - │   │   │   ├── consul - │   │   │   │   ├── consul-config-interactive.sh - │   │   │   │   └── consul-config.sh - │   │   │   ├── etcd3 - │   │   │   │   ├── etcd3-config-interactive.sh - │   │   │   │   └── etcd3-config.sh - │   │   │   ├── nacos - │   │   │   │   ├── nacos-config-interactive.py - │   │   │   │   ├── nacos-config-interactive.sh - │   │   │   │   ├── nacos-config.py - │   │   │   │   └── nacos-config.sh - │   │   │   └── zk - │   │   │   ├── zk-config-interactive.sh - │   │   │   └── zk-config.sh - │   │   ├── logstash - │   │   │   └── config - │   │   │   ├── logstash-kafka.conf - │   │   │   └── logstash-logback.conf - │   │   └── server - │   │   ├── db - │   │   │   ├── dm.sql - │   │   │   ├── mysql.sql - │   │   │   ├── oracle.sql - │   │   │   ├── postgresql.sql - │   │   │   └── sqlserver.sql - │   │   ├── docker-compose - │   │   │   └── docker-compose.yaml - │   │   ├── helm - │   │   │   └── seata-server - │   │   │   ├── Chart.yaml - │   │   │   ├── templates - │   │   │   │   ├── NOTES.txt - │   │   │   │   ├── _helpers.tpl - │   │   │   │   ├── deployment.yaml - │   │   │   │   ├── service.yaml - │   │   │   │   └── tests - │   │   │   │   └── test-connection.yaml - │   │   │   └── values.yaml - │   │   └── kubernetes - │   │   └── seata-server.yaml - │   └── target - │   └── seata-server.jar - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin.tar.gz - ├── apache-seata-2.2.0-SNAPSHOT-incubating-bin.zip - +├── seata-namingserver +│   ├── Dockerfile +│   ├── bin +│   │   ├── seata-namingserver-setup.sh +│   │   ├── seata-namingserver.bat +│   │   └── seata-namingserver.sh +│   ├── conf +│   │   ├── application.yml +│   │   ├── logback +│   │   │   ├── console-appender.xml +│   │   │   └── file-appender.xml +│   │   └── logback-spring.xml +│   ├── lib +│   │   ├── caffeine-2.9.3.jar +│   │   ├── checker-qual-3.37.0.jar +│   │   ├── commons-codec-1.15.jar +│   │   ├── commons-compiler-3.1.10.jar +│   │   ├── commons-lang-2.6.jar +│   │   ├── commons-lang3-3.12.0.jar +│   │   ├── error_prone_annotations-2.21.1.jar +│   │   ├── httpclient-4.5.14.jar +│   │   ├── httpcore-4.4.16.jar +│   │   ├── jackson-annotations-2.13.5.jar +│   │   ├── jackson-core-2.13.5.jar +│   │   ├── jackson-databind-2.13.5.jar +│   │   ├── jackson-datatype-jdk8-2.13.5.jar +│   │   ├── jackson-datatype-jsr310-2.13.5.jar +│   │   ├── jackson-module-parameter-names-2.13.5.jar +│   │   ├── jakarta.annotation-api-1.3.5.jar +│   │   ├── janino-3.1.10.jar +│   │   ├── jul-to-slf4j-1.7.36.jar +│   │   ├── logback-classic-1.2.12.jar +│   │   ├── logback-core-1.2.12.jar +│   │   ├── netty-all-4.1.101.Final.jar +│   │   ├── netty-buffer-4.1.101.Final.jar +│   │   ├── netty-codec-4.1.101.Final.jar +│   │   ├── netty-codec-dns-4.1.101.Final.jar +│   │   ├── netty-codec-haproxy-4.1.101.Final.jar +│   │   ├── netty-codec-http-4.1.101.Final.jar +│   │   ├── netty-codec-http2-4.1.101.Final.jar +│   │   ├── netty-codec-memcache-4.1.101.Final.jar +│   │   ├── netty-codec-mqtt-4.1.101.Final.jar +│   │   ├── netty-codec-redis-4.1.101.Final.jar +│   │   ├── netty-codec-smtp-4.1.101.Final.jar +│   │   ├── netty-codec-socks-4.1.101.Final.jar +│   │   ├── netty-codec-stomp-4.1.101.Final.jar +│   │   ├── netty-codec-xml-4.1.101.Final.jar +│   │   ├── netty-common-4.1.101.Final.jar +│   │   ├── netty-handler-4.1.101.Final.jar +│   │   ├── netty-handler-proxy-4.1.101.Final.jar +│   │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar +│   │   ├── netty-resolver-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar +│   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar +│   │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar +│   │   ├── netty-transport-4.1.101.Final.jar +│   │   ├── netty-transport-classes-epoll-4.1.101.Final.jar +│   │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar +│   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar +│   │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar +│   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar +│   │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar +│   │   ├── netty-transport-native-unix-common-4.1.101.Final.jar +│   │   ├── netty-transport-rxtx-4.1.101.Final.jar +│   │   ├── netty-transport-sctp-4.1.101.Final.jar +│   │   ├── netty-transport-udt-4.1.101.Final.jar +│   │   ├── seata-common-2.2.0.jar +│   │   ├── slf4j-api-1.7.36.jar +│   │   ├── snakeyaml-2.0.jar +│   │   ├── spring-aop-5.3.39.jar +│   │   ├── spring-beans-5.3.39.jar +│   │   ├── spring-boot-2.7.18.jar +│   │   ├── spring-boot-autoconfigure-2.7.18.jar +│   │   ├── spring-boot-starter-2.7.18.jar +│   │   ├── spring-boot-starter-json-2.7.18.jar +│   │   ├── spring-boot-starter-logging-2.7.18.jar +│   │   ├── spring-boot-starter-tomcat-2.7.18.jar +│   │   ├── spring-boot-starter-web-2.7.18.jar +│   │   ├── spring-context-5.3.39.jar +│   │   ├── spring-core-5.3.39.jar +│   │   ├── spring-expression-5.3.39.jar +│   │   ├── spring-jcl-5.3.39.jar +│   │   ├── spring-web-5.3.39.jar +│   │   ├── spring-webmvc-5.3.39.jar +│   │   ├── tomcat-annotations-api-9.0.83.jar +│   │   ├── tomcat-embed-core-9.0.90.jar +│   │   ├── tomcat-embed-el-9.0.90.jar +│   │   └── tomcat-embed-websocket-9.0.90.jar +│   └── target +│   └── seata-namingserver.jar +└── seata-server + ├── Dockerfile + ├── bin + │   ├── seata-server.bat + │   ├── seata-server.sh + │   └── seata-setup.sh + ├── conf + │   ├── application.example.yml + │   ├── application.raft.example.yml + │   ├── application.yml + │   ├── logback + │   │   ├── console-appender.xml + │   │   ├── file-appender.xml + │   │   ├── kafka-appender.xml + │   │   ├── logstash-appender.xml + │   │   └── metric-appender.xml + │   └── logback-spring.xml + ├── ext + │   └── apm-skywalking + │   ├── plugins + │   │   ├── apm-jdbc-commons-8.6.0.jar + │   │   ├── apm-mysql-5.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-6.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-8.x-plugin-8.6.0.jar + │   │   ├── apm-mysql-commons-8.6.0.jar + │   │   └── apm-seata-skywalking-plugin-2.2.0.jar + │   └── skywalking-agent.jar + ├── lib + │   ├── DmJdbcDriver18-8.1.2.192.jar + │   ├── HikariCP-4.0.3.jar + │   ├── animal-sniffer-annotations-1.24.jar + │   ├── annotations-4.1.1.4.jar + │   ├── ant-1.10.12.jar + │   ├── ant-launcher-1.10.12.jar + │   ├── aopalliance-1.0.jar + │   ├── apollo-client-2.0.1.jar + │   ├── apollo-core-2.0.1.jar + │   ├── archaius-core-0.7.6.jar + │   ├── asm-6.0.jar + │   ├── audience-annotations-0.12.0.jar + │   ├── bolt-1.6.7.jar + │   ├── checker-qual-3.37.0.jar + │   ├── commons-codec-1.15.jar + │   ├── commons-compiler-3.1.10.jar + │   ├── commons-configuration-1.10.jar + │   ├── commons-dbcp2-2.9.0.jar + │   ├── commons-io-2.8.0.jar + │   ├── commons-jxpath-1.3.jar + │   ├── commons-lang-2.6.jar + │   ├── commons-logging-1.2.jar + │   ├── commons-math-2.2.jar + │   ├── commons-pool-1.6.jar + │   ├── commons-pool2-2.11.1.jar + │   ├── compactmap-2.0.jar + │   ├── config-1.2.1.jar + │   ├── consul-api-1.4.2.jar + │   ├── curator-client-5.1.0.jar + │   ├── curator-framework-5.1.0.jar + │   ├── curator-recipes-5.1.0.jar + │   ├── curator-test-5.1.0.jar + │   ├── dexx-collections-0.2.jar + │   ├── disruptor-3.3.7.jar + │   ├── druid-1.2.20.jar + │   ├── error_prone_annotations-2.21.1.jar + │   ├── eureka-client-1.10.18.jar + │   ├── failsafe-2.3.3.jar + │   ├── failureaccess-1.0.1.jar + │   ├── fastjson-1.2.83.jar + │   ├── grpc-api-1.66.0.jar + │   ├── grpc-context-1.66.0.jar + │   ├── grpc-core-1.66.0.jar + │   ├── grpc-grpclb-1.27.1.jar + │   ├── grpc-netty-1.66.0.jar + │   ├── grpc-protobuf-1.66.0.jar + │   ├── grpc-protobuf-lite-1.66.0.jar + │   ├── grpc-stub-1.66.0.jar + │   ├── grpc-util-1.66.0.jar + │   ├── gson-2.9.1.jar + │   ├── guava-32.1.3-jre.jar + │   ├── guice-5.0.1.jar + │   ├── h2-2.1.214.jar + │   ├── hamcrest-2.2.jar + │   ├── hamcrest-core-2.2.jar + │   ├── hessian-4.0.3.jar + │   ├── hessian-4.0.63.jar + │   ├── httpasyncclient-4.1.5.jar + │   ├── httpclient-4.5.14.jar + │   ├── httpcore-4.4.16.jar + │   ├── httpcore-nio-4.4.16.jar + │   ├── j2objc-annotations-2.8.jar + │   ├── jackson-annotations-2.13.5.jar + │   ├── jackson-core-2.13.5.jar + │   ├── jackson-core-asl-1.9.13.jar + │   ├── jackson-databind-2.13.5.jar + │   ├── jackson-datatype-jdk8-2.13.5.jar + │   ├── jackson-datatype-jsr310-2.13.5.jar + │   ├── jackson-mapper-asl-1.9.13.jar + │   ├── jackson-module-parameter-names-2.13.5.jar + │   ├── jakarta.annotation-api-1.3.5.jar + │   ├── janino-3.1.10.jar + │   ├── javax.inject-1.jar + │   ├── javax.servlet-api-4.0.1.jar + │   ├── jcommander-1.82.jar + │   ├── jctools-core-2.1.1.jar + │   ├── jdbc + │   │   └── NOTICE.md + │   │   └── mysql-connector-java-8.0.28.jar + │   ├── jedis-3.8.0.jar + │   ├── jersey-apache-client4-1.19.1.jar + │   ├── jersey-client-1.19.1.jar + │   ├── jersey-core-1.19.1.jar + │   ├── jetcd-common-0.5.0.jar + │   ├── jetcd-core-0.5.0.jar + │   ├── jetcd-resolver-0.5.0.jar + │   ├── jettison-1.5.4.jar + │   ├── jjwt-api-0.10.5.jar + │   ├── jjwt-impl-0.10.5.jar + │   ├── jjwt-jackson-0.10.5.jar + │   ├── jna-5.5.0.jar + │   ├── joda-time-2.3.jar + │   ├── jraft-core-1.3.14.jar + │   ├── jsr305-3.0.2.jar + │   ├── jsr311-api-1.1.1.jar + │   ├── jul-to-slf4j-1.7.36.jar + │   ├── junit-4.13.2.jar + │   ├── kafka-clients-3.6.1.jar + │   ├── kryo-5.4.0.jar + │   ├── kryo-serializers-0.45.jar + │   ├── logback-classic-1.2.12.jar + │   ├── logback-core-1.2.12.jar + │   ├── logback-kafka-appender-0.2.0-RC2.jar + │   ├── logstash-logback-encoder-6.5.jar + │   ├── lz4-java-1.7.1.jar + │   ├── metrics-core-4.2.22.jar + │   ├── minlog-1.3.1.jar + │   ├── mxparser-1.2.2.jar + │   ├── nacos-api-1.4.6.jar + │   ├── nacos-client-1.4.6.jar + │   ├── nacos-common-1.4.6.jar + │   ├── netflix-eventbus-0.3.0.jar + │   ├── netflix-infix-0.3.0.jar + │   ├── netty-all-4.1.101.Final.jar + │   ├── netty-buffer-4.1.101.Final.jar + │   ├── netty-codec-4.1.101.Final.jar + │   ├── netty-codec-dns-4.1.101.Final.jar + │   ├── netty-codec-haproxy-4.1.101.Final.jar + │   ├── netty-codec-http-4.1.101.Final.jar + │   ├── netty-codec-http2-4.1.101.Final.jar + │   ├── netty-codec-memcache-4.1.101.Final.jar + │   ├── netty-codec-mqtt-4.1.101.Final.jar + │   ├── netty-codec-redis-4.1.101.Final.jar + │   ├── netty-codec-smtp-4.1.101.Final.jar + │   ├── netty-codec-socks-4.1.101.Final.jar + │   ├── netty-codec-stomp-4.1.101.Final.jar + │   ├── netty-codec-xml-4.1.101.Final.jar + │   ├── netty-common-4.1.101.Final.jar + │   ├── netty-handler-4.1.101.Final.jar + │   ├── netty-handler-proxy-4.1.101.Final.jar + │   ├── netty-handler-ssl-ocsp-4.1.101.Final.jar + │   ├── netty-resolver-4.1.101.Final.jar + │   ├── netty-resolver-dns-4.1.101.Final.jar + │   ├── netty-resolver-dns-classes-macos-4.1.101.Final.jar + │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-aarch_64.jar + │   ├── netty-resolver-dns-native-macos-4.1.101.Final-osx-x86_64.jar + │   ├── netty-transport-4.1.101.Final.jar + │   ├── netty-transport-classes-epoll-4.1.101.Final.jar + │   ├── netty-transport-classes-kqueue-4.1.101.Final.jar + │   ├── netty-transport-native-epoll-4.1.101.Final-linux-aarch_64.jar + │   ├── netty-transport-native-epoll-4.1.101.Final-linux-x86_64.jar + │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-aarch_64.jar + │   ├── netty-transport-native-kqueue-4.1.101.Final-osx-x86_64.jar + │   ├── netty-transport-native-unix-common-4.1.101.Final.jar + │   ├── netty-transport-rxtx-4.1.101.Final.jar + │   ├── netty-transport-sctp-4.1.101.Final.jar + │   ├── netty-transport-udt-4.1.101.Final.jar + │   ├── objenesis-3.2.jar + │   ├── perfmark-api-0.27.0.jar + │   ├── postgresql-42.3.8.jar + │   ├── proto-google-common-protos-2.41.0.jar + │   ├── protobuf-java-3.25.4.jar + │   ├── protobuf-java-util-3.11.0.jar + │   ├── reflectasm-1.11.9.jar + │   ├── registry-client-all-6.3.0.jar + │   ├── rocksdbjni-8.8.1.jar + │   ├── seata-common-2.2.0.jar + │   ├── seata-compressor-all-2.2.0.jar + │   ├── seata-compressor-bzip2-2.2.0.jar + │   ├── seata-compressor-deflater-2.2.0.jar + │   ├── seata-compressor-gzip-2.2.0.jar + │   ├── seata-compressor-lz4-2.2.0.jar + │   ├── seata-compressor-zip-2.2.0.jar + │   ├── seata-compressor-zstd-2.2.0.jar + │   ├── seata-config-all-2.2.0.jar + │   ├── seata-config-apollo-2.2.0.jar + │   ├── seata-config-consul-2.2.0.jar + │   ├── seata-config-core-2.2.0.jar + │   ├── seata-config-etcd3-2.2.0.jar + │   ├── seata-config-nacos-2.2.0.jar + │   ├── seata-config-spring-cloud-2.2.0.jar + │   ├── seata-config-zk-2.2.0.jar + │   ├── seata-console-2.2.0.jar + │   ├── seata-core-2.2.0.jar + │   ├── seata-discovery-all-2.2.0.jar + │   ├── seata-discovery-consul-2.2.0.jar + │   ├── seata-discovery-core-2.2.0.jar + │   ├── seata-discovery-custom-2.2.0.jar + │   ├── seata-discovery-etcd3-2.2.0.jar + │   ├── seata-discovery-eureka-2.2.0.jar + │   ├── seata-discovery-nacos-2.2.0.jar + │   ├── seata-discovery-namingserver-2.2.0.jar + │   ├── seata-discovery-redis-2.2.0.jar + │   ├── seata-discovery-sofa-2.2.0.jar + │   ├── seata-discovery-zk-2.2.0.jar + │   ├── seata-metrics-all-2.2.0.jar + │   ├── seata-metrics-api-2.2.0.jar + │   ├── seata-metrics-core-2.2.0.jar + │   ├── seata-metrics-exporter-prometheus-2.2.0.jar + │   ├── seata-metrics-registry-compact-2.2.0.jar + │   ├── seata-serializer-all-2.2.0.jar + │   ├── seata-serializer-hessian-2.2.0.jar + │   ├── seata-serializer-kryo-2.2.0.jar + │   ├── seata-serializer-protobuf-2.2.0.jar + │   ├── seata-serializer-seata-2.2.0.jar + │   ├── seata-spring-autoconfigure-core-2.2.0.jar + │   ├── seata-spring-autoconfigure-server-2.2.0.jar + │   ├── servo-core-0.12.21.jar + │   ├── simpleclient-0.15.0.jar + │   ├── simpleclient_common-0.15.0.jar + │   ├── simpleclient_httpserver-0.15.0.jar + │   ├── simpleclient_tracer_common-0.15.0.jar + │   ├── simpleclient_tracer_otel-0.15.0.jar + │   ├── simpleclient_tracer_otel_agent-0.15.0.jar + │   ├── slf4j-api-1.7.36.jar + │   ├── snakeyaml-2.0.jar + │   ├── snappy-java-1.1.10.5.jar + │   ├── sofa-common-tools-1.0.12.jar + │   ├── spring-aop-5.3.39.jar + │   ├── spring-beans-5.3.39.jar + │   ├── spring-boot-2.7.18.jar + │   ├── spring-boot-autoconfigure-2.7.18.jar + │   ├── spring-boot-starter-2.7.18.jar + │   ├── spring-boot-starter-json-2.7.18.jar + │   ├── spring-boot-starter-logging-2.7.18.jar + │   ├── spring-boot-starter-security-2.7.18.jar + │   ├── spring-boot-starter-tomcat-2.7.18.jar + │   ├── spring-boot-starter-web-2.7.18.jar + │   ├── spring-context-5.3.39.jar + │   ├── spring-core-5.3.39.jar + │   ├── spring-expression-5.3.39.jar + │   ├── spring-jcl-5.3.39.jar + │   ├── spring-security-config-5.7.11.jar + │   ├── spring-security-core-5.7.11.jar + │   ├── spring-security-crypto-5.7.11.jar + │   ├── spring-security-web-5.7.11.jar + │   ├── spring-test-5.3.39.jar + │   ├── spring-web-5.3.39.jar + │   ├── spring-webmvc-5.3.39.jar + │   ├── tomcat-annotations-api-9.0.83.jar + │   ├── tomcat-embed-core-9.0.90.jar + │   ├── tomcat-embed-el-9.0.90.jar + │   ├── tomcat-embed-websocket-9.0.90.jar + │   ├── xstream-1.4.20.jar + │   ├── zookeeper-3.7.2.jar + │   ├── zookeeper-jute-3.7.2.jar + │   └── zstd-jni-1.5.0-4.jar + ├── script + │   ├── config-center + │   │   ├── README.md + │   │   ├── apollo + │   │   │   ├── apollo-config-interactive.sh + │   │   │   └── apollo-config.sh + │   │   ├── config.txt + │   │   ├── consul + │   │   │   ├── consul-config-interactive.sh + │   │   │   └── consul-config.sh + │   │   ├── etcd3 + │   │   │   ├── etcd3-config-interactive.sh + │   │   │   └── etcd3-config.sh + │   │   ├── nacos + │   │   │   ├── nacos-config-interactive.py + │   │   │   ├── nacos-config-interactive.sh + │   │   │   ├── nacos-config.py + │   │   │   └── nacos-config.sh + │   │   └── zk + │   │   ├── zk-config-interactive.sh + │   │   └── zk-config.sh + │   ├── logstash + │   │   └── config + │   │   ├── logstash-kafka.conf + │   │   └── logstash-logback.conf + │   └── server + │   ├── db + │   │   ├── dm.sql + │   │   ├── mysql.sql + │   │   ├── oracle.sql + │   │   ├── postgresql.sql + │   │   └── sqlserver.sql + │   ├── docker-compose + │   │   └── docker-compose.yaml + │   ├── helm + │   │   └── seata-server + │   │   ├── Chart.yaml + │   │   ├── templates + │   │   │   ├── NOTES.txt + │   │   │   ├── _helpers.tpl + │   │   │   ├── deployment.yaml + │   │   │   ├── service.yaml + │   │   │   └── tests + │   │   │   └── test-connection.yaml + │   │   └── values.yaml + │   └── kubernetes + │   └── seata-server.yaml + └── target + └── seata-server.jar + ``` \ No newline at end of file From f5b897eeb5a4a3fa947a28dd3d8d17c4f84ab41f Mon Sep 17 00:00:00 2001 From: GoodBoyCoder Date: Sat, 21 Sep 2024 22:10:13 +0800 Subject: [PATCH 02/54] optimize: remove the branch registration operation of the XA read-only transaction (#6826) --- changes/en-us/2.x.md | 3 ++ changes/zh-cn/2.x.md | 4 ++- .../rm/datasource/xa/ConnectionProxyXA.java | 13 ++++--- .../datasource/xa/ConnectionProxyXATest.java | 34 +++++++++++++++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index eee100a62d4..1b6199a6121 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -8,6 +8,8 @@ Add changes here for all PR submitted to the 2.x branch. ### optimize: +- [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction + ### refactor: @@ -20,6 +22,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [slievrly](https://github.com/slievrly) +- [GoodBoyCoder](https://github.com/GoodBoyCoder) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index a193a3f7f4e..642eee10df3 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -8,6 +8,8 @@ ### optimize: +- [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 + ### refactor: @@ -22,7 +24,7 @@ - [slievrly](https://github.com/slievrly) - +- [GoodBoyCoder](https://github.com/GoodBoyCoder) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java index 7df7396121e..6d35dfd6301 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXA.java @@ -163,6 +163,11 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { if (currentAutoCommitStatus == autoCommit) { return; } + if (isReadOnly()) { + //If it is a read-only transaction, do nothing + currentAutoCommitStatus = autoCommit; + return; + } if (autoCommit) { // According to JDBC spec: // If this method is called during a transaction and the @@ -210,8 +215,8 @@ public boolean getAutoCommit() throws SQLException { @Override public synchronized void commit() throws SQLException { - if (currentAutoCommitStatus) { - // Ignore the committing on an autocommit session. + if (currentAutoCommitStatus || isReadOnly()) { + // Ignore the committing on an autocommit session and read-only transaction. return; } if (!xaActive || this.xaBranchXid == null) { @@ -251,8 +256,8 @@ public synchronized void commit() throws SQLException { @Override public void rollback() throws SQLException { - if (currentAutoCommitStatus) { - // Ignore the committing on an autocommit session. + if (currentAutoCommitStatus || isReadOnly()) { + // Ignore the committing on an autocommit session and read-only transaction. return; } if (!xaActive || this.xaBranchXid == null) { diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java index 76d3670a09c..77f0f681a88 100644 --- a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/xa/ConnectionProxyXATest.java @@ -204,6 +204,40 @@ public void testCreateStatement() throws Throwable { Assertions.assertTrue(statement instanceof StatementProxyXA); } + @Test + public void testXAReadOnly() throws Throwable { + Connection connection = Mockito.mock(Connection.class); + Mockito.when(connection.getAutoCommit()).thenReturn(true); + Mockito.when(connection.isReadOnly()).thenReturn(true); + + XAResource xaResource = Mockito.mock(XAResource.class); + XAConnection xaConnection = Mockito.mock(XAConnection.class); + Mockito.when(xaConnection.getXAResource()).thenReturn(xaResource); + BaseDataSourceResource baseDataSourceResource = Mockito.mock(BaseDataSourceResource.class); + String xid = "xxx"; + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.doNothing().when(resourceManager).registerResource(any(Resource.class)); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.XA, resourceManager); + + ConnectionProxyXA connectionProxyXA = new ConnectionProxyXA(connection, xaConnection, baseDataSourceResource, xid); + connectionProxyXA.init(); + connectionProxyXA.setAutoCommit(false); + + // Assert setAutoCommit = false was NEVER invoked on the wrapped connection + Mockito.verify(connection, times(0)).setAutoCommit(false); + // Assert XA start was invoked + Mockito.verify(xaResource, times(0)).start(any(Xid.class), any(Integer.class)); + + connectionProxyXA.commit(); + + Mockito.verify(xaResource, times(0)).end(any(Xid.class), any(Integer.class)); + Mockito.verify(xaResource, times(0)).prepare(any(Xid.class)); + + connectionProxyXA.rollback(); + Mockito.verify(xaResource, times(0)).rollback(any(Xid.class)); + } + @AfterAll public static void tearDown(){ RootContext.unbind(); From 49e03ca32c20f5209879aa42f3ba71d5a74c385c Mon Sep 17 00:00:00 2001 From: funkye Date: Mon, 23 Sep 2024 09:21:42 +0800 Subject: [PATCH 03/54] optimize: modify the version to 2.3.0-SNAPSHOT (#6874) --- build/pom.xml | 2 +- changes/en-us/2.x.md | 2 ++ changes/zh-cn/2.x.md | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/build/pom.xml b/build/pom.xml index e7f0815ab6e..e0c95be86fb 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -71,7 +71,7 @@ - 2.2.0 + 2.3.0-SNAPSHOT 1.8 diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 1b6199a6121..79c1f6680c0 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -9,6 +9,7 @@ Add changes here for all PR submitted to the 2.x branch. ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction +- [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT ### refactor: @@ -23,6 +24,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) +- [funky-eyes](https://github.com/funky-eyes) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 642eee10df3..d4f06d24c22 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -9,7 +9,7 @@ ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 - +- [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT ### refactor: @@ -25,6 +25,7 @@ - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) +- [funky-eyes](https://github.com/funky-eyes) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 From 88829f0a6761debd655df72db6f7406cfa834e99 Mon Sep 17 00:00:00 2001 From: jimin Date: Sun, 29 Sep 2024 11:08:29 +0800 Subject: [PATCH 04/54] optimize: upgrade npmjs version (#6892) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../static/console-fe/package-lock.json | 177 +++++++++--------- 3 files changed, 86 insertions(+), 93 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 79c1f6680c0..a2290316d3d 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -10,6 +10,7 @@ Add changes here for all PR submitted to the 2.x branch. ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index d4f06d24c22..d9ef8c79f2d 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -10,6 +10,7 @@ ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 ### refactor: diff --git a/console/src/main/resources/static/console-fe/package-lock.json b/console/src/main/resources/static/console-fe/package-lock.json index ab641c57472..1b86e082a97 100644 --- a/console/src/main/resources/static/console-fe/package-lock.json +++ b/console/src/main/resources/static/console-fe/package-lock.json @@ -4458,9 +4458,9 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -4471,7 +4471,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -4505,21 +4505,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.2.1.tgz", @@ -6189,7 +6174,7 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "engines": { @@ -6217,7 +6202,7 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "engines": { @@ -6677,7 +6662,7 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, @@ -6721,9 +6706,9 @@ } }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" @@ -8090,7 +8075,7 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "engines": { @@ -8175,37 +8160,37 @@ "dev": true }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8232,23 +8217,11 @@ "dev": true }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - } - }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/extend-shallow/-/extend-shallow-2.0.1.tgz", @@ -8464,13 +8437,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8483,7 +8456,7 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { @@ -8492,7 +8465,7 @@ }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, @@ -8682,7 +8655,7 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "engines": { @@ -9560,7 +9533,7 @@ }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "dependencies": { @@ -9698,7 +9671,7 @@ }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "dependencies": { @@ -11259,10 +11232,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -11322,7 +11298,7 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "bin": { @@ -12493,7 +12469,7 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "dependencies": { @@ -12845,9 +12821,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dependencies": { "isarray": "0.0.1" } @@ -13694,15 +13670,18 @@ } }, "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/querystring-es3": { @@ -14895,9 +14874,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmmirror.com/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -14920,7 +14899,7 @@ }, "node_modules/send/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { @@ -14929,13 +14908,22 @@ }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, @@ -15027,15 +15015,15 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -15102,7 +15090,7 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, @@ -15166,18 +15154,21 @@ "dev": true }, "node_modules/side-channel": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.5.tgz", - "integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bind": "^1.0.7", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", "object-inspect": "^1.13.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { @@ -15589,7 +15580,7 @@ }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, "engines": { @@ -16667,7 +16658,7 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "engines": { @@ -17276,7 +17267,7 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "engines": { From 0db8c366ceb402d3336f6a7bee9e40123adf3a8d Mon Sep 17 00:00:00 2001 From: yougecn <40795959+yougecn@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:25:09 +0800 Subject: [PATCH 05/54] feature: support kingbase (#6876) --- changes/en-us/2.x.md | 3 + changes/zh-cn/2.x.md | 2 + .../apache/seata/common/util/PageUtil.java | 2 + .../db/sql/lock/KingbaseLockStoreSql.java | 27 + .../db/sql/log/KingbaseLogStoreSqls.java | 26 + ....seata.core.store.db.sql.lock.LockStoreSql | 3 +- ...e.seata.core.store.db.sql.log.LogStoreSqls | 3 +- .../exec/kingbase/KingbaseInsertExecutor.java | 139 +++++ .../kingbase/KingbaseEscapeHandler.java | 553 ++++++++++++++++++ .../struct/cache/KingbaseTableMetaCache.java | 175 ++++++ .../kingbase/KingbaseUndoDeleteExecutor.java | 79 +++ .../kingbase/KingbaseUndoExecutorHolder.java | 46 ++ .../kingbase/KingbaseUndoInsertExecutor.java | 85 +++ .../undo/kingbase/KingbaseUndoLogManager.java | 105 ++++ .../kingbase/KingbaseUndoUpdateExecutor.java | 79 +++ ...he.seata.rm.datasource.exec.InsertExecutor | 3 +- ...eata.rm.datasource.undo.UndoExecutorHolder | 3 +- ...he.seata.rm.datasource.undo.UndoLogManager | 3 +- .../org.apache.seata.sqlparser.EscapeHandler | 3 +- ...ache.seata.sqlparser.struct.TableMetaCache | 3 +- .../exec/KingbaseInsertExecutorTest.java | 452 ++++++++++++++ .../KingbaseDeleteRecognizerTest.java | 196 +++++++ .../KingbaseInsertRecognizerTest.java | 128 ++++ ...KingbaseSelectForUpdateRecognizerTest.java | 109 ++++ .../KingbaseUpdateRecognizerTest.java | 155 +++++ .../KingbaseUndoDeleteExecutorTest.java | 97 +++ .../KingbaseUndoInsertExecutorTest.java | 95 +++ .../KingbaseUndoUpdateExecutorTest.java | 97 +++ .../keyword/KingbaseEscapeHandlerTest.java | 34 ++ script/client/at/db/kingbase.sql | 43 ++ script/server/db/kingbase.sql | 96 +++ .../kingbase/BaseKingbaseRecognizer.java | 195 ++++++ .../kingbase/KingbaseDeleteRecognizer.java | 137 +++++ .../kingbase/KingbaseInsertRecognizer.java | 163 ++++++ .../KingbaseOperateRecognizerHolder.java | 56 ++ .../KingbaseSelectForUpdateRecognizer.java | 139 +++++ .../kingbase/KingbaseUpdateRecognizer.java | 184 ++++++ ...sqlparser.druid.SQLOperateRecognizerHolder | 3 +- 38 files changed, 3713 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/apache/seata/core/store/db/sql/lock/KingbaseLockStoreSql.java create mode 100644 core/src/main/java/org/apache/seata/core/store/db/sql/log/KingbaseLogStoreSqls.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/kingbase/KingbaseInsertExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/kingbase/KingbaseEscapeHandler.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCache.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoExecutorHolder.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoLogManager.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutor.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/KingbaseInsertExecutorTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseDeleteRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseInsertRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseSelectForUpdateRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseUpdateRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutorTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutorTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutorTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/keyword/KingbaseEscapeHandlerTest.java create mode 100644 script/client/at/db/kingbase.sql create mode 100644 script/server/db/kingbase.sql create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/BaseKingbaseRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseDeleteRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseInsertRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseOperateRecognizerHolder.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseSelectForUpdateRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseUpdateRecognizer.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index a2290316d3d..4bdc421228e 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -4,6 +4,8 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: +- [[#6876](https://github.com/apache/incubator-seata/pull/6876)]support kingbase + ### bugfix: @@ -26,6 +28,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [yougecn](https://github.com/yougecn) Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index d9ef8c79f2d..4151bc235a2 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -3,6 +3,7 @@ ### feature: +[[#6876](https://github.com/apache/incubator-seata/pull/6876)]支持人大金仓数据库(kingbase) ### bugfix: @@ -27,6 +28,7 @@ - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [yougecn](https://github.com/yougecn) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/common/src/main/java/org/apache/seata/common/util/PageUtil.java b/common/src/main/java/org/apache/seata/common/util/PageUtil.java index 90ca42974cd..ea658a64af1 100644 --- a/common/src/main/java/org/apache/seata/common/util/PageUtil.java +++ b/common/src/main/java/org/apache/seata/common/util/PageUtil.java @@ -109,6 +109,7 @@ public static String pageSql(String sourceSql, String dbType, int pageNum, int p case "mysql": case "h2": case "postgresql": + case "kingbase": case "oceanbase": case "dm": return LIMIT_TEMPLATE.replace(SOURCE_SQL_PLACE_HOLD, sourceSql) @@ -143,6 +144,7 @@ public static String countSql(String sourceSql, String dbType) { case "dm": return sourceSql.replaceAll("(?i)(?<=select)(.*)(?=from)", " count(1) "); case "postgresql": + case "kingbase": case "sqlserver": int lastIndexOfOrderBy = sourceSql.toLowerCase().lastIndexOf("order by"); if (lastIndexOfOrderBy != -1) { diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/lock/KingbaseLockStoreSql.java b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/KingbaseLockStoreSql.java new file mode 100644 index 00000000000..1043f0e0123 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/KingbaseLockStoreSql.java @@ -0,0 +1,27 @@ +/* + * 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 org.apache.seata.core.store.db.sql.lock; + +import org.apache.seata.common.loader.LoadLevel; + +/** + * the database lock store kingbase sql + */ +@LoadLevel(name = "kingbase") +public class KingbaseLockStoreSql extends OracleLockStoreSql { + +} diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/log/KingbaseLogStoreSqls.java b/core/src/main/java/org/apache/seata/core/store/db/sql/log/KingbaseLogStoreSqls.java new file mode 100644 index 00000000000..67dec46a895 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/log/KingbaseLogStoreSqls.java @@ -0,0 +1,26 @@ +/* + * 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 org.apache.seata.core.store.db.sql.log; + +import org.apache.seata.common.loader.LoadLevel; + +/** + * Database log store kingbase sql + */ +@LoadLevel(name = "kingbase") +public class KingbaseLogStoreSqls extends OracleLogStoreSqls { +} diff --git a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql index fea6412761c..375298bc638 100644 --- a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql +++ b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql @@ -22,4 +22,5 @@ org.apache.seata.core.store.db.sql.lock.H2LockStoreSql org.apache.seata.core.store.db.sql.lock.SqlServerLockStoreSql org.apache.seata.core.store.db.sql.lock.MariadbLockStoreSql org.apache.seata.core.store.db.sql.lock.PolarDBXLockStoreSql -org.apache.seata.core.store.db.sql.lock.DmLockStoreSql \ No newline at end of file +org.apache.seata.core.store.db.sql.lock.DmLockStoreSql +org.apache.seata.core.store.db.sql.lock.KingbaseLockStoreSql \ No newline at end of file diff --git a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls index 2f87cbe5aae..5eb45d22e0c 100644 --- a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls +++ b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls @@ -22,4 +22,5 @@ org.apache.seata.core.store.db.sql.log.H2LogStoreSqls org.apache.seata.core.store.db.sql.log.SqlServerLogStoreSqls org.apache.seata.core.store.db.sql.log.MariadbLogStoreSqls org.apache.seata.core.store.db.sql.log.PolarDBXLogStoreSqls -org.apache.seata.core.store.db.sql.log.DmLogStoreSqls \ No newline at end of file +org.apache.seata.core.store.db.sql.log.DmLogStoreSqls +org.apache.seata.core.store.db.sql.log.KingbaseLogStoreSqls \ No newline at end of file diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/kingbase/KingbaseInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/kingbase/KingbaseInsertExecutor.java new file mode 100644 index 00000000000..ecafc44269b --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/kingbase/KingbaseInsertExecutor.java @@ -0,0 +1,139 @@ +/* + * 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 org.apache.seata.rm.datasource.exec.kingbase; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.loader.Scope; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.BaseInsertExecutor; +import org.apache.seata.rm.datasource.exec.StatementCallback; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.Sequenceable; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The type Postgresql insert executor. + * + */ +@LoadLevel(name = JdbcConstants.KINGBASE, scope = Scope.PROTOTYPE) +public class KingbaseInsertExecutor extends BaseInsertExecutor implements Sequenceable { + + private static final Logger LOGGER = LoggerFactory.getLogger(KingbaseInsertExecutor.class); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public KingbaseInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + /** + * 1. If the insert columns are not empty and do not contain any pk columns, + * it means that there is no pk value in the insert rows, then all the pk values should come from auto-increment. + *

+ * 2. The pk value exists in insert rows. The possible situations are: + *

    + *
  • The insert columns are empty: all pk values can be obtained from insert rows
  • + *
  • The insert columns contain at least one pk column: first obtain the existing pk value from the insert rows, and other from auto-increment
  • + *
+ * + * @return {@link Map}<{@link String}, {@link List}<{@link Object}>> + * @throws SQLException the sql exception + */ + @Override + public Map> getPkValues() throws SQLException { + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + Map> pkValuesMap = new HashMap<>(pkColumnNameList.size()); + + // first obtain the existing pk value from the insert rows (if exists) + if (!containsColumns() || containsAnyPk()) { + pkValuesMap.putAll(getPkValuesByColumn()); + } + // other from auto-increment + for (String columnName : pkColumnNameList) { + if (!pkValuesMap.containsKey(columnName)) { + pkValuesMap.put(columnName, getGeneratedKeys(columnName)); + } + } + return pkValuesMap; + } + + /** + * Whether the insert columns contain any pk columns + * + * @return true: contain at least one pk column. false: do not contain any pk columns + */ + public boolean containsAnyPk() { + SQLInsertRecognizer recognizer = (SQLInsertRecognizer)sqlRecognizer; + List insertColumns = recognizer.getInsertColumns(); + if (CollectionUtils.isEmpty(insertColumns)) { + return false; + } + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + if (CollectionUtils.isEmpty(pkColumnNameList)) { + return false; + } + List newColumns = ColumnUtils.delEscape(insertColumns, getDbType()); + return pkColumnNameList.stream().anyMatch(pkColumn -> newColumns.contains(pkColumn) + || CollectionUtils.toUpperList(newColumns).contains(pkColumn.toUpperCase())); + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + Set keySet = pkValuesMap.keySet(); + for (String pkKey : keySet) { + List pkValues = pkValuesMap.get(pkKey); + for (int i = 0; i < pkValues.size(); i++) { + if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlSequenceExpr) { + pkValues.set(i, getPkValuesBySequence((SqlSequenceExpr) pkValues.get(i), pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlMethodExpr) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof Null) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } + } + pkValuesMap.put(pkKey, pkValues); + } + return pkValuesMap; + } + + @Override + public String getSequenceSql(SqlSequenceExpr expr) { + return "SELECT " + expr.getSequence() + ".currval FROM DUAL"; + } + +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/kingbase/KingbaseEscapeHandler.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/kingbase/KingbaseEscapeHandler.java new file mode 100644 index 00000000000..05c9cdbc8bb --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/kingbase/KingbaseEscapeHandler.java @@ -0,0 +1,553 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.handler.kingbase; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.EscapeHandler; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * The type kingbase keyword checker. + */ +@LoadLevel(name = JdbcConstants.KINGBASE) +public class KingbaseEscapeHandler implements EscapeHandler { + + private Set keywordSet = Arrays.stream(KingbaseEscapeHandler.KingbaseKeyword.values()).map(KingbaseEscapeHandler.KingbaseKeyword::name).collect(Collectors.toSet()); + + /** + * kingbase keyword + */ + private enum KingbaseKeyword { + /** + * ACCESS is kingbase keyword + */ + ACCESS("ACCESS"), + /** + * ADD is kingbase keyword + */ + ADD("ADD"), + /** + * ALL is kingbase keyword + */ + ALL("ALL"), + /** + * ALTER is kingbase keyword + */ + ALTER("ALTER"), + /** + * AND is kingbase keyword + */ + AND("AND"), + /** + * ANY is kingbase keyword + */ + ANY("ANY"), + /** + * AS is kingbase keyword + */ + AS("AS"), + /** + * ASC is kingbase keyword + */ + ASC("ASC"), + /** + * AUDIT is kingbase keyword + */ + AUDIT("AUDIT"), + /** + * BETWEEN is kingbase keyword + */ + BETWEEN("BETWEEN"), + /** + * BY is kingbase keyword + */ + BY("BY"), + /** + * CHAR is kingbase keyword + */ + CHAR("CHAR"), + /** + * CHECK is kingbase keyword + */ + CHECK("CHECK"), + /** + * CLUSTER is kingbase keyword + */ + CLUSTER("CLUSTER"), + /** + * COLUMN is kingbase keyword + */ + COLUMN("COLUMN"), + /** + * COLUMN_VALUE is kingbase keyword + */ + COLUMN_VALUE("COLUMN_VALUE"), + /** + * COMMENT is kingbase keyword + */ + COMMENT("COMMENT"), + /** + * COMPRESS is kingbase keyword + */ + COMPRESS("COMPRESS"), + /** + * CONNECT is kingbase keyword + */ + CONNECT("CONNECT"), + /** + * CREATE is kingbase keyword + */ + CREATE("CREATE"), + /** + * CURRENT is kingbase keyword + */ + CURRENT("CURRENT"), + /** + * DATE is kingbase keyword + */ + DATE("DATE"), + /** + * DECIMAL is kingbase keyword + */ + DECIMAL("DECIMAL"), + /** + * DEFAULT is kingbase keyword + */ + DEFAULT("DEFAULT"), + /** + * DELETE is kingbase keyword + */ + DELETE("DELETE"), + /** + * DESC is kingbase keyword + */ + DESC("DESC"), + /** + * DISTINCT is kingbase keyword + */ + DISTINCT("DISTINCT"), + /** + * DROP is kingbase keyword + */ + DROP("DROP"), + /** + * ELSE is kingbase keyword + */ + ELSE("ELSE"), + /** + * EXCLUSIVE is kingbase keyword + */ + EXCLUSIVE("EXCLUSIVE"), + /** + * EXISTS is kingbase keyword + */ + EXISTS("EXISTS"), + /** + * FILE is kingbase keyword + */ + FILE("FILE"), + /** + * FLOAT is kingbase keyword + */ + FLOAT("FLOAT"), + /** + * FOR is kingbase keyword + */ + FOR("FOR"), + /** + * FROM is kingbase keyword + */ + FROM("FROM"), + /** + * GRANT is kingbase keyword + */ + GRANT("GRANT"), + /** + * GROUP is kingbase keyword + */ + GROUP("GROUP"), + /** + * HAVING is kingbase keyword + */ + HAVING("HAVING"), + /** + * IDENTIFIED is kingbase keyword + */ + IDENTIFIED("IDENTIFIED"), + /** + * IMMEDIATE is kingbase keyword + */ + IMMEDIATE("IMMEDIATE"), + /** + * IN is kingbase keyword + */ + IN("IN"), + /** + * INCREMENT is kingbase keyword + */ + INCREMENT("INCREMENT"), + /** + * INDEX is kingbase keyword + */ + INDEX("INDEX"), + /** + * INITIAL is kingbase keyword + */ + INITIAL("INITIAL"), + /** + * INSERT is kingbase keyword + */ + INSERT("INSERT"), + /** + * INTEGER is kingbase keyword + */ + INTEGER("INTEGER"), + /** + * INTERSECT is kingbase keyword + */ + INTERSECT("INTERSECT"), + /** + * INTO is kingbase keyword + */ + INTO("INTO"), + /** + * IS is kingbase keyword + */ + IS("IS"), + /** + * LEVEL is kingbase keyword + */ + LEVEL("LEVEL"), + /** + * LIKE is kingbase keyword + */ + LIKE("LIKE"), + /** + * LOCK is kingbase keyword + */ + LOCK("LOCK"), + /** + * LONG is kingbase keyword + */ + LONG("LONG"), + /** + * MAXEXTENTS is kingbase keyword + */ + MAXEXTENTS("MAXEXTENTS"), + /** + * MINUS is kingbase keyword + */ + MINUS("MINUS"), + /** + * MLSLABEL is kingbase keyword + */ + MLSLABEL("MLSLABEL"), + /** + * MODE is kingbase keyword + */ + MODE("MODE"), + /** + * MODIFY is kingbase keyword + */ + MODIFY("MODIFY"), + /** + * NESTED_TABLE_ID is kingbase keyword + */ + NESTED_TABLE_ID("NESTED_TABLE_ID"), + /** + * NOAUDIT is kingbase keyword + */ + NOAUDIT("NOAUDIT"), + /** + * NOCOMPRESS is kingbase keyword + */ + NOCOMPRESS("NOCOMPRESS"), + /** + * NOT is kingbase keyword + */ + NOT("NOT"), + /** + * NOWAIT is kingbase keyword + */ + NOWAIT("NOWAIT"), + /** + * NULL is kingbase keyword + */ + NULL("NULL"), + /** + * NUMBER is kingbase keyword + */ + NUMBER("NUMBER"), + /** + * OF is kingbase keyword + */ + OF("OF"), + /** + * OFFLINE is kingbase keyword + */ + OFFLINE("OFFLINE"), + /** + * ON is kingbase keyword + */ + ON("ON"), + /** + * ONLINE is kingbase keyword + */ + ONLINE("ONLINE"), + /** + * OPTION is kingbase keyword + */ + OPTION("OPTION"), + /** + * OR is kingbase keyword + */ + OR("OR"), + /** + * ORDER is kingbase keyword + */ + ORDER("ORDER"), + /** + * PCTFREE is kingbase keyword + */ + PCTFREE("PCTFREE"), + /** + * PRIOR is kingbase keyword + */ + PRIOR("PRIOR"), + /** + * PUBLIC is kingbase keyword + */ + PUBLIC("PUBLIC"), + /** + * RAW is kingbase keyword + */ + RAW("RAW"), + /** + * RENAME is kingbase keyword + */ + RENAME("RENAME"), + /** + * RESOURCE is kingbase keyword + */ + RESOURCE("RESOURCE"), + /** + * REVOKE is kingbase keyword + */ + REVOKE("REVOKE"), + /** + * ROW is kingbase keyword + */ + ROW("ROW"), + /** + * ROWID is kingbase keyword + */ + ROWID("ROWID"), + /** + * ROWNUM is kingbase keyword + */ + ROWNUM("ROWNUM"), + /** + * ROWS is kingbase keyword + */ + ROWS("ROWS"), + /** + * SELECT is kingbase keyword + */ + SELECT("SELECT"), + /** + * SESSION is kingbase keyword + */ + SESSION("SESSION"), + /** + * SET is kingbase keyword + */ + SET("SET"), + /** + * SHARE is kingbase keyword + */ + SHARE("SHARE"), + /** + * SIZE is kingbase keyword + */ + SIZE("SIZE"), + /** + * SMALLINT is kingbase keyword + */ + SMALLINT("SMALLINT"), + /** + * START is kingbase keyword + */ + START("START"), + /** + * SUCCESSFUL is kingbase keyword + */ + SUCCESSFUL("SUCCESSFUL"), + /** + * SYNONYM is kingbase keyword + */ + SYNONYM("SYNONYM"), + /** + * SYSDATE is kingbase keyword + */ + SYSDATE("SYSDATE"), + /** + * TABLE is kingbase keyword + */ + TABLE("TABLE"), + /** + * THEN is kingbase keyword + */ + THEN("THEN"), + /** + * TO is kingbase keyword + */ + TO("TO"), + /** + * TRIGGER is kingbase keyword + */ + TRIGGER("TRIGGER"), + /** + * UID is kingbase keyword + */ + UID("UID"), + /** + * UNION is kingbase keyword + */ + UNION("UNION"), + /** + * UNIQUE is kingbase keyword + */ + UNIQUE("UNIQUE"), + /** + * UPDATE is kingbase keyword + */ + UPDATE("UPDATE"), + /** + * USER is kingbase keyword + */ + USER("USER"), + /** + * VALIDATE is kingbase keyword + */ + VALIDATE("VALIDATE"), + /** + * VALUES is kingbase keyword + */ + VALUES("VALUES"), + /** + * VARCHAR is kingbase keyword + */ + VARCHAR("VARCHAR"), + /** + * VARCHAR2 is kingbase keyword + */ + VARCHAR2("VARCHAR2"), + /** + * VIEW is kingbase keyword + */ + VIEW("VIEW"), + /** + * WHENEVER is kingbase keyword + */ + WHENEVER("WHENEVER"), + /** + * WHERE is kingbase keyword + */ + WHERE("WHERE"), + /** + * WITH is kingbase keyword + */ + WITH("WITH"); + /** + * The Name. + */ + public final String name; + + KingbaseKeyword(String name) { + this.name = name; + } + } + + @Override + public boolean checkIfKeyWords(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + + @Override + public boolean checkIfNeedEscape(String columnName, TableMeta tableMeta) { + if (StringUtils.isBlank(columnName)) { + return false; + } + columnName = columnName.trim(); + if (containsEscape(columnName)) { + return false; + } + boolean isKeyWord = checkIfKeyWords(columnName); + if (isKeyWord) { + return true; + } + // kingbase + // we are recommend table name and column name must uppercase. + // if exists full uppercase, the table name or column name doesn't bundle escape symbol. + //create\read table TABLE "table" "TABLE" + // + //table √ √ × √ + // + //TABLE √ √ × √ + // + //"table" × × √ × + // + //"TABLE" √ √ × √ + if (null != tableMeta) { + ColumnMeta columnMeta = tableMeta.getColumnMeta(columnName); + if (null != columnMeta) { + return columnMeta.isCaseSensitive(); + } + } else if (isUppercase(columnName)) { + return false; + } + return true; + } + + private static boolean isUppercase(String fieldOrTableName) { + if (fieldOrTableName == null) { + return false; + } + char[] chars = fieldOrTableName.toCharArray(); + for (char ch : chars) { + if (ch >= 'a' && ch <= 'z') { + return false; + } + } + return true; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCache.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCache.java new file mode 100644 index 00000000000..a35aa2dfa52 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/KingbaseTableMetaCache.java @@ -0,0 +1,175 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.struct.cache; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.IndexMeta; +import org.apache.seata.sqlparser.struct.IndexType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * The type Table meta cache. + */ +@LoadLevel(name = JdbcConstants.KINGBASE) +public class KingbaseTableMetaCache extends OracleTableMetaCache { + public static class TableNameMeta { + private final String schema; + private final String tableName; + + public TableNameMeta(String schema, String tableName) { + this.schema = schema; + this.tableName = tableName; + } + + public String getSchema() { + return schema; + } + + public String getTableName() { + return tableName; + } + } + + @Override + protected TableMeta resultSetMetaToSchema(DatabaseMetaData dbmd, String tableName) throws SQLException { + TableMeta result = new TableMeta(); + + TableNameMeta tableNameMeta = toTableNameMeta(tableName, dbmd.getConnection().getSchema()); + result.setTableName(tableNameMeta.getTableName()); + result.setOriginalTableName(tableName); + try (ResultSet rsColumns = dbmd.getColumns("", tableNameMeta.getSchema(), tableNameMeta.getTableName(), "%"); + ResultSet rsIndex = dbmd.getIndexInfo(null, tableNameMeta.getSchema(), tableNameMeta.getTableName(), false, true); + ResultSet rsPrimary = dbmd.getPrimaryKeys(null, tableNameMeta.getSchema(), tableNameMeta.getTableName())) { + processColumns(result, rsColumns); + + processIndexes(result, rsIndex); + + processPrimaries(result, rsPrimary); + if (result.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException(String.format("Could not found any index in the table: %s", tableName)); + } + } + + return result; + } + + protected TableNameMeta toTableNameMeta(String tableName, String schemaFromConnection) { + String[] schemaTable = tableName.split("\\."); + + String schema = schemaTable.length > 1 ? schemaTable[0] : schemaFromConnection; + if (schema != null) { + schema = schema.contains("\"") ? schema.replace("\"", "") : schema.toUpperCase(); + } + + tableName = schemaTable.length > 1 ? schemaTable[1] : tableName; + tableName = tableName.contains("\"") ? tableName.replace("\"", "") : tableName.toUpperCase(); + + return new TableNameMeta(schema, tableName); + } + + protected void processColumns(TableMeta tableMeta, ResultSet rs) throws SQLException { + while (rs.next()) { + ColumnMeta col = toColumnMeta(rs); + tableMeta.getAllColumns().put(col.getColumnName(), col); + } + } + + protected void processIndexes(TableMeta tableMeta, ResultSet rs) throws SQLException { + while (rs.next()) { + String indexName = rs.getString("INDEX_NAME"); + if (StringUtils.isNullOrEmpty(indexName)) { + continue; + } + + String colName = rs.getString("COLUMN_NAME"); + ColumnMeta col = tableMeta.getAllColumns().get(colName); + if (tableMeta.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tableMeta.getAllIndexes().get(indexName); + index.getValues().add(col); + continue; + } + + tableMeta.getAllIndexes().put(indexName, toIndexMeta(rs, indexName, col)); + } + } + + protected void processPrimaries(TableMeta tableMeta, ResultSet rs) throws SQLException { + while (rs.next()) { + String pkColName; + try { + pkColName = rs.getString("COLUMN_NAME"); + } catch (Exception e) { + pkColName = rs.getString("PK_NAME"); + } + + String finalPkColName = pkColName; + for (IndexMeta i : tableMeta.getAllIndexes().values()) { + i.getValues().stream() + .filter(c -> finalPkColName.equals(c.getColumnName())) + .forEach(c -> i.setIndextype(IndexType.PRIMARY)); + } + } + } + + protected ColumnMeta toColumnMeta(ResultSet rs) throws SQLException { + ColumnMeta result = new ColumnMeta(); + result.setTableCat(rs.getString("TABLE_CAT")); + result.setTableSchemaName(rs.getString("TABLE_SCHEM")); + result.setTableName(rs.getString("TABLE_NAME")); + result.setColumnName(rs.getString("COLUMN_NAME")); + result.setDataType(rs.getInt("DATA_TYPE")); + result.setDataTypeName(rs.getString("TYPE_NAME")); + result.setColumnSize(rs.getInt("COLUMN_SIZE")); + result.setDecimalDigits(rs.getInt("DECIMAL_DIGITS")); + result.setNumPrecRadix(rs.getInt("NUM_PREC_RADIX")); + result.setNullAble(rs.getInt("NULLABLE")); + result.setRemarks(rs.getString("REMARKS")); + result.setColumnDef(rs.getString("COLUMN_DEF")); + result.setSqlDataType(rs.getInt("SQL_DATA_TYPE")); + result.setSqlDatetimeSub(rs.getInt("SQL_DATETIME_SUB")); + result.setCharOctetLength(rs.getInt("CHAR_OCTET_LENGTH")); + result.setOrdinalPosition(rs.getInt("ORDINAL_POSITION")); + result.setIsNullAble(rs.getString("IS_NULLABLE")); + return result; + } + + protected IndexMeta toIndexMeta(ResultSet rs, String indexName, ColumnMeta columnMeta) throws SQLException { + IndexMeta result = new IndexMeta(); + result.setIndexName(indexName); + result.setNonUnique(rs.getBoolean("NON_UNIQUE")); + result.setIndexQualifier(rs.getString("INDEX_QUALIFIER")); + result.setType(rs.getShort("TYPE")); + result.setOrdinalPosition(rs.getShort("ORDINAL_POSITION")); + result.setAscOrDesc(rs.getString("ASC_OR_DESC")); + result.setCardinality(rs.getInt("CARDINALITY")); + result.getValues().add(columnMeta); + if (!result.isNonUnique()) { + result.setIndextype(IndexType.UNIQUE); + } else { + result.setIndextype(IndexType.NORMAL); + } + return result; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutor.java new file mode 100644 index 00000000000..a6736605952 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutor.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type kingbase undo delete executor. + * + */ +public class KingbaseUndoDeleteExecutor extends AbstractUndoExecutor { + + /** + * Instantiates a new kingbase undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public KingbaseUndoDeleteExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + /** + * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?) + */ + private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)"; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + List fields = new ArrayList<>(row.nonPrimaryKeys()); + fields.addAll(getOrderedPkList(beforeImage,row,JdbcConstants.KINGBASE)); + + // delete sql undo log before image all field come from table meta, need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String insertColumns = fields.stream() + .map(field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.KINGBASE)) + .collect(Collectors.joining(", ")); + String insertValues = fields.stream().map(field -> "?") + .collect(Collectors.joining(", ")); + + return String.format(INSERT_SQL_TEMPLATE, sqlUndoLog.getTableName(), insertColumns, insertValues); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoExecutorHolder.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoExecutorHolder.java new file mode 100644 index 00000000000..b9084a54454 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoExecutorHolder.java @@ -0,0 +1,46 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.rm.datasource.undo.UndoExecutorHolder; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The Type KingbaseUndoExecutorHolder + * + */ +@LoadLevel(name = JdbcConstants.KINGBASE) +public class KingbaseUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new KingbaseUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new KingbaseUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new KingbaseUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutor.java new file mode 100644 index 00000000000..837d18d7f48 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutor.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.SqlGenerateUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type kingbase undo insert executor. + * + */ +public class KingbaseUndoInsertExecutor extends AbstractUndoExecutor { + + /** + * DELETE FROM a WHERE pk = ? + */ + private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords afterImage = sqlUndoLog.getAfterImage(); + List afterImageRows = afterImage.getRows(); + if (CollectionUtils.isEmpty(afterImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + return generateDeleteSql(afterImageRows,afterImage); + } + + @Override + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) throws SQLException { + int undoIndex = 0; + for (Field pkField:pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + } + + private String generateDeleteSql(List rows, TableRecords afterImage) { + List pkNameList = getOrderedPkList(afterImage, rows.get(0), JdbcConstants.KINGBASE).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.KINGBASE); + return String.format(DELETE_SQL_TEMPLATE, sqlUndoLog.getTableName(), whereSql); + } + + /** + * Instantiates a new kingbase undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public KingbaseUndoInsertExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoLogManager.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoLogManager.java new file mode 100644 index 00000000000..499ba1fed1c --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoLogManager.java @@ -0,0 +1,105 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.DateUtil; +import org.apache.seata.core.compressor.CompressorType; +import org.apache.seata.core.constants.ClientTableColumnsName; +import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager; +import org.apache.seata.rm.datasource.undo.UndoLogParser; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +/** + */ +@LoadLevel(name = JdbcConstants.KINGBASE) +public class KingbaseUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(KingbaseUndoLogManager.class); + + private static final String CHECK_UNDO_LOG_TABLE_EXIST_SQL = "SELECT 1 FROM " + UNDO_LOG_TABLE_NAME + " WHERE ROWNUM = 1"; + + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_ID + "," + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + + ClientTableColumnsName.UNDO_LOG_XID + ", " + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + "VALUES (UNDO_LOG_SEQ.nextval, ?, ?, ?, ?, ?, sysdate, sysdate)"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= to_date(?,'yyyy-mm-dd hh24:mi:ss') and ROWNUM <= ?"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { + String dateStr = DateUtil.formatDate(logCreated, "yyyy-MM-dd HH:mm:ss"); + deletePST.setString(1, dateStr); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId,rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), + State.GlobalFinished, conn); + } + + + private void insertUndoLog(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { + pst.setLong(1, branchID); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected String getCheckUndoLogTableExistSql() { + return CHECK_UNDO_LOG_TABLE_EXIST_SQL; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutor.java new file mode 100644 index 00000000000..78e3985712c --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutor.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.SqlGenerateUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type kingbase undo update executor. + */ +public class KingbaseUndoUpdateExecutor extends AbstractUndoExecutor { + + /** + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 = ? and pk2 = ? + */ + private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); // TODO + } + Row row = beforeImageRows.get(0); + + List nonPkFields = row.nonPrimaryKeys(); + // update sql undo log before image all field come from table meta. need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String updateColumns = nonPkFields.stream().map( + field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.KINGBASE) + " = ?").collect( + Collectors.joining(", ")); + + List pkNameList = getOrderedPkList(beforeImage, row, JdbcConstants.KINGBASE).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.KINGBASE); + + return String.format(UPDATE_SQL_TEMPLATE, sqlUndoLog.getTableName(), updateColumns, whereSql); + } + + /** + * Instantiates a new postgresql undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public KingbaseUndoUpdateExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor index f928928e1dd..49701f788b2 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor @@ -20,4 +20,5 @@ org.apache.seata.rm.datasource.exec.postgresql.PostgresqlInsertExecutor org.apache.seata.rm.datasource.exec.sqlserver.SqlServerInsertExecutor org.apache.seata.rm.datasource.exec.mariadb.MariadbInsertExecutor org.apache.seata.rm.datasource.exec.polardbx.PolarDBXInsertExecutor -org.apache.seata.rm.datasource.exec.dm.DmInsertExecutor \ No newline at end of file +org.apache.seata.rm.datasource.exec.dm.DmInsertExecutor +org.apache.seata.rm.datasource.exec.kingbase.KingbaseInsertExecutor \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder index 4bad8e9af02..178aca432f5 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder @@ -20,4 +20,5 @@ org.apache.seata.rm.datasource.undo.postgresql.PostgresqlUndoExecutorHolder org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoExecutorHolder org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoExecutorHolder org.apache.seata.rm.datasource.undo.polardbx.PolarDBXUndoExecutorHolder -org.apache.seata.rm.datasource.undo.dm.DmUndoExecutorHolder \ No newline at end of file +org.apache.seata.rm.datasource.undo.dm.DmUndoExecutorHolder +org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoExecutorHolder \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager index a18cb71ab84..040242e30e7 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager @@ -20,4 +20,5 @@ org.apache.seata.rm.datasource.undo.postgresql.PostgresqlUndoLogManager org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoLogManager org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoLogManager org.apache.seata.rm.datasource.undo.polardbx.PolarDBXUndoLogManager -org.apache.seata.rm.datasource.undo.dm.DmUndoLogManager \ No newline at end of file +org.apache.seata.rm.datasource.undo.dm.DmUndoLogManager +org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoLogManager \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler index c4f82c85498..6c3ef4dc8aa 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler @@ -20,4 +20,5 @@ org.apache.seata.rm.datasource.sql.handler.postgresql.PostgresqlEscapeHandler org.apache.seata.rm.datasource.sql.handler.mariadb.MariadbEscapeHandler org.apache.seata.rm.datasource.sql.handler.sqlserver.SqlServerEscapeHandler org.apache.seata.rm.datasource.sql.handler.polardbx.PolarDBXEscapeHandler -org.apache.seata.rm.datasource.sql.handler.dm.DmEscapeHandler \ No newline at end of file +org.apache.seata.rm.datasource.sql.handler.dm.DmEscapeHandler +org.apache.seata.rm.datasource.sql.handler.kingbase.KingbaseEscapeHandler \ No newline at end of file diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache index 1b09044c0f6..a55afda3015 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache @@ -20,4 +20,5 @@ org.apache.seata.rm.datasource.sql.struct.cache.PostgresqlTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.SqlServerTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.MariadbTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.PolarDBXTableMetaCache -org.apache.seata.rm.datasource.sql.struct.cache.DmTableMetaCache \ No newline at end of file +org.apache.seata.rm.datasource.sql.struct.cache.DmTableMetaCache +org.apache.seata.rm.datasource.sql.struct.cache.KingbaseTableMetaCache \ No newline at end of file diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/KingbaseInsertExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/KingbaseInsertExecutorTest.java new file mode 100644 index 00000000000..2e34e621864 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/KingbaseInsertExecutorTest.java @@ -0,0 +1,452 @@ +/* + * 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 org.apache.seata.rm.datasource.exec; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.rm.datasource.ConnectionProxy; +import org.apache.seata.rm.datasource.PreparedStatementProxy; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.kingbase.KingbaseInsertExecutor; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +public class KingbaseInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE_ID = 100; + private static final Integer PK_VALUE_USER_ID = 200; + + private ConnectionProxy connectionProxy; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private StatementCallback statementCallback; + + private TableMeta tableMeta; + + private KingbaseInsertExecutor insertExecutor; + + private final int pkIndexId = 0; + + private final int pkIndexUserId = 1; + + private HashMap pkIndexMap; + + private HashMap multiPkIndexMap; + + @BeforeEach + public void init() { + connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.KINGBASE); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new KingbaseInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + }}; + + multiPkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + put(USER_ID_COLUMN, pkIndexUserId); + }}; + } + + @Test + public void testPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValuesSeq = new ArrayList<>(); + pkValuesSeq.add(PK_VALUE_ID); + + doReturn(pkValuesSeq).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeq); + } + + @Test + public void testMultiPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersMultiPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + List pkValuesSeqId = new ArrayList<>(); + pkValuesSeqId.add(PK_VALUE_ID); + List pkValuesSeqUserId = new ArrayList<>(); + pkValuesSeqUserId.add(PK_VALUE_USER_ID); + + doReturn(pkValuesSeqId).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkValuesSeqUserId).when(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + verify(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeqId); + Assertions.assertEquals(pkValuesByColumn.get(USER_ID_COLUMN), pkValuesSeqUserId); + } + + @Test + public void testPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + doReturn(Arrays.asList(new Object[]{PK_VALUE_ID})).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + Map> pkValuesByAuto = insertExecutor.getPkValues(); + + verify(insertExecutor).getGeneratedKeys(ID_COLUMN); + Assertions.assertEquals(pkValuesByAuto.get(ID_COLUMN), Arrays.asList(new Object[]{PK_VALUE_ID})); + } + + @Test + public void testMultiPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersMultiPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValues(); + }); + + + } + + @Test + public void testStatement_pkValueByAuto_NotSupportYetException() throws Exception { + mockInsertColumns(); + mockStatementInsertRows(); + + statementProxy = mock(StatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.KINGBASE); + + insertExecutor = Mockito.spy(new KingbaseInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Map map = new HashMap<>(); + map.put(ID_COLUMN, mock(ColumnMeta.class)); + doReturn(map).when(tableMeta).getPrimaryKeyMap(); + + ResultSet rs = mock(ResultSet.class); + doReturn(rs).when(statementProxy).getGeneratedKeys(); + doReturn(false).when(rs).next(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getGeneratedKeys(ID_COLUMN); + }); + + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValuesByColumn(); + }); + + } + + @Test + public void testGetPkValues_SinglePk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock pk values from insert rows + Map> mockPkValuesFromColumn = new HashMap<>(); + mockPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + + // situation1: insert columns are empty + List columns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain the pk column + columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns are not empty and do not contain the pk column + columns = new ArrayList<>(); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals( + Collections.singletonMap(ID_COLUMN, mockPkValuesAutoGenerated).entrySet(), + insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testGetPkValues_MultiPk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock all pk values from insert rows + Map> mockAllPkValuesFromColumn = new HashMap<>(); + mockAllPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + mockAllPkValuesFromColumn.put(USER_ID_COLUMN, Collections.singletonList(PK_VALUE_USER_ID + 1)); + doReturn(mockAllPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated_ID = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated_ID).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + List mockPkValuesAutoGenerated_USER_ID = Collections.singletonList(PK_VALUE_USER_ID); + doReturn(mockPkValuesAutoGenerated_USER_ID).when(insertExecutor).getGeneratedKeys(USER_ID_COLUMN); + + // situation1: insert columns are empty + List insertColumns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain all pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns contain partial pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + Map> mockPkValuesFromColumn_ID = new HashMap<>(); + mockPkValuesFromColumn_ID.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn_ID).when(insertExecutor).getPkValuesByColumn(); + + Map> expectPkValues = new HashMap<>(mockPkValuesFromColumn_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation4: insert columns are not empty and do not contain the pk column + insertColumns = new ArrayList<>(); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + doReturn(new HashMap<>()).when(insertExecutor).getPkValuesByColumn(); + + expectPkValues = new HashMap<>(); + expectPkValues.put(ID_COLUMN, mockPkValuesAutoGenerated_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testContainsAnyPK() { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + mockInsertColumns(); + doReturn(null).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + List pkColumns = new ArrayList<>(); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private SqlSequenceExpr mockParametersPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private SqlSequenceExpr mockParametersMultiPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(expr); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private void mockParametersPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + private void mockParametersMultiPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(Null.get()); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + } + + private void mockStatementInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList(Null.get(), "xx", "xx", "xx")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseDeleteRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseDeleteRecognizerTest.java new file mode 100644 index 00000000000..b9691666916 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseDeleteRecognizerTest.java @@ -0,0 +1,196 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.kingbase; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleArgumentExpr; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.kingbase.KingbaseDeleteRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class KingbaseDeleteRecognizerTest { + + private static final String DB_TYPE = "kingbase"; + + @Test + public void testGetSqlType() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseDeleteRecognizer recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.DELETE); + } + + @Test + public void testGetTableAlias() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseDeleteRecognizer recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseDeleteRecognizer recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseDeleteRecognizer recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for normal sql + Assertions.assertEquals("id = ?", whereCondition); + + sql = "delete from t where id in (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for sql with in + Assertions.assertEquals("id IN (?)", whereCondition); + + sql = "delete from t where id between ? and ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + ArrayList idParam2 = new ArrayList<>(); + idParam.add(2); + Map result = new HashMap(); + result.put(1, idParam); + result.put(2, idParam2); + return result; + } + }, new ArrayList<>()); + //test for sql with in + Assertions.assertEquals("id BETWEEN ? AND ?", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new KingbaseDeleteRecognizer(s, deleteAst).getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return new HashMap<>(); + } + }, new ArrayList<>()); + }); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseDeleteRecognizer recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = 1"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for normal sql + Assertions.assertEquals("id = 1", whereCondition); + + sql = "delete from t where id in (1)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for sql with in + Assertions.assertEquals("id IN (1)", whereCondition); + + sql = "delete from t where id between 1 and 2"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + //test for sql with in + Assertions.assertEquals("id BETWEEN 1 AND 2", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (1)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new KingbaseDeleteRecognizer(s, deleteAst).getWhereCondition(); + }); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseInsertRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseInsertRecognizerTest.java new file mode 100644 index 00000000000..f3d80ebbf43 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseInsertRecognizerTest.java @@ -0,0 +1,128 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.kingbase; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryDoubleExpr; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.kingbase.KingbaseInsertRecognizer; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + + +public class KingbaseInsertRecognizerTest { + + private static final String DB_TYPE = "kingbase"; + + @Test + public void testGetSqlType() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.INSERT); + } + + @Test + public void testGetTableAlias() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetInsertColumns() { + + //test for no column + String sql = "insert into t values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + List insertColumns = recognizer.getInsertColumns(); + Assertions.assertNull(insertColumns); + + //test for normal + sql = "insert into t(a) values (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + insertColumns = recognizer.getInsertColumns(); + Assertions.assertEquals(1, insertColumns.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getColumns().add(new OracleBinaryDoubleExpr()); + + KingbaseInsertRecognizer kingbaseInsertRecognizer = new KingbaseInsertRecognizer(s, sqlInsertStatement); + kingbaseInsertRecognizer.getInsertColumns(); + }); + } + + @Test + public void testGetInsertRows() { + final int pkIndex = 0; + //test for null value + String sql = "insert into t(id, no, name, age, time) values (id_seq.nextval, null, 'a', ?, now())"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer recognizer = new KingbaseInsertRecognizer(sql, asts.get(0)); + List> insertRows = recognizer.getInsertRows(Collections.singletonList(pkIndex)); + Assertions.assertEquals(1, insertRows.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getValuesList().get(0).getValues().set(pkIndex, new OracleBinaryDoubleExpr()); + + KingbaseInsertRecognizer kingbaseInsertRecognizer = new KingbaseInsertRecognizer(s, sqlInsertStatement); + kingbaseInsertRecognizer.getInsertRows(Collections.singletonList(pkIndex)); + }); + } + + @Test + public void testNotPlaceholder_giveValidPkIndex() { + String sql = "insert into test(create_time) values(sysdate)"; + List sqlStatements = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseInsertRecognizer kingbase = new KingbaseInsertRecognizer(sql, sqlStatements.get(0)); + List> insertRows = kingbase.getInsertRows(Collections.singletonList(-1)); + Assertions.assertTrue(insertRows.get(0).get(0) instanceof NotPlaceholderExpr); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseSelectForUpdateRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseSelectForUpdateRecognizerTest.java new file mode 100644 index 00000000000..3b946d3c89a --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseSelectForUpdateRecognizerTest.java @@ -0,0 +1,109 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.kingbase; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.kingbase.KingbaseSelectForUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public class KingbaseSelectForUpdateRecognizerTest { + + private static final String DB_TYPE = "kingbase"; + + @Test + public void testGetSqlType() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseSelectForUpdateRecognizer recognizer = new KingbaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.SELECT_FOR_UPDATE); + } + + + @Test + public void testGetWhereCondition_0() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseSelectForUpdateRecognizer recognizer = new KingbaseSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseSelectForUpdateRecognizer recognizer = new KingbaseSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + + //test for select was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t for update"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.setSelect(null); + new KingbaseSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + + //test for query was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.getSelect().setQuery(null); + new KingbaseSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + } + + @Test + public void testGetTableAlias() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseSelectForUpdateRecognizer recognizer = new KingbaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseSelectForUpdateRecognizer recognizer = new KingbaseSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseUpdateRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseUpdateRecognizerTest.java new file mode 100644 index 00000000000..1196b5a1d5b --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/kingbase/KingbaseUpdateRecognizerTest.java @@ -0,0 +1,155 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.kingbase; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.kingbase.KingbaseUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +public class KingbaseUpdateRecognizerTest { + + private static final String DB_TYPE = "kingbase"; + + @Test + public void testGetSqlType() { + String sql = "update t set n = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.UPDATE); + } + + @Test + public void testGetUpdateColumns() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + List updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + // test with alias + sql = "update t set a.a = ?, a.b = ?, a.c = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + //test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = a"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setColumn(new OracleCursorExpr()); + } + KingbaseUpdateRecognizer kingbaseUpdateRecognizer = new KingbaseUpdateRecognizer(s, sqlUpdateStatement); + kingbaseUpdateRecognizer.getUpdateColumns(); + }); + } + + @Test + public void testGetUpdateValues() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + List updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with values + sql = "update t set a = 1, b = 2, c = 3"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = ?"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement)sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setValue(new OracleCursorExpr()); + } + KingbaseUpdateRecognizer kingbaseUpdateRecognizer = new KingbaseUpdateRecognizer(s, sqlUpdateStatement); + kingbaseUpdateRecognizer.getUpdateValues(); + }); + } + + @Test + public void testGetWhereCondition_0() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + KingbaseUpdateRecognizer recognizer = new KingbaseUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutorTest.java new file mode 100644 index 00000000000..716e0612e47 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoDeleteExecutorTest.java @@ -0,0 +1,97 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.BaseExecutorTest; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class KingbaseUndoDeleteExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQL() { + KingbaseUndoDeleteExecutor executor = upperCase(); + + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("INSERT")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + KingbaseUndoDeleteExecutor executor = upperCase(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } + + private KingbaseUndoDeleteExecutor upperCase() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "2"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "2"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.DELETE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new KingbaseUndoDeleteExecutor(sqlUndoLog); + } + + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutorTest.java new file mode 100644 index 00000000000..b82d7d93957 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoInsertExecutorTest.java @@ -0,0 +1,95 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.BaseExecutorTest; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class KingbaseUndoInsertExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQL() { + KingbaseUndoInsertExecutor executor = upperCase(); + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("DELETE")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + KingbaseUndoInsertExecutor executor = upperCase(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getAfterImage()); + } + + public KingbaseUndoInsertExecutor upperCase() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "1"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "1"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.INSERT); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new KingbaseUndoInsertExecutor(sqlUndoLog); + } + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutorTest.java new file mode 100644 index 00000000000..3516af4dc4c --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/KingbaseUndoUpdateExecutorTest.java @@ -0,0 +1,97 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase; + +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.BaseExecutorTest; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class KingbaseUndoUpdateExecutorTest extends BaseExecutorTest { + + @Test + public void buildUndoSQLByUpperCase() { + KingbaseUndoUpdateExecutor executor = upperCaseSQL(); + + String sql = executor.buildUndoSQL(); + Assertions.assertNotNull(sql); + Assertions.assertTrue(sql.contains("UPDATE")); + Assertions.assertTrue(sql.contains("ID")); + Assertions.assertTrue(sql.contains("AGE")); + Assertions.assertTrue(sql.contains("TABLE_NAME")); + } + + @Test + public void getUndoRows() { + KingbaseUndoUpdateExecutor executor = upperCaseSQL(); + Assertions.assertEquals(executor.getUndoRows(), executor.getSqlUndoLog().getBeforeImage()); + } + + private KingbaseUndoUpdateExecutor upperCaseSQL() { + TableMeta tableMeta = Mockito.mock(TableMeta.class); + Mockito.when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{"ID"})); + Mockito.when(tableMeta.getTableName()).thenReturn("TABLE_NAME"); + + TableRecords beforeImage = new TableRecords(); + beforeImage.setTableName("TABLE_NAME"); + beforeImage.setTableMeta(tableMeta); + List beforeRows = new ArrayList<>(); + Row row0 = new Row(); + addField(row0, "ID", 1, "1"); + addField(row0, "AGE", 1, "1"); + beforeRows.add(row0); + Row row1 = new Row(); + addField(row1, "ID", 1, "1"); + addField(row1, "AGE", 1, "1"); + beforeRows.add(row1); + beforeImage.setRows(beforeRows); + + TableRecords afterImage = new TableRecords(); + afterImage.setTableName("TABLE_NAME"); + afterImage.setTableMeta(tableMeta); + List afterRows = new ArrayList<>(); + Row row2 = new Row(); + addField(row2, "ID", 1, "1"); + addField(row2, "AGE", 1, "1"); + afterRows.add(row2); + Row row3 = new Row(); + addField(row3, "ID", 1, "1"); + addField(row3, "AGE", 1, "1"); + afterRows.add(row3); + afterImage.setRows(afterRows); + + SQLUndoLog sqlUndoLog = new SQLUndoLog(); + sqlUndoLog.setSqlType(SQLType.UPDATE); + sqlUndoLog.setTableMeta(tableMeta); + sqlUndoLog.setTableName("TABLE_NAME"); + sqlUndoLog.setBeforeImage(beforeImage); + sqlUndoLog.setAfterImage(afterImage); + + return new KingbaseUndoUpdateExecutor(sqlUndoLog); + } + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/keyword/KingbaseEscapeHandlerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/keyword/KingbaseEscapeHandlerTest.java new file mode 100644 index 00000000000..caff55b0d8f --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/kingbase/keyword/KingbaseEscapeHandlerTest.java @@ -0,0 +1,34 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.kingbase.keyword; + +import org.apache.seata.sqlparser.EscapeHandler; +import org.apache.seata.sqlparser.EscapeHandlerFactory; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + + +public class KingbaseEscapeHandlerTest { + + @Test + public void testKingbaseKeywordChecker() { + EscapeHandler escapeHandler = EscapeHandlerFactory.getEscapeHandler(JdbcConstants.KINGBASE); + Assertions.assertNotNull(escapeHandler); + } + +} diff --git a/script/client/at/db/kingbase.sql b/script/client/at/db/kingbase.sql new file mode 100644 index 00000000000..d9c94f37caa --- /dev/null +++ b/script/client/at/db/kingbase.sql @@ -0,0 +1,43 @@ +-- +-- 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. +-- + +-- for AT mode you must to init this sql for you business database. the seata server not need it. +CREATE TABLE undo_log +( + id NUMBER(19) NOT NULL, + branch_id NUMBER(19) NOT NULL, + xid VARCHAR2(128) NOT NULL, + context VARCHAR2(128) NOT NULL, + rollback_info BLOB NOT NULL, + log_status NUMBER(10) NOT NULL, + log_created TIMESTAMP(0) NOT NULL, + log_modified TIMESTAMP(0) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT ux_undo_log UNIQUE (xid, branch_id) +); +CREATE INDEX ix_log_created ON undo_log(log_created); +COMMENT ON TABLE undo_log IS 'AT transaction mode undo table'; +COMMENT ON COLUMN undo_log.branch_id is 'branch transaction id'; +COMMENT ON COLUMN undo_log.xid is 'global transaction id'; +COMMENT ON COLUMN undo_log.context is 'undo_log context,such as serialization'; +COMMENT ON COLUMN undo_log.rollback_info is 'rollback info'; +COMMENT ON COLUMN undo_log.log_status is '0:normal status,1:defense status'; +COMMENT ON COLUMN undo_log.log_created is 'create datetime'; +COMMENT ON COLUMN undo_log.log_modified is 'modify datetime'; + +-- Generate ID using sequence and trigger +CREATE SEQUENCE UNDO_LOG_SEQ START WITH 1 INCREMENT BY 1; \ No newline at end of file diff --git a/script/server/db/kingbase.sql b/script/server/db/kingbase.sql new file mode 100644 index 00000000000..797603b7976 --- /dev/null +++ b/script/server/db/kingbase.sql @@ -0,0 +1,96 @@ +-- +-- 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. +-- + +-- -------------------------------- The script used when storeMode is 'db' -------------------------------- +-- the table to store GlobalSession data +CREATE TABLE global_table +( + xid VARCHAR2(128) NOT NULL, + transaction_id NUMBER(19), + status NUMBER(3) NOT NULL, + application_id VARCHAR2(32), + transaction_service_group VARCHAR2(32), + transaction_name VARCHAR2(128), + timeout NUMBER(10), + begin_time NUMBER(19), + application_data VARCHAR2(2000), + gmt_create TIMESTAMP(0), + gmt_modified TIMESTAMP(0), + PRIMARY KEY (xid) +); + +CREATE INDEX idx_status_gmt_modified ON global_table (status, gmt_modified); +CREATE INDEX idx_transaction_id ON global_table (transaction_id); + +-- the table to store BranchSession data +CREATE TABLE branch_table +( + branch_id NUMBER(19) NOT NULL, + xid VARCHAR2(128) NOT NULL, + transaction_id NUMBER(19), + resource_group_id VARCHAR2(32), + resource_id VARCHAR2(256), + branch_type VARCHAR2(8), + status NUMBER(3), + client_id VARCHAR2(64), + application_data VARCHAR2(2000), + gmt_create TIMESTAMP(6), + gmt_modified TIMESTAMP(6), + PRIMARY KEY (branch_id) +); + +CREATE INDEX idx_xid ON branch_table (xid); + +-- the table to store lock data +CREATE TABLE lock_table +( + row_key VARCHAR2(128) NOT NULL, + xid VARCHAR2(128), + transaction_id NUMBER(19), + branch_id NUMBER(19) NOT NULL, + resource_id VARCHAR2(256), + table_name VARCHAR2(32), + pk VARCHAR2(36), + status NUMBER(3) DEFAULT 0 NOT NULL, + gmt_create TIMESTAMP(0), + gmt_modified TIMESTAMP(0), + PRIMARY KEY (row_key) +); + +comment on column lock_table.status is '0:locked ,1:rollbacking'; +CREATE INDEX idx_branch_id ON lock_table (branch_id); +CREATE INDEX idx_lock_table_xid ON lock_table (xid); +CREATE INDEX idx_status ON lock_table (status); + +CREATE TABLE distributed_lock ( + lock_key VARCHAR2(20) NOT NULL, + lock_value VARCHAR2(20) NOT NULL, + expire DECIMAL(18) NOT NULL, + PRIMARY KEY (lock_key) +); + +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0); +INSERT INTO distributed_lock (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0); + +CREATE TABLE vgroup_table +( + vGroup VARCHAR2(255) PRIMARY KEY, + namespace VARCHAR2(255), + cluster VARCHAR2(255) +); \ No newline at end of file diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/BaseKingbaseRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/BaseKingbaseRecognizer.java new file mode 100644 index 00000000000..022be300ab5 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/BaseKingbaseRecognizer.java @@ -0,0 +1,195 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLMergeStatement; +import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectJoin; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectSubqueryTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitor; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.druid.BaseRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * + */ +public abstract class BaseKingbaseRecognizer extends BaseRecognizer { + + /** + * Instantiates a new kingbase base recognizer + * + * @param originalSql the original sql + */ + public BaseKingbaseRecognizer(String originalSql) { + super(originalSql); + } + + public OracleOutputVisitor createOutputVisitor(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList, + final StringBuilder sb) { + + return new OracleOutputVisitor(sb) { + @Override + public boolean visit(SQLVariantRefExpr x) { + if ("?".equals(x.getName())) { + ArrayList oneParamValues = parametersHolder.getParameters().get(x.getIndex() + 1); + if (paramAppenderList.isEmpty()) { + oneParamValues.forEach(t -> paramAppenderList.add(new ArrayList<>())); + } + for (int i = 0; i < oneParamValues.size(); i++) { + Object o = oneParamValues.get(i); + paramAppenderList.get(i).add(o instanceof Null ? null : o); + } + } + return super.visit(x); + } + }; + } + + public String getWhereCondition(SQLExpr where, final ParametersHolder parametersHolder, final ArrayList> paramAppenderList) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, createOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + public String getWhereCondition(SQLExpr where) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, new OracleOutputVisitor(sb)); + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, new OracleOutputVisitor(sb)); + + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy, final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, createOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + @Override + public boolean isSqlSyntaxSupports() { + OracleASTVisitor visitor = new OracleASTVisitorAdapter() { + @Override + public boolean visit(OracleSelectJoin x) { + //just like: UPDATE table a INNER JOIN table b ON a.id = b.pid ... + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + + @Override + public boolean visit(OracleUpdateStatement x) { + if (x.getTableSource() instanceof OracleSelectSubqueryTableSource) { + //just like: "update (select a.id,a.name from a inner join b on a.id = b.id) t set t.name = 'xxx'" + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + List updateSetItems = x.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + if (updateSetItem.getValue() instanceof SQLQueryExpr) { + //just like: "update a set a.id = (select id from b where a.pid = b.pid)" + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + } + return true; + } + + @Override + public boolean visit(SQLInSubQueryExpr x) { + //just like: ...where id in (select id from t) + throw new NotSupportYetException("not support the sql syntax with InSubQuery:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + + @Override + public boolean visit(OracleSelectSubqueryTableSource x) { + //just like: select * from (select * from t) for update + throw new NotSupportYetException("not support the sql syntax with SubQuery:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + + @Override + public boolean visit(SQLReplaceStatement x) { + //just like: replace into t (id,dr) values (1,'2'), (2,'3') + throw new NotSupportYetException("not support the sql syntax with ReplaceStatement:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + + @Override + public boolean visit(SQLMergeStatement x) { + //just like: merge into ... WHEN MATCHED THEN ... + throw new NotSupportYetException("not support the sql syntax with MergeStatement:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + + @Override + public boolean visit(SQLInsertStatement x) { + if (null != x.getQuery()) { + //just like: insert into t select * from t1 + throw new NotSupportYetException("not support the sql syntax insert with query:" + x + + "\nplease see the doc about SQL restrictions https://seata.apache.org/zh-cn/docs/user/sqlreference/dml"); + } + return true; + } + }; + getAst().accept(visitor); + return true; + } + public String getDbType() { + return JdbcConstants.KINGBASE; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseDeleteRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseDeleteRecognizer.java new file mode 100644 index 00000000000..f3b0cf92aed --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseDeleteRecognizer.java @@ -0,0 +1,137 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLDeleteRecognizer; +import org.apache.seata.sqlparser.SQLType; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type kingbase delete recognizer. + * + */ +public class KingbaseDeleteRecognizer extends BaseKingbaseRecognizer implements SQLDeleteRecognizer { + + private final SQLDeleteStatement ast; + + /** + * Instantiates a new My sql delete recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public KingbaseDeleteRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLDeleteStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.DELETE; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of delete with join table"); + } + }; + SQLTableSource tableSource; + if (ast.getFrom() == null) { + tableSource = ast.getTableSource(); + } else { + tableSource = ast.getFrom(); + } + + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of delete with unknow"); + } + return sb.toString(); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //kingbase does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //kingbase does not support order by yet + return null; + } + + @Override + protected SQLStatement getAst() { + return this.ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseInsertRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseInsertRecognizer.java new file mode 100644 index 00000000000..b49ff9e203b --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseInsertRecognizer.java @@ -0,0 +1,163 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; +import com.alibaba.druid.sql.ast.expr.SQLSequenceExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * + */ +public class KingbaseInsertRecognizer extends BaseKingbaseRecognizer implements SQLInsertRecognizer { + + private final SQLInsertStatement ast; + + /** + * Instantiates a new My sql insert recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public KingbaseInsertRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLInsertStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.INSERT; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit(ast.getTableSource()); + return sb.toString(); + } + + @Override + public boolean insertColumnsIsEmpty() { + return CollectionUtils.isEmpty(ast.getColumns()); + } + + @Override + public List getInsertColumns() { + List columnSQLExprs = ast.getColumns(); + if (columnSQLExprs.isEmpty()) { + // INSERT INTO ta VALUES (...), without fields clarified + return null; + } + List list = new ArrayList<>(columnSQLExprs.size()); + for (SQLExpr expr : columnSQLExprs) { + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List> getInsertRows(Collection primaryKeyIndex) { + List valuesClauses = ast.getValuesList(); + List> rows = new ArrayList<>(valuesClauses.size()); + for (SQLInsertStatement.ValuesClause valuesClause : valuesClauses) { + List exprs = valuesClause.getValues(); + List row = new ArrayList<>(exprs.size()); + rows.add(row); + for (int i = 0, len = exprs.size(); i < len; i++) { + SQLExpr expr = exprs.get(i); + if (expr instanceof SQLNullExpr) { + row.add(Null.get()); + } else if (expr instanceof SQLValuableExpr) { + row.add(((SQLValuableExpr) expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + row.add(((SQLVariantRefExpr) expr).getName()); + } else if (expr instanceof SQLMethodInvokeExpr) { + row.add(SqlMethodExpr.get()); + } else if (expr instanceof SQLSequenceExpr) { + SQLSequenceExpr sequenceExpr = (SQLSequenceExpr) expr; + String sequence = sequenceExpr.getSequence().getSimpleName(); + String function = sequenceExpr.getFunction().name; + row.add(new SqlSequenceExpr(sequence, function)); + } else { + if (primaryKeyIndex.contains(i)) { + wrapSQLParsingException(expr); + } + row.add(NotPlaceholderExpr.get()); + } + } + } + return rows; + } + + @Override + public List getInsertParamsValue() { + return null; + } + + @Override + public List getDuplicateKeyUpdate() { + return null; + } + + @Override + public List getInsertColumnsUnEscape() { + List insertColumns = getInsertColumns(); + return ColumnUtils.delEscape(insertColumns, getDbType()); + } + + @Override + protected SQLStatement getAst() { + return this.ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseOperateRecognizerHolder.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseOperateRecognizerHolder.java new file mode 100644 index 00000000000..f51a8f02378 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseOperateRecognizerHolder.java @@ -0,0 +1,56 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The type KingbaseOperateRecognizerHolder + * + * + */ +@LoadLevel(name = JdbcConstants.KINGBASE) +public class KingbaseOperateRecognizerHolder implements SQLOperateRecognizerHolder { + + @Override + public SQLRecognizer getDeleteRecognizer(String sql, SQLStatement ast) { + return new KingbaseDeleteRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getInsertRecognizer(String sql, SQLStatement ast) { + return new KingbaseInsertRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getUpdateRecognizer(String sql, SQLStatement ast) { + return new KingbaseUpdateRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getSelectForUpdateRecognizer(String sql, SQLStatement ast) { + if (((SQLSelectStatement) ast).getSelect().getFirstQueryBlock().isForUpdate()) { + return new KingbaseSelectForUpdateRecognizer(sql, ast); + } + return null; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseSelectForUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseSelectForUpdateRecognizer.java new file mode 100644 index 00000000000..d191e4f628f --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseSelectForUpdateRecognizer.java @@ -0,0 +1,139 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLSelectRecognizer; +import org.apache.seata.sqlparser.SQLType; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type kingbase select for update recognizer. + * + */ +public class KingbaseSelectForUpdateRecognizer extends BaseKingbaseRecognizer implements SQLSelectRecognizer { + + private final SQLSelectStatement ast; + + /** + * Instantiates a new My sql select for update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public KingbaseSelectForUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLSelectStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.SELECT_FOR_UPDATE; + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy); + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy, parametersHolder, paramAppenderList); + } + + private SQLSelectQueryBlock getSelect() { + SQLSelect select = ast.getSelect(); + if (select == null) { + throw new SQLParsingException("should never happen!"); + } + SQLSelectQueryBlock selectQueryBlock = select.getQueryBlock(); + if (selectQueryBlock == null) { + throw new SQLParsingException("should never happen!"); + } + return selectQueryBlock; + } + + @Override + public String getTableAlias() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + return tableSource.getAlias(); + } + + @Override + public String getTableName() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit((SQLExprTableSource)tableSource); + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseUpdateRecognizer.java new file mode 100644 index 00000000000..53ca49a1a88 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/kingbase/KingbaseUpdateRecognizer.java @@ -0,0 +1,184 @@ +/* + * 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 org.apache.seata.sqlparser.druid.kingbase; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.SQLUpdateRecognizer; +import org.apache.seata.sqlparser.util.ColumnUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type kingbase update recognizer. + * + */ +public class KingbaseUpdateRecognizer extends BaseKingbaseRecognizer implements SQLUpdateRecognizer { + + private SQLUpdateStatement ast; + + /** + * Instantiates a new My sql update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public KingbaseUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLUpdateStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.UPDATE; + } + + @Override + public List getUpdateColumns() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getColumn(); + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else if (expr instanceof SQLPropertyExpr) { + // This is alias case, like UPDATE xxx_tbl a SET a.name = ? WHERE a.id = ? + SQLExpr owner = ((SQLPropertyExpr)expr).getOwner(); + if (owner instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)owner).getName() + "." + ((SQLPropertyExpr)expr).getName()); + //This is table Field Full path, like update xxx_database.xxx_tbl set xxx_database.xxx_tbl.xxx_field... + } else if (((SQLPropertyExpr) expr).getOwnerName().split("\\.").length > 1) { + list.add(((SQLPropertyExpr)expr).getOwnerName() + "." + ((SQLPropertyExpr)expr).getName()); + } + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateValues() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getValue(); + if (expr instanceof SQLValuableExpr) { + list.add(((SQLValuableExpr)expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + list.add(new VMarker()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateColumnsUnEscape() { + List updateColumns = getUpdateColumns(); + return ColumnUtils.delEscape(updateColumns, getDbType()); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //kingbase does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //kingbase does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //kingbase does not support order by yet + return null; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of update with join table"); + } + }; + SQLTableSource tableSource = ast.getTableSource(); + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of update with unknow"); + } + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return this.ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder index 78decdb2af3..a30fcf51bf7 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder +++ b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder @@ -20,4 +20,5 @@ org.apache.seata.sqlparser.druid.oracle.OracleOperateRecognizerHolder org.apache.seata.sqlparser.druid.postgresql.PostgresqlOperateRecognizerHolder org.apache.seata.sqlparser.druid.sqlserver.SqlServerOperateRecognizerHolder org.apache.seata.sqlparser.druid.polardbx.PolarDBXOperateRecognizerHolder -org.apache.seata.sqlparser.druid.dm.DmOperateRecognizerHolder \ No newline at end of file +org.apache.seata.sqlparser.druid.dm.DmOperateRecognizerHolder +org.apache.seata.sqlparser.druid.kingbase.KingbaseOperateRecognizerHolder \ No newline at end of file From 851e5de2e4b30d0ac59c847f4c0b2ec91dff2fdd Mon Sep 17 00:00:00 2001 From: qxyuan853 <142065397+qxyuan853@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:48:13 +0800 Subject: [PATCH 06/54] optimize: optimize raftsnapshot read (#6896) --- .../raft/snapshot/RaftSnapshotSerializer.java | 35 ++++++++++++++++--- .../server/raft/RaftSyncMessageTest.java | 12 +++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java index cdf065a1552..1d275920326 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshotSerializer.java @@ -21,8 +21,13 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; +import org.apache.seata.common.exception.ErrorCode; +import org.apache.seata.common.exception.SeataRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,9 +42,19 @@ public class RaftSnapshotSerializer { private static final Logger LOGGER = LoggerFactory.getLogger(RaftSnapshotSerializer.class); + private static final List PERMITS = new ArrayList<>(); + static { + PERMITS.add(RaftSnapshot.class.getName()); + PERMITS.add(RaftSnapshot.SnapshotType.class.getName()); + PERMITS.add(io.seata.server.cluster.raft.snapshot.RaftSnapshot.class.getName()); + PERMITS.add(io.seata.server.cluster.raft.snapshot.RaftSnapshot.SnapshotType.class.getName()); + PERMITS.add(java.lang.Enum.class.getName()); + PERMITS.add("[B"); + } + public static byte[] encode(RaftSnapshot raftSnapshot) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos)) { + ObjectOutputStream oos = new ObjectOutputStream(bos)) { Serializer serializer = EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSnapshot.getCodec()).name()); Optional.ofNullable(raftSnapshot.getBody()).ifPresent(value -> raftSnapshot.setBody( @@ -51,7 +66,7 @@ public static byte[] encode(RaftSnapshot raftSnapshot) throws IOException { public static byte[] encode(io.seata.server.cluster.raft.snapshot.RaftSnapshot raftSnapshot) throws IOException { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos)) { + ObjectOutputStream oos = new ObjectOutputStream(bos)) { Serializer serializer = EnhancedServiceLoader.load(Serializer.class, SerializerType.getByCode(raftSnapshot.getCodec()).name()); Optional.ofNullable(raftSnapshot.getBody()).ifPresent(value -> raftSnapshot.setBody( @@ -63,7 +78,16 @@ public static byte[] encode(io.seata.server.cluster.raft.snapshot.RaftSnapshot r public static RaftSnapshot decode(byte[] raftSnapshotByte) throws IOException { try (ByteArrayInputStream bin = new ByteArrayInputStream(raftSnapshotByte); - ObjectInputStream ois = new ObjectInputStream(bin)) { + ObjectInputStream ois = new ObjectInputStream(bin) { + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + if (!PERMITS.contains(desc.getName())) { + throw new SeataRuntimeException(ErrorCode.ERR_DESERIALIZATION_SECURITY, + "Failed to deserialize object: " + desc.getName() + " is not permitted"); + } + return super.resolveClass(desc); + } + }) { Object object = ois.readObject(); RaftSnapshot raftSnapshot; if (object instanceof io.seata.server.cluster.raft.snapshot.RaftSnapshot) { @@ -83,8 +107,11 @@ public static RaftSnapshot decode(byte[] raftSnapshotByte) throws IOException { .ifPresent(value -> raftSnapshot.setBody(serializer.deserialize(CompressorFactory .getCompressor(raftSnapshot.getCompressor()).decompress((byte[])raftSnapshot.getBody())))); return raftSnapshot; - } catch (ClassNotFoundException e) { + } catch (Exception e) { LOGGER.info("Failed to read raft snapshot: {}", e.getMessage(), e); + if (e instanceof SeataRuntimeException) { + throw (SeataRuntimeException)e; + } throw new IOException(e); } } diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java index 97b919516e1..e6f9c8f905d 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java @@ -116,6 +116,18 @@ public void testMsgSerializeCompatible() throws IOException { Assertions.assertEquals(1234, ((RaftBranchSessionSyncMsg) raftSyncMessageByBranch.getBody()).getBranchSession().getBranchId()); } + @Test + public void testSecuritySnapshotSerialize() throws IOException { + TestSecurity testSecurity = new TestSecurity(); + byte[] bytes; + try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos)) { + oos.writeObject(testSecurity); + bytes = bos.toByteArray(); + } + Assertions.assertThrows(SeataRuntimeException.class,()->RaftSnapshotSerializer.decode(bytes)); + } + @Test public void testSnapshotSerialize() throws IOException, TransactionException { Map sessionMap = new HashMap<>(); From 247c1a00fea606148afd11004297e87df2015b16 Mon Sep 17 00:00:00 2001 From: MaoMaoandSnail <37172584+MaoMaoandSnail@users.noreply.github.com> Date: Mon, 30 Sep 2024 11:01:00 +0800 Subject: [PATCH 07/54] optimize: Correct word spelling errors (#6889) --- changes/en-us/2.x.md | 2 ++ changes/zh-cn/2.x.md | 3 +++ .../saga/engine/impl/ProcessCtrlStateMachineEngine.java | 6 +++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 4bdc421228e..9264873e9ee 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -13,6 +13,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version +- [[#6889](https://github.com/apache/incubator-seata/pull/6889)] Correct word spelling errors ### refactor: @@ -28,6 +29,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 4151bc235a2..c36201c632f 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -12,6 +12,7 @@ - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 +- [[#6889](https://github.com/apache/incubator-seata/pull/6889)] 修正单词拼写错误 ### refactor: @@ -28,8 +29,10 @@ - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) + 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java b/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java index 9d05f5e2749..d18e72f8be8 100644 --- a/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java +++ b/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/impl/ProcessCtrlStateMachineEngine.java @@ -263,7 +263,7 @@ protected StateMachineInstance forwardInternal(String stateMachineInstId, Map concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size()); nullSafeCopy(contextVariables, concurrentContextVariables); @@ -508,7 +508,7 @@ public StateMachineInstance compensateInternal(String stateMachineInstId, Map concurrentContextVariables = new ConcurrentHashMap<>(contextVariables.size()); nullSafeCopy(contextVariables, concurrentContextVariables); @@ -696,7 +696,7 @@ private String buildExceptionMessage(StateMachineInstance stateMachineInstance, return stringBuilder.toString(); } - private void putBusinesskeyToContextariables(StateMachineInstance stateMachineInstance, + private void putBusinessKeyToContextVariables(StateMachineInstance stateMachineInstance, Map contextVariables) { if (StringUtils.hasText(stateMachineInstance.getBusinessKey()) && !contextVariables.containsKey( DomainConstants.VAR_NAME_BUSINESSKEY)) { From 01e9eca9d1cf58b3be4e97f5d51c4daa06c7619f Mon Sep 17 00:00:00 2001 From: Dmitry Kryukov Date: Mon, 30 Sep 2024 09:39:31 +0300 Subject: [PATCH 08/54] optimize: removed write only object (#6883) --- changes/en-us/2.x.md | 3 +++ changes/zh-cn/2.x.md | 4 +++- .../org/apache/seata/spring/tcc/TccAnnotationProcessor.java | 3 --- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 9264873e9ee..2749fcae6f2 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -12,6 +12,7 @@ Add changes here for all PR submitted to the 2.x branch. ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6883](https://github.com/apache/incubator-seata/pull/6874)] remove write only object - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] Correct word spelling errors @@ -29,8 +30,10 @@ Thanks to these contributors for their code commits. Please report an unintended - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) + Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index c36201c632f..4589b3b8c48 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -10,7 +10,8 @@ ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 -- [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6874](https://github.com/apache/incubator-seata/pull/6874)] 修改版本为2.3.0-SNAPSHOT +- [[#6883](https://github.com/apache/incubator-seata/pull/6874)] 删除代码中无用对象的创建 - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] 修正单词拼写错误 @@ -29,6 +30,7 @@ - [slievrly](https://github.com/slievrly) - [GoodBoyCoder](https://github.com/GoodBoyCoder) - [funky-eyes](https://github.com/funky-eyes) +- [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) diff --git a/spring/src/main/java/org/apache/seata/spring/tcc/TccAnnotationProcessor.java b/spring/src/main/java/org/apache/seata/spring/tcc/TccAnnotationProcessor.java index 448167439a7..099c5782090 100644 --- a/spring/src/main/java/org/apache/seata/spring/tcc/TccAnnotationProcessor.java +++ b/spring/src/main/java/org/apache/seata/spring/tcc/TccAnnotationProcessor.java @@ -16,7 +16,6 @@ */ package org.apache.seata.spring.tcc; -import org.apache.seata.integration.tx.api.remoting.RemotingDesc; import org.apache.seata.integration.tx.api.util.ProxyUtil; import org.apache.seata.rm.tcc.api.TwoPhaseBusinessAction; import org.slf4j.Logger; @@ -101,8 +100,6 @@ public void addTccAdvise(Object bean, String beanName, Field field, Class servic } for (Method method : field.getType().getMethods()) { if (!Modifier.isStatic(method.getModifiers()) && (method.isAnnotationPresent(TwoPhaseBusinessAction.class))) { - RemotingDesc remotingDesc = new RemotingDesc(); - remotingDesc.setServiceClass(serviceClass); Object proxyBean = ProxyUtil.createProxy(bean, beanName); field.setAccessible(true); From 0af912523e7d2ec37a34a3ce7e5404254e484ea4 Mon Sep 17 00:00:00 2001 From: jimin Date: Mon, 30 Sep 2024 15:01:17 +0800 Subject: [PATCH 09/54] optimize: upgrade npmjs version in saga module (#6898) --- changes/en-us/2.x.md | 2 + changes/zh-cn/2.x.md | 4 +- .../package-lock.json | 355 ++++++++++-------- 3 files changed, 197 insertions(+), 164 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2749fcae6f2..fcdb6c6523a 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -12,9 +12,11 @@ Add changes here for all PR submitted to the 2.x branch. ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version in console module - [[#6883](https://github.com/apache/incubator-seata/pull/6874)] remove write only object - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] Correct word spelling errors +- [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 4589b3b8c48..016c32c4651 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -10,11 +10,13 @@ ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 +- [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT +- [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 console 模块 npmjs 版本 - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] 修改版本为2.3.0-SNAPSHOT - [[#6883](https://github.com/apache/incubator-seata/pull/6874)] 删除代码中无用对象的创建 - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] 修正单词拼写错误 - +- [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 ### refactor: diff --git a/saga/seata-saga-statemachine-designer/package-lock.json b/saga/seata-saga-statemachine-designer/package-lock.json index 818d953ea07..2cb1f08c0d7 100644 --- a/saga/seata-saga-statemachine-designer/package-lock.json +++ b/saga/seata-saga-statemachine-designer/package-lock.json @@ -2382,30 +2382,10 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", - "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "node_modules/@types/express": { @@ -2562,9 +2542,9 @@ "dev": true }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -2584,9 +2564,9 @@ "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { @@ -2607,15 +2587,15 @@ "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { @@ -2643,28 +2623,28 @@ "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -2672,24 +2652,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -2698,12 +2678,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -2780,10 +2760,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -3186,9 +3166,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -3199,7 +3179,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -3323,14 +3303,19 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3834,17 +3819,20 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -4075,18 +4063,18 @@ "dev": true }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "engines": { "node": ">= 0.8" } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4179,6 +4167,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-iterator-helpers": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz", @@ -5060,37 +5069,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -5260,13 +5269,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -5502,16 +5511,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5682,12 +5695,12 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6935,10 +6948,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "dev": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -6965,12 +6981,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -7689,9 +7705,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", "dev": true }, "node_modules/path-type": { @@ -8012,12 +8028,12 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -8579,9 +8595,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { "debug": "2.6.9", @@ -8617,6 +8633,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -8711,31 +8736,32 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/set-function-length": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", - "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8804,14 +8830,18 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dev": true, "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9577,9 +9607,9 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", @@ -9599,34 +9629,33 @@ } }, "node_modules/webpack": { - "version": "5.89.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", - "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { From f83b6eabb8e33d065cf4e43e247d7f4b4c30de08 Mon Sep 17 00:00:00 2001 From: yiqi <77573225+PleaseGiveMeTheCoke@users.noreply.github.com> Date: Sat, 5 Oct 2024 13:14:21 +0800 Subject: [PATCH 10/54] feature: support grpc protocol (#6881) --- changes/en-us/2.x.md | 2 + changes/zh-cn/2.x.md | 3 +- .../seata/common/ConfigurationKeys.java | 2 + .../apache/seata/common/DefaultValues.java | 1 + core/pom.xml | 21 ++ .../apache/seata/core/protocol/Protocol.java | 39 ++++ .../core/protocol/detector/Http2Detector.java | 66 +++++++ .../protocol/detector/ProtocolDetector.java | 26 +++ .../core/protocol/detector/SeataDetector.java | 50 +++++ .../core/rpc/netty/NettyClientBootstrap.java | 50 ++++- .../core/rpc/netty/NettyClientConfig.java | 5 + .../core/rpc/netty/NettyServerBootstrap.java | 10 +- .../core/rpc/netty/ProtocolDetectHandler.java | 62 ++++++ .../core/rpc/netty/grpc/GrpcDecoder.java | 118 ++++++++++++ .../core/rpc/netty/grpc/GrpcEncoder.java | 99 ++++++++++ .../core/rpc/netty/grpc/GrpcHeaderEnum.java | 49 +++++ .../core/rpc/netty/v1/ProtocolEncoderV1.java | 1 - .../serializer/SerializerServiceLoader.java | 28 ++- .../protocol/transcation/grpcMessage.proto | 32 ++++ dependencies/pom.xml | 6 +- script/client/conf/file.conf | 2 + script/client/spring/application.properties | 1 + script/client/spring/application.yml | 1 + script/config-center/config.txt | 1 + .../properties/TransportProperties.java | 11 ++ test/pom.xml | 26 +++ .../core/rpc/netty/mockserver/GrpcTest.java | 180 ++++++++++++++++++ .../netty/mockserver/MockGrpcServerTest.java | 120 ++++++++++++ .../protocol/transcation/grpcMessage.proto | 32 ++++ 29 files changed, 1026 insertions(+), 18 deletions(-) create mode 100644 core/src/main/java/org/apache/seata/core/protocol/Protocol.java create mode 100644 core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java create mode 100644 core/src/main/java/org/apache/seata/core/protocol/detector/ProtocolDetector.java create mode 100644 core/src/main/java/org/apache/seata/core/protocol/detector/SeataDetector.java create mode 100644 core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDetectHandler.java create mode 100644 core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java create mode 100644 core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java create mode 100644 core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcHeaderEnum.java create mode 100644 core/src/main/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto create mode 100644 test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java create mode 100644 test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java create mode 100644 test/src/test/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index fcdb6c6523a..2dd147c01b5 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -5,6 +5,7 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: - [[#6876](https://github.com/apache/incubator-seata/pull/6876)]support kingbase +- [[#6881](https://github.com/apache/incubator-seata/pull/6881)]support grpc ### bugfix: @@ -35,6 +36,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) +- [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 016c32c4651..624db67f904 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -4,6 +4,7 @@ ### feature: [[#6876](https://github.com/apache/incubator-seata/pull/6876)]支持人大金仓数据库(kingbase) +[[#6881](https://github.com/apache/incubator-seata/pull/6881)]全链路支持grpc ### bugfix: @@ -35,7 +36,7 @@ - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) - +- [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index dec76f51b7c..ff8436b6dc9 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -628,6 +628,8 @@ public interface ConfigurationKeys { @Deprecated String ENABLE_CLIENT_BATCH_SEND_REQUEST = TRANSPORT_PREFIX + "enableClientBatchSendRequest"; + String TRANSPORT_PROTOCOL = TRANSPORT_PREFIX + "protocol"; + /** * The constant ENABLE_TM_CLIENT_BATCH_SEND_REQUEST */ diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index 8c484a1ab0f..eb0d40bb308 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -63,6 +63,7 @@ public interface DefaultValues { String DEFAULT_BOSS_THREAD_PREFIX = "NettyBoss"; String DEFAULT_NIO_WORKER_THREAD_PREFIX = "NettyServerNIOWorker"; String DEFAULT_EXECUTOR_THREAD_PREFIX = "NettyServerBizHandler"; + String DEFAULT_PROTOCOL = "seata"; boolean DEFAULT_TRANSPORT_HEARTBEAT = true; boolean DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION = true; diff --git a/core/pom.xml b/core/pom.xml index 9de6107bc03..26ce3dc9018 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -69,6 +69,10 @@ fastjson test + + com.google.protobuf + protobuf-java + @@ -90,6 +94,23 @@ + + org.xolstice.maven.plugins + protobuf-maven-plugin + + ${project.basedir}/src/main/resources/protobuf/org/apache/seata/protocol/transcation/ + + com.google.protobuf:protoc:3.25.4:exe:${os.detected.classifier} + + + + + + compile + + + + diff --git a/core/src/main/java/org/apache/seata/core/protocol/Protocol.java b/core/src/main/java/org/apache/seata/core/protocol/Protocol.java new file mode 100644 index 00000000000..fe3cc000cfc --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/Protocol.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.seata.core.protocol; + +/** + * seata transport protocol + */ +public enum Protocol { + + /** + * grpc + */ + GPRC("grpc"), + + /** + * seata + */ + SEATA("seata"); + + public final String value; + + Protocol(String value) { + this.value = value; + } +} diff --git a/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java b/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java new file mode 100644 index 00000000000..a004894f9b1 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/detector/Http2Detector.java @@ -0,0 +1,66 @@ +/* + * 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 org.apache.seata.core.protocol.detector; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.util.CharsetUtil; +import org.apache.seata.core.rpc.netty.grpc.GrpcDecoder; +import org.apache.seata.core.rpc.netty.grpc.GrpcEncoder; + +public class Http2Detector implements ProtocolDetector { + private static final byte[] HTTP2_PREFIX_BYTES = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(CharsetUtil.UTF_8); + private ChannelHandler[] serverHandlers; + + public Http2Detector(ChannelHandler[] serverHandlers) { + this.serverHandlers = serverHandlers; + } + + @Override + public boolean detect(ByteBuf in) { + if (in.readableBytes() < HTTP2_PREFIX_BYTES.length) { + return false; + } + for (int i = 0; i < HTTP2_PREFIX_BYTES.length; i++) { + if (in.getByte(i) != HTTP2_PREFIX_BYTES[i]) { + return false; + } + } + return true; + } + + @Override + public ChannelHandler[] getHandlers() { + return new ChannelHandler[]{ + Http2FrameCodecBuilder.forServer().build(), + new Http2MultiplexHandler(new ChannelInitializer() { + @Override + protected void initChannel(Http2StreamChannel ch) { + final ChannelPipeline p = ch.pipeline(); + p.addLast(new GrpcDecoder()); + p.addLast(new GrpcEncoder()); + p.addLast(serverHandlers); + } + }) + }; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/seata/core/protocol/detector/ProtocolDetector.java b/core/src/main/java/org/apache/seata/core/protocol/detector/ProtocolDetector.java new file mode 100644 index 00000000000..89d5d10e7be --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/detector/ProtocolDetector.java @@ -0,0 +1,26 @@ +/* + * 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 org.apache.seata.core.protocol.detector; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; + +public interface ProtocolDetector { + boolean detect(ByteBuf in); + + ChannelHandler[] getHandlers(); +} diff --git a/core/src/main/java/org/apache/seata/core/protocol/detector/SeataDetector.java b/core/src/main/java/org/apache/seata/core/protocol/detector/SeataDetector.java new file mode 100644 index 00000000000..b9c30b0bc6d --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/protocol/detector/SeataDetector.java @@ -0,0 +1,50 @@ +/* + * 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 org.apache.seata.core.protocol.detector; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import org.apache.seata.core.rpc.netty.MultiProtocolDecoder; + +public class SeataDetector implements ProtocolDetector { + private static final byte[] MAGIC_CODE_BYTES = {(byte) 0xda, (byte) 0xda}; + private ChannelHandler[] serverHandlers; + + public SeataDetector(ChannelHandler[] serverHandlers) { + this.serverHandlers = serverHandlers; + } + + @Override + public boolean detect(ByteBuf in) { + if (in.readableBytes() < MAGIC_CODE_BYTES.length) { + return false; + } + for (int i = 0; i < MAGIC_CODE_BYTES.length; i++) { + if (in.getByte(i) != MAGIC_CODE_BYTES[i]) { + return false; + } + } + return true; + } + + @Override + public ChannelHandler[] getHandlers() { + MultiProtocolDecoder multiProtocolDecoder = new MultiProtocolDecoder(serverHandlers); + + return new ChannelHandler[]{multiProtocolDecoder}; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java index 4aaafc0acb0..0fbd9ff0795 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java @@ -18,8 +18,11 @@ import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; +import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; @@ -28,13 +31,19 @@ import io.netty.channel.epoll.EpollMode; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http2.Http2FrameCodecBuilder; +import io.netty.handler.codec.http2.Http2MultiplexHandler; +import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; import io.netty.util.internal.PlatformDependent; import org.apache.seata.common.exception.FrameworkException; import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.core.protocol.Protocol; import org.apache.seata.core.rpc.RemotingBootstrap; +import org.apache.seata.core.rpc.netty.grpc.GrpcDecoder; +import org.apache.seata.core.rpc.netty.grpc.GrpcEncoder; import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1; import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1; import org.slf4j.Logger; @@ -130,14 +139,18 @@ public void start() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); - pipeline - .addLast(new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(), - nettyClientConfig.getChannelMaxWriteIdleSeconds(), - nettyClientConfig.getChannelMaxAllIdleSeconds())) - .addLast(new ProtocolDecoderV1()) - .addLast(new ProtocolEncoderV1()); - if (channelHandlers != null) { - addChannelPipelineLast(ch, channelHandlers); + if (nettyClientConfig.getProtocol().equals(Protocol.GPRC.value)) { + pipeline.addLast(Http2FrameCodecBuilder.forClient().build()) + .addLast(new Http2MultiplexHandler(new ChannelDuplexHandler())); + } else { + pipeline.addLast(new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(), + nettyClientConfig.getChannelMaxWriteIdleSeconds(), + nettyClientConfig.getChannelMaxAllIdleSeconds())); + pipeline.addLast(new ProtocolDecoderV1()) + .addLast(new ProtocolEncoderV1()); + if (channelHandlers != null) { + addChannelPipelineLast(ch, channelHandlers); + } } } }); @@ -177,9 +190,30 @@ public Channel getNewChannel(InetSocketAddress address) { } else { channel = f.channel(); } + + if (nettyClientConfig.getProtocol().equals(Protocol.GPRC.value)) { + Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(channel); + bootstrap.handler(new ChannelInboundHandlerAdapter() { + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + Channel channel = ctx.channel(); + channel.pipeline().addLast(new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(), + nettyClientConfig.getChannelMaxWriteIdleSeconds(), + nettyClientConfig.getChannelMaxAllIdleSeconds())); + channel.pipeline().addLast(new GrpcDecoder()); + channel.pipeline().addLast(new GrpcEncoder()); + if (channelHandlers != null) { + addChannelPipelineLast(channel, channelHandlers); + } + } + }); + channel = bootstrap.open().get(); + } + } catch (Exception e) { throw new FrameworkException(e, "can not connect to services-server."); } + return channel; } diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientConfig.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientConfig.java index f0e047ad58d..68583608262 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientConfig.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientConfig.java @@ -21,6 +21,7 @@ import org.apache.seata.core.rpc.TransportServerType; import static org.apache.seata.common.DefaultValues.DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST; +import static org.apache.seata.common.DefaultValues.DEFAULT_PROTOCOL; import static org.apache.seata.common.DefaultValues.DEFAULT_RPC_RM_REQUEST_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_RPC_TM_REQUEST_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_SELECTOR_THREAD_PREFIX; @@ -451,6 +452,10 @@ public String getRmDispatchThreadPrefix() { return RPC_DISPATCH_THREAD_PREFIX + "_" + NettyPoolKey.TransactionRole.RMROLE.name(); } + public String getProtocol() { + return CONFIG.getConfig(org.apache.seata.common.ConfigurationKeys.TRANSPORT_PROTOCOL, DEFAULT_PROTOCOL); + } + @Deprecated public static boolean isEnableClientBatchSendRequest() { return ENABLE_CLIENT_BATCH_SEND_REQUEST; diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyServerBootstrap.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyServerBootstrap.java index c7b2aa57c21..b589396e5ab 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyServerBootstrap.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyServerBootstrap.java @@ -92,6 +92,10 @@ protected void setChannelHandlers(final ChannelHandler... handlers) { } } + protected ChannelHandler[] getChannelHandlers() { + return channelHandlers; + } + /** * Add channel pipeline last. * @@ -158,10 +162,8 @@ public void start() { .childHandler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) { - MultiProtocolDecoder multiProtocolDecoder = new MultiProtocolDecoder(channelHandlers); - ch.pipeline() - .addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0)) - .addLast(multiProtocolDecoder); + ch.pipeline().addLast(new IdleStateHandler(nettyServerConfig.getChannelMaxReadIdleSeconds(), 0, 0)) + .addLast(new ProtocolDetectHandler(NettyServerBootstrap.this)); } }); diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDetectHandler.java b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDetectHandler.java new file mode 100644 index 00000000000..9f1b5f8c113 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDetectHandler.java @@ -0,0 +1,62 @@ +/* + * 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 org.apache.seata.core.rpc.netty; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; +import org.apache.seata.core.protocol.detector.Http2Detector; +import org.apache.seata.core.protocol.detector.ProtocolDetector; +import org.apache.seata.core.protocol.detector.SeataDetector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class ProtocolDetectHandler extends ByteToMessageDecoder { + private static final Logger LOGGER = LoggerFactory.getLogger(ProtocolDetectHandler.class); + private NettyServerBootstrap nettyServerBootstrap; + private ProtocolDetector[] supportedProtocolDetectors; + + public ProtocolDetectHandler(NettyServerBootstrap nettyServerBootstrap) { + this.nettyServerBootstrap = nettyServerBootstrap; + this.supportedProtocolDetectors = new ProtocolDetector[]{new Http2Detector(nettyServerBootstrap.getChannelHandlers()), new SeataDetector(nettyServerBootstrap.getChannelHandlers())}; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + for (ProtocolDetector protocolDetector : supportedProtocolDetectors) { + if (protocolDetector.detect(in)) { + ChannelHandler[] protocolHandlers = protocolDetector.getHandlers(); + ctx.pipeline().addLast(protocolHandlers); + ctx.pipeline().remove(this); + + in.resetReaderIndex(); + return; + } + + in.resetReaderIndex(); + } + + byte[] preface = new byte[in.readableBytes()]; + in.readBytes(preface); + LOGGER.error("Can not recognize protocol from remote {}, preface = {}", ctx.channel().remoteAddress(), preface); + in.clear(); + ctx.close(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java new file mode 100644 index 00000000000..71c9caf8be9 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java @@ -0,0 +1,118 @@ +/* + * 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 org.apache.seata.core.rpc.netty.grpc; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.Http2DataFrame; +import io.netty.handler.codec.http2.Http2HeadersFrame; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; +import org.apache.commons.lang.StringUtils; +import org.apache.seata.core.compressor.Compressor; +import org.apache.seata.core.compressor.CompressorFactory; +import org.apache.seata.core.protocol.HeartbeatMessage; +import org.apache.seata.core.protocol.ProtocolConstants; +import org.apache.seata.core.protocol.RpcMessage; +import org.apache.seata.core.protocol.generated.GrpcMessageProto; +import org.apache.seata.core.serializer.Serializer; +import org.apache.seata.core.serializer.SerializerServiceLoader; +import org.apache.seata.core.serializer.SerializerType; + +import java.util.Map; + +public class GrpcDecoder extends ChannelDuplexHandler { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof Http2HeadersFrame) { + onHeadersRead(ctx, (Http2HeadersFrame) msg); + } else if (msg instanceof Http2DataFrame) { + onDataRead(ctx, (Http2DataFrame) msg); + } else if (msg instanceof ReferenceCounted) { + ReferenceCountUtil.release(msg); + } + } + + public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exception { + ByteBuf content = msg.content(); + try { + int readableBytes = content.readableBytes(); + byte[] bytes = new byte[readableBytes]; + content.readBytes(bytes); + if (bytes.length < 5) { + return; + } + + int srcPos = 0; + while (srcPos < readableBytes) { + // The first byte defaults to 0, indicating that no decompression is required + // Read the value of the next four bytes as the length of the body + int length = ((bytes[srcPos + 1] & 0xFF) << 24) | ((bytes[srcPos + 2] & 0xFF) << 16) + | ((bytes[srcPos + 3] & 0xFF) << 8) | (bytes[srcPos + 4] & 0xFF); + + byte[] data = new byte[length]; + System.arraycopy(bytes, srcPos + 5, data, 0, length); + GrpcMessageProto grpcMessageProto = GrpcMessageProto.parseFrom(data); + byte[] bodyBytes = grpcMessageProto.getBody().toByteArray(); + int messageType = grpcMessageProto.getMessageType(); + int messageId = grpcMessageProto.getId(); + Map headMap = grpcMessageProto.getHeadMapMap(); + + RpcMessage rpcMsg = new RpcMessage(); + if (messageType <= Byte.MAX_VALUE && messageType >= Byte.MIN_VALUE) { + rpcMsg.setMessageType((byte) messageType); + } + rpcMsg.setId(messageId); + rpcMsg.setHeadMap(grpcMessageProto.getHeadMapMap()); + + if (messageType == ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST) { + rpcMsg.setBody(HeartbeatMessage.PING); + } else if (messageType == ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) { + rpcMsg.setBody(HeartbeatMessage.PONG); + } else { + String compressType = headMap.get(GrpcHeaderEnum.COMPRESS_TYPE.header); + if (StringUtils.isNotBlank(compressType)) { + byte compress = Byte.parseByte(compressType); + rpcMsg.setCompressor(compress); + Compressor compressor = CompressorFactory.getCompressor(compress); + bodyBytes = compressor.decompress(bodyBytes); + } + String codecValue = headMap.get(GrpcHeaderEnum.CODEC_TYPE.header); + int codec = Integer.parseInt(codecValue); + SerializerType serializerType = SerializerType.getByCode(codec); + rpcMsg.setCodec(serializerType.getCode()); + Serializer serializer = SerializerServiceLoader.load(serializerType); + Object messageBody = serializer.deserialize(bodyBytes); + rpcMsg.setBody(messageBody); + } + + ctx.fireChannelRead(rpcMsg); + + srcPos += length + 5; + } + } finally { + ReferenceCountUtil.release(content); + } + } + + + public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame headersFrame) throws Exception { + // TODO Subsequent decompression logic is possible + } +} diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java new file mode 100644 index 00000000000..dbbbfe1be48 --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java @@ -0,0 +1,99 @@ +/* + * 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 org.apache.seata.core.rpc.netty.grpc; + +import com.google.protobuf.ByteString; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2Headers; +import org.apache.seata.core.compressor.Compressor; +import org.apache.seata.core.compressor.CompressorFactory; +import org.apache.seata.core.protocol.ProtocolConstants; +import org.apache.seata.core.protocol.RpcMessage; +import org.apache.seata.core.protocol.generated.GrpcMessageProto; +import org.apache.seata.core.serializer.Serializer; +import org.apache.seata.core.serializer.SerializerServiceLoader; +import org.apache.seata.core.serializer.SerializerType; + +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class GrpcEncoder extends ChannelOutboundHandlerAdapter { + private final AtomicBoolean headerSent = new AtomicBoolean(false); + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof RpcMessage)) { + throw new UnsupportedOperationException("GrpcEncoder not support class:" + msg.getClass()); + } + + RpcMessage rpcMessage = (RpcMessage) msg; + byte messageType = rpcMessage.getMessageType(); + Map headMap = rpcMessage.getHeadMap(); + Object body = rpcMessage.getBody(); + int id = rpcMessage.getId(); + + if (headerSent.compareAndSet(false, true)) { + Http2Headers headers = new DefaultHttp2Headers(); + headers.add(GrpcHeaderEnum.HTTP2_STATUS.header, String.valueOf(200)); + headers.add(GrpcHeaderEnum.GRPC_STATUS.header, String.valueOf(0)); + headers.add(GrpcHeaderEnum.GRPC_CONTENT_TYPE.header, "application/grpc"); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(headers)); + } + + ByteString dataBytes; + if (messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST + && messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) { + Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(SerializerType.PROTOBUF.getCode())); + byte[] serializedBytes = serializer.serialize(body); + Compressor compressor = CompressorFactory.getCompressor(rpcMessage.getCompressor()); + dataBytes = ByteString.copyFrom(compressor.compress(serializedBytes)); + } else { + dataBytes = ByteString.EMPTY; + } + headMap.put(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.PROTOBUF.getCode())); + headMap.put(GrpcHeaderEnum.COMPRESS_TYPE.header, String.valueOf(rpcMessage.getCompressor())); + GrpcMessageProto.Builder builder = GrpcMessageProto.newBuilder() + .putAllHeadMap(headMap) + .setMessageType(messageType) + .setId(id); + builder.setBody(ByteString.copyFrom(dataBytes.toByteArray())); + GrpcMessageProto grpcMessageProto = builder.build(); + + byte[] bodyBytes = grpcMessageProto.toByteArray(); + if (bodyBytes != null) { + byte[] messageWithPrefix = new byte[bodyBytes.length + 5]; + // The first byte is 0, indicating no compression + messageWithPrefix[0] = 0; + ByteBuffer buffer = ByteBuffer.allocate(4); + buffer.putInt(bodyBytes.length); + byte[] lengthBytes = buffer.array(); + // The last four bytes indicate the length + System.arraycopy(lengthBytes, 0, messageWithPrefix, 1, 4); + // The remaining bytes are body + System.arraycopy(bodyBytes, 0, messageWithPrefix, 5, bodyBytes.length); + ctx.writeAndFlush(new DefaultHttp2DataFrame(Unpooled.wrappedBuffer(messageWithPrefix))); + } + } + +} diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcHeaderEnum.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcHeaderEnum.java new file mode 100644 index 00000000000..ed8e729b47d --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcHeaderEnum.java @@ -0,0 +1,49 @@ +/* + * 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 org.apache.seata.core.rpc.netty.grpc; + +public enum GrpcHeaderEnum { + + /** + * grpc status + */ + GRPC_STATUS("grpc-status"), + /** + * http2 status + */ + HTTP2_STATUS(":status"), + /** + * content-type + */ + GRPC_CONTENT_TYPE("content-type"), + + /** + * codec-type + */ + CODEC_TYPE("codec-type"), + + /** + * compress-type + */ + COMPRESS_TYPE("compress-type"); + + public final String header; + + GrpcHeaderEnum(String header) { + this.header = header; + } +} diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java index dd01b948dba..39180f3bdc0 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java @@ -66,7 +66,6 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder implements ProtocolE public void encode(RpcMessage message, ByteBuf out) { try { - ProtocolRpcMessageV1 rpcMessage = new ProtocolRpcMessageV1(); rpcMessage.rpcMsg2ProtocolMsg(message); diff --git a/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java b/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java index 63ce440edd1..0aa9bd340e8 100644 --- a/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java +++ b/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java @@ -72,8 +72,7 @@ public static Serializer load(SerializerType type, byte version) throws Enhanced "Please manually reference 'org.apache.seata:seata-serializer-protobuf' dependency."); } - - String key = serialzerKey(type, version); + String key = serializerKey(type, version); Serializer serializer = SERIALIZER_MAP.get(key); if (serializer == null) { if (type == SerializerType.SEATA) { @@ -86,7 +85,30 @@ public static Serializer load(SerializerType type, byte version) throws Enhanced return serializer; } - private static String serialzerKey(SerializerType type, byte version) { + /** + * Load the service of {@link Serializer} + * + * @param type the serializer type + * @return the service of {@link Serializer} + * @throws EnhancedServiceNotFoundException the enhanced service not found exception + */ + public static Serializer load(SerializerType type) throws EnhancedServiceNotFoundException { + if (type == SerializerType.PROTOBUF && !CONTAINS_PROTOBUF_DEPENDENCY) { + throw new EnhancedServiceNotFoundException("The class '" + PROTOBUF_SERIALIZER_CLASS_NAME + "' not found. " + + "Please manually reference 'org.apache.seata:seata-serializer-protobuf' dependency."); + } + + String key = type.name(); + Serializer serializer = SERIALIZER_MAP.get(key); + if (serializer == null) { + serializer = EnhancedServiceLoader.load(Serializer.class, type.name()); + + SERIALIZER_MAP.put(key, serializer); + } + return serializer; + } + + private static String serializerKey(SerializerType type, byte version) { if (type == SerializerType.SEATA) { return type.name() + version; } diff --git a/core/src/main/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto b/core/src/main/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto new file mode 100644 index 00000000000..dd61bd95f48 --- /dev/null +++ b/core/src/main/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ +syntax = "proto3"; +package org.apache.seata.protocol.protobuf; +option java_multiple_files = true; +option java_outer_classname = "GrpcMessage"; +option java_package = "org.apache.seata.core.protocol.generated"; + +message GrpcMessageProto { + int32 id = 1; + int32 messageType = 2; + map headMap = 3; + bytes body = 4; +} + +service SeataService { + rpc sendRequest (stream GrpcMessageProto) returns (stream GrpcMessageProto); +} \ No newline at end of file diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 4bf0450d4f3..15178f14166 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -76,7 +76,6 @@ 4.1.101.Final 4.0.3 1.6.7 - 3.25.4 1.66.0 5.4.0 @@ -623,6 +622,11 @@ grpc-core ${grpc.version} + + io.grpc + grpc-alts + ${grpc.version} + io.grpc grpc-api diff --git a/script/client/conf/file.conf b/script/client/conf/file.conf index 92c836e17a6..4b121660922 100644 --- a/script/client/conf/file.conf +++ b/script/client/conf/file.conf @@ -16,6 +16,8 @@ # transport { + # communication protocols, seata or grpc, default seata + protocol = "seata" # tcp, unix-domain-socket type = "TCP" #NIO, NATIVE diff --git a/script/client/spring/application.properties b/script/client/spring/application.properties index cb7d93c2ab7..2a72d1e5f79 100755 --- a/script/client/spring/application.properties +++ b/script/client/spring/application.properties @@ -64,6 +64,7 @@ seata.log.exception-rate=100 seata.service.vgroup-mapping.default_tx_group=default seata.service.grouplist.default=127.0.0.1:8091 seata.service.disable-global-transaction=false +seata.transport.protocol=seata seata.transport.shutdown.wait=3 seata.transport.thread-factory.boss-thread-prefix=NettyBoss seata.transport.thread-factory.worker-thread-prefix=NettyServerNIOWorker diff --git a/script/client/spring/application.yml b/script/client/spring/application.yml index 9eef693d332..a6100f05740 100755 --- a/script/client/spring/application.yml +++ b/script/client/spring/application.yml @@ -73,6 +73,7 @@ seata: default: 127.0.0.1:8091 disable-global-transaction: false transport: + protocol: seata shutdown: wait: 3 thread-factory: diff --git a/script/config-center/config.txt b/script/config-center/config.txt index 99cd7bd1313..8cf986f3f94 100644 --- a/script/config-center/config.txt +++ b/script/config-center/config.txt @@ -17,6 +17,7 @@ #For details about configuration items, see https://seata.apache.org/zh-cn/docs/user/configurations #Transport configuration, for client and server +transport.protocol=seata transport.type=TCP transport.server=NIO transport.heartbeat=true diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/TransportProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/TransportProperties.java index 8b4caa4e8f6..64f38a385d0 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/TransportProperties.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/TransportProperties.java @@ -23,6 +23,7 @@ import static org.apache.seata.common.DefaultValues.DEFAULT_ENABLE_RM_CLIENT_BATCH_SEND_REQUEST; import static org.apache.seata.common.DefaultValues.DEFAULT_ENABLE_TC_SERVER_BATCH_SEND_RESPONSE; import static org.apache.seata.common.DefaultValues.DEFAULT_ENABLE_TM_CLIENT_BATCH_SEND_REQUEST; +import static org.apache.seata.common.DefaultValues.DEFAULT_PROTOCOL; import static org.apache.seata.common.DefaultValues.DEFAULT_RPC_RM_REQUEST_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_RPC_TC_REQUEST_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_RPC_TM_REQUEST_TIMEOUT; @@ -54,6 +55,8 @@ public class TransportProperties { */ private String compressor = "none"; + private String protocol = DEFAULT_PROTOCOL; + /** * enable client batch send request */ @@ -193,4 +196,12 @@ public long getRpcTcRequestTimeout() { public void setRpcTcRequestTimeout(long rpcTcRequestTimeout) { this.rpcTcRequestTimeout = rpcTcRequestTimeout; } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } } diff --git a/test/pom.xml b/test/pom.xml index 91d4c9f60fb..d35f25bad5e 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -40,6 +40,28 @@ true + + org.xolstice.maven.plugins + protobuf-maven-plugin + + ${project.basedir}/src/test/resources/protobuf/org/apache/seata/protocol/transcation/ + + com.google.protobuf:protoc:3.25.4:exe:${os.detected.classifier} + + grpc-java + + io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + + + + + + compile + compile-custom + + + + @@ -49,6 +71,10 @@ seata-tm ${project.version} + + io.grpc + grpc-alts + diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java new file mode 100644 index 00000000000..0d63d2eb70f --- /dev/null +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java @@ -0,0 +1,180 @@ +/* + * 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 org.apache.seata.core.rpc.netty.mockserver; + +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import io.grpc.stub.StreamObserver; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.ConfigurationTestHelper; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.protocol.generated.GrpcMessageProto; +import org.apache.seata.core.rpc.netty.RmNettyRemotingClient; +import org.apache.seata.core.rpc.netty.TmNettyRemotingClient; +import org.apache.seata.mockserver.MockServer; +import org.apache.seata.serializer.protobuf.generated.*; +import org.apache.seata.core.protocol.generated.SeataServiceGrpc; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class GrpcTest { + + private static ManagedChannel channel; + + private static SeataServiceGrpc.SeataServiceStub seataServiceStub; + + @BeforeAll + public static void before() { + ConfigurationFactory.reload(); + ConfigurationTestHelper.putConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL, String.valueOf(ProtocolTestConstants.MOCK_SERVER_PORT)); + MockServer.start(ProtocolTestConstants.MOCK_SERVER_PORT); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + + channel = ManagedChannelBuilder.forAddress("127.0.0.1", ProtocolTestConstants.MOCK_SERVER_PORT).usePlaintext().build(); + seataServiceStub = SeataServiceGrpc.newStub(channel); + } + + @AfterAll + public static void after() { + //MockServer.close(); + ConfigurationTestHelper.removeConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + } + + private GrpcMessageProto getRegisterTMRequest() { + AbstractIdentifyRequestProto abstractIdentifyRequestProto = AbstractIdentifyRequestProto.newBuilder() + .setApplicationId("test-applicationId") + .build(); + RegisterTMRequestProto registerTMRequestProto = RegisterTMRequestProto.newBuilder() + .setAbstractIdentifyRequest(abstractIdentifyRequestProto) + .build(); + + return GrpcMessageProto.newBuilder().setBody(registerTMRequestProto.toByteString()).build(); + } + + private GrpcMessageProto getGlobalBeginRequest() { + GlobalBeginRequestProto globalBeginRequestProto = GlobalBeginRequestProto.newBuilder() + .setTransactionName("test-transaction") + .setTimeout(2000) + .build(); + return GrpcMessageProto.newBuilder().setBody(globalBeginRequestProto.toByteString()).build(); + } + + private GrpcMessageProto getBranchRegisterRequest() { + BranchRegisterRequestProto branchRegisterRequestProto = BranchRegisterRequestProto.newBuilder() + .setXid("1") + .setLockKey("1") + .setResourceId("test-resource") + .setBranchType(BranchTypeProto.TCC) + .setApplicationData("{\"mock\":\"mock\"}") + .build(); + + return GrpcMessageProto.newBuilder().setBody(branchRegisterRequestProto.toByteString()).build(); + } + + private GrpcMessageProto getGlobalCommitRequest() { + AbstractGlobalEndRequestProto globalEndRequestProto = AbstractGlobalEndRequestProto.newBuilder() + .setXid("1") + .build(); + GlobalCommitRequestProto globalCommitRequestProto = GlobalCommitRequestProto.newBuilder() + .setAbstractGlobalEndRequest(globalEndRequestProto) + .build(); + + return GrpcMessageProto.newBuilder().setBody(globalCommitRequestProto.toByteString()).build(); + } + + private GrpcMessageProto getGlobalRollbackRequest() { + AbstractGlobalEndRequestProto globalEndRequestProto = AbstractGlobalEndRequestProto.newBuilder() + .setXid("1") + .build(); + GlobalRollbackRequestProto globalRollbackRequestProto = GlobalRollbackRequestProto.newBuilder() + .setAbstractGlobalEndRequest(globalEndRequestProto) + .build(); + + return GrpcMessageProto.newBuilder().setBody(globalRollbackRequestProto.toByteString()).build(); + } + + @Test + public void testCommit() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(4); + StreamObserver streamObserver = new StreamObserver() { + @Override + public void onNext(GrpcMessageProto grpcMessageProto) { + System.out.println("receive : " + grpcMessageProto.toString()); + countDownLatch.countDown(); + } + + @Override + public void onError(Throwable throwable) { + throwable.printStackTrace(); + } + + @Override + public void onCompleted() { + + } + }; + + StreamObserver response = seataServiceStub.sendRequest(streamObserver); + response.onNext(getRegisterTMRequest()); + response.onNext(getGlobalBeginRequest()); + response.onNext(getBranchRegisterRequest()); + response.onNext(getGlobalCommitRequest()); + + response.onCompleted(); + + countDownLatch.await(10, TimeUnit.SECONDS); + } + + @Test + public void testRollback() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(4); + StreamObserver streamObserver = new StreamObserver() { + @Override + public void onNext(GrpcMessageProto grpcMessageProto) { + System.out.println("receive : " + grpcMessageProto.toString()); + countDownLatch.countDown(); + } + + @Override + public void onError(Throwable throwable) { + throwable.printStackTrace(); + } + + @Override + public void onCompleted() { + + } + }; + + StreamObserver response = seataServiceStub.sendRequest(streamObserver); + response.onNext(getRegisterTMRequest()); + response.onNext(getGlobalBeginRequest()); + response.onNext(getBranchRegisterRequest()); + response.onNext(getGlobalRollbackRequest()); + + response.onCompleted(); + + countDownLatch.await(10, TimeUnit.SECONDS); + } +} diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java new file mode 100644 index 00000000000..3744ddd270f --- /dev/null +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java @@ -0,0 +1,120 @@ +/* + * 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 org.apache.seata.core.rpc.netty.mockserver; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.ConfigurationTestHelper; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.TransactionManager; +import org.apache.seata.core.protocol.Protocol; +import org.apache.seata.core.rpc.netty.RmNettyRemotingClient; +import org.apache.seata.core.rpc.netty.TmNettyRemotingClient; +import org.apache.seata.mockserver.MockCoordinator; +import org.apache.seata.mockserver.MockServer; +import org.apache.seata.rm.DefaultResourceManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * the type MockServerTest + */ +public class MockGrpcServerTest { + + static String RESOURCE_ID = "mock-action"; + + Logger logger = LoggerFactory.getLogger(MockGrpcServerTest.class); + + @BeforeAll + public static void before() { + ConfigurationFactory.reload(); + ConfigurationTestHelper.putConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL, String.valueOf(ProtocolTestConstants.MOCK_SERVER_PORT)); + ConfigurationTestHelper.putConfig(ConfigurationKeys.TRANSPORT_PROTOCOL, Protocol.GPRC.value); + MockServer.start(ProtocolTestConstants.MOCK_SERVER_PORT); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + } + + @AfterAll + public static void after() { + //MockServer.close(); + ConfigurationTestHelper.removeConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL); + ConfigurationTestHelper.removeConfig(ConfigurationKeys.TRANSPORT_PROTOCOL); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + } + + @Test + public void testCommit() throws TransactionException { + String xid = doTestCommit(0); + Assertions.assertEquals(1, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(0, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testCommitRetry() throws TransactionException { + String xid = doTestCommit(2); + Assertions.assertEquals(3, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(0, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testRollback() throws TransactionException { + String xid = doTestRollback(0); + Assertions.assertEquals(0, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(1, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testRollbackRetry() throws TransactionException { + String xid = doTestRollback(2); + Assertions.assertEquals(0, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(3, Action1Impl.getRollbackTimes(xid)); + } + + private String doTestCommit(int times) throws TransactionException { + TransactionManager tm = TmClientTest.getTm(); + DefaultResourceManager rm = RmClientTest.getRm(RESOURCE_ID); + + String xid = tm.begin(ProtocolTestConstants.APPLICATION_ID, ProtocolTestConstants.SERVICE_GROUP, "test-commit", 60000); + MockCoordinator.getInstance().setExpectedRetry(xid, times); + Long branchId = rm.branchRegister(BranchType.TCC, RESOURCE_ID, "1", xid, "{\"mock\":\"mock\"}", "1"); + GlobalStatus commit = tm.commit(xid); + Assertions.assertEquals(GlobalStatus.Committed, commit); + return xid; + } + + private String doTestRollback(int times) throws TransactionException { + TransactionManager tm = TmClientTest.getTm(); + DefaultResourceManager rm = RmClientTest.getRm(RESOURCE_ID); + + String xid = tm.begin(ProtocolTestConstants.APPLICATION_ID, ProtocolTestConstants.SERVICE_GROUP, "test-rollback", 60000); + logger.info("doTestRollback xid:{}", xid); + MockCoordinator.getInstance().setExpectedRetry(xid, times); + Long branchId = rm.branchRegister(BranchType.TCC, RESOURCE_ID, "1", xid, "{\"mock\":\"mock\"}", "1"); + GlobalStatus rollback = tm.rollback(xid); + Assertions.assertEquals(GlobalStatus.Rollbacked, rollback); + return xid; + + } +} diff --git a/test/src/test/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto b/test/src/test/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto new file mode 100644 index 00000000000..dd61bd95f48 --- /dev/null +++ b/test/src/test/resources/protobuf/org/apache/seata/protocol/transcation/grpcMessage.proto @@ -0,0 +1,32 @@ +/* + * 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. + */ +syntax = "proto3"; +package org.apache.seata.protocol.protobuf; +option java_multiple_files = true; +option java_outer_classname = "GrpcMessage"; +option java_package = "org.apache.seata.core.protocol.generated"; + +message GrpcMessageProto { + int32 id = 1; + int32 messageType = 2; + map headMap = 3; + bytes body = 4; +} + +service SeataService { + rpc sendRequest (stream GrpcMessageProto) returns (stream GrpcMessageProto); +} \ No newline at end of file From b16f9cd5199b835a5450959e87cc21d7b504c0ea Mon Sep 17 00:00:00 2001 From: A Cabbage <928124786@qq.com> Date: Tue, 8 Oct 2024 10:10:43 +0800 Subject: [PATCH 11/54] bugfix: fix file.conf read failed after package (#6899) --- changes/en-us/2.x.md | 4 +- changes/zh-cn/2.x.md | 3 +- .../seata/config/FileConfiguration.java | 38 +++++++++---------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2dd147c01b5..f6d8b97e192 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -8,7 +8,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6881](https://github.com/apache/incubator-seata/pull/6881)]support grpc ### bugfix: - +- [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction @@ -36,8 +36,8 @@ Thanks to these contributors for their code commits. Please report an unintended - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) +- [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) - Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 624db67f904..6662c288c6a 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -7,7 +7,7 @@ [[#6881](https://github.com/apache/incubator-seata/pull/6881)]全链路支持grpc ### bugfix: - +- [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 @@ -36,6 +36,7 @@ - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) +- [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/FileConfiguration.java b/config/seata-config-core/src/main/java/org/apache/seata/config/FileConfiguration.java index 8c775fe2c93..91b2a290c04 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/FileConfiguration.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/FileConfiguration.java @@ -16,13 +16,22 @@ */ package org.apache.seata.config; +import org.apache.commons.lang.ObjectUtils; +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigFuture.ConfigOperation; +import org.apache.seata.config.file.FileConfig; + import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -32,15 +41,8 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import org.apache.seata.common.thread.NamedThreadFactory; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.ConfigFuture.ConfigOperation; -import org.apache.seata.config.file.FileConfig; -import org.apache.commons.lang.ObjectUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * The type FileConfiguration. * @@ -134,7 +136,6 @@ private File getConfigFile(String name) { boolean filePathCustom = name.startsWith(SYS_FILE_RESOURCE_PREFIX); String filePath = filePathCustom ? name.substring(SYS_FILE_RESOURCE_PREFIX.length()) : name; String decodedPath = URLDecoder.decode(filePath, StandardCharsets.UTF_8.name()); - File targetFile = getFileFromFileSystem(decodedPath); if (targetFile != null) { return targetFile; @@ -157,21 +158,18 @@ private File getFileFromFileSystem(String decodedPath) { // run with jar file and not package third lib into jar file, this.getClass().getClassLoader() will be null URL resourceUrl = this.getClass().getClassLoader().getResource(""); - String[] tryPaths = null; + // try to get log dir (spring.config.additional-location) after package and run sh or bat in bin dir + String configLocation = System.getProperty("spring.config.additional-location"); + List tryPathsList = new ArrayList<>(); + tryPathsList.add(decodedPath); if (resourceUrl != null) { - tryPaths = new String[]{ - // first: project dir - resourceUrl.getPath() + decodedPath, - // second: system path - decodedPath - }; - } else { - tryPaths = new String[]{ - decodedPath - }; + tryPathsList.add(resourceUrl.getPath() + decodedPath); + } + if (configLocation != null) { + tryPathsList.add(configLocation + decodedPath); } - + String[] tryPaths = tryPathsList.toArray(new String[0]); for (String tryPath : tryPaths) { File targetFile = new File(tryPath); if (targetFile.exists()) { From f9ef0e59ce93ce03777a295e078afdf59ce0a917 Mon Sep 17 00:00:00 2001 From: jimin Date: Tue, 8 Oct 2024 10:14:09 +0800 Subject: [PATCH 12/54] optimize: optimize readme docs (#6902) --- README.md | 12 ++++++------ changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5b7d3ae5d34..359190a0c39 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Build Status](https://github.com/apache/incubator-seata/workflows/build/badge.svg?branch=develop)](https://github.com/apache/incubator-seata/actions) [![codecov](https://codecov.io/gh/apache/incubator-seata/graph/badge.svg?token=tbmHt2ZfxO)](https://codecov.io/gh/apache/incubator-seata) [![license](https://img.shields.io/github/license/apache/incubator-seata.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![maven](https://img.shields.io/maven-central/v/io.apache/incubator-seata-parent?versionSuffix=2.0.0)](https://search.maven.org/search?q=io.seata) +[![maven](https://img.shields.io/maven-central/v/org.apache.seata/seata-all?versionSuffix=2.1.0)](https://central.sonatype.com/search?q=org.apache.seata%3Aseata-all) ## What is Seata? @@ -82,16 +82,16 @@ For more details about principle and design, please go to [Seata wiki page](http ## Maven dependency -Depending on the scenario, choose one of the two dependencies: `io.seata:seata-all` or `io.seata:seata-spring-boot-starter`. +Depending on the scenario, choose one of the two dependencies: `org.apache.seata:seata-all` or `org.apache.seata:seata-spring-boot-starter`. ```xml - 2.0.0 + 2.1.0 - io.seata + org.apache.seata seata-all ${seata.version} @@ -99,7 +99,7 @@ Depending on the scenario, choose one of the two dependencies: `io.seata:seata-a - io.seata + org.apache.seata seata-spring-boot-starter ${seata.version} @@ -142,7 +142,7 @@ Contributors are welcomed to join the Seata project. Please check [CONTRIBUTING] * [Seata Website](https://github.com/apache/incubator-seata.github.io) - Seata official website * [Seata GoLang](https://github.com/apache/incubator-seata-go) - Seata GoLang client and server * [Seata Samples](https://github.com/apache/incubator-seata-samples) - Samples for Seata -* [Seata GoLang Simples](https://github.com/apache/incubator-seata-go-samples) - Samples for Seata GoLang +* [Seata GoLang Samples](https://github.com/apache/incubator-seata-go-samples) - Samples for Seata GoLang * [Seata K8s](https://github.com/apache/incubator-seata-k8s) - Seata integration with k8s * [Seata CLI](https://github.com/apache/incubator-seata-ctl) - CLI tool for Seata diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index f6d8b97e192..70b242675d8 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -18,6 +18,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] Correct word spelling errors - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module +- [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 6662c288c6a..cbc9debeac9 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -17,7 +17,8 @@ - [[#6883](https://github.com/apache/incubator-seata/pull/6874)] 删除代码中无用对象的创建 - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] 修正单词拼写错误 -- [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 +- [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 +- [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 ### refactor: From 4bd9557ef592416da8423867457673ddf2b48773 Mon Sep 17 00:00:00 2001 From: MaoMaoandSnail <37172584+MaoMaoandSnail@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:20:19 +0800 Subject: [PATCH 13/54] bugfix: DesignerJson to StandardJson: SubStateMachine CompensateState cannot be recognized (#6890) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../saga/statelang/parser/utils/DesignerJsonTransformer.java | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 70b242675d8..96d8512a2dd 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -9,6 +9,7 @@ Add changes here for all PR submitted to the 2.x branch. ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package +- [[#6890](https://github.com/apache/incubator-seata/pull/6890)] fix designerJson to standardJson: subStateMachine compensateState cannot be recognized ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index cbc9debeac9..b100d18320c 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -8,6 +8,7 @@ ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 +- [[#6890](https://github.com/apache/incubator-seata/pull/6890)] 修复saga设计json转标准json过程中: 子状态机补偿节点无法被识别 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java b/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java index d653b6708ae..7a69c8b5c25 100644 --- a/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java +++ b/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformer.java @@ -122,7 +122,7 @@ private static void transformEdge(Map machineJsonObject, List Date: Tue, 8 Oct 2024 10:58:16 +0200 Subject: [PATCH 14/54] optimize: Log args mismatch (#6879) --- changes/en-us/2.x.md | 3 +++ changes/zh-cn/2.x.md | 4 ++++ .../seata/core/rpc/netty/NettyClientChannelManager.java | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 96d8512a2dd..e252b79b246 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -19,6 +19,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] upgrade npmjs version - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] Correct word spelling errors - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module +- [[#6879](https://github.com/apache/incubator-seata/pull/6879)] fix log argument mismatch issue - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs @@ -38,8 +39,10 @@ Thanks to these contributors for their code commits. Please report an unintended - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) +- [arrrnold17](https://github.com/arrrnold17) - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) + Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index b100d18320c..2c69ea823b7 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -18,9 +18,12 @@ - [[#6883](https://github.com/apache/incubator-seata/pull/6874)] 删除代码中无用对象的创建 - [[#6892](https://github.com/apache/incubator-seata/pull/6892)] 升级 npmjs 版本 - [[#6889](https://github.com/apache/incubator-seata/pull/6889)] 修正单词拼写错误 +- [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 +- [[#6879](https://github.com/apache/incubator-seata/pull/6879)] 修复日志参数不匹配问题 - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 + ### refactor: @@ -38,6 +41,7 @@ - [dk2k](https://github.com/dk2k) - [MaoMaoandSnail](https://github.com/MaoMaoandSnail) - [yougecn](https://github.com/yougecn) +- [arrrnold17](https://github.com/arrrnold17) - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java index 96287c72837..7be0de2e729 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java @@ -243,7 +243,7 @@ void doReconnect(List availList, String transactionServiceGroup) { failedMap.values().stream().map(Throwable::getMessage).collect(Collectors.toSet())); } else if (LOGGER.isDebugEnabled()) { failedMap.forEach((key, value) -> { - LOGGER.error("{} can not connect to {} cause:{} trace information:{}", + LOGGER.error("{} can not connect to {} cause:{} trace information:", FrameworkErrorCode.NetConnect.getErrCode(), key, value.getMessage(), value); }); } From 679f86e2c4a6c888b0e371014b41dd7d3912053e Mon Sep 17 00:00:00 2001 From: funkye Date: Thu, 10 Oct 2024 09:31:46 +0800 Subject: [PATCH 15/54] bugfix: fix the issue of Codecov not generating reports (#6907) --- build/pom.xml | 3 +++ changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + 3 files changed, 5 insertions(+) diff --git a/build/pom.xml b/build/pom.xml index e0c95be86fb..9e26a12f1a1 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -138,6 +138,9 @@ false true + + + true ${IMAGE_NAME} diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index e252b79b246..364a24b9db1 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -10,6 +10,7 @@ Add changes here for all PR submitted to the 2.x branch. ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] fix designerJson to standardJson: subStateMachine compensateState cannot be recognized +- [[#6907](https://github.com/apache/incubator-seata/pull/6907)] fix the issue of Codecov not generating reports ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 2c69ea823b7..ac9c9973906 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -9,6 +9,7 @@ ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] 修复saga设计json转标准json过程中: 子状态机补偿节点无法被识别 +- [[#6907](https://github.com/apache/incubator-seata/pull/6907)] 修复Codecov未生成报告的问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 From 3b8ccfc10ff1de69cdab017fce38bc9fe21cbac2 Mon Sep 17 00:00:00 2001 From: yougecn <40795959+yougecn@users.noreply.github.com> Date: Thu, 10 Oct 2024 18:17:12 +0800 Subject: [PATCH 16/54] optimize: h2 dependency adds test scope (#6906) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + server/pom.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 364a24b9db1..e93e075b700 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -22,6 +22,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] fix log argument mismatch issue - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs +- [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index ac9c9973906..771a64ad0d7 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -23,6 +23,7 @@ - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] 修复日志参数不匹配问题 - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 +- [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope ### refactor: diff --git a/server/pom.xml b/server/pom.xml index d74710fa619..63d34056a5d 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -226,6 +226,7 @@ com.h2database h2 + test From 57527868f8e00fc990c3609983b91b061cac7162 Mon Sep 17 00:00:00 2001 From: hanshaohua Date: Fri, 11 Oct 2024 15:16:11 +0800 Subject: [PATCH 17/54] feature: support shentong database (#6864) --- changes/en-us/2.x.md | 2 + changes/zh-cn/2.x.md | 7 +- .../apache/seata/common/util/PageUtil.java | 4 + .../seata/common/util/PageUtilTest.java | 2 + .../apache/seata/core/constants/DBType.java | 7 +- .../lock/DistributedLockSqlFactory.java | 2 +- .../db/sql/lock/LockStoreSqlFactory.java | 2 +- .../store/db/sql/lock/OscarLockStoreSql.java | 28 + .../store/db/sql/log/OscarLogStoreSqls.java | 27 + ....seata.core.store.db.sql.lock.LockStoreSql | 3 +- ...e.seata.core.store.db.sql.log.LogStoreSqls | 3 +- .../db/sql/lock/LockStoreSqlFactoryTest.java | 49 + .../db/sql/log/LogStoreSqlsFactoryTest.java | 36 + .../seata/rm/datasource/DataSourceProxy.java | 14 + .../exec/oscar/OscarInsertExecutor.java | 139 + .../sql/handler/oscar/OscarEscapeHandler.java | 2661 +++++++++++++++++ .../sql/struct/cache/OscarTableMetaCache.java | 199 ++ .../undo/oscar/OscarUndoDeleteExecutor.java | 79 + .../undo/oscar/OscarUndoExecutorHolder.java | 46 + .../undo/oscar/OscarUndoInsertExecutor.java | 86 + .../undo/oscar/OscarUndoLogManager.java | 106 + .../undo/oscar/OscarUndoUpdateExecutor.java | 80 + ...he.seata.rm.datasource.exec.InsertExecutor | 3 +- ...eata.rm.datasource.undo.UndoExecutorHolder | 3 +- ...he.seata.rm.datasource.undo.UndoLogManager | 3 +- .../org.apache.seata.sqlparser.EscapeHandler | 3 +- ...ache.seata.sqlparser.struct.TableMetaCache | 3 +- .../exec/OscarInsertExecutorTest.java | 446 +++ .../oscar/OscarDeleteRecognizerTest.java | 198 ++ .../oscar/OscarInsertRecognizerTest.java | 130 + .../OscarSelectForUpdateRecognizerTest.java | 111 + .../oscar/OscarUpdateRecognizerTest.java | 157 + script/client/at/db/oscar.sql | 43 + script/server/db/oscar.sql | 94 + .../seata/sqlparser/util/JdbcConstants.java | 2 + .../druid/oscar/BaseOscarRecognizer.java | 196 ++ .../druid/oscar/OscarDeleteRecognizer.java | 137 + .../druid/oscar/OscarInsertRecognizer.java | 164 + .../oscar/OscarOperateRecognizerHolder.java | 55 + .../oscar/OscarSelectForUpdateRecognizer.java | 139 + .../druid/oscar/OscarUpdateRecognizer.java | 184 ++ ...sqlparser.druid.SQLOperateRecognizerHolder | 3 +- 42 files changed, 5643 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/org/apache/seata/core/store/db/sql/lock/OscarLockStoreSql.java create mode 100644 core/src/main/java/org/apache/seata/core/store/db/sql/log/OscarLogStoreSqls.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oscar/OscarInsertExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oscar/OscarEscapeHandler.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCache.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoDeleteExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoExecutorHolder.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoInsertExecutor.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoLogManager.java create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoUpdateExecutor.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OscarInsertExecutorTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarDeleteRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarInsertRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarSelectForUpdateRecognizerTest.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarUpdateRecognizerTest.java create mode 100644 script/client/at/db/oscar.sql create mode 100644 script/server/db/oscar.sql create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/BaseOscarRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarDeleteRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarInsertRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarOperateRecognizerHolder.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarSelectForUpdateRecognizer.java create mode 100644 sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarUpdateRecognizer.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index e93e075b700..a4a567fa78d 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -6,6 +6,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6876](https://github.com/apache/incubator-seata/pull/6876)]support kingbase - [[#6881](https://github.com/apache/incubator-seata/pull/6881)]support grpc +- [[#6864](https://github.com/apache/incubator-seata/pull/6864)]support shentong database ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package @@ -44,6 +45,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [arrrnold17](https://github.com/arrrnold17) - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) +- [dsomehan](https://github.com/dsomehan) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 771a64ad0d7..efb5f32ae50 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -3,8 +3,10 @@ ### feature: -[[#6876](https://github.com/apache/incubator-seata/pull/6876)]支持人大金仓数据库(kingbase) -[[#6881](https://github.com/apache/incubator-seata/pull/6881)]全链路支持grpc +- [[#6876](https://github.com/apache/incubator-seata/pull/6876)]支持人大金仓数据库(kingbase) +- [[#6881](https://github.com/apache/incubator-seata/pull/6881)]全链路支持grpc +- [[#6864](https://github.com/apache/incubator-seata/pull/6864)]支持神通数据库(oscar) + ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 @@ -46,6 +48,7 @@ - [arrrnold17](https://github.com/arrrnold17) - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) +- [dsomehan](https://github.com/dsomehan) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/common/src/main/java/org/apache/seata/common/util/PageUtil.java b/common/src/main/java/org/apache/seata/common/util/PageUtil.java index ea658a64af1..b49b10eddc4 100644 --- a/common/src/main/java/org/apache/seata/common/util/PageUtil.java +++ b/common/src/main/java/org/apache/seata/common/util/PageUtil.java @@ -112,6 +112,7 @@ public static String pageSql(String sourceSql, String dbType, int pageNum, int p case "kingbase": case "oceanbase": case "dm": + case "oscar": return LIMIT_TEMPLATE.replace(SOURCE_SQL_PLACE_HOLD, sourceSql) .replace(LIMIT_PLACE_HOLD, String.valueOf(pageSize)) .replace(OFFSET_PLACE_HOLD, String.valueOf((pageNum - 1) * pageSize)); @@ -142,6 +143,7 @@ public static String countSql(String sourceSql, String dbType) { case "oceanbase": case "oracle": case "dm": + case "oscar": return sourceSql.replaceAll("(?i)(?<=select)(.*)(?=from)", " count(1) "); case "postgresql": case "kingbase": @@ -185,6 +187,7 @@ public static String getTimeStartSql(String dbType, String timeColumnName) { case "postgresql": case "sqlserver": case "dm": + case "oscar": return " and FLOOR(" + timeColumnName + "/1000) >= ? "; default: throw new IllegalArgumentException("The DB type :" + dbType + " is not supported yet"); @@ -204,6 +207,7 @@ public static String getTimeEndSql(String dbType, String timeColumnName) { case "postgresql": case "sqlserver": case "dm": + case "oscar": return " and FLOOR(" + timeColumnName + "/1000) <= ? "; default: throw new IllegalArgumentException("The DB type :" + dbType + " is not supported yet"); diff --git a/common/src/test/java/org/apache/seata/common/util/PageUtilTest.java b/common/src/test/java/org/apache/seata/common/util/PageUtilTest.java index 5fcdcfd5116..640fa037015 100644 --- a/common/src/test/java/org/apache/seata/common/util/PageUtilTest.java +++ b/common/src/test/java/org/apache/seata/common/util/PageUtilTest.java @@ -44,6 +44,7 @@ public void testPageSql() { assertEquals(PageUtil.pageSql(sourceSql, "postgresql", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "oceanbase", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "dm", 1, 5), mysqlTargetSql); + assertEquals(PageUtil.pageSql(sourceSql, "oscar", 1, 5), mysqlTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "oracle", 1, 5), oracleTargetSql); assertEquals(PageUtil.pageSql(sourceSql, "sqlserver", 1, 5), sqlserverTargetSql); @@ -61,6 +62,7 @@ void testCountSql() { assertEquals(PageUtil.countSql(sourceSql, "postgresql"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "oceanbase"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "dm"), targetSql); + assertEquals(PageUtil.countSql(sourceSql, "oscar"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "oracle"), targetSql); assertEquals(PageUtil.countSql(sourceSql, "sqlserver"), targetSql); diff --git a/core/src/main/java/org/apache/seata/core/constants/DBType.java b/core/src/main/java/org/apache/seata/core/constants/DBType.java index af0e82d312a..3521e3e4545 100644 --- a/core/src/main/java/org/apache/seata/core/constants/DBType.java +++ b/core/src/main/java/org/apache/seata/core/constants/DBType.java @@ -192,7 +192,12 @@ public enum DBType { /** * PolarDB db type. */ - POLARDB; + POLARDB, + + /** + * oscar db type. + */ + OSCAR; /** * Valueof db type. diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/distributed/lock/DistributedLockSqlFactory.java b/core/src/main/java/org/apache/seata/core/store/db/sql/distributed/lock/DistributedLockSqlFactory.java index 2072d4631c5..8f589780caa 100644 --- a/core/src/main/java/org/apache/seata/core/store/db/sql/distributed/lock/DistributedLockSqlFactory.java +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/distributed/lock/DistributedLockSqlFactory.java @@ -34,7 +34,7 @@ public class DistributedLockSqlFactory { /** * get the lock store sql * - * @param dbType the dbType, support mysql/oracle/h2/postgre/oceanbase/dm/sqlserver ... + * @param dbType the dbType, support mysql/oracle/h2/postgre/oceanbase/dm/sqlserver/oscar ... * @return lock store sql */ public static DistributedLockSql getDistributedLogStoreSql(String dbType) { diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactory.java b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactory.java index dcf5781edc5..6fb7a61e335 100644 --- a/core/src/main/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactory.java +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactory.java @@ -34,7 +34,7 @@ public class LockStoreSqlFactory { /** * get the lock store sql * - * @param dbType the dbType, support mysql/oracle/h2/postgre/oceanbase/dm + * @param dbType the dbType, support mysql/oracle/h2/postgre/oceanbase/dm/oscar * @return lock store sql */ public static LockStoreSql getLogStoreSql(String dbType) { diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/lock/OscarLockStoreSql.java b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/OscarLockStoreSql.java new file mode 100644 index 00000000000..bd72749ea1f --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/lock/OscarLockStoreSql.java @@ -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 org.apache.seata.core.store.db.sql.lock; + + +import org.apache.seata.common.loader.LoadLevel; + +/** + * the database lock store shentong sql + * + */ +@LoadLevel(name = "oscar") +public class OscarLockStoreSql extends OracleLockStoreSql { +} diff --git a/core/src/main/java/org/apache/seata/core/store/db/sql/log/OscarLogStoreSqls.java b/core/src/main/java/org/apache/seata/core/store/db/sql/log/OscarLogStoreSqls.java new file mode 100644 index 00000000000..f310e03ff2e --- /dev/null +++ b/core/src/main/java/org/apache/seata/core/store/db/sql/log/OscarLogStoreSqls.java @@ -0,0 +1,27 @@ +/* + * 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 org.apache.seata.core.store.db.sql.log; + + +import org.apache.seata.common.loader.LoadLevel; + +/** + * Database log store oscar sql + */ +@LoadLevel(name = "oscar") +public class OscarLogStoreSqls extends OracleLogStoreSqls { +} diff --git a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql index 375298bc638..838ee9d70f3 100644 --- a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql +++ b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.lock.LockStoreSql @@ -23,4 +23,5 @@ org.apache.seata.core.store.db.sql.lock.SqlServerLockStoreSql org.apache.seata.core.store.db.sql.lock.MariadbLockStoreSql org.apache.seata.core.store.db.sql.lock.PolarDBXLockStoreSql org.apache.seata.core.store.db.sql.lock.DmLockStoreSql -org.apache.seata.core.store.db.sql.lock.KingbaseLockStoreSql \ No newline at end of file +org.apache.seata.core.store.db.sql.lock.OscarLockStoreSql +org.apache.seata.core.store.db.sql.lock.KingbaseLockStoreSql diff --git a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls index 5eb45d22e0c..90f94d6a3e5 100644 --- a/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls +++ b/core/src/main/resources/META-INF/services/org.apache.seata.core.store.db.sql.log.LogStoreSqls @@ -23,4 +23,5 @@ org.apache.seata.core.store.db.sql.log.SqlServerLogStoreSqls org.apache.seata.core.store.db.sql.log.MariadbLogStoreSqls org.apache.seata.core.store.db.sql.log.PolarDBXLogStoreSqls org.apache.seata.core.store.db.sql.log.DmLogStoreSqls -org.apache.seata.core.store.db.sql.log.KingbaseLogStoreSqls \ No newline at end of file +org.apache.seata.core.store.db.sql.log.OscarLogStoreSqls +org.apache.seata.core.store.db.sql.log.KingbaseLogStoreSqls diff --git a/core/src/test/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java b/core/src/test/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java index 4a598bbe52a..a38f75644cb 100644 --- a/core/src/test/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java +++ b/core/src/test/java/org/apache/seata/core/store/db/sql/lock/LockStoreSqlFactoryTest.java @@ -40,6 +40,8 @@ public class LockStoreSqlFactoryTest { private static LockStoreSql DM_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("dm"); + private static LockStoreSql OSCAR_LOCK_STORE = LockStoreSqlFactory.getLogStoreSql("oscar"); + private static String GLOBAL_TABLE = "global_table"; private static String BRANCH_TABLE = "branch_table"; @@ -379,4 +381,51 @@ public void dmLockTest() { sql = DM_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, 3); Assertions.assertEquals(EXPECT_CHECK_BRANCH_LOCKABLE_SQL,sql); } + + + @Test + public void oscarLockTest() { + String sql; + // Get insert lock sql string. + sql = OSCAR_LOCK_STORE.getInsertLockSQL(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OSCAR_LOCK_STORE.getInsertLockSQL(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get delete lock sql string. + sql = OSCAR_LOCK_STORE.getDeleteLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OSCAR_LOCK_STORE.getDeleteLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSql(GLOBAL_TABLE, 3); + Assertions.assertEquals(EXPECT_BATCH_GLOBAL_DELETE_LOCK_SQL,sql); + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSql(BRANCH_TABLE, 3); + Assertions.assertEquals(EXPECT_BATCH_BRANCH_DELETE_LOCK_SQL,sql); + + // Get batch delete lock sql string. + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSqlByBranchId(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSqlByBranchId(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get batch delete lock sql string. + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSqlByXid(GLOBAL_TABLE); + Assertions.assertEquals(EXPECT_BATCH_GLOBAL_DELETE_LOCK_BY_BRANCHS_SQL,sql); + sql = OSCAR_LOCK_STORE.getBatchDeleteLockSqlByXid(BRANCH_TABLE); + Assertions.assertEquals(EXPECT_BATCH_BRANCH_DELETE_LOCK_BY_BRANCHS_SQL,sql); + + // Get query lock sql string. + sql = OSCAR_LOCK_STORE.getQueryLockSql(GLOBAL_TABLE); + Assertions.assertNotNull(sql); + sql = OSCAR_LOCK_STORE.getQueryLockSql(BRANCH_TABLE); + Assertions.assertNotNull(sql); + + // Get check lock sql string. + sql = OSCAR_LOCK_STORE.getCheckLockableSql(GLOBAL_TABLE, 3); + Assertions.assertEquals(EXPECT_CHECK_GLOBAL_LOCKABLE_SQL,sql); + sql = OSCAR_LOCK_STORE.getCheckLockableSql(BRANCH_TABLE, 3); + Assertions.assertEquals(EXPECT_CHECK_BRANCH_LOCKABLE_SQL,sql); + } } diff --git a/core/src/test/java/org/apache/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java b/core/src/test/java/org/apache/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java index f3b9421862e..45c1aa3d101 100644 --- a/core/src/test/java/org/apache/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java +++ b/core/src/test/java/org/apache/seata/core/store/db/sql/log/LogStoreSqlsFactoryTest.java @@ -34,6 +34,8 @@ public class LogStoreSqlsFactoryTest { private static LogStoreSqls dmLog = LogStoreSqlsFactory.getLogStoreSqls("dm"); + private static LogStoreSqls oscarLog = LogStoreSqlsFactory.getLogStoreSqls("oscar"); + private static String globalTable = "global_table"; private static String branchTable = "branch_table"; @@ -246,4 +248,38 @@ public void dmLogTest() { sql = dmLog.getQueryBranchMax(branchTable); Assertions.assertNotNull(sql); } + + @Test + public void oscarLogTest() { + String sql = oscarLog.getInsertGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getUpdateGlobalTransactionStatusSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getDeleteGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryGlobalTransactionSQL(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryGlobalTransactionSQLByTransactionId(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryGlobalTransactionSQLByStatus(globalTable, "1"); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryGlobalTransactionForRecoverySQL(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getInsertBranchTransactionSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getUpdateBranchTransactionStatusSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getDeleteBranchTransactionByBranchIdSQL(branchTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getDeleteBranchTransactionByXId(branchTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryBranchTransaction(branchTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryBranchTransaction(branchTable, "1"); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryGlobalMax(globalTable); + Assertions.assertNotNull(sql); + sql = oscarLog.getQueryBranchMax(branchTable); + Assertions.assertNotNull(sql); + } } diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/DataSourceProxy.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/DataSourceProxy.java index dfab706294e..8868a0c964b 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/DataSourceProxy.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/DataSourceProxy.java @@ -243,6 +243,8 @@ private void initResourceId() { initSqlServerResourceId(); } else if (JdbcConstants.DM.equals(dbType)) { initDMResourceId(); + } else if (JdbcConstants.OSCAR.equals(dbType)) { + initOscarResourceId(); } else { initDefaultResourceId(); } @@ -321,6 +323,18 @@ private void initDMResourceId() { } } + /** + * init the oscar resource id + * jdbc:oscar://192.168.x.xx:2003/OSRDB + */ + private void initOscarResourceId() { + if (jdbcUrl.contains("?")) { + resourceId = jdbcUrl.substring(0, jdbcUrl.indexOf('?')) + "/" + userName; + } else { + resourceId = jdbcUrl + "/" + userName; + } + } + /** * prevent pg sql url like * jdbc:postgresql://127.0.0.1:5432/seata?currentSchema=public diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oscar/OscarInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oscar/OscarInsertExecutor.java new file mode 100644 index 00000000000..1a68e7f30d2 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/oscar/OscarInsertExecutor.java @@ -0,0 +1,139 @@ +/* + * 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 org.apache.seata.rm.datasource.exec.oscar; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.loader.Scope; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.BaseInsertExecutor; +import org.apache.seata.rm.datasource.exec.StatementCallback; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.Sequenceable; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The type Oscar insert executor. + * + */ +@LoadLevel(name = JdbcConstants.OSCAR, scope = Scope.PROTOTYPE) +public class OscarInsertExecutor extends BaseInsertExecutor implements Sequenceable { + + private static final Logger LOGGER = LoggerFactory.getLogger(OscarInsertExecutor.class); + + /** + * Instantiates a new Abstract dml base executor. + * + * @param statementProxy the statement proxy + * @param statementCallback the statement callback + * @param sqlRecognizer the sql recognizer + */ + public OscarInsertExecutor(StatementProxy statementProxy, StatementCallback statementCallback, + SQLRecognizer sqlRecognizer) { + super(statementProxy, statementCallback, sqlRecognizer); + } + + /** + * 1. If the insert columns are not empty and do not contain any pk columns, + * it means that there is no pk value in the insert rows, then all the pk values should come from auto-increment. + *

+ * 2. The pk value exists in insert rows. The possible situations are: + *

    + *
  • The insert columns are empty: all pk values can be obtained from insert rows
  • + *
  • The insert columns contain at least one pk column: first obtain the existing pk value from the insert rows, and other from auto-increment
  • + *
+ * + * @return {@link Map}<{@link String}, {@link List}<{@link Object}>> + * @throws SQLException the sql exception + */ + @Override + public Map> getPkValues() throws SQLException { + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + Map> pkValuesMap = new HashMap<>(pkColumnNameList.size()); + + // first obtain the existing pk value from the insert rows (if exists) + if (!containsColumns() || containsAnyPk()) { + pkValuesMap.putAll(getPkValuesByColumn()); + } + // other from auto-increment + for (String columnName : pkColumnNameList) { + if (!pkValuesMap.containsKey(columnName)) { + pkValuesMap.put(columnName, getGeneratedKeys(columnName)); + } + } + return pkValuesMap; + } + + /** + * Whether the insert columns contain any pk columns + * + * @return true: contain at least one pk column. false: do not contain any pk columns + */ + public boolean containsAnyPk() { + SQLInsertRecognizer recognizer = (SQLInsertRecognizer)sqlRecognizer; + List insertColumns = recognizer.getInsertColumns(); + if (CollectionUtils.isEmpty(insertColumns)) { + return false; + } + List pkColumnNameList = getTableMeta().getPrimaryKeyOnlyName(); + if (CollectionUtils.isEmpty(pkColumnNameList)) { + return false; + } + List newColumns = ColumnUtils.delEscape(insertColumns, getDbType()); + return pkColumnNameList.stream().anyMatch(pkColumn -> newColumns.contains(pkColumn) + || CollectionUtils.toUpperList(newColumns).contains(pkColumn.toUpperCase())); + } + + @Override + public Map> getPkValuesByColumn() throws SQLException { + Map> pkValuesMap = parsePkValuesFromStatement(); + Set keySet = pkValuesMap.keySet(); + for (String pkKey : keySet) { + List pkValues = pkValuesMap.get(pkKey); + for (int i = 0; i < pkValues.size(); i++) { + if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlSequenceExpr) { + pkValues.set(i, getPkValuesBySequence((SqlSequenceExpr) pkValues.get(i), pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof SqlMethodExpr) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } else if (!pkKey.isEmpty() && pkValues.get(i) instanceof Null) { + pkValues.set(i, getGeneratedKeys(pkKey).get(0)); + } + } + pkValuesMap.put(pkKey, pkValues); + } + return pkValuesMap; + } + + @Override + public String getSequenceSql(SqlSequenceExpr expr) { + return "SELECT " + expr.getSequence() + ".currval FROM DUAL"; + } + +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oscar/OscarEscapeHandler.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oscar/OscarEscapeHandler.java new file mode 100644 index 00000000000..22c67143ef1 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/handler/oscar/OscarEscapeHandler.java @@ -0,0 +1,2661 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.handler.oscar; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.EscapeHandler; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The type OSCAR keyword checker. + * + */ +@LoadLevel(name = JdbcConstants.OSCAR) +public class OscarEscapeHandler implements EscapeHandler { + + protected Set keywordSet = Arrays.stream(OscarKeyword.values()).map(OscarKeyword::name).collect(Collectors.toSet()); + + /** + * OSCAR keyword + */ + private enum OscarKeyword { + /** + * ABORT is oscar keyword + */ + ABORT("ABORT"), + /** + * ABSOLUTE is oscar keyword + */ + ABSOLUTE("ABSOLUTE"), + /** + * ACCESS is oscar keyword + */ + ACCESS("ACCESS"), + /** + * ACCESSED is oscar keyword + */ + ACCESSED("ACCESSED"), + /** + * ACTION is oscar keyword + */ + ACTION("ACTION"), + /** + * ADD is oscar keyword + */ + ADD("ADD"), + /** + * ADMIN is oscar keyword + */ + ADMIN("ADMIN"), + /** + * ADVISOR is oscar keyword + */ + ADVISOR("ADVISOR"), + /** + * AFTER is oscar keyword + */ + AFTER("AFTER"), + /** + * AGGREGATE is oscar keyword + */ + AGGREGATE("AGGREGATE"), + /** + * ALTER is oscar keyword + */ + ALTER("ALTER"), + /** + * ALWAYS is oscar keyword + */ + ALWAYS("ALWAYS"), + /** + * ANALYSE is oscar keyword + */ + ANALYSE("ANALYSE"), + /** + * ANALYZE is oscar keyword + */ + ANALYZE("ANALYZE"), + /** + * ANALYZER is oscar keyword + */ + ANALYZER("ANALYZER"), + /** + * APP is oscar keyword + */ + APP("APP"), + /** + * ARCHIVE is oscar keyword + */ + ARCHIVE("ARCHIVE"), + /** + * ARCHIVELOG is oscar keyword + */ + ARCHIVELOG("ARCHIVELOG"), + /** + * ARE is oscar keyword + */ + ARE("ARE"), + /** + * ARRAY is oscar keyword + */ + ARRAY("ARRAY"), + /** + * ASC is oscar keyword + */ + ASC("ASC"), + /** + * ASSERTION is oscar keyword + */ + ASSERTION("ASSERTION"), + /** + * ASSIGNMENT is oscar keyword + */ + ASSIGNMENT("ASSIGNMENT"), + /** + * AST is oscar keyword + */ + AST("AST"), + /** + * ASYNC is oscar keyword + */ + ASYNC("ASYNC"), + /** + * ATTRIBUTES is oscar keyword + */ + ATTRIBUTES("ATTRIBUTES"), + /** + * AUDIT is oscar keyword + */ + AUDIT("AUDIT"), + /** + * AUDITFILE is oscar keyword + */ + AUDITFILE("AUDITFILE"), + /** + * AUTHID is oscar keyword + */ + AUTHID("AUTHID"), + /** + * AUTHORIZATION is oscar keyword + */ + AUTHORIZATION("AUTHORIZATION"), + /** + * AUTO is oscar keyword + */ + AUTO("AUTO"), + /** + * AUTO_INCREMENT is oscar keyword + */ + AUTO_INCREMENT("AUTO_INCREMENT"), + /** + * AUTOEXTEND is oscar keyword + */ + AUTOEXTEND("AUTOEXTEND"), + /** + * BACKUP is oscar keyword + */ + BACKUP("BACKUP"), + /** + * BACKWARD is oscar keyword + */ + BACKWARD("BACKWARD"), + /** + * BASICANALYZER is oscar keyword + */ + BASICANALYZER("BASICANALYZER"), + /** + * BATCHSIZE is oscar keyword + */ + BATCHSIZE("BATCHSIZE"), + /** + * BEFORE is oscar keyword + */ + BEFORE("BEFORE"), + /** + * BEGIN is oscar keyword + */ + BEGIN("BEGIN"), + /** + * BETWEEN is oscar keyword + */ + BETWEEN("BETWEEN"), + /** + * BIGINT is oscar keyword + */ + BIGINT("BIGINT"), + /** + * BINARY is oscar keyword + */ + BINARY("BINARY"), + /** + * BINLOG is oscar keyword + */ + BINLOG("BINLOG"), + /** + * BIT is oscar keyword + */ + BIT("BIT"), + /** + * BITMAP is oscar keyword + */ + BITMAP("BITMAP"), + /** + * BLOCK is oscar keyword + */ + BLOCK("BLOCK"), + /** + * BODY is oscar keyword + */ + BODY("BODY"), + /** + * BOOLEAN is oscar keyword + */ + BOOLEAN("BOOLEAN"), + /** + * BOTH is oscar keyword + */ + BOTH("BOTH"), + /** + * BPCHAR is oscar keyword + */ + BPCHAR("BPCHAR"), + /** + * BUFFER is oscar keyword + */ + BUFFER("BUFFER"), + /** + * BUFFER_CACHE is oscar keyword + */ + BUFFER_CACHE("BUFFER_CACHE"), + /** + * BUFFER_POOL is oscar keyword + */ + BUFFER_POOL("BUFFER_POOL"), + /** + * BUILD is oscar keyword + */ + BUILD("BUILD"), + /** + * BULK is oscar keyword + */ + BULK("BULK"), + /** + * BY is oscar keyword + */ + BY("BY"), + /** + * BYTE is oscar keyword + */ + BYTE("BYTE"), + /** + * CACHE is oscar keyword + */ + CACHE("CACHE"), + /** + * CALL is oscar keyword + */ + CALL("CALL"), + /** + * CALLED is oscar keyword + */ + CALLED("CALLED"), + /** + * CANCEL is oscar keyword + */ + CANCEL("CANCEL"), + /** + * CASCADED is oscar keyword + */ + CASCADED("CASCADED"), + /** + * CDC is oscar keyword + */ + CDC("CDC"), + /** + * CHAIN is oscar keyword + */ + CHAIN("CHAIN"), + /** + * CHANGE is oscar keyword + */ + CHANGE("CHANGE"), + /** + * CHARACTERISTICS is oscar keyword + */ + CHARACTERISTICS("CHARACTERISTICS"), + /** + * CHARACTERSET is oscar keyword + */ + CHARACTERSET("CHARACTERSET"), + /** + * CHEAT is oscar keyword + */ + CHEAT("CHEAT"), + /** + * CHECKPOINT is oscar keyword + */ + CHECKPOINT("CHECKPOINT"), + /** + * CHINESEANALYZER is oscar keyword + */ + CHINESEANALYZER("CHINESEANALYZER"), + /** + * CHUNK is oscar keyword + */ + CHUNK("CHUNK"), + /** + * CJKANALYZER is oscar keyword + */ + CJKANALYZER("CJKANALYZER"), + /** + * CLASS is oscar keyword + */ + CLASS("CLASS"), + /** + * CLEAN is oscar keyword + */ + CLEAN("CLEAN"), + /** + * CLOSE is oscar keyword + */ + CLOSE("CLOSE"), + /** + * CLUSTER is oscar keyword + */ + CLUSTER("CLUSTER"), + /** + * COLUMNS is oscar keyword + */ + COLUMNS("COLUMNS"), + /** + * COMMENT is oscar keyword + */ + COMMENT("COMMENT"), + /** + * COMMENTS is oscar keyword + */ + COMMENTS("COMMENTS"), + /** + * COMMIT is oscar keyword + */ + COMMIT("COMMIT"), + /** + * COMMITTED is oscar keyword + */ + COMMITTED("COMMITTED"), + /** + * COMPILE is oscar keyword + */ + COMPILE("COMPILE"), + /** + * COMPLETE is oscar keyword + */ + COMPLETE("COMPLETE"), + /** + * COMPRESS is oscar keyword + */ + COMPRESS("COMPRESS"), + /** + * CONCAT is oscar keyword + */ + CONCAT("CONCAT"), + /** + * CONFIGURATION is oscar keyword + */ + CONFIGURATION("CONFIGURATION"), + /** + * CONNECT is oscar keyword + */ + CONNECT("CONNECT"), + /** + * CONNECT_BY_ISCYCLE is oscar keyword + */ + CONNECT_BY_ISCYCLE("CONNECT_BY_ISCYCLE"), + /** + * CONNECT_BY_ISLEAF is oscar keyword + */ + CONNECT_BY_ISLEAF("CONNECT_BY_ISLEAF"), + /** + * CONNECT_BY_ROOT is oscar keyword + */ + CONNECT_BY_ROOT("CONNECT_BY_ROOT"), + /** + * CONSTRAINTS is oscar keyword + */ + CONSTRAINTS("CONSTRAINTS"), + /** + * CONTENT is oscar keyword + */ + CONTENT("CONTENT"), + /** + * CONTEXT is oscar keyword + */ + CONTEXT("CONTEXT"), + /** + * CONTINUE is oscar keyword + */ + CONTINUE("CONTINUE"), + /** + * CONTROLFILE is oscar keyword + */ + CONTROLFILE("CONTROLFILE"), + /** + * CONVERSION is oscar keyword + */ + CONVERSION("CONVERSION"), + /** + * COPY is oscar keyword + */ + COPY("COPY"), + /** + * CROSS is oscar keyword + */ + CROSS("CROSS"), + /** + * CSV is oscar keyword + */ + CSV("CSV"), + /** + * CUBE is oscar keyword + */ + CUBE("CUBE"), + /** + * CURRENT is oscar keyword + */ + CURRENT("CURRENT"), + /** + * CURRENT_USER is oscar keyword + */ + CURRENT_USER("CURRENT_USER"), + /** + * CURSOR is oscar keyword + */ + CURSOR("CURSOR"), + /** + * CYCLE is oscar keyword + */ + CYCLE("CYCLE"), + /** + * DATA is oscar keyword + */ + DATA("DATA"), + /** + * DATABASE is oscar keyword + */ + DATABASE("DATABASE"), + /** + * DATABASELINK is oscar keyword + */ + DATABASELINK("DATABASELINK"), + /** + * DATAFILE is oscar keyword + */ + DATAFILE("DATAFILE"), + /** + * DATAFILETYPE is oscar keyword + */ + DATAFILETYPE("DATAFILETYPE"), + /** + * DATE is oscar keyword + */ + DATE("DATE"), + /** + * DATE_ADD is oscar keyword + */ + DATE_ADD("DATE_ADD"), + /** + * DATE_SUB is oscar keyword + */ + DATE_SUB("DATE_SUB"), + /** + * DATEFORMAT is oscar keyword + */ + DATEFORMAT("DATEFORMAT"), + /** + * DATETIME is oscar keyword + */ + DATETIME("DATETIME"), + /** + * DAY is oscar keyword + */ + DAY("DAY"), + /** + * DBA is oscar keyword + */ + DBA("DBA"), + /** + * DEALLOCATE is oscar keyword + */ + DEALLOCATE("DEALLOCATE"), + /** + * DEBUG is oscar keyword + */ + DEBUG("DEBUG"), + /** + * DEC is oscar keyword + */ + DEC("DEC"), + /** + * DECLARE is oscar keyword + */ + DECLARE("DECLARE"), + /** + * DECODE is oscar keyword + */ + DECODE("DECODE"), + /** + * DECRYPT is oscar keyword + */ + DECRYPT("DECRYPT"), + /** + * DEFERRABLE is oscar keyword + */ + DEFERRABLE("DEFERRABLE"), + /** + * DEFERRED is oscar keyword + */ + DEFERRED("DEFERRED"), + /** + * DEFINER is oscar keyword + */ + DEFINER("DEFINER"), + /** + * DELETE is oscar keyword + */ + DELETE("DELETE"), + /** + * DELIMITED is oscar keyword + */ + DELIMITED("DELIMITED"), + /** + * DELIMITER is oscar keyword + */ + DELIMITER("DELIMITER"), + /** + * DELIMITERS is oscar keyword + */ + DELIMITERS("DELIMITERS"), + /** + * DEMAND is oscar keyword + */ + DEMAND("DEMAND"), + /** + * DENSE_RANK is oscar keyword + */ + DENSE_RANK("DENSE_RANK"), + /** + * DESC is oscar keyword + */ + DESC("DESC"), + /** + * DESCRIPTION is oscar keyword + */ + DESCRIPTION("DESCRIPTION"), + /** + * DETERMINISTIC is oscar keyword + */ + DETERMINISTIC("DETERMINISTIC"), + /** + * DIRECTORY is oscar keyword + */ + DIRECTORY("DIRECTORY"), + /** + * DISABLE is oscar keyword + */ + DISABLE("DISABLE"), + /** + * DOCUMENT is oscar keyword + */ + DOCUMENT("DOCUMENT"), + /** + * DOMAIN is oscar keyword + */ + DOMAIN("DOMAIN"), + /** + * DOUBLE is oscar keyword + */ + DOUBLE("DOUBLE"), + /** + * DUMP is oscar keyword + */ + DUMP("DUMP"), + /** + * EACH is oscar keyword + */ + EACH("EACH"), + /** + * ELOG is oscar keyword + */ + ELOG("ELOG"), + /** + * ELT is oscar keyword + */ + ELT("ELT"), + /** + * EMPTY is oscar keyword + */ + EMPTY("EMPTY"), + /** + * ENABLE is oscar keyword + */ + ENABLE("ENABLE"), + /** + * ENCODING is oscar keyword + */ + ENCODING("ENCODING"), + /** + * ENCRYPT is oscar keyword + */ + ENCRYPT("ENCRYPT"), + /** + * ENCRYPTED is oscar keyword + */ + ENCRYPTED("ENCRYPTED"), + /** + * ENCRYPTION is oscar keyword + */ + ENCRYPTION("ENCRYPTION"), + /** + * END is oscar keyword + */ + END("END"), + /** + * ERROR is oscar keyword + */ + ERROR("ERROR"), + /** + * ERRORS is oscar keyword + */ + ERRORS("ERRORS"), + /** + * ESCALATION is oscar keyword + */ + ESCALATION("ESCALATION"), + /** + * ESCAPE is oscar keyword + */ + ESCAPE("ESCAPE"), + /** + * EVENTS is oscar keyword + */ + EVENTS("EVENTS"), + /** + * EXCHANGE is oscar keyword + */ + EXCHANGE("EXCHANGE"), + /** + * EXCLUDING is oscar keyword + */ + EXCLUDING("EXCLUDING"), + /** + * EXCLUSIVE is oscar keyword + */ + EXCLUSIVE("EXCLUSIVE"), + /** + * EXEC is oscar keyword + */ + EXEC("EXEC"), + /** + * EXECUTE is oscar keyword + */ + EXECUTE("EXECUTE"), + /** + * EXPLAIN is oscar keyword + */ + EXPLAIN("EXPLAIN"), + /** + * EXPORT is oscar keyword + */ + EXPORT("EXPORT"), + /** + * EXTEND is oscar keyword + */ + EXTEND("EXTEND"), + /** + * EXTERNALLY is oscar keyword + */ + EXTERNALLY("EXTERNALLY"), + /** + * FAILOVER is oscar keyword + */ + FAILOVER("FAILOVER"), + /** + * FALSE is oscar keyword + */ + FALSE("FALSE"), + /** + * FAR is oscar keyword + */ + FAR("FAR"), + /** + * FAST is oscar keyword + */ + FAST("FAST"), + /** + * FAULT is oscar keyword + */ + FAULT("FAULT"), + /** + * FETCH is oscar keyword + */ + FETCH("FETCH"), + /** + * FIELD is oscar keyword + */ + FIELD("FIELD"), + /** + * FIELDS is oscar keyword + */ + FIELDS("FIELDS"), + /** + * FIELDTERMINATOR is oscar keyword + */ + FIELDTERMINATOR("FIELDTERMINATOR"), + /** + * FILE is oscar keyword + */ + FILE("FILE"), + /** + * FILESIZE is oscar keyword + */ + FILESIZE("FILESIZE"), + /** + * FILL is oscar keyword + */ + FILL("FILL"), + /** + * FILTER is oscar keyword + */ + FILTER("FILTER"), + /** + * FIRE_TRIGGERS is oscar keyword + */ + FIRE_TRIGGERS("FIRE_TRIGGERS"), + /** + * FIRST is oscar keyword + */ + FIRST("FIRST"), + /** + * FIRSTROW is oscar keyword + */ + FIRSTROW("FIRSTROW"), + /** + * FLUSH is oscar keyword + */ + FLUSH("FLUSH"), + /** + * FOLLOWING is oscar keyword + */ + FOLLOWING("FOLLOWING"), + /** + * FORCE is oscar keyword + */ + FORCE("FORCE"), + /** + * FOREIGNKEY_CONSTRAINTS is oscar keyword + */ + FOREIGNKEY_CONSTRAINTS("FOREIGNKEY_CONSTRAINTS"), + /** + * FOREVER is oscar keyword + */ + FOREVER("FOREVER"), + /** + * FORMATFILE is oscar keyword + */ + FORMATFILE("FORMATFILE"), + /** + * FORWARD is oscar keyword + */ + FORWARD("FORWARD"), + /** + * FREELISTS is oscar keyword + */ + FREELISTS("FREELISTS"), + /** + * FREEPOOLS is oscar keyword + */ + FREEPOOLS("FREEPOOLS"), + /** + * FULL is oscar keyword + */ + FULL("FULL"), + /** + * FULLTEXT is oscar keyword + */ + FULLTEXT("FULLTEXT"), + /** + * FUNCTION is oscar keyword + */ + FUNCTION("FUNCTION"), + /** + * G is oscar keyword + */ + G("G"), + /** + * GB is oscar keyword + */ + GB("GB"), + /** + * GBK is oscar keyword + */ + GBK("GBK"), + /** + * GCOV is oscar keyword + */ + GCOV("GCOV"), + /** + * GENERATED is oscar keyword + */ + GENERATED("GENERATED"), + /** + * GEOGRAPHY is oscar keyword + */ + GEOGRAPHY("GEOGRAPHY"), + /** + * GEOMETRY is oscar keyword + */ + GEOMETRY("GEOMETRY"), + /** + * GET is oscar keyword + */ + GET("GET"), + /** + * GETCLOBVAL is oscar keyword + */ + GETCLOBVAL("GETCLOBVAL"), + /** + * GETSTRINGVAL is oscar keyword + */ + GETSTRINGVAL("GETSTRINGVAL"), + /** + * GLOBAL is oscar keyword + */ + GLOBAL("GLOBAL"), + /** + * GLOBAL_NAME is oscar keyword + */ + GLOBAL_NAME("GLOBAL_NAME"), + /** + * GLOBALLY is oscar keyword + */ + GLOBALLY("GLOBALLY"), + /** + * GREATEST is oscar keyword + */ + GREATEST("GREATEST"), + /** + * GROUPING is oscar keyword + */ + GROUPING("GROUPING"), + /** + * GROUPING_ID is oscar keyword + */ + GROUPING_ID("GROUPING_ID"), + /** + * GUARANTEE is oscar keyword + */ + GUARANTEE("GUARANTEE"), + /** + * HANDLER is oscar keyword + */ + HANDLER("HANDLER"), + /** + * HASH is oscar keyword + */ + HASH("HASH"), + /** + * HEADER is oscar keyword + */ + HEADER("HEADER"), + /** + * HEAP is oscar keyword + */ + HEAP("HEAP"), + /** + * HOLD is oscar keyword + */ + HOLD("HOLD"), + /** + * HOUR is oscar keyword + */ + HOUR("HOUR"), + /** + * IDENTIFIED is oscar keyword + */ + IDENTIFIED("IDENTIFIED"), + /** + * IDENTITY is oscar keyword + */ + IDENTITY("IDENTITY"), + /** + * IF is oscar keyword + */ + IF("IF"), + /** + * IGNORE is oscar keyword + */ + IGNORE("IGNORE"), + /** + * ILIKE is oscar keyword + */ + ILIKE("ILIKE"), + /** + * IMMEDIATE is oscar keyword + */ + IMMEDIATE("IMMEDIATE"), + /** + * IMMUTABLE is oscar keyword + */ + IMMUTABLE("IMMUTABLE"), + /** + * IMPLICIT is oscar keyword + */ + IMPLICIT("IMPLICIT"), + /** + * IMPORT is oscar keyword + */ + IMPORT("IMPORT"), + /** + * IMPORT_POLCOL is oscar keyword + */ + IMPORT_POLCOL("IMPORT_POLCOL"), + /** + * INCREMENT is oscar keyword + */ + INCREMENT("INCREMENT"), + /** + * INDEX is oscar keyword + */ + INDEX("INDEX"), + /** + * INDEXES is oscar keyword + */ + INDEXES("INDEXES"), + /** + * INHERITS is oscar keyword + */ + INHERITS("INHERITS"), + /** + * INIT is oscar keyword + */ + INIT("INIT"), + /** + * INITIAL is oscar keyword + */ + INITIAL("INITIAL"), + /** + * INITIALIZED is oscar keyword + */ + INITIALIZED("INITIALIZED"), + /** + * INITIALLY is oscar keyword + */ + INITIALLY("INITIALLY"), + /** + * INITRANS is oscar keyword + */ + INITRANS("INITRANS"), + /** + * INNER is oscar keyword + */ + INNER("INNER"), + /** + * INOUT is oscar keyword + */ + INOUT("INOUT"), + /** + * INPUT is oscar keyword + */ + INPUT("INPUT"), + /** + * INSENSITIVE is oscar keyword + */ + INSENSITIVE("INSENSITIVE"), + /** + * INSERT is oscar keyword + */ + INSERT("INSERT"), + /** + * INSTEAD is oscar keyword + */ + INSTEAD("INSTEAD"), + /** + * INTERVAL is oscar keyword + */ + INTERVAL("INTERVAL"), + /** + * INVALIDATE is oscar keyword + */ + INVALIDATE("INVALIDATE"), + /** + * INVISIBLE is oscar keyword + */ + INVISIBLE("INVISIBLE"), + /** + * INVOKER is oscar keyword + */ + INVOKER("INVOKER"), + /** + * IP is oscar keyword + */ + IP("IP"), + /** + * IS is oscar keyword + */ + IS("IS"), + /** + * ISNULL is oscar keyword + */ + ISNULL("ISNULL"), + /** + * ISOLATION is oscar keyword + */ + ISOLATION("ISOLATION"), + /** + * JOIN is oscar keyword + */ + JOIN("JOIN"), + /** + * JSON is oscar keyword + */ + JSON("JSON"), + /** + * JSON_TABLE is oscar keyword + */ + JSON_TABLE("JSON_TABLE"), + /** + * JSON_VALUE is oscar keyword + */ + JSON_VALUE("JSON_VALUE"), + /** + * K is oscar keyword + */ + K("K"), + /** + * KB is oscar keyword + */ + KB("KB"), + /** + * KEEP is oscar keyword + */ + KEEP("KEEP"), + /** + * KEEPIDENTITY is oscar keyword + */ + KEEPIDENTITY("KEEPIDENTITY"), + /** + * KEEPNULLS is oscar keyword + */ + KEEPNULLS("KEEPNULLS"), + /** + * KEY is oscar keyword + */ + KEY("KEY"), + /** + * KEYSTORE is oscar keyword + */ + KEYSTORE("KEYSTORE"), + /** + * KILL is oscar keyword + */ + KILL("KILL"), + /** + * KILOBYTES_PER_BATCH is oscar keyword + */ + KILOBYTES_PER_BATCH("KILOBYTES_PER_BATCH"), + /** + * KSTORE is oscar keyword + */ + KSTORE("KSTORE"), + /** + * LABEL is oscar keyword + */ + LABEL("LABEL"), + /** + * LANCOMPILER is oscar keyword + */ + LANCOMPILER("LANCOMPILER"), + /** + * LANGUAGE is oscar keyword + */ + LANGUAGE("LANGUAGE"), + /** + * LAST is oscar keyword + */ + LAST("LAST"), + /** + * LASTROW is oscar keyword + */ + LASTROW("LASTROW"), + /** + * LC_COLLATE is oscar keyword + */ + LC_COLLATE("LC_COLLATE"), + /** + * LC_CTYPE is oscar keyword + */ + LC_CTYPE("LC_CTYPE"), + /** + * LDRTRIM is oscar keyword + */ + LDRTRIM("LDRTRIM"), + /** + * LEADING is oscar keyword + */ + LEADING("LEADING"), + /** + * LEAK is oscar keyword + */ + LEAK("LEAK"), + /** + * LEAST is oscar keyword + */ + LEAST("LEAST"), + /** + * LEFT is oscar keyword + */ + LEFT("LEFT"), + /** + * LESS is oscar keyword + */ + LESS("LESS"), + /** + * LIFETIME is oscar keyword + */ + LIFETIME("LIFETIME"), + /** + * LIKE is oscar keyword + */ + LIKE("LIKE"), + /** + * LIMIT is oscar keyword + */ + LIMIT("LIMIT"), + /** + * LIST is oscar keyword + */ + LIST("LIST"), + /** + * LISTEN is oscar keyword + */ + LISTEN("LISTEN"), + /** + * LOAD is oscar keyword + */ + LOAD("LOAD"), + /** + * LOB is oscar keyword + */ + LOB("LOB"), + /** + * LOCAL is oscar keyword + */ + LOCAL("LOCAL"), + /** + * LOCATION is oscar keyword + */ + LOCATION("LOCATION"), + /** + * LOCK is oscar keyword + */ + LOCK("LOCK"), + /** + * LOCKED is oscar keyword + */ + LOCKED("LOCKED"), + /** + * LOG is oscar keyword + */ + LOG("LOG"), + /** + * LOGFILE is oscar keyword + */ + LOGFILE("LOGFILE"), + /** + * LOGGING is oscar keyword + */ + LOGGING("LOGGING"), + /** + * LOGICAL is oscar keyword + */ + LOGICAL("LOGICAL"), + /** + * LONG is oscar keyword + */ + LONG("LONG"), + /** + * LOOP is oscar keyword + */ + LOOP("LOOP"), + /** + * LRTRIM is oscar keyword + */ + LRTRIM("LRTRIM"), + /** + * LSN is oscar keyword + */ + LSN("LSN"), + /** + * LTRIM is oscar keyword + */ + LTRIM("LTRIM"), + /** + * M is oscar keyword + */ + M("M"), + /** + * MAINTAIN_INDEX is oscar keyword + */ + MAINTAIN_INDEX("MAINTAIN_INDEX"), + /** + * MAINTENANCE is oscar keyword + */ + MAINTENANCE("MAINTENANCE"), + /** + * MANUAL is oscar keyword + */ + MANUAL("MANUAL"), + /** + * MASKING is oscar keyword + */ + MASKING("MASKING"), + /** + * MATCH is oscar keyword + */ + MATCH("MATCH"), + /** + * MATCHED is oscar keyword + */ + MATCHED("MATCHED"), + /** + * MATERIALIZED is oscar keyword + */ + MATERIALIZED("MATERIALIZED"), + /** + * MAX is oscar keyword + */ + MAX("MAX"), + /** + * MAXERRORS is oscar keyword + */ + MAXERRORS("MAXERRORS"), + /** + * MAXEXTENDS is oscar keyword + */ + MAXEXTENDS("MAXEXTENDS"), + /** + * MAXEXTENTS is oscar keyword + */ + MAXEXTENTS("MAXEXTENTS"), + /** + * MAXSIZE is oscar keyword + */ + MAXSIZE("MAXSIZE"), + /** + * MAXTRANS is oscar keyword + */ + MAXTRANS("MAXTRANS"), + /** + * MAXVALUE is oscar keyword + */ + MAXVALUE("MAXVALUE"), + /** + * MB is oscar keyword + */ + MB("MB"), + /** + * MEMBER is oscar keyword + */ + MEMBER("MEMBER"), + /** + * MEMORY is oscar keyword + */ + MEMORY("MEMORY"), + /** + * MERGE is oscar keyword + */ + MERGE("MERGE"), + /** + * MIN is oscar keyword + */ + MIN("MIN"), + /** + * MINEXTENDS is oscar keyword + */ + MINEXTENDS("MINEXTENDS"), + /** + * MINEXTENTS is oscar keyword + */ + MINEXTENTS("MINEXTENTS"), + /** + * MINSIZE is oscar keyword + */ + MINSIZE("MINSIZE"), + /** + * MINUS is oscar keyword + */ + MINUS("MINUS"), + /** + * MINUTE is oscar keyword + */ + MINUTE("MINUTE"), + /** + * MINVALUE is oscar keyword + */ + MINVALUE("MINVALUE"), + /** + * MISSING is oscar keyword + */ + MISSING("MISSING"), + /** + * MOD is oscar keyword + */ + MOD("MOD"), + /** + * MODE is oscar keyword + */ + MODE("MODE"), + /** + * MODIFY is oscar keyword + */ + MODIFY("MODIFY"), + /** + * MONEY is oscar keyword + */ + MONEY("MONEY"), + /** + * MONTH is oscar keyword + */ + MONTH("MONTH"), + /** + * MOUNT is oscar keyword + */ + MOUNT("MOUNT"), + /** + * MOVE is oscar keyword + */ + MOVE("MOVE"), + /** + * MOVEMENT is oscar keyword + */ + MOVEMENT("MOVEMENT"), + /** + * MULTICOLUMN is oscar keyword + */ + MULTICOLUMN("MULTICOLUMN"), + /** + * MULTIPLE is oscar keyword + */ + MULTIPLE("MULTIPLE"), + /** + * NAME is oscar keyword + */ + NAME("NAME"), + /** + * NAMES is oscar keyword + */ + NAMES("NAMES"), + /** + * NATURAL is oscar keyword + */ + NATURAL("NATURAL"), + /** + * NCHAR is oscar keyword + */ + NCHAR("NCHAR"), + /** + * NEVER is oscar keyword + */ + NEVER("NEVER"), + /** + * NEWLINE is oscar keyword + */ + NEWLINE("NEWLINE"), + /** + * NEXT is oscar keyword + */ + NEXT("NEXT"), + /** + * NEXTVAL is oscar keyword + */ + NEXTVAL("NEXTVAL"), + /** + * NO is oscar keyword + */ + NO("NO"), + /** + * NOARCHIVELOG is oscar keyword + */ + NOARCHIVELOG("NOARCHIVELOG"), + /** + * NOAUDIT is oscar keyword + */ + NOAUDIT("NOAUDIT"), + /** + * NOCACHE is oscar keyword + */ + NOCACHE("NOCACHE"), + /** + * NOCOMPRESS is oscar keyword + */ + NOCOMPRESS("NOCOMPRESS"), + /** + * NOCOPY is oscar keyword + */ + NOCOPY("NOCOPY"), + /** + * NOCYCLE is oscar keyword + */ + NOCYCLE("NOCYCLE"), + /** + * NODE is oscar keyword + */ + NODE("NODE"), + /** + * NOGUARANTEE is oscar keyword + */ + NOGUARANTEE("NOGUARANTEE"), + /** + * NOLOGGING is oscar keyword + */ + NOLOGGING("NOLOGGING"), + /** + * NOMAXVALUE is oscar keyword + */ + NOMAXVALUE("NOMAXVALUE"), + /** + * NOMINVALUE is oscar keyword + */ + NOMINVALUE("NOMINVALUE"), + /** + * NOMOUNT is oscar keyword + */ + NOMOUNT("NOMOUNT"), + /** + * NORMAL is oscar keyword + */ + NORMAL("NORMAL"), + /** + * NOTHING is oscar keyword + */ + NOTHING("NOTHING"), + /** + * NOTIFY is oscar keyword + */ + NOTIFY("NOTIFY"), + /** + * NOTNULL is oscar keyword + */ + NOTNULL("NOTNULL"), + /** + * NOTRIM is oscar keyword + */ + NOTRIM("NOTRIM"), + /** + * NOVALIDATE is oscar keyword + */ + NOVALIDATE("NOVALIDATE"), + /** + * NOWAIT is oscar keyword + */ + NOWAIT("NOWAIT"), + /** + * NVARCHAR2 is oscar keyword + */ + NVARCHAR2("NVARCHAR2"), + /** + * NVL is oscar keyword + */ + NVL("NVL"), + /** + * NVL2 is oscar keyword + */ + NVL2("NVL2"), + /** + * OBJECT is oscar keyword + */ + OBJECT("OBJECT"), + /** + * OF is oscar keyword + */ + OF("OF"), + /** + * OFF is oscar keyword + */ + OFF("OFF"), + /** + * OFFLINE is oscar keyword + */ + OFFLINE("OFFLINE"), + /** + * OFFSET is oscar keyword + */ + OFFSET("OFFSET"), + /** + * OIDS is oscar keyword + */ + OIDS("OIDS"), + /** + * ONLINE is oscar keyword + */ + ONLINE("ONLINE"), + /** + * OPEN is oscar keyword + */ + OPEN("OPEN"), + /** + * OPERATOR is oscar keyword + */ + OPERATOR("OPERATOR"), + /** + * OPTIMIZE is oscar keyword + */ + OPTIMIZE("OPTIMIZE"), + /** + * OPTIMIZE_KSCACHE is oscar keyword + */ + OPTIMIZE_KSCACHE("OPTIMIZE_KSCACHE"), + /** + * OPTION is oscar keyword + */ + OPTION("OPTION"), + /** + * ORACLE is oscar keyword + */ + ORACLE("ORACLE"), + /** + * ORDINALITY is oscar keyword + */ + ORDINALITY("ORDINALITY"), + /** + * ORGANIZATION is oscar keyword + */ + ORGANIZATION("ORGANIZATION"), + /** + * OSCAR is oscar keyword + */ + OSCAR("OSCAR"), + /** + * OUT is oscar keyword + */ + OUT("OUT"), + /** + * OUTER is oscar keyword + */ + OUTER("OUTER"), + /** + * OUTLINE is oscar keyword + */ + OUTLINE("OUTLINE"), + /** + * OVER is oscar keyword + */ + OVER("OVER"), + /** + * OVERFLOW is oscar keyword + */ + OVERFLOW("OVERFLOW"), + /** + * OVERLAPS is oscar keyword + */ + OVERLAPS("OVERLAPS"), + /** + * OVERLAY is oscar keyword + */ + OVERLAY("OVERLAY"), + /** + * OWNER is oscar keyword + */ + OWNER("OWNER"), + /** + * PACKAGE is oscar keyword + */ + PACKAGE("PACKAGE"), + /** + * PAGESIZE is oscar keyword + */ + PAGESIZE("PAGESIZE"), + /** + * PARALLEL is oscar keyword + */ + PARALLEL("PARALLEL"), + /** + * PARAMETER is oscar keyword + */ + PARAMETER("PARAMETER"), + /** + * PARAMINFO is oscar keyword + */ + PARAMINFO("PARAMINFO"), + /** + * PARTIAL is oscar keyword + */ + PARTIAL("PARTIAL"), + /** + * PARTITION is oscar keyword + */ + PARTITION("PARTITION"), + /** + * PARTITIONS is oscar keyword + */ + PARTITIONS("PARTITIONS"), + /** + * PASSING is oscar keyword + */ + PASSING("PASSING"), + /** + * PASSWORD is oscar keyword + */ + PASSWORD("PASSWORD"), + /** + * PATH is oscar keyword + */ + PATH("PATH"), + /** + * PCTFREE is oscar keyword + */ + PCTFREE("PCTFREE"), + /** + * PCTINCREASE is oscar keyword + */ + PCTINCREASE("PCTINCREASE"), + /** + * PCTTHRESHOLD is oscar keyword + */ + PCTTHRESHOLD("PCTTHRESHOLD"), + /** + * PCTUSED is oscar keyword + */ + PCTUSED("PCTUSED"), + /** + * PCTVERSION is oscar keyword + */ + PCTVERSION("PCTVERSION"), + /** + * PENDANT is oscar keyword + */ + PENDANT("PENDANT"), + /** + * PETENTION is oscar keyword + */ + PETENTION("PETENTION"), + /** + * PFILE is oscar keyword + */ + PFILE("PFILE"), + /** + * PIPELINED is oscar keyword + */ + PIPELINED("PIPELINED"), + /** + * PIVOT is oscar keyword + */ + PIVOT("PIVOT"), + /** + * PLACING is oscar keyword + */ + PLACING("PLACING"), + /** + * PLS_INTEGER is oscar keyword + */ + PLS_INTEGER("PLS_INTEGER"), + /** + * POLICY is oscar keyword + */ + POLICY("POLICY"), + /** + * PORT is oscar keyword + */ + PORT("PORT"), + /** + * POSITION is oscar keyword + */ + POSITION("POSITION"), + /** + * PRECEDING is oscar keyword + */ + PRECEDING("PRECEDING"), + /** + * PRECISION is oscar keyword + */ + PRECISION("PRECISION"), + /** + * PREPARE is oscar keyword + */ + PREPARE("PREPARE"), + /** + * PRESERVE is oscar keyword + */ + PRESERVE("PRESERVE"), + /** + * PREVAL is oscar keyword + */ + PREVAL("PREVAL"), + /** + * PRIMARY is oscar keyword + */ + PRIMARY("PRIMARY"), + /** + * PRIOR is oscar keyword + */ + PRIOR("PRIOR"), + /** + * PRIORITY is oscar keyword + */ + PRIORITY("PRIORITY"), + /** + * PRIVILEGES is oscar keyword + */ + PRIVILEGES("PRIVILEGES"), + /** + * PROCEDURAL is oscar keyword + */ + PROCEDURAL("PROCEDURAL"), + /** + * PROCEDURE is oscar keyword + */ + PROCEDURE("PROCEDURE"), + /** + * PUBLIC is oscar keyword + */ + PUBLIC("PUBLIC"), + /** + * PURGE is oscar keyword + */ + PURGE("PURGE"), + /** + * QU is oscar keyword + */ + QU("QU"), + /** + * QUERY is oscar keyword + */ + QUERY("QUERY"), + /** + * QUICK is oscar keyword + */ + QUICK("QUICK"), + /** + * QUOTE is oscar keyword + */ + QUOTE("QUOTE"), + /** + * RAC is oscar keyword + */ + RAC("RAC"), + /** + * RANGE is oscar keyword + */ + RANGE("RANGE"), + /** + * RATIO_TO_REPORT is oscar keyword + */ + RATIO_TO_REPORT("RATIO_TO_REPORT"), + /** + * RAW is oscar keyword + */ + RAW("RAW"), + /** + * READ is oscar keyword + */ + READ("READ"), + /** + * READABLE is oscar keyword + */ + READABLE("READABLE"), + /** + * READS is oscar keyword + */ + READS("READS"), + /** + * READSIZE is oscar keyword + */ + READSIZE("READSIZE"), + /** + * REBUILD is oscar keyword + */ + REBUILD("REBUILD"), + /** + * RECHECK is oscar keyword + */ + RECHECK("RECHECK"), + /** + * RECORDS is oscar keyword + */ + RECORDS("RECORDS"), + /** + * RECOVERY is oscar keyword + */ + RECOVERY("RECOVERY"), + /** + * RECREATE is oscar keyword + */ + RECREATE("RECREATE"), + /** + * RECURSIVE is oscar keyword + */ + RECURSIVE("RECURSIVE"), + /** + * RECYCLE is oscar keyword + */ + RECYCLE("RECYCLE"), + /** + * REFRESH is oscar keyword + */ + REFRESH("REFRESH"), + /** + * REGEXP is oscar keyword + */ + REGEXP("REGEXP"), + /** + * REGION is oscar keyword + */ + REGION("REGION"), + /** + * REJECT is oscar keyword + */ + REJECT("REJECT"), + /** + * RELATIVE is oscar keyword + */ + RELATIVE("RELATIVE"), + /** + * REMOVE is oscar keyword + */ + REMOVE("REMOVE"), + /** + * RENAME is oscar keyword + */ + RENAME("RENAME"), + /** + * REPEATABLE is oscar keyword + */ + REPEATABLE("REPEATABLE"), + /** + * REPLACE is oscar keyword + */ + REPLACE("REPLACE"), + /** + * RESET is oscar keyword + */ + RESET("RESET"), + /** + * RESIZE is oscar keyword + */ + RESIZE("RESIZE"), + /** + * RESOURCE is oscar keyword + */ + RESOURCE("RESOURCE"), + /** + * RESTART is oscar keyword + */ + RESTART("RESTART"), + /** + * RESTORE is oscar keyword + */ + RESTORE("RESTORE"), + /** + * RESTRICT is oscar keyword + */ + RESTRICT("RESTRICT"), + /** + * RESULT is oscar keyword + */ + RESULT("RESULT"), + /** + * RESUME is oscar keyword + */ + RESUME("RESUME"), + /** + * RETENTION is oscar keyword + */ + RETENTION("RETENTION"), + /** + * RETURN is oscar keyword + */ + RETURN("RETURN"), + /** + * RETURN_GENERATED_KEYS is oscar keyword + */ + RETURN_GENERATED_KEYS("RETURN_GENERATED_KEYS"), + /** + * RETURNING is oscar keyword + */ + RETURNING("RETURNING"), + /** + * RETURNS is oscar keyword + */ + RETURNS("RETURNS"), + /** + * REUSE is oscar keyword + */ + REUSE("REUSE"), + /** + * REVERSE is oscar keyword + */ + REVERSE("REVERSE"), + /** + * REVOKE is oscar keyword + */ + REVOKE("REVOKE"), + /** + * REWRITE is oscar keyword + */ + REWRITE("REWRITE"), + /** + * RIGHT is oscar keyword + */ + RIGHT("RIGHT"), + /** + * ROLE is oscar keyword + */ + ROLE("ROLE"), + /** + * ROLLBACK is oscar keyword + */ + ROLLBACK("ROLLBACK"), + /** + * ROLLUP is oscar keyword + */ + ROLLUP("ROLLUP"), + /** + * ROW is oscar keyword + */ + ROW("ROW"), + /** + * ROWDESCRIPTION is oscar keyword + */ + ROWDESCRIPTION("ROWDESCRIPTION"), + /** + * ROWID is oscar keyword + */ + ROWID("ROWID"), + /** + * ROWS is oscar keyword + */ + ROWS("ROWS"), + /** + * ROWS_PER_BATCH is oscar keyword + */ + ROWS_PER_BATCH("ROWS_PER_BATCH"), + /** + * ROWTERMINATOR is oscar keyword + */ + ROWTERMINATOR("ROWTERMINATOR"), + /** + * ROWTYPE is oscar keyword + */ + ROWTYPE("ROWTYPE"), + /** + * RTRIM is oscar keyword + */ + RTRIM("RTRIM"), + /** + * RULE is oscar keyword + */ + RULE("RULE"), + /** + * SAMPLE is oscar keyword + */ + SAMPLE("SAMPLE"), + /** + * SAVEPOINT is oscar keyword + */ + SAVEPOINT("SAVEPOINT"), + /** + * SCAN is oscar keyword + */ + SCAN("SCAN"), + /** + * SCHEMA is oscar keyword + */ + SCHEMA("SCHEMA"), + /** + * SCN is oscar keyword + */ + SCN("SCN"), + /** + * SCROLL is oscar keyword + */ + SCROLL("SCROLL"), + /** + * SECOND is oscar keyword + */ + SECOND("SECOND"), + /** + * SECURITY is oscar keyword + */ + SECURITY("SECURITY"), + /** + * SEGMENT is oscar keyword + */ + SEGMENT("SEGMENT"), + /** + * SEPARATOR is oscar keyword + */ + SEPARATOR("SEPARATOR"), + /** + * SEQUENCE is oscar keyword + */ + SEQUENCE("SEQUENCE"), + /** + * SERIALIZABLE is oscar keyword + */ + SERIALIZABLE("SERIALIZABLE"), + /** + * SESSION is oscar keyword + */ + SESSION("SESSION"), + /** + * SETS is oscar keyword + */ + SETS("SETS"), + /** + * SHARE is oscar keyword + */ + SHARE("SHARE"), + /** + * SHOW is oscar keyword + */ + SHOW("SHOW"), + /** + * SHRINK is oscar keyword + */ + SHRINK("SHRINK"), + /** + * SHRINKLOG is oscar keyword + */ + SHRINKLOG("SHRINKLOG"), + /** + * SHUTDOWN is oscar keyword + */ + SHUTDOWN("SHUTDOWN"), + /** + * SIBLINGS is oscar keyword + */ + SIBLINGS("SIBLINGS"), + /** + * SIGNED is oscar keyword + */ + SIGNED("SIGNED"), + /** + * SILENTLY is oscar keyword + */ + SILENTLY("SILENTLY"), + /** + * SIMILAR is oscar keyword + */ + SIMILAR("SIMILAR"), + /** + * SIMPLE is oscar keyword + */ + SIMPLE("SIMPLE"), + /** + * SINGLE is oscar keyword + */ + SINGLE("SINGLE"), + /** + * SINGLEROW is oscar keyword + */ + SINGLEROW("SINGLEROW"), + /** + * SIZE is oscar keyword + */ + SIZE("SIZE"), + /** + * SKIP is oscar keyword + */ + SKIP("SKIP"), + /** + * SMALLINT is oscar keyword + */ + SMALLINT("SMALLINT"), + /** + * SPACE is oscar keyword + */ + SPACE("SPACE"), + /** + * SPLIT is oscar keyword + */ + SPLIT("SPLIT"), + /** + * STABLE is oscar keyword + */ + STABLE("STABLE"), + /** + * STANDALONE is oscar keyword + */ + STANDALONE("STANDALONE"), + /** + * STANDARDANALYZER is oscar keyword + */ + STANDARDANALYZER("STANDARDANALYZER"), + /** + * START is oscar keyword + */ + START("START"), + /** + * STARTFILE is oscar keyword + */ + STARTFILE("STARTFILE"), + /** + * STARTPOS is oscar keyword + */ + STARTPOS("STARTPOS"), + /** + * STARTTIME is oscar keyword + */ + STARTTIME("STARTTIME"), + /** + * STARTUP is oscar keyword + */ + STARTUP("STARTUP"), + /** + * STATEMENT is oscar keyword + */ + STATEMENT("STATEMENT"), + /** + * STATIC is oscar keyword + */ + STATIC("STATIC"), + /** + * STATISTICS is oscar keyword + */ + STATISTICS("STATISTICS"), + /** + * STDIN is oscar keyword + */ + STDIN("STDIN"), + /** + * STDOUT is oscar keyword + */ + STDOUT("STDOUT"), + /** + * STOP is oscar keyword + */ + STOP("STOP"), + /** + * STOPFILE is oscar keyword + */ + STOPFILE("STOPFILE"), + /** + * STOPPOS is oscar keyword + */ + STOPPOS("STOPPOS"), + /** + * STOPTIME is oscar keyword + */ + STOPTIME("STOPTIME"), + /** + * STOPWORDS is oscar keyword + */ + STOPWORDS("STOPWORDS"), + /** + * STORAGE is oscar keyword + */ + STORAGE("STORAGE"), + /** + * STORE is oscar keyword + */ + STORE("STORE"), + /** + * STORED is oscar keyword + */ + STORED("STORED"), + /** + * STRICT is oscar keyword + */ + STRICT("STRICT"), + /** + * SUBPARTITION is oscar keyword + */ + SUBPARTITION("SUBPARTITION"), + /** + * SUBPARTITIONS is oscar keyword + */ + SUBPARTITIONS("SUBPARTITIONS"), + /** + * SUBSTRING is oscar keyword + */ + SUBSTRING("SUBSTRING"), + /** + * SUCCESSFUL is oscar keyword + */ + SUCCESSFUL("SUCCESSFUL"), + /** + * SUSPEND is oscar keyword + */ + SUSPEND("SUSPEND"), + /** + * SWITCHOVER is oscar keyword + */ + SWITCHOVER("SWITCHOVER"), + /** + * SYNC is oscar keyword + */ + SYNC("SYNC"), + /** + * SYSAUX is oscar keyword + */ + SYSAUX("SYSAUX"), + /** + * SYSID is oscar keyword + */ + SYSID("SYSID"), + /** + * SYSTEM is oscar keyword + */ + SYSTEM("SYSTEM"), + /** + * T is oscar keyword + */ + T("T"), + /** + * TABLESPACE is oscar keyword + */ + TABLESPACE("TABLESPACE"), + /** + * TB is oscar keyword + */ + TB("TB"), + /** + * TEMP is oscar keyword + */ + TEMP("TEMP"), + /** + * TEMPFILE is oscar keyword + */ + TEMPFILE("TEMPFILE"), + /** + * TEMPLATE is oscar keyword + */ + TEMPLATE("TEMPLATE"), + /** + * TEMPORARY is oscar keyword + */ + TEMPORARY("TEMPORARY"), + /** + * TERMINATED is oscar keyword + */ + TERMINATED("TERMINATED"), + /** + * THAN is oscar keyword + */ + THAN("THAN"), + /** + * TIMES is oscar keyword + */ + TIMES("TIMES"), + /** + * TIMEZONE is oscar keyword + */ + TIMEZONE("TIMEZONE"), + /** + * TINYINT is oscar keyword + */ + TINYINT("TINYINT"), + /** + * TOAST is oscar keyword + */ + TOAST("TOAST"), + /** + * TRACE is oscar keyword + */ + TRACE("TRACE"), + /** + * TRACKING is oscar keyword + */ + TRACKING("TRACKING"), + /** + * TRAIL is oscar keyword + */ + TRAIL("TRAIL"), + /** + * TRAILING is oscar keyword + */ + TRAILING("TRAILING"), + /** + * TRANSACTION is oscar keyword + */ + TRANSACTION("TRANSACTION"), + /** + * TRANSACTIONAL is oscar keyword + */ + TRANSACTIONAL("TRANSACTIONAL"), + /** + * TRANSFORMS is oscar keyword + */ + TRANSFORMS("TRANSFORMS"), + /** + * TREAT is oscar keyword + */ + TREAT("TREAT"), + /** + * TRIAL is oscar keyword + */ + TRIAL("TRIAL"), + /** + * TRIGGER is oscar keyword + */ + TRIGGER("TRIGGER"), + /** + * TRIGGERS is oscar keyword + */ + TRIGGERS("TRIGGERS"), + /** + * TRIM is oscar keyword + */ + TRIM("TRIM"), + /** + * TRUE is oscar keyword + */ + TRUE("TRUE"), + /** + * TRUNCATE is oscar keyword + */ + TRUNCATE("TRUNCATE"), + /** + * TRUSTED is oscar keyword + */ + TRUSTED("TRUSTED"), + /** + * TUPLE is oscar keyword + */ + TUPLE("TUPLE"), + /** + * TYPE is oscar keyword + */ + TYPE("TYPE"), + /** + * UNBOUNDED is oscar keyword + */ + UNBOUNDED("UNBOUNDED"), + /** + * UNCOMMITTED is oscar keyword + */ + UNCOMMITTED("UNCOMMITTED"), + /** + * UNDO is oscar keyword + */ + UNDO("UNDO"), + /** + * UNENCRYPTED is oscar keyword + */ + UNENCRYPTED("UNENCRYPTED"), + /** + * UNKNOWN is oscar keyword + */ + UNKNOWN("UNKNOWN"), + /** + * UNLIMITED is oscar keyword + */ + UNLIMITED("UNLIMITED"), + /** + * UNLISTEN is oscar keyword + */ + UNLISTEN("UNLISTEN"), + /** + * UNLOCK is oscar keyword + */ + UNLOCK("UNLOCK"), + /** + * UNMAINTENANCE is oscar keyword + */ + UNMAINTENANCE("UNMAINTENANCE"), + /** + * UNPIVOT is oscar keyword + */ + UNPIVOT("UNPIVOT"), + /** + * UNSIGNED is oscar keyword + */ + UNSIGNED("UNSIGNED"), + /** + * UNTIL is oscar keyword + */ + UNTIL("UNTIL"), + /** + * UNUSABLE is oscar keyword + */ + UNUSABLE("UNUSABLE"), + /** + * UP is oscar keyword + */ + UP("UP"), + /** + * UPDATE is oscar keyword + */ + UPDATE("UPDATE"), + /** + * UPDATELABEL is oscar keyword + */ + UPDATELABEL("UPDATELABEL"), + /** + * UPDATEXML is oscar keyword + */ + UPDATEXML("UPDATEXML"), + /** + * USAGE is oscar keyword + */ + USAGE("USAGE"), + /** + * USE is oscar keyword + */ + USE("USE"), + /** + * USER is oscar keyword + */ + USER("USER"), + /** + * UTF8 is oscar keyword + */ + UTF8("UTF8"), + /** + * UTF8MB4 is oscar keyword + */ + UTF8MB4("UTF8MB4"), + /** + * VACUUM is oscar keyword + */ + VACUUM("VACUUM"), + /** + * VALID is oscar keyword + */ + VALID("VALID"), + /** + * VALIDATE is oscar keyword + */ + VALIDATE("VALIDATE"), + /** + * VALIDATION is oscar keyword + */ + VALIDATION("VALIDATION"), + /** + * VALIDATOR is oscar keyword + */ + VALIDATOR("VALIDATOR"), + /** + * VALUE is oscar keyword + */ + VALUE("VALUE"), + /** + * VALUES is oscar keyword + */ + VALUES("VALUES"), + /** + * VARBINARY is oscar keyword + */ + VARBINARY("VARBINARY"), + /** + * VARBIT is oscar keyword + */ + VARBIT("VARBIT"), + /** + * VARCHAR is oscar keyword + */ + VARCHAR("VARCHAR"), + /** + * VARCHAR2 is oscar keyword + */ + VARCHAR2("VARCHAR2"), + /** + * VARYING is oscar keyword + */ + VARYING("VARYING"), + /** + * VERBOSE is oscar keyword + */ + VERBOSE("VERBOSE"), + /** + * VERSION is oscar keyword + */ + VERSION("VERSION"), + /** + * VERSIONS is oscar keyword + */ + VERSIONS("VERSIONS"), + /** + * VIEW is oscar keyword + */ + VIEW("VIEW"), + /** + * VIRTUAL is oscar keyword + */ + VIRTUAL("VIRTUAL"), + /** + * VISIBLE is oscar keyword + */ + VISIBLE("VISIBLE"), + /** + * VOLATILE is oscar keyword + */ + VOLATILE("VOLATILE"), + /** + * VOTEDISK is oscar keyword + */ + VOTEDISK("VOTEDISK"), + /** + * WAIT is oscar keyword + */ + WAIT("WAIT"), + /** + * WALLET is oscar keyword + */ + WALLET("WALLET"), + /** + * WEIGHT is oscar keyword + */ + WEIGHT("WEIGHT"), + /** + * WHEN is oscar keyword + */ + WHEN("WHEN"), + /** + * WHENEVER is oscar keyword + */ + WHENEVER("WHENEVER"), + /** + * WINDOW is oscar keyword + */ + WINDOW("WINDOW"), + /** + * WORK is oscar keyword + */ + WORK("WORK"), + /** + * XML is oscar keyword + */ + XML("XML"), + /** + * XMLATTRIBUTES is oscar keyword + */ + XMLATTRIBUTES("XMLATTRIBUTES"), + /** + * XMLCONCAT is oscar keyword + */ + XMLCONCAT("XMLCONCAT"), + /** + * XMLELEMENT is oscar keyword + */ + XMLELEMENT("XMLELEMENT"), + /** + * XMLFOREST is oscar keyword + */ + XMLFOREST("XMLFOREST"), + /** + * XMLPARSE is oscar keyword + */ + XMLPARSE("XMLPARSE"), + /** + * XMLPI is oscar keyword + */ + XMLPI("XMLPI"), + /** + * XMLROOT is oscar keyword + */ + XMLROOT("XMLROOT"), + /** + * XMLSERIALIZE is oscar keyword + */ + XMLSERIALIZE("XMLSERIALIZE"), + /** + * XMLTABLE is oscar keyword + */ + XMLTABLE("XMLTABLE"), + /** + * YEAR is oscar keyword + */ + YEAR("YEAR"), + /** + * YES is oscar keyword + */ + YES("YES"), + /** + * ZONE is oscar keyword + */ + ZONE("ZONE"); + /** + * The Name. + */ + public final String name; + OscarKeyword(String name) { + this.name = name; + } + } + + + @Override + public boolean checkIfKeyWords(String fieldOrTableName) { + if (keywordSet.contains(fieldOrTableName)) { + return true; + } + if (fieldOrTableName != null) { + fieldOrTableName = fieldOrTableName.toUpperCase(); + } + return keywordSet.contains(fieldOrTableName); + + } + + @Override + public boolean checkIfNeedEscape(String columnName, TableMeta tableMeta) { + if (StringUtils.isBlank(columnName)) { + return false; + } + columnName = columnName.trim(); + if (containsEscape(columnName)) { + return false; + } + boolean isKeyWord = checkIfKeyWords(columnName); + if (isKeyWord) { + return true; + } + // oscar + // we are recommend table name and column name must uppercase. + // if exists full uppercase, the table name or column name doesn't bundle escape symbol. + //create\read table TABLE "table" "TABLE" + if (null != tableMeta) { + ColumnMeta columnMeta = tableMeta.getColumnMeta(columnName); + if (null != columnMeta) { + return columnMeta.isCaseSensitive(); + } + } else if (isUppercase(columnName)) { + return false; + } + return true; + } + + private static boolean isUppercase(String fieldOrTableName) { + if (fieldOrTableName == null) { + return false; + } + char[] chars = fieldOrTableName.toCharArray(); + for (char ch : chars) { + if (ch >= 'a' && ch <= 'z') { + return false; + } + } + return true; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCache.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCache.java new file mode 100644 index 00000000000..bed10c785f1 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/sql/struct/cache/OscarTableMetaCache.java @@ -0,0 +1,199 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.struct.cache; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.IndexMeta; +import org.apache.seata.sqlparser.struct.IndexType; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The type Table meta cache. + * + */ +@LoadLevel(name = JdbcConstants.OSCAR) +public class OscarTableMetaCache extends AbstractTableMetaCache { + + @Override + protected String getCacheKey(Connection connection, String tableName, String resourceId) { + StringBuilder cacheKey = new StringBuilder(resourceId); + cacheKey.append("."); + + //separate it to schemaName and tableName + String[] tableNameWithSchema = tableName.split("\\."); + String defaultTableName = tableNameWithSchema.length > 1 ? tableNameWithSchema[1] : tableNameWithSchema[0]; + + //oscar does not implement supportsMixedCaseIdentifiers in DatabaseMetadata + if (defaultTableName.contains("\"")) { + cacheKey.append(defaultTableName.replace("\"", "")); + } else { + // oscar default store in upper case + cacheKey.append(defaultTableName.toUpperCase()); + } + + return cacheKey.toString(); + } + + @Override + protected TableMeta fetchSchema(Connection connection, String tableName) throws SQLException { + try { + return resultSetMetaToSchema(connection.getMetaData(), tableName); + } catch (SQLException sqlEx) { + throw sqlEx; + } catch (Exception e) { + throw new SQLException(String.format("Failed to fetch schema of %s", tableName), e); + } + } + + private TableMeta resultSetMetaToSchema(DatabaseMetaData dbmd, String tableName) throws SQLException { + TableMeta tm = new TableMeta(); + tm.setTableName(tableName); + String[] schemaTable = tableName.split("\\."); + + String schemaName = schemaTable.length > 1 ? schemaTable[0] : dbmd.getConnection().getSchema(); + tableName = schemaTable.length > 1 ? schemaTable[1] : tableName; + if (schemaName.contains("\"")) { + schemaName = schemaName.replace("\"", ""); + } else { + schemaName = schemaName.toUpperCase(); + } + + if (tableName.contains("\"")) { + tableName = tableName.replace("\"", ""); + + } else { + tableName = tableName.toUpperCase(); + } + tm.setCaseSensitive(StringUtils.hasLowerCase(tableName)); + + try (ResultSet rsColumns = dbmd.getColumns("", schemaName, tableName, "%"); + ResultSet rsIndex = dbmd.getIndexInfo(null, schemaName, tableName, false, true); + ResultSet rsPrimary = dbmd.getPrimaryKeys(null, schemaName, tableName)) { + while (rsColumns.next()) { + ColumnMeta col = new ColumnMeta(); + col.setTableCat(rsColumns.getString("TABLE_CAT")); + col.setTableSchemaName(rsColumns.getString("TABLE_SCHEM")); + col.setTableName(rsColumns.getString("TABLE_NAME")); + col.setColumnName(rsColumns.getString("COLUMN_NAME")); + col.setDataType(rsColumns.getInt("DATA_TYPE")); + col.setDataTypeName(rsColumns.getString("TYPE_NAME")); + col.setColumnSize(rsColumns.getInt("COLUMN_SIZE")); + col.setDecimalDigits(rsColumns.getInt("DECIMAL_DIGITS")); + col.setNumPrecRadix(rsColumns.getInt("NUM_PREC_RADIX")); + col.setNullAble(rsColumns.getInt("NULLABLE")); + col.setRemarks(rsColumns.getString("REMARKS")); + col.setColumnDef(rsColumns.getString("COLUMN_DEF")); + col.setSqlDataType(rsColumns.getInt("SQL_DATA_TYPE")); + col.setSqlDatetimeSub(rsColumns.getInt("SQL_DATETIME_SUB")); + col.setCharOctetLength(rsColumns.getInt("CHAR_OCTET_LENGTH")); + col.setOrdinalPosition(rsColumns.getInt("ORDINAL_POSITION")); + col.setIsNullAble(rsColumns.getString("IS_NULLABLE")); + col.setCaseSensitive(StringUtils.hasLowerCase(col.getColumnName())); + + if (tm.getAllColumns().containsKey(col.getColumnName())) { + throw new NotSupportYetException("Not support the table has the same column name with different case yet"); + } + tm.getAllColumns().put(col.getColumnName(), col); + } + + while (rsIndex.next()) { + String indexName = rsIndex.getString("INDEX_NAME"); + if (StringUtils.isNullOrEmpty(indexName)) { + continue; + } + String colName = rsIndex.getString("COLUMN_NAME"); + ColumnMeta col = tm.getAllColumns().get(colName); + if (tm.getAllIndexes().containsKey(indexName)) { + IndexMeta index = tm.getAllIndexes().get(indexName); + index.getValues().add(col); + } else { + IndexMeta index = new IndexMeta(); + index.setIndexName(indexName); + index.setNonUnique(rsIndex.getBoolean("NON_UNIQUE")); + index.setIndexQualifier(rsIndex.getString("INDEX_QUALIFIER")); + index.setIndexName(rsIndex.getString("INDEX_NAME")); + index.setType(rsIndex.getShort("TYPE")); + index.setOrdinalPosition(rsIndex.getShort("ORDINAL_POSITION")); + index.setAscOrDesc(rsIndex.getString("ASC_OR_DESC")); + index.setCardinality(rsIndex.getLong("CARDINALITY")); + index.getValues().add(col); + if (!index.isNonUnique()) { + index.setIndextype(IndexType.UNIQUE); + } else { + index.setIndextype(IndexType.NORMAL); + } + tm.getAllIndexes().put(indexName, index); + + } + } + if (tm.getAllIndexes().isEmpty()) { + throw new ShouldNeverHappenException(String.format("Could not found any index in the table: %s", tableName)); + } + // when we create a primary key constraint oracle will uses and existing unique index. + // if we create a unique index before create a primary constraint in the same column will cause the problem + // that primary key constraint name was different from the unique name. + List pkcol = new ArrayList<>(); + while (rsPrimary.next()) { + String pkConstraintName = rsPrimary.getString("PK_NAME"); + if (tm.getAllIndexes().containsKey(pkConstraintName)) { + IndexMeta index = tm.getAllIndexes().get(pkConstraintName); + index.setIndextype(IndexType.PRIMARY); + } else { + //save the columns that constraint primary key name was different from unique index name + pkcol.add(rsPrimary.getString("COLUMN_NAME")); + } + } + //find the index that belong to the primary key constraint + if (!pkcol.isEmpty()) { + int matchCols = 0; + for (Map.Entry entry : tm.getAllIndexes().entrySet()) { + IndexMeta index = entry.getValue(); + // only the unique index and all the unique index's columes same as primary key columes, + // it belongs to primary key + if (index.getIndextype().value() == IndexType.UNIQUE.value()) { + for (ColumnMeta col : index.getValues()) { + if (pkcol.contains(col.getColumnName())) { + matchCols++; + } + } + if (matchCols == pkcol.size()) { + index.setIndextype(IndexType.PRIMARY); + // each table only has one primary key + break; + } else { + matchCols = 0; + } + } + } + } + } + return tm; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoDeleteExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoDeleteExecutor.java new file mode 100644 index 00000000000..04105a4b152 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoDeleteExecutor.java @@ -0,0 +1,79 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.oscar; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type oscar undo delete executor. + * + */ +public class OscarUndoDeleteExecutor extends AbstractUndoExecutor { + + /** + * INSERT INTO a (x, y, z, pk) VALUES (?, ?, ?, ?) + */ + private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (%s) VALUES (%s)"; + + /** + * Instantiates a new oscar undo delete executor. + * + * @param sqlUndoLog the sql undo log + */ + public OscarUndoDeleteExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + List fields = new ArrayList<>(row.nonPrimaryKeys()); + fields.addAll(getOrderedPkList(beforeImage,row,JdbcConstants.OSCAR)); + + // delete sql undo log before image all field come from table meta, need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String insertColumns = fields.stream() + .map(field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.OSCAR)) + .collect(Collectors.joining(", ")); + String insertValues = fields.stream().map(field -> "?") + .collect(Collectors.joining(", ")); + + return String.format(INSERT_SQL_TEMPLATE, sqlUndoLog.getTableName(), insertColumns, insertValues); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoExecutorHolder.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoExecutorHolder.java new file mode 100644 index 00000000000..aa6c560136e --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoExecutorHolder.java @@ -0,0 +1,46 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.oscar; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.rm.datasource.undo.UndoExecutorHolder; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The Type OscarUndoExecutorHolder + * + */ +@LoadLevel(name = JdbcConstants.OSCAR) +public class OscarUndoExecutorHolder implements UndoExecutorHolder { + + @Override + public AbstractUndoExecutor getInsertExecutor(SQLUndoLog sqlUndoLog) { + return new OscarUndoInsertExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getUpdateExecutor(SQLUndoLog sqlUndoLog) { + return new OscarUndoUpdateExecutor(sqlUndoLog); + } + + @Override + public AbstractUndoExecutor getDeleteExecutor(SQLUndoLog sqlUndoLog) { + return new OscarUndoDeleteExecutor(sqlUndoLog); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoInsertExecutor.java new file mode 100644 index 00000000000..3e56b37059f --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoInsertExecutor.java @@ -0,0 +1,86 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.oscar; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.SqlGenerateUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type oscar undo insert executor. + * + */ +public class OscarUndoInsertExecutor extends AbstractUndoExecutor { + + /** + * DELETE FROM a WHERE pk = ? + */ + private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords afterImage = sqlUndoLog.getAfterImage(); + List afterImageRows = afterImage.getRows(); + if (CollectionUtils.isEmpty(afterImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + return generateDeleteSql(afterImageRows,afterImage); + } + + @Override + protected void undoPrepare(PreparedStatement undoPST, ArrayList undoValues, List pkValueList) + throws SQLException { + int undoIndex = 0; + for (Field pkField:pkValueList) { + undoIndex++; + undoPST.setObject(undoIndex, pkField.getValue(), pkField.getType()); + } + } + + private String generateDeleteSql(List rows, TableRecords afterImage) { + List pkNameList = getOrderedPkList(afterImage, rows.get(0), JdbcConstants.OSCAR).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.OSCAR); + return String.format(DELETE_SQL_TEMPLATE, sqlUndoLog.getTableName(), whereSql); + } + + /** + * Instantiates a new Oscar undo insert executor. + * + * @param sqlUndoLog the sql undo log + */ + public OscarUndoInsertExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getAfterImage(); + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoLogManager.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoLogManager.java new file mode 100644 index 00000000000..68b405b6237 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoLogManager.java @@ -0,0 +1,106 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.oscar; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.DateUtil; +import org.apache.seata.core.compressor.CompressorType; +import org.apache.seata.core.constants.ClientTableColumnsName; +import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager; +import org.apache.seata.rm.datasource.undo.UndoLogParser; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; + +/** + * The type Oscar undo log manager. + */ +@LoadLevel(name = JdbcConstants.OSCAR) +public class OscarUndoLogManager extends AbstractUndoLogManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(OscarUndoLogManager.class); + + private static final String CHECK_UNDO_LOG_TABLE_EXIST_SQL = "SELECT 1 FROM " + UNDO_LOG_TABLE_NAME + " WHERE ROWNUM = 1"; + + private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + + " (" + ClientTableColumnsName.UNDO_LOG_ID + "," + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " + + ClientTableColumnsName.UNDO_LOG_XID + ", " + ClientTableColumnsName.UNDO_LOG_CONTEXT + ", " + + ClientTableColumnsName.UNDO_LOG_ROLLBACK_INFO + ", " + ClientTableColumnsName.UNDO_LOG_LOG_STATUS + ", " + + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + ", " + ClientTableColumnsName.UNDO_LOG_LOG_MODIFIED + ")" + + "VALUES (UNDO_LOG_SEQ.nextval, ?, ?, ?, ?, ?, sysdate, sysdate)"; + + private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= to_date(?,'yyyy-mm-dd hh24:mi:ss') and ROWNUM <= ?"; + + @Override + public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { + String dateStr = DateUtil.formatDate(logCreated, "yyyy-MM-dd HH:mm:ss"); + deletePST.setString(1, dateStr); + deletePST.setInt(2, limitRows); + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + return deleteRows; + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected void insertUndoLogWithNormal(String xid, long branchId, String rollbackCtx, byte[] undoLogContent, + Connection conn) throws SQLException { + insertUndoLog(xid, branchId,rollbackCtx, undoLogContent, State.Normal, conn); + } + + @Override + protected void insertUndoLogWithGlobalFinished(String xid, long branchId, UndoLogParser parser, Connection conn) throws SQLException { + insertUndoLog(xid, branchId, buildContext(parser.getName(), CompressorType.NONE), parser.getDefaultContent(), + State.GlobalFinished, conn); + } + + + private void insertUndoLog(String xid, long branchID, String rollbackCtx, byte[] undoLogContent, + State state, Connection conn) throws SQLException { + try (PreparedStatement pst = conn.prepareStatement(INSERT_UNDO_LOG_SQL)) { + pst.setLong(1, branchID); + pst.setString(2, xid); + pst.setString(3, rollbackCtx); + pst.setBytes(4, undoLogContent); + pst.setInt(5, state.getValue()); + pst.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + @Override + protected String getCheckUndoLogTableExistSql() { + return CHECK_UNDO_LOG_TABLE_EXIST_SQL; + } +} diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoUpdateExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoUpdateExecutor.java new file mode 100644 index 00000000000..d2364d7f482 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oscar/OscarUndoUpdateExecutor.java @@ -0,0 +1,80 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.oscar; + +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.rm.datasource.SqlGenerateUtils; +import org.apache.seata.rm.datasource.sql.struct.Field; +import org.apache.seata.rm.datasource.sql.struct.Row; +import org.apache.seata.rm.datasource.sql.struct.TableRecords; +import org.apache.seata.rm.datasource.undo.AbstractUndoExecutor; +import org.apache.seata.rm.datasource.undo.SQLUndoLog; +import org.apache.seata.sqlparser.util.ColumnUtils; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * The type oscar undo update executor. + * + */ +public class OscarUndoUpdateExecutor extends AbstractUndoExecutor { + + /** + * UPDATE a SET x = ?, y = ?, z = ? WHERE pk1 = ? and pk2 = ? + */ + private static final String UPDATE_SQL_TEMPLATE = "UPDATE %s SET %s WHERE %s "; + + @Override + protected String buildUndoSQL() { + TableRecords beforeImage = sqlUndoLog.getBeforeImage(); + List beforeImageRows = beforeImage.getRows(); + if (CollectionUtils.isEmpty(beforeImageRows)) { + throw new ShouldNeverHappenException("Invalid UNDO LOG"); + } + Row row = beforeImageRows.get(0); + + List nonPkFields = row.nonPrimaryKeys(); + // update sql undo log before image all field come from table meta. need add escape. + // see BaseTransactionalExecutor#buildTableRecords + String updateColumns = nonPkFields.stream().map( + field -> ColumnUtils.addEscape(field.getName(), JdbcConstants.OSCAR) + " = ?").collect( + Collectors.joining(", ")); + + List pkNameList = getOrderedPkList(beforeImage, row, JdbcConstants.OSCAR).stream().map( + e -> e.getName()).collect(Collectors.toList()); + String whereSql = SqlGenerateUtils.buildWhereConditionByPKs(pkNameList, JdbcConstants.OSCAR); + + return String.format(UPDATE_SQL_TEMPLATE, sqlUndoLog.getTableName(), updateColumns, whereSql); + } + + /** + * Instantiates a new Oscar undo update executor. + * + * @param sqlUndoLog the sql undo log + */ + public OscarUndoUpdateExecutor(SQLUndoLog sqlUndoLog) { + super(sqlUndoLog); + } + + @Override + protected TableRecords getUndoRows() { + return sqlUndoLog.getBeforeImage(); + } +} diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor index 49701f788b2..bdd61e97e24 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.exec.InsertExecutor @@ -21,4 +21,5 @@ org.apache.seata.rm.datasource.exec.sqlserver.SqlServerInsertExecutor org.apache.seata.rm.datasource.exec.mariadb.MariadbInsertExecutor org.apache.seata.rm.datasource.exec.polardbx.PolarDBXInsertExecutor org.apache.seata.rm.datasource.exec.dm.DmInsertExecutor -org.apache.seata.rm.datasource.exec.kingbase.KingbaseInsertExecutor \ No newline at end of file +org.apache.seata.rm.datasource.exec.oscar.OscarInsertExecutor +org.apache.seata.rm.datasource.exec.kingbase.KingbaseInsertExecutor diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder index 178aca432f5..e2ae497e2ab 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoExecutorHolder @@ -21,4 +21,5 @@ org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoExecutorHolder org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoExecutorHolder org.apache.seata.rm.datasource.undo.polardbx.PolarDBXUndoExecutorHolder org.apache.seata.rm.datasource.undo.dm.DmUndoExecutorHolder -org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoExecutorHolder \ No newline at end of file +org.apache.seata.rm.datasource.undo.oscar.OscarUndoExecutorHolder +org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoExecutorHolder diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager index 040242e30e7..fbc80b929ff 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogManager @@ -21,4 +21,5 @@ org.apache.seata.rm.datasource.undo.sqlserver.SqlServerUndoLogManager org.apache.seata.rm.datasource.undo.mariadb.MariadbUndoLogManager org.apache.seata.rm.datasource.undo.polardbx.PolarDBXUndoLogManager org.apache.seata.rm.datasource.undo.dm.DmUndoLogManager -org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoLogManager \ No newline at end of file +org.apache.seata.rm.datasource.undo.oscar.OscarUndoLogManager +org.apache.seata.rm.datasource.undo.kingbase.KingbaseUndoLogManager diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler index 6c3ef4dc8aa..0b4af20529c 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.EscapeHandler @@ -21,4 +21,5 @@ org.apache.seata.rm.datasource.sql.handler.mariadb.MariadbEscapeHandler org.apache.seata.rm.datasource.sql.handler.sqlserver.SqlServerEscapeHandler org.apache.seata.rm.datasource.sql.handler.polardbx.PolarDBXEscapeHandler org.apache.seata.rm.datasource.sql.handler.dm.DmEscapeHandler -org.apache.seata.rm.datasource.sql.handler.kingbase.KingbaseEscapeHandler \ No newline at end of file +org.apache.seata.rm.datasource.sql.handler.oscar.OscarEscapeHandler +org.apache.seata.rm.datasource.sql.handler.kingbase.KingbaseEscapeHandler diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache index a55afda3015..4ed7384dac1 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.sqlparser.struct.TableMetaCache @@ -21,4 +21,5 @@ org.apache.seata.rm.datasource.sql.struct.cache.SqlServerTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.MariadbTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.PolarDBXTableMetaCache org.apache.seata.rm.datasource.sql.struct.cache.DmTableMetaCache -org.apache.seata.rm.datasource.sql.struct.cache.KingbaseTableMetaCache \ No newline at end of file +org.apache.seata.rm.datasource.sql.struct.cache.OscarTableMetaCache +org.apache.seata.rm.datasource.sql.struct.cache.KingbaseTableMetaCache diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OscarInsertExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OscarInsertExecutorTest.java new file mode 100644 index 00000000000..0e27823212c --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/OscarInsertExecutorTest.java @@ -0,0 +1,446 @@ +/* + * 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 org.apache.seata.rm.datasource.exec; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.rm.datasource.ConnectionProxy; +import org.apache.seata.rm.datasource.PreparedStatementProxy; +import org.apache.seata.rm.datasource.StatementProxy; +import org.apache.seata.rm.datasource.exec.oscar.OscarInsertExecutor; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.struct.ColumnMeta; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.struct.TableMeta; +import org.apache.seata.sqlparser.util.JdbcConstants; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +import static org.mockito.Mockito.*; + +/** + * The type Oscar insert executor test. + */ +public class OscarInsertExecutorTest { + + private static final String ID_COLUMN = "id"; + private static final String USER_ID_COLUMN = "user_id"; + private static final String USER_NAME_COLUMN = "user_name"; + private static final String USER_STATUS_COLUMN = "user_status"; + private static final Integer PK_VALUE_ID = 100; + private static final Integer PK_VALUE_USER_ID = 200; + + private ConnectionProxy connectionProxy; + + private StatementProxy statementProxy; + + private SQLInsertRecognizer sqlInsertRecognizer; + + private StatementCallback statementCallback; + + private TableMeta tableMeta; + + private OscarInsertExecutor insertExecutor; + + private final int pkIndexId = 0; + + private final int pkIndexUserId = 1; + + private HashMap pkIndexMap; + + private HashMap multiPkIndexMap; + + @BeforeEach + public void init() { + connectionProxy = mock(ConnectionProxy.class); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.OSCAR); + + statementProxy = mock(PreparedStatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + + statementCallback = mock(StatementCallback.class); + sqlInsertRecognizer = mock(SQLInsertRecognizer.class); + tableMeta = mock(TableMeta.class); + insertExecutor = Mockito.spy(new OscarInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + pkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + }}; + + multiPkIndexMap = new HashMap() {{ + put(ID_COLUMN, pkIndexId); + put(USER_ID_COLUMN, pkIndexUserId); + }}; + } + + @Test + public void testPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + List pkValuesSeq = new ArrayList<>(); + pkValuesSeq.add(PK_VALUE_ID); + + doReturn(pkValuesSeq).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeq); + } + + @Test + public void testMultiPkValue_sequence() throws Exception { + mockInsertColumns(); + SqlSequenceExpr expr = mockParametersMultiPkWithSeq(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + List pkValuesSeqId = new ArrayList<>(); + pkValuesSeqId.add(PK_VALUE_ID); + List pkValuesSeqUserId = new ArrayList<>(); + pkValuesSeqUserId.add(PK_VALUE_USER_ID); + + doReturn(pkValuesSeqId).when(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + doReturn(pkValuesSeqUserId).when(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + + Map> pkValuesByColumn = insertExecutor.getPkValuesByColumn(); + verify(insertExecutor).getPkValuesBySequence(expr, ID_COLUMN); + verify(insertExecutor).getPkValuesBySequence(expr, USER_ID_COLUMN); + Assertions.assertEquals(pkValuesByColumn.get(ID_COLUMN), pkValuesSeqId); + Assertions.assertEquals(pkValuesByColumn.get(USER_ID_COLUMN), pkValuesSeqUserId); + } + + @Test + public void testPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN})); + doReturn(Arrays.asList(new Object[]{PK_VALUE_ID})).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + Map> pkValuesByAuto = insertExecutor.getPkValues(); + + verify(insertExecutor).getGeneratedKeys(ID_COLUMN); + Assertions.assertEquals(pkValuesByAuto.get(ID_COLUMN), Arrays.asList(new Object[]{PK_VALUE_ID})); + } + + @Test + public void testMultiPkValue_auto() throws Exception { + mockInsertColumns(); + mockParametersMultiPkWithAuto(); + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + doReturn(multiPkIndexMap).when(insertExecutor).getPkIndex(); + when(tableMeta.getPrimaryKeyOnlyName()).thenReturn(Arrays.asList(new String[]{ID_COLUMN, USER_ID_COLUMN})); + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValues(); + }); + + + } + + @Test + public void testStatement_pkValueByAuto_NotSupportYetException() throws Exception { + mockInsertColumns(); + mockStatementInsertRows(); + + statementProxy = mock(StatementProxy.class); + when(statementProxy.getConnectionProxy()).thenReturn(connectionProxy); + when(connectionProxy.getDbType()).thenReturn(JdbcConstants.ORACLE); + + insertExecutor = Mockito.spy(new OscarInsertExecutor(statementProxy, statementCallback, sqlInsertRecognizer)); + + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Map map = new HashMap<>(); + map.put(ID_COLUMN, mock(ColumnMeta.class)); + doReturn(map).when(tableMeta).getPrimaryKeyMap(); + + ResultSet rs = mock(ResultSet.class); + doReturn(rs).when(statementProxy).getGeneratedKeys(); + doReturn(false).when(rs).next(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getGeneratedKeys(ID_COLUMN); + }); + + doReturn(pkIndexMap).when(insertExecutor).getPkIndex(); + + Assertions.assertThrows(NotSupportYetException.class, () -> { + insertExecutor.getPkValuesByColumn(); + }); + + } + + @Test + public void testGetPkValues_SinglePk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock pk values from insert rows + Map> mockPkValuesFromColumn = new HashMap<>(); + mockPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + + // situation1: insert columns are empty + List columns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain the pk column + columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns are not empty and do not contain the pk column + columns = new ArrayList<>(); + columns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals( + Collections.singletonMap(ID_COLUMN, mockPkValuesAutoGenerated).entrySet(), + insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testGetPkValues_MultiPk() throws SQLException { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + List pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + + // mock all pk values from insert rows + Map> mockAllPkValuesFromColumn = new HashMap<>(); + mockAllPkValuesFromColumn.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + mockAllPkValuesFromColumn.put(USER_ID_COLUMN, Collections.singletonList(PK_VALUE_USER_ID + 1)); + doReturn(mockAllPkValuesFromColumn).when(insertExecutor).getPkValuesByColumn(); + + // mock pk values from auto increment + List mockPkValuesAutoGenerated_ID = Collections.singletonList(PK_VALUE_ID); + doReturn(mockPkValuesAutoGenerated_ID).when(insertExecutor).getGeneratedKeys(ID_COLUMN); + List mockPkValuesAutoGenerated_USER_ID = Collections.singletonList(PK_VALUE_USER_ID); + doReturn(mockPkValuesAutoGenerated_USER_ID).when(insertExecutor).getGeneratedKeys(USER_ID_COLUMN); + + // situation1: insert columns are empty + List insertColumns = new ArrayList<>(); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(true); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation2: insert columns contain all pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + Assertions.assertIterableEquals(mockAllPkValuesFromColumn.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation3: insert columns contain partial pk columns + insertColumns = new ArrayList<>(); + insertColumns.add(ID_COLUMN); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + Map> mockPkValuesFromColumn_ID = new HashMap<>(); + mockPkValuesFromColumn_ID.put(ID_COLUMN, Collections.singletonList(PK_VALUE_ID + 1)); + doReturn(mockPkValuesFromColumn_ID).when(insertExecutor).getPkValuesByColumn(); + + Map> expectPkValues = new HashMap<>(mockPkValuesFromColumn_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + + // situation4: insert columns are not empty and do not contain the pk column + insertColumns = new ArrayList<>(); + insertColumns.add(USER_NAME_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(insertColumns); + when(sqlInsertRecognizer.insertColumnsIsEmpty()).thenReturn(false); + + doReturn(new HashMap<>()).when(insertExecutor).getPkValuesByColumn(); + + expectPkValues = new HashMap<>(); + expectPkValues.put(ID_COLUMN, mockPkValuesAutoGenerated_ID); + expectPkValues.put(USER_ID_COLUMN, mockPkValuesAutoGenerated_USER_ID); + Assertions.assertIterableEquals(expectPkValues.entrySet(), insertExecutor.getPkValues().entrySet()); + } + + @Test + public void testContainsAnyPK() { + doReturn(tableMeta).when(insertExecutor).getTableMeta(); + + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + mockInsertColumns(); + doReturn(null).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + List pkColumns = new ArrayList<>(); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertFalse(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(USER_ID_COLUMN); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + + pkColumns = new ArrayList<>(); + pkColumns.add(ID_COLUMN); + pkColumns.add(System.currentTimeMillis() + ""); + doReturn(pkColumns).when(tableMeta).getPrimaryKeyOnlyName(); + Assertions.assertTrue(insertExecutor.containsAnyPk()); + } + + private List mockInsertColumns() { + List columns = new ArrayList<>(); + columns.add(ID_COLUMN); + columns.add(USER_ID_COLUMN); + columns.add(USER_NAME_COLUMN); + columns.add(USER_STATUS_COLUMN); + when(sqlInsertRecognizer.getInsertColumns()).thenReturn(columns); + return columns; + } + + private SqlSequenceExpr mockParametersPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private SqlSequenceExpr mockParametersMultiPkWithSeq() { + SqlSequenceExpr expr = new SqlSequenceExpr("seq", "nextval"); + Map> paramters = new HashMap(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(expr); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(expr); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + + return expr; + } + + private void mockParametersPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add("userId1"); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + private void mockParametersMultiPkWithAuto() { + Map> paramters = new HashMap<>(4); + ArrayList arrayList0 = new ArrayList<>(); + arrayList0.add(Null.get()); + ArrayList arrayList1 = new ArrayList<>(); + arrayList1.add(Null.get()); + ArrayList arrayList2 = new ArrayList<>(); + arrayList2.add("userName1"); + ArrayList arrayList3 = new ArrayList<>(); + arrayList3.add("userStatus1"); + paramters.put(1, arrayList0); + paramters.put(2, arrayList1); + paramters.put(3, arrayList2); + paramters.put(4, arrayList3); + PreparedStatementProxy psp = (PreparedStatementProxy) this.statementProxy; + when(psp.getParameters()).thenReturn(paramters); + + List> rows = new ArrayList<>(); + rows.add(Arrays.asList("?", "?", "?", "?")); + when(sqlInsertRecognizer.getInsertRows(multiPkIndexMap.values())).thenReturn(rows); + } + + private void mockStatementInsertRows() { + List> rows = new ArrayList<>(); + rows.add(Arrays.asList(Null.get(), "xx", "xx", "xx")); + when(sqlInsertRecognizer.getInsertRows(pkIndexMap.values())).thenReturn(rows); + } + + +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarDeleteRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarDeleteRecognizerTest.java new file mode 100644 index 00000000000..405198f5b55 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarDeleteRecognizerTest.java @@ -0,0 +1,198 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.oscar; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleArgumentExpr; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.oscar.OscarDeleteRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The type Oscar delete recognizer test. + */ +public class OscarDeleteRecognizerTest { + + private static final String DB_TYPE = "oscar"; + + @Test + public void testGetSqlType() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarDeleteRecognizer recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.DELETE); + } + + @Test + public void testGetTableAlias() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarDeleteRecognizer recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "delete from t where id = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarDeleteRecognizer recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetWhereCondition_0() { + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarDeleteRecognizer recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for normal sql + Assertions.assertEquals("id = ?", whereCondition); + + sql = "delete from t where id in (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + Map result = new HashMap(); + result.put(1, idParam); + return result; + } + }, new ArrayList<>()); + + //test for sql with in + Assertions.assertEquals("id IN (?)", whereCondition); + + sql = "delete from t where id between ? and ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + ArrayList idParam = new ArrayList<>(); + idParam.add(1); + ArrayList idParam2 = new ArrayList<>(); + idParam.add(2); + Map result = new HashMap(); + result.put(1, idParam); + result.put(2, idParam2); + return result; + } + }, new ArrayList<>()); + //test for sql with in + Assertions.assertEquals("id BETWEEN ? AND ?", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OscarDeleteRecognizer(s, deleteAst).getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return new HashMap<>(); + } + }, new ArrayList<>()); + }); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "delete from t"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarDeleteRecognizer recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + //test for no condition + Assertions.assertEquals("", whereCondition); + + sql = "delete from t where id = 1"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for normal sql + Assertions.assertEquals("id = 1", whereCondition); + + sql = "delete from t where id in (1)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + + //test for sql with in + Assertions.assertEquals("id IN (1)", whereCondition); + + sql = "delete from t where id between 1 and 2"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarDeleteRecognizer(sql, asts.get(0)); + whereCondition = recognizer.getWhereCondition(); + //test for sql with in + Assertions.assertEquals("id BETWEEN 1 AND 2", whereCondition); + + //test for exception + Assertions.assertThrows(IllegalArgumentException.class, () -> { + String s = "delete from t where id in (1)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLDeleteStatement deleteAst = (SQLDeleteStatement) sqlStatements.get(0); + deleteAst.setWhere(new OracleArgumentExpr()); + new OscarDeleteRecognizer(s, deleteAst).getWhereCondition(); + }); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarInsertRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarInsertRecognizerTest.java new file mode 100644 index 00000000000..2a025866816 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarInsertRecognizerTest.java @@ -0,0 +1,130 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.oscar; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryDoubleExpr; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.oscar.OscarInsertRecognizer; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.List; + +/** + * The type Oscar insert recognizer test. + */ +public class OscarInsertRecognizerTest { + + private static final String DB_TYPE = "oscar"; + + @Test + public void testGetSqlType() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.INSERT); + } + + @Test + public void testGetTableAlias() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "insert into t(id) values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } + + @Test + public void testGetInsertColumns() { + + //test for no column + String sql = "insert into t values (?)"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + List insertColumns = recognizer.getInsertColumns(); + Assertions.assertNull(insertColumns); + + //test for normal + sql = "insert into t(a) values (?)"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + + recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + insertColumns = recognizer.getInsertColumns(); + Assertions.assertEquals(1, insertColumns.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getColumns().add(new OracleBinaryDoubleExpr()); + + OscarInsertRecognizer oscarInsertRecognizer = new OscarInsertRecognizer(s, sqlInsertStatement); + oscarInsertRecognizer.getInsertColumns(); + }); + } + + @Test + public void testGetInsertRows() { + final int pkIndex = 0; + //test for null value + String sql = "insert into t(id, no, name, age, time) values (id_seq.nextval, null, 'a', ?, now())"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer recognizer = new OscarInsertRecognizer(sql, asts.get(0)); + List> insertRows = recognizer.getInsertRows(Collections.singletonList(pkIndex)); + Assertions.assertEquals(1, insertRows.size()); + + //test for exception + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "insert into t(a) values (?)"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLInsertStatement sqlInsertStatement = (SQLInsertStatement)sqlStatements.get(0); + sqlInsertStatement.getValuesList().get(0).getValues().set(pkIndex, new OracleBinaryDoubleExpr()); + + OscarInsertRecognizer oscarInsertRecognizer = new OscarInsertRecognizer(s, sqlInsertStatement); + oscarInsertRecognizer.getInsertRows(Collections.singletonList(pkIndex)); + }); + } + + @Test + public void testNotPlaceholder_giveValidPkIndex() { + String sql = "insert into test(create_time) values(sysdate)"; + List sqlStatements = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarInsertRecognizer oscar = new OscarInsertRecognizer(sql, sqlStatements.get(0)); + List> insertRows = oscar.getInsertRows(Collections.singletonList(-1)); + Assertions.assertTrue(insertRows.get(0).get(0) instanceof NotPlaceholderExpr); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarSelectForUpdateRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarSelectForUpdateRecognizerTest.java new file mode 100644 index 00000000000..604dcd99132 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarSelectForUpdateRecognizerTest.java @@ -0,0 +1,111 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.oscar; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.oscar.OscarSelectForUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The type Oscar select for update recognizer test. + */ +public class OscarSelectForUpdateRecognizerTest { + + private static final String DB_TYPE = "oscar"; + + @Test + public void testGetSqlType() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarSelectForUpdateRecognizer recognizer = new OscarSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.SELECT_FOR_UPDATE); + } + + + @Test + public void testGetWhereCondition_0() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarSelectForUpdateRecognizer recognizer = new OscarSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + String sql = "select * from t for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarSelectForUpdateRecognizer recognizer = new OscarSelectForUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + + //test for select was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t for update"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.setSelect(null); + new OscarSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + + //test for query was null + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "select * from t"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLSelectStatement selectAst = (SQLSelectStatement) sqlStatements.get(0); + selectAst.getSelect().setQuery(null); + new OscarSelectForUpdateRecognizer(s, selectAst).getWhereCondition(); + }); + } + + @Test + public void testGetTableAlias() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarSelectForUpdateRecognizer recognizer = new OscarSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "select * from t where id = ? for update"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarSelectForUpdateRecognizer recognizer = new OscarSelectForUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarUpdateRecognizerTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarUpdateRecognizerTest.java new file mode 100644 index 00000000000..120086affc5 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/sql/druid/oscar/OscarUpdateRecognizerTest.java @@ -0,0 +1,157 @@ +/* + * 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 org.apache.seata.rm.datasource.sql.druid.oscar; + +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.druid.oscar.OscarUpdateRecognizer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The Oscar Update Recognizer Test. + */ +public class OscarUpdateRecognizerTest { + + private static final String DB_TYPE = "oscar"; + + @Test + public void testGetSqlType() { + String sql = "update t set n = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getSQLType(), SQLType.UPDATE); + } + + @Test + public void testGetUpdateColumns() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + List updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + // test with alias + sql = "update t set a.a = ?, a.b = ?, a.c = ?"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + updateColumns = recognizer.getUpdateColumns(); + Assertions.assertEquals(updateColumns.size(), 3); + + //test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = a"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement) sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setColumn(new OracleCursorExpr()); + } + OscarUpdateRecognizer oscarUpdateRecognizer = new OscarUpdateRecognizer(s, sqlUpdateStatement); + oscarUpdateRecognizer.getUpdateColumns(); + }); + } + + @Test + public void testGetUpdateValues() { + // test with normal + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + List updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with values + sql = "update t set a = 1, b = 2, c = 3"; + asts = SQLUtils.parseStatements(sql, DB_TYPE); + recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + updateValues = recognizer.getUpdateValues(); + Assertions.assertEquals(updateValues.size(), 3); + + // test with error + Assertions.assertThrows(SQLParsingException.class, () -> { + String s = "update t set a = ?"; + List sqlStatements = SQLUtils.parseStatements(s, DB_TYPE); + SQLUpdateStatement sqlUpdateStatement = (SQLUpdateStatement)sqlStatements.get(0); + List updateSetItems = sqlUpdateStatement.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + updateSetItem.setValue(new OracleCursorExpr()); + } + OscarUpdateRecognizer oscarUpdateRecognizer = new OscarUpdateRecognizer(s, sqlUpdateStatement); + oscarUpdateRecognizer.getUpdateValues(); + }); + } + + @Test + public void testGetWhereCondition_0() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(new ParametersHolder() { + @Override + public Map> getParameters() { + return null; + } + }, new ArrayList<>()); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetWhereCondition_1() { + + String sql = "update t set a = 1"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + String whereCondition = recognizer.getWhereCondition(); + + Assertions.assertEquals("", whereCondition); + } + + @Test + public void testGetTableAlias() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + Assertions.assertNull(recognizer.getTableAlias()); + } + + @Test + public void testGetTableName() { + String sql = "update t set a = ?, b = ?, c = ?"; + List asts = SQLUtils.parseStatements(sql, DB_TYPE); + + OscarUpdateRecognizer recognizer = new OscarUpdateRecognizer(sql, asts.get(0)); + Assertions.assertEquals(recognizer.getTableName(), "t"); + } +} diff --git a/script/client/at/db/oscar.sql b/script/client/at/db/oscar.sql new file mode 100644 index 00000000000..ff62de40f76 --- /dev/null +++ b/script/client/at/db/oscar.sql @@ -0,0 +1,43 @@ +-- +-- 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. +-- +-- for AT mode you must to init this sql for you business database. the seata server not need it. +CREATE TABLE UNDO_LOG +( + ID numeric(19,0) NOT NULL, + BRANCH_ID numeric(19,0) NOT NULL, + XID character varying(128) NOT NULL, + "CONTEXT" character varying(128) NOT NULL, + ROLLBACK_INFO blob NOT NULL, + LOG_STATUS numeric(10,0) NOT NULL, + LOG_CREATED timestamp(0) without time zone NOT NULL, + LOG_MODIFIED timestamp(0) without time zone NOT NULL, + CONSTRAINT UNDO_LOG_PKEY PRIMARY KEY (ID), + CONSTRAINT UX_UNDO_LOG UNIQUE (XID, BRANCH_ID) +); + +CREATE INDEX ix_log_created ON UNDO_LOG(LOG_CREATED); +COMMENT ON TABLE UNDO_LOG IS 'AT transaction mode undo table'; +COMMENT ON COLUMN UNDO_LOG.BRANCH_ID is 'branch transaction id'; +COMMENT ON COLUMN UNDO_LOG.XID is 'global transaction id'; +COMMENT ON COLUMN UNDO_LOG.CONTEXT is 'undo_log context,such as serialization'; +COMMENT ON COLUMN UNDO_LOG.ROLLBACK_INFO is 'rollback info'; +COMMENT ON COLUMN UNDO_LOG.LOG_STATUS is '0:normal status,1:defense status'; +COMMENT ON COLUMN UNDO_LOG.LOG_CREATED is 'create datetime'; +COMMENT ON COLUMN UNDO_LOG.LOG_MODIFIED is 'modify datetime'; + +-- Generate ID using sequence and trigger +CREATE SEQUENCE UNDO_LOG_SEQ START WITH 1 INCREMENT BY 1; \ No newline at end of file diff --git a/script/server/db/oscar.sql b/script/server/db/oscar.sql new file mode 100644 index 00000000000..a65b5e8b0d5 --- /dev/null +++ b/script/server/db/oscar.sql @@ -0,0 +1,94 @@ +-- +-- 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. +-- +-- -------------------------------- The script used when storeMode is 'db' -------------------------------- +-- the table to store GlobalSession data +CREATE TABLE global_table +( + XID VARCHAR2(128) NOT NULL, + TRANSACTION_ID NUMBER(19), + STATUS NUMBER(3) NOT NULL, + APPLICATION_ID VARCHAR2(32), + TRANSACTION_SERVICE_GROUP VARCHAR2(32), + TRANSACTION_NAME VARCHAR2(128), + TIMEOUT NUMBER(10), + BEGIN_TIME NUMBER(19), + APPLICATION_DATA VARCHAR2(2000), + GMT_CREATE TIMESTAMP(0), + GMT_MODIFIED TIMESTAMP(0), + PRIMARY KEY (XID) +); + +CREATE INDEX idx_status_gmt_modified ON global_table (STATUS, GMT_MODIFIED); +CREATE INDEX idx_transaction_id ON global_table (TRANSACTION_ID); + +-- the table to store BranchSession data +CREATE TABLE branch_table +( + BRANCH_ID NUMBER(19) NOT NULL, + XID VARCHAR2(128) NOT NULL, + TRANSACTION_ID NUMBER(19), + RESOURCE_GROUP_ID VARCHAR2(32), + RESOURCE_ID VARCHAR2(256), + BRANCH_TYPE VARCHAR2(8), + STATUS NUMBER(3), + CLIENT_ID VARCHAR2(64), + APPLICATION_DATA VARCHAR2(2000), + GMT_CREATE TIMESTAMP(6), + GMT_MODIFIED TIMESTAMP(6), + PRIMARY KEY (BRANCH_ID) +); + +CREATE INDEX idx_xid ON branch_table (XID); + +-- the table to store lock data +CREATE TABLE lock_table +( + ROW_KEY VARCHAR2(128) NOT NULL, + XID VARCHAR2(128), + TRANSACTION_ID NUMBER(19), + BRANCH_ID NUMBER(19) NOT NULL, + RESOURCE_ID VARCHAR2(256), + TABLE_NAME VARCHAR2(32), + PK VARCHAR2(36), + STATUS NUMBER(3) DEFAULT 0 NOT NULL, + GMT_CREATE TIMESTAMP(0), + GMT_MODIFIED TIMESTAMP(0), + PRIMARY KEY (ROW_KEY) +); + +comment on column lock_table.STATUS is '0:locked ,1:rollbacking'; +CREATE INDEX idx_branch_id ON lock_table (BRANCH_ID); +CREATE INDEX idx_lock_table_xid ON lock_table (XID); +CREATE INDEX idx_status ON lock_table (STATUS); + +CREATE TABLE distributed_lock ( + LOCK_KEY VARCHAR2(20) NOT NULL, + LOCK_VALUE VARCHAR2(20) NOT NULL, + EXPIRE DECIMAL(18) NOT NULL, + PRIMARY KEY (LOCK_KEY) +); + +INSERT INTO distributed_lock (LOCK_KEY, LOCK_VALUE, EXPIRE) VALUES ('AsyncCommitting', ' ', 0); +INSERT INTO distributed_lock (LOCK_KEY, LOCK_VALUE, EXPIRE) VALUES ('RetryCommitting', ' ', 0); +INSERT INTO distributed_lock (LOCK_KEY, LOCK_VALUE, EXPIRE) VALUES ('RetryRollbacking', ' ', 0); +INSERT INTO distributed_lock (LOCK_KEY, LOCK_VALUE, EXPIRE) VALUES ('TxTimeoutCheck', ' ', 0); +CREATE TABLE VGROUP_TABLE +( + VGROUP VARCHAR2(255) PRIMARY KEY, + NAMESPACE VARCHAR2(255), + CLUSTER VARCHAR2(255) +); \ No newline at end of file diff --git a/sqlparser/seata-sqlparser-core/src/main/java/org/apache/seata/sqlparser/util/JdbcConstants.java b/sqlparser/seata-sqlparser-core/src/main/java/org/apache/seata/sqlparser/util/JdbcConstants.java index d923f8918f7..e5d50257318 100644 --- a/sqlparser/seata-sqlparser-core/src/main/java/org/apache/seata/sqlparser/util/JdbcConstants.java +++ b/sqlparser/seata-sqlparser-core/src/main/java/org/apache/seata/sqlparser/util/JdbcConstants.java @@ -84,4 +84,6 @@ public interface JdbcConstants { String POLARDB = "polardb"; String POLARDBX = "polardb-x"; + + String OSCAR = "oscar"; } diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/BaseOscarRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/BaseOscarRecognizer.java new file mode 100644 index 00000000000..a0a3b505354 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/BaseOscarRecognizer.java @@ -0,0 +1,196 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.expr.SQLInSubQueryExpr; +import com.alibaba.druid.sql.ast.expr.SQLQueryExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLMergeStatement; +import com.alibaba.druid.sql.ast.statement.SQLReplaceStatement; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectJoin; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleSelectSubqueryTableSource; +import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitor; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleASTVisitorAdapter; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.druid.BaseRecognizer; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.util.JdbcConstants; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The type Base oscar recognizer. + */ +public abstract class BaseOscarRecognizer extends BaseRecognizer { + + /** + * Instantiates a new oscar base recognizer + * + * @param originalSql the original sql + */ + public BaseOscarRecognizer(String originalSql) { + super(originalSql); + } + + public OracleOutputVisitor createOutputVisitor(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList, + final StringBuilder sb) { + + return new OracleOutputVisitor(sb) { + @Override + public boolean visit(SQLVariantRefExpr x) { + if ("?".equals(x.getName())) { + ArrayList oneParamValues = parametersHolder.getParameters().get(x.getIndex() + 1); + if (paramAppenderList.isEmpty()) { + oneParamValues.forEach(t -> paramAppenderList.add(new ArrayList<>())); + } + for (int i = 0; i < oneParamValues.size(); i++) { + Object o = oneParamValues.get(i); + paramAppenderList.get(i).add(o instanceof Null ? null : o); + } + } + return super.visit(x); + } + }; + } + + public String getWhereCondition(SQLExpr where, final ParametersHolder parametersHolder, final ArrayList> paramAppenderList) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, createOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + public String getWhereCondition(SQLExpr where) { + if (Objects.isNull(where)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeVisit(where, new OracleOutputVisitor(sb)); + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, new OracleOutputVisitor(sb)); + + return sb.toString(); + } + + protected String getOrderByCondition(SQLOrderBy sqlOrderBy, final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + if (Objects.isNull(sqlOrderBy)) { + return StringUtils.EMPTY; + } + + StringBuilder sb = new StringBuilder(); + executeOrderBy(sqlOrderBy, createOutputVisitor(parametersHolder, paramAppenderList, sb)); + return sb.toString(); + } + + @Override + public boolean isSqlSyntaxSupports() { + OracleASTVisitor visitor = new OracleASTVisitorAdapter() { + @Override + public boolean visit(OracleSelectJoin x) { + //just like: UPDATE table a INNER JOIN table b ON a.id = b.pid ... + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + + @Override + public boolean visit(OracleUpdateStatement x) { + if (x.getTableSource() instanceof OracleSelectSubqueryTableSource) { + //just like: "update (select a.id,a.name from a inner join b on a.id = b.id) t set t.name = 'xxx'" + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + List updateSetItems = x.getItems(); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + if (updateSetItem.getValue() instanceof SQLQueryExpr) { + //just like: "update a set a.id = (select id from b where a.pid = b.pid)" + throw new NotSupportYetException("not support the sql syntax with join table:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + } + return true; + } + + @Override + public boolean visit(SQLInSubQueryExpr x) { + //just like: ...where id in (select id from t) + throw new NotSupportYetException("not support the sql syntax with InSubQuery:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + + @Override + public boolean visit(OracleSelectSubqueryTableSource x) { + //just like: select * from (select * from t) for update + throw new NotSupportYetException("not support the sql syntax with SubQuery:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + + @Override + public boolean visit(SQLReplaceStatement x) { + //just like: replace into t (id,dr) values (1,'2'), (2,'3') + throw new NotSupportYetException("not support the sql syntax with ReplaceStatement:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + + @Override + public boolean visit(SQLMergeStatement x) { + //just like: merge into ... WHEN MATCHED THEN ... + throw new NotSupportYetException("not support the sql syntax with MergeStatement:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + + @Override + public boolean visit(SQLInsertStatement x) { + if (null != x.getQuery()) { + //just like: insert into t select * from t1 + throw new NotSupportYetException("not support the sql syntax insert with query:" + x + + "\nplease see the doc about SQL restrictions https://seata.io/zh-cn/docs/user/sqlreference/dml.html"); + } + return true; + } + }; + getAst().accept(visitor); + return true; + } + + public String getDbType() { + return JdbcConstants.OSCAR; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarDeleteRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarDeleteRecognizer.java new file mode 100644 index 00000000000..fd9213a5349 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarDeleteRecognizer.java @@ -0,0 +1,137 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLDeleteRecognizer; +import org.apache.seata.sqlparser.SQLType; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type oscar delete recognizer. + * + */ +public class OscarDeleteRecognizer extends BaseOscarRecognizer implements SQLDeleteRecognizer { + + private final SQLDeleteStatement ast; + + /** + * Instantiates a new Oscar sql delete recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OscarDeleteRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLDeleteStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.DELETE; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of delete with join table"); + } + }; + SQLTableSource tableSource; + if (ast.getFrom() == null) { + tableSource = ast.getTableSource(); + } else { + tableSource = ast.getFrom(); + } + + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of delete with unknow"); + } + return sb.toString(); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //oscar does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oscar does not support order by yet + return null; + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarInsertRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarInsertRecognizer.java new file mode 100644 index 00000000000..9e731d95529 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarInsertRecognizer.java @@ -0,0 +1,164 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; +import com.alibaba.druid.sql.ast.expr.SQLSequenceExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.sqlparser.SQLInsertRecognizer; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.struct.NotPlaceholderExpr; +import org.apache.seata.sqlparser.struct.Null; +import org.apache.seata.sqlparser.struct.SqlMethodExpr; +import org.apache.seata.sqlparser.struct.SqlSequenceExpr; +import org.apache.seata.sqlparser.util.ColumnUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * The type oscar insert recognizer. + * + */ +public class OscarInsertRecognizer extends BaseOscarRecognizer implements SQLInsertRecognizer { + + private final SQLInsertStatement ast; + + /** + * Instantiates a new Oscar sql insert recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OscarInsertRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLInsertStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.INSERT; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit(ast.getTableSource()); + return sb.toString(); + } + + @Override + public boolean insertColumnsIsEmpty() { + return CollectionUtils.isEmpty(ast.getColumns()); + } + + @Override + public List getInsertColumns() { + List columnSQLExprs = ast.getColumns(); + if (columnSQLExprs.isEmpty()) { + // INSERT INTO ta VALUES (...), without fields clarified + return null; + } + List list = new ArrayList<>(columnSQLExprs.size()); + for (SQLExpr expr : columnSQLExprs) { + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List> getInsertRows(Collection primaryKeyIndex) { + List valuesClauses = ast.getValuesList(); + List> rows = new ArrayList<>(valuesClauses.size()); + for (SQLInsertStatement.ValuesClause valuesClause : valuesClauses) { + List exprs = valuesClause.getValues(); + List row = new ArrayList<>(exprs.size()); + rows.add(row); + for (int i = 0, len = exprs.size(); i < len; i++) { + SQLExpr expr = exprs.get(i); + if (expr instanceof SQLNullExpr) { + row.add(Null.get()); + } else if (expr instanceof SQLValuableExpr) { + row.add(((SQLValuableExpr) expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + row.add(((SQLVariantRefExpr) expr).getName()); + } else if (expr instanceof SQLMethodInvokeExpr) { + row.add(SqlMethodExpr.get()); + } else if (expr instanceof SQLSequenceExpr) { + SQLSequenceExpr sequenceExpr = (SQLSequenceExpr) expr; + String sequence = sequenceExpr.getSequence().getSimpleName(); + String function = sequenceExpr.getFunction().name; + row.add(new SqlSequenceExpr(sequence, function)); + } else { + if (primaryKeyIndex.contains(i)) { + wrapSQLParsingException(expr); + } + row.add(NotPlaceholderExpr.get()); + } + } + } + return rows; + } + + @Override + public List getInsertParamsValue() { + return null; + } + + @Override + public List getDuplicateKeyUpdate() { + return null; + } + + @Override + public List getInsertColumnsUnEscape() { + List insertColumns = getInsertColumns(); + return ColumnUtils.delEscape(insertColumns, getDbType()); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarOperateRecognizerHolder.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarOperateRecognizerHolder.java new file mode 100644 index 00000000000..4cffed03774 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarOperateRecognizerHolder.java @@ -0,0 +1,55 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.sqlparser.SQLRecognizer; +import org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder; +import org.apache.seata.sqlparser.util.JdbcConstants; + +/** + * The Type OscarOperateRecognizerHolder + * + */ +@LoadLevel(name = JdbcConstants.OSCAR) +public class OscarOperateRecognizerHolder implements SQLOperateRecognizerHolder { + + @Override + public SQLRecognizer getDeleteRecognizer(String sql, SQLStatement ast) { + return new OscarDeleteRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getInsertRecognizer(String sql, SQLStatement ast) { + return new OscarInsertRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getUpdateRecognizer(String sql, SQLStatement ast) { + return new OscarUpdateRecognizer(sql, ast); + } + + @Override + public SQLRecognizer getSelectForUpdateRecognizer(String sql, SQLStatement ast) { + if (((SQLSelectStatement) ast).getSelect().getFirstQueryBlock().isForUpdate()) { + return new OscarSelectForUpdateRecognizer(sql, ast); + } + return null; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarSelectForUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarSelectForUpdateRecognizer.java new file mode 100644 index 00000000000..6e30f25f9b4 --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarSelectForUpdateRecognizer.java @@ -0,0 +1,139 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLOrderBy; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLParsingException; +import org.apache.seata.sqlparser.SQLSelectRecognizer; +import org.apache.seata.sqlparser.SQLType; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type oscar select for update recognizer. + * + */ +public class OscarSelectForUpdateRecognizer extends BaseOscarRecognizer implements SQLSelectRecognizer { + + private final SQLSelectStatement ast; + + /** + * Instantiates a new Oscar sql select for update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OscarSelectForUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLSelectStatement)ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.SELECT_FOR_UPDATE; + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLExpr where = selectQueryBlock.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy); + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + SQLOrderBy sqlOrderBy = getSelect().getOrderBy(); + return super.getOrderByCondition(sqlOrderBy, parametersHolder, paramAppenderList); + } + + private SQLSelectQueryBlock getSelect() { + SQLSelect select = ast.getSelect(); + if (select == null) { + throw new SQLParsingException("should never happen!"); + } + SQLSelectQueryBlock selectQueryBlock = select.getQueryBlock(); + if (selectQueryBlock == null) { + throw new SQLParsingException("should never happen!"); + } + return selectQueryBlock; + } + + @Override + public String getTableAlias() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + return tableSource.getAlias(); + } + + @Override + public String getTableName() { + SQLSelectQueryBlock selectQueryBlock = getSelect(); + SQLTableSource tableSource = selectQueryBlock.getFrom(); + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + }; + visitor.visit((SQLExprTableSource)tableSource); + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarUpdateRecognizer.java b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarUpdateRecognizer.java new file mode 100644 index 00000000000..2f22469238f --- /dev/null +++ b/sqlparser/seata-sqlparser-druid/src/main/java/org/apache/seata/sqlparser/druid/oscar/OscarUpdateRecognizer.java @@ -0,0 +1,184 @@ +/* + * 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 org.apache.seata.sqlparser.druid.oscar; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; +import com.alibaba.druid.sql.ast.expr.SQLValuableExpr; +import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem; +import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.druid.sql.dialect.oracle.visitor.OracleOutputVisitor; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.sqlparser.ParametersHolder; +import org.apache.seata.sqlparser.SQLType; +import org.apache.seata.sqlparser.SQLUpdateRecognizer; +import org.apache.seata.sqlparser.util.ColumnUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * The type oscar update recognizer. + * + */ +public class OscarUpdateRecognizer extends BaseOscarRecognizer implements SQLUpdateRecognizer { + + private SQLUpdateStatement ast; + + /** + * Instantiates a new Oscar sql update recognizer. + * + * @param originalSQL the original sql + * @param ast the ast + */ + public OscarUpdateRecognizer(String originalSQL, SQLStatement ast) { + super(originalSQL); + this.ast = (SQLUpdateStatement) ast; + } + + @Override + public SQLType getSQLType() { + return SQLType.UPDATE; + } + + @Override + public List getUpdateColumns() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getColumn(); + if (expr instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)expr).getName()); + } else if (expr instanceof SQLPropertyExpr) { + // This is alias case, like UPDATE xxx_tbl a SET a.name = ? WHERE a.id = ? + SQLExpr owner = ((SQLPropertyExpr)expr).getOwner(); + if (owner instanceof SQLIdentifierExpr) { + list.add(((SQLIdentifierExpr)owner).getName() + "." + ((SQLPropertyExpr)expr).getName()); + //This is table Field Full path, like update xxx_database.xxx_tbl set xxx_database.xxx_tbl.xxx_field... + } else if (((SQLPropertyExpr) expr).getOwnerName().split("\\.").length > 1) { + list.add(((SQLPropertyExpr)expr).getOwnerName() + "." + ((SQLPropertyExpr)expr).getName()); + } + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateValues() { + List updateSetItems = ast.getItems(); + List list = new ArrayList<>(updateSetItems.size()); + for (SQLUpdateSetItem updateSetItem : updateSetItems) { + SQLExpr expr = updateSetItem.getValue(); + if (expr instanceof SQLValuableExpr) { + list.add(((SQLValuableExpr)expr).getValue()); + } else if (expr instanceof SQLVariantRefExpr) { + list.add(new VMarker()); + } else { + wrapSQLParsingException(expr); + } + } + return list; + } + + @Override + public List getUpdateColumnsUnEscape() { + List updateColumns = getUpdateColumns(); + return ColumnUtils.delEscape(updateColumns, getDbType()); + } + + @Override + public String getWhereCondition(final ParametersHolder parametersHolder, + final ArrayList> paramAppenderList) { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where, parametersHolder, paramAppenderList); + } + + @Override + public String getWhereCondition() { + SQLExpr where = ast.getWhere(); + return super.getWhereCondition(where); + } + + @Override + public String getLimitCondition() { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getLimitCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oscar does not support limit or rownum yet + return null; + } + + @Override + public String getOrderByCondition() { + //oscar does not support order by yet + return null; + } + + @Override + public String getOrderByCondition(ParametersHolder parametersHolder, ArrayList> paramAppenderList) { + //oscar does not support order by yet + return null; + } + + @Override + public String getTableAlias() { + return ast.getTableSource().getAlias(); + } + + @Override + public String getTableName() { + StringBuilder sb = new StringBuilder(); + OracleOutputVisitor visitor = new OracleOutputVisitor(sb) { + + @Override + public boolean visit(SQLExprTableSource x) { + printTableSourceExpr(x.getExpr()); + return false; + } + + @Override + public boolean visit(SQLJoinTableSource x) { + throw new NotSupportYetException("not support the syntax of update with join table"); + } + }; + SQLTableSource tableSource = ast.getTableSource(); + if (tableSource instanceof SQLExprTableSource) { + visitor.visit((SQLExprTableSource) tableSource); + } else if (tableSource instanceof SQLJoinTableSource) { + visitor.visit((SQLJoinTableSource) tableSource); + } else { + throw new NotSupportYetException("not support the syntax of update with unknow"); + } + return sb.toString(); + } + + @Override + protected SQLStatement getAst() { + return ast; + } +} diff --git a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder index a30fcf51bf7..4d33c59879f 100644 --- a/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder +++ b/sqlparser/seata-sqlparser-druid/src/main/resources/META-INF/services/org.apache.seata.sqlparser.druid.SQLOperateRecognizerHolder @@ -21,4 +21,5 @@ org.apache.seata.sqlparser.druid.postgresql.PostgresqlOperateRecognizerHolder org.apache.seata.sqlparser.druid.sqlserver.SqlServerOperateRecognizerHolder org.apache.seata.sqlparser.druid.polardbx.PolarDBXOperateRecognizerHolder org.apache.seata.sqlparser.druid.dm.DmOperateRecognizerHolder -org.apache.seata.sqlparser.druid.kingbase.KingbaseOperateRecognizerHolder \ No newline at end of file +org.apache.seata.sqlparser.druid.oscar.OscarOperateRecognizerHolder +org.apache.seata.sqlparser.druid.kingbase.KingbaseOperateRecognizerHolder From 87c1696382fab1f9f080328c0f283d3c6d6a5b65 Mon Sep 17 00:00:00 2001 From: jimin Date: Sat, 12 Oct 2024 10:08:51 +0800 Subject: [PATCH 18/54] optimize: remove incompatible licenses at build time (#6905) --- changes/en-us/2.x.md | 8 +++++--- changes/zh-cn/2.x.md | 7 ++++--- distribution/LICENSE | 6 ------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index a4a567fa78d..8ab2435a4a2 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -4,9 +4,9 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: -- [[#6876](https://github.com/apache/incubator-seata/pull/6876)]support kingbase -- [[#6881](https://github.com/apache/incubator-seata/pull/6881)]support grpc -- [[#6864](https://github.com/apache/incubator-seata/pull/6864)]support shentong database +- [[#6876](https://github.com/apache/incubator-seata/pull/6876)] support kingbase +- [[#6881](https://github.com/apache/incubator-seata/pull/6881)] support grpc +- [[#6864](https://github.com/apache/incubator-seata/pull/6864)] support shentong database ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package @@ -23,9 +23,11 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] fix log argument mismatch issue - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs +- [[#6905](https://github.com/apache/incubator-seata/pull/6905)] remove incompatible licenses at build time - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope + ### refactor: ### security: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index efb5f32ae50..f55ecc0b535 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -3,9 +3,9 @@ ### feature: -- [[#6876](https://github.com/apache/incubator-seata/pull/6876)]支持人大金仓数据库(kingbase) -- [[#6881](https://github.com/apache/incubator-seata/pull/6881)]全链路支持grpc -- [[#6864](https://github.com/apache/incubator-seata/pull/6864)]支持神通数据库(oscar) +- [[#6876](https://github.com/apache/incubator-seata/pull/6876)] 支持人大金仓数据库(kingbase) +- [[#6881](https://github.com/apache/incubator-seata/pull/6881)] client和server支持grpc协议 +- [[#6864](https://github.com/apache/incubator-seata/pull/6864)] 支持神通数据库(oscar) ### bugfix: @@ -25,6 +25,7 @@ - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] 修复日志参数不匹配问题 - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 +- [[#6905](https://github.com/apache/incubator-seata/pull/6905)] 移除构建期不兼容的 license - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope diff --git a/distribution/LICENSE b/distribution/LICENSE index f4605ffca41..2c483b9217a 100644 --- a/distribution/LICENSE +++ b/distribution/LICENSE @@ -490,12 +490,6 @@ BSD-3-Clause licenses org.antlr:antlr4-runtime 4.8 BSD-3-Clause org.abego.treelayout:org.abego.treelayout.core 1.0.3 BSD-3-Clause -======================================================================== -CC-BY-4.0 licenses -======================================================================== - - caniuse-lite 1.0.30001589 CC-BY-4.0 - ======================================================================== CDDL-1.0 licenses ======================================================================== From 66bd3a407a327b0ea4a765dbe28a660d69f43859 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Sat, 12 Oct 2024 15:46:13 +0800 Subject: [PATCH 19/54] optimize: fix some typos in project (#6911) --- changes/en-us/2.x.md | 3 ++- changes/zh-cn/2.x.md | 3 ++- .../src/main/java/io/seata/tm/api/GlobalTransaction.java | 2 +- .../java/org/apache/seata/config/ConfigurationFactory.java | 6 +++--- .../apache/seata/console/security/CustomUserDetails.java | 2 +- .../console/security/CustomUserDetailsServiceImpl.java | 2 +- .../seata/core/exception/TransactionExceptionCode.java | 2 +- .../main/java/org/apache/seata/core/protocol/Protocol.java | 2 +- .../apache/seata/core/rpc/netty/NettyClientBootstrap.java | 4 ++-- .../rm/datasource/undo/oracle/OracleUndoInsertExecutor.java | 2 +- .../seata/rm/datasource/exec/MySQLInsertExecutorTest.java | 2 +- .../proctrl/eventing/impl/ProcessCtrlEventPublisher.java | 2 +- .../saga/statelang/parser/impl/ServiceTaskStateParser.java | 2 +- .../java/org/apache/seata/server/session/GlobalSession.java | 2 +- .../org/apache/seata/server/storage/SessionConverter.java | 4 ++-- .../org/apache/seata/sqlparser/struct/ColumnMetaTest.java | 2 +- .../java/org/apache/seata/mockserver/MockCoordinator.java | 2 +- .../seata/mockserver/controller/MockHelpController.java | 2 +- .../org/apache/seata/core/rpc/netty/TmNettyClientTest.java | 2 +- .../seata/core/rpc/netty/mockserver/MockGrpcServerTest.java | 2 +- .../seata/core/rpc/netty/mockserver/TmClientTest.java | 2 +- .../java/org/apache/seata/tm/api/GlobalTransaction.java | 2 +- 22 files changed, 28 insertions(+), 26 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 8ab2435a4a2..5e0fdf97518 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -25,7 +25,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] remove incompatible licenses at build time - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope - +- [[#6911](https://github.com/apache/incubator-seata/pull/6911)] fix some typos in project ### refactor: @@ -48,6 +48,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) - [dsomehan](https://github.com/dsomehan) +- [psxjoy](https://github.com/psxjoy) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index f55ecc0b535..8d5b402cf25 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -27,6 +27,7 @@ - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] 移除构建期不兼容的 license - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope +- [[#6911](https://github.com/apache/incubator-seata/pull/6911)] 修正项目中的部分拼写错误 ### refactor: @@ -50,7 +51,7 @@ - [xjlgod](https://github.com/xjlgod) - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) - [dsomehan](https://github.com/dsomehan) - +- [psxjoy](https://github.com/psxjoy) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/compatible/src/main/java/io/seata/tm/api/GlobalTransaction.java b/compatible/src/main/java/io/seata/tm/api/GlobalTransaction.java index bd563b9531e..dd4f55fdeb2 100644 --- a/compatible/src/main/java/io/seata/tm/api/GlobalTransaction.java +++ b/compatible/src/main/java/io/seata/tm/api/GlobalTransaction.java @@ -82,7 +82,7 @@ public interface GlobalTransaction extends BaseTransaction { /** * Suspend the global transaction. * - * @param clean the clean if true, clean the transaction context. otherwise,supend only + * @param clean the clean if true, clean the transaction context. otherwise,suspend only * @return the SuspendedResourcesHolder which holds the suspend resources * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown * @see SuspendedResourcesHolder diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationFactory.java index a05235afbcd..e7ba659bc7b 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationFactory.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationFactory.java @@ -59,7 +59,7 @@ public final class ConfigurationFactory { public static volatile FileConfiguration ORIGIN_FILE_INSTANCE = null; static { - initOriginConfiguraction(); + initOriginConfiguration(); load(); maybeNeedOriginFileInstance(); } @@ -83,7 +83,7 @@ private static void load() { CURRENT_FILE_INSTANCE = extConfiguration == null ? configuration : extConfiguration; } - private static void initOriginConfiguraction() { + private static void initOriginConfiguration() { String seataConfigName = System.getProperty(SYSTEM_PROPERTY_SEATA_CONFIG_NAME); if (seataConfigName == null) { seataConfigName = System.getenv(ENV_SEATA_CONFIG_NAME); @@ -225,7 +225,7 @@ private static Configuration getNonSpringConfiguration(String configTypeName) { public static void reload() { ConfigurationCache.clear(); - initOriginConfiguraction(); + initOriginConfiguration(); load(); maybeNeedOriginFileInstance(); instance = null; diff --git a/console/src/main/java/org/apache/seata/console/security/CustomUserDetails.java b/console/src/main/java/org/apache/seata/console/security/CustomUserDetails.java index 7218feea327..f61ad30986e 100644 --- a/console/src/main/java/org/apache/seata/console/security/CustomUserDetails.java +++ b/console/src/main/java/org/apache/seata/console/security/CustomUserDetails.java @@ -23,7 +23,7 @@ import org.springframework.security.core.userdetails.UserDetails; /** - * custem user + * custom user * */ public class CustomUserDetails implements UserDetails { diff --git a/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java b/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java index fbc35832ee5..a001b4e116a 100644 --- a/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java +++ b/console/src/main/java/org/apache/seata/console/security/CustomUserDetailsServiceImpl.java @@ -26,7 +26,7 @@ import org.springframework.stereotype.Service; /** - * Custem user service + * Custom user service * */ @Service diff --git a/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java b/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java index 9c2e63d753b..16a2e899dc6 100644 --- a/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java +++ b/core/src/main/java/org/apache/seata/core/exception/TransactionExceptionCode.java @@ -105,7 +105,7 @@ public enum TransactionExceptionCode { /** * Failed to lock global transaction exception code. */ - FailedLockGlobalTranscation, + FailedLockGlobalTransaction, /** * FailedWriteSession diff --git a/core/src/main/java/org/apache/seata/core/protocol/Protocol.java b/core/src/main/java/org/apache/seata/core/protocol/Protocol.java index fe3cc000cfc..fa3bf5dd3ca 100644 --- a/core/src/main/java/org/apache/seata/core/protocol/Protocol.java +++ b/core/src/main/java/org/apache/seata/core/protocol/Protocol.java @@ -24,7 +24,7 @@ public enum Protocol { /** * grpc */ - GPRC("grpc"), + GRPC("grpc"), /** * seata diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java index 0fbd9ff0795..990b2449546 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java @@ -139,7 +139,7 @@ public void start() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); - if (nettyClientConfig.getProtocol().equals(Protocol.GPRC.value)) { + if (nettyClientConfig.getProtocol().equals(Protocol.GRPC.value)) { pipeline.addLast(Http2FrameCodecBuilder.forClient().build()) .addLast(new Http2MultiplexHandler(new ChannelDuplexHandler())); } else { @@ -191,7 +191,7 @@ public Channel getNewChannel(InetSocketAddress address) { channel = f.channel(); } - if (nettyClientConfig.getProtocol().equals(Protocol.GPRC.value)) { + if (nettyClientConfig.getProtocol().equals(Protocol.GRPC.value)) { Http2StreamChannelBootstrap bootstrap = new Http2StreamChannelBootstrap(channel); bootstrap.handler(new ChannelInboundHandlerAdapter() { @Override diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java index 16392ebd690..3c03f279071 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/oracle/OracleUndoInsertExecutor.java @@ -33,7 +33,7 @@ import org.apache.seata.sqlparser.util.JdbcConstants; /** - * The type oralce undo insert executor. + * The type oracle undo insert executor. * */ public class OracleUndoInsertExecutor extends AbstractUndoExecutor { diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertExecutorTest.java index 7d90502549f..babb6f59142 100644 --- a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertExecutorTest.java +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/MySQLInsertExecutorTest.java @@ -225,7 +225,7 @@ public void testBeforeAndAfterImageColumnWithQuote() throws SQLException { @Test public void testBeforeAndAfterImageUpperColumn() throws SQLException { - String sql = "insert into table_insert_executor_test(ID, USER_ID, NMAE, SEX) values (1, 1, 'will', 1)"; + String sql = "insert into table_insert_executor_test(ID, USER_ID, NAME, SEX) values (1, 1, 'will', 1)"; List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); MySQLInsertRecognizer recognizer = new MySQLInsertRecognizer(sql, asts.get(0)); newInsertExecutor = new MySQLInsertExecutor(newStatementProxy, (statement, args) -> null, recognizer); diff --git a/saga/seata-saga-processctrl/src/main/java/org/apache/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java b/saga/seata-saga-processctrl/src/main/java/org/apache/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java index 96ae269ce06..5b519ae68de 100644 --- a/saga/seata-saga-processctrl/src/main/java/org/apache/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java +++ b/saga/seata-saga-processctrl/src/main/java/org/apache/seata/saga/proctrl/eventing/impl/ProcessCtrlEventPublisher.java @@ -22,7 +22,7 @@ import org.apache.seata.saga.proctrl.eventing.EventPublisher; /** - * ProcessCtrl Event Pulisher + * ProcessCtrl Event Publisher * */ public class ProcessCtrlEventPublisher implements EventPublisher { diff --git a/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java b/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java index 8b5a1338ea1..be8548eda76 100644 --- a/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java +++ b/saga/seata-saga-statelang/src/main/java/org/apache/seata/saga/statelang/parser/impl/ServiceTaskStateParser.java @@ -24,7 +24,7 @@ import org.apache.seata.saga.statelang.parser.StateParser; /** - * ServcieTaskTask parser + * ServiceTaskTask parser * */ public class ServiceTaskStateParser extends AbstractTaskStateParser implements StateParser { diff --git a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java index 0da8f0ff6e6..8cfc0ecbc67 100644 --- a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java +++ b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java @@ -757,7 +757,7 @@ public void lock() throws TransactionException { } catch (InterruptedException e) { LOGGER.error("Interrupted error", e); } - throw new GlobalTransactionException(TransactionExceptionCode.FailedLockGlobalTranscation, "Lock global session failed"); + throw new GlobalTransactionException(TransactionExceptionCode.FailedLockGlobalTransaction, "Lock global session failed"); } public void unlock() { diff --git a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java index 732be622ee0..e468bb2567d 100644 --- a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java +++ b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java @@ -149,13 +149,13 @@ public static void convertToGlobalSessionVo(List result, List converToBranchSession(List branchSessions) { + public static Set convertToBranchSession(List branchSessions) { Set branchSessionVOs = new HashSet<>(branchSessions.size()); if (CollectionUtils.isNotEmpty(branchSessions)) { for (BranchSession branchSession : branchSessions) { diff --git a/sqlparser/seata-sqlparser-core/src/test/java/org/apache/seata/sqlparser/struct/ColumnMetaTest.java b/sqlparser/seata-sqlparser-core/src/test/java/org/apache/seata/sqlparser/struct/ColumnMetaTest.java index 50f41ff120d..5aad62b68ca 100644 --- a/sqlparser/seata-sqlparser-core/src/test/java/org/apache/seata/sqlparser/struct/ColumnMetaTest.java +++ b/sqlparser/seata-sqlparser-core/src/test/java/org/apache/seata/sqlparser/struct/ColumnMetaTest.java @@ -108,7 +108,7 @@ public void testSetGetColumnSize() { } @Test - public void testSetGetDemicalDigits() { + public void testSetGetDecimalDigits() { ColumnMeta columnMeta = new ColumnMeta(); columnMeta.setDecimalDigits(2); assertEquals(2, columnMeta.getDecimalDigits()); diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java b/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java index ac5d17fea82..181e824b15e 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java +++ b/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java @@ -245,7 +245,7 @@ public void setRemotingServer(RemotingServer remotingServer) { } - public void setExepectedResult(String xid, ResultCode expected) { + public void setExpectedResult(String xid, ResultCode expected) { expectedResultMap.put(xid, expected); } diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java b/test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java index 4bb3fb36981..0fd181c6e77 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java +++ b/test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java @@ -39,7 +39,7 @@ public String health() { @PostMapping("/expect/result") public String expectResult(@RequestParam String xid, @RequestParam int code) { - MockCoordinator.getInstance().setExepectedResult(xid, ResultCode.get(code)); + MockCoordinator.getInstance().setExpectedResult(xid, ResultCode.get(code)); return OK; } diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java index ae723d23800..9e855645305 100644 --- a/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java @@ -180,7 +180,7 @@ public void testSendMsgWithResponse() throws Exception { BranchRegisterRequest request = new BranchRegisterRequest(); request.setXid("127.0.0.1:8091:1249853"); request.setLockKey("lock key testSendMsgWithResponse"); - request.setResourceId("resoutceId1"); + request.setResourceId("resourceId1"); BranchRegisterResponse branchRegisterResponse = (BranchRegisterResponse) tmNettyRemotingClient.sendSyncRequest(request); Assertions.assertNotNull(branchRegisterResponse); Assertions.assertEquals(ResultCode.Failed, branchRegisterResponse.getResultCode()); diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java index 3744ddd270f..ec3c50b540b 100644 --- a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockGrpcServerTest.java @@ -49,7 +49,7 @@ public class MockGrpcServerTest { public static void before() { ConfigurationFactory.reload(); ConfigurationTestHelper.putConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL, String.valueOf(ProtocolTestConstants.MOCK_SERVER_PORT)); - ConfigurationTestHelper.putConfig(ConfigurationKeys.TRANSPORT_PROTOCOL, Protocol.GPRC.value); + ConfigurationTestHelper.putConfig(ConfigurationKeys.TRANSPORT_PROTOCOL, Protocol.GRPC.value); MockServer.start(ProtocolTestConstants.MOCK_SERVER_PORT); TmNettyRemotingClient.getInstance().destroy(); RmNettyRemotingClient.getInstance().destroy(); diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/TmClientTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/TmClientTest.java index 1bd2325a9df..84e05bad6d6 100644 --- a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/TmClientTest.java +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/TmClientTest.java @@ -64,7 +64,7 @@ public static void testTm() throws Exception { LOGGER.info("globalReport ok:" + globalReport); Assertions.assertEquals(globalReport, GlobalStatus.Committed); - MockCoordinator.getInstance().setExepectedResult(xid, ResultCode.Failed); + MockCoordinator.getInstance().setExpectedResult(xid, ResultCode.Failed); // GlobalStatus globalReport2 = tm.globalReport(xid, GlobalStatus.Committed); GlobalStatus rollback2 = tm.rollback(xid); diff --git a/tm/src/main/java/org/apache/seata/tm/api/GlobalTransaction.java b/tm/src/main/java/org/apache/seata/tm/api/GlobalTransaction.java index 69453e80346..e93814b23a1 100644 --- a/tm/src/main/java/org/apache/seata/tm/api/GlobalTransaction.java +++ b/tm/src/main/java/org/apache/seata/tm/api/GlobalTransaction.java @@ -81,7 +81,7 @@ public interface GlobalTransaction extends BaseTransaction { /** * Suspend the global transaction. * - * @param clean the clean if true, clean the transaction context. otherwise,supend only + * @param clean the clean if true, clean the transaction context. otherwise,suspend only * @return the SuspendedResourcesHolder which holds the suspend resources * @throws TransactionException Any exception that fails this will be wrapped with TransactionException and thrown * @see SuspendedResourcesHolder From 980ba23b82c372c3f201e2d7beb3d5be62e081f1 Mon Sep 17 00:00:00 2001 From: xingfudeshi Date: Mon, 14 Oct 2024 17:00:34 +0800 Subject: [PATCH 20/54] optimize:Use the openjdk image of eclipse-temurin as the base image (#6918) --- .github/workflows/publish-docker.yml | 18 ++++++++++++------ .travis.yml | 12 ++++++++---- changes/en-us/2.x.md | 3 +++ changes/zh-cn/2.x.md | 4 +++- distribution/docker/namingserver/Dockerfile | 6 +++--- distribution/docker/server/Dockerfile | 6 +++--- distribution/release-seata.xml | 14 ++++++++++++++ pom.xml | 17 +++++------------ 8 files changed, 51 insertions(+), 29 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index f5ab0427080..5aeb7f02e0f 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - java: [ 8, 17 ] + java: [ 8, 17, 21 ] steps: # step 1 - name: "Checkout" @@ -39,10 +39,9 @@ jobs: REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} run: | if [ "${{ github.ref_name }}" == "develop" ] || [ "${{ github.ref_name }}" == "snapshot" || [ "${{ github.ref_name }}" == "2.x" ]; then - ./mvnw -T 4C clean package -Dimage.name=openjdk:8u342 -Pimage -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + ./mvnw -T 4C clean package -Dimage.name=eclipse-temurin:8u422-b05-jdk -Pimage -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; else - ./mvnw -T 4C clean package -Dimage.name=openjdk:8u342 -Pimage,release-image-based-on-java8 -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; - ./mvnw -T 4C clean package -Dimage.name=openjdk:8u342-slim -Pimage,release-image-based-on-java8-slim -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + ./mvnw -T 4C clean package -Dimage.name=eclipse-temurin:8u422-b05-jdk -Pimage,release-image-based-on-java8 -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; fi # step 4 based on java17 - name: "Publish images to DockerHub based on java17" @@ -51,5 +50,12 @@ jobs: REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USER }} REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} run: | - ./mvnw -T 4C clean package -Dimage.name=openjdk:17.0.2 -Pimage,release-image-based-on-java17 -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; - ./mvnw -T 4C clean package -Dimage.name=openjdk:17.0.2-slim -Pimage,release-image-based-on-java17-slim -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + ./mvnw -T 4C clean package -Dimage.name=eclipse-temurin:17.0.12_7-jdk -Pimage,release-image-based-on-java17 -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; + # step 5 based on java21 + - name: "Publish images to DockerHub based on java21" + if: ${{ matrix.java == 21 && github.ref_name != 'develop' && github.ref_name != 'snapshot' && github.ref_name != '2.x' }} + env: + REGISTRY_USERNAME: ${{ secrets.DOCKERHUB_USER }} + REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + run: | + ./mvnw -T 4C clean package -Dimage.name=eclipse-temurin:21.0.4_7-jdk -Pimage,release-image-based-on-java21 -DskipTests -e -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn; diff --git a/.travis.yml b/.travis.yml index 3ad84c1a2f7..68e75c415d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,8 +17,9 @@ language: java sudo: false # faster builds jdk: - - openjdk11 - openjdk8 + - openjdk17 + - openjdk21 cache: directories: @@ -28,10 +29,13 @@ install: true before_script: - if [ "$TRAVIS_JDK_VERSION" == "openjdk8" ]; then - export IMAGE_NAME="openjdk:8-jre-slim"; + export IMAGE_NAME="eclipse-temurin:8u422-b05-jre"; fi - - if [ "$TRAVIS_JDK_VERSION" == "openjdk11" ]; then - export IMAGE_NAME="openjdk:11-jre-stretch"; + - if [ "$TRAVIS_JDK_VERSION" == "openjdk17" ]; then + export IMAGE_NAME="eclipse-temurin:17.0.12_7-jre"; + fi + - if [ "$TRAVIS_JDK_VERSION" == "openjdk21" ]; then + export IMAGE_NAME="eclipse-temurin:21.0.4_7-jre"; fi script: diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 5e0fdf97518..f33315450e4 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -26,6 +26,8 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] remove incompatible licenses at build time - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] fix some typos in project +- [[#6918](https://github.com/apache/incubator-seata/pull/6918)] Use the openjdk image of eclipse-temurin as the base image + ### refactor: @@ -49,6 +51,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) +- [xingfudeshi](https://github.com/xingfudeshi) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 8d5b402cf25..a99f7c3e6f9 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -28,7 +28,8 @@ - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] 移除构建期不兼容的 license - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] 修正项目中的部分拼写错误 - +- [[#6918](https://github.com/apache/incubator-seata/pull/6918)] 使用eclipse-temurin的openjdk镜像作为基础镜像 +- ### refactor: @@ -52,6 +53,7 @@ - [PleaseGiveMeTheCoke](https://github.com/PleaseGiveMeTheCoke) - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) +- [xingfudeshi](https://github.com/xingfudeshi) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/distribution/docker/namingserver/Dockerfile b/distribution/docker/namingserver/Dockerfile index 9fe827ae295..2ba04dea5b3 100644 --- a/distribution/docker/namingserver/Dockerfile +++ b/distribution/docker/namingserver/Dockerfile @@ -25,7 +25,7 @@ # 1. docker run --name=seata-namingserver -d seata-namingserver:2.2.0-dev # # https://hub.docker.com/r/apache/seata-namingserver -FROM openjdk:8u342 +FROM eclipse-temurin:8u422-b05-jdk # set label LABEL maintainer="Seata " @@ -37,8 +37,8 @@ ADD bin/ /seata-namingserver/bin ADD lib/ /seata-namingserver/lib ADD conf/ /seata-namingserver/conf ADD target /seata-namingserver/target -ADD ../LICENSE ./LICENSE -ADD ../NOTICE ./LICENSE +ADD LICENSE /LICENSE +ADD NOTICE /NOTICE # set extra environment ENV LOADER_PATH="/seata-namingserver/lib" diff --git a/distribution/docker/server/Dockerfile b/distribution/docker/server/Dockerfile index 5f2f281f33f..1464b78648e 100644 --- a/distribution/docker/server/Dockerfile +++ b/distribution/docker/server/Dockerfile @@ -25,7 +25,7 @@ # 1. docker run --name=seata-server -d seata-server:2.2.0-dev # # https://hub.docker.com/r/apache/seata-server -FROM openjdk:8u342 +FROM eclipse-temurin:8u422-b05-jdk # set label LABEL maintainer="Seata " @@ -38,8 +38,8 @@ ADD ext/ /seata-server/ext ADD lib/ /seata-server/lib ADD conf/ /seata-server/conf ADD target /seata-server/target -ADD ../LICENSE ./LICENSE -ADD ../NOTICE ./LICENSE +ADD LICENSE /LICENSE +ADD NOTICE /NOTICE # set extra environment ENV LOADER_PATH="/seata-server/lib" diff --git a/distribution/release-seata.xml b/distribution/release-seata.xml index 04c8f88b608..5aa32a0f276 100644 --- a/distribution/release-seata.xml +++ b/distribution/release-seata.xml @@ -105,6 +105,13 @@ LICENSE LICENSE + seata-server/ + + + + LICENSE + LICENSE + seata-namingserver/ @@ -115,6 +122,13 @@ NOTICE NOTICE + seata-server/ + + + + NOTICE + NOTICE + seata-namingserver/ diff --git a/pom.xml b/pom.xml index 8e78921ba96..958e9d497b1 100644 --- a/pom.xml +++ b/pom.xml @@ -194,30 +194,23 @@ false - - - release-image-based-on-java8-slim - - ${project.version}-slim - false - - release-image-based-on-java17 - ${project.version}.jre17 + ${project.version}.jdk17 false - + - release-image-based-on-java17-slim + release-image-based-on-java21 - ${project.version}.jre17-slim + ${project.version}.jdk21 false + checkstyle From 1e26c91395047402f8415c357f81da90357a8760 Mon Sep 17 00:00:00 2001 From: lixingjia <49072684+lixingjia77@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:19:10 +0800 Subject: [PATCH 21/54] bugfix: resolve issue in Raft model a follower's crash may lead to the continued use of expired tokens (#6925) --- changes/en-us/2.x.md | 2 ++ changes/zh-cn/2.x.md | 2 ++ .../seata/discovery/registry/raft/RaftRegistryServiceImpl.java | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index f33315450e4..f951c973c78 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -12,6 +12,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] fix designerJson to standardJson: subStateMachine compensateState cannot be recognized - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] fix the issue of Codecov not generating reports +- [[#6925](https://github.com/apache/incubator-seata/pull/6925)] fix the issue in Raft model a follower's crash may lead to the continued use of expired tokens ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction @@ -52,6 +53,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) - [xingfudeshi](https://github.com/xingfudeshi) +- [lixingjia77](https://github.com/lixingjia77) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index a99f7c3e6f9..8f83c2af5f4 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -12,6 +12,7 @@ - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] 修复saga设计json转标准json过程中: 子状态机补偿节点无法被识别 - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] 修复Codecov未生成报告的问题 +- [[#6925](https://github.com/apache/incubator-seata/pull/6925)] 修复Raft模式下,Follower崩溃可能导致Client继续使用过期令牌的问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 @@ -54,6 +55,7 @@ - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) - [xingfudeshi](https://github.com/xingfudeshi) +- [lixingjia77](https://github.com/lixingjia77) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java index f52464ef4ae..b5a28967ec4 100644 --- a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java +++ b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java @@ -414,7 +414,6 @@ private static void refreshToken(String tcAddress) throws RetryableException { Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); String response = null; - tokenTimeStamp = System.currentTimeMillis(); try (CloseableHttpResponse httpResponse = HttpClientUtil.doPost("http://" + tcAddress + "/api/v1/auth/login", param, header, 1000)) { if (httpResponse != null) { @@ -427,6 +426,7 @@ private static void refreshToken(String tcAddress) throws RetryableException { throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); } jwtToken = jsonNode.get("data").asText(); + tokenTimeStamp = System.currentTimeMillis(); } else { //authorized failed,throw exception to kill process throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); From 077d8a2e2b34f28a1b2b2745c37bd826c156b82a Mon Sep 17 00:00:00 2001 From: o-jimin <71332088+o-jimin@users.noreply.github.com> Date: Thu, 17 Oct 2024 18:58:19 +0900 Subject: [PATCH 22/54] bugfix: Enhance 401 Error Handling by Refreshing Token in acquireClusterMetaData Method (#6923) --- changes/en-us/2.x.md | 2 ++ changes/zh-cn/2.x.md | 4 ++++ .../registry/raft/RaftRegistryServiceImpl.java | 10 +++++++--- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index f951c973c78..a44ec1d5aa0 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -12,6 +12,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] fix designerJson to standardJson: subStateMachine compensateState cannot be recognized - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] fix the issue of Codecov not generating reports +- [[#6923](https://github.com/apache/incubator-seata/pull/6923)] Enhance 401 Error Handling by Refreshing Token - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] fix the issue in Raft model a follower's crash may lead to the continued use of expired tokens ### optimize: @@ -53,6 +54,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) - [xingfudeshi](https://github.com/xingfudeshi) +- [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 8f83c2af5f4..05d217ae562 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -12,8 +12,10 @@ - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] 修复saga设计json转标准json过程中: 子状态机补偿节点无法被识别 - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] 修复Codecov未生成报告的问题 +- [[#6923](https://github.com/apache/incubator-seata/pull/6923)] 增强 401 错误处理,通过刷新令牌 - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] 修复Raft模式下,Follower崩溃可能导致Client继续使用过期令牌的问题 + ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT @@ -55,7 +57,9 @@ - [dsomehan](https://github.com/dsomehan) - [psxjoy](https://github.com/psxjoy) - [xingfudeshi](https://github.com/xingfudeshi) +- [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) + 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java index b5a28967ec4..8ba0d2256ed 100644 --- a/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java +++ b/discovery/seata-discovery-raft/src/main/java/org/apache/seata/discovery/registry/raft/RaftRegistryServiceImpl.java @@ -377,14 +377,18 @@ private static void acquireClusterMetaData(String clusterName, String group) thr try (CloseableHttpResponse httpResponse = HttpClientUtil.doGet("http://" + tcAddress + "/metadata/v1/cluster", param, header, 1000)) { if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + int statusCode = httpResponse.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_OK) { response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); - } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + } else if (statusCode == HttpStatus.SC_UNAUTHORIZED) { if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { - throw new RetryableException("Authentication failed!"); + refreshToken(tcAddress); + throw new RetryableException("Token refreshed, retrying request."); } else { throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); } + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); } } MetadataResponse metadataResponse; From fabcceaa8fdf76181ccabeb253ea2d49213ef314 Mon Sep 17 00:00:00 2001 From: funkye Date: Fri, 18 Oct 2024 22:56:10 +0800 Subject: [PATCH 23/54] bugfix: Lock contention failure in file & Raft mode did not exit (#6932) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../apache/seata/server/storage/file/lock/FileLocker.java | 8 ++------ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index a44ec1d5aa0..b655da2930a 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -14,6 +14,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] fix the issue of Codecov not generating reports - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] Enhance 401 Error Handling by Refreshing Token - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] fix the issue in Raft model a follower's crash may lead to the continued use of expired tokens +- [[#6932](https://github.com/apache/incubator-seata/pull/6932)] when enabling local transactions, the lock contention failure in file & raft mode does not exit, leading to a lingering lock ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 05d217ae562..74c16200115 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -14,6 +14,7 @@ - [[#6907](https://github.com/apache/incubator-seata/pull/6907)] 修复Codecov未生成报告的问题 - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] 增强 401 错误处理,通过刷新令牌 - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] 修复Raft模式下,Follower崩溃可能导致Client继续使用过期令牌的问题 +- [[#6932](https://github.com/apache/incubator-seata/pull/6932)] 修复开启本地事务时file&raft模式下锁争抢失败未退出导致可能出现残留锁 ### optimize: diff --git a/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java b/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java index 4e5e484fc35..c9630e0aa10 100644 --- a/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java +++ b/server/src/main/java/org/apache/seata/server/storage/file/lock/FileLocker.java @@ -108,12 +108,8 @@ public boolean acquireLock(List rowLocks, boolean autoCommit, boolean s failFast = true; break; } - if (canLock) { - canLock = false; - if (autoCommit) { - break; - } - } + canLock = false; + break; } } if (failFast) { From 4223b8529e6e8bb1a4c1f7c5bd2312cd7cd7ab79 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Wed, 23 Oct 2024 09:49:43 +0800 Subject: [PATCH 24/54] optimize: update online chat information in README.md (#6938) --- README.md | 5 +++-- changes/en-us/2.x.md | 2 +- changes/zh-cn/2.x.md | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 359190a0c39..578339fb3bb 100644 --- a/README.md +++ b/README.md @@ -134,8 +134,9 @@ Contributors are welcomed to join the Seata project. Please check [CONTRIBUTING] * dev@seata.apache.org , for dev/user discussion. [subscribe](mailto:dev-subscribe@seata.apache.org), [unsubscribe](mailto:dev-unsubscribe@seata.apache.org), [archive](https://lists.apache.org/list.html?dev@seata.apache.org) * Online chat: - - +| Dingtalk group | Wechat office account | QQ group | Wechat assistant | +|:---------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------:| +| | | | | ## Seata ecosystem diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index b655da2930a..a9afb386fae 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -30,7 +30,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] fix some typos in project - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] Use the openjdk image of eclipse-temurin as the base image - +- [[#6938](https://github.com/apache/incubator-seata/pull/6938)] Update online chat information in README.md ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 74c16200115..f7934c454aa 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -33,7 +33,7 @@ - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] 修正项目中的部分拼写错误 - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] 使用eclipse-temurin的openjdk镜像作为基础镜像 -- +- [[#6938](https://github.com/apache/incubator-seata/pull/6938)] 更新 README.md 中的社区联系信息 ### refactor: From dbeba6120ba36851f884271132df6b6363a843d0 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Wed, 23 Oct 2024 18:24:10 +0800 Subject: [PATCH 25/54] optimize: expand unit test coverage for the [rocketmq] module. (#6927) feature: add unit-test for rocketmq module --- changes/en-us/2.x.md | 2 +- changes/zh-cn/2.x.md | 2 +- .../integration/rocketmq/SeataMQProducer.java | 34 +- .../rocketmq/SeataMQProducerTest.java | 340 +++++++++++++++++- .../rocketmq/TCCRocketMQImplTest.java | 267 ++++++++++++++ 5 files changed, 629 insertions(+), 16 deletions(-) create mode 100644 rocketmq/src/test/java/org/apache/seata/integration/rocketmq/TCCRocketMQImplTest.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index a9afb386fae..9156a0b1b3c 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -38,7 +38,7 @@ Add changes here for all PR submitted to the 2.x branch. ### security: ### test: - +- [[#6927](https://github.com/apache/incubator-seata/pull/6927)] Add unit tests for the `seata-rocketmq` module Thanks to these contributors for their code commits. Please report an unintended omission. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index f7934c454aa..eafcc9b80eb 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -41,7 +41,7 @@ ### security: ### test: - +- [[#6927](https://github.com/apache/incubator-seata/pull/6927)] 增加`seata-rocketmq`模块的测试用例 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 diff --git a/rocketmq/src/main/java/org/apache/seata/integration/rocketmq/SeataMQProducer.java b/rocketmq/src/main/java/org/apache/seata/integration/rocketmq/SeataMQProducer.java index 2846d00073e..decd1c90603 100644 --- a/rocketmq/src/main/java/org/apache/seata/integration/rocketmq/SeataMQProducer.java +++ b/rocketmq/src/main/java/org/apache/seata/integration/rocketmq/SeataMQProducer.java @@ -16,16 +16,12 @@ */ package org.apache.seata.integration.rocketmq; -import org.apache.rocketmq.client.producer.SendStatus; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.core.context.RootContext; -import org.apache.seata.core.model.GlobalStatus; -import org.apache.seata.rm.DefaultResourceManager; import org.apache.rocketmq.client.Validators; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; import org.apache.rocketmq.client.producer.TransactionListener; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.common.message.Message; @@ -34,6 +30,10 @@ import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.core.context.RootContext; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.rm.DefaultResourceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,8 +47,10 @@ public class SeataMQProducer extends TransactionMQProducer { private static final Logger LOGGER = LoggerFactory.getLogger(SeataMQProducer.class); - private static final List COMMIT_STATUSES = Arrays.asList(GlobalStatus.Committed, GlobalStatus.Committing, GlobalStatus.CommitRetrying); - private static final List ROLLBACK_STATUSES = Arrays.asList(GlobalStatus.Rollbacked, GlobalStatus.Rollbacking, GlobalStatus.RollbackRetrying); + private static final List COMMIT_STATUSES = + Arrays.asList(GlobalStatus.Committed, GlobalStatus.Committing, GlobalStatus.CommitRetrying); + private static final List ROLLBACK_STATUSES = + Arrays.asList(GlobalStatus.Rollbacked, GlobalStatus.Rollbacking, GlobalStatus.RollbackRetrying); public static String PROPERTY_SEATA_XID = RootContext.KEY_XID; public static String PROPERTY_SEATA_BRANCHID = RootContext.KEY_BRANCHID; @@ -75,7 +77,8 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { LOGGER.error("msg has no xid, msgTransactionId: {}, msg will be rollback", msg.getTransactionId()); return LocalTransactionState.ROLLBACK_MESSAGE; } - GlobalStatus globalStatus = DefaultResourceManager.get().getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, xid); + GlobalStatus globalStatus = + DefaultResourceManager.get().getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, xid); if (COMMIT_STATUSES.contains(globalStatus)) { return LocalTransactionState.COMMIT_MESSAGE; } else if (ROLLBACK_STATUSES.contains(globalStatus) || GlobalStatus.isOnePhaseTimeout(globalStatus)) { @@ -90,12 +93,14 @@ public LocalTransactionState checkLocalTransaction(MessageExt msg) { } @Override - public SendResult send(Message msg) throws MQClientException, MQBrokerException, RemotingException, InterruptedException { + public SendResult send(Message msg) + throws MQClientException, MQBrokerException, RemotingException, InterruptedException { return send(msg, this.getSendMsgTimeout()); } @Override - public SendResult send(Message msg, long timeout) throws MQClientException, MQBrokerException, RemotingException, InterruptedException { + public SendResult send(Message msg, long timeout) + throws MQClientException, MQBrokerException, RemotingException, InterruptedException { if (RootContext.inGlobalTransaction()) { if (tccRocketMQ == null) { throw new RuntimeException("TCCRocketMQ is not initialized"); @@ -106,7 +111,8 @@ public SendResult send(Message msg, long timeout) throws MQClientException, MQBr } } - public SendResult doSendMessageInTransaction(final Message msg, long timeout, String xid, long branchId) throws MQClientException { + public SendResult doSendMessageInTransaction(final Message msg, long timeout, String xid, long branchId) + throws MQClientException { msg.setTopic(withNamespace(msg.getTopic())); if (msg.getDelayTimeLevel() != 0) { MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL); @@ -119,7 +125,7 @@ public SendResult doSendMessageInTransaction(final Message msg, long timeout, St MessageAccessor.putProperty(msg, PROPERTY_SEATA_XID, xid); MessageAccessor.putProperty(msg, PROPERTY_SEATA_BRANCHID, String.valueOf(branchId)); try { - sendResult = super.send(msg, timeout); + sendResult = superSend(msg, timeout); } catch (Exception e) { throw new MQClientException("send message Exception", e); } @@ -137,6 +143,10 @@ public SendResult doSendMessageInTransaction(final Message msg, long timeout, St return sendResult; } + public SendResult superSend(Message msg, long timeout) + throws MQClientException, MQBrokerException, RemotingException, InterruptedException { + return super.send(msg, timeout); + } @Override public TransactionListener getTransactionListener() { diff --git a/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/SeataMQProducerTest.java b/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/SeataMQProducerTest.java index 7b8ab979d58..a43bb0fb200 100644 --- a/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/SeataMQProducerTest.java +++ b/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/SeataMQProducerTest.java @@ -16,16 +16,352 @@ */ package org.apache.seata.integration.rocketmq; +import org.apache.rocketmq.client.exception.MQBrokerException; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.producer.LocalTransactionState; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.client.producer.TransactionListener; +import org.apache.rocketmq.client.producer.TransactionMQProducer; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.common.message.MessageAccessor; +import org.apache.rocketmq.common.message.MessageConst; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.seata.core.context.RootContext; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.rm.DefaultResourceManager; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; /** * seata mq producer test **/ public class SeataMQProducerTest { + @Mock + private TransactionMQProducer transactionMQProducer; + + private TCCRocketMQ tccRocketMQ; + @InjectMocks + private SeataMQProducer producer; + private SeataMQProducer producerTwo; + private SeataMQProducer seataMQProducer; + private TransactionListener transactionListener; + + @BeforeEach + void setUp() { + producer = Mockito.spy(new SeataMQProducer("testGroup")); + seataMQProducer = spy(new SeataMQProducer("testGroup")); + tccRocketMQ = mock(TCCRocketMQImpl.class); + producer.setTccRocketMQ(tccRocketMQ); + producerTwo = new SeataMQProducer("namespace", "producerGroup", null); + transactionListener = producerTwo.getTransactionListener(); + } + @Test - public void testCreate(){ + public void testCreate() { new SeataMQProducer("testProducerGroup"); - new SeataMQProducer("testNamespace", "testProducerGroup",null); + new SeataMQProducer("testNamespace", "testProducerGroup", null); + } + + @Test + void testExecuteLocalTransaction() { + Message msg = new Message(); + assertEquals(LocalTransactionState.UNKNOW, transactionListener.executeLocalTransaction(msg, null)); + } + + @Test + void testCheckLocalTransactionWithNoXid() { + MessageExt msg = new MessageExt(); + msg.setTransactionId("testTransactionId"); + assertEquals(LocalTransactionState.ROLLBACK_MESSAGE, transactionListener.checkLocalTransaction(msg)); + } + + @Test + void testCheckLocalTransactionWithCommitStatus() { + MessageExt msg = new MessageExt(); + msg.putUserProperty(SeataMQProducer.PROPERTY_SEATA_XID, "testXid"); + + try (MockedStatic mockedStatic = mockStatic(DefaultResourceManager.class)) { + DefaultResourceManager mockResourceManager = mock(DefaultResourceManager.class); + mockedStatic.when(DefaultResourceManager::get).thenReturn(mockResourceManager); + when(mockResourceManager.getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, "testXid")).thenReturn( + GlobalStatus.Committed); + + assertEquals(LocalTransactionState.COMMIT_MESSAGE, transactionListener.checkLocalTransaction(msg)); + } + } + + @Test + void testCheckLocalTransactionWithRollbackStatus() { + MessageExt msg = new MessageExt(); + msg.putUserProperty(SeataMQProducer.PROPERTY_SEATA_XID, "testXid"); + + try (MockedStatic mockedStatic = mockStatic(DefaultResourceManager.class)) { + DefaultResourceManager mockResourceManager = mock(DefaultResourceManager.class); + mockedStatic.when(DefaultResourceManager::get).thenReturn(mockResourceManager); + when(mockResourceManager.getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, "testXid")).thenReturn( + GlobalStatus.Rollbacked); + + assertEquals(LocalTransactionState.ROLLBACK_MESSAGE, transactionListener.checkLocalTransaction(msg)); + } + } + + @Test + void testCheckLocalTransactionWithFinishedStatus() { + MessageExt msg = new MessageExt(); + msg.putUserProperty(SeataMQProducer.PROPERTY_SEATA_XID, "testXid"); + + try (MockedStatic mockedStatic = mockStatic(DefaultResourceManager.class)) { + DefaultResourceManager mockResourceManager = mock(DefaultResourceManager.class); + mockedStatic.when(DefaultResourceManager::get).thenReturn(mockResourceManager); + when(mockResourceManager.getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, "testXid")).thenReturn( + GlobalStatus.Finished); + + assertEquals(LocalTransactionState.ROLLBACK_MESSAGE, transactionListener.checkLocalTransaction(msg)); + } + } + + @Test + void testCheckLocalTransactionWithUnknownStatus() { + MessageExt msg = new MessageExt(); + msg.putUserProperty(SeataMQProducer.PROPERTY_SEATA_XID, "testXid"); + + try (MockedStatic mockedStatic = mockStatic(DefaultResourceManager.class)) { + DefaultResourceManager mockResourceManager = mock(DefaultResourceManager.class); + mockedStatic.when(DefaultResourceManager::get).thenReturn(mockResourceManager); + when(mockResourceManager.getGlobalStatus(SeataMQProducerFactory.ROCKET_BRANCH_TYPE, "testXid")).thenReturn( + GlobalStatus.Begin); + + assertEquals(LocalTransactionState.UNKNOW, transactionListener.checkLocalTransaction(msg)); + } + } + + @Test + void testSendWithoutGlobalTransaction() + throws MQClientException, RemotingException, MQBrokerException, InterruptedException { + Message msg = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + SendResult expectedResult = mock(SendResult.class); + + doReturn(expectedResult).when(producer).send(msg, timeout); + + SendResult result = producer.send(msg, timeout); + + assertSame(expectedResult, result); + verify(producer).send(msg, timeout); + verifyNoInteractions(tccRocketMQ); + } + + @Test + void testSendWithGlobalTransaction() + throws MQClientException, RemotingException, MQBrokerException, InterruptedException { + Message msg = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + SendResult expectedResult = mock(SendResult.class); + + RootContext.bind("DummyXID"); + try { + when(tccRocketMQ.prepare(msg, timeout)).thenReturn(expectedResult); + + SendResult result = producer.send(msg, timeout); + + assertSame(expectedResult, result); + verify(tccRocketMQ).prepare(msg, timeout); + } finally { + RootContext.unbind(); + } + } + + @Test + void testSendWithGlobalTransactionAndNullTCCRocketMQ() { + Message msg = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + + producer.setTccRocketMQ(null); + RootContext.bind("DummyXID"); + try { + assertThrows(RuntimeException.class, () -> producer.send(msg, timeout)); + } finally { + RootContext.unbind(); + } + } + + @Test + void testSend() throws MQClientException, RemotingException, MQBrokerException, InterruptedException { + + Message msg = new Message("testTopic", "testBody".getBytes()); + SendResult expectedResult = mock(SendResult.class); + int expectedTimeout = 3000; + + doReturn(expectedTimeout).when(producer).getSendMsgTimeout(); + doReturn(expectedResult).when(producer).send(any(Message.class), anyLong()); + + SendResult result = producer.send(msg); + + assertSame(expectedResult, result); + verify(producer).send(msg, expectedTimeout); } + + @Test + void testSendWithException() throws MQClientException, RemotingException, MQBrokerException, InterruptedException { + + Message msg = new Message("testTopic", "testBody".getBytes()); + int expectedTimeout = 3000; + + doReturn(expectedTimeout).when(producer).getSendMsgTimeout(); + doThrow(new MQClientException("Test exception", null)).when(producer).send(any(Message.class), anyInt()); + + assertThrows(MQClientException.class, () -> producer.send(msg)); + verify(producer).send(msg, expectedTimeout); + } + + @Test + void testDoSendMessageInTransactionWithNonOkStatus() throws Exception { + + Message msg = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + SendResult mockSendResult = mock(SendResult.class); + when(mockSendResult.getSendStatus()).thenReturn(SendStatus.FLUSH_DISK_TIMEOUT); + + doReturn(mockSendResult).when(producer).send(any(Message.class), anyLong()); + + assertThrows(MQClientException.class, () -> producer.doSendMessageInTransaction(msg, timeout, xid, branchId)); + } + + @Test + void testDoSendMessageInTransactionWithException() throws Exception { + + Message msg = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + doThrow(new RuntimeException("Test exception")).when(producer).send(any(Message.class), anyLong()); + doCallRealMethod().when(producer) + .doSendMessageInTransaction(any(Message.class), anyLong(), anyString(), anyLong()); + + assertThrows(MQClientException.class, () -> producer.doSendMessageInTransaction(msg, timeout, xid, branchId)); + } + + @Test + void testDoSendMessageInTransactionSuccess() throws Exception { + + Message msg = new Message("testTopic", "testTag", "testKey", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + SendResult mockSendResult = new SendResult(); + mockSendResult.setSendStatus(SendStatus.SEND_OK); + mockSendResult.setTransactionId("testTransactionId"); + + doReturn(mockSendResult).when(seataMQProducer).superSend(any(Message.class), anyLong()); + + SendResult result = seataMQProducer.doSendMessageInTransaction(msg, timeout, xid, branchId); + + assertNotNull(result); + assertEquals(SendStatus.SEND_OK, result.getSendStatus()); + assertEquals("testTransactionId", msg.getUserProperty("__transactionId__")); + assertEquals("true", msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED)); + assertEquals(seataMQProducer.getProducerGroup(), msg.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP)); + assertEquals(xid, msg.getProperty(SeataMQProducer.PROPERTY_SEATA_XID)); + assertEquals(String.valueOf(branchId), msg.getProperty(SeataMQProducer.PROPERTY_SEATA_BRANCHID)); + + verify(seataMQProducer).superSend(msg, timeout); + } + + @Test + void testDoSendMessageInTransactionSendException() throws Exception { + + Message msg = new Message("testTopic", "testTag", "testKey", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + doThrow(new RuntimeException("Send failed")).when(seataMQProducer).superSend(any(Message.class), anyLong()); + + assertThrows(MQClientException.class, + () -> seataMQProducer.doSendMessageInTransaction(msg, timeout, xid, branchId)); + + verify(seataMQProducer).superSend(msg, timeout); + } + + @Test + void testDoSendMessageInTransactionSendStatusNotOk() throws Exception { + + Message msg = new Message("testTopic", "testTag", "testKey", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + SendResult mockSendResult = new SendResult(); + mockSendResult.setSendStatus(SendStatus.FLUSH_DISK_TIMEOUT); + + doReturn(mockSendResult).when(seataMQProducer).superSend(any(Message.class), anyLong()); + + assertThrows(RuntimeException.class, + () -> seataMQProducer.doSendMessageInTransaction(msg, timeout, xid, branchId)); + + verify(seataMQProducer).superSend(msg, timeout); + } + + @Test + void testDoSendMessageInTransactionWithTransactionId() throws Exception { + + Message msg = new Message("testTopic", "testTag", "testKey", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + SendResult mockSendResult = new SendResult(); + mockSendResult.setSendStatus(SendStatus.SEND_OK); + mockSendResult.setTransactionId("testTransactionId"); + + MessageAccessor.putProperty(msg, MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, "clientTransactionId"); + + doReturn(mockSendResult).when(seataMQProducer).superSend(any(Message.class), anyLong()); + + SendResult result = seataMQProducer.doSendMessageInTransaction(msg, timeout, xid, branchId); + + assertNotNull(result); + assertEquals(SendStatus.SEND_OK, result.getSendStatus()); + assertEquals("testTransactionId", msg.getUserProperty("__transactionId__")); + assertEquals("clientTransactionId", msg.getTransactionId()); + + verify(seataMQProducer).superSend(msg, timeout); + } + + @Test + void getTransactionListenerShouldReturnNonNullTransactionListener() { + TransactionListener transactionListener = producer.getTransactionListener(); + assertNotNull(transactionListener, "TransactionListener should not be null"); + } + } diff --git a/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/TCCRocketMQImplTest.java b/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/TCCRocketMQImplTest.java new file mode 100644 index 00000000000..af9ef218f2b --- /dev/null +++ b/rocketmq/src/test/java/org/apache/seata/integration/rocketmq/TCCRocketMQImplTest.java @@ -0,0 +1,267 @@ +/* + * 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 org.apache.seata.integration.rocketmq; + +import java.lang.reflect.Field; +import java.net.UnknownHostException; +import java.util.concurrent.TimeoutException; +import org.apache.rocketmq.client.exception.MQBrokerException; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl; +import org.apache.rocketmq.client.producer.LocalTransactionState; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.rm.tcc.api.BusinessActionContextUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * the type TCCRocketMQImpl + */ +public class TCCRocketMQImplTest { + @Mock + private SeataMQProducer producer; + + @Mock + private DefaultMQProducerImpl producerImpl; + @Mock + private BusinessActionContext businessActionContext; + + private TCCRocketMQImpl tccRocketMQ; + private TCCRocketMQImpl prepareTccRocketMQ; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + tccRocketMQ = new TCCRocketMQImpl(); + prepareTccRocketMQ = new TCCRocketMQImpl(); + + Field producerImplField = TCCRocketMQImpl.class.getDeclaredField("producerImpl"); + producerImplField.setAccessible(true); + producerImplField.set(tccRocketMQ, producerImpl); + prepareTccRocketMQ.setProducer(producer); + } + + @Test + void testPrepare() throws MQClientException { + MockedStatic mockedStatic = mockStatic(BusinessActionContextUtil.class); + try { + + Message message = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + mockedStatic.when(BusinessActionContextUtil::getContext).thenReturn(businessActionContext); + when(businessActionContext.getXid()).thenReturn(xid); + when(businessActionContext.getBranchId()).thenReturn(branchId); + + SendResult mockSendResult = mock(SendResult.class); + when(mockSendResult.getSendStatus()).thenReturn(SendStatus.SEND_OK); + when(producer.doSendMessageInTransaction(message, timeout, xid, branchId)).thenReturn(mockSendResult); + + SendResult result = prepareTccRocketMQ.prepare(message, timeout); + + assertNotNull(result); + assertEquals(SendStatus.SEND_OK, result.getSendStatus()); + assertEquals(0, message.getDelayTimeLevel()); + + verify(producer).doSendMessageInTransaction(message, timeout, xid, branchId); + mockedStatic.verify(BusinessActionContextUtil::getContext, times(1)); + } finally { + mockedStatic.close(); + } + } + + @Test + void testPrepareWithException() throws MQClientException { + MockedStatic mockedStatic = mockStatic(BusinessActionContextUtil.class); + try { + + Message message = new Message("testTopic", "testBody".getBytes()); + long timeout = 3000L; + String xid = "testXid"; + long branchId = 123L; + + mockedStatic.when(BusinessActionContextUtil::getContext).thenReturn(businessActionContext); + when(businessActionContext.getXid()).thenReturn(xid); + when(businessActionContext.getBranchId()).thenReturn(branchId); + + when(producer.doSendMessageInTransaction(message, timeout, xid, branchId)).thenThrow( + new MQClientException("Test exception", null)); + + assertThrows(MQClientException.class, () -> prepareTccRocketMQ.prepare(message, timeout)); + + verify(producer).doSendMessageInTransaction(message, timeout, xid, branchId); + mockedStatic.verify(BusinessActionContextUtil::getContext, times(1)); + mockedStatic.verify(() -> BusinessActionContextUtil.addContext(any()), never()); + } finally { + mockedStatic.close(); + } + } + + @Test + void testCommitSuccess() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException, TimeoutException, + TransactionException { + + Message message = new Message("testTopic", "testBody".getBytes()); + SendResult sendResult = mock(SendResult.class); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(message); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(sendResult); + when(businessActionContext.getXid()).thenReturn("testXid"); + when(businessActionContext.getBranchId()).thenReturn(123L); + + boolean result = tccRocketMQ.commit(businessActionContext); + + assertTrue(result); + verify(producerImpl).endTransaction(eq(message), eq(sendResult), eq(LocalTransactionState.COMMIT_MESSAGE), + isNull()); + } + + @Test + void testCommitWithNullMessage() { + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(null); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn( + mock(SendResult.class)); + + assertThrows(TransactionException.class, () -> tccRocketMQ.commit(businessActionContext)); + } + + @Test + void testCommitWithNullSendResult() { + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(new Message()); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(null); + + assertThrows(TransactionException.class, () -> tccRocketMQ.commit(businessActionContext)); + } + + @Test + void testCommitWithException() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException, TimeoutException { + + Message message = new Message("testTopic", "testBody".getBytes()); + SendResult sendResult = mock(SendResult.class); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(message); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(sendResult); + + doThrow(new MQBrokerException(1, "Test exception")).when(producerImpl) + .endTransaction(any(), any(), any(), any()); + + assertThrows(MQBrokerException.class, () -> tccRocketMQ.commit(businessActionContext)); + } + + @Test + void testRollbackSuccess() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException, TransactionException { + + Message message = new Message("testTopic", "testBody".getBytes()); + SendResult sendResult = mock(SendResult.class); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(message); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(sendResult); + when(businessActionContext.getXid()).thenReturn("testXid"); + when(businessActionContext.getBranchId()).thenReturn(123L); + + boolean result = tccRocketMQ.rollback(businessActionContext); + + assertTrue(result); + verify(producerImpl).endTransaction(eq(message), eq(sendResult), eq(LocalTransactionState.ROLLBACK_MESSAGE), + isNull()); + } + + @Test + void testRollbackWithNullMessage() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException, TransactionException { + + SendResult sendResult = mock(SendResult.class); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(null); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(sendResult); + when(businessActionContext.getXid()).thenReturn("testXid"); + when(businessActionContext.getBranchId()).thenReturn(123L); + + boolean result = tccRocketMQ.rollback(businessActionContext); + + assertTrue(result); + verify(producerImpl).endTransaction(isNull(), eq(sendResult), eq(LocalTransactionState.ROLLBACK_MESSAGE), + isNull()); + } + + @Test + void testRollbackWithNullSendResult() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException, TransactionException { + + Message message = new Message("testTopic", "testBody".getBytes()); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(message); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(null); + when(businessActionContext.getXid()).thenReturn("testXid"); + when(businessActionContext.getBranchId()).thenReturn(123L); + + boolean result = tccRocketMQ.rollback(businessActionContext); + + assertTrue(result); + verify(producerImpl).endTransaction(eq(message), isNull(), eq(LocalTransactionState.ROLLBACK_MESSAGE), + isNull()); + } + + @Test + void testRollbackWithException() + throws UnknownHostException, MQBrokerException, RemotingException, InterruptedException { + + Message message = new Message("testTopic", "testBody".getBytes()); + SendResult sendResult = mock(SendResult.class); + + when(businessActionContext.getActionContext("ROCKET_MSG", Message.class)).thenReturn(message); + when(businessActionContext.getActionContext("ROCKET_SEND_RESULT", SendResult.class)).thenReturn(sendResult); + when(businessActionContext.getXid()).thenReturn("testXid"); + when(businessActionContext.getBranchId()).thenReturn(123L); + + doThrow(new MQBrokerException(1, "Test exception")).when(producerImpl) + .endTransaction(any(), any(), any(), any()); + + assertThrows(MQBrokerException.class, () -> tccRocketMQ.rollback(businessActionContext)); + } + +} From ab4f44e2257bb29e2954261aab955c63df5e9fb1 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Thu, 24 Oct 2024 15:05:42 +0800 Subject: [PATCH 26/54] bugfix: fix ConcurrentModificationException in SessionConverter.convertBranchSession (#6943) --- README.md | 2 +- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 +- .../server/storage/SessionConverter.java | 5 +- .../server/storage/SessionConverterTest.java | 111 ++++++++++++++++++ 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 server/src/test/java/org/apache/seata/server/storage/SessionConverterTest.java diff --git a/README.md b/README.md index 578339fb3bb..59209dacde9 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ Contributors are welcomed to join the Seata project. Please check [CONTRIBUTING] * dev@seata.apache.org , for dev/user discussion. [subscribe](mailto:dev-subscribe@seata.apache.org), [unsubscribe](mailto:dev-unsubscribe@seata.apache.org), [archive](https://lists.apache.org/list.html?dev@seata.apache.org) * Online chat: -| Dingtalk group | Wechat office account | QQ group | Wechat assistant | +| Dingtalk group | Wechat official account | QQ group | Wechat assistant | |:---------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------------------------------:| | | | | | diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 9156a0b1b3c..382e99aaeb7 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -15,6 +15,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] Enhance 401 Error Handling by Refreshing Token - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] fix the issue in Raft model a follower's crash may lead to the continued use of expired tokens - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] when enabling local transactions, the lock contention failure in file & raft mode does not exit, leading to a lingering lock +- [[#6943](https://github.com/apache/incubator-seata/pull/6943)] fix the conversion error for `convertBranchSession` in concurrent environment. ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index eafcc9b80eb..f602f2b01d7 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -15,7 +15,7 @@ - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] 增强 401 错误处理,通过刷新令牌 - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] 修复Raft模式下,Follower崩溃可能导致Client继续使用过期令牌的问题 - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] 修复开启本地事务时file&raft模式下锁争抢失败未退出导致可能出现残留锁 - +- [[#6943](https://github.com/apache/incubator-seata/pull/6943)] 修复并发状态下 `convertBranchSession` 转换报错问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java index e468bb2567d..4b90905a851 100644 --- a/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java +++ b/server/src/main/java/org/apache/seata/server/storage/SessionConverter.java @@ -213,9 +213,10 @@ public static Set convertBranchSession(List bran return Collections.emptySet(); } - final Set result = new HashSet<>(branchSessions.size()); + List safeBranchSessions = new ArrayList<>(branchSessions); + final Set result = new HashSet<>(safeBranchSessions.size()); - for (BranchSession session : branchSessions) { + for (BranchSession session : safeBranchSessions) { result.add(new BranchSessionVO( session.getXid(), session.getTransactionId(), diff --git a/server/src/test/java/org/apache/seata/server/storage/SessionConverterTest.java b/server/src/test/java/org/apache/seata/server/storage/SessionConverterTest.java new file mode 100644 index 00000000000..d1e173cd5ac --- /dev/null +++ b/server/src/test/java/org/apache/seata/server/storage/SessionConverterTest.java @@ -0,0 +1,111 @@ +/* + * 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 org.apache.seata.server.storage; +// +//import java.util.ArrayList; +//import java.util.ConcurrentModificationException; +//import java.util.List; +//import java.util.concurrent.CountDownLatch; +//import java.util.concurrent.ExecutorService; +//import java.util.concurrent.Executors; +//import java.util.concurrent.atomic.AtomicBoolean; +//import org.apache.seata.core.model.BranchStatus; +//import org.apache.seata.core.model.BranchType; +//import org.apache.seata.server.session.BranchSession; +//import org.junit.jupiter.api.RepeatedTest; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.junit.jupiter.MockitoExtension; +//import org.springframework.boot.test.context.SpringBootTest; +// +//import static org.junit.jupiter.api.Assertions.assertFalse; +// +//@ExtendWith(MockitoExtension.class) +//@SpringBootTest +//public class SessionConverterTest { +// // Repeat 100 for adding success per +// @RepeatedTest(100) +// public void testConcurrentModificationException() throws InterruptedException { +// List branchSessions = new ArrayList<>(); +// for (int i = 0; i < 1000; i++) { +// branchSessions.add(createMockBranchSession(i)); +// } +// +// CountDownLatch startLatch = new CountDownLatch(1); +// CountDownLatch endLatch = new CountDownLatch(2); +// AtomicBoolean exceptionThrown = new AtomicBoolean(false); +// +// ExecutorService executorService = Executors.newFixedThreadPool(2); +// +// // Thread for converting branch sessions +// executorService.submit(() -> { +// try { +// startLatch.await(); +// for (int i = 0; i < 100; i++) { +// try { +// SessionConverter.convertBranchSession(branchSessions); +// } catch (ConcurrentModificationException e) { +// exceptionThrown.set(true); +// break; +// } +// } +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// } finally { +// endLatch.countDown(); +// } +// }); +// +// // Thread for modifying the list +// executorService.submit(() -> { +// try { +// startLatch.await(); +// for (int i = 0; i < 1000; i++) { +// branchSessions.add(createMockBranchSession(1000 + i)); +// if (i % 10 == 0) { +// branchSessions.remove(0); +// } +// } +// } catch (InterruptedException e) { +// Thread.currentThread().interrupt(); +// } finally { +// endLatch.countDown(); +// } +// }); +// // Start both threads +// startLatch.countDown(); +// // Wait for both threads to finish +// endLatch.await(); +// +// executorService.shutdown(); +// +// assertFalse(exceptionThrown.get(), "ConcurrentModificationException was not thrown"); +// } +// +// private BranchSession createMockBranchSession(int id) { +// BranchSession session = new BranchSession(); +// session.setXid("xid" + id); +// session.setTransactionId(id); +// session.setBranchId(id); +// session.setResourceGroupId("resourceGroup" + id); +// session.setResourceId("resource" + id); +// session.setBranchType(BranchType.AT); +// session.setStatus(BranchStatus.Registered); +// session.setClientId("client" + id); +// session.setApplicationData("data" + id); +// return session; +// } +//} From 0b9d7c9a4972e0674a4da681ee9e7d3a6a194e92 Mon Sep 17 00:00:00 2001 From: JeckXu <13485386017@163.com> Date: Thu, 24 Oct 2024 15:19:51 +0800 Subject: [PATCH 27/54] bugfix: fix NacosRegistry lookup behavior transactionServiceGroup is empty causing NPE (#6945) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../discovery/registry/nacos/NacosRegistryServiceImpl.java | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 382e99aaeb7..3fc4fce7937 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -15,6 +15,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] Enhance 401 Error Handling by Refreshing Token - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] fix the issue in Raft model a follower's crash may lead to the continued use of expired tokens - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] when enabling local transactions, the lock contention failure in file & raft mode does not exit, leading to a lingering lock +- [[#6940](https://github.com/apache/incubator-seata/pull/6940)] Fix NacosRegistry lookup behavior transactionServiceGroup is empty causing NPE error - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] fix the conversion error for `convertBranchSession` in concurrent environment. ### optimize: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index f602f2b01d7..41b0965f395 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -15,6 +15,7 @@ - [[#6923](https://github.com/apache/incubator-seata/pull/6923)] 增强 401 错误处理,通过刷新令牌 - [[#6925](https://github.com/apache/incubator-seata/pull/6925)] 修复Raft模式下,Follower崩溃可能导致Client继续使用过期令牌的问题 - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] 修复开启本地事务时file&raft模式下锁争抢失败未退出导致可能出现残留锁 +- [[#6940](https://github.com/apache/incubator-seata/pull/6940)] 修复NacosRegistry lookup 行为 transactionServiceGroup 为空导致 NPE 错误 - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] 修复并发状态下 `convertBranchSession` 转换报错问题 ### optimize: diff --git a/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java b/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java index 1f6abccba4a..1d19d8814dc 100644 --- a/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java +++ b/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java @@ -194,8 +194,9 @@ public List lookup(String key) throws Exception { .map(eachInstance -> new InetSocketAddress(eachInstance.getIp(), eachInstance.getPort())) .collect(Collectors.toList()); CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList); - - removeOfflineAddressesIfNecessary(transactionServiceGroup, clusterName, newAddressList); + if (StringUtils.isNotEmpty(transactionServiceGroup)) { + removeOfflineAddressesIfNecessary(transactionServiceGroup, clusterName, newAddressList); + } } }); } From e91e1131e59eea0d5bbfed331232018902f433f9 Mon Sep 17 00:00:00 2001 From: laywin Date: Fri, 25 Oct 2024 13:24:11 +0800 Subject: [PATCH 28/54] bugfix: fix npe for nacos registry when look up address (#6947) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../seata/discovery/registry/nacos/NacosRegistryServiceImpl.java | 1 + 3 files changed, 3 insertions(+) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 3fc4fce7937..5bdcb562279 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -17,6 +17,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] when enabling local transactions, the lock contention failure in file & raft mode does not exit, leading to a lingering lock - [[#6940](https://github.com/apache/incubator-seata/pull/6940)] Fix NacosRegistry lookup behavior transactionServiceGroup is empty causing NPE error - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] fix the conversion error for `convertBranchSession` in concurrent environment. +- [[#6947](https://github.com/apache/incubator-seata/pull/6947)] fix npe for nacos registry when look up address ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 41b0965f395..efb6cad9b72 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -17,6 +17,7 @@ - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] 修复开启本地事务时file&raft模式下锁争抢失败未退出导致可能出现残留锁 - [[#6940](https://github.com/apache/incubator-seata/pull/6940)] 修复NacosRegistry lookup 行为 transactionServiceGroup 为空导致 NPE 错误 - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] 修复并发状态下 `convertBranchSession` 转换报错问题 +- [[#6947](https://github.com/apache/incubator-seata/pull/6947)] 修复nacos注册中心查询可用地址时的空指针问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java b/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java index 1d19d8814dc..5edfaed70d5 100644 --- a/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java +++ b/discovery/seata-discovery-nacos/src/main/java/org/apache/seata/discovery/registry/nacos/NacosRegistryServiceImpl.java @@ -147,6 +147,7 @@ public void unsubscribe(String cluster, EventListener listener) throws Exception @Override public List lookup(String key) throws Exception { + transactionServiceGroup = key; String clusterName = getServiceGroup(key); if (clusterName == null) { String missingDataId = PREFIX_SERVICE_ROOT + CONFIG_SPLIT_CHAR + PREFIX_SERVICE_MAPPING + key; From df76d3686c7e5d08ff2f52e264f2280e8a1a2615 Mon Sep 17 00:00:00 2001 From: xingfudeshi Date: Sat, 26 Oct 2024 10:56:22 +0800 Subject: [PATCH 29/54] optimize: remove JVM parameter app.id (#6950) --- changes/en-us/2.x.md | 2 +- changes/zh-cn/2.x.md | 1 + distribution/bin/seata-setup.sh | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 5bdcb562279..84c7edd9554 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -34,7 +34,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] fix some typos in project - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] Use the openjdk image of eclipse-temurin as the base image - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] Update online chat information in README.md - +- [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index efb6cad9b72..c63c19b8060 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -36,6 +36,7 @@ - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] 修正项目中的部分拼写错误 - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] 使用eclipse-temurin的openjdk镜像作为基础镜像 - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] 更新 README.md 中的社区联系信息 +- [[#6950](https://github.com/apache/incubator-seata/pull/6950)] 移除JVM参数app.id ### refactor: diff --git a/distribution/bin/seata-setup.sh b/distribution/bin/seata-setup.sh index 50b6a6121ef..a391f172d84 100644 --- a/distribution/bin/seata-setup.sh +++ b/distribution/bin/seata-setup.sh @@ -150,7 +150,7 @@ else fi JAVA_OPT="${JAVA_OPT} -Dio.netty.leakDetectionLevel=advanced" -JAVA_OPT="${JAVA_OPT} -Dapp.name=seata-server -Dapp.pid=${$} -Dapp.home=${BASEDIR} -Dbasedir=${BASEDIR}" +JAVA_OPT="${JAVA_OPT} -Dapp.name=seata-server -Dapp.home=${BASEDIR} -Dbasedir=${BASEDIR}" if [ "$JMX_ENABLE" = "true" ]; then JMX_PORT=$JMX_PORT From 832d4c6f36710f8a9476dbc7b6943daf665022c5 Mon Sep 17 00:00:00 2001 From: xingfudeshi Date: Sat, 26 Oct 2024 10:57:47 +0800 Subject: [PATCH 30/54] bugfix: fix the CI build issue on the ARM64 platform (#6948) --- .github/workflows/build.yml | 6 +++++- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a5bdf5c2e39..dd84681ba7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,11 +84,15 @@ jobs: - name: "Set up QEMU" id: qemu uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - name: "Set up Docker Buildx" + uses: docker/setup-buildx-action@v3 # step 3 - name: "Build with Maven on 'arm64v8/ubuntu:20.04' OS (Skip tests)" run: | docker run --rm -v ${{ github.workspace }}:/ws:rw --workdir=/ws \ - arm64v8/ubuntu:20.04 \ + --platform linux/arm64 arm64v8/ubuntu:20.04 \ bash -exc 'apt-get update -y && \ apt-get install maven -y && \ apt-get install -y python3 python3-pip python3-distutils && \ diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 84c7edd9554..2d1b8ffe1fb 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -17,6 +17,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] when enabling local transactions, the lock contention failure in file & raft mode does not exit, leading to a lingering lock - [[#6940](https://github.com/apache/incubator-seata/pull/6940)] Fix NacosRegistry lookup behavior transactionServiceGroup is empty causing NPE error - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] fix the conversion error for `convertBranchSession` in concurrent environment. +- [[#6948](https://github.com/apache/incubator-seata/pull/6948)] Fix the CI build issue on the ARM64 platform - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] fix npe for nacos registry when look up address ### optimize: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index c63c19b8060..321e751c6a7 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -17,6 +17,7 @@ - [[#6932](https://github.com/apache/incubator-seata/pull/6932)] 修复开启本地事务时file&raft模式下锁争抢失败未退出导致可能出现残留锁 - [[#6940](https://github.com/apache/incubator-seata/pull/6940)] 修复NacosRegistry lookup 行为 transactionServiceGroup 为空导致 NPE 错误 - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] 修复并发状态下 `convertBranchSession` 转换报错问题 +- [[#6948](https://github.com/apache/incubator-seata/pull/6948)] 修复在ARM64平台下CI构建出错的问题 - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] 修复nacos注册中心查询可用地址时的空指针问题 ### optimize: From b0fdff8a2fd3560638f488cee759f77a4b20ec9f Mon Sep 17 00:00:00 2001 From: LegGasai Date: Sun, 27 Oct 2024 12:31:36 +0800 Subject: [PATCH 31/54] feature: support raft configuration center (#6830) --- .gitignore | 4 + all/pom.xml | 5 + build/pom.xml | 1 + .../seata/common/ConfigurationKeys.java | 26 + .../org/apache/seata/common/Constants.java | 34 + .../apache/seata/common/DefaultValues.java | 4 + .../common/config/ConfigDataResponse.java | 59 ++ .../common/metadata/MetadataResponse.java | 11 +- .../apache/seata/common/store/StoreMode.java | 5 + .../apache/seata/common/util/NumberUtils.java | 18 + .../seata/common/util/NumberUtilsTest.java | 8 + config/pom.xml | 1 + config/seata-config-all/pom.xml | 5 + config/seata-config-core/pom.xml | 5 + .../org/apache/seata/config/ConfigType.java | 4 + .../config/ConfigurationChangeEvent.java | 4 + .../config/dto/ConfigurationInfoDto.java | 44 ++ .../seata/config/dto/ConfigurationItem.java | 65 ++ .../config/dto/ConfigurationItemMeta.java | 54 ++ .../config/store/ConfigStoreManager.java | 96 +++ .../store/ConfigStoreManagerFactory.java | 49 ++ .../store/ConfigStoreManagerProvider.java | 28 + .../rocksdb/RocksDBConfigStoreManager.java | 691 +++++++++++++++++ .../RocksDBConfigStoreManagerProvider.java | 30 + .../config/store/rocksdb/RocksDBFactory.java | 97 +++ .../store/rocksdb/RocksDBOptionsFactory.java | 128 ++++ ...ta.config.store.ConfigStoreManagerProvider | 17 + .../config/store/rocksdb/RocksDBTest.java | 190 +++++ .../src/test/resources/registry.conf | 10 + config/seata-config-raft/pom.xml | 44 ++ .../config/raft/RaftConfigurationClient.java | 694 ++++++++++++++++++ .../raft/RaftConfigurationProvider.java | 38 + .../config/raft/RaftConfigurationServer.java | 206 ++++++ ....apache.seata.config.ConfigurationProvider | 17 + ....apache.seata.config.ConfigurationProvider | 17 + .../src/test/resources/registry.conf | 101 +++ .../resources/static/console-fe/package.json | 1 + .../resources/static/console-fe/src/app.tsx | 7 +- .../static/console-fe/src/locales/en-us.ts | 25 + .../static/console-fe/src/locales/zh-cn.ts | 25 + .../src/pages/ConfigInfo/ConfigInfo.tsx | 644 ++++++++++++++++ .../src/pages/ConfigInfo/index.scss | 16 + .../console-fe/src/pages/ConfigInfo/index.ts | 21 + .../static/console-fe/src/router.tsx | 2 + .../console-fe/src/service/configInfo.ts | 80 ++ .../static/console-fe/src/utils/request.ts | 47 ++ dependencies/pom.xml | 7 + .../SeataCoreEnvironmentPostProcessor.java | 9 +- .../boot/autoconfigure/StarterConstants.java | 4 +- .../config/ConfigRaftProperties.java | 82 +++ .../config/ConfigStoreProperties.java | 81 ++ .../config/ConfigRaftPropertiesTest.java | 53 ++ .../config/ConfigStorePropertiesTest.java | 52 ++ .../resources/application-test.properties | 12 + .../java/org/apache/seata/server/Server.java | 2 + .../seata/server/ServerApplication.java | 4 + .../org/apache/seata/server/ServerRunner.java | 9 +- .../listener/ClusterConfigChangeEvent.java | 50 ++ .../listener/ClusterConfigChangeListener.java | 26 + .../manager/ClusterConfigWatcherManager.java | 111 +++ .../server/cluster/raft/RaftConfigServer.java | 119 +++ .../cluster/raft/RaftConfigServerManager.java | 259 +++++++ .../cluster/raft/RaftConfigStateMachine.java | 455 ++++++++++++ .../cluster/raft/RaftServerManager.java | 4 +- .../config/AbstractRaftConfigMsgExecute.java | 29 + .../config/ConfigOperationExecute.java | 143 ++++ .../execute/config/ConfigOperationType.java | 70 ++ .../ConfigOperationRequestProcessor.java | 67 ++ .../PutNodeInfoRequestProcessor.java | 23 + .../request/ConfigOperationRequest.java | 137 ++++ .../response/ConfigOperationResponse.java | 78 ++ .../cluster/raft/snapshot/RaftSnapshot.java | 7 +- .../snapshot/config/ConfigSnapshotFile.java | 106 +++ .../ConfigLeaderMetadataSnapshotFile.java | 88 +++ .../sync/msg/RaftConfigOperationSyncMsg.java | 45 ++ .../raft/sync/msg/RaftSyncMsgType.java | 4 +- .../raft/sync/msg/closure/ConfigClosure.java | 63 ++ .../raft/sync/msg/dto/ConfigOperationDTO.java | 98 +++ .../cluster/raft/util/RaftConfigTaskUtil.java | 78 ++ .../server/cluster/watch/ConfigWatcher.java | 89 +++ .../server/config/ConfigurationProcessor.java | 108 +++ .../server/controller/ClusterController.java | 259 +++++++ .../listener/SeataPropertiesLoader.java | 3 + .../src/main/resources/configuration-meta.yml | 625 ++++++++++++++++ .../config/ConfigurationProcessorTest.java | 55 ++ .../seata/server/raft/RaftServerTest.java | 10 +- .../server/raft/RaftSyncMessageTest.java | 39 +- .../execute/ConfigOperationExecuteTest.java | 81 ++ 88 files changed, 7303 insertions(+), 19 deletions(-) create mode 100644 common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java create mode 100644 config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider create mode 100644 config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java create mode 100644 config/seata-config-raft/pom.xml create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java create mode 100644 config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider create mode 100644 config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider create mode 100644 config/seata-config-raft/src/test/resources/registry.conf create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts create mode 100644 console/src/main/resources/static/console-fe/src/service/configInfo.ts create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java create mode 100644 server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java create mode 100644 server/src/main/resources/configuration-meta.yml create mode 100644 server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java create mode 100644 server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java diff --git a/.gitignore b/.gitignore index 308540907c1..63a74b7da4f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ dependency-reduced-pom.xml /distribution/sessionStore/ /distribution/*/sessionStore/ /file_store/ +/configStore/ +/config/configStore/ +/distribution/configStore/ +/distribution/*/configStore/ # system ignore .DS_Store diff --git a/all/pom.xml b/all/pom.xml index 7e4eb9ab293..cad7663f923 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -91,6 +91,11 @@ seata-config-spring-cloud ${project.version} + + org.apache.seata + seata-config-raft + ${project.version} + org.apache.seata seata-core diff --git a/build/pom.xml b/build/pom.xml index 9e26a12f1a1..7ba41bb5b68 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -311,6 +311,7 @@ *-pom.xml **/db_store/** **/sessionStore/** + **/configStore/** **/root.data false diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index ff8436b6dc9..054b131ae2f 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -1019,6 +1019,31 @@ public interface ConfigurationKeys { */ String ROCKET_MQ_MSG_TIMEOUT = SERVER_PREFIX + "rocketmqMsgTimeout"; + String CONFIG_STORE_PREFIX = FILE_ROOT_PREFIX_CONFIG + "raft" + FILE_CONFIG_SPLIT_CHAR + "db" + FILE_CONFIG_SPLIT_CHAR; + + /** + * The constant CONFIG_STORE_TYPE + */ + String CONFIG_STORE_TYPE = CONFIG_STORE_PREFIX + "type"; + + /** + * The constant CONFIG_STORE_DIR + */ + String CONFIG_STORE_DIR = CONFIG_STORE_PREFIX + "dir"; + + /** + * The constant CONFIG_STORE_DESTROY_ON_SHUTDOWN + */ + String CONFIG_STORE_DESTROY_ON_SHUTDOWN = CONFIG_STORE_PREFIX + "destroyOnShutdown"; + + /** + * The constant CONFIG_STORE_NAMESPACE + */ + String CONFIG_STORE_NAMESPACE = CONFIG_STORE_PREFIX + "namespace"; + /** + * The constant CONFIG_STORE_DATA_ID + */ + String CONFIG_STORE_DATA_ID = CONFIG_STORE_PREFIX + "dataId"; /** * */ @@ -1053,4 +1078,5 @@ public interface ConfigurationKeys { * The constant META_PREFIX */ String META_PREFIX = SEATA_FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + "metadata."; + } diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index 43da1827e05..c1d2489d968 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -225,6 +225,36 @@ public interface Constants { */ String JACKSON_JSON_TEXT_PREFIX = "{\"@class\":"; + /** + * The constant APPLICATION_TYPE_KEY + */ + String APPLICATION_TYPE_KEY = "application.type"; + + /** + * The constant APPLICATION_TYPE_SERVER + */ + String APPLICATION_TYPE_SERVER = "server"; + + /** + * The constant APPLICATION_TYPE_CLIENT + */ + String APPLICATION_TYPE_CLIENT = "client"; + + /** + * The constant DEFAULT_STORE_NAMESPACE in raft configuration + */ + + String DEFAULT_STORE_NAMESPACE = "default"; + /** + * The constant DEFAULT_STORE_DATA_ID in raft configuration + */ + String DEFAULT_STORE_DATA_ID = "seata.properties"; + + /** + * The constant RAFT_CONFIG_GROUP + */ + String RAFT_CONFIG_GROUP = "config"; + /** * The constant DEAD_LOCK_SQL_STATE */ @@ -235,4 +265,8 @@ public interface Constants { */ int DEAD_LOCK_ERROR_CODE = 1213; + /** + * The constant CONFIGURATION_META_FILE_NAME + */ + String CONFIGURATION_META_FILE_NAME = "configuration-meta.yml"; } diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index eb0d40bb308..2396ecd2d14 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -315,4 +315,8 @@ public interface DefaultValues { String DRUID_LOCATION = "lib/sqlparser/druid.jar"; int DEFAULT_ROCKET_MQ_MSG_TIMEOUT = 60 * 1000; + + String DEFAULT_DB_STORE_FILE_DIR = "configStore"; + + String DEFAULT_DB_TYPE = "rocksdb"; } diff --git a/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java b/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java new file mode 100644 index 00000000000..577e1431772 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java @@ -0,0 +1,59 @@ +/* + * 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 org.apache.seata.common.config; + +import java.io.Serializable; + +public class ConfigDataResponse implements Serializable { + private static final long serialVersionUID = -1959848221874923781L; + private T result; + private String errMsg; + private Boolean success; + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + @Override + public String toString() { + return "ConfigDataResponse{" + + "result=" + result + + ", errMsg='" + errMsg + '\'' + + ", success=" + success + + '}'; + } +} diff --git a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java index 609402947fb..0432cc21613 100644 --- a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java +++ b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java @@ -25,6 +25,8 @@ public class MetadataResponse { String storeMode; + String configMode; + long term; public List getNodes() { @@ -50,5 +52,12 @@ public long getTerm() { public void setTerm(long term) { this.term = term; } - + + public String getConfigMode() { + return configMode; + } + + public void setConfigMode(String configMode) { + this.configMode = configMode; + } } diff --git a/common/src/main/java/org/apache/seata/common/store/StoreMode.java b/common/src/main/java/org/apache/seata/common/store/StoreMode.java index 29c90d544f2..68a5092fd16 100644 --- a/common/src/main/java/org/apache/seata/common/store/StoreMode.java +++ b/common/src/main/java/org/apache/seata/common/store/StoreMode.java @@ -16,6 +16,8 @@ */ package org.apache.seata.common.store; +import org.apache.seata.common.util.StringUtils; + /** * transaction log store mode * @@ -55,6 +57,9 @@ public enum StoreMode { * @return the store mode */ public static StoreMode get(String name) { + if (StringUtils.isEmpty(name)) { + return null; + } for (StoreMode sm : StoreMode.class.getEnumConstants()) { if (sm.name.equalsIgnoreCase(name)) { return sm; diff --git a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java index 7374bdc08c5..fdd91addff8 100644 --- a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java +++ b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java @@ -60,4 +60,22 @@ public static Long toLong(String str) { } return null; } + + public static byte[] longToBytes(long x) { + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte)(x & 0xFF); + x >>= 8; + } + return result; + } + + public static long bytesToLong(byte[] bytes) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= bytes[i] & 0xFF; + } + return result; + } } diff --git a/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java b/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java index 828f7c2fe26..1db14ee78b5 100644 --- a/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java +++ b/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java @@ -36,4 +36,12 @@ public void testToInReturnDefaultValueWithFormatIsInvalid() { public void testToInReturnParsedValue() { Assertions.assertEquals(10, NumberUtils.toInt("10", 9)); } + + @Test + public void testBytesAndLong() { + Long a = 123456L; + byte[] bytes = NumberUtils.longToBytes(a); + long b = NumberUtils.bytesToLong(bytes); + Assertions.assertEquals(a, b); + } } diff --git a/config/pom.xml b/config/pom.xml index 3ed784fb152..a0d76aa6e9c 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -41,5 +41,6 @@ seata-config-etcd3 seata-config-consul seata-config-spring-cloud + seata-config-raft diff --git a/config/seata-config-all/pom.xml b/config/seata-config-all/pom.xml index 0eceb6a2e82..1818bffcd3c 100644 --- a/config/seata-config-all/pom.xml +++ b/config/seata-config-all/pom.xml @@ -60,6 +60,11 @@ seata-config-spring-cloud ${project.version} + + ${project.groupId} + seata-config-raft + ${project.version} + diff --git a/config/seata-config-core/pom.xml b/config/seata-config-core/pom.xml index 896a7070fe3..501276ff505 100644 --- a/config/seata-config-core/pom.xml +++ b/config/seata-config-core/pom.xml @@ -43,6 +43,11 @@ org.yaml snakeyaml + + org.rocksdb + rocksdbjni + + diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java index 869d2d19c40..441e30c65b1 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java @@ -49,6 +49,10 @@ public enum ConfigType { * spring cloud config type */ SpringCloudConfig, + /** + * Raft config type + */ + Raft, /** * Custom config type */ diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java index 714f8b17678..c3f7076aa8b 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java @@ -38,6 +38,10 @@ public ConfigurationChangeEvent(String dataId, String newValue) { this(dataId, DEFAULT_NAMESPACE, null, newValue, ConfigurationChangeType.MODIFY); } + public ConfigurationChangeEvent(String namespace, String dataId, String newValue) { + this(dataId, namespace, null, newValue, ConfigurationChangeType.MODIFY); + } + public ConfigurationChangeEvent(String dataId, String namespace, String oldValue, String newValue, ConfigurationChangeType type) { this.dataId = dataId; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java new file mode 100644 index 00000000000..6dff2018856 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java @@ -0,0 +1,44 @@ +/* + * 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 org.apache.seata.config.dto; + +import java.io.Serializable; +import java.util.Map; + +public class ConfigurationInfoDto implements Serializable { + private static final long serialVersionUID = 72337179613855724L; + + private Map config; + + private Long version; + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public Long getVersion() { + return version; + } + + public void setVersion(Long version) { + this.version = version; + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java new file mode 100644 index 00000000000..6b47cb801e9 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java @@ -0,0 +1,65 @@ +/* + * 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 org.apache.seata.config.dto; + +import java.io.Serializable; + +/** + * The configuration items + * + */ +public class ConfigurationItem implements Serializable { + private static final long serialVersionUID = 32787493713855767L; + private String key; + private Object value; + private String description; + private Object defaultValue; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Object getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + + +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java new file mode 100644 index 00000000000..d7c493d7879 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java @@ -0,0 +1,54 @@ +/* + * 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 org.apache.seata.config.dto; + +import java.io.Serializable; + +/** + * The configuration items meta + * + */ +public class ConfigurationItemMeta implements Serializable { + + private static final long serialVersionUID = 8771878731395411166L; + private final String key; + private final String description; + private final Object defaultValue; + private final Boolean isEncrypt; + + public ConfigurationItemMeta(String key, String description, Object defaultValue, Boolean isEncrypt) { + this.key = key; + this.description = description; + this.defaultValue = defaultValue; + this.isEncrypt = isEncrypt; + } + + public String getKey() { + return key; + } + + public String getDescription() { + return description; + } + + public Object getDefaultValue() { + return defaultValue; + } + public Boolean getEncrypt() { + return isEncrypt; + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java new file mode 100644 index 00000000000..9fab926a52b --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java @@ -0,0 +1,96 @@ +/* + * 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 org.apache.seata.config.store; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigurationChangeListener; +import org.apache.seata.config.processor.ConfigDataType; +import org.apache.seata.config.processor.ConfigProcessor; + +/** + * The interface Local config store manager. + * + */ +public interface ConfigStoreManager { + String get(String namespace, String dataId, String key); + + Map getAll(String namespace, String dataId); + + Boolean put(String namespace, String dataId, String key, Object value); + + Boolean delete(String namespace, String dataId, String key); + + Boolean putAll(String namespace, String dataId, Map configMap); + + Boolean deleteAll(String namespace, String dataId); + + Boolean isEmpty(String namespace, String dataId); + + Map> getConfigMap(); + + Boolean putConfigMap(Map> configMap); + + Boolean clearData(); + + List getAllNamespaces(); + + List getAllDataIds(String namespace); + + Long getConfigVersion(String namespace, String dataId); + + Boolean putConfigVersion(String namespace, String dataId, Long version); + + Boolean deleteConfigVersion(String namespace, String dataId); + void destroy(); + void shutdown(); + + default void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; + + default void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; + + static String convertConfig2Str(Map configs) { + StringBuilder sb = new StringBuilder(); + if (CollectionUtils.isEmpty(configs)) { + sb.toString(); + } + for (Map.Entry entry : configs.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("\n"); + } + return sb.toString(); + } + + static Map convertConfigStr2Map(String configStr) { + if (StringUtils.isEmpty(configStr)) { + return new HashMap<>(); + } + Map configs = new HashMap<>(); + try { + Properties properties = ConfigProcessor.processConfig(configStr, ConfigDataType.properties.name()); + properties.forEach((k, v) -> configs.put(k.toString(), v)); + return configs; + } catch (IOException e) { + return new HashMap<>(); + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java new file mode 100644 index 00000000000..1f692893909 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java @@ -0,0 +1,49 @@ +/* + * 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 org.apache.seata.config.store; + +import java.util.Objects; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + +public class ConfigStoreManagerFactory { + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static volatile ConfigStoreManager instance; + + public static ConfigStoreManager getInstance() { + if (instance == null) { + synchronized (ConfigStoreManagerFactory.class) { + if (instance == null) { + String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); + instance = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + } + } + } + return instance; + } + + public static void destroy() { + if (instance != null) { + instance.shutdown(); + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java new file mode 100644 index 00000000000..15aededefd5 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java @@ -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 org.apache.seata.config.store; + +/** + * the interface configStoreManager provider + */ +public interface ConfigStoreManagerProvider { + /** + * provide a AbstractConfigStoreManager implementation instance + * @return ConfigStoreManager + */ + ConfigStoreManager provide(); +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java new file mode 100644 index 00000000000..70ac7ae176c --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -0,0 +1,691 @@ +/* + * 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 org.apache.seata.config.store.rocksdb; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.NumberUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationChangeEvent; +import org.apache.seata.config.ConfigurationChangeListener; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.FileConfiguration; +import org.apache.seata.config.store.ConfigStoreManager; +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.DBOptions; +import org.rocksdb.Options; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.rocksdb.RocksIterator; +import org.rocksdb.WriteBatch; +import org.rocksdb.WriteOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.ConfigurationKeys.CLIENT_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; +import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_PREFIX_CONFIG; +import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_PREFIX_REGISTRY; +import static org.apache.seata.common.ConfigurationKeys.LOG_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.METRICS_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.SEATA_FILE_PREFIX_ROOT_CONFIG; +import static org.apache.seata.common.ConfigurationKeys.SERVER_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.SERVICE_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.STORE_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.TCC_PREFIX; +import static org.apache.seata.common.ConfigurationKeys.TRANSPORT_PREFIX; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; + +/** + * The RocksDB config store manager + * + */ +public class RocksDBConfigStoreManager implements ConfigStoreManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final String DB_PATH = RocksDBOptionsFactory.getDBPath(); + private static final String DEFAULT_NAMESPACE = DEFAULT_STORE_NAMESPACE; + private static final String DEFAULT_DATA_ID = DEFAULT_STORE_DATA_ID; + private static String CURRENT_DATA_ID; + private static String CURRENT_NAMESPACE; + private static final String NAME_KEY = "name"; + private static final String FILE_TYPE = "file"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final DBOptions DB_OPTIONS = RocksDBOptionsFactory.getDBOptions(); + private static final Map LOCK_MAP = new ConcurrentHashMap<>(); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap>> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>( + MAP_INITIAL_CAPACITY); + + //====================================NON COMMON FILED=================================== + private static volatile RocksDBConfigStoreManager instance; + private RocksDB rocksdb; + private final Map columnFamilyHandleMap = new ConcurrentHashMap<>(); + private static final String VERSION_COLUMN_FAMILY = "config_version"; + private static final List PREFIX_LIST = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, + STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX, LOG_PREFIX, TCC_PREFIX); + + + public static RocksDBConfigStoreManager getInstance() { + if (instance == null) { + synchronized (RocksDBConfigStoreManager.class) { + if (instance == null) { + instance = new RocksDBConfigStoreManager(); + } + } + } + return instance; + } + + public RocksDBConfigStoreManager() { + super(); + CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_NAMESPACE); + CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_DATA_ID); + openRocksDB(); + maybeNeedLoadOriginConfig(); + LOGGER.info("RocksDBConfigStoreManager initialized successfully"); + } + + private void openRocksDB() { + final List handles = new ArrayList<>(); + final List descriptors = new ArrayList<>(); + try (final Options options = new Options()) { + List cfs = RocksDB.listColumnFamilies(options, DB_PATH); + for (byte[] cf : cfs) { + String namespace = new String(cf); + descriptors.add(new ColumnFamilyDescriptor(cf, RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); + } + // create default column family and config version column family + if (CollectionUtils.isEmpty(descriptors)) { + descriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, RocksDBOptionsFactory.getColumnFamilyOptionsMap(new String(RocksDB.DEFAULT_COLUMN_FAMILY)))); + descriptors.add(new ColumnFamilyDescriptor(VERSION_COLUMN_FAMILY.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(VERSION_COLUMN_FAMILY))); + } + this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS, descriptors, handles); + for (ColumnFamilyHandle handle : handles) { + columnFamilyHandleMap.put(new String(handle.getName()), handle); + } + } catch (RocksDBException e) { + LOGGER.error("open rocksdb error", e); + } + } + + private ColumnFamilyHandle getOrCreateColumnFamilyHandle(String namespace) throws RocksDBException { + ColumnFamilyHandle handle = columnFamilyHandleMap.get(namespace); + if (handle == null) { + synchronized (RocksDBConfigStoreManager.class) { + handle = columnFamilyHandleMap.get(namespace); + if (handle == null) { + handle = rocksdb.createColumnFamily(new ColumnFamilyDescriptor( + namespace.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); + columnFamilyHandleMap.put(namespace, handle); + } + } + } + return handle; + } + + /** + * load origin config if first startup + */ + private void maybeNeedLoadOriginConfig() { + if (isEmpty(CURRENT_NAMESPACE, CURRENT_DATA_ID)) { + Map configs = new HashMap<>(); + Map seataConfigs = new HashMap<>(); + String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, + ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY); + String name = FILE_CONFIG.getConfig(pathDataId); + // create FileConfiguration for read file.conf + Optional originFileInstance = Optional.ofNullable(new FileConfiguration(name)); + originFileInstance + .ifPresent(fileConfiguration -> configs.putAll(fileConfiguration.getFileConfig().getAllConfig())); + configs.forEach((k, v) -> { + if (v instanceof String) { + if (StringUtils.isEmpty((String)v)) { + return; + } + } + // compatible with the config under Spring Boot + if (k.startsWith(SEATA_FILE_PREFIX_ROOT_CONFIG)) { + k = k.substring(SEATA_FILE_PREFIX_ROOT_CONFIG.length()); + } + // filter all seata related configs + if (PREFIX_LIST.stream().anyMatch(k::startsWith)) { + seataConfigs.put(k, v); + } + }); + putAll(CURRENT_NAMESPACE, CURRENT_DATA_ID, seataConfigs); + LOGGER.info("Load initialization configuration file sucessfully in namespace: {}, dataId: {}", CURRENT_NAMESPACE, CURRENT_DATA_ID); + } + } + + /** + * Acquire lock of the given namespace + * @param namespace + */ + private ReentrantReadWriteLock acquireLock(String namespace) { + return LOCK_MAP.computeIfAbsent(namespace, k -> new ReentrantReadWriteLock()); + } + + /** + * Get config map of the given namespace and dataId + * @param namespace + * @param dataId + * @return + * @throws RocksDBException + */ + private Map getConfigMap(String namespace, String dataId) throws RocksDBException { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + // the column family not exist, return empty map + if (handle == null) { + return new HashMap<>(); + } + byte[] value = rocksdb.get(handle, dataId.getBytes(DEFAULT_CHARSET)); + String configStr = value != null ? new String(value, DEFAULT_CHARSET) : null; + return ConfigStoreManager.convertConfigStr2Map(configStr); + } finally { + lock.readLock().unlock(); + } + } + + /** + * Get the config value of the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @return + */ + @Override + public String get(String namespace, String dataId, String key) { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + try { + Map configMap = getConfigMap(namespace, dataId); + return configMap.get(key) != null ? configMap.get(key).toString() : null; + } catch (RocksDBException e) { + LOGGER.error("Failed to get value for key: " + key, e); + } finally { + lock.readLock().unlock(); + } + return null; + } + + /** + * Get all config items of the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ + @Override + public Map getAll(String namespace, String dataId) { + try { + return getConfigMap(namespace, dataId); + } catch (RocksDBException e) { + LOGGER.error("Failed to get all configs", e); + } + return null; + } + + /** + * Put a config item to the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @param value + * @return + */ + @Override + public Boolean put(String namespace, String dataId, String key, Object value) { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + Map configMap = getConfigMap(namespace, dataId); + configMap.put(key, value); + String configStr = ConfigStoreManager.convertConfig2Str(configMap); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to put value for key: " + key, e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + /** + * Delete a config item with the given key from the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @return + */ + @Override + public Boolean delete(String namespace, String dataId, String key) { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + Map configMap = getConfigMap(namespace, dataId); + configMap.remove(key); + String configStr = ConfigStoreManager.convertConfig2Str(configMap); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to delete value for key: " + key, e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + /** + * Put all config items into the given namespace and dataId + * @param namespace + * @param dataId + * @param configMap + * @return + */ + @Override + public Boolean putAll(String namespace, String dataId, Map configMap) { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + String configStr = ConfigStoreManager.convertConfig2Str(configMap); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to put all configs", e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + /** + * Delete all config items in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ + @Override + public Boolean deleteAll(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.delete(handle, dataId.getBytes(DEFAULT_CHARSET)); + deleteConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, null)); + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to clear all configs", e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + + /** + * Get all key-values pairs in all namespaces, mainly used for backup or snapshot + * @return Map(namespace -> Map(dataId -> value)) + */ + @Override + public Map> getConfigMap() { + Map> configMap = new HashMap<>(); + for (String namespace : columnFamilyHandleMap.keySet()) { + HashMap configs = new HashMap<>(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + RocksIterator iterator = null; + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + iterator = rocksdb.newIterator(handle); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + String key = new String(iterator.key(), DEFAULT_CHARSET); + String value = new String(iterator.value(), DEFAULT_CHARSET); + configs.put(key, value); + } + configMap.put(namespace, configs); + } catch (RocksDBException e) { + LOGGER.error("Failed to get configMap in namespace : {}", namespace, e); + } finally { + if (iterator != null) { + iterator.close(); + } + lock.readLock().unlock(); + } + } + return configMap; + } + + /** + * Put all key-value pairs into the specified column family, mainly used for backup or snapshot + * @param configMap Map(namespace -> Map(dataId -> value)) + * @return + */ + @Override + public Boolean putConfigMap(Map> configMap) { + try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { + for (Map.Entry> entry : configMap.entrySet()) { + String namespace = entry.getKey(); + Map configs = entry.getValue(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + for (Map.Entry nsEntry : configs .entrySet()) { + batch.put(handle, nsEntry.getKey().getBytes(DEFAULT_CHARSET), nsEntry.getValue().toString().getBytes(DEFAULT_CHARSET)); + } + } catch (RocksDBException e) { + LOGGER.error("Failed to put configMap in namespace : {}", namespace, e); + } finally { + lock.writeLock().unlock(); + } + } + rocksdb.write(writeOptions, batch); + for (Map.Entry> entry : configMap.entrySet()) { + String namespace = entry.getKey(); + Map configs = entry.getValue(); + for (Map.Entry kv : configs.entrySet()) { + String dataId = kv.getKey(); + updateConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, kv.getKey(), kv.getValue().toString())); + } + } + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to put all configMap", e); + return false; + } + } + + /** + * Empty all data in rocksdb, i.e. delete all key-value pairs in all column family + * @return + */ + @Override + public Boolean clearData() { + Map> clearDataMap = new HashMap<>(); + try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { + for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { + String namespace = new String(handle.getName()); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + HashSet deleteKeySet = new HashSet<>(); + try (RocksIterator iterator = rocksdb.newIterator(handle)) { + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + batch.delete(handle, iterator.key()); + deleteKeySet.add(new String(iterator.key())); + } + clearDataMap.put(namespace, deleteKeySet); + } finally { + lock.writeLock().unlock(); + } + } + rocksdb.write(writeOptions, batch); + for (Map.Entry> entry : clearDataMap.entrySet()) { + String namespace = entry.getKey(); + for (String key : entry.getValue()) { + deleteConfigVersion(namespace, key); + notifyConfigChange(namespace, key, new ConfigurationChangeEvent(namespace, key, null)); + } + } + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to clear all data in rocksdb", e); + return false; + } + } + + /** + * Check whether the config data exists in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ + @Override + public Boolean isEmpty(String namespace, String dataId) { + return CollectionUtils.isEmpty(getAll(namespace, dataId)); + } + + /** + * Get all namespaces in current rocksdb instance + * @return + */ + @Override + public List getAllNamespaces() { + return columnFamilyHandleMap.keySet().stream() + .filter(namespace -> !VERSION_COLUMN_FAMILY.equals(namespace)) + .collect(Collectors.toList()); + } + + /** + * Get all dataIds in the given namespace + * @param namespace + * @return + */ + @Override + public List getAllDataIds(String namespace) { + if (StringUtils.isEmpty(namespace) || !columnFamilyHandleMap.containsKey(namespace)) { + return Collections.emptyList(); + } + List dataIds = new ArrayList<>(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + RocksIterator iterator = null; + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + iterator = rocksdb.newIterator(handle); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + String dataId = new String(iterator.key(), DEFAULT_CHARSET); + dataIds.add(dataId); + } + } catch (RocksDBException e) { + LOGGER.error("Failed to get all dataIds in namespace: {}", namespace, e); + } finally { + if (iterator != null) { + iterator.close(); + } + lock.readLock().unlock(); + } + return dataIds; + } + + /** + * Get the config version in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ + @Override + public Long getConfigVersion(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.readLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + byte[] value = rocksdb.get(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); + return value != null ? NumberUtils.bytesToLong(value) : null; + } catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to get config version in namespace: {} and dataId: {}", namespace, dataId, e); + } finally { + lock.readLock().unlock(); + } + return null; + } + + /** + * Put the config version in the given namespace and dataId + * @param namespace + * @param dataId + * @param version + * @return + */ + @Override + public Boolean putConfigVersion(String namespace, String dataId, Long version) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.writeLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + rocksdb.put(handle, configVersionKey.getBytes(DEFAULT_CHARSET), NumberUtils.longToBytes(version)); + return true; + } catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + /** + * Delete the config version in the given namespace and dataId when the config data is deleted. + * @param namespace + * @param dataId + * @return + */ + @Override + public Boolean deleteConfigVersion(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.writeLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + rocksdb.delete(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); + return true; + } catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + private String getConfigVersionKey(String namespace, String dataId) { + if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(dataId)) { + throw new IllegalArgumentException("Invalid config namespace or dataId"); + } + return namespace + "_" + dataId; + } + + @Override + public void shutdown() { + synchronized (RocksDBConfigStoreManager.class) { + // 1. close all handles + for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { + if (handle != null) { + handle.close(); + } + } + // 2. close options + RocksDBOptionsFactory.releaseAllOptions(); + // 3. close db + RocksDBFactory.close(); + // 4. destroy db if needed + if (RocksDBOptionsFactory.getDBDestroyOnShutdown()) { + destroy(); + } + // 5. help gc + columnFamilyHandleMap.clear(); + this.rocksdb = null; + LOGGER.info("RocksDBConfigStoreManager has shutdown"); + } + } + + @Override + public void destroy() { + RocksDBFactory.destroy(DB_PATH); + LOGGER.info("DB destroyed, the db path is: {}.", DB_PATH); + } + + + @Override + public void addConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { + return; + } + Map> listenerMap = CONFIG_LISTENERS_MAP.computeIfAbsent(namespace, k -> new ConcurrentHashMap<>()); + listenerMap.computeIfAbsent(dataId, k -> ConcurrentHashMap.newKeySet()) + .add(listener); + } + + @Override + public void removeConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { + return; + } + // dataId -> listener + Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); + if (CollectionUtils.isNotEmpty(listenerMap)) { + Set configChangeListeners = listenerMap.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.remove(listener); + } + } + } + + private void updateConfigVersion(String namespace, String dataId) { + Long version = getConfigVersion(namespace, dataId); + if (version == null) { + version = 0L; + } + putConfigVersion(namespace, dataId, version + 1); + } + + + private void notifyConfigChange(String namespace, String dataId, ConfigurationChangeEvent event) { + Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); + if (CollectionUtils.isNotEmpty(listenerMap)) { + Set configChangeListeners = listenerMap.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.forEach(listener -> listener.onChangeEvent(event)); + } + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java new file mode 100644 index 00000000000..b9b32a05992 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java @@ -0,0 +1,30 @@ +/* + * 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 org.apache.seata.config.store.rocksdb; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerProvider; + + +@LoadLevel(name = "Rocksdb", order = 1) +public class RocksDBConfigStoreManagerProvider implements ConfigStoreManagerProvider { + @Override + public ConfigStoreManager provide() { + return RocksDBConfigStoreManager.getInstance(); + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java new file mode 100644 index 00000000000..f03ac872334 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java @@ -0,0 +1,97 @@ +/* + * 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 org.apache.seata.config.store.rocksdb; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.DBOptions; +import org.rocksdb.RocksDB; +import org.rocksdb.Options; +import org.rocksdb.RocksDBException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The RocksDB Factory + * + */ +public class RocksDBFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBFactory.class); + + private static volatile RocksDB instance = null; + + + static { + RocksDB.loadLibrary(); + } + + public static RocksDB getInstance(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { + if (instance == null) { + synchronized (RocksDBFactory.class) { + if (instance == null) { + instance = build(dbPath, dbOptions, columnFamilyDescriptors, columnFamilyHandles); + } + } + } + return instance; + } + + private static RocksDB build(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { + try { + checkPath(dbPath); + return RocksDB.open(dbOptions, dbPath, columnFamilyDescriptors, columnFamilyHandles); + } catch (RocksDBException | IOException e) { + LOGGER.error("RocksDB open error: {}", e.getMessage(), e); + return null; + } + } + + + public static synchronized void close() { + if (instance != null) { + instance.close(); + instance = null; + } + } + + public static synchronized void destroy(String dbPath) { + close(); + try (final Options opt = new Options()) { + RocksDB.destroyDB(dbPath, opt); + } catch (RocksDBException e) { + LOGGER.error("RocksDB destroy error: {}", e.getMessage(), e); + } + } + + private static void checkPath(String dbPath) throws IOException { + File directory = new File(dbPath); + String message; + if (directory.exists()) { + if (!directory.isDirectory()) { + message = "File " + directory + " exists and is not a directory. Unable to create directory."; + throw new IOException(message); + } + } else if (!directory.mkdirs() && !directory.isDirectory()) { + message = "Unable to create directory " + directory; + throw new IOException(message); + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java new file mode 100644 index 00000000000..9846a8b98e1 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java @@ -0,0 +1,128 @@ +/* + * 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 org.apache.seata.config.store.rocksdb; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.rocksdb.ColumnFamilyOptions; +import org.rocksdb.CompactionStyle; +import org.rocksdb.CompressionType; +import org.rocksdb.DBOptions; +import org.rocksdb.util.SizeUnit; + +import static java.io.File.separator; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DESTROY_ON_SHUTDOWN; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DIR; +import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + + +/** + * The RocksDB options builder + * + */ +public class RocksDBOptionsFactory { + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + public static final String ROCKSDB_SUFFIX = "rocksdb"; + private static volatile DBOptions options = null; + private static final Map COLUMN_FAMILY_OPTIONS_MAP = new ConcurrentHashMap<>(); + public static DBOptions getDBOptions() { + if (options == null) { + synchronized (RocksDBOptionsFactory.class) { + if (options == null) { + options = buildDBOptions(); + } + } + } + return options; + } + + public static ColumnFamilyOptions getColumnFamilyOptionsMap(final String namespace) { + ColumnFamilyOptions opts = COLUMN_FAMILY_OPTIONS_MAP.get(namespace); + if (opts == null) { + final ColumnFamilyOptions newOpts = buildColumnFamilyOptions(); + opts = COLUMN_FAMILY_OPTIONS_MAP.putIfAbsent(namespace, newOpts); + if (opts != null) { + newOpts.close(); + } else { + opts = newOpts; + } + } + return opts; + } + public static String getDBPath() { + String dir = FILE_CONFIG.getConfig(CONFIG_STORE_DIR); + String group = FILE_CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + return String.join(separator, dir, group, ROCKSDB_SUFFIX); + } + + public static boolean getDBDestroyOnShutdown() { + return FILE_CONFIG.getBoolean(CONFIG_STORE_DESTROY_ON_SHUTDOWN, false); + } + + private static DBOptions buildDBOptions() { + final DBOptions options = new DBOptions(); + // If the database does not exist, create it + options.setCreateIfMissing(true); + // If true, missing column families will be automatically created. + options.setCreateMissingColumnFamilies(true); + // Retain only the latest log file + options.setKeepLogFileNum(1); + // Disable log file rolling based on time + options.setLogFileTimeToRoll(0); + // Disable log file rolling based on size + options.setMaxLogFileSize(0); + // Number of open files that can be used by the DB. + options.setMaxOpenFiles(-1); + return options; + } + + private static ColumnFamilyOptions buildColumnFamilyOptions() { + ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); + // set little write buffer size, since the size of config file is small + columnFamilyOptions.setWriteBufferSize(4 * SizeUnit.MB); + // Set memtable prefix bloom filter to reduce memory usage. + columnFamilyOptions.setMemtablePrefixBloomSizeRatio(0.125); + // Set compression type + columnFamilyOptions.setCompressionType(CompressionType.LZ4_COMPRESSION); + // Set compaction style + columnFamilyOptions.setCompactionStyle(CompactionStyle.LEVEL); + // Optimize level style compaction + columnFamilyOptions.optimizeLevelStyleCompaction(); + return columnFamilyOptions; + } + + public static void releaseAllOptions() { + // close all options + if (options != null) { + options.close(); + } + for (final ColumnFamilyOptions opts : COLUMN_FAMILY_OPTIONS_MAP.values()) { + if (opts != null) { + opts.close(); + } + } + // help gc + options = null; + COLUMN_FAMILY_OPTIONS_MAP.clear(); + } + +} diff --git a/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider b/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider new file mode 100644 index 00000000000..0d199fafe7c --- /dev/null +++ b/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManagerProvider \ No newline at end of file diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java new file mode 100644 index 00000000000..6850ebedde5 --- /dev/null +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -0,0 +1,190 @@ +/* + * 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 org.apache.seata.config.store.rocksdb; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; + + +class RocksDBTest { + private static RocksDBConfigStoreManager configStoreManager; + + private static final String dataId = DEFAULT_STORE_DATA_ID; + private static final String namespace = DEFAULT_STORE_NAMESPACE; + @BeforeAll + static void setUp() { + configStoreManager = RocksDBConfigStoreManager.getInstance(); + } + + @AfterAll + static void tearDown() { + if (configStoreManager != null) { + configStoreManager.shutdown(); + configStoreManager.destroy(); + } + } + + @Test + void getConfigStoreManagerTest() { + Assertions.assertNotNull(configStoreManager); + } + + + @Test + void crudTest() { + configStoreManager.deleteAll(namespace, dataId); + String key = "aaa"; + String value = "bbb"; + String updateValue = "ccc"; + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); + Assertions.assertEquals(value, configStoreManager.get(namespace, dataId, key)); + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, updateValue)); + Assertions.assertEquals(updateValue, configStoreManager.get(namespace, dataId, key)); + Assertions.assertTrue(configStoreManager.delete(namespace, dataId, key)); + Assertions.assertNull(configStoreManager.get(namespace, dataId, key)); + + } + + @Test + void uploadConfigTest() { + configStoreManager.deleteAll(namespace, dataId); + HashMap uploadConfigs = new HashMap<>(); + uploadConfigs.put("aaa","111"); + uploadConfigs.put("bbb","222"); + Assertions.assertTrue(configStoreManager.putAll(namespace, dataId, uploadConfigs)); + Assertions.assertEquals(uploadConfigs, configStoreManager.getAll(namespace, dataId)); + configStoreManager.deleteAll(namespace, dataId); + Assertions.assertTrue(configStoreManager.isEmpty(namespace, dataId)); + } + + + @Test + void multiGroupTest() { + configStoreManager.deleteAll(namespace, dataId); + String group1 = "group1"; + String group2 = "group2"; + String key = "aaa"; + String value1 = "aaa"; + String value2 = "bbb"; + // put and get + Assertions.assertTrue(configStoreManager.put(namespace, group1, key, value1)); + Assertions.assertTrue(configStoreManager.put(namespace, group2, key, value2)); + Assertions.assertEquals(value1, configStoreManager.get(namespace, group1, key)); + Assertions.assertEquals(value2, configStoreManager.get(namespace, group2, key)); + + // delete + Assertions.assertTrue(configStoreManager.delete(namespace, group1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace, group2, key)); + Assertions.assertNull(configStoreManager.get(namespace, group1, key)); + Assertions.assertNull(configStoreManager.get(namespace, group2, key)); + } + + + @Test + void multiNamespaceAndGroupTest() { + configStoreManager.clearData(); + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + List namespaces = Arrays.asList(DEFAULT_STORE_NAMESPACE, namespace1, namespace2); + String dataId1 = "dataId1"; + String dataId2 = "dataId2"; + List dataIds = Arrays.asList(dataId1, dataId2); + String key = "aaa"; + // put and get + Assertions.assertTrue(configStoreManager.put(namespace1, dataId1, key , "11")); + Assertions.assertTrue(configStoreManager.put(namespace1, dataId2, key , "12")); + Assertions.assertTrue(configStoreManager.put(namespace2, dataId1, key , "21")); + Assertions.assertTrue(configStoreManager.put(namespace2, dataId2, key , "22")); + Assertions.assertEquals("11", configStoreManager.get(namespace1, dataId1, key)); + Assertions.assertEquals("12", configStoreManager.get(namespace1, dataId2, key)); + Assertions.assertEquals("21", configStoreManager.get(namespace2, dataId1, key)); + Assertions.assertEquals("22", configStoreManager.get(namespace2, dataId2, key)); + Assertions.assertEquals(namespaces.size(), configStoreManager.getAllNamespaces().size()); + Assertions.assertEquals(dataIds.size(), configStoreManager.getAllDataIds(namespace1).size()); + Assertions.assertEquals(dataIds.size(), configStoreManager.getAllDataIds(namespace2).size()); + // delete + Assertions.assertTrue(configStoreManager.delete(namespace1, dataId1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace1, dataId2, key)); + Assertions.assertTrue(configStoreManager.delete(namespace2, dataId1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace2, dataId2, key)); + Assertions.assertNull(configStoreManager.get(namespace1, dataId1, key)); + Assertions.assertNull(configStoreManager.get(namespace1, dataId2, key)); + Assertions.assertNull(configStoreManager.get(namespace2, dataId1, key)); + Assertions.assertNull(configStoreManager.get(namespace2, dataId2, key)); + } + + @Test + void uploadTest() { + configStoreManager.clearData(); + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String dataId1 = "dataId1"; + String dataId2 = "dataId2"; + HashMap> configMap = new HashMap>(); + HashMap map1 = new HashMap() {{ + put(dataId1, "11"); + put(dataId2, "12"); + }}; + HashMap map2 = new HashMap() {{ + put(dataId1, "21"); + put(dataId2, "22"); + }}; + configMap.put(namespace1,map1); + configMap.put(namespace2,map2); + // ensure default namespace + configMap.put("default",new HashMap<>()); + Assertions.assertTrue(configStoreManager.putConfigMap(configMap)); + Map> other = configStoreManager.getConfigMap(); + + Assertions.assertEquals(configMap.get(namespace1), other.get(namespace1)); + Assertions.assertEquals(configMap.get(namespace2), other.get(namespace2)); + Assertions.assertEquals(configMap.get("default"), other.get("default")); + + Assertions.assertDoesNotThrow(()->configStoreManager.getAll(namespace1, dataId1)); + } + + @Test + void configVersionTest() { + configStoreManager.clearData(); + Long version = 0L; + + String key = "aaa"; + String value = "bbb"; + String newValue = "ccc"; + + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); + version++; + Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); + + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, newValue)); + version++; + Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); + + Assertions.assertTrue(configStoreManager.deleteAll(namespace, dataId)); + Assertions.assertNull(configStoreManager.getConfigVersion(namespace, dataId)); + } +} diff --git a/config/seata-config-core/src/test/resources/registry.conf b/config/seata-config-core/src/test/resources/registry.conf index bab6e8ec0ef..343179ab6a8 100644 --- a/config/seata-config-core/src/test/resources/registry.conf +++ b/config/seata-config-core/src/test/resources/registry.conf @@ -87,4 +87,14 @@ config { file { name = "file.conf" } + raft { + db { + type = "rocksdb" + dir = "configStore" + destroy-on-shutdown = false + namespace = "default" + dataId = "seata.properties" + } + } + } diff --git a/config/seata-config-raft/pom.xml b/config/seata-config-raft/pom.xml new file mode 100644 index 00000000000..20b7b3ddb5c --- /dev/null +++ b/config/seata-config-raft/pom.xml @@ -0,0 +1,44 @@ + + + + + org.apache.seata + seata-config + ${revision} + + 4.0.0 + seata-config-raft + seata-config-raft ${project.version} + config-raft for Seata built with Maven + + + + org.apache.seata + seata-config-core + ${project.version} + + + org.apache.httpcomponents + httpclient + + + + \ No newline at end of file diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java new file mode 100644 index 00000000000..c27626f9eec --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -0,0 +1,694 @@ +/* + * 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 org.apache.seata.config.raft; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.EntityUtils; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.config.ConfigDataResponse; +import org.apache.seata.common.exception.AuthenticationFailedException; +import org.apache.seata.common.exception.ErrorCode; +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.exception.RetryableException; +import org.apache.seata.common.exception.SeataRuntimeException; +import org.apache.seata.common.metadata.Metadata; +import org.apache.seata.common.metadata.MetadataResponse; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.HttpClientUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.AbstractConfiguration; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationChangeEvent; +import org.apache.seata.config.ConfigurationChangeListener; +import org.apache.seata.config.ConfigurationChangeType; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.dto.ConfigurationInfoDto; +import org.apache.seata.config.dto.ConfigurationItem; +import org.apache.seata.config.store.ConfigStoreManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; +import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + +/** + * The type Raft configuration of client. + * + */ +public class RaftConfigurationClient extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationClient.class); + private static final String CONFIG_TYPE = "raft"; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; + private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; + private static final String CONFIG_NAMESPACE; + private static final String CONFIG_DATA_ID; + private static final String HTTP_PREFIX = "http://"; + private static final String USERNAME_KEY = "username"; + private static final String PASSWORD_KEY = "password"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String TOKEN_VALID_TIME_MS_KEY = "tokenValidityInMilliseconds"; + private static volatile RaftConfigurationClient instance; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String USERNAME; + private static final String PASSWORD; + private static final long TOKEN_EXPIRE_TIME_IN_MILLISECONDS; + private static volatile long tokenTimeStamp = -1; + private static volatile String jwtToken; + private static final String IP_PORT_SPLIT_CHAR = ":"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final Map> INIT_ADDRESSES = new HashMap<>(); + private static final Metadata METADATA = new Metadata(); + private static volatile ThreadPoolExecutor REFRESH_METADATA_EXECUTOR; + private static volatile ThreadPoolExecutor REFRESH_CONFIG_EXECUTOR; + private static final AtomicBoolean CLOSED = new AtomicBoolean(false); + private static final AtomicBoolean CONFIG_CLOSED = new AtomicBoolean(false); + private static volatile Properties seataConfig = new Properties(); + private static final AtomicLong CONFIG_VERSION = new AtomicLong(0); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + + private static ConfigStoreListener CONFIG_LISTENER; + static { + USERNAME = FILE_CONFIG.getConfig(getRaftUsernameKey()); + PASSWORD = FILE_CONFIG.getConfig(getRaftPasswordKey()); + TOKEN_EXPIRE_TIME_IN_MILLISECONDS = FILE_CONFIG.getLong(getTokenExpireTimeInMillisecondsKey(), 29 * 60 * 1000L); + CONFIG_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); + CONFIG_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); + } + + public static RaftConfigurationClient getInstance() { + if (instance == null) { + synchronized (RaftConfigurationClient.class) { + if (instance == null) { + instance = new RaftConfigurationClient(); + } + } + } + return instance; + } + + private RaftConfigurationClient() { + initClusterMetaData(); + initClientConfig(); + } + + private static void initClientConfig() { + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); + if (configMap != null) { + seataConfig.putAll(configMap); + } + CONFIG_LISTENER = new ConfigStoreListener(CONFIG_NAMESPACE, null); + startQueryConfigData(); + } catch (RetryableException e) { + LOGGER.error("init config properties error:{}", e.getMessage(), e); + } + + } + private static String queryHttpAddress(String clusterName, String group) { + List nodeList = METADATA.getNodes(clusterName, group); + List addressList = null; + Stream stream = null; + if (CollectionUtils.isNotEmpty(nodeList)) { + addressList = + nodeList.stream().map(node -> node.getControl().createAddress()).collect(Collectors.toList()); + } else { + stream = INIT_ADDRESSES.get(clusterName).stream(); + } + if (addressList != null) { + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } else { + Map map = new HashMap<>(); + if (CollectionUtils.isNotEmpty(nodeList)) { + for (Node node : nodeList) { + map.put(new InetSocketAddress(node.getTransaction().getHost(), node.getTransaction().getPort()).getAddress().getHostAddress() + + IP_PORT_SPLIT_CHAR + node.getTransaction().getPort(), node); + } + } + addressList = stream.map(inetSocketAddress -> { + String host = inetSocketAddress.getAddress().getHostAddress(); + Node node = map.get(host + IP_PORT_SPLIT_CHAR + inetSocketAddress.getPort()); + return host + IP_PORT_SPLIT_CHAR + + (node != null ? node.getControl().getPort() : inetSocketAddress.getPort()); + }).collect(Collectors.toList()); + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } + } + private static void acquireClusterMetaData(String clusterName, String group) throws RetryableException { + String tcAddress = queryHttpAddress(clusterName, group); + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + if (StringUtils.isNotBlank(tcAddress)) { + Map param = new HashMap<>(); + // param.put("group", group); + String response = null; + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doGet(HTTP_PREFIX + tcAddress + "/metadata/v1/config/cluster", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } + MetadataResponse metadataResponse; + if (StringUtils.isNotBlank(response)) { + try { + metadataResponse = OBJECT_MAPPER.readValue(response, MetadataResponse.class); + METADATA.refreshMetadata(clusterName, metadataResponse); + } catch (JsonProcessingException e) { + LOGGER.error("acquireClusterMetaData:{}", e.getMessage(), e); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + } + + @SuppressWarnings("unchecked") + private static Map acquireClusterConfigData(String clusterName, String group, String configNamespace, String configDataId) throws RetryableException { + String tcAddress = queryHttpAddress(clusterName, group); + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + if (StringUtils.isNotBlank(tcAddress)) { + Map param = new HashMap<>(); + param.put("namespace", configNamespace); + param.put("dataId", configDataId); + String response = null; + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doGet(HTTP_PREFIX + tcAddress + "/metadata/v1/config/getAll", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } + + ConfigDataResponse configDataResponse; + if (StringUtils.isNotBlank(response)) { + try { + configDataResponse = OBJECT_MAPPER.readValue(response, new TypeReference>() { + }); + if (configDataResponse.getSuccess()) { + ConfigurationInfoDto configurationInfoDto = configDataResponse.getResult(); + Map configItemMap = configurationInfoDto.getConfig(); + Map configMap = configItemMap.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, entry -> entry.getValue().getValue())); + Long version = configurationInfoDto.getVersion() == null ? -1 : configurationInfoDto.getVersion(); + Long currentVersion = CONFIG_VERSION.get(); + if (version < currentVersion) { + LOGGER.info("The configuration version: {} of the server is lower than the current configuration: {} , it may be expired configuration.", version, CONFIG_VERSION.get()); + throw new RetryableException("Expired configuration!"); + } else { + CONFIG_VERSION.set(version); + return configMap; + } + } else { + throw new RetryableException(configDataResponse.getErrMsg()); + } + } catch (JsonProcessingException e) { + LOGGER.error("acquireClusterConfigData:{}", e.getMessage(), e); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + return null; + } + + protected static void startQueryMetadata() { + if (REFRESH_METADATA_EXECUTOR == null) { + synchronized (INIT_ADDRESSES) { + if (REFRESH_METADATA_EXECUTOR == null) { + REFRESH_METADATA_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshMetadata", 1, true)); + REFRESH_METADATA_EXECUTOR.execute(() -> { + long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); + long currentTime = System.currentTimeMillis(); + while (!CLOSED.get()) { + try { + // Forced refresh of metadata information after set age + boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; + String clusterName = RAFT_CLUSTER; + if (!fetch) { + fetch = watch(); + } + // Cluster changes or reaches timeout refresh time + if (fetch) { + for (String group : METADATA.groups(clusterName)) { + try { + acquireClusterMetaData(clusterName, group); + } catch (Exception e) { + // prevents an exception from being thrown that causes the thread to break + if (e instanceof RetryableException) { + throw e; + } else { + LOGGER.error("failed to get the leader address,error: {}", e.getMessage(), e); + } + } + } + currentTime = System.currentTimeMillis(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refresh seata cluster metadata time: {}", currentTime); + } + } + } catch (RetryableException e) { + LOGGER.error("startQueryMetadata:{}", e.getMessage(), e); + try { + TimeUnit.MILLISECONDS.sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (CLOSED.compareAndSet(false, true)) { + REFRESH_METADATA_EXECUTOR.shutdown(); + } + })); + } + } + } + } + + protected static void startQueryConfigData() { + if (REFRESH_CONFIG_EXECUTOR == null) { + synchronized (RaftConfigurationClient.class) { + if (REFRESH_CONFIG_EXECUTOR == null) { + REFRESH_CONFIG_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshConfig", 1, true)); + REFRESH_CONFIG_EXECUTOR.execute(() -> { + long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); + long currentTime = System.currentTimeMillis(); + while (!CONFIG_CLOSED.get()) { + try { + // Forced refresh of metadata information after set age + boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; + if (!fetch) { + fetch = configWatch(); + } + // Cluster config changes or reaches timeout refresh time + if (fetch) { + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); + if (CollectionUtils.isNotEmpty(configMap)) { + notifyConfigMayChange(configMap); + } + } catch (Exception e) { + // prevents an exception from being thrown that causes the thread to break + if (e instanceof RetryableException) { + throw e; + } else { + LOGGER.error("failed to get the config ,error: {}", e.getMessage(), e); + } + } + + currentTime = System.currentTimeMillis(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refresh seata cluster config time: {}", currentTime); + } + } + } catch (RetryableException e) { + LOGGER.error("startQueryConfigData:{}", e.getMessage(), e); + try { + TimeUnit.MILLISECONDS.sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + if (CONFIG_CLOSED.compareAndSet(false, true)) { + REFRESH_CONFIG_EXECUTOR.shutdown(); + } + })); + } + } + } + } + private static boolean watch() throws RetryableException { + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + Map param = new HashMap<>(); + String clusterName = RAFT_CLUSTER; + Map groupTerms = METADATA.getClusterTerm(clusterName); + groupTerms.forEach((k, v) -> param.put(k, String.valueOf(v))); + for (String group : groupTerms.keySet()) { + String tcAddress = queryHttpAddress(clusterName, group); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + try (CloseableHttpResponse response = + HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/metadata/v1/watch", param, header, 30000)) { + if (response != null) { + StatusLine statusLine = response.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + } + } catch (IOException e) { + LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage(), e); + throw new RetryableException(e.getMessage(), e); + } + break; + } + return false; + } + + private static boolean configWatch() throws RetryableException { + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + String tcAddress = queryHttpAddress(RAFT_CLUSTER, RAFT_GROUP); + Map param = new HashMap<>(); + param.put("namespace", CONFIG_NAMESPACE); + param.put("dataId", CONFIG_DATA_ID); + param.put("version", CONFIG_VERSION.toString()); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + try (CloseableHttpResponse response = + HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/metadata/v1/config/watch", param, header, 30000)) { + if (response != null) { + StatusLine statusLine = response.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + } + } catch (IOException e) { + LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage(), e); + throw new RetryableException(e.getMessage(), e); + } + return false; + } + private static void initClusterMetaData() { + String clusterName = RAFT_CLUSTER; + String group = RAFT_GROUP; + if (!METADATA.containsGroup(clusterName)) { + String raftClusterAddress = FILE_CONFIG.getConfig(getRaftServerAddrKey()); + if (StringUtils.isNotBlank(raftClusterAddress)) { + List list = new ArrayList<>(); + String[] addresses = raftClusterAddress.split(","); + for (String address : addresses) { + String[] endpoint = address.split(IP_PORT_SPLIT_CHAR); + String host = endpoint[0]; + int port = Integer.parseInt(endpoint[1]); + list.add(new InetSocketAddress(host, port)); + } + if (CollectionUtils.isEmpty(list)) { + throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, + "There are no valid raft addr! you should configure the correct [config.raft.server-addr] in the config file"); + } + INIT_ADDRESSES.put(clusterName, list); + // init jwt token + try { + refreshToken(queryHttpAddress(clusterName, group)); + } catch (Exception e) { + throw new RuntimeException("Init fetch token failed!", e); + } + // Refresh the metadata by initializing the address + try { + acquireClusterMetaData(clusterName, group); + } catch (RetryableException e) { + LOGGER.error("init cluster metadata fail: {}", e.getMessage(), e); + } + startQueryMetadata(); + } + } + } + + + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support operation putConfig"); + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = seataConfig.getProperty(dataId); + if (value == null) { + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); + if (CollectionUtils.isNotEmpty(configMap)) { + value = configMap.get(dataId) == null ? null : configMap.get(dataId).toString(); + } + } catch (RetryableException e) { + LOGGER.error(e.getMessage()); + } + } + return value == null ? defaultValue : value; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + throw new NotSupportYetException("not support operation removeConfig"); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + ConfigStoreListener storeListener = new ConfigStoreListener(dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, storeListener); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + configListeners.remove(entry); + } + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + return configListeners.keySet(); + } else { + return null; + } + } + + private static void notifyConfigMayChange(Map configMap) { + String configStr = ConfigStoreManager.convertConfig2Str(configMap); + CONFIG_LISTENER.onChangeEvent(new ConfigurationChangeEvent(CONFIG_NAMESPACE, configStr)); + } + + + private static String getRaftUsernameKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, USERNAME_KEY); + } + private static String getRaftPasswordKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PASSWORD_KEY); + } + private static String getRaftServerAddrKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, SERVER_ADDR_KEY); + } + + private static String getTokenExpireTimeInMillisecondsKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, TOKEN_VALID_TIME_MS_KEY); + } + + private static boolean isTokenExpired() { + if (tokenTimeStamp == -1) { + return true; + } + long tokenExpiredTime = tokenTimeStamp + TOKEN_EXPIRE_TIME_IN_MILLISECONDS; + return System.currentTimeMillis() >= tokenExpiredTime; + } + + private static synchronized void refreshToken(String tcAddress) throws RetryableException { + // double-check if the token is expired inside the synchronized method to avoid repeated token refreshes in multiple threads + if (!isTokenExpired()) { + return; + } + // if username and password is not in config , return + if (StringUtils.isBlank(USERNAME) || StringUtils.isBlank(PASSWORD)) { + return; + } + // get token and set it in cache + Map param = new HashMap<>(); + param.put(USERNAME_KEY, USERNAME); + param.put(PASSWORD_KEY, PASSWORD); + + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); + String response = null; + tokenTimeStamp = System.currentTimeMillis(); + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/api/v1/auth/login", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + JsonNode jsonNode = OBJECT_MAPPER.readTree(response); + String codeStatus = jsonNode.get("code").asText(); + if (!StringUtils.equals(codeStatus, "200")) { + //authorized failed,throw exception to kill process + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + jwtToken = jsonNode.get("data").asText(); + } else { + //authorized failed,throw exception to kill process + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + + private static class ConfigStoreListener implements ConfigurationChangeListener { + private final String dataId; + private final ConfigurationChangeListener listener; + + public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + } + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (CONFIG_NAMESPACE.equals(event.getDataId())) { + Properties seataConfigNew = new Properties(); + Map newConfigMap = ConfigStoreManager.convertConfigStr2Map(event.getNewValue()); + if (CollectionUtils.isNotEmpty(newConfigMap)) { + seataConfigNew.putAll(newConfigMap); + } + //Get all the monitored dataids and judge whether it has been modified + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setNamespace(CONFIG_NAMESPACE) + .setChangeType(ConfigurationChangeType.MODIFY); + + // notify ConfigurationCache + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(newEvent); + } + } + } + seataConfig = seataConfigNew; + } + } + } + +} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java new file mode 100644 index 00000000000..c2470aa1bdd --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java @@ -0,0 +1,38 @@ +/* + * 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 org.apache.seata.config.raft; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationProvider; + +import static org.apache.seata.common.Constants.APPLICATION_TYPE_KEY; +import static org.apache.seata.common.Constants.APPLICATION_TYPE_SERVER; + +@LoadLevel(name = "Raft", order = 1) +public class RaftConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + // todo : optimize + String applicationType = System.getProperty(APPLICATION_TYPE_KEY); + if (APPLICATION_TYPE_SERVER.equals(applicationType)) { + return RaftConfigurationServer.getInstance(); + } else { + return RaftConfigurationClient.getInstance(); + } + } +} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java new file mode 100644 index 00000000000..843e2b9f093 --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -0,0 +1,206 @@ +/* + * 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 org.apache.seata.config.raft; + +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.AbstractConfiguration; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationChangeEvent; +import org.apache.seata.config.ConfigurationChangeListener; +import org.apache.seata.config.ConfigurationChangeType; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; + + +public class RaftConfigurationServer extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationServer.class); + private static volatile RaftConfigurationServer instance; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static ConfigStoreManager configStoreManager; + private static String CURRENT_NAMESPACE; + private static String CURRENT_DATA_ID; + private static final String CONFIG_TYPE = "raft"; + private static volatile Properties seataConfig = new Properties(); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + + private static void initServerConfig() { + configStoreManager = ConfigStoreManagerFactory.getInstance(); + CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); + CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); + // load config from store + Map configMap = configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID); + seataConfig.putAll(configMap); + // build listener + ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, null); + configStoreManager.addConfigListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, storeListener); + } + + + public static RaftConfigurationServer getInstance() { + if (instance == null) { + synchronized (RaftConfigurationServer.class) { + if (instance == null) { + instance = new RaftConfigurationServer(); + } + } + } + return instance; + } + + private RaftConfigurationServer() { + initServerConfig(); + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support operation putConfig"); + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = seataConfig.getProperty(dataId); + if (value == null) { + value = configStoreManager.get(CURRENT_NAMESPACE, CURRENT_DATA_ID, dataId); + } + return value == null ? defaultValue : value; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + throw new NotSupportYetException("not support operation removeConfig"); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, storeListener); + configStoreManager.addConfigListener(CURRENT_NAMESPACE, dataId, storeListener); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + ConfigStoreListener storeListener = null; + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + storeListener = configListeners.get(listener); + configListeners.remove(entry); + } + if (storeListener != null) { + configStoreManager.removeConfigListener(CURRENT_NAMESPACE, dataId, storeListener); + } + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)) { + return configListeners.keySet(); + } else { + return null; + } + } + + + /** + * the type config change listener for raft config store + */ + private static class ConfigStoreListener implements ConfigurationChangeListener { + private final String namespace; + private final String dataId; + private final ConfigurationChangeListener listener; + + public ConfigStoreListener(String namespace, String dataId, ConfigurationChangeListener listener) { + this.namespace = namespace; + this.dataId = dataId; + this.listener = listener; + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (CURRENT_DATA_ID.equals(event.getDataId())) { + Properties seataConfigNew = new Properties(); + seataConfigNew.putAll(configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID)); + + //Get all the monitored dataids and judge whether it has been modified + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setNamespace(CURRENT_NAMESPACE) + .setChangeType(ConfigurationChangeType.MODIFY); + + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(newEvent); + } + } + } + seataConfig = seataConfigNew; + return; + } + // Compatible with old writing + listener.onProcessEvent(event); + } + } +} diff --git a/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider new file mode 100644 index 00000000000..589333e6a30 --- /dev/null +++ b/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider new file mode 100644 index 00000000000..589333e6a30 --- /dev/null +++ b/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-raft/src/test/resources/registry.conf b/config/seata-config-raft/src/test/resources/registry.conf new file mode 100644 index 00000000000..22313ebf407 --- /dev/null +++ b/config/seata-config-raft/src/test/resources/registry.conf @@ -0,0 +1,101 @@ +# +# 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. +# + +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3、raft + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file.conf" + } + db { + type = "rocksdb" + dir = "configStore" + destroyOnShutdown = false + group = "SEATA_GROUP" + } + raft { + serverAddr = "127.0.0.1:7091" + username = "seata" + password = "seata" + } +} diff --git a/console/src/main/resources/static/console-fe/package.json b/console/src/main/resources/static/console-fe/package.json index 7acb85de0bd..568d556759a 100644 --- a/console/src/main/resources/static/console-fe/package.json +++ b/console/src/main/resources/static/console-fe/package.json @@ -78,6 +78,7 @@ "@alicloud/console-components-actions": "^1.1.1", "@alicloud/console-components-app-layout": "^1.1.4", "@alicloud/console-components-console-menu": "^1.2.12", + "@alifd/next": "^1.24.18", "@babel/traverse": "^7.23.7", "axios": "^1.7.4", "browserify-sign": "^4.2.2", diff --git a/console/src/main/resources/static/console-fe/src/app.tsx b/console/src/main/resources/static/console-fe/src/app.tsx index d3bac4a24d0..b003efcbd52 100644 --- a/console/src/main/resources/static/console-fe/src/app.tsx +++ b/console/src/main/resources/static/console-fe/src/app.tsx @@ -78,7 +78,8 @@ class App extends React.Component { get menu() { const { locale }: AppPropsType = this.props; const { MenuRouter = {} } = locale; - const { overview, transactionInfo, globalLockInfo, sagaStatemachineDesigner } = MenuRouter; + const { overview, transactionInfo, globalLockInfo, configInfo, sagaStatemachineDesigner } = MenuRouter; + return { items: [ // { @@ -93,6 +94,10 @@ class App extends React.Component { key: '/globallock/list', label: globalLockInfo, }, + { + key: '/config/list', + label: configInfo, + }, { key: '/sagastatemachinedesigner', label: sagaStatemachineDesigner, diff --git a/console/src/main/resources/static/console-fe/src/locales/en-us.ts b/console/src/main/resources/static/console-fe/src/locales/en-us.ts index 58e3a8fba06..ce64d4ab78b 100644 --- a/console/src/main/resources/static/console-fe/src/locales/en-us.ts +++ b/console/src/main/resources/static/console-fe/src/locales/en-us.ts @@ -22,6 +22,7 @@ const enUs: ILocale = { overview: 'Overview', transactionInfo: 'TransactionInfo', globalLockInfo: 'GlobalLockInfo', + configInfo: 'ConfigurationInfo', sagaStatemachineDesigner: 'SagaStatemachineDesigner', }, Header: { @@ -70,6 +71,30 @@ const enUs: ILocale = { resetButtonLabel: 'Reset', searchButtonLabel: 'Search', }, + ConfigInfo: { + title: 'ConfigurationInfo', + subTitle: 'list', + resetButtonLabel: 'Reset', + searchButtonLabel: 'Search', + createButtonLabel: 'Create', + editButtonLabel: 'Edit', + deleteButtonLabel: 'Delete', + clearButtonLabel: 'Clear', + uploadButtonLabel: 'Upload', + operateTitle: 'Actions', + disableTitle: 'This page is only available if the Configuration Center type is raft mode', + editTitle: 'Edit Config', + deleteTitle: 'Delete Config', + deleteConfirmLabel: 'Are you sure you want to delete the configuration item with key: ', + deleteAllConfirmLabel: 'Are you sure you want to clear all configuration items in the following namespace and dataId: ', + addTitle: 'Add Config', + operationSuccess: 'Operation Success!', + operationFailed: 'Operation Failed', + inputFilterPlaceholder: 'Please select filter criteria', + fieldFillingTips: 'Please fill in the required fields', + uploadTitle: 'Upload Config', + uploadFileButtonLabel: 'Upload File', + }, }; export default enUs; diff --git a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts index e2ed430d514..5b367720152 100644 --- a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts +++ b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts @@ -22,6 +22,7 @@ const zhCn: ILocale = { overview: '概览', transactionInfo: '事务信息', globalLockInfo: '全局锁信息', + configInfo:'配置信息', sagaStatemachineDesigner: 'Saga状态机设计器', }, Header: { @@ -70,6 +71,30 @@ const zhCn: ILocale = { resetButtonLabel: '重置', searchButtonLabel: '搜索', }, + ConfigInfo: { + title: '配置信息', + subTitle: '配置列表页', + resetButtonLabel: '重置', + searchButtonLabel: '搜索', + createButtonLabel: '创建', + editButtonLabel: '编辑', + deleteButtonLabel: '删除', + clearButtonLabel: '清空', + uploadButtonLabel: '上传', + operateTitle: '操作', + disableTitle: '该页面仅在配置中心类型为raft模式下可用', + editTitle: '编辑配置', + deleteTitle: '删除配置', + deleteConfirmLabel: '确认需要删除以下配置项: ', + deleteAllConfirmLabel: '确认需要清空以下namespace和dataId中的所有配置项: ', + addTitle: '新增配置', + operationSuccess: '操作成功!', + operationFailed: '操作失败', + inputFilterPlaceholder: '请选择筛选条件', + fieldFillingTips: '请将必要字段填充完整', + uploadTitle: '上传配置', + uploadFileButtonLabel: '上传文件', + }, }; export default zhCn; diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx new file mode 100644 index 00000000000..47567171a5c --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx @@ -0,0 +1,644 @@ +/* + * 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. + */ +import React from 'react'; +import { + ConfigProvider, + Table, + Button, + DatePicker, + Form, + Icon, + Pagination, + Input, + Dialog, + Message, + Select, + Upload, +} from '@alicloud/console-components'; +import { withRouter } from 'react-router-dom'; + +import {getConfig, getClusterInfo, putConfig, deleteConfig, deleteAllConfig, getAllNamespaces, getAllDataIds, uploadConfig} from "@/service/configInfo"; +import Page from '@/components/Page'; +import { GlobalProps } from '@/module'; +import styled, { css } from 'styled-components'; +import PropTypes from 'prop-types'; +import './index.scss'; + +import moment from "moment/moment"; +type ConfigInfoState = { + configList: Array; + editDialogVisible: boolean; + deleteDialogVisible: boolean; + uploadDialogVisible: boolean; + loading: boolean; + configParam: ConfigParam; + editDialogInfo: DialogInfo; + deleteDialogInfo: DeleteDialogInfo; + uploadDialogInfo: UploadDialogInfo; + isRaft: boolean; + namespaces: Array; + dataIds: Array; +} +export type ConfigParam = { + namespace: string, + dataId: string, +}; + +type DialogInfo = { + isEdit: boolean; + namespace: string; + dataId: string; + key: string; + value: string; +} + +type DeleteDialogInfo = { + namespace: string; + dataId: string; +} + +type UploadDialogInfo = { + namespace: string; + dataId: string; + file: File; +} + +const FormItem = Form.Item; + + + +class ConfigInfo extends React.Component { + static displayName = 'ConfigInfo'; + + static propTypes = { + locale: PropTypes.object, + history: PropTypes.object, + }; + + state: ConfigInfoState = { + configList: [], + loading: false, + editDialogVisible: false, + deleteDialogVisible: false, + uploadDialogVisible: false, + namespaces: [], + dataIds: [], + configParam: { + namespace: '', + dataId: '', + }, + editDialogInfo: { + isEdit: false, + namespace: '', + dataId: '', + key: '', + value: '', + }, + deleteDialogInfo: { + namespace: '', + dataId: '', + }, + uploadDialogInfo: { + namespace: '', + dataId: '', + file: null, + }, + isRaft: false, + } + + componentDidMount() { + this.init(); + this.pollingInterval = setInterval(this.refreshConfigData, 10000); // 每10秒刷新一次 + } + componentWillUnmount() { + clearInterval(this.pollingInterval); + } + + init = async () => { + const { disableTitle } = this.props.locale; + this.setState({ loading: true }); + try { + const response = await getClusterInfo(); + const raftMode = response.configMode + if (raftMode === 'raft') { + this.setState({ isRaft: true, loading: false }); + this.fetchNamespaces(); + } else { + this.setState({ loading: false }); + Message.error(disableTitle); + setTimeout(() => this.props.history.goBack(), 1000); + } + //this.setState({ clusterInfo: result, loading: false }); + } catch (error) { + Message.error('Failed to fetch cluster info'); + this.setState({ loading: false }); + this.props.history.goBack(); + } + } + + refreshConfigData = () => { + this.fetchNamespaces(); + if (this.state.configParam.namespace) { + this.fetchDataIds(this.state.configParam.namespace); + } + } + fetchNamespaces = async () => { + try { + const response = await getAllNamespaces(); + const result = response.result; + this.setState({ namespaces: result }); + } catch (error) { + Message.error('Failed to fetch namespace list'); + } + } + + fetchDataIds = async (namespace: string) => { + try { + const response = await getAllDataIds({ namespace }); + const result = response.result; + this.setState({ dataIds: result }); + } catch (error) { + Message.error('Failed to fetch dataIds'); + } + } + + fetchConfigList = async () => { + this.setState({ loading: true }); + try { + const response = await getConfig({namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}); + if (response.success && response.result){ + const { config } = response.result; + console.log(config); + const configList = Object.keys(config).map((key) => ({ ...config[key] })); + this.setState({ configList, loading: false }); + } else { + Message.error(response.errMsg || 'Failed to fetch config list'); + this.setState({ loading: false }); + } + } catch (error) { + Message.error('Failed to fetch config list'); + this.setState({ loading: false }); + } + } + searchFilterOnChange = async (key:string, val:string) => { + this.setState({ + configParam: Object.assign(this.state.configParam, + { [key]: val }), + }); + if (key === 'namespace') { + this.setState({ + configParam: Object.assign(this.state.configParam, + { dataId: '' }), + }); + await this.fetchDataIds(val); + } + } + search = () => { + this.fetchConfigList(); + } + resetSearchFilter = () => { + this.setState({ + configParam: { + // pagination info don`t reset + namespace: '', + dataId: '', + }, + }); + } + + resetDialog = () => { + this.setState({ + editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}, + deleteDialogInfo: {namespace: '', dataId: ''}, + uploadDialogInfo: {namespace: '', dataId: '', file: null}, + }); + }; + openEditDialog = (config: { key: string; value: string }) => { + this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, ...config}}); + }; + + openDeleteDialog = () => { + this.setState({ deleteDialogVisible: true, deleteDialogInfo: {namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}}); + } + + openUploadDialog = () => { + this.setState({ uploadDialogVisible: true, uploadDialogInfo: {namespace: this.state.configParam.namespace, dataId: '', file: null}}); + } + + createConfig = () => { + this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: false, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}}); + }; + + closeDialog = () => { + this.setState({ editDialogVisible: false, deleteDialogVisible: false, uploadDialogVisible: false}); + this.resetDialog(); + }; + + handleAddOrEditConfig = async () => { + const { operationSuccess, operationFail } = this.props.locale; + const { editDialogInfo } = this.state; + try { + const response =await putConfig({ + namespace: editDialogInfo.namespace, + dataId: editDialogInfo.dataId, + key: editDialogInfo.key, + value: editDialogInfo.value, + }); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + editDialogVisible: false, + configParam: { + namespace: editDialogInfo.namespace, + dataId: editDialogInfo.dataId, + } + }); + this.fetchNamespaces(); + this.fetchDataIds(editDialogInfo.namespace); + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleDeleteConfig = async (record: { key: string }) => { + const { deleteTitle, deleteConfirmLabel, operationSuccess, operationFail } = this.props.locale; + Dialog.confirm({ + title: deleteTitle, + content: deleteConfirmLabel + `${record.key} ?`, + onOk: async () => { + try { + const response = await deleteConfig({ namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: record.key }); + if (response.success) { + Message.success(operationSuccess); + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + }, + onCancel: () => { + + }, + }); + } + + handleDeleteAllConfig = async () => { + const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; + const { namespace, dataId } = this.state.deleteDialogInfo; + + if (!namespace || !dataId) { + Message.error(fieldFillingTips); + return; + } + try { + const response = await deleteAllConfig({ namespace, dataId }); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + configParam: { + namespace: namespace, + dataId: dataId, + }, + deleteDialogVisible: false, + deleteDialogInfo: { namespace: '', dataId: '' }, + }); + this.fetchDataIds(namespace) + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleUploadConfig = async () => { + const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; + const { namespace, dataId,file } = this.state.uploadDialogInfo; + if (!namespace || !dataId || !file) { + Message.error(fieldFillingTips); + return; + } + const formData = new FormData(); + formData.append('namespace', namespace); + formData.append('dataId', dataId); + formData.append('file', file); + + try { + const response = await uploadConfig(formData); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + uploadDialogVisible: false, + uploadDialogInfo: { namespace: '', dataId: '', file: null}, + configParam: { + namespace: namespace, + dataId: dataId, + } + }); + this.fetchNamespaces(); + this.fetchDataIds(namespace) + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + editDialogInfo: { + ...prevState.editDialogInfo, + [key]: value, + }, + })); + }; + + handleDeleteDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + deleteDialogInfo: { + ...prevState.deleteDialogInfo, + [key]: value + } + })) + } + + handleUploadDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + uploadDialogInfo: { + ...prevState.uploadDialogInfo, + [key]: value + } + })) + } + + handleFileInputChange = (fileList: Array) => { + const file = fileList.length > 0 ? fileList[0] : null; + if (file && file.originFileObj) { + this.handleUploadDialogInputChange('file', file.originFileObj); + } + } + render() { + const { locale = {} } = this.props; + const { title, subTitle, + searchButtonLabel, + resetButtonLabel, + createButtonLabel, + clearButtonLabel, + uploadButtonLabel, + operateTitle, + editTitle, + deleteTitle, + uploadTitle, + deleteAllConfirmLabel, + editButtonLabel, + deleteButtonLabel, + inputFilterPlaceholder, + uploadFileButtonLabel, + } = locale; + + return ( + + {/* search form */} +
+ {/* {search filters} */} + + { this.searchFilterOnChange('dataId', value); }} + dataSource={this.state.dataIds} + style={{ width: 200 }} + hasClear={true} + /> + + + {/* {reset search filter button} */} + + + {resetButtonLabel} + + + {/* {search button} */} + + + {searchButtonLabel} + + + + + {createButtonLabel} + + + + + {uploadButtonLabel} + + + + + {clearButtonLabel} + + +
+ + {/* config info table */} +
+ + + + + + ( + <> + + + + )} + /> +
+
+ {/* config edit dialog */} + +
+ + this.handleDialogInputChange('namespace', value)} + /> + + + this.handleDialogInputChange('dataId', value)} + /> + + + this.handleDialogInputChange('key', value)} + /> + + + this.handleDialogInputChange('value', value)} + /> + +
+
+ + {/* config delete dialog*/} + +
+ + + { + this.handleDeleteDialogInputChange('namespace', value)} + } + /> + + + { + this.handleDeleteDialogInputChange('dataId', value)} + } + /> + +
+
+ + {/* config upload dialog*/} + +
+ + + this.handleUploadDialogInputChange('namespace', value)} + /> + + + this.handleUploadDialogInputChange('dataId', value)} + /> + + + false} // Prevent auto-upload + accept={".txt,.text,.yaml,.yml,.properties"} + limit={1} + > + + + +
+
+
+ ); + } +} +export default withRouter(ConfigProvider.config(ConfigInfo, {})); diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss new file mode 100644 index 00000000000..2944f981947 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss @@ -0,0 +1,16 @@ +/* + * 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. + */ diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts new file mode 100644 index 00000000000..d30c8ca5a93 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts @@ -0,0 +1,21 @@ +/* + * 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. + */ +import ConfigInfo from './ConfigInfo'; + +export * from './ConfigInfo'; + +export default ConfigInfo; diff --git a/console/src/main/resources/static/console-fe/src/router.tsx b/console/src/main/resources/static/console-fe/src/router.tsx index d881b472d02..88c7be6b920 100644 --- a/console/src/main/resources/static/console-fe/src/router.tsx +++ b/console/src/main/resources/static/console-fe/src/router.tsx @@ -18,10 +18,12 @@ import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; import Overview from '@/pages/Overview'; import TransactionInfo from '@/pages/TransactionInfo'; import GlobalLockInfo from './pages/GlobalLockInfo'; +import ConfigInfo from "./pages/ConfigInfo"; export default [ // { path: '/', exact: true, render: () => }, // { path: '/Overview', component: Overview }, { path: '/transaction/list', component: TransactionInfo }, { path: '/globallock/list', component: GlobalLockInfo }, + { path: '/config/list', component: ConfigInfo }, ]; diff --git a/console/src/main/resources/static/console-fe/src/service/configInfo.ts b/console/src/main/resources/static/console-fe/src/service/configInfo.ts new file mode 100644 index 00000000000..37e4658d689 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/service/configInfo.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ +import {configRequest} from '@/utils/request'; + + +export async function getConfig(params: { namespace: string, dataId: string}): Promise { + const result = await configRequest('/config/getAll', { + method: 'get', + params, + }); + return result; +} + +export async function putConfig(params: { namespace: string, dataId: string, key: string, value: string}): Promise { + const result = await configRequest('/config/put', { + method: 'post', + params, + }); + return result; +} + +export async function deleteConfig(params: { namespace: string, dataId: string, key: string }): Promise { + const result = await configRequest('/config/delete', { + method: 'delete', + params, + }); + return result; +} + +export async function deleteAllConfig(params: { namespace: string, dataId: string}): Promise { + const result = await configRequest('/config/deleteAll', { + method: 'delete', + params, + }); + return result; +} + +export async function uploadConfig(formData: FormData): Promise { + const result = await configRequest('/config/upload', { + method: 'post', + data: formData, + }); + return result; +} + +export async function getClusterInfo(): Promise { + const result = await configRequest('/config/cluster', { + method: 'get', + }); + return result; +} + +export async function getAllNamespaces(): Promise { + const result = await configRequest('/config/getNamespaces', { + method: 'get', + }); + return result; +} + +export async function getAllDataIds(params: { namespace: string}): Promise { + const result = await configRequest('/config/getDataIds', { + method: 'get', + params, + }); + return result; +} diff --git a/console/src/main/resources/static/console-fe/src/utils/request.ts b/console/src/main/resources/static/console-fe/src/utils/request.ts index 47ab7597615..e28c6a2e5c6 100644 --- a/console/src/main/resources/static/console-fe/src/utils/request.ts +++ b/console/src/main/resources/static/console-fe/src/utils/request.ts @@ -90,3 +90,50 @@ const request = () => { }; export default request(); + + +const clusterRequest = () => { + const instance: AxiosInstance = axios.create({ + baseURL: 'metadata/v1', + method: 'get', + }); + + instance.interceptors.request.use((config: AxiosRequestConfig) => { + let authHeader: string | null = localStorage.getItem(AUTHORIZATION_HEADER); + // add jwt header + config.headers[AUTHORIZATION_HEADER] = authHeader; + return config; + }) + + instance.interceptors.response.use( + (response: AxiosResponse): Promise => { + const isSuccess = get(response, 'data.success'); + if (response.status === 200 || isSuccess) { + return Promise.resolve(get(response, 'data')); + } else { + const errorText = + get(response, 'data.errMsg') || + response.statusText; + Message.error(errorText); + return Promise.reject(response); + } + }, + error => { + if (error.response) { + const { status } = error.response; + if (status === 403 || status === 401) { + (window as any).globalHistory.replace('/login'); + return; + } + Message.error(`HTTP ERROR: ${status}`); + } else { + Message.error(API_GENERAL_ERROR_MESSAGE); + } + return Promise.reject(error); + } + ); + + return instance; +}; + +export const configRequest = clusterRequest(); diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 15178f14166..4f54761c851 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -76,6 +76,8 @@ 4.1.101.Final 4.0.3 1.6.7 + 8.8.1 + 3.25.4 1.66.0 5.4.0 @@ -234,6 +236,11 @@ bolt ${sofa.bolt.version} + + org.rocksdb + rocksdbjni + ${rocksdbjni.version} + com.alibaba fastjson diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java index 67bc7bd75ab..c9a451f7228 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java @@ -17,6 +17,7 @@ package org.apache.seata.spring.boot.autoconfigure; import java.util.concurrent.atomic.AtomicBoolean; + import org.apache.seata.spring.boot.autoconfigure.properties.LogProperties; import org.apache.seata.spring.boot.autoconfigure.properties.ShutdownProperties; import org.apache.seata.spring.boot.autoconfigure.properties.ThreadFactoryProperties; @@ -28,6 +29,8 @@ import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigFileProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigNacosProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigProperties; +import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigRaftProperties; +import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigStoreProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigZooKeeperProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryConsulProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryCustomProperties; @@ -45,6 +48,7 @@ import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; + import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_APOLLO_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CONSUL_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CUSTOM_PREFIX; @@ -52,6 +56,8 @@ import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_FILE_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_NACOS_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_PREFIX; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_RAFT_PREFIX; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_STORE_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_ZK_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.LOG_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.PROPERTY_BEAN_MAP; @@ -70,7 +76,6 @@ import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.THREAD_FACTORY_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.TRANSPORT_PREFIX; - public class SeataCoreEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { private static final AtomicBoolean INIT = new AtomicBoolean(false); @@ -97,6 +102,8 @@ public static void init() { PROPERTY_BEAN_MAP.put(CONFIG_APOLLO_PREFIX, ConfigApolloProperties.class); PROPERTY_BEAN_MAP.put(CONFIG_ETCD3_PREFIX, ConfigEtcd3Properties.class); PROPERTY_BEAN_MAP.put(CONFIG_CUSTOM_PREFIX, ConfigCustomProperties.class); + PROPERTY_BEAN_MAP.put(CONFIG_STORE_PREFIX, ConfigStoreProperties.class); + PROPERTY_BEAN_MAP.put(CONFIG_RAFT_PREFIX, ConfigRaftProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_CONSUL_PREFIX, RegistryConsulProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_ETCD3_PREFIX, RegistryEtcd3Properties.class); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java index 5ec088d43ff..45d66c91a94 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java @@ -66,8 +66,8 @@ public interface StarterConstants { String CONFIG_ZK_PREFIX = CONFIG_PREFIX + ".zk"; String CONFIG_FILE_PREFIX = CONFIG_PREFIX + ".file"; String CONFIG_CUSTOM_PREFIX = CONFIG_PREFIX + ".custom"; - - + String CONFIG_RAFT_PREFIX = CONFIG_PREFIX + ".raft"; + String CONFIG_STORE_PREFIX = CONFIG_RAFT_PREFIX + ".db"; String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; String SERVER_RAFT_PREFIX = SERVER_PREFIX + ".raft"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java new file mode 100644 index 00000000000..af2fff40e9b --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java @@ -0,0 +1,82 @@ +/* + * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_RAFT_PREFIX; + +@Component +@ConfigurationProperties(prefix = CONFIG_RAFT_PREFIX) +public class ConfigRaftProperties { + private String serverAddr; + + private Long metadataMaxAgeMs = 30000L; + + private String username; + + private String password; + + private Long tokenValidityInMilliseconds = 29 * 60 * 1000L; + + public Long getMetadataMaxAgeMs() { + return metadataMaxAgeMs; + } + + public ConfigRaftProperties setMetadataMaxAgeMs(Long metadataMaxAgeMs) { + this.metadataMaxAgeMs = metadataMaxAgeMs; + return this; + } + + public String getUsername() { + return username; + } + + public ConfigRaftProperties setUsername(String username) { + this.username = username; + return this; + } + + public String getPassword() { + return password; + } + + public ConfigRaftProperties setPassword(String password) { + this.password = password; + return this; + } + + public Long getTokenValidityInMilliseconds() { + return tokenValidityInMilliseconds; + } + + public ConfigRaftProperties setTokenValidityInMilliseconds(Long tokenValidityInMilliseconds) { + this.tokenValidityInMilliseconds = tokenValidityInMilliseconds; + return this; + } + + public String getServerAddr() { + return serverAddr; + } + + public ConfigRaftProperties setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + return this; + } + +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java new file mode 100644 index 00000000000..3e361ba1f3f --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java @@ -0,0 +1,81 @@ +/* + * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_STORE_PREFIX; + + +@Component +@ConfigurationProperties(prefix = CONFIG_STORE_PREFIX) +public class ConfigStoreProperties { + /** + * rocksdb, (leveldb, caffeine) + */ + private String type = "rocksdb"; + private String dir = "configStore"; + private boolean destroyOnShutdown = false; + private String namespace = "default"; + private String dataId = "seata.properties"; + + public String getType() { + return type; + } + + public ConfigStoreProperties setType(String type) { + this.type = type; + return this; + } + + public String getDir() { + return dir; + } + + public ConfigStoreProperties setDir(String dir) { + this.dir = dir; + return this; + } + + public boolean isDestroyOnShutdown() { + return destroyOnShutdown; + } + + public ConfigStoreProperties setDestroyOnShutdown(boolean destroyOnShutdown) { + this.destroyOnShutdown = destroyOnShutdown; + return this; + } + + public String getNamespace() { + return namespace; + } + + public ConfigStoreProperties setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + public String getDataId() { + return dataId; + } + + public ConfigStoreProperties setDataId(String dataId) { + this.dataId = dataId; + return this; + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java new file mode 100644 index 00000000000..62afe4d6b37 --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java @@ -0,0 +1,53 @@ +/* + * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ExtConfigurationProvider; +import org.apache.seata.config.FileConfiguration; +import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; +import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + + +@org.springframework.context.annotation.Configuration +@Import(SpringApplicationContextProvider.class) +public class ConfigRaftPropertiesTest extends BasePropertiesTest { + @Bean("testConfigRaftProperties") + public ConfigRaftProperties configRaftProperties() { + return new ConfigRaftProperties().setUsername(STR_TEST_AAA).setPassword(STR_TEST_BBB).setServerAddr(STR_TEST_CCC).setMetadataMaxAgeMs((long)LONG_TEST_ONE).setTokenValidityInMilliseconds((long)LONG_TEST_TWO); + } + + @Test + public void testConfigRaftProperties() { + FileConfiguration configuration = mock(FileConfiguration.class); + Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + + assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.username")); + assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.password")); + assertEquals(STR_TEST_CCC, currentConfiguration.getConfig("config.raft.serverAddr")); + assertEquals(LONG_TEST_ONE, currentConfiguration.getInt("config.raft.metadataMaxAgeMs")); + assertEquals(LONG_TEST_TWO, currentConfiguration.getInt("config.raft.tokenValidityInMilliseconds")); + + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java new file mode 100644 index 00000000000..e1f16469d47 --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java @@ -0,0 +1,52 @@ +/* + * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ExtConfigurationProvider; +import org.apache.seata.config.FileConfiguration; +import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; +import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; + +@org.springframework.context.annotation.Configuration +@Import(SpringApplicationContextProvider.class) +public class ConfigStorePropertiesTest extends BasePropertiesTest { + @Bean("testConfigStoreProperties") + public ConfigStoreProperties configStoreProperties() { + return new ConfigStoreProperties().setType(STR_TEST_AAA).setDir(STR_TEST_BBB).setDestroyOnShutdown(false).setNamespace(STR_TEST_DDD).setDataId(STR_TEST_EEE); + } + + @Test + public void testConfigStoreProperties() { + FileConfiguration configuration = mock(FileConfiguration.class); + Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + + assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.db.type")); + assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.db.dir")); + assertFalse(currentConfiguration.getBoolean("config.raft.db.destroyOnShutdown")); + assertEquals(STR_TEST_DDD, currentConfiguration.getConfig("config.raft.db.namespace")); + assertEquals(STR_TEST_EEE, currentConfiguration.getConfig("config.raft.db.dataId")); + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties index 8467d6cc7b9..27d7e4143bd 100755 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties @@ -51,3 +51,15 @@ seata.config.zk.password=ddd seata.config.zk.node-path=aaa seata.config.custom.name=aaa + +seata.config.raft.db.type=aaa +seata.config.raft.db.dir=bbb +seata.config.raft.db.destroy-on-shutdown=false +seata.config.raft.db.namespace=ddd +seata.config.raft.db.data-id=eee + +seata.config.raft.username=aaa +seata.config.raft.password=bbb +seata.config.raft.server-addr=ccc +seata.config.raft.metadata-max-age-ms=1 +seata.config.raft.token-validity-in-milliseconds=2 diff --git a/server/src/main/java/org/apache/seata/server/Server.java b/server/src/main/java/org/apache/seata/server/Server.java index c699ef037b1..b10379a78b8 100644 --- a/server/src/main/java/org/apache/seata/server/Server.java +++ b/server/src/main/java/org/apache/seata/server/Server.java @@ -108,6 +108,8 @@ public void start(String[] args) { serverInstance.serverInstanceInit(); // let ServerRunner do destroy instead ShutdownHook, see https://github.com/seata/seata/issues/4028 + + ServerRunner.addToFirstDisposable(coordinator); ServerRunner.addDisposable(coordinator); nettyRemotingServer.init(); } diff --git a/server/src/main/java/org/apache/seata/server/ServerApplication.java b/server/src/main/java/org/apache/seata/server/ServerApplication.java index 952187137e3..9c5e1ccf86b 100644 --- a/server/src/main/java/org/apache/seata/server/ServerApplication.java +++ b/server/src/main/java/org/apache/seata/server/ServerApplication.java @@ -21,11 +21,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import static org.apache.seata.common.Constants.APPLICATION_TYPE_KEY; +import static org.apache.seata.common.Constants.APPLICATION_TYPE_SERVER; + /** */ @SpringBootApplication(scanBasePackages = {"org.apache.seata"}) public class ServerApplication { public static void main(String[] args) throws IOException { + System.setProperty(APPLICATION_TYPE_KEY, APPLICATION_TYPE_SERVER); // run the spring-boot application SpringApplication.run(ServerApplication.class, args); } diff --git a/server/src/main/java/org/apache/seata/server/ServerRunner.java b/server/src/main/java/org/apache/seata/server/ServerRunner.java index a48c7379fdf..ba0fe8066dd 100644 --- a/server/src/main/java/org/apache/seata/server/ServerRunner.java +++ b/server/src/main/java/org/apache/seata/server/ServerRunner.java @@ -54,9 +54,14 @@ public static void addDisposable(Disposable disposable) { DISPOSABLE_LIST.add(disposable); } + public static void addToFirstDisposable(Disposable disposable) { + DISPOSABLE_LIST.add(0, disposable); + } + @Resource Server seataServer; + @Override public void run(String... args) { try { @@ -83,7 +88,7 @@ public boolean started() { public void destroy() throws Exception { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("destoryAll starting"); + LOGGER.debug("destory All starting"); } for (Disposable disposable : DISPOSABLE_LIST) { @@ -91,7 +96,7 @@ public void destroy() throws Exception { } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("destoryAll finish"); + LOGGER.debug("destory All finish"); } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java new file mode 100644 index 00000000000..fd9461bcdbd --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java @@ -0,0 +1,50 @@ +/* + * 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 org.apache.seata.server.cluster.listener; + +import org.springframework.context.ApplicationEvent; + +/** + * The type ClusterConfigChangeEvent + */ +public class ClusterConfigChangeEvent extends ApplicationEvent { + + private String namespace; + private String dataId; + + public ClusterConfigChangeEvent(Object source, String namespace, String dataId) { + super(source); + this.namespace = namespace; + this.dataId = dataId; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java new file mode 100644 index 00000000000..4236a15bc71 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java @@ -0,0 +1,26 @@ +/* + * 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 org.apache.seata.server.cluster.listener; + +public interface ClusterConfigChangeListener { + + /** + * cluster config change event + * @param event event + */ + void onChangeEvent(ClusterConfigChangeEvent event); +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java new file mode 100644 index 00000000000..4e81fbff1e5 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java @@ -0,0 +1,111 @@ +/* + * 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 org.apache.seata.server.cluster.manager; + +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletResponse; + +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeListener; +import org.apache.seata.server.cluster.watch.ConfigWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * + * The type of cluster config watcher manager. + */ +@Component +public class ClusterConfigWatcherManager implements ClusterConfigChangeListener { + private static final Logger LOGGER = LoggerFactory.getLogger(ClusterConfigWatcherManager.class); + + private static final Map>>> WATCHERS = new ConcurrentHashMap<>(); + + private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("long-polling", 1)); + + @PostConstruct + public void init() { + // Responds to monitors that time out + scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> { + for (String namespace : WATCHERS.keySet()) { + Map>> dataIdWatchersMap = WATCHERS.get(namespace); + for (String dataId : dataIdWatchersMap.keySet()) { + Optional.ofNullable(dataIdWatchersMap.remove(dataId)) + .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { + if (System.currentTimeMillis() >= watcher.getTimeout()) { + HttpServletResponse httpServletResponse = + (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); + watcher.setDone(true); + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + ((AsyncContext)watcher.getAsyncContext()).complete(); + } + if (!watcher.isDone()) { + // Re-register + registryWatcher(watcher); + } + })); + } + } + }, 1, 1, TimeUnit.SECONDS); + } + @Override + @EventListener + @Async + public void onChangeEvent(ClusterConfigChangeEvent event) { + String namespace = event.getNamespace(); + String dataId = event.getDataId(); + Map>> dataIdWatchersMap = WATCHERS.get(namespace); + if (CollectionUtils.isNotEmpty(dataIdWatchersMap)) { + Optional.ofNullable(dataIdWatchersMap.remove(dataId)) + .ifPresent(watchers -> watchers.parallelStream().forEach(this::notify)); + } + } + + private void notify(ConfigWatcher watcher) { + AsyncContext asyncContext = (AsyncContext)watcher.getAsyncContext(); + HttpServletResponse httpServletResponse = (HttpServletResponse)asyncContext.getResponse(); + watcher.setDone(true); + LOGGER.info("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); + } + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + asyncContext.complete(); + } + + public void registryWatcher(ConfigWatcher watcher) { + String namespace = watcher.getNamespace(); + String dataId = watcher.getDataId(); + WATCHERS.computeIfAbsent(namespace, ns -> new ConcurrentHashMap<>()) + .computeIfAbsent(dataId, did -> new ConcurrentLinkedQueue<>()).add(watcher); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java new file mode 100644 index 00000000000..faf0e5ba9ef --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java @@ -0,0 +1,119 @@ +/* + * 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 org.apache.seata.server.cluster.raft; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RaftGroupService; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.codahale.metrics.Slf4jReporter; +import org.apache.commons.io.FileUtils; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.rpc.Disposable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_ENABLED; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_INITIAL_DELAY; + +public class RaftConfigServer implements Disposable, Closeable { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final RaftConfigStateMachine raftStateMachine; + private final String groupId; + private final String groupPath; + private final NodeOptions nodeOptions; + private final PeerId serverId; + private final RpcServer rpcServer; + private RaftGroupService raftGroupService; + private Node node; + + public RaftConfigServer(final String dataPath, final String groupId, final PeerId serverId, final NodeOptions nodeOptions, final RpcServer rpcServer) + throws IOException { + this.groupId = groupId; + this.groupPath = dataPath + File.separator + groupId; + // Initialize the state machine + this.raftStateMachine = new RaftConfigStateMachine(groupId); + this.nodeOptions = nodeOptions; + this.serverId = serverId; + this.rpcServer = rpcServer; + } + + public void start() throws IOException { + // Initialization path + FileUtils.forceMkdir(new File(groupPath)); + // Set the state machine to startup parameters + nodeOptions.setFsm(this.raftStateMachine); + // Set the storage path + // Log, must + nodeOptions.setLogUri(groupPath + File.separator + "log"); + // Meta information, must + nodeOptions.setRaftMetaUri(groupPath + File.separator + "raft_meta"); + // Snapshot, optional, is generally recommended + nodeOptions.setSnapshotUri(groupPath + File.separator + "snapshot"); + boolean reporterEnabled = ConfigurationFactory.CURRENT_FILE_INSTANCE.getBoolean(SERVER_RAFT_REPORTER_ENABLED, false); + nodeOptions.setEnableMetrics(reporterEnabled); + // Initialize the raft Group service framework + this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); + this.node = this.raftGroupService.start(false); + RouteTable.getInstance().updateConfiguration(groupId, node.getOptions().getInitialConf()); + if (reporterEnabled) { + final Slf4jReporter reporter = Slf4jReporter.forRegistry(node.getNodeMetrics().getMetricRegistry()) + .outputTo(logger).convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS).build(); + reporter.start(ConfigurationFactory.CURRENT_FILE_INSTANCE.getInt(SERVER_RAFT_REPORTER_INITIAL_DELAY, 60), + TimeUnit.MINUTES); + } + } + + public Node getNode() { + return this.node; + } + + + public RaftConfigStateMachine getRaftStateMachine() { + return raftStateMachine; + } + + public PeerId getServerId() { + return serverId; + } + + @Override + public void close() { + destroy(); + } + + @Override + public void destroy() { + Optional.ofNullable(raftGroupService).ifPresent(r -> { + r.shutdown(); + try { + r.join(); + } catch (InterruptedException e) { + logger.warn("Interrupted when RaftServer destroying", e); + } + }); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java new file mode 100644 index 00000000000..8413d2a237c --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java @@ -0,0 +1,259 @@ +/* + * 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 org.apache.seata.server.cluster.raft; + +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import com.alipay.remoting.serialization.SerializerManager; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.RaftServiceFactory; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.CliOptions; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.rpc.CliClientService; +import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.XID; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigType; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.store.ConfigStoreManagerFactory; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.server.ServerRunner; +import org.apache.seata.server.cluster.raft.processor.ConfigOperationRequestProcessor; +import org.apache.seata.server.cluster.raft.processor.PutNodeInfoRequestProcessor; +import org.apache.seata.server.cluster.raft.serializer.JacksonBoltSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.io.File.separator; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DIR; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_APPLY_BATCH; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_DISRUPTOR_BUFFER_SIZE; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_ELECTION_TIMEOUT_MS; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_APPEND_BUFFER_SIZE; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_PORT_CAMEL; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SNAPSHOT_INTERVAL; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SYNC; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_STORE_FILE_DIR; +import static org.apache.seata.common.DefaultValues.DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGEX_SPLIT_CHAR; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFERED_NETWORKS; + +/** + * The type to manager raft server of config center + */ +public class RaftConfigServerManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigServerManager.class); + private static final AtomicBoolean INIT = new AtomicBoolean(false); + private static final org.apache.seata.config.Configuration CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static RpcServer rpcServer; + private static RaftConfigServer raftServer; + private static volatile boolean RAFT_MODE; + private static final String GROUP = RAFT_CONFIG_GROUP; + + public static CliService getCliServiceInstance() { + return RaftConfigServerManager.SingletonHandler.CLI_SERVICE; + } + + public static CliClientService getCliClientServiceInstance() { + return RaftConfigServerManager.SingletonHandler.CLI_CLIENT_SERVICE; + } + + public static void init() { + if (INIT.compareAndSet(false, true)) { + String initConfStr = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + String configTypeName = CONFIG.getConfig(org.apache.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG + + org.apache.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + org.apache.seata.config.ConfigurationKeys.FILE_ROOT_TYPE); + RAFT_MODE = ConfigType.Raft.name().equalsIgnoreCase(configTypeName); + if (!RAFT_MODE) { + return; + } + if (StringUtils.isBlank(initConfStr)) { + if (RAFT_MODE) { + throw new IllegalArgumentException( + "Raft config mode must config: " + ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + } + return; + } + final Configuration initConf = new Configuration(); + if (!initConf.parse(initConfStr)) { + throw new IllegalArgumentException("fail to parse initConf:" + initConfStr); + } + int port = Integer.parseInt(System.getProperty(SERVER_RAFT_PORT_CAMEL, "0")); + PeerId serverId = null; + // XID may be null when configuration center is not initialized. + String host = null; + if (XID.getIpAddress() == null) { + String preferredNetworks = CONFIG.getConfig(REGISTRY_PREFERED_NETWORKS); + host = StringUtils.isNotBlank(preferredNetworks) ? NetUtil.getLocalIp(preferredNetworks.split(REGEX_SPLIT_CHAR)) : NetUtil.getLocalIp(); + } else { + host = XID.getIpAddress(); + } + if (port <= 0) { + // Highly available deployments require different nodes + for (PeerId peer : initConf.getPeers()) { + if (StringUtils.equals(peer.getIp(), host)) { + if (serverId != null) { + throw new IllegalArgumentException( + "server.raft.cluster has duplicate ip, For local debugging, use -Dserver.raftPort to specify the raft port"); + } + serverId = peer; + } + } + } else { + // Local debugging use + serverId = new PeerId(host, port); + } + final String dataPath = CONFIG.getConfig(CONFIG_STORE_DIR, DEFAULT_DB_STORE_FILE_DIR) + + separator + "raft" + separator + serverId.getPort(); + try { + // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this + // separately + SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); + rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); + raftServer = new RaftConfigServer(dataPath, GROUP, serverId, initNodeOptions(initConf), rpcServer); + } catch (IOException e) { + throw new IllegalArgumentException("fail init raft cluster:" + e.getMessage(), e); + } + } + } + public static void start() { + if (!RAFT_MODE) { + return; + } + try { + if (raftServer != null) { + raftServer.start(); + } + } catch (IOException e) { + LOGGER.error("start seata server raft cluster error, group: {} ", GROUP, e); + throw new RuntimeException(e); + } + LOGGER.info("started seata server raft cluster, group: {} ", GROUP); + + if (rpcServer != null) { + rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); + rpcServer.registerProcessor(new ConfigOperationRequestProcessor()); + if (!rpcServer.init(null)) { + throw new RuntimeException("start raft node fail!"); + } + } + // Make sure to close it at the end, as other components may still use the configuration, such as ShutdownWaitTime. + ServerRunner.addDisposable(() -> { + RaftConfigServerManager.destroy(); + ConfigStoreManagerFactory.destroy(); + }); + } + + + public static void destroy() { + if (raftServer != null) { + raftServer.close(); + } + LOGGER.info("closed seata server raft cluster, group: {} ", GROUP); + Optional.ofNullable(rpcServer).ifPresent(RpcServer::shutdown); + raftServer = null; + rpcServer = null; + RAFT_MODE = false; + INIT.set(false); + } + public static boolean isRaftMode() { + return RAFT_MODE; + } + public static RaftConfigServer getRaftServer() { + return raftServer; + } + + public static RpcServer getRpcServer() { + return rpcServer; + } + + public static boolean isLeader() { + AtomicReference stateMachine = new AtomicReference<>(); + Optional.ofNullable(raftServer).ifPresent(raftConfigServer -> { + stateMachine.set(raftConfigServer.getRaftStateMachine()); + }); + RaftConfigStateMachine raftStateMachine = stateMachine.get(); + return raftStateMachine != null && raftStateMachine.isLeader(); + } + + public static PeerId getLeader() { + + RouteTable routeTable = RouteTable.getInstance(); + try { + routeTable.refreshLeader(getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); + return routeTable.selectLeader(RAFT_CONFIG_GROUP); + } catch (Exception e) { + LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); + } + return null; + + } + private static RaftOptions initRaftOptions() { + RaftOptions raftOptions = new RaftOptions(); + raftOptions.setApplyBatch(CONFIG.getInt(SERVER_RAFT_APPLY_BATCH, raftOptions.getApplyBatch())); + raftOptions.setMaxAppendBufferSize( + CONFIG.getInt(SERVER_RAFT_MAX_APPEND_BUFFER_SIZE, raftOptions.getMaxAppendBufferSize())); + raftOptions.setDisruptorBufferSize( + CONFIG.getInt(SERVER_RAFT_DISRUPTOR_BUFFER_SIZE, raftOptions.getDisruptorBufferSize())); + raftOptions.setMaxReplicatorInflightMsgs( + CONFIG.getInt(SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS, raftOptions.getMaxReplicatorInflightMsgs())); + raftOptions.setSync(CONFIG.getBoolean(SERVER_RAFT_SYNC, raftOptions.isSync())); + return raftOptions; + } + + private static NodeOptions initNodeOptions(Configuration initConf) { + NodeOptions nodeOptions = new NodeOptions(); + // enable the CLI service. + nodeOptions.setDisableCli(false); + // snapshot should be made every 600 seconds + int snapshotInterval = CONFIG.getInt(SERVER_RAFT_SNAPSHOT_INTERVAL, 60 * 10); + nodeOptions.setSnapshotIntervalSecs(snapshotInterval); + nodeOptions.setRaftOptions(initRaftOptions()); + // set the election timeout to 1 second + nodeOptions + .setElectionTimeoutMs(CONFIG.getInt(SERVER_RAFT_ELECTION_TIMEOUT_MS, DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS)); + // set up the initial cluster configuration + nodeOptions.setInitialConf(initConf); + return nodeOptions; + } + public static String getGroup() { + return GROUP; + } + private static class SingletonHandler { + private static final CliService CLI_SERVICE = RaftServiceFactory.createAndInitCliService(new CliOptions()); + private static final CliClientService CLI_CLIENT_SERVICE = new CliClientServiceImpl(); + + static { + CLI_CLIENT_SERVICE.init(new CliOptions()); + } + + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java new file mode 100644 index 00000000000..4a9f48884da --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java @@ -0,0 +1,455 @@ +/* + * 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 org.apache.seata.server.cluster.raft; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.rpc.InvokeContext; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.common.XID; +import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.common.metadata.ClusterRole; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.server.cluster.listener.ClusterChangeEvent; +import org.apache.seata.server.cluster.raft.context.SeataClusterContext; +import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationExecute; +import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.apache.seata.server.cluster.raft.snapshot.config.ConfigSnapshotFile; +import org.apache.seata.server.cluster.raft.snapshot.metadata.ConfigLeaderMetadataSnapshotFile; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType; +import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; +import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.env.Environment; + +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; +import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.CONFIG_OPERATION; +import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REFRESH_CLUSTER_METADATA; + + +/** + * The type raft config state machine. + */ +public class RaftConfigStateMachine extends StateMachineAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigStateMachine.class); + + private final String group; + + private final List snapshotFiles = new ArrayList<>(); + + private static final Map> EXECUTES = new HashMap<>(); + + private volatile RaftClusterMetadata raftClusterMetadata = new RaftClusterMetadata(); + + private final Lock lock = new ReentrantLock(); + + private static final ScheduledThreadPoolExecutor RESYNC_METADATA_POOL = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("reSyncMetadataPool", 1, true)); + + /** + * Leader term + */ + private final AtomicLong leaderTerm = new AtomicLong(-1); + + /** + * current term + */ + private final AtomicLong currentTerm = new AtomicLong(-1); + + private final AtomicBoolean initSync = new AtomicBoolean(false); + + private ScheduledFuture scheduledFuture; + + public boolean isLeader() { + return this.leaderTerm.get() > 0; + } + + public RaftConfigStateMachine(String group) { + this.group = group; + + EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { + refreshClusterMetadata(syncMsg); + return null; + }); + registryStoreSnapshotFile(new ConfigLeaderMetadataSnapshotFile(group)); + registryStoreSnapshotFile(new ConfigSnapshotFile(group)); + EXECUTES.put(CONFIG_OPERATION, new ConfigOperationExecute()); + EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { + refreshClusterMetadata(syncMsg); + return null; + }); + this.scheduledFuture = + RESYNC_METADATA_POOL.scheduleAtFixedRate(() -> syncCurrentNodeInfo(group), 10, 10, TimeUnit.SECONDS); + } + + @Override + public void onApply(Iterator iterator) { + while (iterator.hasNext()) { + Closure done = iterator.done(); + if (done != null) { + // leader does not need to be serialized, just execute the task directly + if (done instanceof ConfigClosure) { + ConfigClosure configClosure = (ConfigClosure) done; + RaftBaseMsg msg = configClosure.getRaftBaseMsg(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sync msg: {}", msg); + } + ConfigOperationResponse response = (ConfigOperationResponse) onExecuteRaft(msg); + configClosure.getResponse().setSuccess(response.isSuccess()); + configClosure.getResponse().setResult(response.getResult()); + configClosure.getResponse().setErrMsg(response.getErrMsg()); + configClosure.run(Status.OK()); + } else { + // If it's not a ConfigClosure, just run it with OK status + done.run(Status.OK()); + } + } else { + ByteBuffer byteBuffer = iterator.getData(); + // if data is empty, it is only a heartbeat event and can be ignored + if (byteBuffer != null && byteBuffer.hasRemaining()) { + RaftBaseMsg msg = (RaftBaseMsg) RaftSyncMessageSerializer.decode(byteBuffer.array()).getBody(); + // follower executes the corresponding task + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sync msg: {}", msg); + } + onExecuteRaft(msg); + } + } + iterator.next(); + } + } + + @Override + public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + Status status = snapshotFile.save(writer); + if (!status.isOk()) { + done.run(status); + return; + } + } + LOGGER.info("groupId: {}, onSnapshotSave cost: {} ms.", group, System.currentTimeMillis() - current); + done.run(Status.OK()); + } + + @Override + public boolean onSnapshotLoad(final SnapshotReader reader) { + if (isLeader()) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Leader is not supposed to load snapshot"); + } + return false; + } + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + if (!snapshotFile.load(reader)) { + return false; + } + } + LOGGER.info("groupId: {}, onSnapshotLoad cost: {} ms.", group, System.currentTimeMillis() - current); + return true; + } + @Override + public void onLeaderStart(final long term) { + boolean leader = isLeader(); + this.leaderTerm.set(term); + LOGGER.info("groupId: {}, onLeaderStart: term={}.", group, term); + this.currentTerm.set(term); + syncMetadata(); + if (!leader && RaftConfigServerManager.isRaftMode()) { + Configuration conf = RouteTable.getInstance().getConfiguration(group); + // A member change might trigger a leader re-election. At this point, it’s necessary to filter out non-existent members and synchronize again. + changePeers(conf); + } + } + + @Override + public void onLeaderStop(final Status status) { + this.leaderTerm.set(-1); + LOGGER.info("groupId: {}, onLeaderStop: status={}.", group, status); + } + + @Override + public void onStopFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStopFollowing: {}.", group, ctx); + } + + @Override + public void onStartFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStartFollowing: {}.", group, ctx); + this.currentTerm.set(ctx.getTerm()); + CompletableFuture.runAsync(() -> syncCurrentNodeInfo(ctx.getLeaderId()), RESYNC_METADATA_POOL); + } + + @Override + public void onConfigurationCommitted(Configuration conf) { + LOGGER.info("groupId: {}, onConfigurationCommitted: {}.", group, conf); + RouteTable.getInstance().updateConfiguration(group, conf); + // After a member change, the metadata needs to be synchronized again. + initSync.compareAndSet(true, false); + if (isLeader()) { + changePeers(conf); + } + } + private void changePeers(Configuration conf) { + lock.lock(); + try { + List newFollowers = conf.getPeers(); + Set newLearners = conf.getLearners(); + List currentFollowers = raftClusterMetadata.getFollowers(); + if (CollectionUtils.isNotEmpty(newFollowers)) { + raftClusterMetadata.setFollowers(currentFollowers.stream().filter(node -> contains(node, newFollowers)) + .collect(Collectors.toList())); + } + if (CollectionUtils.isNotEmpty(newLearners)) { + raftClusterMetadata.setLearner(raftClusterMetadata.getLearner().stream() + .filter(node -> contains(node, newLearners)).collect(Collectors.toList())); + } else { + raftClusterMetadata.setLearner(Collections.emptyList()); + } + CompletableFuture.runAsync(this::syncMetadata, RESYNC_METADATA_POOL); + } finally { + lock.unlock(); + } + } + + private boolean contains(Node node, Collection list) { + // This indicates that the node is of a lower version. + // When scaling up or down on a higher version + // you need to ensure that the cluster is consistent first + // otherwise, the lower version nodes may be removed. + if (node.getInternal() == null) { + return true; + } + PeerId nodePeer = new PeerId(node.getInternal().getHost(), node.getInternal().getPort()); + return list.contains(nodePeer); + } + + public void syncMetadata() { + if (isLeader()) { + SeataClusterContext.bindGroup(group); + try { + RaftClusterMetadataMsg raftClusterMetadataMsg = + new RaftClusterMetadataMsg(changeOrInitRaftClusterMetadata()); + RaftConfigTaskUtil.createTask(status -> refreshClusterMetadata(raftClusterMetadataMsg), + raftClusterMetadataMsg, null); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } finally { + SeataClusterContext.unbindGroup(); + } + } + } + + private Object onExecuteRaft(RaftBaseMsg msg) { + RaftMsgExecute execute = EXECUTES.get(msg.getMsgType()); + if (execute == null) { + throw new RuntimeException( + "the state machine does not allow events that cannot be executed, please feedback the information to the Seata community !!! msg: " + + msg); + } + try { + return execute.execute(msg); + } catch (Throwable e) { + LOGGER.error("Message synchronization failure: {}, msgType: {}", e.getMessage(), msg.getMsgType(), e); + throw new RuntimeException(e); + } + } + + public AtomicLong getCurrentTerm() { + return currentTerm; + } + + public void registryStoreSnapshotFile(StoreSnapshotFile storeSnapshotFile) { + snapshotFiles.add(storeSnapshotFile); + } + + public RaftClusterMetadata getRaftLeaderMetadata() { + return raftClusterMetadata; + } + + public void setRaftLeaderMetadata(RaftClusterMetadata raftClusterMetadata) { + this.raftClusterMetadata = raftClusterMetadata; + } + + public RaftClusterMetadata changeOrInitRaftClusterMetadata() { + raftClusterMetadata.setTerm(this.currentTerm.get()); + Node leaderNode = raftClusterMetadata.getLeader(); + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + PeerId cureentPeerId = raftServer.getServerId(); + // After the re-election, the leader information may be different from the latest leader, and you need to replace the leader information + if (leaderNode == null || (leaderNode.getInternal() != null + && !cureentPeerId.equals(new PeerId(leaderNode.getInternal().getHost(), leaderNode.getInternal().getPort())))) { + Node leader = + raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0 ? 8091 : XID.getPort(), raftServer.getServerId().getPort(), + Integer.parseInt( + ((Environment) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) + .getProperty("server.port", String.valueOf(7091))), + group, Collections.emptyMap()); + leader.setRole(ClusterRole.LEADER); + raftClusterMetadata.setLeader(leader); + } + return raftClusterMetadata; + } + + public void refreshClusterMetadata(RaftBaseMsg syncMsg) { + // Directly receive messages from the leader and update the cluster metadata + raftClusterMetadata = ((RaftClusterMetadataMsg)syncMsg).getRaftClusterMetadata(); + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterChangeEvent(this, group, raftClusterMetadata.getTerm(), this.isLeader())); + LOGGER.info("groupId: {}, refresh cluster metadata: {}", group, raftClusterMetadata); + } + + } + + private void syncCurrentNodeInfo(String group) { + if (initSync.compareAndSet(false, true)) { + try { + RouteTable.getInstance().refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), group, 1000); + PeerId peerId = RouteTable.getInstance().selectLeader(group); + if (peerId != null) { + syncCurrentNodeInfo(peerId); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + } + + private void syncCurrentNodeInfo(PeerId leaderPeerId) { + try { + // Ensure that the current leader must be version 2.1 or later to synchronize the operation + Node leader = raftClusterMetadata.getLeader(); + if (leader != null && StringUtils.isNotBlank(leader.getVersion())) { + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + PeerId cureentPeerId = raftServer.getServerId(); + Node node = raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0 ? 8091 : XID.getPort(), cureentPeerId.getPort(), + Integer.parseInt( + ((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) + .getProperty("server.port", String.valueOf(7091))), + group, Collections.emptyMap()); + InvokeContext invokeContext = new InvokeContext(); + PutNodeMetadataRequest putNodeInfoRequest = new PutNodeMetadataRequest(node); + Configuration configuration = RouteTable.getInstance().getConfiguration(group); + node.setRole( + configuration.getPeers().contains(cureentPeerId) ? ClusterRole.FOLLOWER : ClusterRole.LEARNER); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + CliClientServiceImpl cliClientService = + (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + // The previous leader may be an old snapshot or log playback, which is not accurate, and you + // need to get the leader again + cliClientService.getRpcClient().invokeAsync(leaderPeerId.getEndpoint(), putNodeInfoRequest, + invokeContext, (result, err) -> { + if (err == null) { + PutNodeMetadataResponse putNodeMetadataResponse = (PutNodeMetadataResponse)result; + if (putNodeMetadataResponse.isSuccess()) { + scheduledFuture.cancel(true); + LOGGER.info("sync node info to leader: {}, result: {}", leaderPeerId, result); + } else { + initSync.compareAndSet(true, false); + LOGGER.info( + "sync node info to leader: {}, result: {}, retry will be made at the time of the re-election or after 10 seconds", + leaderPeerId, result); + } + } else { + initSync.compareAndSet(true, false); + LOGGER.error("sync node info to leader: {}, error: {}", leaderPeerId, err.getMessage(), + err); + } + }, 30000); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + + public void changeNodeMetadata(Node node) { + lock.lock(); + try { + List list = node.getRole() == ClusterRole.FOLLOWER ? raftClusterMetadata.getFollowers() + : raftClusterMetadata.getLearner(); + // If the node currently exists, modify it + for (Node follower : list) { + Node.Endpoint endpoint = follower.getInternal(); + if (endpoint != null) { + // change old follower node metadata + if (endpoint.getHost().equals(node.getInternal().getHost()) + && endpoint.getPort() == node.getInternal().getPort()) { + follower.setTransaction(node.getTransaction()); + follower.setControl(node.getControl()); + follower.setGroup(group); + follower.setMetadata(node.getMetadata()); + follower.setVersion(node.getVersion()); + follower.setRole(node.getRole()); + return; + } + } + } + // add new node node metadata + list.add(node); + syncMetadata(); + } finally { + lock.unlock(); + } + } + + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java index b891bea8484..519b1af4fff 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java @@ -132,7 +132,7 @@ public static void init() { try { // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this // separately - rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); + rpcServer = RaftConfigServerManager.getRpcServer() == null ? RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()) : RaftConfigServerManager.getRpcServer(); RaftServer raftServer = new RaftServer(dataPath, group, serverId, initNodeOptions(initConf), rpcServer); // as the foundation for multi raft group in the future RAFT_SERVER_MAP.put(group, raftServer); @@ -152,7 +152,7 @@ public static void start() { } LOGGER.info("started seata server raft cluster, group: {} ", group); }); - if (rpcServer != null) { + if (rpcServer != null && RaftConfigServerManager.getRpcServer() == null) { rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); if (!rpcServer.init(null)) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java new file mode 100644 index 00000000000..4ce6b31e342 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java @@ -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 org.apache.seata.server.cluster.raft.execute.config; + +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerFactory; +import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; + + + +public abstract class AbstractRaftConfigMsgExecute implements RaftMsgExecute { + + protected ConfigStoreManager configStoreManager = ConfigStoreManagerFactory.getInstance(); + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java new file mode 100644 index 00000000000..9ebdb937b4b --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java @@ -0,0 +1,143 @@ +/* + * 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 org.apache.seata.server.cluster.raft.execute.config; + +import java.util.List; +import java.util.Map; + +import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.config.dto.ConfigurationInfoDto; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; +import org.apache.seata.config.dto.ConfigurationItem; +import org.apache.seata.server.config.ConfigurationProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; + +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; + + +public class ConfigOperationExecute extends AbstractRaftConfigMsgExecute { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigOperationExecute.class); + + @Override + public Object execute(RaftBaseMsg syncMsg) throws Throwable { + RaftConfigOperationSyncMsg configSyncMsg = (RaftConfigOperationSyncMsg) syncMsg; + ConfigOperationDTO configOperation = configSyncMsg.getConfigOperation(); + switch (configOperation.getOptType()) { + case PUT: + return put(configOperation); + case DELETE: + return delete(configOperation); + case DELETE_ALL: + return deleteAll(configOperation); + case GET: + return get(configOperation); + case GET_ALL: + return getAll(configOperation); + case UPLOAD: + return upload(configOperation); + case GET_NAMESPACES: + return getNamespaces(configOperation); + case GET_DATA_IDS: + return getDataIds(configOperation); + default: + return ConfigOperationResponse.fail("unknown operation type"); + } + } + + private ConfigOperationResponse get(ConfigOperationDTO configOperation) { + String result = configStoreManager.get(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); + // fill config description and default value + ConfigurationItem item = ConfigurationProcessor.processConfigItem(configOperation.getKey(), result); + return ConfigOperationResponse.success(item); + } + + private ConfigOperationResponse put(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.put(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey(), configOperation.getValue()); + if (success) { + // ApplicationContext may not have been started at this point + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); + } + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); + } + return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse delete(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.delete(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); + if (success) { + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); + } + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); + } + return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse deleteAll(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.deleteAll(configOperation.getNamespace(), configOperation.getDataId()); + if (success) { + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); + } + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); + } + return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse upload(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.putAll(configOperation.getNamespace(), configOperation.getDataId(), (Map) configOperation.getValue()); + if (success) { + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); + } + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); + } + return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse getAll(ConfigOperationDTO configOperation) { + Map configMap = configStoreManager.getAll(configOperation.getNamespace(), configOperation.getDataId()); + Long configVersion = configStoreManager.getConfigVersion(configOperation.getNamespace(), configOperation.getDataId()); + // fill config description and default value + Map itemMap = ConfigurationProcessor.processConfigMap(configMap); + ConfigurationInfoDto configurationInfoDto = new ConfigurationInfoDto(); + configurationInfoDto.setConfig(itemMap); + configurationInfoDto.setVersion(configVersion); + return ConfigOperationResponse.success(configurationInfoDto); + } + + private ConfigOperationResponse getNamespaces(ConfigOperationDTO configOperation) { + List namespaces = configStoreManager.getAllNamespaces(); + return ConfigOperationResponse.success(namespaces); + } + + private ConfigOperationResponse getDataIds(ConfigOperationDTO configOperation) { + List dataIds = configStoreManager.getAllDataIds(configOperation.getNamespace()); + return ConfigOperationResponse.success(dataIds); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java new file mode 100644 index 00000000000..8d38613d43d --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java @@ -0,0 +1,70 @@ +/* + * 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 org.apache.seata.server.cluster.raft.execute.config; + +public enum ConfigOperationType { + + /** + * Get configuration operation + */ + GET("get"), + + /** + * Put configuration operation + */ + PUT("put"), + + /** + * Delete configuration operation + */ + DELETE("delete"), + + /** + * Delete all configuration operation + */ + DELETE_ALL("deleteAll"), + + /** + * Upload configuration operation + */ + UPLOAD("upload"), + + /** + * Get all configuration operation + */ + GET_ALL("getAll"), + + /** + * Get namespaces operation + */ + GET_NAMESPACES("getNamespaces"), + + /** + * Get data ids operation + */ + GET_DATA_IDS("getDataIds"); + + private final String type; + + ConfigOperationType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java new file mode 100644 index 00000000000..8cc1f2abe62 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java @@ -0,0 +1,67 @@ +/* + * 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 org.apache.seata.server.cluster.raft.processor; + +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; +import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; +import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; + + +public class ConfigOperationRequestProcessor implements RpcProcessor { + private static final String NOT_LEADER = "not leader"; + @Override + public void handleRequest(RpcContext rpcCtx, ConfigOperationRequest request) { + if (RaftConfigServerManager.isLeader()) { + onExecute(rpcCtx, request); + } else { + rpcCtx.sendResponse(ConfigOperationResponse.fail(NOT_LEADER)); + } + } + + private void onExecute(RpcContext rpcCtx, ConfigOperationRequest request) { + ConfigOperationDTO operationDTO = ConfigOperationDTO.convertConfigRequest2Dto(request); + RaftConfigOperationSyncMsg syncMsg = new RaftConfigOperationSyncMsg(operationDTO); + ConfigOperationResponse response = new ConfigOperationResponse(); + ConfigClosure closure = new ConfigClosure(); + closure.setRaftBaseMsg(syncMsg); + closure.setResponse(response); + closure.setDone(status -> { + if (!status.isOk()) { + response.setSuccess(false); + response.setErrMsg(status.getErrorMsg()); + } + rpcCtx.sendResponse(response); + }); + try { + RaftConfigTaskUtil.createTask(closure, syncMsg, null); + } catch (TransactionException e) { + throw new RuntimeException(e); + } + } + + @Override + public String interest() { + return ConfigOperationRequest.class.getName(); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java index de90cbd8c5d..41e060fad60 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java @@ -19,12 +19,16 @@ import com.alipay.sofa.jraft.rpc.RpcContext; import com.alipay.sofa.jraft.rpc.RpcProcessor; import org.apache.seata.common.metadata.Node; +import org.apache.seata.server.cluster.raft.RaftConfigServer; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.RaftConfigStateMachine; import org.apache.seata.server.cluster.raft.RaftServer; import org.apache.seata.server.cluster.raft.RaftServerManager; import org.apache.seata.server.cluster.raft.RaftStateMachine; import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; + public class PutNodeInfoRequestProcessor implements RpcProcessor { public PutNodeInfoRequestProcessor() { @@ -35,6 +39,25 @@ public PutNodeInfoRequestProcessor() { public void handleRequest(RpcContext rpcCtx, PutNodeMetadataRequest request) { Node node = request.getNode(); String group = node.getGroup(); + if (RaftConfigServerManager.getGroup().equals(group)) { + changeConfigGroupRequest(group, node, rpcCtx, request); + } else { + changeNormalGroupRequest(group, node, rpcCtx, request); + } + } + + private static void changeConfigGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request) { + if (RaftConfigServerManager.isLeader()) { + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + RaftConfigStateMachine raftStateMachine = raftServer.getRaftStateMachine(); + raftStateMachine.changeNodeMetadata(node); + rpcCtx.sendResponse(new PutNodeMetadataResponse(true)); + } else { + rpcCtx.sendResponse(new PutNodeMetadataResponse(false)); + } + } + + private static void changeNormalGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request) { if (RaftServerManager.isLeader(group)) { RaftServer raftServer = RaftServerManager.getRaftServer(group); RaftStateMachine raftStateMachine = raftServer.getRaftStateMachine(); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java new file mode 100644 index 00000000000..d0ace0b2a50 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java @@ -0,0 +1,137 @@ +/* + * 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 org.apache.seata.server.cluster.raft.processor.request; + +import java.io.Serializable; +import java.util.Map; + +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; + +public class ConfigOperationRequest implements Serializable { + private static final long serialVersionUID = -1149573667621259458L; + private ConfigOperationType optType; + private String namespace; + private String dataId; + private String key; + private Object value; + + public ConfigOperationRequest() { + } + + public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId) { + this.optType = optType; + this.namespace = namespace; + this.dataId = dataId; + } + + public ConfigOperationRequest(ConfigOperationType optType,String namespace, String dataId, String key) { + this.optType = optType; + this.namespace = namespace; + this.dataId = dataId; + this.key = key; + } + + public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId, String key, Object value) { + this.optType = optType; + this.namespace = namespace; + this.dataId = dataId; + this.key = key; + this.value = value; + } + + public static ConfigOperationRequest buildGetRequest(String namespace, String dataId, String key) { + return new ConfigOperationRequest(ConfigOperationType.GET, namespace, dataId, key); + } + + public static ConfigOperationRequest buildPutRequest(String namespace, String dataId, String key, String value) { + return new ConfigOperationRequest(ConfigOperationType.PUT, namespace, dataId, key, value); + } + + public static ConfigOperationRequest buildDeleteRequest(String namespace, String dataId, String key) { + return new ConfigOperationRequest(ConfigOperationType.DELETE, namespace, dataId, key); + } + public static ConfigOperationRequest buildDeleteAllRequest(String namespace, String dataId) { + return new ConfigOperationRequest(ConfigOperationType.DELETE_ALL, namespace, dataId); + } + + public static ConfigOperationRequest buildGetAllRequest(String namespace, String dataId) { + return new ConfigOperationRequest(ConfigOperationType.GET_ALL, namespace, dataId); + } + + public static ConfigOperationRequest buildUploadRequest(String namespace, String dataId, Map configMap) { + return new ConfigOperationRequest(ConfigOperationType.UPLOAD, namespace, dataId, null, configMap); + } + + public static ConfigOperationRequest buildGetNamespaces() { + return new ConfigOperationRequest(ConfigOperationType.GET_NAMESPACES, null, null); + } + + public static ConfigOperationRequest buildGetDataIds(String namespace) { + return new ConfigOperationRequest(ConfigOperationType.GET_DATA_IDS, namespace, null); + } + + + public ConfigOperationType getOptType() { + return optType; + } + public void setOptType(ConfigOperationType optType) { + this.optType = optType; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + @Override + public String toString() { + return "ConfigOperationRequest{" + + "optType=" + optType + + ", namespace='" + namespace + '\'' + + ", dataId='" + dataId + '\'' + + ", key='" + key + '\'' + + ", value=" + value + + '}'; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java new file mode 100644 index 00000000000..4b1a2222e11 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java @@ -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. + */ +package org.apache.seata.server.cluster.raft.processor.response; + +import java.io.Serializable; + +public class ConfigOperationResponse implements Serializable { + private static final long serialVersionUID = -1439073440621259777L; + + private Object result; + private boolean success; + private String errMsg; + + public ConfigOperationResponse() { + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public static ConfigOperationResponse success() { + ConfigOperationResponse response = new ConfigOperationResponse(); + response.setSuccess(true); + return response; + } + + public static ConfigOperationResponse success(Object result) { + ConfigOperationResponse response = success(); + response.setResult(result); + return response; + } + + public static ConfigOperationResponse fail() { + ConfigOperationResponse response = new ConfigOperationResponse(); + response.setSuccess(false); + return response; + } + + public static ConfigOperationResponse fail(String errMsg) { + ConfigOperationResponse response = fail(); + response.setErrMsg(errMsg); + return response; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java index e8c1cc68f31..ec5695bd309 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java @@ -131,8 +131,11 @@ public enum SnapshotType { /** * leader metadata snapshot */ - leader_metadata("leader_metadata"); - + leader_metadata("leader_metadata"), + /** + * config snapshot + */ + config("config"); final String type; SnapshotType(String type) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java new file mode 100644 index 00000000000..971902cb98d --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java @@ -0,0 +1,106 @@ +/* + * 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 org.apache.seata.server.cluster.raft.snapshot.config; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerProvider; +import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + +public class ConfigSnapshotFile implements Serializable, StoreSnapshotFile { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigSnapshotFile.class); + + private static final long serialVersionUID = 1452307567830545914L; + + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + private static ConfigStoreManager configStoreManager; + private final String group; + + private final String fileName = "config"; + + public ConfigSnapshotFile(String group) { + this.group = group; + String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); + configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + } + + @Override + public Status save(SnapshotWriter writer) { + Map> configMap = configStoreManager.getConfigMap(); + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(configMap); + raftSnapshot.setType(RaftSnapshot.SnapshotType.config); + LOGGER.info("groupId: {}, config size: {}", group, configMap.size()); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + LOGGER.info("on snapshot load start index: {}", reader.load().getLastIncludedIndex()); + Map> configMap = (Map>)load(path); + ConfigStoreManager configStoreManager = RocksDBConfigStoreManager.getInstance(); + configStoreManager.clearData(); + configStoreManager.putConfigMap(configMap); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("on snapshot load end index: {}", reader.load().getLastIncludedIndex()); + } + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java new file mode 100644 index 00000000000..8089d30ba9c --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java @@ -0,0 +1,88 @@ +/* + * 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 org.apache.seata.server.cluster.raft.snapshot.metadata; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConfigLeaderMetadataSnapshotFile implements Serializable, StoreSnapshotFile { + private static final long serialVersionUID = 43235664615355354L; + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLeaderMetadataSnapshotFile.class); + + private final String group; + + private final String fileName = "leader_metadata"; + + public ConfigLeaderMetadataSnapshotFile(String group) { + this.group = group; + } + + + @Override + public Status save(SnapshotWriter writer) { + RaftSnapshot raftSnapshot = new RaftSnapshot(); + RaftClusterMetadata raftClusterMetadata = + RaftConfigServerManager.getRaftServer().getRaftStateMachine().getRaftLeaderMetadata(); + raftSnapshot.setBody(raftClusterMetadata); + raftSnapshot.setType(RaftSnapshot.SnapshotType.leader_metadata); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + RaftClusterMetadata raftClusterMetadata = (RaftClusterMetadata)load(path); + RaftConfigServerManager.getRaftServer().getRaftStateMachine() + .setRaftLeaderMetadata(raftClusterMetadata); + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } +} + diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java new file mode 100644 index 00000000000..53a0c3cb281 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java @@ -0,0 +1,45 @@ +/* + * 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 org.apache.seata.server.cluster.raft.sync.msg; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; + + +public class RaftConfigOperationSyncMsg extends RaftBaseMsg { + + private static final long serialVersionUID = -3344345671349834321L; + private ConfigOperationDTO configOperation; + + public RaftConfigOperationSyncMsg(ConfigOperationDTO configOperation) { + this.msgType = RaftSyncMsgType.CONFIG_OPERATION; + this.configOperation = configOperation; + } + + public RaftConfigOperationSyncMsg() { + } + + public ConfigOperationDTO getConfigOperation() { + return configOperation; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java index f74cdb88c52..869fe011991 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java @@ -55,5 +55,7 @@ public enum RaftSyncMsgType { /** * refresh cluster metadata */ - REFRESH_CLUSTER_METADATA; + REFRESH_CLUSTER_METADATA, + + CONFIG_OPERATION; } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java new file mode 100644 index 00000000000..ade72303882 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java @@ -0,0 +1,63 @@ +/* + * 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 org.apache.seata.server.cluster.raft.sync.msg.closure; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Status; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; + +/** + * The type of closure for configuration sync in raft + */ +public class ConfigClosure implements Closure { + + private RaftBaseMsg raftBaseMsg; + private ConfigOperationResponse response; + private Closure done; + + @Override + public void run(Status status) { + if (done != null) { + done.run(status); + } + } + + public RaftBaseMsg getRaftBaseMsg() { + return raftBaseMsg; + } + + public void setRaftBaseMsg(RaftBaseMsg raftBaseMsg) { + this.raftBaseMsg = raftBaseMsg; + } + + public ConfigOperationResponse getResponse() { + return response; + } + + public void setResponse(ConfigOperationResponse response) { + this.response = response; + } + + public Closure getDone() { + return done; + } + + public void setDone(Closure done) { + this.done = done; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java new file mode 100644 index 00000000000..0f3e0795f43 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java @@ -0,0 +1,98 @@ +/* + * 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 org.apache.seata.server.cluster.raft.sync.msg.dto; + +import java.io.Serializable; + +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; + +public class ConfigOperationDTO implements Serializable { + private static final long serialVersionUID = -1237293571963636954L; + + private ConfigOperationType optType; + private String namespace; + private String dataId; + private String key; + private Object value; + + public ConfigOperationDTO() { + } + + public ConfigOperationDTO(ConfigOperationType optType, String namespace, String dataId, String key, Object value) { + this.optType = optType; + this.namespace = namespace; + this.dataId = dataId; + this.key = key; + this.value = value; + } + + public ConfigOperationType getOptType() { + return optType; + } + + public void setOptType(ConfigOperationType optType) { + this.optType = optType; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public static ConfigOperationDTO convertConfigRequest2Dto(ConfigOperationRequest request) { + return new ConfigOperationDTO(request.getOptType(), request.getNamespace(), request.getDataId(), request.getKey(), request.getValue()); + } + + @Override + public String toString() { + return "ConfigOperationDTO{" + + "optType=" + optType + + ", namespace='" + namespace + '\'' + + ", dataId='" + dataId + '\'' + + ", key='" + key + '\'' + + ", value=" + value + + '}'; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java b/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java new file mode 100644 index 00000000000..2d50d6249eb --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java @@ -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. + */ +package org.apache.seata.server.cluster.raft.util; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.entity.Task; +import org.apache.seata.core.exception.GlobalTransactionException; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; + +/** + */ +public class RaftConfigTaskUtil { + public static boolean createTask(Closure done, Object data, CompletableFuture completableFuture) + throws TransactionException { + final Task task = new Task(); + if (data != null) { + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + raftSyncMessage.setBody(data); + try { + task.setData(ByteBuffer.wrap(RaftSyncMessageSerializer.encode(raftSyncMessage))); + } catch (IOException e) { + throw new TransactionException(e); + } + } + task.setDone(done == null ? status -> { + } : done); + RaftConfigServerManager.getRaftServer().getNode().apply(task); + if (completableFuture != null) { + return futureGet(completableFuture); + } + return true; + } + + public static boolean createTask(Closure done, CompletableFuture completableFuture) + throws TransactionException { + return createTask(done, null, completableFuture); + } + + public static boolean futureGet(CompletableFuture completableFuture) throws TransactionException { + try { + return completableFuture.get(); + } catch (InterruptedException e) { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } catch (ExecutionException e) { + if (e.getCause() instanceof TransactionException) { + throw (TransactionException)e.getCause(); + } else { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } + } + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java b/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java new file mode 100644 index 00000000000..d2dfcd6c725 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java @@ -0,0 +1,89 @@ +/* + * 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 org.apache.seata.server.cluster.watch; + +import static org.apache.seata.server.cluster.watch.Watcher.Protocol.HTTP; + +public class ConfigWatcher { + private String namespace; + + private String dataId; + + private volatile boolean done = false; + + private T asyncContext; + + private long timeout; + + + private String protocol = HTTP; + + public ConfigWatcher(String namespace, String dataId, T asyncContext, int timeout) { + this.namespace = namespace; + this.dataId = dataId; + this.asyncContext = asyncContext; + this.timeout = System.currentTimeMillis() + timeout; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public boolean isDone() { + return done; + } + + public void setDone(boolean done) { + this.done = done; + } + + public T getAsyncContext() { + return asyncContext; + } + + public void setAsyncContext(T asyncContext) { + this.asyncContext = asyncContext; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } +} diff --git a/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java b/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java new file mode 100644 index 00000000000..06e6d6d7487 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java @@ -0,0 +1,108 @@ +/* + * 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 org.apache.seata.server.config; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.seata.common.Constants; +import org.apache.seata.config.dto.ConfigurationItem; +import org.apache.seata.config.dto.ConfigurationItemMeta; +import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.yaml.snakeyaml.Yaml; + + +/** + * The configuration items processor + * + */ +public class ConfigurationProcessor { + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); + private static final String ENCRYPT_STRING = "******"; + private static final String NOT_SEATA_CONFIG = "Not Seata configuration"; + private static final Map CONFIGURATION_ITEMS_META_MAP = new HashMap<>(); + private static final String META_FILE_NAME = Constants.CONFIGURATION_META_FILE_NAME; + private static final String CONFIG_META_KEY = "configuration-meta"; + private static final String META_KEY_KEY = "key"; + private static final String META_DESC_KEY = "desc"; + private static final String META_DEFAULT_VALUE_KEY = "defaultValue"; + private static final String META_ENCRYPT_KEY = "isEncrypt"; + + static { + loadConfigurationItemMeta(); + } + + /** + * load Configuration items meta from local yaml file. + */ + @SuppressWarnings("unchecked") + private static void loadConfigurationItemMeta() { + try (InputStream inputStream = new ClassPathResource(META_FILE_NAME).getInputStream()) { + Yaml yaml = new Yaml(); + Map map = yaml.load(inputStream); + List> configItemMetaList = (List>) map.get(CONFIG_META_KEY); + for (Map metaMap : configItemMetaList) { + ConfigurationItemMeta itemMeta = mapToConfigItemMeta(metaMap); + CONFIGURATION_ITEMS_META_MAP.put(itemMeta.getKey(), itemMeta); + } + } catch (Exception e) { + LOGGER.error("Failed to load configuration meta file", e); + } + } + + private static ConfigurationItemMeta mapToConfigItemMeta(Map configItemMap) { + String key = (String) configItemMap.get(META_KEY_KEY); + String desc = (String) configItemMap.get(META_DESC_KEY); + Object defaultValue = configItemMap.get(META_DEFAULT_VALUE_KEY); + Boolean isEncrypt = (Boolean) configItemMap.get(META_ENCRYPT_KEY); + return new ConfigurationItemMeta(key, desc, defaultValue, isEncrypt); + } + /** + * process configuration items map (fill description and default value ,or encrypt sensitive data). + */ + public static Map processConfigMap(Map configMap) { + return configMap.entrySet().stream() + .map(entry -> { + String key = entry.getKey(); + Object value = entry.getValue(); + return new HashMap.SimpleEntry<>(key, processConfigItem(key, value)); + }) + .collect(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), HashMap::putAll); + } + + /** + * Process ConfigurationItem + */ + public static ConfigurationItem processConfigItem(String key, Object value) { + ConfigurationItemMeta meta = CONFIGURATION_ITEMS_META_MAP.get(key); + ConfigurationItem item = new ConfigurationItem(); + item.setKey(key); + item.setDescription(meta == null ? NOT_SEATA_CONFIG : meta.getDescription()); + item.setDefaultValue(meta == null ? null : meta.getDefaultValue()); + if (meta != null && meta.getEncrypt()) { + item.setValue(ENCRYPT_STRING); + } else { + item.setValue(value); + } + return item; + } +} diff --git a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java index 816f6e3e250..888e9d667e7 100644 --- a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java +++ b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java @@ -16,40 +16,66 @@ */ package org.apache.seata.server.controller; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.Properties; import java.util.Set; + import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.AsyncContext; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import com.alipay.sofa.jraft.RouteTable; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.rpc.InvokeContext; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.metadata.MetadataResponse; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.result.Result; import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigType; import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.processor.ConfigProcessor; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerFactory; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.server.cluster.manager.ClusterConfigWatcherManager; import org.apache.seata.server.cluster.manager.ClusterWatcherManager; +import org.apache.seata.server.cluster.raft.RaftConfigServer; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.cluster.raft.RaftServer; import org.apache.seata.server.cluster.raft.RaftServerManager; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.apache.seata.server.cluster.watch.ConfigWatcher; import org.apache.seata.server.cluster.watch.Watcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.ApplicationContext; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import static org.apache.seata.common.ConfigurationKeys.SEATA_FILE_PREFIX_ROOT_CONFIG; import static org.apache.seata.common.ConfigurationKeys.STORE_MODE; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** @@ -63,14 +89,33 @@ public class ClusterController { @Resource private ClusterWatcherManager clusterWatcherManager; + @Resource + private ClusterConfigWatcherManager clusterConfigWatcherManager; + private ServerProperties serverProperties; + private ConfigStoreManager configStoreManager; @Resource ApplicationContext applicationContext; + private static final LinkedHashMap SUFFIX_MAP = new LinkedHashMap(8) { + { + put("txt", "properties"); + put("text", "properties"); + put("properties", "properties"); + put("yml", "yaml"); + put("yaml", "yaml"); + } + }; @PostConstruct private void init() { this.serverProperties = applicationContext.getBean(ServerProperties.class); + // only initialize configStoreManager in raft configuration. + String configType = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + ConfigurationKeys.FILE_ROOT_TYPE); + if (ConfigType.Raft.name().equalsIgnoreCase(configType)) { + configStoreManager = ConfigStoreManagerFactory.getInstance(); + } } @PostMapping("/changeCluster") @@ -89,6 +134,21 @@ public Result changeCluster(@RequestParam String raftClusterStr) { return result; } + @PostMapping("/changeConfigCluster") + public Result changeConfigCluster(@RequestParam String raftClusterStr) { + Result result = new Result<>(); + final Configuration newConf = new Configuration(); + if (!newConf.parse(raftClusterStr)) { + result.setMessage("fail to parse initConf:" + raftClusterStr); + } else { + String group = RaftConfigServerManager.getGroup(); + RaftConfigServerManager.getCliServiceInstance().changePeers(group, + RouteTable.getInstance().getConfiguration(group), newConf); + RouteTable.getInstance().updateConfiguration(group, newConf); + } + return result; + } + @GetMapping("/cluster") public MetadataResponse cluster(String group) { MetadataResponse metadataResponse = new MetadataResponse(); @@ -122,6 +182,187 @@ public MetadataResponse cluster(String group) { return metadataResponse; } + @GetMapping("/config/cluster") + public MetadataResponse configCluster() { + MetadataResponse metadataResponse = new MetadataResponse(); + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + if (raftServer != null) { + String configType = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + ConfigurationKeys.FILE_ROOT_TYPE); + metadataResponse.setConfigMode(configType); + RouteTable routeTable = RouteTable.getInstance(); + try { + routeTable.refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); + PeerId leader = routeTable.selectLeader(RAFT_CONFIG_GROUP); + if (leader != null) { + Set nodes = new HashSet<>(); + RaftClusterMetadata raftClusterMetadata = raftServer.getRaftStateMachine().getRaftLeaderMetadata(); + Node leaderNode = raftServer.getRaftStateMachine().getRaftLeaderMetadata().getLeader(); + leaderNode.setGroup(RAFT_CONFIG_GROUP); + nodes.add(leaderNode); + nodes.addAll(raftClusterMetadata.getLearner()); + nodes.addAll(raftClusterMetadata.getFollowers()); + metadataResponse.setTerm(raftClusterMetadata.getTerm()); + metadataResponse.setNodes(new ArrayList<>(nodes)); + } + } catch (Exception e) { + LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); + } + } + return metadataResponse; + } + + @GetMapping("/config/get") + public ConfigOperationResponse getConfig(String namespace, String dataId, String key) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + checkParam(key, "key"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildGetRequest(namespace, dataId, key); + return executeConfigOperationRequest(request); + } + + @PostMapping("/config/put") + public ConfigOperationResponse putConfig(String namespace, String dataId, String key, String value) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + checkParam(key, "key"); + checkParam(value, "value"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildPutRequest(namespace, dataId, key, value); + return executeConfigOperationRequest(request); + } + + @DeleteMapping("/config/delete") + public ConfigOperationResponse deleteConfig(String namespace, String dataId, String key) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + checkParam(key, "key"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildDeleteRequest(namespace, dataId, key); + return executeConfigOperationRequest(request); + } + + @DeleteMapping("/config/deleteAll") + public ConfigOperationResponse deleteAllConfig(String namespace, String dataId) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildDeleteAllRequest(namespace, dataId); + return executeConfigOperationRequest(request); + } + + @GetMapping("/config/getAll") + public ConfigOperationResponse getAllConfig(String namespace, String dataId) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildGetAllRequest(namespace, dataId); + return executeConfigOperationRequest(request); + } + + @PostMapping("/config/upload") + public ConfigOperationResponse uploadConfig(@RequestParam("namespace") String namespace, @RequestParam("dataId") String dataId, @RequestParam("file") MultipartFile file) { + try { + checkParam(namespace, "namespace"); + checkParam(dataId, "dataId"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + if (file == null || file.isEmpty()) { + return ConfigOperationResponse.fail("The configuration file cannot be empty!"); + } + String fileName = file.getOriginalFilename(); + String dataType = SUFFIX_MAP.get(getFileType(fileName)); + if (StringUtils.isEmpty(dataType)) { + return ConfigOperationResponse.fail("The configuration file type is not supported!"); + } + StringBuilder sb = new StringBuilder(); + Map configMap = new HashMap<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append('\n'); + } + Properties properties = ConfigProcessor.processConfig(sb.toString(), dataType); + for (String key : properties.stringPropertyNames()) { + String value = properties.getProperty(key); + // remove 'seata.' prefix compatible with the config under Spring Boot + if (key.startsWith(SEATA_FILE_PREFIX_ROOT_CONFIG)) { + key = key.substring(SEATA_FILE_PREFIX_ROOT_CONFIG.length()); + } + configMap.put(key, value); + } + } catch (IOException e) { + LOGGER.error("Failed to read config file: {}", e.getMessage()); + return ConfigOperationResponse.fail("Failed to read config file"); + } + ConfigOperationRequest request = ConfigOperationRequest.buildUploadRequest(namespace, dataId, configMap); + return executeConfigOperationRequest(request); + } + + private static String getFileType(String fileName) { + if (StringUtils.isEmpty(fileName)) { + return null; + } + return fileName.substring(fileName.lastIndexOf(".") + 1); + } + + @GetMapping("/config/getNamespaces") + public ConfigOperationResponse getNamespaces() { + ConfigOperationRequest request = ConfigOperationRequest.buildGetNamespaces(); + return executeConfigOperationRequest(request); + } + + @GetMapping("/config/getDataIds") + public ConfigOperationResponse getDataIds(String namespace) { + try { + checkParam(namespace, "namespace"); + } catch (IllegalArgumentException e) { + return ConfigOperationResponse.fail(e.getMessage()); + } + ConfigOperationRequest request = ConfigOperationRequest.buildGetDataIds(namespace); + return executeConfigOperationRequest(request); + } + + private ConfigOperationResponse executeConfigOperationRequest(ConfigOperationRequest request) { + PeerId leader = RaftConfigServerManager.getLeader(); + if (leader == null) { + return ConfigOperationResponse.fail("failed to get leader"); + } + InvokeContext invokeContext = new InvokeContext(); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + try { + return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); + } catch (Exception e) { + LOGGER.error("Failed to execute request: {}", request.toString()); + return ConfigOperationResponse.fail(e.getMessage()); + } + } + + private void checkParam(final String param, final String key) { + if (StringUtils.isEmpty(param)) { + throw new IllegalArgumentException("Param '" + key + "' is required."); + } + } + @PostMapping("/watch") public void watch(HttpServletRequest request, @RequestParam Map groupTerms, @RequestParam(defaultValue = "28000") int timeout) { @@ -134,4 +375,22 @@ public void watch(HttpServletRequest request, @RequestParam Map }); } + @PostMapping("/config/watch") + public void configWatch(HttpServletRequest request, @RequestParam String namespace, @RequestParam String dataId, @RequestParam(required = false) Long version, + @RequestParam(defaultValue = "28000") int timeout) { + Long currentVersion = configStoreManager.getConfigVersion(namespace, dataId); + // if the config version of client is lower than the server, return directly + if (version == null || (currentVersion != null && version < currentVersion)) { + AsyncContext context = request.startAsync(); + HttpServletResponse httpServletResponse = (HttpServletResponse) context.getResponse(); + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + context.complete(); + return; + } + AsyncContext context = request.startAsync(); + context.setTimeout(0L); + ConfigWatcher configWatcher = new ConfigWatcher<>(namespace, dataId, context, timeout); + clusterConfigWatcherManager.registryWatcher(configWatcher); + } + } diff --git a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java index 974416b26c5..70b8daca678 100644 --- a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java +++ b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java @@ -21,6 +21,7 @@ import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.config.FileConfiguration; import org.apache.seata.config.file.FileConfig; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.store.StoreConfig; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -74,6 +75,8 @@ public void initialize(ConfigurableApplicationContext applicationContext) { environment.getPropertySources().addLast(new PropertiesPropertySource("seataOldConfig", properties)); } // Load by priority + RaftConfigServerManager.init(); + RaftConfigServerManager.start(); System.setProperty("sessionMode", StoreConfig.getSessionMode().getName()); System.setProperty("lockMode", StoreConfig.getLockMode().getName()); } diff --git a/server/src/main/resources/configuration-meta.yml b/server/src/main/resources/configuration-meta.yml new file mode 100644 index 00000000000..c3cbe593e77 --- /dev/null +++ b/server/src/main/resources/configuration-meta.yml @@ -0,0 +1,625 @@ +# +# 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. +# + +configuration-meta: + # common configuration + - key: 'transport.type' + desc: 'socket 通信方式' + defaultValue: TCP + isEncrypt: false + + - key: 'transport.server' + desc: 'socket 通道类型' + defaultValue: null + isEncrypt: false + + - key: 'transport.threadFactory.bossThreadSize' + desc: 'Netty 通信模型 Boss group 线程数' + defaultValue: 1 + isEncrypt: false + + - key: 'transport.threadFactory.workerThreadSize' + desc: 'Netty 通信模型 Worker group 线程数,线程的默认工作模式有 4 种: Auto(2*CPU 核数 + 1)、Pin(CPU 核数)、BusyPin(CPU 核数 + 1)、Default(2*CPU 核数)' + defaultValue: Default + isEncrypt: false + + - key: 'transport.shutdown.wait' + desc: '服务端 Netty 线程池关闭前等待服务下线时间' + defaultValue: 3 + isEncrypt: false + + - key: 'transport.serialization' + desc: 'client 和 server 通信编解码方式' + defaultValue: seata + isEncrypt: false + + - key: 'transport.compressor' + desc: 'client 和 server 通信数据压缩方式,支持 none、gzip、zip、sevenz、bzip2、lz4、deflater、zstd' + defaultValue: none + isEncrypt: false + + - key: 'transport.heartbeat' + desc: 'client 和 server 通信心跳检测开关' + defaultValue: true + isEncrypt: false + + - key: 'registry.type' + desc: '注册中心类型,支持 file 、nacos 、redis、eureka、zk、consul、etcd3、sofa、custom' + defaultValue: file + isEncrypt: false + + - key: 'config.type' + desc: '配置中心类型,支持 file、nacos 、apollo、zk、consul、etcd3、springcloud、custom' + defaultValue: file + isEncrypt: false + + # server configuration + - key: 'transport.enableTcServerBatchSendResponse' + desc: 'TC 批量发送回复消息开关' + defaultValue: false + isEncrypt: false + + - key: 'transport.rpcTcRequestTimeout' + desc: 'TC 二阶段下发请求超时时间' + defaultValue: 30 + isEncrypt: false + + - key: 'server.undo.logSaveDays' + desc: 'undo 保留天数,log_status=1 和未正常清理的 undo' + defaultValue: 7 + isEncrypt: false + + - key: 'server.undo.logDeletePeriod' + desc: 'undo 清理线程间隔时间' + defaultValue: 86400000 + isEncrypt: false + + - key: 'server.maxCommitRetryTimeout' + desc: '二阶段提交重试超时时长,单位 ms,s,m,h,d,默认值为-1 表示无限重试' + defaultValue: -1 + isEncrypt: false + + - key: 'server.maxRollbackRetryTimeout' + desc: '二阶段回滚重试超时时长,同 commit' + defaultValue: -1 + isEncrypt: false + + - key: 'server.recovery.committingRetryPeriod' + desc: '二阶段提交未完成状态全局事务重试提交线程间隔时间' + defaultValue: 1000 + isEncrypt: false + + - key: 'server.recovery.asynCommittingRetryPeriod' + desc: '二阶段异步提交状态重试提交线程间隔时间' + defaultValue: 1000 + isEncrypt: false + + - key: 'server.recovery.rollbackingRetryPeriod' + desc: '二阶段回滚状态重试回滚线程间隔时间' + defaultValue: 1000 + isEncrypt: false + + - key: 'server.recovery.timeoutRetryPeriod' + desc: '超时状态检测重试线程间隔时间,检测出超时将全局事务置入回滚会话管理器' + defaultValue: 1000 + isEncrypt: false + + - key: 'server.rollbackRetryTimeoutUnlockEnable' + desc: '二阶段回滚超时后是否释放锁' + defaultValue: false + isEncrypt: false + + - key: 'server.distributedLockExpireTime' + desc: 'Server 端事务管理全局锁超时时间' + defaultValue: 10000 + isEncrypt: false + + - key: 'server.server.xaerNotaRetryTimeout' + desc: '防止 XA 分支事务悬挂的重试超时时间' + defaultValue: 60000 + isEncrypt: false + + - key: 'server.session.branchAsyncQueueSize' + desc: '分支事务 Session 异步删除线程池队列大小' + defaultValue: 5000 + isEncrypt: false + + - key: 'server.session.enableBranchAsyncRemove' + desc: '分支事务 Session 异步删除开关' + defaultValue: false + isEncrypt: false + + - key: 'server.enableParallelRequestHandle' + desc: '对于批量请求消息的并行处理开关' + defaultValue: true + isEncrypt: false + + - key: 'server.enableParallelHandleBranch' + desc: '二阶段并行下发开关' + defaultValue: false + isEncrypt: false + + - key: 'server.applicationDataLimitCheck' + desc: '是否开启应用数据大小检查' + defaultValue: false + isEncrypt: false + + - key: 'server.applicationDataLimit' + desc: '应用数据大小限制' + defaultValue: 64000 + isEncrypt: false + + - key: 'server.raft.group' + desc: 'raft 存储模式下的 group,client 的事务分组值要与之对应' + defaultValue: default + isEncrypt: false + + - key: 'server.raft.server-addr' + desc: 'raft 集群列表如 192.168.0.111:9091,192.168.0.112:9091' + defaultValue: null + isEncrypt: false + + - key: 'server.raft.snapshot-interval' + desc: '间隔多久做一次内存快照,暂停状态机,但能提高停机恢复速度' + defaultValue: 600 + isEncrypt: false + + - key: 'server.raft.apply-batch' + desc: '任务累积批次后提交至 leader' + defaultValue: 32 + isEncrypt: false + + - key: 'server.raft.max-append-bufferSize' + desc: 'raft 日志存储缓冲区最大大小' + defaultValue: 256K + isEncrypt: false + + - key: 'server.raft.max-replicator-inflight-msgs' + desc: '启用 pipeline 请求情况下,最大 in-flight 请求数' + defaultValue: 256 + isEncrypt: false + + - key: 'server.raft.disruptor-buffer-size' + desc: '内部 disruptor buffer 大小,适当调高该值适应写入吞吐量高场景' + defaultValue: 16384 + isEncrypt: false + + - key: 'server.raft.election-timeout-ms' + desc: '超过多久没有 leader 心跳开始重选举' + defaultValue: 1000 + isEncrypt: false + + - key: 'server.raft.reporter-enabled' + desc: 'raft 自身的监控是否开启' + defaultValue: false + isEncrypt: false + + - key: 'server.raft.reporter-initial-delay' + desc: '监控输出间隔' + defaultValue: 60 + isEncrypt: false + + - key: 'server.raft.serialization' + desc: '序列化方式,仅支持 jackson' + defaultValue: jackson + isEncrypt: false + + - key: 'server.raft.compressor' + desc: 'raftlog 和 snapshot 的压缩方式,支持 gzip, zstd, lz4' + defaultValue: none + isEncrypt: false + + - key: 'server.raft.sync' + desc: 'raftlog 同步刷盘' + defaultValue: true + isEncrypt: false + + - key: 'store.mode' + desc: '事务会话信息存储方式' + defaultValue: file + isEncrypt: false + + - key: 'store.lock.mode' + desc: '事务锁信息存储方式' + defaultValue: file + isEncrypt: false + + - key: 'store.session.mode' + desc: '事务回话信息存储方式' + defaultValue: file + isEncrypt: false + + - key: 'store.publicKey' + desc: 'db 或 redis 存储密码解密公钥' + defaultValue: null + isEncrypt: true + + - key: 'store.file.dir' + desc: 'file 模式文件存储文件夹名' + defaultValue: sessionStore + isEncrypt: false + + - key: 'store.file.maxBranchSessionSize' + desc: 'file 模式文件存储分支 session 最大字节数' + defaultValue: 16384(16kb) + isEncrypt: false + + - key: 'store.file.maxGlobalSessionSize' + desc: 'file 模式文件存储全局 session 最大字节数' + defaultValue: 512b + isEncrypt: false + + - key: 'store.file.fileWriteBufferCacheSize' + desc: 'file 模式文件存储 buffer 最大缓存大小' + defaultValue: 16384(16kb) + isEncrypt: false + + - key: 'store.file.flushDiskMode' + desc: 'file 模式文件存储刷盘策略' + defaultValue: async + isEncrypt: false + + - key: 'store.file.sessionReloadReadSize' + desc: 'file 模式文件存储 Server 节点重启后从备份文件中恢复的 session 或 lock key 上限个数' + defaultValue: 100 + isEncrypt: false + + - key: 'store.db.datasource' + desc: 'db 模式数据源类型' + defaultValue: '' + isEncrypt: false + + - key: 'store.db.dbType' + desc: 'db 模式数据库类型' + defaultValue: '' + isEncrypt: false + + - key: 'store.db.driverClassName' + desc: 'db 模式数据库驱动' + defaultValue: '' + isEncrypt: false + + - key: 'store.db.url' + desc: 'db 模式数据库 url' + defaultValue: '' + isEncrypt: false + + - key: 'store.db.user' + desc: 'db 模式数据库账户' + defaultValue: '' + isEncrypt: false + + - key: 'store.db.password' + desc: 'db 模式数据库账户密码' + defaultValue: '' + isEncrypt: true + + - key: 'store.db.minConn' + desc: 'db 模式数据库初始连接数' + defaultValue: 1 + isEncrypt: false + + - key: 'store.db.maxConn' + desc: 'db 模式数据库最大连接数' + defaultValue: 20 + isEncrypt: false + + - key: 'store.db.maxWait' + desc: 'db 模式获取连接时最大等待时间' + defaultValue: 5000 + isEncrypt: false + + - key: 'store.db.globalTable' + desc: 'db 模式全局事务表名' + defaultValue: 'global_table' + isEncrypt: false + + - key: 'store.db.branchTable' + desc: 'db 模式分支事务表名' + defaultValue: 'branch_table' + isEncrypt: false + + - key: 'store.db.lockTable' + desc: 'db 模式全局锁表名' + defaultValue: 'lock_table' + isEncrypt: false + + - key: 'store.db.queryLimit' + desc: 'db 模式查询全局事务一次的最大条数' + defaultValue: 100 + isEncrypt: false + + - key: 'store.db.distributedLockTable' + desc: 'db 模式 Sever 端事务管理全局锁存储表名' + defaultValue: 'distributed_lock' + isEncrypt: false + + - key: 'store.redis.mode' + desc: 'redis 模式' + defaultValue: single + isEncrypt: false + + - key: 'store.redis.single.host' + desc: '单机模式下 redis 的 host' + defaultValue: '' + isEncrypt: false + + - key: 'store.redis.single.port' + desc: '单机模式下 redis 的 port' + defaultValue: '' + isEncrypt: false + + - key: 'store.redis.sentinel.masterName' + desc: 'sentinel 模式下 redis 的主库名称' + defaultValue: '' + isEncrypt: false + + - key: 'store.redis.sentinel.sentinelHosts' + desc: 'sentinel 模式下 sentinel 的 hosts' + defaultValue: '' + isEncrypt: false + + - key: 'store.redis.host' + desc: 'redis 模式 ip' + defaultValue: '127.0.0.1' + isEncrypt: false + + - key: 'store.redis.port' + desc: 'redis 模式端口' + defaultValue: 6379 + isEncrypt: false + + - key: 'store.redis.maxConn' + desc: 'redis 模式最大连接数' + defaultValue: 10 + isEncrypt: false + + - key: 'store.redis.minConn' + desc: 'redis 模式最小连接数' + defaultValue: 1 + isEncrypt: false + + - key: 'store.redis.database' + desc: 'redis 模式默认库' + defaultValue: 0 + isEncrypt: false + + - key: 'store.redis.password' + desc: 'redis 模式密码' + defaultValue: 'null' + isEncrypt: true + + - key: 'store.redis.queryLimit' + desc: 'redis 模式一次查询最大条数' + defaultValue: 100 + isEncrypt: false + + - key: 'store.redis.type' + desc: 'redis 模式主要使用的方式: lua, pippline' + defaultValue: 'pippline' + isEncrypt: false + + - key: 'metrics.enabled' + desc: '是否启用 Metrics' + defaultValue: false + isEncrypt: false + + - key: 'metrics.registryType' + desc: '指标注册器类型' + defaultValue: 'compact' + isEncrypt: false + + - key: 'metrics.exporterList' + desc: '指标结果 Measurement 数据输出器列表' + defaultValue: 'prometheus' + isEncrypt: false + + - key: 'metrics.exporterPrometheusPort' + desc: 'prometheus 输出器 Client 端口号' + defaultValue: 9898 + isEncrypt: false + + # client configuration + - key: 'seata.enabled' + desc: '是否开启 spring-boot 自动装配' + defaultValue: true + isEncrypt: false + + - key: 'seata.enableAutoDataSourceProxy' + desc: '是否开启数据源自动代理' + defaultValue: true + isEncrypt: false + + - key: 'seata.useJdkProxy' + desc: '是否使用 JDK 代理作为数据源自动代理的实现方式' + defaultValue: false + isEncrypt: false + + - key: 'transport.enableClientBatchSendRequest' + desc: '客户端事务消息请求是否批量合并发送' + defaultValue: true + isEncrypt: false + + - key: 'transport.enableTmClientChannelCheckFailFast' + desc: '客户端 TM 快速失败检查' + defaultValue: true + isEncrypt: false + + - key: 'transport.enableRmClientChannelCheckFailFast' + desc: '客户端 RM 快速失败检查' + defaultValue: true + isEncrypt: false + + - key: 'client.log.exceptionRate' + desc: '日志异常输出概率' + defaultValue: 100 + isEncrypt: false + + - key: 'service.vgroupMapping.my_test_tx_group' + desc: '事务群组(附录 1)' + defaultValue: '' + isEncrypt: false + + - key: 'service.default.grouplist' + desc: 'TC 服务列表(附录 2)' + defaultValue: '' + isEncrypt: false + + - key: 'service.disableGlobalTransaction' + desc: '全局事务开关' + defaultValue: false + isEncrypt: false + + - key: 'client.tm.degradeCheck' + desc: '降级开关' + defaultValue: false + isEncrypt: false + + - key: 'client.tm.degradeCheckAllowTimes' + desc: '升降级达标阈值' + defaultValue: 10 + isEncrypt: false + + - key: 'client.tm.degradeCheckPeriod' + desc: '服务自检周期,单位 ms,每 2 秒进行一次服务自检' + defaultValue: 2000 + isEncrypt: false + + - key: 'client.rm.reportSuccessEnable' + desc: '是否上报一阶段成功' + defaultValue: false + isEncrypt: false + + - key: 'client.rm.asyncCommitBufferLimit' + desc: '异步提交缓存队列长度' + defaultValue: 10000 + isEncrypt: false + + - key: 'client.rm.lock.retryInterval' + desc: '校验或占用全局锁重试间隔,单位毫秒' + defaultValue: 10 + isEncrypt: false + + - key: 'client.rm.lock.retryTimes' + desc: '校验或占用全局锁重试次数' + defaultValue: 30 + isEncrypt: false + + - key: 'client.rm.lock.retryPolicyBranchRollbackOnConflict' + desc: '分支事务与其它全局回滚事务冲突时锁策略' + defaultValue: true + isEncrypt: false + + - key: 'client.rm.reportRetryCount' + desc: '一阶段结果上报 TC 重试次数' + defaultValue: 5 + isEncrypt: false + + - key: 'client.rm.tableMetaCheckEnable' + desc: '自动刷新缓存中的表结构' + defaultValue: false + isEncrypt: false + + - key: 'client.rm.tableMetaCheckerInterval' + desc: '定时刷新缓存中表结构间隔时间,单位秒' + defaultValue: 60 + isEncrypt: false + + - key: 'client.rm.sagaBranchRegisterEnable' + desc: '是否开启 saga 分支注册' + defaultValue: false + isEncrypt: false + + - key: 'client.rm.sagaJsonParser' + desc: 'saga 模式中数据序列化方式' + defaultValue: fastjson + isEncrypt: false + + - key: 'client.rm.tccActionInterceptorOrder' + desc: 'tcc 拦截器顺序' + defaultValue: 'Ordered.HIGHEST_PRECEDENCE + 1000' + isEncrypt: false + + - key: 'client.rm.applicationDataLimitCheck' + desc: '客户端应用数据是否开启限制' + defaultValue: false + isEncrypt: false + + - key: 'client.rm.applicationDataLimit' + desc: '客户端应用数据上报限制' + defaultValue: 64000 + isEncrypt: false + + - key: 'client.tm.commitRetryCount' + desc: '一阶段全局提交结果上报 TC 重试次数' + defaultValue: 1 + isEncrypt: false + + - key: 'client.tm.rollbackRetryCount' + desc: '一阶段全局回滚结果上报 TC 重试次数' + defaultValue: 1 + isEncrypt: false + + - key: 'client.tm.defaultGlobalTransactionTimeout' + desc: '全局事务超时时间' + defaultValue: 60 + isEncrypt: false + + - key: 'client.tm.interceptorOrder' + desc: 'TM 全局事务拦截器顺序' + defaultValue: 'Ordered.HIGHEST_PRECEDENCE + 1000' + isEncrypt: false + + - key: 'client.undo.dataValidation' + desc: '二阶段回滚镜像校验' + defaultValue: true + isEncrypt: false + + - key: 'client.undo.logSerialization' + desc: 'undo 序列化方式' + defaultValue: jackson + isEncrypt: false + + - key: 'client.undo.logTable' + desc: '自定义 undo 表名' + defaultValue: undo_log + isEncrypt: false + + - key: 'client.undo.onlyCareUpdateColumns' + desc: '只生成被更新列的镜像' + defaultValue: true + isEncrypt: false + + - key: 'client.undo.compress.enable' + desc: 'undo log 压缩开关' + defaultValue: true + isEncrypt: false + + - key: 'client.undo.compress.type' + desc: 'undo log 压缩算法' + defaultValue: zip + isEncrypt: false + + - key: 'client.undo.compress.threshold' + desc: 'undo log 压缩阈值' + defaultValue: '64k' + isEncrypt: false + + - key: 'client.rm.sqlParserType' + desc: 'sql 解析类型' + defaultValue: druid + isEncrypt: false diff --git a/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java b/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java new file mode 100644 index 00000000000..589f06ac49f --- /dev/null +++ b/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java @@ -0,0 +1,55 @@ +/* + * 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 org.apache.seata.server.config; + + +import java.util.HashMap; +import java.util.Map; + +import org.apache.seata.config.dto.ConfigurationItem; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class ConfigurationProcessorTest { + + @Test + void processConfigMap() { + String key1 = "transport.type"; + String value1 = "TCP"; + String key2 = "UNKNOWN"; + String value2 = "UNKNOWN"; + HashMap configMap = new HashMap<>(); + configMap.put(key1, value1); + configMap.put(key2, value2); + + Map itemMap = ConfigurationProcessor.processConfigMap(configMap); + + ConfigurationItem item1 = itemMap.get(key1); + ConfigurationItem item2 = itemMap.get(key2); + Assertions.assertEquals(2, itemMap.size()); + Assertions.assertEquals(key1, item1.getKey()); + Assertions.assertEquals(value1, item1.getValue()); + Assertions.assertNotNull(item1.getDefaultValue()); + Assertions.assertNotNull(item1.getDescription()); + + Assertions.assertEquals(key2, item2.getKey()); + Assertions.assertEquals(value2, item2.getValue()); + Assertions.assertNull(item2.getDefaultValue()); + Assertions.assertNotNull(item2.getDescription()); + + } +} diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java index a85de536066..a2ef8c3650e 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java @@ -23,10 +23,7 @@ import org.apache.seata.server.lock.LockerManagerFactory; import org.apache.seata.server.session.SessionHolder; import org.apache.seata.server.store.StoreConfig; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @@ -38,6 +35,11 @@ public static void setUp(ApplicationContext context) { LockerManagerFactory.destroy(); SessionHolder.destroy(); } + @BeforeEach + public void init() { + System.setProperty("server.raftPort", "0"); + System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR, ""); + } @AfterEach public void destroy() { diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java index e6f9c8f905d..33d4334d2d4 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java @@ -29,15 +29,18 @@ import org.apache.seata.common.metadata.Node; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchType; +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; +import org.apache.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; import org.apache.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; -import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; -import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; -import org.apache.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; import org.apache.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; import org.apache.seata.server.session.GlobalSession; @@ -219,4 +222,34 @@ public void testRaftClusterMetadataSerialize() throws IOException { Assertions.assertEquals(ClusterRole.LEARNER,learner1.getRole()); } + @Test + public void testConfigSnapshotSerialize() throws IOException{ + Map configMap = new HashMap<>(); + configMap.put("config.type","file"); + configMap.put("store","file"); + + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(configMap); + raftSnapshot.setType(RaftSnapshot.SnapshotType.config); + byte[] msg = RaftSnapshotSerializer.encode(raftSnapshot); + RaftSnapshot raftSnapshot1 = RaftSnapshotSerializer.decode(msg); + HashMap configMap1 = (HashMap) raftSnapshot1.getBody(); + Assertions.assertEquals(configMap,configMap1); + } + + @Test + public void testConfigMsgSerialize() throws IOException{ + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + ConfigOperationDTO configOperationDTO = new ConfigOperationDTO(ConfigOperationType.PUT, "namespace", "dataId", "key", "value"); + RaftConfigOperationSyncMsg configSyncMsg = new RaftConfigOperationSyncMsg(configOperationDTO); + raftSyncMessage.setBody(configSyncMsg); + byte[] msg = RaftSyncMessageSerializer.encode(raftSyncMessage); + RaftSyncMessage raftSyncMessage1 = RaftSyncMessageSerializer.decode(msg); + Assertions.assertEquals(configSyncMsg.getMsgType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getMsgType()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getKey(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getKey()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getValue(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getValue()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getNamespace(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getNamespace()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getDataId(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getDataId()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getOptType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getOptType()); + } } diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java new file mode 100644 index 00000000000..0a7724ce165 --- /dev/null +++ b/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java @@ -0,0 +1,81 @@ +/* + * 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 org.apache.seata.server.raft.execute; + +import javax.annotation.Resource; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.config.ConfigurationCache; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.controller.ClusterController; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContext; + +@SpringBootTest +class ConfigOperationExecuteTest { + @Resource + private ClusterController clusterController; + + private static final String NAMESPACE = "test"; + private static final String DATA_ID = "test"; + + @BeforeAll + public static void setUp(ApplicationContext context) { + RaftConfigServerManager.destroy(); + System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR, NetUtil.getLocalIp() + ":9191"); + System.setProperty("config.type", "raft"); + System.setProperty("registry.preferredNetworks", "*"); + System.setProperty("config.raft.db.type", "rocksdb"); + System.setProperty("config.raft.db.dir", "configStore"); + System.setProperty("config.raft.db.destroyOnShutdown", "true"); + RaftConfigServerManager.init(); + RaftConfigServerManager.start(); + } + + @AfterAll + public static void destroy() { + RaftConfigServerManager.destroy(); + ConfigurationCache.clear(); + System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR,""); + Assertions.assertNull(RaftConfigServerManager.getRaftServer()); + System.clearProperty("config.type"); + System.clearProperty("registry.preferredNetworks"); + System.clearProperty("config.raft.db.dir"); + } + + @Test + public void testCRUD() { + clusterController.deleteAllConfig(NAMESPACE, DATA_ID); + String key1 = "aaa"; + String value1 = "bbb"; + String key2 = "ccc"; + String value2 = "ddd"; + Assertions.assertTrue(clusterController.getConfig(NAMESPACE, DATA_ID, key1).isSuccess()); + Assertions.assertTrue(clusterController.getAllConfig(NAMESPACE, DATA_ID).isSuccess()); + Assertions.assertTrue(clusterController.putConfig(NAMESPACE, DATA_ID, key1, value1).isSuccess()); + Assertions.assertTrue(clusterController.putConfig(NAMESPACE, DATA_ID, key2, value2).isSuccess()); + Assertions.assertTrue(clusterController.deleteConfig(NAMESPACE, DATA_ID, key1).isSuccess()); + Assertions.assertTrue(clusterController.deleteAllConfig(NAMESPACE, DATA_ID).isSuccess()); + Assertions.assertTrue(clusterController.getNamespaces().isSuccess()); + Assertions.assertTrue(clusterController.getDataIds(NAMESPACE).isSuccess()); + } +} From 3de2382e8d80dd0b51adf6ac2b687a4b42b01b03 Mon Sep 17 00:00:00 2001 From: xingfudeshi Date: Sun, 27 Oct 2024 12:59:17 +0800 Subject: [PATCH 32/54] Revert "feature: support raft configuration center (#6830)" (#6953) --- .gitignore | 4 - all/pom.xml | 5 - build/pom.xml | 1 - .../seata/common/ConfigurationKeys.java | 26 - .../org/apache/seata/common/Constants.java | 34 - .../apache/seata/common/DefaultValues.java | 4 - .../common/config/ConfigDataResponse.java | 59 -- .../common/metadata/MetadataResponse.java | 11 +- .../apache/seata/common/store/StoreMode.java | 5 - .../apache/seata/common/util/NumberUtils.java | 18 - .../seata/common/util/NumberUtilsTest.java | 8 - config/pom.xml | 1 - config/seata-config-all/pom.xml | 5 - config/seata-config-core/pom.xml | 5 - .../org/apache/seata/config/ConfigType.java | 4 - .../config/ConfigurationChangeEvent.java | 4 - .../config/dto/ConfigurationInfoDto.java | 44 -- .../seata/config/dto/ConfigurationItem.java | 65 -- .../config/dto/ConfigurationItemMeta.java | 54 -- .../config/store/ConfigStoreManager.java | 96 --- .../store/ConfigStoreManagerFactory.java | 49 -- .../store/ConfigStoreManagerProvider.java | 28 - .../rocksdb/RocksDBConfigStoreManager.java | 691 ----------------- .../RocksDBConfigStoreManagerProvider.java | 30 - .../config/store/rocksdb/RocksDBFactory.java | 97 --- .../store/rocksdb/RocksDBOptionsFactory.java | 128 ---- ...ta.config.store.ConfigStoreManagerProvider | 17 - .../config/store/rocksdb/RocksDBTest.java | 190 ----- .../src/test/resources/registry.conf | 10 - config/seata-config-raft/pom.xml | 44 -- .../config/raft/RaftConfigurationClient.java | 694 ------------------ .../raft/RaftConfigurationProvider.java | 38 - .../config/raft/RaftConfigurationServer.java | 206 ------ ....apache.seata.config.ConfigurationProvider | 17 - ....apache.seata.config.ConfigurationProvider | 17 - .../src/test/resources/registry.conf | 101 --- .../resources/static/console-fe/package.json | 1 - .../resources/static/console-fe/src/app.tsx | 7 +- .../static/console-fe/src/locales/en-us.ts | 25 - .../static/console-fe/src/locales/zh-cn.ts | 25 - .../src/pages/ConfigInfo/ConfigInfo.tsx | 644 ---------------- .../src/pages/ConfigInfo/index.scss | 16 - .../console-fe/src/pages/ConfigInfo/index.ts | 21 - .../static/console-fe/src/router.tsx | 2 - .../console-fe/src/service/configInfo.ts | 80 -- .../static/console-fe/src/utils/request.ts | 47 -- dependencies/pom.xml | 7 - .../SeataCoreEnvironmentPostProcessor.java | 9 +- .../boot/autoconfigure/StarterConstants.java | 4 +- .../config/ConfigRaftProperties.java | 82 --- .../config/ConfigStoreProperties.java | 81 -- .../config/ConfigRaftPropertiesTest.java | 53 -- .../config/ConfigStorePropertiesTest.java | 52 -- .../resources/application-test.properties | 12 - .../java/org/apache/seata/server/Server.java | 2 - .../seata/server/ServerApplication.java | 4 - .../org/apache/seata/server/ServerRunner.java | 9 +- .../listener/ClusterConfigChangeEvent.java | 50 -- .../listener/ClusterConfigChangeListener.java | 26 - .../manager/ClusterConfigWatcherManager.java | 111 --- .../server/cluster/raft/RaftConfigServer.java | 119 --- .../cluster/raft/RaftConfigServerManager.java | 259 ------- .../cluster/raft/RaftConfigStateMachine.java | 455 ------------ .../cluster/raft/RaftServerManager.java | 4 +- .../config/AbstractRaftConfigMsgExecute.java | 29 - .../config/ConfigOperationExecute.java | 143 ---- .../execute/config/ConfigOperationType.java | 70 -- .../ConfigOperationRequestProcessor.java | 67 -- .../PutNodeInfoRequestProcessor.java | 23 - .../request/ConfigOperationRequest.java | 137 ---- .../response/ConfigOperationResponse.java | 78 -- .../cluster/raft/snapshot/RaftSnapshot.java | 7 +- .../snapshot/config/ConfigSnapshotFile.java | 106 --- .../ConfigLeaderMetadataSnapshotFile.java | 88 --- .../sync/msg/RaftConfigOperationSyncMsg.java | 45 -- .../raft/sync/msg/RaftSyncMsgType.java | 4 +- .../raft/sync/msg/closure/ConfigClosure.java | 63 -- .../raft/sync/msg/dto/ConfigOperationDTO.java | 98 --- .../cluster/raft/util/RaftConfigTaskUtil.java | 78 -- .../server/cluster/watch/ConfigWatcher.java | 89 --- .../server/config/ConfigurationProcessor.java | 108 --- .../server/controller/ClusterController.java | 259 ------- .../listener/SeataPropertiesLoader.java | 3 - .../src/main/resources/configuration-meta.yml | 625 ---------------- .../config/ConfigurationProcessorTest.java | 55 -- .../seata/server/raft/RaftServerTest.java | 10 +- .../server/raft/RaftSyncMessageTest.java | 39 +- .../execute/ConfigOperationExecuteTest.java | 81 -- 88 files changed, 19 insertions(+), 7303 deletions(-) delete mode 100644 common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java delete mode 100644 config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider delete mode 100644 config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java delete mode 100644 config/seata-config-raft/pom.xml delete mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java delete mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java delete mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java delete mode 100644 config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider delete mode 100644 config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider delete mode 100644 config/seata-config-raft/src/test/resources/registry.conf delete mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx delete mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss delete mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts delete mode 100644 console/src/main/resources/static/console-fe/src/service/configInfo.ts delete mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java delete mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java delete mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java delete mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java delete mode 100644 server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java delete mode 100644 server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java delete mode 100644 server/src/main/resources/configuration-meta.yml delete mode 100644 server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java delete mode 100644 server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java diff --git a/.gitignore b/.gitignore index 63a74b7da4f..308540907c1 100644 --- a/.gitignore +++ b/.gitignore @@ -46,10 +46,6 @@ dependency-reduced-pom.xml /distribution/sessionStore/ /distribution/*/sessionStore/ /file_store/ -/configStore/ -/config/configStore/ -/distribution/configStore/ -/distribution/*/configStore/ # system ignore .DS_Store diff --git a/all/pom.xml b/all/pom.xml index cad7663f923..7e4eb9ab293 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -91,11 +91,6 @@ seata-config-spring-cloud ${project.version} - - org.apache.seata - seata-config-raft - ${project.version} - org.apache.seata seata-core diff --git a/build/pom.xml b/build/pom.xml index 7ba41bb5b68..9e26a12f1a1 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -311,7 +311,6 @@ *-pom.xml **/db_store/** **/sessionStore/** - **/configStore/** **/root.data false diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index 054b131ae2f..ff8436b6dc9 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -1019,31 +1019,6 @@ public interface ConfigurationKeys { */ String ROCKET_MQ_MSG_TIMEOUT = SERVER_PREFIX + "rocketmqMsgTimeout"; - String CONFIG_STORE_PREFIX = FILE_ROOT_PREFIX_CONFIG + "raft" + FILE_CONFIG_SPLIT_CHAR + "db" + FILE_CONFIG_SPLIT_CHAR; - - /** - * The constant CONFIG_STORE_TYPE - */ - String CONFIG_STORE_TYPE = CONFIG_STORE_PREFIX + "type"; - - /** - * The constant CONFIG_STORE_DIR - */ - String CONFIG_STORE_DIR = CONFIG_STORE_PREFIX + "dir"; - - /** - * The constant CONFIG_STORE_DESTROY_ON_SHUTDOWN - */ - String CONFIG_STORE_DESTROY_ON_SHUTDOWN = CONFIG_STORE_PREFIX + "destroyOnShutdown"; - - /** - * The constant CONFIG_STORE_NAMESPACE - */ - String CONFIG_STORE_NAMESPACE = CONFIG_STORE_PREFIX + "namespace"; - /** - * The constant CONFIG_STORE_DATA_ID - */ - String CONFIG_STORE_DATA_ID = CONFIG_STORE_PREFIX + "dataId"; /** * */ @@ -1078,5 +1053,4 @@ public interface ConfigurationKeys { * The constant META_PREFIX */ String META_PREFIX = SEATA_FILE_ROOT_CONFIG + FILE_CONFIG_SPLIT_CHAR + FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + "metadata."; - } diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index c1d2489d968..43da1827e05 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -225,36 +225,6 @@ public interface Constants { */ String JACKSON_JSON_TEXT_PREFIX = "{\"@class\":"; - /** - * The constant APPLICATION_TYPE_KEY - */ - String APPLICATION_TYPE_KEY = "application.type"; - - /** - * The constant APPLICATION_TYPE_SERVER - */ - String APPLICATION_TYPE_SERVER = "server"; - - /** - * The constant APPLICATION_TYPE_CLIENT - */ - String APPLICATION_TYPE_CLIENT = "client"; - - /** - * The constant DEFAULT_STORE_NAMESPACE in raft configuration - */ - - String DEFAULT_STORE_NAMESPACE = "default"; - /** - * The constant DEFAULT_STORE_DATA_ID in raft configuration - */ - String DEFAULT_STORE_DATA_ID = "seata.properties"; - - /** - * The constant RAFT_CONFIG_GROUP - */ - String RAFT_CONFIG_GROUP = "config"; - /** * The constant DEAD_LOCK_SQL_STATE */ @@ -265,8 +235,4 @@ public interface Constants { */ int DEAD_LOCK_ERROR_CODE = 1213; - /** - * The constant CONFIGURATION_META_FILE_NAME - */ - String CONFIGURATION_META_FILE_NAME = "configuration-meta.yml"; } diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index 2396ecd2d14..eb0d40bb308 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -315,8 +315,4 @@ public interface DefaultValues { String DRUID_LOCATION = "lib/sqlparser/druid.jar"; int DEFAULT_ROCKET_MQ_MSG_TIMEOUT = 60 * 1000; - - String DEFAULT_DB_STORE_FILE_DIR = "configStore"; - - String DEFAULT_DB_TYPE = "rocksdb"; } diff --git a/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java b/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java deleted file mode 100644 index 577e1431772..00000000000 --- a/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 org.apache.seata.common.config; - -import java.io.Serializable; - -public class ConfigDataResponse implements Serializable { - private static final long serialVersionUID = -1959848221874923781L; - private T result; - private String errMsg; - private Boolean success; - - public T getResult() { - return result; - } - - public void setResult(T result) { - this.result = result; - } - - public String getErrMsg() { - return errMsg; - } - - public void setErrMsg(String errMsg) { - this.errMsg = errMsg; - } - - public Boolean getSuccess() { - return success; - } - - public void setSuccess(Boolean success) { - this.success = success; - } - - @Override - public String toString() { - return "ConfigDataResponse{" + - "result=" + result + - ", errMsg='" + errMsg + '\'' + - ", success=" + success + - '}'; - } -} diff --git a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java index 0432cc21613..609402947fb 100644 --- a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java +++ b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java @@ -25,8 +25,6 @@ public class MetadataResponse { String storeMode; - String configMode; - long term; public List getNodes() { @@ -52,12 +50,5 @@ public long getTerm() { public void setTerm(long term) { this.term = term; } - - public String getConfigMode() { - return configMode; - } - - public void setConfigMode(String configMode) { - this.configMode = configMode; - } + } diff --git a/common/src/main/java/org/apache/seata/common/store/StoreMode.java b/common/src/main/java/org/apache/seata/common/store/StoreMode.java index 68a5092fd16..29c90d544f2 100644 --- a/common/src/main/java/org/apache/seata/common/store/StoreMode.java +++ b/common/src/main/java/org/apache/seata/common/store/StoreMode.java @@ -16,8 +16,6 @@ */ package org.apache.seata.common.store; -import org.apache.seata.common.util.StringUtils; - /** * transaction log store mode * @@ -57,9 +55,6 @@ public enum StoreMode { * @return the store mode */ public static StoreMode get(String name) { - if (StringUtils.isEmpty(name)) { - return null; - } for (StoreMode sm : StoreMode.class.getEnumConstants()) { if (sm.name.equalsIgnoreCase(name)) { return sm; diff --git a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java index fdd91addff8..7374bdc08c5 100644 --- a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java +++ b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java @@ -60,22 +60,4 @@ public static Long toLong(String str) { } return null; } - - public static byte[] longToBytes(long x) { - byte[] result = new byte[8]; - for (int i = 7; i >= 0; i--) { - result[i] = (byte)(x & 0xFF); - x >>= 8; - } - return result; - } - - public static long bytesToLong(byte[] bytes) { - long result = 0; - for (int i = 0; i < 8; i++) { - result <<= 8; - result |= bytes[i] & 0xFF; - } - return result; - } } diff --git a/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java b/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java index 1db14ee78b5..828f7c2fe26 100644 --- a/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java +++ b/common/src/test/java/org/apache/seata/common/util/NumberUtilsTest.java @@ -36,12 +36,4 @@ public void testToInReturnDefaultValueWithFormatIsInvalid() { public void testToInReturnParsedValue() { Assertions.assertEquals(10, NumberUtils.toInt("10", 9)); } - - @Test - public void testBytesAndLong() { - Long a = 123456L; - byte[] bytes = NumberUtils.longToBytes(a); - long b = NumberUtils.bytesToLong(bytes); - Assertions.assertEquals(a, b); - } } diff --git a/config/pom.xml b/config/pom.xml index a0d76aa6e9c..3ed784fb152 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -41,6 +41,5 @@ seata-config-etcd3 seata-config-consul seata-config-spring-cloud - seata-config-raft diff --git a/config/seata-config-all/pom.xml b/config/seata-config-all/pom.xml index 1818bffcd3c..0eceb6a2e82 100644 --- a/config/seata-config-all/pom.xml +++ b/config/seata-config-all/pom.xml @@ -60,11 +60,6 @@ seata-config-spring-cloud ${project.version} - - ${project.groupId} - seata-config-raft - ${project.version} - diff --git a/config/seata-config-core/pom.xml b/config/seata-config-core/pom.xml index 501276ff505..896a7070fe3 100644 --- a/config/seata-config-core/pom.xml +++ b/config/seata-config-core/pom.xml @@ -43,11 +43,6 @@ org.yaml snakeyaml - - org.rocksdb - rocksdbjni - - diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java index 441e30c65b1..869d2d19c40 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java @@ -49,10 +49,6 @@ public enum ConfigType { * spring cloud config type */ SpringCloudConfig, - /** - * Raft config type - */ - Raft, /** * Custom config type */ diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java index c3f7076aa8b..714f8b17678 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java @@ -38,10 +38,6 @@ public ConfigurationChangeEvent(String dataId, String newValue) { this(dataId, DEFAULT_NAMESPACE, null, newValue, ConfigurationChangeType.MODIFY); } - public ConfigurationChangeEvent(String namespace, String dataId, String newValue) { - this(dataId, namespace, null, newValue, ConfigurationChangeType.MODIFY); - } - public ConfigurationChangeEvent(String dataId, String namespace, String oldValue, String newValue, ConfigurationChangeType type) { this.dataId = dataId; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java deleted file mode 100644 index 6dff2018856..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationInfoDto.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 org.apache.seata.config.dto; - -import java.io.Serializable; -import java.util.Map; - -public class ConfigurationInfoDto implements Serializable { - private static final long serialVersionUID = 72337179613855724L; - - private Map config; - - private Long version; - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public Long getVersion() { - return version; - } - - public void setVersion(Long version) { - this.version = version; - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java deleted file mode 100644 index 6b47cb801e9..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItem.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 org.apache.seata.config.dto; - -import java.io.Serializable; - -/** - * The configuration items - * - */ -public class ConfigurationItem implements Serializable { - private static final long serialVersionUID = 32787493713855767L; - private String key; - private Object value; - private String description; - private Object defaultValue; - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public Object getDefaultValue() { - return defaultValue; - } - - public void setDefaultValue(Object defaultValue) { - this.defaultValue = defaultValue; - } - - -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java b/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java deleted file mode 100644 index d7c493d7879..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/dto/ConfigurationItemMeta.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 org.apache.seata.config.dto; - -import java.io.Serializable; - -/** - * The configuration items meta - * - */ -public class ConfigurationItemMeta implements Serializable { - - private static final long serialVersionUID = 8771878731395411166L; - private final String key; - private final String description; - private final Object defaultValue; - private final Boolean isEncrypt; - - public ConfigurationItemMeta(String key, String description, Object defaultValue, Boolean isEncrypt) { - this.key = key; - this.description = description; - this.defaultValue = defaultValue; - this.isEncrypt = isEncrypt; - } - - public String getKey() { - return key; - } - - public String getDescription() { - return description; - } - - public Object getDefaultValue() { - return defaultValue; - } - public Boolean getEncrypt() { - return isEncrypt; - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java deleted file mode 100644 index 9fab926a52b..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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 org.apache.seata.config.store; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.ConfigurationChangeListener; -import org.apache.seata.config.processor.ConfigDataType; -import org.apache.seata.config.processor.ConfigProcessor; - -/** - * The interface Local config store manager. - * - */ -public interface ConfigStoreManager { - String get(String namespace, String dataId, String key); - - Map getAll(String namespace, String dataId); - - Boolean put(String namespace, String dataId, String key, Object value); - - Boolean delete(String namespace, String dataId, String key); - - Boolean putAll(String namespace, String dataId, Map configMap); - - Boolean deleteAll(String namespace, String dataId); - - Boolean isEmpty(String namespace, String dataId); - - Map> getConfigMap(); - - Boolean putConfigMap(Map> configMap); - - Boolean clearData(); - - List getAllNamespaces(); - - List getAllDataIds(String namespace); - - Long getConfigVersion(String namespace, String dataId); - - Boolean putConfigVersion(String namespace, String dataId, Long version); - - Boolean deleteConfigVersion(String namespace, String dataId); - void destroy(); - void shutdown(); - - default void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; - - default void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; - - static String convertConfig2Str(Map configs) { - StringBuilder sb = new StringBuilder(); - if (CollectionUtils.isEmpty(configs)) { - sb.toString(); - } - for (Map.Entry entry : configs.entrySet()) { - sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("\n"); - } - return sb.toString(); - } - - static Map convertConfigStr2Map(String configStr) { - if (StringUtils.isEmpty(configStr)) { - return new HashMap<>(); - } - Map configs = new HashMap<>(); - try { - Properties properties = ConfigProcessor.processConfig(configStr, ConfigDataType.properties.name()); - properties.forEach((k, v) -> configs.put(k.toString(), v)); - return configs; - } catch (IOException e) { - return new HashMap<>(); - } - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java deleted file mode 100644 index 1f692893909..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 org.apache.seata.config.store; - -import java.util.Objects; - -import org.apache.seata.common.loader.EnhancedServiceLoader; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationFactory; - -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; -import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; - -public class ConfigStoreManagerFactory { - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static volatile ConfigStoreManager instance; - - public static ConfigStoreManager getInstance() { - if (instance == null) { - synchronized (ConfigStoreManagerFactory.class) { - if (instance == null) { - String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); - instance = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); - } - } - } - return instance; - } - - public static void destroy() { - if (instance != null) { - instance.shutdown(); - } - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java deleted file mode 100644 index 15aededefd5..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 org.apache.seata.config.store; - -/** - * the interface configStoreManager provider - */ -public interface ConfigStoreManagerProvider { - /** - * provide a AbstractConfigStoreManager implementation instance - * @return ConfigStoreManager - */ - ConfigStoreManager provide(); -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java deleted file mode 100644 index 70ac7ae176c..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * 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 org.apache.seata.config.store.rocksdb; - -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.stream.Collectors; - -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.NumberUtils; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationChangeEvent; -import org.apache.seata.config.ConfigurationChangeListener; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.FileConfiguration; -import org.apache.seata.config.store.ConfigStoreManager; -import org.rocksdb.ColumnFamilyDescriptor; -import org.rocksdb.ColumnFamilyHandle; -import org.rocksdb.DBOptions; -import org.rocksdb.Options; -import org.rocksdb.RocksDB; -import org.rocksdb.RocksDBException; -import org.rocksdb.RocksIterator; -import org.rocksdb.WriteBatch; -import org.rocksdb.WriteOptions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.seata.common.ConfigurationKeys.CLIENT_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; -import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_PREFIX_CONFIG; -import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_PREFIX_REGISTRY; -import static org.apache.seata.common.ConfigurationKeys.LOG_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.METRICS_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.SEATA_FILE_PREFIX_ROOT_CONFIG; -import static org.apache.seata.common.ConfigurationKeys.SERVER_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.SERVICE_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.STORE_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.TCC_PREFIX; -import static org.apache.seata.common.ConfigurationKeys.TRANSPORT_PREFIX; -import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; -import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; - -/** - * The RocksDB config store manager - * - */ -public class RocksDBConfigStoreManager implements ConfigStoreManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); - private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final String DB_PATH = RocksDBOptionsFactory.getDBPath(); - private static final String DEFAULT_NAMESPACE = DEFAULT_STORE_NAMESPACE; - private static final String DEFAULT_DATA_ID = DEFAULT_STORE_DATA_ID; - private static String CURRENT_DATA_ID; - private static String CURRENT_NAMESPACE; - private static final String NAME_KEY = "name"; - private static final String FILE_TYPE = "file"; - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static final DBOptions DB_OPTIONS = RocksDBOptionsFactory.getDBOptions(); - private static final Map LOCK_MAP = new ConcurrentHashMap<>(); - private static final int MAP_INITIAL_CAPACITY = 8; - private static final ConcurrentMap>> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>( - MAP_INITIAL_CAPACITY); - - //====================================NON COMMON FILED=================================== - private static volatile RocksDBConfigStoreManager instance; - private RocksDB rocksdb; - private final Map columnFamilyHandleMap = new ConcurrentHashMap<>(); - private static final String VERSION_COLUMN_FAMILY = "config_version"; - private static final List PREFIX_LIST = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, - STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX, LOG_PREFIX, TCC_PREFIX); - - - public static RocksDBConfigStoreManager getInstance() { - if (instance == null) { - synchronized (RocksDBConfigStoreManager.class) { - if (instance == null) { - instance = new RocksDBConfigStoreManager(); - } - } - } - return instance; - } - - public RocksDBConfigStoreManager() { - super(); - CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_NAMESPACE); - CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_DATA_ID); - openRocksDB(); - maybeNeedLoadOriginConfig(); - LOGGER.info("RocksDBConfigStoreManager initialized successfully"); - } - - private void openRocksDB() { - final List handles = new ArrayList<>(); - final List descriptors = new ArrayList<>(); - try (final Options options = new Options()) { - List cfs = RocksDB.listColumnFamilies(options, DB_PATH); - for (byte[] cf : cfs) { - String namespace = new String(cf); - descriptors.add(new ColumnFamilyDescriptor(cf, RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); - } - // create default column family and config version column family - if (CollectionUtils.isEmpty(descriptors)) { - descriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, RocksDBOptionsFactory.getColumnFamilyOptionsMap(new String(RocksDB.DEFAULT_COLUMN_FAMILY)))); - descriptors.add(new ColumnFamilyDescriptor(VERSION_COLUMN_FAMILY.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(VERSION_COLUMN_FAMILY))); - } - this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS, descriptors, handles); - for (ColumnFamilyHandle handle : handles) { - columnFamilyHandleMap.put(new String(handle.getName()), handle); - } - } catch (RocksDBException e) { - LOGGER.error("open rocksdb error", e); - } - } - - private ColumnFamilyHandle getOrCreateColumnFamilyHandle(String namespace) throws RocksDBException { - ColumnFamilyHandle handle = columnFamilyHandleMap.get(namespace); - if (handle == null) { - synchronized (RocksDBConfigStoreManager.class) { - handle = columnFamilyHandleMap.get(namespace); - if (handle == null) { - handle = rocksdb.createColumnFamily(new ColumnFamilyDescriptor( - namespace.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); - columnFamilyHandleMap.put(namespace, handle); - } - } - } - return handle; - } - - /** - * load origin config if first startup - */ - private void maybeNeedLoadOriginConfig() { - if (isEmpty(CURRENT_NAMESPACE, CURRENT_DATA_ID)) { - Map configs = new HashMap<>(); - Map seataConfigs = new HashMap<>(); - String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, - ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY); - String name = FILE_CONFIG.getConfig(pathDataId); - // create FileConfiguration for read file.conf - Optional originFileInstance = Optional.ofNullable(new FileConfiguration(name)); - originFileInstance - .ifPresent(fileConfiguration -> configs.putAll(fileConfiguration.getFileConfig().getAllConfig())); - configs.forEach((k, v) -> { - if (v instanceof String) { - if (StringUtils.isEmpty((String)v)) { - return; - } - } - // compatible with the config under Spring Boot - if (k.startsWith(SEATA_FILE_PREFIX_ROOT_CONFIG)) { - k = k.substring(SEATA_FILE_PREFIX_ROOT_CONFIG.length()); - } - // filter all seata related configs - if (PREFIX_LIST.stream().anyMatch(k::startsWith)) { - seataConfigs.put(k, v); - } - }); - putAll(CURRENT_NAMESPACE, CURRENT_DATA_ID, seataConfigs); - LOGGER.info("Load initialization configuration file sucessfully in namespace: {}, dataId: {}", CURRENT_NAMESPACE, CURRENT_DATA_ID); - } - } - - /** - * Acquire lock of the given namespace - * @param namespace - */ - private ReentrantReadWriteLock acquireLock(String namespace) { - return LOCK_MAP.computeIfAbsent(namespace, k -> new ReentrantReadWriteLock()); - } - - /** - * Get config map of the given namespace and dataId - * @param namespace - * @param dataId - * @return - * @throws RocksDBException - */ - private Map getConfigMap(String namespace, String dataId) throws RocksDBException { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.readLock().lock(); - try { - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - // the column family not exist, return empty map - if (handle == null) { - return new HashMap<>(); - } - byte[] value = rocksdb.get(handle, dataId.getBytes(DEFAULT_CHARSET)); - String configStr = value != null ? new String(value, DEFAULT_CHARSET) : null; - return ConfigStoreManager.convertConfigStr2Map(configStr); - } finally { - lock.readLock().unlock(); - } - } - - /** - * Get the config value of the given namespace and dataId - * @param namespace - * @param dataId - * @param key - * @return - */ - @Override - public String get(String namespace, String dataId, String key) { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.readLock().lock(); - try { - Map configMap = getConfigMap(namespace, dataId); - return configMap.get(key) != null ? configMap.get(key).toString() : null; - } catch (RocksDBException e) { - LOGGER.error("Failed to get value for key: " + key, e); - } finally { - lock.readLock().unlock(); - } - return null; - } - - /** - * Get all config items of the given namespace and dataId - * @param namespace - * @param dataId - * @return - */ - @Override - public Map getAll(String namespace, String dataId) { - try { - return getConfigMap(namespace, dataId); - } catch (RocksDBException e) { - LOGGER.error("Failed to get all configs", e); - } - return null; - } - - /** - * Put a config item to the given namespace and dataId - * @param namespace - * @param dataId - * @param key - * @param value - * @return - */ - @Override - public Boolean put(String namespace, String dataId, String key, Object value) { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - try { - Map configMap = getConfigMap(namespace, dataId); - configMap.put(key, value); - String configStr = ConfigStoreManager.convertConfig2Str(configMap); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - updateConfigVersion(namespace, dataId); - notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to put value for key: " + key, e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - /** - * Delete a config item with the given key from the given namespace and dataId - * @param namespace - * @param dataId - * @param key - * @return - */ - @Override - public Boolean delete(String namespace, String dataId, String key) { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - try { - Map configMap = getConfigMap(namespace, dataId); - configMap.remove(key); - String configStr = ConfigStoreManager.convertConfig2Str(configMap); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - updateConfigVersion(namespace, dataId); - notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to delete value for key: " + key, e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - /** - * Put all config items into the given namespace and dataId - * @param namespace - * @param dataId - * @param configMap - * @return - */ - @Override - public Boolean putAll(String namespace, String dataId, Map configMap) { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - try { - String configStr = ConfigStoreManager.convertConfig2Str(configMap); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - updateConfigVersion(namespace, dataId); - notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to put all configs", e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - /** - * Delete all config items in the given namespace and dataId - * @param namespace - * @param dataId - * @return - */ - @Override - public Boolean deleteAll(String namespace, String dataId) { - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - try { - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - rocksdb.delete(handle, dataId.getBytes(DEFAULT_CHARSET)); - deleteConfigVersion(namespace, dataId); - notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, null)); - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to clear all configs", e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - - /** - * Get all key-values pairs in all namespaces, mainly used for backup or snapshot - * @return Map(namespace -> Map(dataId -> value)) - */ - @Override - public Map> getConfigMap() { - Map> configMap = new HashMap<>(); - for (String namespace : columnFamilyHandleMap.keySet()) { - HashMap configs = new HashMap<>(); - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.readLock().lock(); - RocksIterator iterator = null; - try { - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - iterator = rocksdb.newIterator(handle); - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - String key = new String(iterator.key(), DEFAULT_CHARSET); - String value = new String(iterator.value(), DEFAULT_CHARSET); - configs.put(key, value); - } - configMap.put(namespace, configs); - } catch (RocksDBException e) { - LOGGER.error("Failed to get configMap in namespace : {}", namespace, e); - } finally { - if (iterator != null) { - iterator.close(); - } - lock.readLock().unlock(); - } - } - return configMap; - } - - /** - * Put all key-value pairs into the specified column family, mainly used for backup or snapshot - * @param configMap Map(namespace -> Map(dataId -> value)) - * @return - */ - @Override - public Boolean putConfigMap(Map> configMap) { - try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { - for (Map.Entry> entry : configMap.entrySet()) { - String namespace = entry.getKey(); - Map configs = entry.getValue(); - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - try { - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - for (Map.Entry nsEntry : configs .entrySet()) { - batch.put(handle, nsEntry.getKey().getBytes(DEFAULT_CHARSET), nsEntry.getValue().toString().getBytes(DEFAULT_CHARSET)); - } - } catch (RocksDBException e) { - LOGGER.error("Failed to put configMap in namespace : {}", namespace, e); - } finally { - lock.writeLock().unlock(); - } - } - rocksdb.write(writeOptions, batch); - for (Map.Entry> entry : configMap.entrySet()) { - String namespace = entry.getKey(); - Map configs = entry.getValue(); - for (Map.Entry kv : configs.entrySet()) { - String dataId = kv.getKey(); - updateConfigVersion(namespace, dataId); - notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, kv.getKey(), kv.getValue().toString())); - } - } - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to put all configMap", e); - return false; - } - } - - /** - * Empty all data in rocksdb, i.e. delete all key-value pairs in all column family - * @return - */ - @Override - public Boolean clearData() { - Map> clearDataMap = new HashMap<>(); - try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { - for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { - String namespace = new String(handle.getName()); - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.writeLock().lock(); - HashSet deleteKeySet = new HashSet<>(); - try (RocksIterator iterator = rocksdb.newIterator(handle)) { - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - batch.delete(handle, iterator.key()); - deleteKeySet.add(new String(iterator.key())); - } - clearDataMap.put(namespace, deleteKeySet); - } finally { - lock.writeLock().unlock(); - } - } - rocksdb.write(writeOptions, batch); - for (Map.Entry> entry : clearDataMap.entrySet()) { - String namespace = entry.getKey(); - for (String key : entry.getValue()) { - deleteConfigVersion(namespace, key); - notifyConfigChange(namespace, key, new ConfigurationChangeEvent(namespace, key, null)); - } - } - return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to clear all data in rocksdb", e); - return false; - } - } - - /** - * Check whether the config data exists in the given namespace and dataId - * @param namespace - * @param dataId - * @return - */ - @Override - public Boolean isEmpty(String namespace, String dataId) { - return CollectionUtils.isEmpty(getAll(namespace, dataId)); - } - - /** - * Get all namespaces in current rocksdb instance - * @return - */ - @Override - public List getAllNamespaces() { - return columnFamilyHandleMap.keySet().stream() - .filter(namespace -> !VERSION_COLUMN_FAMILY.equals(namespace)) - .collect(Collectors.toList()); - } - - /** - * Get all dataIds in the given namespace - * @param namespace - * @return - */ - @Override - public List getAllDataIds(String namespace) { - if (StringUtils.isEmpty(namespace) || !columnFamilyHandleMap.containsKey(namespace)) { - return Collections.emptyList(); - } - List dataIds = new ArrayList<>(); - ReentrantReadWriteLock lock = acquireLock(namespace); - lock.readLock().lock(); - RocksIterator iterator = null; - try { - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - iterator = rocksdb.newIterator(handle); - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - String dataId = new String(iterator.key(), DEFAULT_CHARSET); - dataIds.add(dataId); - } - } catch (RocksDBException e) { - LOGGER.error("Failed to get all dataIds in namespace: {}", namespace, e); - } finally { - if (iterator != null) { - iterator.close(); - } - lock.readLock().unlock(); - } - return dataIds; - } - - /** - * Get the config version in the given namespace and dataId - * @param namespace - * @param dataId - * @return - */ - @Override - public Long getConfigVersion(String namespace, String dataId) { - ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); - lock.readLock().lock(); - try { - String configVersionKey = getConfigVersionKey(namespace, dataId); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); - byte[] value = rocksdb.get(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); - return value != null ? NumberUtils.bytesToLong(value) : null; - } catch (RocksDBException | IllegalArgumentException e) { - LOGGER.error("Failed to get config version in namespace: {} and dataId: {}", namespace, dataId, e); - } finally { - lock.readLock().unlock(); - } - return null; - } - - /** - * Put the config version in the given namespace and dataId - * @param namespace - * @param dataId - * @param version - * @return - */ - @Override - public Boolean putConfigVersion(String namespace, String dataId, Long version) { - ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); - lock.writeLock().lock(); - try { - String configVersionKey = getConfigVersionKey(namespace, dataId); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); - rocksdb.put(handle, configVersionKey.getBytes(DEFAULT_CHARSET), NumberUtils.longToBytes(version)); - return true; - } catch (RocksDBException | IllegalArgumentException e) { - LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - /** - * Delete the config version in the given namespace and dataId when the config data is deleted. - * @param namespace - * @param dataId - * @return - */ - @Override - public Boolean deleteConfigVersion(String namespace, String dataId) { - ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); - lock.writeLock().lock(); - try { - String configVersionKey = getConfigVersionKey(namespace, dataId); - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); - rocksdb.delete(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); - return true; - } catch (RocksDBException | IllegalArgumentException e) { - LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); - } finally { - lock.writeLock().unlock(); - } - return false; - } - - private String getConfigVersionKey(String namespace, String dataId) { - if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(dataId)) { - throw new IllegalArgumentException("Invalid config namespace or dataId"); - } - return namespace + "_" + dataId; - } - - @Override - public void shutdown() { - synchronized (RocksDBConfigStoreManager.class) { - // 1. close all handles - for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { - if (handle != null) { - handle.close(); - } - } - // 2. close options - RocksDBOptionsFactory.releaseAllOptions(); - // 3. close db - RocksDBFactory.close(); - // 4. destroy db if needed - if (RocksDBOptionsFactory.getDBDestroyOnShutdown()) { - destroy(); - } - // 5. help gc - columnFamilyHandleMap.clear(); - this.rocksdb = null; - LOGGER.info("RocksDBConfigStoreManager has shutdown"); - } - } - - @Override - public void destroy() { - RocksDBFactory.destroy(DB_PATH); - LOGGER.info("DB destroyed, the db path is: {}.", DB_PATH); - } - - - @Override - public void addConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { - return; - } - Map> listenerMap = CONFIG_LISTENERS_MAP.computeIfAbsent(namespace, k -> new ConcurrentHashMap<>()); - listenerMap.computeIfAbsent(dataId, k -> ConcurrentHashMap.newKeySet()) - .add(listener); - } - - @Override - public void removeConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { - return; - } - // dataId -> listener - Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); - if (CollectionUtils.isNotEmpty(listenerMap)) { - Set configChangeListeners = listenerMap.get(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - configChangeListeners.remove(listener); - } - } - } - - private void updateConfigVersion(String namespace, String dataId) { - Long version = getConfigVersion(namespace, dataId); - if (version == null) { - version = 0L; - } - putConfigVersion(namespace, dataId, version + 1); - } - - - private void notifyConfigChange(String namespace, String dataId, ConfigurationChangeEvent event) { - Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); - if (CollectionUtils.isNotEmpty(listenerMap)) { - Set configChangeListeners = listenerMap.get(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - configChangeListeners.forEach(listener -> listener.onChangeEvent(event)); - } - } - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java deleted file mode 100644 index b9b32a05992..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 org.apache.seata.config.store.rocksdb; - -import org.apache.seata.common.loader.LoadLevel; -import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerProvider; - - -@LoadLevel(name = "Rocksdb", order = 1) -public class RocksDBConfigStoreManagerProvider implements ConfigStoreManagerProvider { - @Override - public ConfigStoreManager provide() { - return RocksDBConfigStoreManager.getInstance(); - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java deleted file mode 100644 index f03ac872334..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 org.apache.seata.config.store.rocksdb; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import org.rocksdb.ColumnFamilyDescriptor; -import org.rocksdb.ColumnFamilyHandle; -import org.rocksdb.DBOptions; -import org.rocksdb.RocksDB; -import org.rocksdb.Options; -import org.rocksdb.RocksDBException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The RocksDB Factory - * - */ -public class RocksDBFactory { - private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBFactory.class); - - private static volatile RocksDB instance = null; - - - static { - RocksDB.loadLibrary(); - } - - public static RocksDB getInstance(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { - if (instance == null) { - synchronized (RocksDBFactory.class) { - if (instance == null) { - instance = build(dbPath, dbOptions, columnFamilyDescriptors, columnFamilyHandles); - } - } - } - return instance; - } - - private static RocksDB build(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { - try { - checkPath(dbPath); - return RocksDB.open(dbOptions, dbPath, columnFamilyDescriptors, columnFamilyHandles); - } catch (RocksDBException | IOException e) { - LOGGER.error("RocksDB open error: {}", e.getMessage(), e); - return null; - } - } - - - public static synchronized void close() { - if (instance != null) { - instance.close(); - instance = null; - } - } - - public static synchronized void destroy(String dbPath) { - close(); - try (final Options opt = new Options()) { - RocksDB.destroyDB(dbPath, opt); - } catch (RocksDBException e) { - LOGGER.error("RocksDB destroy error: {}", e.getMessage(), e); - } - } - - private static void checkPath(String dbPath) throws IOException { - File directory = new File(dbPath); - String message; - if (directory.exists()) { - if (!directory.isDirectory()) { - message = "File " + directory + " exists and is not a directory. Unable to create directory."; - throw new IOException(message); - } - } else if (!directory.mkdirs() && !directory.isDirectory()) { - message = "Unable to create directory " + directory; - throw new IOException(message); - } - } -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java deleted file mode 100644 index 9846a8b98e1..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 org.apache.seata.config.store.rocksdb; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationFactory; -import org.rocksdb.ColumnFamilyOptions; -import org.rocksdb.CompactionStyle; -import org.rocksdb.CompressionType; -import org.rocksdb.DBOptions; -import org.rocksdb.util.SizeUnit; - -import static java.io.File.separator; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DESTROY_ON_SHUTDOWN; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DIR; -import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; - - -/** - * The RocksDB options builder - * - */ -public class RocksDBOptionsFactory { - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - - public static final String ROCKSDB_SUFFIX = "rocksdb"; - private static volatile DBOptions options = null; - private static final Map COLUMN_FAMILY_OPTIONS_MAP = new ConcurrentHashMap<>(); - public static DBOptions getDBOptions() { - if (options == null) { - synchronized (RocksDBOptionsFactory.class) { - if (options == null) { - options = buildDBOptions(); - } - } - } - return options; - } - - public static ColumnFamilyOptions getColumnFamilyOptionsMap(final String namespace) { - ColumnFamilyOptions opts = COLUMN_FAMILY_OPTIONS_MAP.get(namespace); - if (opts == null) { - final ColumnFamilyOptions newOpts = buildColumnFamilyOptions(); - opts = COLUMN_FAMILY_OPTIONS_MAP.putIfAbsent(namespace, newOpts); - if (opts != null) { - newOpts.close(); - } else { - opts = newOpts; - } - } - return opts; - } - public static String getDBPath() { - String dir = FILE_CONFIG.getConfig(CONFIG_STORE_DIR); - String group = FILE_CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); - return String.join(separator, dir, group, ROCKSDB_SUFFIX); - } - - public static boolean getDBDestroyOnShutdown() { - return FILE_CONFIG.getBoolean(CONFIG_STORE_DESTROY_ON_SHUTDOWN, false); - } - - private static DBOptions buildDBOptions() { - final DBOptions options = new DBOptions(); - // If the database does not exist, create it - options.setCreateIfMissing(true); - // If true, missing column families will be automatically created. - options.setCreateMissingColumnFamilies(true); - // Retain only the latest log file - options.setKeepLogFileNum(1); - // Disable log file rolling based on time - options.setLogFileTimeToRoll(0); - // Disable log file rolling based on size - options.setMaxLogFileSize(0); - // Number of open files that can be used by the DB. - options.setMaxOpenFiles(-1); - return options; - } - - private static ColumnFamilyOptions buildColumnFamilyOptions() { - ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); - // set little write buffer size, since the size of config file is small - columnFamilyOptions.setWriteBufferSize(4 * SizeUnit.MB); - // Set memtable prefix bloom filter to reduce memory usage. - columnFamilyOptions.setMemtablePrefixBloomSizeRatio(0.125); - // Set compression type - columnFamilyOptions.setCompressionType(CompressionType.LZ4_COMPRESSION); - // Set compaction style - columnFamilyOptions.setCompactionStyle(CompactionStyle.LEVEL); - // Optimize level style compaction - columnFamilyOptions.optimizeLevelStyleCompaction(); - return columnFamilyOptions; - } - - public static void releaseAllOptions() { - // close all options - if (options != null) { - options.close(); - } - for (final ColumnFamilyOptions opts : COLUMN_FAMILY_OPTIONS_MAP.values()) { - if (opts != null) { - opts.close(); - } - } - // help gc - options = null; - COLUMN_FAMILY_OPTIONS_MAP.clear(); - } - -} diff --git a/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider b/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider deleted file mode 100644 index 0d199fafe7c..00000000000 --- a/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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. -# -org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManagerProvider \ No newline at end of file diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java deleted file mode 100644 index 6850ebedde5..00000000000 --- a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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 org.apache.seata.config.store.rocksdb; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; -import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; - - -class RocksDBTest { - private static RocksDBConfigStoreManager configStoreManager; - - private static final String dataId = DEFAULT_STORE_DATA_ID; - private static final String namespace = DEFAULT_STORE_NAMESPACE; - @BeforeAll - static void setUp() { - configStoreManager = RocksDBConfigStoreManager.getInstance(); - } - - @AfterAll - static void tearDown() { - if (configStoreManager != null) { - configStoreManager.shutdown(); - configStoreManager.destroy(); - } - } - - @Test - void getConfigStoreManagerTest() { - Assertions.assertNotNull(configStoreManager); - } - - - @Test - void crudTest() { - configStoreManager.deleteAll(namespace, dataId); - String key = "aaa"; - String value = "bbb"; - String updateValue = "ccc"; - Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); - Assertions.assertEquals(value, configStoreManager.get(namespace, dataId, key)); - Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, updateValue)); - Assertions.assertEquals(updateValue, configStoreManager.get(namespace, dataId, key)); - Assertions.assertTrue(configStoreManager.delete(namespace, dataId, key)); - Assertions.assertNull(configStoreManager.get(namespace, dataId, key)); - - } - - @Test - void uploadConfigTest() { - configStoreManager.deleteAll(namespace, dataId); - HashMap uploadConfigs = new HashMap<>(); - uploadConfigs.put("aaa","111"); - uploadConfigs.put("bbb","222"); - Assertions.assertTrue(configStoreManager.putAll(namespace, dataId, uploadConfigs)); - Assertions.assertEquals(uploadConfigs, configStoreManager.getAll(namespace, dataId)); - configStoreManager.deleteAll(namespace, dataId); - Assertions.assertTrue(configStoreManager.isEmpty(namespace, dataId)); - } - - - @Test - void multiGroupTest() { - configStoreManager.deleteAll(namespace, dataId); - String group1 = "group1"; - String group2 = "group2"; - String key = "aaa"; - String value1 = "aaa"; - String value2 = "bbb"; - // put and get - Assertions.assertTrue(configStoreManager.put(namespace, group1, key, value1)); - Assertions.assertTrue(configStoreManager.put(namespace, group2, key, value2)); - Assertions.assertEquals(value1, configStoreManager.get(namespace, group1, key)); - Assertions.assertEquals(value2, configStoreManager.get(namespace, group2, key)); - - // delete - Assertions.assertTrue(configStoreManager.delete(namespace, group1, key)); - Assertions.assertTrue(configStoreManager.delete(namespace, group2, key)); - Assertions.assertNull(configStoreManager.get(namespace, group1, key)); - Assertions.assertNull(configStoreManager.get(namespace, group2, key)); - } - - - @Test - void multiNamespaceAndGroupTest() { - configStoreManager.clearData(); - String namespace1 = "namespace1"; - String namespace2 = "namespace2"; - List namespaces = Arrays.asList(DEFAULT_STORE_NAMESPACE, namespace1, namespace2); - String dataId1 = "dataId1"; - String dataId2 = "dataId2"; - List dataIds = Arrays.asList(dataId1, dataId2); - String key = "aaa"; - // put and get - Assertions.assertTrue(configStoreManager.put(namespace1, dataId1, key , "11")); - Assertions.assertTrue(configStoreManager.put(namespace1, dataId2, key , "12")); - Assertions.assertTrue(configStoreManager.put(namespace2, dataId1, key , "21")); - Assertions.assertTrue(configStoreManager.put(namespace2, dataId2, key , "22")); - Assertions.assertEquals("11", configStoreManager.get(namespace1, dataId1, key)); - Assertions.assertEquals("12", configStoreManager.get(namespace1, dataId2, key)); - Assertions.assertEquals("21", configStoreManager.get(namespace2, dataId1, key)); - Assertions.assertEquals("22", configStoreManager.get(namespace2, dataId2, key)); - Assertions.assertEquals(namespaces.size(), configStoreManager.getAllNamespaces().size()); - Assertions.assertEquals(dataIds.size(), configStoreManager.getAllDataIds(namespace1).size()); - Assertions.assertEquals(dataIds.size(), configStoreManager.getAllDataIds(namespace2).size()); - // delete - Assertions.assertTrue(configStoreManager.delete(namespace1, dataId1, key)); - Assertions.assertTrue(configStoreManager.delete(namespace1, dataId2, key)); - Assertions.assertTrue(configStoreManager.delete(namespace2, dataId1, key)); - Assertions.assertTrue(configStoreManager.delete(namespace2, dataId2, key)); - Assertions.assertNull(configStoreManager.get(namespace1, dataId1, key)); - Assertions.assertNull(configStoreManager.get(namespace1, dataId2, key)); - Assertions.assertNull(configStoreManager.get(namespace2, dataId1, key)); - Assertions.assertNull(configStoreManager.get(namespace2, dataId2, key)); - } - - @Test - void uploadTest() { - configStoreManager.clearData(); - String namespace1 = "namespace1"; - String namespace2 = "namespace2"; - String dataId1 = "dataId1"; - String dataId2 = "dataId2"; - HashMap> configMap = new HashMap>(); - HashMap map1 = new HashMap() {{ - put(dataId1, "11"); - put(dataId2, "12"); - }}; - HashMap map2 = new HashMap() {{ - put(dataId1, "21"); - put(dataId2, "22"); - }}; - configMap.put(namespace1,map1); - configMap.put(namespace2,map2); - // ensure default namespace - configMap.put("default",new HashMap<>()); - Assertions.assertTrue(configStoreManager.putConfigMap(configMap)); - Map> other = configStoreManager.getConfigMap(); - - Assertions.assertEquals(configMap.get(namespace1), other.get(namespace1)); - Assertions.assertEquals(configMap.get(namespace2), other.get(namespace2)); - Assertions.assertEquals(configMap.get("default"), other.get("default")); - - Assertions.assertDoesNotThrow(()->configStoreManager.getAll(namespace1, dataId1)); - } - - @Test - void configVersionTest() { - configStoreManager.clearData(); - Long version = 0L; - - String key = "aaa"; - String value = "bbb"; - String newValue = "ccc"; - - Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); - version++; - Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); - - Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, newValue)); - version++; - Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); - - Assertions.assertTrue(configStoreManager.deleteAll(namespace, dataId)); - Assertions.assertNull(configStoreManager.getConfigVersion(namespace, dataId)); - } -} diff --git a/config/seata-config-core/src/test/resources/registry.conf b/config/seata-config-core/src/test/resources/registry.conf index 343179ab6a8..bab6e8ec0ef 100644 --- a/config/seata-config-core/src/test/resources/registry.conf +++ b/config/seata-config-core/src/test/resources/registry.conf @@ -87,14 +87,4 @@ config { file { name = "file.conf" } - raft { - db { - type = "rocksdb" - dir = "configStore" - destroy-on-shutdown = false - namespace = "default" - dataId = "seata.properties" - } - } - } diff --git a/config/seata-config-raft/pom.xml b/config/seata-config-raft/pom.xml deleted file mode 100644 index 20b7b3ddb5c..00000000000 --- a/config/seata-config-raft/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - org.apache.seata - seata-config - ${revision} - - 4.0.0 - seata-config-raft - seata-config-raft ${project.version} - config-raft for Seata built with Maven - - - - org.apache.seata - seata-config-core - ${project.version} - - - org.apache.httpcomponents - httpclient - - - - \ No newline at end of file diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java deleted file mode 100644 index c27626f9eec..00000000000 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java +++ /dev/null @@ -1,694 +0,0 @@ -/* - * 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 org.apache.seata.config.raft; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.ContentType; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.common.config.ConfigDataResponse; -import org.apache.seata.common.exception.AuthenticationFailedException; -import org.apache.seata.common.exception.ErrorCode; -import org.apache.seata.common.exception.NotSupportYetException; -import org.apache.seata.common.exception.RetryableException; -import org.apache.seata.common.exception.SeataRuntimeException; -import org.apache.seata.common.metadata.Metadata; -import org.apache.seata.common.metadata.MetadataResponse; -import org.apache.seata.common.metadata.Node; -import org.apache.seata.common.thread.NamedThreadFactory; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.HttpClientUtil; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.AbstractConfiguration; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationChangeEvent; -import org.apache.seata.config.ConfigurationChangeListener; -import org.apache.seata.config.ConfigurationChangeType; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.dto.ConfigurationInfoDto; -import org.apache.seata.config.dto.ConfigurationItem; -import org.apache.seata.config.store.ConfigStoreManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; -import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; -import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; -import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; -import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; - -/** - * The type Raft configuration of client. - * - */ -public class RaftConfigurationClient extends AbstractConfiguration { - private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationClient.class); - private static final String CONFIG_TYPE = "raft"; - private static final String SERVER_ADDR_KEY = "serverAddr"; - private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; - private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; - private static final String CONFIG_NAMESPACE; - private static final String CONFIG_DATA_ID; - private static final String HTTP_PREFIX = "http://"; - private static final String USERNAME_KEY = "username"; - private static final String PASSWORD_KEY = "password"; - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String TOKEN_VALID_TIME_MS_KEY = "tokenValidityInMilliseconds"; - private static volatile RaftConfigurationClient instance; - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static final String USERNAME; - private static final String PASSWORD; - private static final long TOKEN_EXPIRE_TIME_IN_MILLISECONDS; - private static volatile long tokenTimeStamp = -1; - private static volatile String jwtToken; - private static final String IP_PORT_SPLIT_CHAR = ":"; - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final Map> INIT_ADDRESSES = new HashMap<>(); - private static final Metadata METADATA = new Metadata(); - private static volatile ThreadPoolExecutor REFRESH_METADATA_EXECUTOR; - private static volatile ThreadPoolExecutor REFRESH_CONFIG_EXECUTOR; - private static final AtomicBoolean CLOSED = new AtomicBoolean(false); - private static final AtomicBoolean CONFIG_CLOSED = new AtomicBoolean(false); - private static volatile Properties seataConfig = new Properties(); - private static final AtomicLong CONFIG_VERSION = new AtomicLong(0); - private static final int MAP_INITIAL_CAPACITY = 8; - private static final ConcurrentMap> CONFIG_LISTENERS_MAP - = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); - - private static ConfigStoreListener CONFIG_LISTENER; - static { - USERNAME = FILE_CONFIG.getConfig(getRaftUsernameKey()); - PASSWORD = FILE_CONFIG.getConfig(getRaftPasswordKey()); - TOKEN_EXPIRE_TIME_IN_MILLISECONDS = FILE_CONFIG.getLong(getTokenExpireTimeInMillisecondsKey(), 29 * 60 * 1000L); - CONFIG_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); - CONFIG_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); - } - - public static RaftConfigurationClient getInstance() { - if (instance == null) { - synchronized (RaftConfigurationClient.class) { - if (instance == null) { - instance = new RaftConfigurationClient(); - } - } - } - return instance; - } - - private RaftConfigurationClient() { - initClusterMetaData(); - initClientConfig(); - } - - private static void initClientConfig() { - try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); - if (configMap != null) { - seataConfig.putAll(configMap); - } - CONFIG_LISTENER = new ConfigStoreListener(CONFIG_NAMESPACE, null); - startQueryConfigData(); - } catch (RetryableException e) { - LOGGER.error("init config properties error:{}", e.getMessage(), e); - } - - } - private static String queryHttpAddress(String clusterName, String group) { - List nodeList = METADATA.getNodes(clusterName, group); - List addressList = null; - Stream stream = null; - if (CollectionUtils.isNotEmpty(nodeList)) { - addressList = - nodeList.stream().map(node -> node.getControl().createAddress()).collect(Collectors.toList()); - } else { - stream = INIT_ADDRESSES.get(clusterName).stream(); - } - if (addressList != null) { - return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); - } else { - Map map = new HashMap<>(); - if (CollectionUtils.isNotEmpty(nodeList)) { - for (Node node : nodeList) { - map.put(new InetSocketAddress(node.getTransaction().getHost(), node.getTransaction().getPort()).getAddress().getHostAddress() - + IP_PORT_SPLIT_CHAR + node.getTransaction().getPort(), node); - } - } - addressList = stream.map(inetSocketAddress -> { - String host = inetSocketAddress.getAddress().getHostAddress(); - Node node = map.get(host + IP_PORT_SPLIT_CHAR + inetSocketAddress.getPort()); - return host + IP_PORT_SPLIT_CHAR - + (node != null ? node.getControl().getPort() : inetSocketAddress.getPort()); - }).collect(Collectors.toList()); - return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); - } - } - private static void acquireClusterMetaData(String clusterName, String group) throws RetryableException { - String tcAddress = queryHttpAddress(clusterName, group); - Map header = new HashMap<>(); - header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - if (isTokenExpired()) { - refreshToken(tcAddress); - } - if (StringUtils.isNotBlank(jwtToken)) { - header.put(AUTHORIZATION_HEADER, jwtToken); - } - if (StringUtils.isNotBlank(tcAddress)) { - Map param = new HashMap<>(); - // param.put("group", group); - String response = null; - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doGet(HTTP_PREFIX + tcAddress + "/metadata/v1/config/cluster", param, header, 1000)) { - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); - } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { - throw new RetryableException("Authentication failed!"); - } else { - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - } - } - MetadataResponse metadataResponse; - if (StringUtils.isNotBlank(response)) { - try { - metadataResponse = OBJECT_MAPPER.readValue(response, MetadataResponse.class); - METADATA.refreshMetadata(clusterName, metadataResponse); - } catch (JsonProcessingException e) { - LOGGER.error("acquireClusterMetaData:{}", e.getMessage(), e); - } - } - } catch (IOException e) { - throw new RetryableException(e.getMessage(), e); - } - } - } - - @SuppressWarnings("unchecked") - private static Map acquireClusterConfigData(String clusterName, String group, String configNamespace, String configDataId) throws RetryableException { - String tcAddress = queryHttpAddress(clusterName, group); - Map header = new HashMap<>(); - header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - if (isTokenExpired()) { - refreshToken(tcAddress); - } - if (StringUtils.isNotBlank(jwtToken)) { - header.put(AUTHORIZATION_HEADER, jwtToken); - } - if (StringUtils.isNotBlank(tcAddress)) { - Map param = new HashMap<>(); - param.put("namespace", configNamespace); - param.put("dataId", configDataId); - String response = null; - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doGet(HTTP_PREFIX + tcAddress + "/metadata/v1/config/getAll", param, header, 1000)) { - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); - } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { - throw new RetryableException("Authentication failed!"); - } else { - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - } - } - - ConfigDataResponse configDataResponse; - if (StringUtils.isNotBlank(response)) { - try { - configDataResponse = OBJECT_MAPPER.readValue(response, new TypeReference>() { - }); - if (configDataResponse.getSuccess()) { - ConfigurationInfoDto configurationInfoDto = configDataResponse.getResult(); - Map configItemMap = configurationInfoDto.getConfig(); - Map configMap = configItemMap.entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, entry -> entry.getValue().getValue())); - Long version = configurationInfoDto.getVersion() == null ? -1 : configurationInfoDto.getVersion(); - Long currentVersion = CONFIG_VERSION.get(); - if (version < currentVersion) { - LOGGER.info("The configuration version: {} of the server is lower than the current configuration: {} , it may be expired configuration.", version, CONFIG_VERSION.get()); - throw new RetryableException("Expired configuration!"); - } else { - CONFIG_VERSION.set(version); - return configMap; - } - } else { - throw new RetryableException(configDataResponse.getErrMsg()); - } - } catch (JsonProcessingException e) { - LOGGER.error("acquireClusterConfigData:{}", e.getMessage(), e); - } - } - } catch (IOException e) { - throw new RetryableException(e.getMessage(), e); - } - } - return null; - } - - protected static void startQueryMetadata() { - if (REFRESH_METADATA_EXECUTOR == null) { - synchronized (INIT_ADDRESSES) { - if (REFRESH_METADATA_EXECUTOR == null) { - REFRESH_METADATA_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshMetadata", 1, true)); - REFRESH_METADATA_EXECUTOR.execute(() -> { - long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); - long currentTime = System.currentTimeMillis(); - while (!CLOSED.get()) { - try { - // Forced refresh of metadata information after set age - boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; - String clusterName = RAFT_CLUSTER; - if (!fetch) { - fetch = watch(); - } - // Cluster changes or reaches timeout refresh time - if (fetch) { - for (String group : METADATA.groups(clusterName)) { - try { - acquireClusterMetaData(clusterName, group); - } catch (Exception e) { - // prevents an exception from being thrown that causes the thread to break - if (e instanceof RetryableException) { - throw e; - } else { - LOGGER.error("failed to get the leader address,error: {}", e.getMessage(), e); - } - } - } - currentTime = System.currentTimeMillis(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("refresh seata cluster metadata time: {}", currentTime); - } - } - } catch (RetryableException e) { - LOGGER.error("startQueryMetadata:{}", e.getMessage(), e); - try { - TimeUnit.MILLISECONDS.sleep(1000); - } catch (InterruptedException ignored) { - } - } - } - }); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (CLOSED.compareAndSet(false, true)) { - REFRESH_METADATA_EXECUTOR.shutdown(); - } - })); - } - } - } - } - - protected static void startQueryConfigData() { - if (REFRESH_CONFIG_EXECUTOR == null) { - synchronized (RaftConfigurationClient.class) { - if (REFRESH_CONFIG_EXECUTOR == null) { - REFRESH_CONFIG_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshConfig", 1, true)); - REFRESH_CONFIG_EXECUTOR.execute(() -> { - long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); - long currentTime = System.currentTimeMillis(); - while (!CONFIG_CLOSED.get()) { - try { - // Forced refresh of metadata information after set age - boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; - if (!fetch) { - fetch = configWatch(); - } - // Cluster config changes or reaches timeout refresh time - if (fetch) { - try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); - if (CollectionUtils.isNotEmpty(configMap)) { - notifyConfigMayChange(configMap); - } - } catch (Exception e) { - // prevents an exception from being thrown that causes the thread to break - if (e instanceof RetryableException) { - throw e; - } else { - LOGGER.error("failed to get the config ,error: {}", e.getMessage(), e); - } - } - - currentTime = System.currentTimeMillis(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("refresh seata cluster config time: {}", currentTime); - } - } - } catch (RetryableException e) { - LOGGER.error("startQueryConfigData:{}", e.getMessage(), e); - try { - TimeUnit.MILLISECONDS.sleep(1000); - } catch (InterruptedException ignored) { - } - } - } - }); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (CONFIG_CLOSED.compareAndSet(false, true)) { - REFRESH_CONFIG_EXECUTOR.shutdown(); - } - })); - } - } - } - } - private static boolean watch() throws RetryableException { - Map header = new HashMap<>(); - header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - Map param = new HashMap<>(); - String clusterName = RAFT_CLUSTER; - Map groupTerms = METADATA.getClusterTerm(clusterName); - groupTerms.forEach((k, v) -> param.put(k, String.valueOf(v))); - for (String group : groupTerms.keySet()) { - String tcAddress = queryHttpAddress(clusterName, group); - if (isTokenExpired()) { - refreshToken(tcAddress); - } - if (StringUtils.isNotBlank(jwtToken)) { - header.put(AUTHORIZATION_HEADER, jwtToken); - } - try (CloseableHttpResponse response = - HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/metadata/v1/watch", param, header, 30000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { - throw new RetryableException("Authentication failed!"); - } else { - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - } - return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; - } - } catch (IOException e) { - LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage(), e); - throw new RetryableException(e.getMessage(), e); - } - break; - } - return false; - } - - private static boolean configWatch() throws RetryableException { - Map header = new HashMap<>(); - header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); - String tcAddress = queryHttpAddress(RAFT_CLUSTER, RAFT_GROUP); - Map param = new HashMap<>(); - param.put("namespace", CONFIG_NAMESPACE); - param.put("dataId", CONFIG_DATA_ID); - param.put("version", CONFIG_VERSION.toString()); - if (isTokenExpired()) { - refreshToken(tcAddress); - } - if (StringUtils.isNotBlank(jwtToken)) { - header.put(AUTHORIZATION_HEADER, jwtToken); - } - try (CloseableHttpResponse response = - HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/metadata/v1/config/watch", param, header, 30000)) { - if (response != null) { - StatusLine statusLine = response.getStatusLine(); - if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { - if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { - throw new RetryableException("Authentication failed!"); - } else { - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - } - return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; - } - } catch (IOException e) { - LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage(), e); - throw new RetryableException(e.getMessage(), e); - } - return false; - } - private static void initClusterMetaData() { - String clusterName = RAFT_CLUSTER; - String group = RAFT_GROUP; - if (!METADATA.containsGroup(clusterName)) { - String raftClusterAddress = FILE_CONFIG.getConfig(getRaftServerAddrKey()); - if (StringUtils.isNotBlank(raftClusterAddress)) { - List list = new ArrayList<>(); - String[] addresses = raftClusterAddress.split(","); - for (String address : addresses) { - String[] endpoint = address.split(IP_PORT_SPLIT_CHAR); - String host = endpoint[0]; - int port = Integer.parseInt(endpoint[1]); - list.add(new InetSocketAddress(host, port)); - } - if (CollectionUtils.isEmpty(list)) { - throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, - "There are no valid raft addr! you should configure the correct [config.raft.server-addr] in the config file"); - } - INIT_ADDRESSES.put(clusterName, list); - // init jwt token - try { - refreshToken(queryHttpAddress(clusterName, group)); - } catch (Exception e) { - throw new RuntimeException("Init fetch token failed!", e); - } - // Refresh the metadata by initializing the address - try { - acquireClusterMetaData(clusterName, group); - } catch (RetryableException e) { - LOGGER.error("init cluster metadata fail: {}", e.getMessage(), e); - } - startQueryMetadata(); - } - } - } - - - - @Override - public String getTypeName() { - return CONFIG_TYPE; - } - - @Override - public boolean putConfig(String dataId, String content, long timeoutMills) { - throw new NotSupportYetException("not support operation putConfig"); - } - - @Override - public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { - String value = seataConfig.getProperty(dataId); - if (value == null) { - try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); - if (CollectionUtils.isNotEmpty(configMap)) { - value = configMap.get(dataId) == null ? null : configMap.get(dataId).toString(); - } - } catch (RetryableException e) { - LOGGER.error(e.getMessage()); - } - } - return value == null ? defaultValue : value; - } - - @Override - public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { - throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); - } - - @Override - public boolean removeConfig(String dataId, long timeoutMills) { - throw new NotSupportYetException("not support operation removeConfig"); - } - - @Override - public void addConfigListener(String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { - return; - } - ConfigStoreListener storeListener = new ConfigStoreListener(dataId, listener); - CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) - .put(listener, storeListener); - } - - @Override - public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { - return; - } - Set configChangeListeners = getConfigListeners(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - for (ConfigurationChangeListener entry : configChangeListeners) { - if (listener.equals(entry)) { - Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (configListeners != null) { - configListeners.remove(entry); - } - break; - } - } - } - } - - @Override - public Set getConfigListeners(String dataId) { - ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (CollectionUtils.isNotEmpty(configListeners)) { - return configListeners.keySet(); - } else { - return null; - } - } - - private static void notifyConfigMayChange(Map configMap) { - String configStr = ConfigStoreManager.convertConfig2Str(configMap); - CONFIG_LISTENER.onChangeEvent(new ConfigurationChangeEvent(CONFIG_NAMESPACE, configStr)); - } - - - private static String getRaftUsernameKey() { - return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, USERNAME_KEY); - } - private static String getRaftPasswordKey() { - return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PASSWORD_KEY); - } - private static String getRaftServerAddrKey() { - return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, SERVER_ADDR_KEY); - } - - private static String getTokenExpireTimeInMillisecondsKey() { - return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, TOKEN_VALID_TIME_MS_KEY); - } - - private static boolean isTokenExpired() { - if (tokenTimeStamp == -1) { - return true; - } - long tokenExpiredTime = tokenTimeStamp + TOKEN_EXPIRE_TIME_IN_MILLISECONDS; - return System.currentTimeMillis() >= tokenExpiredTime; - } - - private static synchronized void refreshToken(String tcAddress) throws RetryableException { - // double-check if the token is expired inside the synchronized method to avoid repeated token refreshes in multiple threads - if (!isTokenExpired()) { - return; - } - // if username and password is not in config , return - if (StringUtils.isBlank(USERNAME) || StringUtils.isBlank(PASSWORD)) { - return; - } - // get token and set it in cache - Map param = new HashMap<>(); - param.put(USERNAME_KEY, USERNAME); - param.put(PASSWORD_KEY, PASSWORD); - - Map header = new HashMap<>(); - header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); - String response = null; - tokenTimeStamp = System.currentTimeMillis(); - try (CloseableHttpResponse httpResponse = - HttpClientUtil.doPost(HTTP_PREFIX + tcAddress + "/api/v1/auth/login", param, header, 1000)) { - if (httpResponse != null) { - if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { - response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); - JsonNode jsonNode = OBJECT_MAPPER.readTree(response); - String codeStatus = jsonNode.get("code").asText(); - if (!StringUtils.equals(codeStatus, "200")) { - //authorized failed,throw exception to kill process - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - jwtToken = jsonNode.get("data").asText(); - } else { - //authorized failed,throw exception to kill process - throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); - } - } - } catch (IOException e) { - throw new RetryableException(e.getMessage(), e); - } - } - - private static class ConfigStoreListener implements ConfigurationChangeListener { - private final String dataId; - private final ConfigurationChangeListener listener; - - public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) { - this.dataId = dataId; - this.listener = listener; - } - @Override - public void onChangeEvent(ConfigurationChangeEvent event) { - if (CONFIG_NAMESPACE.equals(event.getDataId())) { - Properties seataConfigNew = new Properties(); - Map newConfigMap = ConfigStoreManager.convertConfigStr2Map(event.getNewValue()); - if (CollectionUtils.isNotEmpty(newConfigMap)) { - seataConfigNew.putAll(newConfigMap); - } - //Get all the monitored dataids and judge whether it has been modified - for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { - String listenedDataId = entry.getKey(); - String propertyOld = seataConfig.getProperty(listenedDataId, ""); - String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); - if (!propertyOld.equals(propertyNew)) { - ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() - .setDataId(listenedDataId) - .setNewValue(propertyNew) - .setNamespace(CONFIG_NAMESPACE) - .setChangeType(ConfigurationChangeType.MODIFY); - - // notify ConfigurationCache - ConcurrentMap configListeners = entry.getValue(); - for (ConfigurationChangeListener configListener : configListeners.keySet()) { - configListener.onProcessEvent(newEvent); - } - } - } - seataConfig = seataConfigNew; - } - } - } - -} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java deleted file mode 100644 index c2470aa1bdd..00000000000 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 org.apache.seata.config.raft; - -import org.apache.seata.common.loader.LoadLevel; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationProvider; - -import static org.apache.seata.common.Constants.APPLICATION_TYPE_KEY; -import static org.apache.seata.common.Constants.APPLICATION_TYPE_SERVER; - -@LoadLevel(name = "Raft", order = 1) -public class RaftConfigurationProvider implements ConfigurationProvider { - @Override - public Configuration provide() { - // todo : optimize - String applicationType = System.getProperty(APPLICATION_TYPE_KEY); - if (APPLICATION_TYPE_SERVER.equals(applicationType)) { - return RaftConfigurationServer.getInstance(); - } else { - return RaftConfigurationClient.getInstance(); - } - } -} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java deleted file mode 100644 index 843e2b9f093..00000000000 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * 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 org.apache.seata.config.raft; - -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.apache.seata.common.exception.NotSupportYetException; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.AbstractConfiguration; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationChangeEvent; -import org.apache.seata.config.ConfigurationChangeListener; -import org.apache.seata.config.ConfigurationChangeType; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; -import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; -import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; - - -public class RaftConfigurationServer extends AbstractConfiguration { - private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationServer.class); - private static volatile RaftConfigurationServer instance; - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static ConfigStoreManager configStoreManager; - private static String CURRENT_NAMESPACE; - private static String CURRENT_DATA_ID; - private static final String CONFIG_TYPE = "raft"; - private static volatile Properties seataConfig = new Properties(); - private static final int MAP_INITIAL_CAPACITY = 8; - private static final ConcurrentMap> CONFIG_LISTENERS_MAP - = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); - - private static void initServerConfig() { - configStoreManager = ConfigStoreManagerFactory.getInstance(); - CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); - CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); - // load config from store - Map configMap = configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID); - seataConfig.putAll(configMap); - // build listener - ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, null); - configStoreManager.addConfigListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, storeListener); - } - - - public static RaftConfigurationServer getInstance() { - if (instance == null) { - synchronized (RaftConfigurationServer.class) { - if (instance == null) { - instance = new RaftConfigurationServer(); - } - } - } - return instance; - } - - private RaftConfigurationServer() { - initServerConfig(); - } - - @Override - public String getTypeName() { - return CONFIG_TYPE; - } - - @Override - public boolean putConfig(String dataId, String content, long timeoutMills) { - throw new NotSupportYetException("not support operation putConfig"); - } - - @Override - public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { - String value = seataConfig.getProperty(dataId); - if (value == null) { - value = configStoreManager.get(CURRENT_NAMESPACE, CURRENT_DATA_ID, dataId); - } - return value == null ? defaultValue : value; - } - - @Override - public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { - throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); - } - - @Override - public boolean removeConfig(String dataId, long timeoutMills) { - throw new NotSupportYetException("not support operation removeConfig"); - } - - @Override - public void addConfigListener(String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { - return; - } - ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, dataId, listener); - CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) - .put(listener, storeListener); - configStoreManager.addConfigListener(CURRENT_NAMESPACE, dataId, storeListener); - } - - @Override - public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { - return; - } - Set configChangeListeners = getConfigListeners(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - for (ConfigurationChangeListener entry : configChangeListeners) { - if (listener.equals(entry)) { - ConfigStoreListener storeListener = null; - Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (configListeners != null) { - storeListener = configListeners.get(listener); - configListeners.remove(entry); - } - if (storeListener != null) { - configStoreManager.removeConfigListener(CURRENT_NAMESPACE, dataId, storeListener); - } - break; - } - } - } - } - - @Override - public Set getConfigListeners(String dataId) { - ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (CollectionUtils.isNotEmpty(configListeners)) { - return configListeners.keySet(); - } else { - return null; - } - } - - - /** - * the type config change listener for raft config store - */ - private static class ConfigStoreListener implements ConfigurationChangeListener { - private final String namespace; - private final String dataId; - private final ConfigurationChangeListener listener; - - public ConfigStoreListener(String namespace, String dataId, ConfigurationChangeListener listener) { - this.namespace = namespace; - this.dataId = dataId; - this.listener = listener; - } - - @Override - public void onChangeEvent(ConfigurationChangeEvent event) { - if (CURRENT_DATA_ID.equals(event.getDataId())) { - Properties seataConfigNew = new Properties(); - seataConfigNew.putAll(configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID)); - - //Get all the monitored dataids and judge whether it has been modified - for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { - String listenedDataId = entry.getKey(); - String propertyOld = seataConfig.getProperty(listenedDataId, ""); - String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); - if (!propertyOld.equals(propertyNew)) { - ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() - .setDataId(listenedDataId) - .setNewValue(propertyNew) - .setNamespace(CURRENT_NAMESPACE) - .setChangeType(ConfigurationChangeType.MODIFY); - - ConcurrentMap configListeners = entry.getValue(); - for (ConfigurationChangeListener configListener : configListeners.keySet()) { - configListener.onProcessEvent(newEvent); - } - } - } - seataConfig = seataConfigNew; - return; - } - // Compatible with old writing - listener.onProcessEvent(event); - } - } -} diff --git a/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider deleted file mode 100644 index 589333e6a30..00000000000 --- a/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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. -# -org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider deleted file mode 100644 index 589333e6a30..00000000000 --- a/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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. -# -org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-raft/src/test/resources/registry.conf b/config/seata-config-raft/src/test/resources/registry.conf deleted file mode 100644 index 22313ebf407..00000000000 --- a/config/seata-config-raft/src/test/resources/registry.conf +++ /dev/null @@ -1,101 +0,0 @@ -# -# 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. -# - -registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa - type = "file" - - nacos { - serverAddr = "localhost" - namespace = "" - cluster = "default" - } - eureka { - serviceUrl = "http://localhost:8761/eureka" - application = "default" - weight = "1" - } - redis { - serverAddr = "localhost:6379" - db = "0" - } - zk { - cluster = "default" - serverAddr = "127.0.0.1:2181" - sessionTimeout = 6000 - connectTimeout = 2000 - } - consul { - cluster = "default" - serverAddr = "127.0.0.1:8500" - } - etcd3 { - cluster = "default" - serverAddr = "http://localhost:2379" - } - sofa { - serverAddr = "127.0.0.1:9603" - application = "default" - region = "DEFAULT_ZONE" - datacenter = "DefaultDataCenter" - cluster = "default" - group = "SEATA_GROUP" - addressWaitTime = "3000" - } - file { - name = "file.conf" - } -} - -config { - # file、nacos 、apollo、zk、consul、etcd3、raft - type = "file" - - nacos { - serverAddr = "localhost" - namespace = "" - } - consul { - serverAddr = "127.0.0.1:8500" - } - apollo { - appId = "seata-server" - apolloMeta = "http://192.168.1.204:8801" - } - zk { - serverAddr = "127.0.0.1:2181" - sessionTimeout = 6000 - connectTimeout = 2000 - } - etcd3 { - serverAddr = "http://localhost:2379" - } - file { - name = "file.conf" - } - db { - type = "rocksdb" - dir = "configStore" - destroyOnShutdown = false - group = "SEATA_GROUP" - } - raft { - serverAddr = "127.0.0.1:7091" - username = "seata" - password = "seata" - } -} diff --git a/console/src/main/resources/static/console-fe/package.json b/console/src/main/resources/static/console-fe/package.json index 568d556759a..7acb85de0bd 100644 --- a/console/src/main/resources/static/console-fe/package.json +++ b/console/src/main/resources/static/console-fe/package.json @@ -78,7 +78,6 @@ "@alicloud/console-components-actions": "^1.1.1", "@alicloud/console-components-app-layout": "^1.1.4", "@alicloud/console-components-console-menu": "^1.2.12", - "@alifd/next": "^1.24.18", "@babel/traverse": "^7.23.7", "axios": "^1.7.4", "browserify-sign": "^4.2.2", diff --git a/console/src/main/resources/static/console-fe/src/app.tsx b/console/src/main/resources/static/console-fe/src/app.tsx index b003efcbd52..d3bac4a24d0 100644 --- a/console/src/main/resources/static/console-fe/src/app.tsx +++ b/console/src/main/resources/static/console-fe/src/app.tsx @@ -78,8 +78,7 @@ class App extends React.Component { get menu() { const { locale }: AppPropsType = this.props; const { MenuRouter = {} } = locale; - const { overview, transactionInfo, globalLockInfo, configInfo, sagaStatemachineDesigner } = MenuRouter; - + const { overview, transactionInfo, globalLockInfo, sagaStatemachineDesigner } = MenuRouter; return { items: [ // { @@ -94,10 +93,6 @@ class App extends React.Component { key: '/globallock/list', label: globalLockInfo, }, - { - key: '/config/list', - label: configInfo, - }, { key: '/sagastatemachinedesigner', label: sagaStatemachineDesigner, diff --git a/console/src/main/resources/static/console-fe/src/locales/en-us.ts b/console/src/main/resources/static/console-fe/src/locales/en-us.ts index ce64d4ab78b..58e3a8fba06 100644 --- a/console/src/main/resources/static/console-fe/src/locales/en-us.ts +++ b/console/src/main/resources/static/console-fe/src/locales/en-us.ts @@ -22,7 +22,6 @@ const enUs: ILocale = { overview: 'Overview', transactionInfo: 'TransactionInfo', globalLockInfo: 'GlobalLockInfo', - configInfo: 'ConfigurationInfo', sagaStatemachineDesigner: 'SagaStatemachineDesigner', }, Header: { @@ -71,30 +70,6 @@ const enUs: ILocale = { resetButtonLabel: 'Reset', searchButtonLabel: 'Search', }, - ConfigInfo: { - title: 'ConfigurationInfo', - subTitle: 'list', - resetButtonLabel: 'Reset', - searchButtonLabel: 'Search', - createButtonLabel: 'Create', - editButtonLabel: 'Edit', - deleteButtonLabel: 'Delete', - clearButtonLabel: 'Clear', - uploadButtonLabel: 'Upload', - operateTitle: 'Actions', - disableTitle: 'This page is only available if the Configuration Center type is raft mode', - editTitle: 'Edit Config', - deleteTitle: 'Delete Config', - deleteConfirmLabel: 'Are you sure you want to delete the configuration item with key: ', - deleteAllConfirmLabel: 'Are you sure you want to clear all configuration items in the following namespace and dataId: ', - addTitle: 'Add Config', - operationSuccess: 'Operation Success!', - operationFailed: 'Operation Failed', - inputFilterPlaceholder: 'Please select filter criteria', - fieldFillingTips: 'Please fill in the required fields', - uploadTitle: 'Upload Config', - uploadFileButtonLabel: 'Upload File', - }, }; export default enUs; diff --git a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts index 5b367720152..e2ed430d514 100644 --- a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts +++ b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts @@ -22,7 +22,6 @@ const zhCn: ILocale = { overview: '概览', transactionInfo: '事务信息', globalLockInfo: '全局锁信息', - configInfo:'配置信息', sagaStatemachineDesigner: 'Saga状态机设计器', }, Header: { @@ -71,30 +70,6 @@ const zhCn: ILocale = { resetButtonLabel: '重置', searchButtonLabel: '搜索', }, - ConfigInfo: { - title: '配置信息', - subTitle: '配置列表页', - resetButtonLabel: '重置', - searchButtonLabel: '搜索', - createButtonLabel: '创建', - editButtonLabel: '编辑', - deleteButtonLabel: '删除', - clearButtonLabel: '清空', - uploadButtonLabel: '上传', - operateTitle: '操作', - disableTitle: '该页面仅在配置中心类型为raft模式下可用', - editTitle: '编辑配置', - deleteTitle: '删除配置', - deleteConfirmLabel: '确认需要删除以下配置项: ', - deleteAllConfirmLabel: '确认需要清空以下namespace和dataId中的所有配置项: ', - addTitle: '新增配置', - operationSuccess: '操作成功!', - operationFailed: '操作失败', - inputFilterPlaceholder: '请选择筛选条件', - fieldFillingTips: '请将必要字段填充完整', - uploadTitle: '上传配置', - uploadFileButtonLabel: '上传文件', - }, }; export default zhCn; diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx deleted file mode 100644 index 47567171a5c..00000000000 --- a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx +++ /dev/null @@ -1,644 +0,0 @@ -/* - * 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. - */ -import React from 'react'; -import { - ConfigProvider, - Table, - Button, - DatePicker, - Form, - Icon, - Pagination, - Input, - Dialog, - Message, - Select, - Upload, -} from '@alicloud/console-components'; -import { withRouter } from 'react-router-dom'; - -import {getConfig, getClusterInfo, putConfig, deleteConfig, deleteAllConfig, getAllNamespaces, getAllDataIds, uploadConfig} from "@/service/configInfo"; -import Page from '@/components/Page'; -import { GlobalProps } from '@/module'; -import styled, { css } from 'styled-components'; -import PropTypes from 'prop-types'; -import './index.scss'; - -import moment from "moment/moment"; -type ConfigInfoState = { - configList: Array; - editDialogVisible: boolean; - deleteDialogVisible: boolean; - uploadDialogVisible: boolean; - loading: boolean; - configParam: ConfigParam; - editDialogInfo: DialogInfo; - deleteDialogInfo: DeleteDialogInfo; - uploadDialogInfo: UploadDialogInfo; - isRaft: boolean; - namespaces: Array; - dataIds: Array; -} -export type ConfigParam = { - namespace: string, - dataId: string, -}; - -type DialogInfo = { - isEdit: boolean; - namespace: string; - dataId: string; - key: string; - value: string; -} - -type DeleteDialogInfo = { - namespace: string; - dataId: string; -} - -type UploadDialogInfo = { - namespace: string; - dataId: string; - file: File; -} - -const FormItem = Form.Item; - - - -class ConfigInfo extends React.Component { - static displayName = 'ConfigInfo'; - - static propTypes = { - locale: PropTypes.object, - history: PropTypes.object, - }; - - state: ConfigInfoState = { - configList: [], - loading: false, - editDialogVisible: false, - deleteDialogVisible: false, - uploadDialogVisible: false, - namespaces: [], - dataIds: [], - configParam: { - namespace: '', - dataId: '', - }, - editDialogInfo: { - isEdit: false, - namespace: '', - dataId: '', - key: '', - value: '', - }, - deleteDialogInfo: { - namespace: '', - dataId: '', - }, - uploadDialogInfo: { - namespace: '', - dataId: '', - file: null, - }, - isRaft: false, - } - - componentDidMount() { - this.init(); - this.pollingInterval = setInterval(this.refreshConfigData, 10000); // 每10秒刷新一次 - } - componentWillUnmount() { - clearInterval(this.pollingInterval); - } - - init = async () => { - const { disableTitle } = this.props.locale; - this.setState({ loading: true }); - try { - const response = await getClusterInfo(); - const raftMode = response.configMode - if (raftMode === 'raft') { - this.setState({ isRaft: true, loading: false }); - this.fetchNamespaces(); - } else { - this.setState({ loading: false }); - Message.error(disableTitle); - setTimeout(() => this.props.history.goBack(), 1000); - } - //this.setState({ clusterInfo: result, loading: false }); - } catch (error) { - Message.error('Failed to fetch cluster info'); - this.setState({ loading: false }); - this.props.history.goBack(); - } - } - - refreshConfigData = () => { - this.fetchNamespaces(); - if (this.state.configParam.namespace) { - this.fetchDataIds(this.state.configParam.namespace); - } - } - fetchNamespaces = async () => { - try { - const response = await getAllNamespaces(); - const result = response.result; - this.setState({ namespaces: result }); - } catch (error) { - Message.error('Failed to fetch namespace list'); - } - } - - fetchDataIds = async (namespace: string) => { - try { - const response = await getAllDataIds({ namespace }); - const result = response.result; - this.setState({ dataIds: result }); - } catch (error) { - Message.error('Failed to fetch dataIds'); - } - } - - fetchConfigList = async () => { - this.setState({ loading: true }); - try { - const response = await getConfig({namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}); - if (response.success && response.result){ - const { config } = response.result; - console.log(config); - const configList = Object.keys(config).map((key) => ({ ...config[key] })); - this.setState({ configList, loading: false }); - } else { - Message.error(response.errMsg || 'Failed to fetch config list'); - this.setState({ loading: false }); - } - } catch (error) { - Message.error('Failed to fetch config list'); - this.setState({ loading: false }); - } - } - searchFilterOnChange = async (key:string, val:string) => { - this.setState({ - configParam: Object.assign(this.state.configParam, - { [key]: val }), - }); - if (key === 'namespace') { - this.setState({ - configParam: Object.assign(this.state.configParam, - { dataId: '' }), - }); - await this.fetchDataIds(val); - } - } - search = () => { - this.fetchConfigList(); - } - resetSearchFilter = () => { - this.setState({ - configParam: { - // pagination info don`t reset - namespace: '', - dataId: '', - }, - }); - } - - resetDialog = () => { - this.setState({ - editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}, - deleteDialogInfo: {namespace: '', dataId: ''}, - uploadDialogInfo: {namespace: '', dataId: '', file: null}, - }); - }; - openEditDialog = (config: { key: string; value: string }) => { - this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, ...config}}); - }; - - openDeleteDialog = () => { - this.setState({ deleteDialogVisible: true, deleteDialogInfo: {namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}}); - } - - openUploadDialog = () => { - this.setState({ uploadDialogVisible: true, uploadDialogInfo: {namespace: this.state.configParam.namespace, dataId: '', file: null}}); - } - - createConfig = () => { - this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: false, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}}); - }; - - closeDialog = () => { - this.setState({ editDialogVisible: false, deleteDialogVisible: false, uploadDialogVisible: false}); - this.resetDialog(); - }; - - handleAddOrEditConfig = async () => { - const { operationSuccess, operationFail } = this.props.locale; - const { editDialogInfo } = this.state; - try { - const response =await putConfig({ - namespace: editDialogInfo.namespace, - dataId: editDialogInfo.dataId, - key: editDialogInfo.key, - value: editDialogInfo.value, - }); - if (response.success) { - Message.success(operationSuccess); - this.setState({ - editDialogVisible: false, - configParam: { - namespace: editDialogInfo.namespace, - dataId: editDialogInfo.dataId, - } - }); - this.fetchNamespaces(); - this.fetchDataIds(editDialogInfo.namespace); - this.fetchConfigList(); - } else { - Message.error(response.errMsg || operationFail); - } - } catch (error) { - Message.error(operationFail); - } - } - - handleDeleteConfig = async (record: { key: string }) => { - const { deleteTitle, deleteConfirmLabel, operationSuccess, operationFail } = this.props.locale; - Dialog.confirm({ - title: deleteTitle, - content: deleteConfirmLabel + `${record.key} ?`, - onOk: async () => { - try { - const response = await deleteConfig({ namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: record.key }); - if (response.success) { - Message.success(operationSuccess); - this.fetchConfigList(); - } else { - Message.error(response.errMsg || operationFail); - } - } catch (error) { - Message.error(operationFail); - } - }, - onCancel: () => { - - }, - }); - } - - handleDeleteAllConfig = async () => { - const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; - const { namespace, dataId } = this.state.deleteDialogInfo; - - if (!namespace || !dataId) { - Message.error(fieldFillingTips); - return; - } - try { - const response = await deleteAllConfig({ namespace, dataId }); - if (response.success) { - Message.success(operationSuccess); - this.setState({ - configParam: { - namespace: namespace, - dataId: dataId, - }, - deleteDialogVisible: false, - deleteDialogInfo: { namespace: '', dataId: '' }, - }); - this.fetchDataIds(namespace) - this.fetchConfigList(); - } else { - Message.error(response.errMsg || operationFail); - } - } catch (error) { - Message.error(operationFail); - } - } - - handleUploadConfig = async () => { - const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; - const { namespace, dataId,file } = this.state.uploadDialogInfo; - if (!namespace || !dataId || !file) { - Message.error(fieldFillingTips); - return; - } - const formData = new FormData(); - formData.append('namespace', namespace); - formData.append('dataId', dataId); - formData.append('file', file); - - try { - const response = await uploadConfig(formData); - if (response.success) { - Message.success(operationSuccess); - this.setState({ - uploadDialogVisible: false, - uploadDialogInfo: { namespace: '', dataId: '', file: null}, - configParam: { - namespace: namespace, - dataId: dataId, - } - }); - this.fetchNamespaces(); - this.fetchDataIds(namespace) - this.fetchConfigList(); - } else { - Message.error(response.errMsg || operationFail); - } - } catch (error) { - Message.error(operationFail); - } - } - - handleDialogInputChange = (key: string, value: string) => { - this.setState((prevState) => ({ - editDialogInfo: { - ...prevState.editDialogInfo, - [key]: value, - }, - })); - }; - - handleDeleteDialogInputChange = (key: string, value: string) => { - this.setState((prevState) => ({ - deleteDialogInfo: { - ...prevState.deleteDialogInfo, - [key]: value - } - })) - } - - handleUploadDialogInputChange = (key: string, value: string) => { - this.setState((prevState) => ({ - uploadDialogInfo: { - ...prevState.uploadDialogInfo, - [key]: value - } - })) - } - - handleFileInputChange = (fileList: Array) => { - const file = fileList.length > 0 ? fileList[0] : null; - if (file && file.originFileObj) { - this.handleUploadDialogInputChange('file', file.originFileObj); - } - } - render() { - const { locale = {} } = this.props; - const { title, subTitle, - searchButtonLabel, - resetButtonLabel, - createButtonLabel, - clearButtonLabel, - uploadButtonLabel, - operateTitle, - editTitle, - deleteTitle, - uploadTitle, - deleteAllConfirmLabel, - editButtonLabel, - deleteButtonLabel, - inputFilterPlaceholder, - uploadFileButtonLabel, - } = locale; - - return ( - - {/* search form */} -
- {/* {search filters} */} - - { this.searchFilterOnChange('dataId', value); }} - dataSource={this.state.dataIds} - style={{ width: 200 }} - hasClear={true} - /> - - - {/* {reset search filter button} */} - - - {resetButtonLabel} - - - {/* {search button} */} - - - {searchButtonLabel} - - - - - {createButtonLabel} - - - - - {uploadButtonLabel} - - - - - {clearButtonLabel} - - -
- - {/* config info table */} -
- - - - - - ( - <> - - - - )} - /> -
-
- {/* config edit dialog */} - -
- - this.handleDialogInputChange('namespace', value)} - /> - - - this.handleDialogInputChange('dataId', value)} - /> - - - this.handleDialogInputChange('key', value)} - /> - - - this.handleDialogInputChange('value', value)} - /> - -
-
- - {/* config delete dialog*/} - -
- - - { - this.handleDeleteDialogInputChange('namespace', value)} - } - /> - - - { - this.handleDeleteDialogInputChange('dataId', value)} - } - /> - -
-
- - {/* config upload dialog*/} - -
- - - this.handleUploadDialogInputChange('namespace', value)} - /> - - - this.handleUploadDialogInputChange('dataId', value)} - /> - - - false} // Prevent auto-upload - accept={".txt,.text,.yaml,.yml,.properties"} - limit={1} - > - - - -
-
-
- ); - } -} -export default withRouter(ConfigProvider.config(ConfigInfo, {})); diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss deleted file mode 100644 index 2944f981947..00000000000 --- a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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. - */ diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts deleted file mode 100644 index d30c8ca5a93..00000000000 --- a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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. - */ -import ConfigInfo from './ConfigInfo'; - -export * from './ConfigInfo'; - -export default ConfigInfo; diff --git a/console/src/main/resources/static/console-fe/src/router.tsx b/console/src/main/resources/static/console-fe/src/router.tsx index 88c7be6b920..d881b472d02 100644 --- a/console/src/main/resources/static/console-fe/src/router.tsx +++ b/console/src/main/resources/static/console-fe/src/router.tsx @@ -18,12 +18,10 @@ import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; import Overview from '@/pages/Overview'; import TransactionInfo from '@/pages/TransactionInfo'; import GlobalLockInfo from './pages/GlobalLockInfo'; -import ConfigInfo from "./pages/ConfigInfo"; export default [ // { path: '/', exact: true, render: () => }, // { path: '/Overview', component: Overview }, { path: '/transaction/list', component: TransactionInfo }, { path: '/globallock/list', component: GlobalLockInfo }, - { path: '/config/list', component: ConfigInfo }, ]; diff --git a/console/src/main/resources/static/console-fe/src/service/configInfo.ts b/console/src/main/resources/static/console-fe/src/service/configInfo.ts deleted file mode 100644 index 37e4658d689..00000000000 --- a/console/src/main/resources/static/console-fe/src/service/configInfo.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ -import {configRequest} from '@/utils/request'; - - -export async function getConfig(params: { namespace: string, dataId: string}): Promise { - const result = await configRequest('/config/getAll', { - method: 'get', - params, - }); - return result; -} - -export async function putConfig(params: { namespace: string, dataId: string, key: string, value: string}): Promise { - const result = await configRequest('/config/put', { - method: 'post', - params, - }); - return result; -} - -export async function deleteConfig(params: { namespace: string, dataId: string, key: string }): Promise { - const result = await configRequest('/config/delete', { - method: 'delete', - params, - }); - return result; -} - -export async function deleteAllConfig(params: { namespace: string, dataId: string}): Promise { - const result = await configRequest('/config/deleteAll', { - method: 'delete', - params, - }); - return result; -} - -export async function uploadConfig(formData: FormData): Promise { - const result = await configRequest('/config/upload', { - method: 'post', - data: formData, - }); - return result; -} - -export async function getClusterInfo(): Promise { - const result = await configRequest('/config/cluster', { - method: 'get', - }); - return result; -} - -export async function getAllNamespaces(): Promise { - const result = await configRequest('/config/getNamespaces', { - method: 'get', - }); - return result; -} - -export async function getAllDataIds(params: { namespace: string}): Promise { - const result = await configRequest('/config/getDataIds', { - method: 'get', - params, - }); - return result; -} diff --git a/console/src/main/resources/static/console-fe/src/utils/request.ts b/console/src/main/resources/static/console-fe/src/utils/request.ts index e28c6a2e5c6..47ab7597615 100644 --- a/console/src/main/resources/static/console-fe/src/utils/request.ts +++ b/console/src/main/resources/static/console-fe/src/utils/request.ts @@ -90,50 +90,3 @@ const request = () => { }; export default request(); - - -const clusterRequest = () => { - const instance: AxiosInstance = axios.create({ - baseURL: 'metadata/v1', - method: 'get', - }); - - instance.interceptors.request.use((config: AxiosRequestConfig) => { - let authHeader: string | null = localStorage.getItem(AUTHORIZATION_HEADER); - // add jwt header - config.headers[AUTHORIZATION_HEADER] = authHeader; - return config; - }) - - instance.interceptors.response.use( - (response: AxiosResponse): Promise => { - const isSuccess = get(response, 'data.success'); - if (response.status === 200 || isSuccess) { - return Promise.resolve(get(response, 'data')); - } else { - const errorText = - get(response, 'data.errMsg') || - response.statusText; - Message.error(errorText); - return Promise.reject(response); - } - }, - error => { - if (error.response) { - const { status } = error.response; - if (status === 403 || status === 401) { - (window as any).globalHistory.replace('/login'); - return; - } - Message.error(`HTTP ERROR: ${status}`); - } else { - Message.error(API_GENERAL_ERROR_MESSAGE); - } - return Promise.reject(error); - } - ); - - return instance; -}; - -export const configRequest = clusterRequest(); diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 4f54761c851..15178f14166 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -76,8 +76,6 @@ 4.1.101.Final 4.0.3 1.6.7 - 8.8.1 - 3.25.4 1.66.0 5.4.0 @@ -236,11 +234,6 @@ bolt ${sofa.bolt.version} - - org.rocksdb - rocksdbjni - ${rocksdbjni.version} - com.alibaba fastjson diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java index c9a451f7228..67bc7bd75ab 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java @@ -17,7 +17,6 @@ package org.apache.seata.spring.boot.autoconfigure; import java.util.concurrent.atomic.AtomicBoolean; - import org.apache.seata.spring.boot.autoconfigure.properties.LogProperties; import org.apache.seata.spring.boot.autoconfigure.properties.ShutdownProperties; import org.apache.seata.spring.boot.autoconfigure.properties.ThreadFactoryProperties; @@ -29,8 +28,6 @@ import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigFileProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigNacosProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigRaftProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigStoreProperties; import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigZooKeeperProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryConsulProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryCustomProperties; @@ -48,7 +45,6 @@ import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; - import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_APOLLO_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CONSUL_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CUSTOM_PREFIX; @@ -56,8 +52,6 @@ import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_FILE_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_NACOS_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_RAFT_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_STORE_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_ZK_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.LOG_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.PROPERTY_BEAN_MAP; @@ -76,6 +70,7 @@ import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.THREAD_FACTORY_PREFIX; import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.TRANSPORT_PREFIX; + public class SeataCoreEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { private static final AtomicBoolean INIT = new AtomicBoolean(false); @@ -102,8 +97,6 @@ public static void init() { PROPERTY_BEAN_MAP.put(CONFIG_APOLLO_PREFIX, ConfigApolloProperties.class); PROPERTY_BEAN_MAP.put(CONFIG_ETCD3_PREFIX, ConfigEtcd3Properties.class); PROPERTY_BEAN_MAP.put(CONFIG_CUSTOM_PREFIX, ConfigCustomProperties.class); - PROPERTY_BEAN_MAP.put(CONFIG_STORE_PREFIX, ConfigStoreProperties.class); - PROPERTY_BEAN_MAP.put(CONFIG_RAFT_PREFIX, ConfigRaftProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_CONSUL_PREFIX, RegistryConsulProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_ETCD3_PREFIX, RegistryEtcd3Properties.class); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java index 45d66c91a94..5ec088d43ff 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java @@ -66,8 +66,8 @@ public interface StarterConstants { String CONFIG_ZK_PREFIX = CONFIG_PREFIX + ".zk"; String CONFIG_FILE_PREFIX = CONFIG_PREFIX + ".file"; String CONFIG_CUSTOM_PREFIX = CONFIG_PREFIX + ".custom"; - String CONFIG_RAFT_PREFIX = CONFIG_PREFIX + ".raft"; - String CONFIG_STORE_PREFIX = CONFIG_RAFT_PREFIX + ".db"; + + String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; String SERVER_RAFT_PREFIX = SERVER_PREFIX + ".raft"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java deleted file mode 100644 index af2fff40e9b..00000000000 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_RAFT_PREFIX; - -@Component -@ConfigurationProperties(prefix = CONFIG_RAFT_PREFIX) -public class ConfigRaftProperties { - private String serverAddr; - - private Long metadataMaxAgeMs = 30000L; - - private String username; - - private String password; - - private Long tokenValidityInMilliseconds = 29 * 60 * 1000L; - - public Long getMetadataMaxAgeMs() { - return metadataMaxAgeMs; - } - - public ConfigRaftProperties setMetadataMaxAgeMs(Long metadataMaxAgeMs) { - this.metadataMaxAgeMs = metadataMaxAgeMs; - return this; - } - - public String getUsername() { - return username; - } - - public ConfigRaftProperties setUsername(String username) { - this.username = username; - return this; - } - - public String getPassword() { - return password; - } - - public ConfigRaftProperties setPassword(String password) { - this.password = password; - return this; - } - - public Long getTokenValidityInMilliseconds() { - return tokenValidityInMilliseconds; - } - - public ConfigRaftProperties setTokenValidityInMilliseconds(Long tokenValidityInMilliseconds) { - this.tokenValidityInMilliseconds = tokenValidityInMilliseconds; - return this; - } - - public String getServerAddr() { - return serverAddr; - } - - public ConfigRaftProperties setServerAddr(String serverAddr) { - this.serverAddr = serverAddr; - return this; - } - -} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java deleted file mode 100644 index 3e361ba1f3f..00000000000 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_STORE_PREFIX; - - -@Component -@ConfigurationProperties(prefix = CONFIG_STORE_PREFIX) -public class ConfigStoreProperties { - /** - * rocksdb, (leveldb, caffeine) - */ - private String type = "rocksdb"; - private String dir = "configStore"; - private boolean destroyOnShutdown = false; - private String namespace = "default"; - private String dataId = "seata.properties"; - - public String getType() { - return type; - } - - public ConfigStoreProperties setType(String type) { - this.type = type; - return this; - } - - public String getDir() { - return dir; - } - - public ConfigStoreProperties setDir(String dir) { - this.dir = dir; - return this; - } - - public boolean isDestroyOnShutdown() { - return destroyOnShutdown; - } - - public ConfigStoreProperties setDestroyOnShutdown(boolean destroyOnShutdown) { - this.destroyOnShutdown = destroyOnShutdown; - return this; - } - - public String getNamespace() { - return namespace; - } - - public ConfigStoreProperties setNamespace(String namespace) { - this.namespace = namespace; - return this; - } - - public String getDataId() { - return dataId; - } - - public ConfigStoreProperties setDataId(String dataId) { - this.dataId = dataId; - return this; - } -} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java deleted file mode 100644 index 62afe4d6b37..00000000000 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftPropertiesTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; - -import org.apache.seata.common.loader.EnhancedServiceLoader; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ExtConfigurationProvider; -import org.apache.seata.config.FileConfiguration; -import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; -import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; - - -@org.springframework.context.annotation.Configuration -@Import(SpringApplicationContextProvider.class) -public class ConfigRaftPropertiesTest extends BasePropertiesTest { - @Bean("testConfigRaftProperties") - public ConfigRaftProperties configRaftProperties() { - return new ConfigRaftProperties().setUsername(STR_TEST_AAA).setPassword(STR_TEST_BBB).setServerAddr(STR_TEST_CCC).setMetadataMaxAgeMs((long)LONG_TEST_ONE).setTokenValidityInMilliseconds((long)LONG_TEST_TWO); - } - - @Test - public void testConfigRaftProperties() { - FileConfiguration configuration = mock(FileConfiguration.class); - Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - - assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.username")); - assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.password")); - assertEquals(STR_TEST_CCC, currentConfiguration.getConfig("config.raft.serverAddr")); - assertEquals(LONG_TEST_ONE, currentConfiguration.getInt("config.raft.metadataMaxAgeMs")); - assertEquals(LONG_TEST_TWO, currentConfiguration.getInt("config.raft.tokenValidityInMilliseconds")); - - } -} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java deleted file mode 100644 index e1f16469d47..00000000000 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStorePropertiesTest.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 org.apache.seata.spring.boot.autoconfigure.properties.config; - -import org.apache.seata.common.loader.EnhancedServiceLoader; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ExtConfigurationProvider; -import org.apache.seata.config.FileConfiguration; -import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; -import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.Mockito.mock; - -@org.springframework.context.annotation.Configuration -@Import(SpringApplicationContextProvider.class) -public class ConfigStorePropertiesTest extends BasePropertiesTest { - @Bean("testConfigStoreProperties") - public ConfigStoreProperties configStoreProperties() { - return new ConfigStoreProperties().setType(STR_TEST_AAA).setDir(STR_TEST_BBB).setDestroyOnShutdown(false).setNamespace(STR_TEST_DDD).setDataId(STR_TEST_EEE); - } - - @Test - public void testConfigStoreProperties() { - FileConfiguration configuration = mock(FileConfiguration.class); - Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); - - assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.db.type")); - assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.db.dir")); - assertFalse(currentConfiguration.getBoolean("config.raft.db.destroyOnShutdown")); - assertEquals(STR_TEST_DDD, currentConfiguration.getConfig("config.raft.db.namespace")); - assertEquals(STR_TEST_EEE, currentConfiguration.getConfig("config.raft.db.dataId")); - } -} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties index 27d7e4143bd..8467d6cc7b9 100755 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties @@ -51,15 +51,3 @@ seata.config.zk.password=ddd seata.config.zk.node-path=aaa seata.config.custom.name=aaa - -seata.config.raft.db.type=aaa -seata.config.raft.db.dir=bbb -seata.config.raft.db.destroy-on-shutdown=false -seata.config.raft.db.namespace=ddd -seata.config.raft.db.data-id=eee - -seata.config.raft.username=aaa -seata.config.raft.password=bbb -seata.config.raft.server-addr=ccc -seata.config.raft.metadata-max-age-ms=1 -seata.config.raft.token-validity-in-milliseconds=2 diff --git a/server/src/main/java/org/apache/seata/server/Server.java b/server/src/main/java/org/apache/seata/server/Server.java index b10379a78b8..c699ef037b1 100644 --- a/server/src/main/java/org/apache/seata/server/Server.java +++ b/server/src/main/java/org/apache/seata/server/Server.java @@ -108,8 +108,6 @@ public void start(String[] args) { serverInstance.serverInstanceInit(); // let ServerRunner do destroy instead ShutdownHook, see https://github.com/seata/seata/issues/4028 - - ServerRunner.addToFirstDisposable(coordinator); ServerRunner.addDisposable(coordinator); nettyRemotingServer.init(); } diff --git a/server/src/main/java/org/apache/seata/server/ServerApplication.java b/server/src/main/java/org/apache/seata/server/ServerApplication.java index 9c5e1ccf86b..952187137e3 100644 --- a/server/src/main/java/org/apache/seata/server/ServerApplication.java +++ b/server/src/main/java/org/apache/seata/server/ServerApplication.java @@ -21,15 +21,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import static org.apache.seata.common.Constants.APPLICATION_TYPE_KEY; -import static org.apache.seata.common.Constants.APPLICATION_TYPE_SERVER; - /** */ @SpringBootApplication(scanBasePackages = {"org.apache.seata"}) public class ServerApplication { public static void main(String[] args) throws IOException { - System.setProperty(APPLICATION_TYPE_KEY, APPLICATION_TYPE_SERVER); // run the spring-boot application SpringApplication.run(ServerApplication.class, args); } diff --git a/server/src/main/java/org/apache/seata/server/ServerRunner.java b/server/src/main/java/org/apache/seata/server/ServerRunner.java index ba0fe8066dd..a48c7379fdf 100644 --- a/server/src/main/java/org/apache/seata/server/ServerRunner.java +++ b/server/src/main/java/org/apache/seata/server/ServerRunner.java @@ -54,14 +54,9 @@ public static void addDisposable(Disposable disposable) { DISPOSABLE_LIST.add(disposable); } - public static void addToFirstDisposable(Disposable disposable) { - DISPOSABLE_LIST.add(0, disposable); - } - @Resource Server seataServer; - @Override public void run(String... args) { try { @@ -88,7 +83,7 @@ public boolean started() { public void destroy() throws Exception { if (LOGGER.isDebugEnabled()) { - LOGGER.debug("destory All starting"); + LOGGER.debug("destoryAll starting"); } for (Disposable disposable : DISPOSABLE_LIST) { @@ -96,7 +91,7 @@ public void destroy() throws Exception { } if (LOGGER.isDebugEnabled()) { - LOGGER.debug("destory All finish"); + LOGGER.debug("destoryAll finish"); } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java deleted file mode 100644 index fd9461bcdbd..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.listener; - -import org.springframework.context.ApplicationEvent; - -/** - * The type ClusterConfigChangeEvent - */ -public class ClusterConfigChangeEvent extends ApplicationEvent { - - private String namespace; - private String dataId; - - public ClusterConfigChangeEvent(Object source, String namespace, String dataId) { - super(source); - this.namespace = namespace; - this.dataId = dataId; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java deleted file mode 100644 index 4236a15bc71..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.listener; - -public interface ClusterConfigChangeListener { - - /** - * cluster config change event - * @param event event - */ - void onChangeEvent(ClusterConfigChangeEvent event); -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java deleted file mode 100644 index 4e81fbff1e5..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.manager; - -import java.util.Map; -import java.util.Optional; -import java.util.Queue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PostConstruct; -import javax.servlet.AsyncContext; -import javax.servlet.http.HttpServletResponse; - -import org.apache.seata.common.thread.NamedThreadFactory; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; -import org.apache.seata.server.cluster.listener.ClusterConfigChangeListener; -import org.apache.seata.server.cluster.watch.ConfigWatcher; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.event.EventListener; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -/** - * - * The type of cluster config watcher manager. - */ -@Component -public class ClusterConfigWatcherManager implements ClusterConfigChangeListener { - private static final Logger LOGGER = LoggerFactory.getLogger(ClusterConfigWatcherManager.class); - - private static final Map>>> WATCHERS = new ConcurrentHashMap<>(); - - private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = - new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("long-polling", 1)); - - @PostConstruct - public void init() { - // Responds to monitors that time out - scheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> { - for (String namespace : WATCHERS.keySet()) { - Map>> dataIdWatchersMap = WATCHERS.get(namespace); - for (String dataId : dataIdWatchersMap.keySet()) { - Optional.ofNullable(dataIdWatchersMap.remove(dataId)) - .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { - if (System.currentTimeMillis() >= watcher.getTimeout()) { - HttpServletResponse httpServletResponse = - (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); - watcher.setDone(true); - httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - ((AsyncContext)watcher.getAsyncContext()).complete(); - } - if (!watcher.isDone()) { - // Re-register - registryWatcher(watcher); - } - })); - } - } - }, 1, 1, TimeUnit.SECONDS); - } - @Override - @EventListener - @Async - public void onChangeEvent(ClusterConfigChangeEvent event) { - String namespace = event.getNamespace(); - String dataId = event.getDataId(); - Map>> dataIdWatchersMap = WATCHERS.get(namespace); - if (CollectionUtils.isNotEmpty(dataIdWatchersMap)) { - Optional.ofNullable(dataIdWatchersMap.remove(dataId)) - .ifPresent(watchers -> watchers.parallelStream().forEach(this::notify)); - } - } - - private void notify(ConfigWatcher watcher) { - AsyncContext asyncContext = (AsyncContext)watcher.getAsyncContext(); - HttpServletResponse httpServletResponse = (HttpServletResponse)asyncContext.getResponse(); - watcher.setDone(true); - LOGGER.info("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); - } - httpServletResponse.setStatus(HttpServletResponse.SC_OK); - asyncContext.complete(); - } - - public void registryWatcher(ConfigWatcher watcher) { - String namespace = watcher.getNamespace(); - String dataId = watcher.getDataId(); - WATCHERS.computeIfAbsent(namespace, ns -> new ConcurrentHashMap<>()) - .computeIfAbsent(dataId, did -> new ConcurrentLinkedQueue<>()).add(watcher); - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java deleted file mode 100644 index faf0e5ba9ef..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft; - -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import com.alipay.sofa.jraft.Node; -import com.alipay.sofa.jraft.RaftGroupService; -import com.alipay.sofa.jraft.RouteTable; -import com.alipay.sofa.jraft.entity.PeerId; -import com.alipay.sofa.jraft.option.NodeOptions; -import com.alipay.sofa.jraft.rpc.RpcServer; -import com.codahale.metrics.Slf4jReporter; -import org.apache.commons.io.FileUtils; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.core.rpc.Disposable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_ENABLED; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_INITIAL_DELAY; - -public class RaftConfigServer implements Disposable, Closeable { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final RaftConfigStateMachine raftStateMachine; - private final String groupId; - private final String groupPath; - private final NodeOptions nodeOptions; - private final PeerId serverId; - private final RpcServer rpcServer; - private RaftGroupService raftGroupService; - private Node node; - - public RaftConfigServer(final String dataPath, final String groupId, final PeerId serverId, final NodeOptions nodeOptions, final RpcServer rpcServer) - throws IOException { - this.groupId = groupId; - this.groupPath = dataPath + File.separator + groupId; - // Initialize the state machine - this.raftStateMachine = new RaftConfigStateMachine(groupId); - this.nodeOptions = nodeOptions; - this.serverId = serverId; - this.rpcServer = rpcServer; - } - - public void start() throws IOException { - // Initialization path - FileUtils.forceMkdir(new File(groupPath)); - // Set the state machine to startup parameters - nodeOptions.setFsm(this.raftStateMachine); - // Set the storage path - // Log, must - nodeOptions.setLogUri(groupPath + File.separator + "log"); - // Meta information, must - nodeOptions.setRaftMetaUri(groupPath + File.separator + "raft_meta"); - // Snapshot, optional, is generally recommended - nodeOptions.setSnapshotUri(groupPath + File.separator + "snapshot"); - boolean reporterEnabled = ConfigurationFactory.CURRENT_FILE_INSTANCE.getBoolean(SERVER_RAFT_REPORTER_ENABLED, false); - nodeOptions.setEnableMetrics(reporterEnabled); - // Initialize the raft Group service framework - this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); - this.node = this.raftGroupService.start(false); - RouteTable.getInstance().updateConfiguration(groupId, node.getOptions().getInitialConf()); - if (reporterEnabled) { - final Slf4jReporter reporter = Slf4jReporter.forRegistry(node.getNodeMetrics().getMetricRegistry()) - .outputTo(logger).convertRatesTo(TimeUnit.SECONDS) - .convertDurationsTo(TimeUnit.MILLISECONDS).build(); - reporter.start(ConfigurationFactory.CURRENT_FILE_INSTANCE.getInt(SERVER_RAFT_REPORTER_INITIAL_DELAY, 60), - TimeUnit.MINUTES); - } - } - - public Node getNode() { - return this.node; - } - - - public RaftConfigStateMachine getRaftStateMachine() { - return raftStateMachine; - } - - public PeerId getServerId() { - return serverId; - } - - @Override - public void close() { - destroy(); - } - - @Override - public void destroy() { - Optional.ofNullable(raftGroupService).ifPresent(r -> { - r.shutdown(); - try { - r.join(); - } catch (InterruptedException e) { - logger.warn("Interrupted when RaftServer destroying", e); - } - }); - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java deleted file mode 100644 index 8413d2a237c..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft; - -import java.io.IOException; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import com.alipay.remoting.serialization.SerializerManager; -import com.alipay.sofa.jraft.CliService; -import com.alipay.sofa.jraft.RaftServiceFactory; -import com.alipay.sofa.jraft.RouteTable; -import com.alipay.sofa.jraft.conf.Configuration; -import com.alipay.sofa.jraft.entity.PeerId; -import com.alipay.sofa.jraft.option.CliOptions; -import com.alipay.sofa.jraft.option.NodeOptions; -import com.alipay.sofa.jraft.option.RaftOptions; -import com.alipay.sofa.jraft.rpc.CliClientService; -import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; -import com.alipay.sofa.jraft.rpc.RpcServer; -import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.common.XID; -import org.apache.seata.common.util.NetUtil; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.ConfigType; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.store.ConfigStoreManagerFactory; -import org.apache.seata.core.serializer.SerializerType; -import org.apache.seata.server.ServerRunner; -import org.apache.seata.server.cluster.raft.processor.ConfigOperationRequestProcessor; -import org.apache.seata.server.cluster.raft.processor.PutNodeInfoRequestProcessor; -import org.apache.seata.server.cluster.raft.serializer.JacksonBoltSerializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static java.io.File.separator; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DIR; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_APPLY_BATCH; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_DISRUPTOR_BUFFER_SIZE; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_ELECTION_TIMEOUT_MS; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_APPEND_BUFFER_SIZE; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_PORT_CAMEL; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SNAPSHOT_INTERVAL; -import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SYNC; -import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; -import static org.apache.seata.common.DefaultValues.DEFAULT_DB_STORE_FILE_DIR; -import static org.apache.seata.common.DefaultValues.DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGEX_SPLIT_CHAR; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFERED_NETWORKS; - -/** - * The type to manager raft server of config center - */ -public class RaftConfigServerManager { - private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigServerManager.class); - private static final AtomicBoolean INIT = new AtomicBoolean(false); - private static final org.apache.seata.config.Configuration CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static RpcServer rpcServer; - private static RaftConfigServer raftServer; - private static volatile boolean RAFT_MODE; - private static final String GROUP = RAFT_CONFIG_GROUP; - - public static CliService getCliServiceInstance() { - return RaftConfigServerManager.SingletonHandler.CLI_SERVICE; - } - - public static CliClientService getCliClientServiceInstance() { - return RaftConfigServerManager.SingletonHandler.CLI_CLIENT_SERVICE; - } - - public static void init() { - if (INIT.compareAndSet(false, true)) { - String initConfStr = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); - String configTypeName = CONFIG.getConfig(org.apache.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG - + org.apache.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + org.apache.seata.config.ConfigurationKeys.FILE_ROOT_TYPE); - RAFT_MODE = ConfigType.Raft.name().equalsIgnoreCase(configTypeName); - if (!RAFT_MODE) { - return; - } - if (StringUtils.isBlank(initConfStr)) { - if (RAFT_MODE) { - throw new IllegalArgumentException( - "Raft config mode must config: " + ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); - } - return; - } - final Configuration initConf = new Configuration(); - if (!initConf.parse(initConfStr)) { - throw new IllegalArgumentException("fail to parse initConf:" + initConfStr); - } - int port = Integer.parseInt(System.getProperty(SERVER_RAFT_PORT_CAMEL, "0")); - PeerId serverId = null; - // XID may be null when configuration center is not initialized. - String host = null; - if (XID.getIpAddress() == null) { - String preferredNetworks = CONFIG.getConfig(REGISTRY_PREFERED_NETWORKS); - host = StringUtils.isNotBlank(preferredNetworks) ? NetUtil.getLocalIp(preferredNetworks.split(REGEX_SPLIT_CHAR)) : NetUtil.getLocalIp(); - } else { - host = XID.getIpAddress(); - } - if (port <= 0) { - // Highly available deployments require different nodes - for (PeerId peer : initConf.getPeers()) { - if (StringUtils.equals(peer.getIp(), host)) { - if (serverId != null) { - throw new IllegalArgumentException( - "server.raft.cluster has duplicate ip, For local debugging, use -Dserver.raftPort to specify the raft port"); - } - serverId = peer; - } - } - } else { - // Local debugging use - serverId = new PeerId(host, port); - } - final String dataPath = CONFIG.getConfig(CONFIG_STORE_DIR, DEFAULT_DB_STORE_FILE_DIR) - + separator + "raft" + separator + serverId.getPort(); - try { - // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this - // separately - SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); - rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); - raftServer = new RaftConfigServer(dataPath, GROUP, serverId, initNodeOptions(initConf), rpcServer); - } catch (IOException e) { - throw new IllegalArgumentException("fail init raft cluster:" + e.getMessage(), e); - } - } - } - public static void start() { - if (!RAFT_MODE) { - return; - } - try { - if (raftServer != null) { - raftServer.start(); - } - } catch (IOException e) { - LOGGER.error("start seata server raft cluster error, group: {} ", GROUP, e); - throw new RuntimeException(e); - } - LOGGER.info("started seata server raft cluster, group: {} ", GROUP); - - if (rpcServer != null) { - rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); - rpcServer.registerProcessor(new ConfigOperationRequestProcessor()); - if (!rpcServer.init(null)) { - throw new RuntimeException("start raft node fail!"); - } - } - // Make sure to close it at the end, as other components may still use the configuration, such as ShutdownWaitTime. - ServerRunner.addDisposable(() -> { - RaftConfigServerManager.destroy(); - ConfigStoreManagerFactory.destroy(); - }); - } - - - public static void destroy() { - if (raftServer != null) { - raftServer.close(); - } - LOGGER.info("closed seata server raft cluster, group: {} ", GROUP); - Optional.ofNullable(rpcServer).ifPresent(RpcServer::shutdown); - raftServer = null; - rpcServer = null; - RAFT_MODE = false; - INIT.set(false); - } - public static boolean isRaftMode() { - return RAFT_MODE; - } - public static RaftConfigServer getRaftServer() { - return raftServer; - } - - public static RpcServer getRpcServer() { - return rpcServer; - } - - public static boolean isLeader() { - AtomicReference stateMachine = new AtomicReference<>(); - Optional.ofNullable(raftServer).ifPresent(raftConfigServer -> { - stateMachine.set(raftConfigServer.getRaftStateMachine()); - }); - RaftConfigStateMachine raftStateMachine = stateMachine.get(); - return raftStateMachine != null && raftStateMachine.isLeader(); - } - - public static PeerId getLeader() { - - RouteTable routeTable = RouteTable.getInstance(); - try { - routeTable.refreshLeader(getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); - return routeTable.selectLeader(RAFT_CONFIG_GROUP); - } catch (Exception e) { - LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); - } - return null; - - } - private static RaftOptions initRaftOptions() { - RaftOptions raftOptions = new RaftOptions(); - raftOptions.setApplyBatch(CONFIG.getInt(SERVER_RAFT_APPLY_BATCH, raftOptions.getApplyBatch())); - raftOptions.setMaxAppendBufferSize( - CONFIG.getInt(SERVER_RAFT_MAX_APPEND_BUFFER_SIZE, raftOptions.getMaxAppendBufferSize())); - raftOptions.setDisruptorBufferSize( - CONFIG.getInt(SERVER_RAFT_DISRUPTOR_BUFFER_SIZE, raftOptions.getDisruptorBufferSize())); - raftOptions.setMaxReplicatorInflightMsgs( - CONFIG.getInt(SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS, raftOptions.getMaxReplicatorInflightMsgs())); - raftOptions.setSync(CONFIG.getBoolean(SERVER_RAFT_SYNC, raftOptions.isSync())); - return raftOptions; - } - - private static NodeOptions initNodeOptions(Configuration initConf) { - NodeOptions nodeOptions = new NodeOptions(); - // enable the CLI service. - nodeOptions.setDisableCli(false); - // snapshot should be made every 600 seconds - int snapshotInterval = CONFIG.getInt(SERVER_RAFT_SNAPSHOT_INTERVAL, 60 * 10); - nodeOptions.setSnapshotIntervalSecs(snapshotInterval); - nodeOptions.setRaftOptions(initRaftOptions()); - // set the election timeout to 1 second - nodeOptions - .setElectionTimeoutMs(CONFIG.getInt(SERVER_RAFT_ELECTION_TIMEOUT_MS, DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS)); - // set up the initial cluster configuration - nodeOptions.setInitialConf(initConf); - return nodeOptions; - } - public static String getGroup() { - return GROUP; - } - private static class SingletonHandler { - private static final CliService CLI_SERVICE = RaftServiceFactory.createAndInitCliService(new CliOptions()); - private static final CliClientService CLI_CLIENT_SERVICE = new CliClientServiceImpl(); - - static { - CLI_CLIENT_SERVICE.init(new CliOptions()); - } - - } - -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java deleted file mode 100644 index 4a9f48884da..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java +++ /dev/null @@ -1,455 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft; - -import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; - -import com.alipay.sofa.jraft.Closure; -import com.alipay.sofa.jraft.Iterator; -import com.alipay.sofa.jraft.RouteTable; -import com.alipay.sofa.jraft.Status; -import com.alipay.sofa.jraft.conf.Configuration; -import com.alipay.sofa.jraft.core.StateMachineAdapter; -import com.alipay.sofa.jraft.entity.LeaderChangeContext; -import com.alipay.sofa.jraft.entity.PeerId; -import com.alipay.sofa.jraft.rpc.InvokeContext; -import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; -import org.apache.seata.common.XID; -import org.apache.seata.common.holder.ObjectHolder; -import org.apache.seata.common.metadata.ClusterRole; -import org.apache.seata.common.metadata.Node; -import org.apache.seata.common.thread.NamedThreadFactory; -import org.apache.seata.common.util.CollectionUtils; -import org.apache.seata.common.util.NetUtil; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.core.serializer.SerializerType; -import org.apache.seata.server.cluster.listener.ClusterChangeEvent; -import org.apache.seata.server.cluster.raft.context.SeataClusterContext; -import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; -import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationExecute; -import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; -import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; -import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; -import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; -import org.apache.seata.server.cluster.raft.snapshot.config.ConfigSnapshotFile; -import org.apache.seata.server.cluster.raft.snapshot.metadata.ConfigLeaderMetadataSnapshotFile; -import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; -import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType; -import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; -import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; -import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.core.env.Environment; - -import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; -import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT; -import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.CONFIG_OPERATION; -import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.REFRESH_CLUSTER_METADATA; - - -/** - * The type raft config state machine. - */ -public class RaftConfigStateMachine extends StateMachineAdapter { - private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigStateMachine.class); - - private final String group; - - private final List snapshotFiles = new ArrayList<>(); - - private static final Map> EXECUTES = new HashMap<>(); - - private volatile RaftClusterMetadata raftClusterMetadata = new RaftClusterMetadata(); - - private final Lock lock = new ReentrantLock(); - - private static final ScheduledThreadPoolExecutor RESYNC_METADATA_POOL = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("reSyncMetadataPool", 1, true)); - - /** - * Leader term - */ - private final AtomicLong leaderTerm = new AtomicLong(-1); - - /** - * current term - */ - private final AtomicLong currentTerm = new AtomicLong(-1); - - private final AtomicBoolean initSync = new AtomicBoolean(false); - - private ScheduledFuture scheduledFuture; - - public boolean isLeader() { - return this.leaderTerm.get() > 0; - } - - public RaftConfigStateMachine(String group) { - this.group = group; - - EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { - refreshClusterMetadata(syncMsg); - return null; - }); - registryStoreSnapshotFile(new ConfigLeaderMetadataSnapshotFile(group)); - registryStoreSnapshotFile(new ConfigSnapshotFile(group)); - EXECUTES.put(CONFIG_OPERATION, new ConfigOperationExecute()); - EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { - refreshClusterMetadata(syncMsg); - return null; - }); - this.scheduledFuture = - RESYNC_METADATA_POOL.scheduleAtFixedRate(() -> syncCurrentNodeInfo(group), 10, 10, TimeUnit.SECONDS); - } - - @Override - public void onApply(Iterator iterator) { - while (iterator.hasNext()) { - Closure done = iterator.done(); - if (done != null) { - // leader does not need to be serialized, just execute the task directly - if (done instanceof ConfigClosure) { - ConfigClosure configClosure = (ConfigClosure) done; - RaftBaseMsg msg = configClosure.getRaftBaseMsg(); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("sync msg: {}", msg); - } - ConfigOperationResponse response = (ConfigOperationResponse) onExecuteRaft(msg); - configClosure.getResponse().setSuccess(response.isSuccess()); - configClosure.getResponse().setResult(response.getResult()); - configClosure.getResponse().setErrMsg(response.getErrMsg()); - configClosure.run(Status.OK()); - } else { - // If it's not a ConfigClosure, just run it with OK status - done.run(Status.OK()); - } - } else { - ByteBuffer byteBuffer = iterator.getData(); - // if data is empty, it is only a heartbeat event and can be ignored - if (byteBuffer != null && byteBuffer.hasRemaining()) { - RaftBaseMsg msg = (RaftBaseMsg) RaftSyncMessageSerializer.decode(byteBuffer.array()).getBody(); - // follower executes the corresponding task - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("sync msg: {}", msg); - } - onExecuteRaft(msg); - } - } - iterator.next(); - } - } - - @Override - public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { - long current = System.currentTimeMillis(); - for (StoreSnapshotFile snapshotFile : snapshotFiles) { - Status status = snapshotFile.save(writer); - if (!status.isOk()) { - done.run(status); - return; - } - } - LOGGER.info("groupId: {}, onSnapshotSave cost: {} ms.", group, System.currentTimeMillis() - current); - done.run(Status.OK()); - } - - @Override - public boolean onSnapshotLoad(final SnapshotReader reader) { - if (isLeader()) { - if (LOGGER.isWarnEnabled()) { - LOGGER.warn("Leader is not supposed to load snapshot"); - } - return false; - } - long current = System.currentTimeMillis(); - for (StoreSnapshotFile snapshotFile : snapshotFiles) { - if (!snapshotFile.load(reader)) { - return false; - } - } - LOGGER.info("groupId: {}, onSnapshotLoad cost: {} ms.", group, System.currentTimeMillis() - current); - return true; - } - @Override - public void onLeaderStart(final long term) { - boolean leader = isLeader(); - this.leaderTerm.set(term); - LOGGER.info("groupId: {}, onLeaderStart: term={}.", group, term); - this.currentTerm.set(term); - syncMetadata(); - if (!leader && RaftConfigServerManager.isRaftMode()) { - Configuration conf = RouteTable.getInstance().getConfiguration(group); - // A member change might trigger a leader re-election. At this point, it’s necessary to filter out non-existent members and synchronize again. - changePeers(conf); - } - } - - @Override - public void onLeaderStop(final Status status) { - this.leaderTerm.set(-1); - LOGGER.info("groupId: {}, onLeaderStop: status={}.", group, status); - } - - @Override - public void onStopFollowing(final LeaderChangeContext ctx) { - LOGGER.info("groupId: {}, onStopFollowing: {}.", group, ctx); - } - - @Override - public void onStartFollowing(final LeaderChangeContext ctx) { - LOGGER.info("groupId: {}, onStartFollowing: {}.", group, ctx); - this.currentTerm.set(ctx.getTerm()); - CompletableFuture.runAsync(() -> syncCurrentNodeInfo(ctx.getLeaderId()), RESYNC_METADATA_POOL); - } - - @Override - public void onConfigurationCommitted(Configuration conf) { - LOGGER.info("groupId: {}, onConfigurationCommitted: {}.", group, conf); - RouteTable.getInstance().updateConfiguration(group, conf); - // After a member change, the metadata needs to be synchronized again. - initSync.compareAndSet(true, false); - if (isLeader()) { - changePeers(conf); - } - } - private void changePeers(Configuration conf) { - lock.lock(); - try { - List newFollowers = conf.getPeers(); - Set newLearners = conf.getLearners(); - List currentFollowers = raftClusterMetadata.getFollowers(); - if (CollectionUtils.isNotEmpty(newFollowers)) { - raftClusterMetadata.setFollowers(currentFollowers.stream().filter(node -> contains(node, newFollowers)) - .collect(Collectors.toList())); - } - if (CollectionUtils.isNotEmpty(newLearners)) { - raftClusterMetadata.setLearner(raftClusterMetadata.getLearner().stream() - .filter(node -> contains(node, newLearners)).collect(Collectors.toList())); - } else { - raftClusterMetadata.setLearner(Collections.emptyList()); - } - CompletableFuture.runAsync(this::syncMetadata, RESYNC_METADATA_POOL); - } finally { - lock.unlock(); - } - } - - private boolean contains(Node node, Collection list) { - // This indicates that the node is of a lower version. - // When scaling up or down on a higher version - // you need to ensure that the cluster is consistent first - // otherwise, the lower version nodes may be removed. - if (node.getInternal() == null) { - return true; - } - PeerId nodePeer = new PeerId(node.getInternal().getHost(), node.getInternal().getPort()); - return list.contains(nodePeer); - } - - public void syncMetadata() { - if (isLeader()) { - SeataClusterContext.bindGroup(group); - try { - RaftClusterMetadataMsg raftClusterMetadataMsg = - new RaftClusterMetadataMsg(changeOrInitRaftClusterMetadata()); - RaftConfigTaskUtil.createTask(status -> refreshClusterMetadata(raftClusterMetadataMsg), - raftClusterMetadataMsg, null); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } finally { - SeataClusterContext.unbindGroup(); - } - } - } - - private Object onExecuteRaft(RaftBaseMsg msg) { - RaftMsgExecute execute = EXECUTES.get(msg.getMsgType()); - if (execute == null) { - throw new RuntimeException( - "the state machine does not allow events that cannot be executed, please feedback the information to the Seata community !!! msg: " - + msg); - } - try { - return execute.execute(msg); - } catch (Throwable e) { - LOGGER.error("Message synchronization failure: {}, msgType: {}", e.getMessage(), msg.getMsgType(), e); - throw new RuntimeException(e); - } - } - - public AtomicLong getCurrentTerm() { - return currentTerm; - } - - public void registryStoreSnapshotFile(StoreSnapshotFile storeSnapshotFile) { - snapshotFiles.add(storeSnapshotFile); - } - - public RaftClusterMetadata getRaftLeaderMetadata() { - return raftClusterMetadata; - } - - public void setRaftLeaderMetadata(RaftClusterMetadata raftClusterMetadata) { - this.raftClusterMetadata = raftClusterMetadata; - } - - public RaftClusterMetadata changeOrInitRaftClusterMetadata() { - raftClusterMetadata.setTerm(this.currentTerm.get()); - Node leaderNode = raftClusterMetadata.getLeader(); - RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); - PeerId cureentPeerId = raftServer.getServerId(); - // After the re-election, the leader information may be different from the latest leader, and you need to replace the leader information - if (leaderNode == null || (leaderNode.getInternal() != null - && !cureentPeerId.equals(new PeerId(leaderNode.getInternal().getHost(), leaderNode.getInternal().getPort())))) { - Node leader = - raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0 ? 8091 : XID.getPort(), raftServer.getServerId().getPort(), - Integer.parseInt( - ((Environment) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) - .getProperty("server.port", String.valueOf(7091))), - group, Collections.emptyMap()); - leader.setRole(ClusterRole.LEADER); - raftClusterMetadata.setLeader(leader); - } - return raftClusterMetadata; - } - - public void refreshClusterMetadata(RaftBaseMsg syncMsg) { - // Directly receive messages from the leader and update the cluster metadata - raftClusterMetadata = ((RaftClusterMetadataMsg)syncMsg).getRaftClusterMetadata(); - if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { - ((ApplicationEventPublisher)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterChangeEvent(this, group, raftClusterMetadata.getTerm(), this.isLeader())); - LOGGER.info("groupId: {}, refresh cluster metadata: {}", group, raftClusterMetadata); - } - - } - - private void syncCurrentNodeInfo(String group) { - if (initSync.compareAndSet(false, true)) { - try { - RouteTable.getInstance().refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), group, 1000); - PeerId peerId = RouteTable.getInstance().selectLeader(group); - if (peerId != null) { - syncCurrentNodeInfo(peerId); - } - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - } - } - - private void syncCurrentNodeInfo(PeerId leaderPeerId) { - try { - // Ensure that the current leader must be version 2.1 or later to synchronize the operation - Node leader = raftClusterMetadata.getLeader(); - if (leader != null && StringUtils.isNotBlank(leader.getVersion())) { - RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); - PeerId cureentPeerId = raftServer.getServerId(); - Node node = raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0 ? 8091 : XID.getPort(), cureentPeerId.getPort(), - Integer.parseInt( - ((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) - .getProperty("server.port", String.valueOf(7091))), - group, Collections.emptyMap()); - InvokeContext invokeContext = new InvokeContext(); - PutNodeMetadataRequest putNodeInfoRequest = new PutNodeMetadataRequest(node); - Configuration configuration = RouteTable.getInstance().getConfiguration(group); - node.setRole( - configuration.getPeers().contains(cureentPeerId) ? ClusterRole.FOLLOWER : ClusterRole.LEARNER); - invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, - SerializerType.JACKSON.getCode()); - CliClientServiceImpl cliClientService = - (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); - // The previous leader may be an old snapshot or log playback, which is not accurate, and you - // need to get the leader again - cliClientService.getRpcClient().invokeAsync(leaderPeerId.getEndpoint(), putNodeInfoRequest, - invokeContext, (result, err) -> { - if (err == null) { - PutNodeMetadataResponse putNodeMetadataResponse = (PutNodeMetadataResponse)result; - if (putNodeMetadataResponse.isSuccess()) { - scheduledFuture.cancel(true); - LOGGER.info("sync node info to leader: {}, result: {}", leaderPeerId, result); - } else { - initSync.compareAndSet(true, false); - LOGGER.info( - "sync node info to leader: {}, result: {}, retry will be made at the time of the re-election or after 10 seconds", - leaderPeerId, result); - } - } else { - initSync.compareAndSet(true, false); - LOGGER.error("sync node info to leader: {}, error: {}", leaderPeerId, err.getMessage(), - err); - } - }, 30000); - } - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - } - - public void changeNodeMetadata(Node node) { - lock.lock(); - try { - List list = node.getRole() == ClusterRole.FOLLOWER ? raftClusterMetadata.getFollowers() - : raftClusterMetadata.getLearner(); - // If the node currently exists, modify it - for (Node follower : list) { - Node.Endpoint endpoint = follower.getInternal(); - if (endpoint != null) { - // change old follower node metadata - if (endpoint.getHost().equals(node.getInternal().getHost()) - && endpoint.getPort() == node.getInternal().getPort()) { - follower.setTransaction(node.getTransaction()); - follower.setControl(node.getControl()); - follower.setGroup(group); - follower.setMetadata(node.getMetadata()); - follower.setVersion(node.getVersion()); - follower.setRole(node.getRole()); - return; - } - } - } - // add new node node metadata - list.add(node); - syncMetadata(); - } finally { - lock.unlock(); - } - } - - -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java index 519b1af4fff..b891bea8484 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java @@ -132,7 +132,7 @@ public static void init() { try { // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this // separately - rpcServer = RaftConfigServerManager.getRpcServer() == null ? RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()) : RaftConfigServerManager.getRpcServer(); + rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); RaftServer raftServer = new RaftServer(dataPath, group, serverId, initNodeOptions(initConf), rpcServer); // as the foundation for multi raft group in the future RAFT_SERVER_MAP.put(group, raftServer); @@ -152,7 +152,7 @@ public static void start() { } LOGGER.info("started seata server raft cluster, group: {} ", group); }); - if (rpcServer != null && RaftConfigServerManager.getRpcServer() == null) { + if (rpcServer != null) { rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); if (!rpcServer.init(null)) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java deleted file mode 100644 index 4ce6b31e342..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.execute.config; - -import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerFactory; -import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; - - - -public abstract class AbstractRaftConfigMsgExecute implements RaftMsgExecute { - - protected ConfigStoreManager configStoreManager = ConfigStoreManagerFactory.getInstance(); - -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java deleted file mode 100644 index 9ebdb937b4b..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.execute.config; - -import java.util.List; -import java.util.Map; - -import org.apache.seata.common.holder.ObjectHolder; -import org.apache.seata.config.dto.ConfigurationInfoDto; -import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; -import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; -import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; -import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; -import org.apache.seata.config.dto.ConfigurationItem; -import org.apache.seata.server.config.ConfigurationProcessor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.ApplicationEventPublisher; - -import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; - - -public class ConfigOperationExecute extends AbstractRaftConfigMsgExecute { - private static final Logger LOGGER = LoggerFactory.getLogger(ConfigOperationExecute.class); - - @Override - public Object execute(RaftBaseMsg syncMsg) throws Throwable { - RaftConfigOperationSyncMsg configSyncMsg = (RaftConfigOperationSyncMsg) syncMsg; - ConfigOperationDTO configOperation = configSyncMsg.getConfigOperation(); - switch (configOperation.getOptType()) { - case PUT: - return put(configOperation); - case DELETE: - return delete(configOperation); - case DELETE_ALL: - return deleteAll(configOperation); - case GET: - return get(configOperation); - case GET_ALL: - return getAll(configOperation); - case UPLOAD: - return upload(configOperation); - case GET_NAMESPACES: - return getNamespaces(configOperation); - case GET_DATA_IDS: - return getDataIds(configOperation); - default: - return ConfigOperationResponse.fail("unknown operation type"); - } - } - - private ConfigOperationResponse get(ConfigOperationDTO configOperation) { - String result = configStoreManager.get(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); - // fill config description and default value - ConfigurationItem item = ConfigurationProcessor.processConfigItem(configOperation.getKey(), result); - return ConfigOperationResponse.success(item); - } - - private ConfigOperationResponse put(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.put(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey(), configOperation.getValue()); - if (success) { - // ApplicationContext may not have been started at this point - if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { - ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); - } - LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); - } - return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); - } - - private ConfigOperationResponse delete(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.delete(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); - if (success) { - if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { - ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); - } - LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); - } - return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); - } - - private ConfigOperationResponse deleteAll(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.deleteAll(configOperation.getNamespace(), configOperation.getDataId()); - if (success) { - if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { - ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); - } - LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); - } - return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); - } - - private ConfigOperationResponse upload(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.putAll(configOperation.getNamespace(), configOperation.getDataId(), (Map) configOperation.getValue()); - if (success) { - if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { - ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); - } - LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); - } - return success ? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); - } - - private ConfigOperationResponse getAll(ConfigOperationDTO configOperation) { - Map configMap = configStoreManager.getAll(configOperation.getNamespace(), configOperation.getDataId()); - Long configVersion = configStoreManager.getConfigVersion(configOperation.getNamespace(), configOperation.getDataId()); - // fill config description and default value - Map itemMap = ConfigurationProcessor.processConfigMap(configMap); - ConfigurationInfoDto configurationInfoDto = new ConfigurationInfoDto(); - configurationInfoDto.setConfig(itemMap); - configurationInfoDto.setVersion(configVersion); - return ConfigOperationResponse.success(configurationInfoDto); - } - - private ConfigOperationResponse getNamespaces(ConfigOperationDTO configOperation) { - List namespaces = configStoreManager.getAllNamespaces(); - return ConfigOperationResponse.success(namespaces); - } - - private ConfigOperationResponse getDataIds(ConfigOperationDTO configOperation) { - List dataIds = configStoreManager.getAllDataIds(configOperation.getNamespace()); - return ConfigOperationResponse.success(dataIds); - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java deleted file mode 100644 index 8d38613d43d..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.execute.config; - -public enum ConfigOperationType { - - /** - * Get configuration operation - */ - GET("get"), - - /** - * Put configuration operation - */ - PUT("put"), - - /** - * Delete configuration operation - */ - DELETE("delete"), - - /** - * Delete all configuration operation - */ - DELETE_ALL("deleteAll"), - - /** - * Upload configuration operation - */ - UPLOAD("upload"), - - /** - * Get all configuration operation - */ - GET_ALL("getAll"), - - /** - * Get namespaces operation - */ - GET_NAMESPACES("getNamespaces"), - - /** - * Get data ids operation - */ - GET_DATA_IDS("getDataIds"); - - private final String type; - - ConfigOperationType(String type) { - this.type = type; - } - - public String getType() { - return type; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java deleted file mode 100644 index 8cc1f2abe62..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.processor; - -import com.alipay.sofa.jraft.rpc.RpcContext; -import com.alipay.sofa.jraft.rpc.RpcProcessor; -import org.apache.seata.core.exception.TransactionException; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; -import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; -import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; -import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; -import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; -import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; -import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; - - -public class ConfigOperationRequestProcessor implements RpcProcessor { - private static final String NOT_LEADER = "not leader"; - @Override - public void handleRequest(RpcContext rpcCtx, ConfigOperationRequest request) { - if (RaftConfigServerManager.isLeader()) { - onExecute(rpcCtx, request); - } else { - rpcCtx.sendResponse(ConfigOperationResponse.fail(NOT_LEADER)); - } - } - - private void onExecute(RpcContext rpcCtx, ConfigOperationRequest request) { - ConfigOperationDTO operationDTO = ConfigOperationDTO.convertConfigRequest2Dto(request); - RaftConfigOperationSyncMsg syncMsg = new RaftConfigOperationSyncMsg(operationDTO); - ConfigOperationResponse response = new ConfigOperationResponse(); - ConfigClosure closure = new ConfigClosure(); - closure.setRaftBaseMsg(syncMsg); - closure.setResponse(response); - closure.setDone(status -> { - if (!status.isOk()) { - response.setSuccess(false); - response.setErrMsg(status.getErrorMsg()); - } - rpcCtx.sendResponse(response); - }); - try { - RaftConfigTaskUtil.createTask(closure, syncMsg, null); - } catch (TransactionException e) { - throw new RuntimeException(e); - } - } - - @Override - public String interest() { - return ConfigOperationRequest.class.getName(); - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java index 41e060fad60..de90cbd8c5d 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java @@ -19,16 +19,12 @@ import com.alipay.sofa.jraft.rpc.RpcContext; import com.alipay.sofa.jraft.rpc.RpcProcessor; import org.apache.seata.common.metadata.Node; -import org.apache.seata.server.cluster.raft.RaftConfigServer; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; -import org.apache.seata.server.cluster.raft.RaftConfigStateMachine; import org.apache.seata.server.cluster.raft.RaftServer; import org.apache.seata.server.cluster.raft.RaftServerManager; import org.apache.seata.server.cluster.raft.RaftStateMachine; import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; - public class PutNodeInfoRequestProcessor implements RpcProcessor { public PutNodeInfoRequestProcessor() { @@ -39,25 +35,6 @@ public PutNodeInfoRequestProcessor() { public void handleRequest(RpcContext rpcCtx, PutNodeMetadataRequest request) { Node node = request.getNode(); String group = node.getGroup(); - if (RaftConfigServerManager.getGroup().equals(group)) { - changeConfigGroupRequest(group, node, rpcCtx, request); - } else { - changeNormalGroupRequest(group, node, rpcCtx, request); - } - } - - private static void changeConfigGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request) { - if (RaftConfigServerManager.isLeader()) { - RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); - RaftConfigStateMachine raftStateMachine = raftServer.getRaftStateMachine(); - raftStateMachine.changeNodeMetadata(node); - rpcCtx.sendResponse(new PutNodeMetadataResponse(true)); - } else { - rpcCtx.sendResponse(new PutNodeMetadataResponse(false)); - } - } - - private static void changeNormalGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request) { if (RaftServerManager.isLeader(group)) { RaftServer raftServer = RaftServerManager.getRaftServer(group); RaftStateMachine raftStateMachine = raftServer.getRaftStateMachine(); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java deleted file mode 100644 index d0ace0b2a50..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.processor.request; - -import java.io.Serializable; -import java.util.Map; - -import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; - -public class ConfigOperationRequest implements Serializable { - private static final long serialVersionUID = -1149573667621259458L; - private ConfigOperationType optType; - private String namespace; - private String dataId; - private String key; - private Object value; - - public ConfigOperationRequest() { - } - - public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId) { - this.optType = optType; - this.namespace = namespace; - this.dataId = dataId; - } - - public ConfigOperationRequest(ConfigOperationType optType,String namespace, String dataId, String key) { - this.optType = optType; - this.namespace = namespace; - this.dataId = dataId; - this.key = key; - } - - public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId, String key, Object value) { - this.optType = optType; - this.namespace = namespace; - this.dataId = dataId; - this.key = key; - this.value = value; - } - - public static ConfigOperationRequest buildGetRequest(String namespace, String dataId, String key) { - return new ConfigOperationRequest(ConfigOperationType.GET, namespace, dataId, key); - } - - public static ConfigOperationRequest buildPutRequest(String namespace, String dataId, String key, String value) { - return new ConfigOperationRequest(ConfigOperationType.PUT, namespace, dataId, key, value); - } - - public static ConfigOperationRequest buildDeleteRequest(String namespace, String dataId, String key) { - return new ConfigOperationRequest(ConfigOperationType.DELETE, namespace, dataId, key); - } - public static ConfigOperationRequest buildDeleteAllRequest(String namespace, String dataId) { - return new ConfigOperationRequest(ConfigOperationType.DELETE_ALL, namespace, dataId); - } - - public static ConfigOperationRequest buildGetAllRequest(String namespace, String dataId) { - return new ConfigOperationRequest(ConfigOperationType.GET_ALL, namespace, dataId); - } - - public static ConfigOperationRequest buildUploadRequest(String namespace, String dataId, Map configMap) { - return new ConfigOperationRequest(ConfigOperationType.UPLOAD, namespace, dataId, null, configMap); - } - - public static ConfigOperationRequest buildGetNamespaces() { - return new ConfigOperationRequest(ConfigOperationType.GET_NAMESPACES, null, null); - } - - public static ConfigOperationRequest buildGetDataIds(String namespace) { - return new ConfigOperationRequest(ConfigOperationType.GET_DATA_IDS, namespace, null); - } - - - public ConfigOperationType getOptType() { - return optType; - } - public void setOptType(ConfigOperationType optType) { - this.optType = optType; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - - @Override - public String toString() { - return "ConfigOperationRequest{" + - "optType=" + optType + - ", namespace='" + namespace + '\'' + - ", dataId='" + dataId + '\'' + - ", key='" + key + '\'' + - ", value=" + value + - '}'; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java deleted file mode 100644 index 4b1a2222e11..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.processor.response; - -import java.io.Serializable; - -public class ConfigOperationResponse implements Serializable { - private static final long serialVersionUID = -1439073440621259777L; - - private Object result; - private boolean success; - private String errMsg; - - public ConfigOperationResponse() { - } - - public Object getResult() { - return result; - } - - public void setResult(Object result) { - this.result = result; - } - - public boolean isSuccess() { - return success; - } - - public void setSuccess(boolean success) { - this.success = success; - } - - public String getErrMsg() { - return errMsg; - } - - public void setErrMsg(String errMsg) { - this.errMsg = errMsg; - } - - public static ConfigOperationResponse success() { - ConfigOperationResponse response = new ConfigOperationResponse(); - response.setSuccess(true); - return response; - } - - public static ConfigOperationResponse success(Object result) { - ConfigOperationResponse response = success(); - response.setResult(result); - return response; - } - - public static ConfigOperationResponse fail() { - ConfigOperationResponse response = new ConfigOperationResponse(); - response.setSuccess(false); - return response; - } - - public static ConfigOperationResponse fail(String errMsg) { - ConfigOperationResponse response = fail(); - response.setErrMsg(errMsg); - return response; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java index ec5695bd309..e8c1cc68f31 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java @@ -131,11 +131,8 @@ public enum SnapshotType { /** * leader metadata snapshot */ - leader_metadata("leader_metadata"), - /** - * config snapshot - */ - config("config"); + leader_metadata("leader_metadata"); + final String type; SnapshotType(String type) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java deleted file mode 100644 index 971902cb98d..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.snapshot.config; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.util.Map; -import java.util.Objects; - -import com.alipay.sofa.jraft.Status; -import com.alipay.sofa.jraft.error.RaftError; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; -import org.apache.seata.common.loader.EnhancedServiceLoader; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerProvider; -import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; -import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; -import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; -import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; - -public class ConfigSnapshotFile implements Serializable, StoreSnapshotFile { - - private static final Logger LOGGER = LoggerFactory.getLogger(ConfigSnapshotFile.class); - - private static final long serialVersionUID = 1452307567830545914L; - - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - - private static ConfigStoreManager configStoreManager; - private final String group; - - private final String fileName = "config"; - - public ConfigSnapshotFile(String group) { - this.group = group; - String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); - configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); - } - - @Override - public Status save(SnapshotWriter writer) { - Map> configMap = configStoreManager.getConfigMap(); - RaftSnapshot raftSnapshot = new RaftSnapshot(); - raftSnapshot.setBody(configMap); - raftSnapshot.setType(RaftSnapshot.SnapshotType.config); - LOGGER.info("groupId: {}, config size: {}", group, configMap.size()); - String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); - try { - if (save(raftSnapshot, path)) { - if (writer.addFile(fileName)) { - return Status.OK(); - } else { - return new Status(RaftError.EIO, "Fail to add file to writer"); - } - } - } catch (IOException e) { - LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); - } - return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); - } - - @Override - public boolean load(SnapshotReader reader) { - if (reader.getFileMeta(fileName) == null) { - LOGGER.error("Fail to find data file in {}", reader.getPath()); - return false; - } - String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); - try { - LOGGER.info("on snapshot load start index: {}", reader.load().getLastIncludedIndex()); - Map> configMap = (Map>)load(path); - ConfigStoreManager configStoreManager = RocksDBConfigStoreManager.getInstance(); - configStoreManager.clearData(); - configStoreManager.putConfigMap(configMap); - if (LOGGER.isInfoEnabled()) { - LOGGER.info("on snapshot load end index: {}", reader.load().getLastIncludedIndex()); - } - return true; - } catch (final Exception e) { - LOGGER.error("fail to load snapshot from {}", path, e); - return false; - } - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java deleted file mode 100644 index 8089d30ba9c..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.snapshot.metadata; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; - -import com.alipay.sofa.jraft.Status; -import com.alipay.sofa.jraft.error.RaftError; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; -import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; -import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; -import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; -import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ConfigLeaderMetadataSnapshotFile implements Serializable, StoreSnapshotFile { - private static final long serialVersionUID = 43235664615355354L; - - private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLeaderMetadataSnapshotFile.class); - - private final String group; - - private final String fileName = "leader_metadata"; - - public ConfigLeaderMetadataSnapshotFile(String group) { - this.group = group; - } - - - @Override - public Status save(SnapshotWriter writer) { - RaftSnapshot raftSnapshot = new RaftSnapshot(); - RaftClusterMetadata raftClusterMetadata = - RaftConfigServerManager.getRaftServer().getRaftStateMachine().getRaftLeaderMetadata(); - raftSnapshot.setBody(raftClusterMetadata); - raftSnapshot.setType(RaftSnapshot.SnapshotType.leader_metadata); - String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); - try { - if (save(raftSnapshot, path)) { - if (writer.addFile(fileName)) { - return Status.OK(); - } else { - return new Status(RaftError.EIO, "Fail to add file to writer"); - } - } - } catch (IOException e) { - LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); - } - return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); - } - - @Override - public boolean load(SnapshotReader reader) { - if (reader.getFileMeta(fileName) == null) { - LOGGER.error("Fail to find data file in {}", reader.getPath()); - return false; - } - String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); - try { - RaftClusterMetadata raftClusterMetadata = (RaftClusterMetadata)load(path); - RaftConfigServerManager.getRaftServer().getRaftStateMachine() - .setRaftLeaderMetadata(raftClusterMetadata); - return true; - } catch (final Exception e) { - LOGGER.error("fail to load snapshot from {}", path, e); - return false; - } - } -} - diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java deleted file mode 100644 index 53a0c3cb281..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.sync.msg; - -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; - - -public class RaftConfigOperationSyncMsg extends RaftBaseMsg { - - private static final long serialVersionUID = -3344345671349834321L; - private ConfigOperationDTO configOperation; - - public RaftConfigOperationSyncMsg(ConfigOperationDTO configOperation) { - this.msgType = RaftSyncMsgType.CONFIG_OPERATION; - this.configOperation = configOperation; - } - - public RaftConfigOperationSyncMsg() { - } - - public ConfigOperationDTO getConfigOperation() { - return configOperation; - } - - @Override - public String toString() { - return StringUtils.toString(this); - } - -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java index 869fe011991..f74cdb88c52 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java @@ -55,7 +55,5 @@ public enum RaftSyncMsgType { /** * refresh cluster metadata */ - REFRESH_CLUSTER_METADATA, - - CONFIG_OPERATION; + REFRESH_CLUSTER_METADATA; } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java deleted file mode 100644 index ade72303882..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.sync.msg.closure; - -import com.alipay.sofa.jraft.Closure; -import com.alipay.sofa.jraft.Status; -import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; -import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; - -/** - * The type of closure for configuration sync in raft - */ -public class ConfigClosure implements Closure { - - private RaftBaseMsg raftBaseMsg; - private ConfigOperationResponse response; - private Closure done; - - @Override - public void run(Status status) { - if (done != null) { - done.run(status); - } - } - - public RaftBaseMsg getRaftBaseMsg() { - return raftBaseMsg; - } - - public void setRaftBaseMsg(RaftBaseMsg raftBaseMsg) { - this.raftBaseMsg = raftBaseMsg; - } - - public ConfigOperationResponse getResponse() { - return response; - } - - public void setResponse(ConfigOperationResponse response) { - this.response = response; - } - - public Closure getDone() { - return done; - } - - public void setDone(Closure done) { - this.done = done; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java deleted file mode 100644 index 0f3e0795f43..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.sync.msg.dto; - -import java.io.Serializable; - -import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; -import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; - -public class ConfigOperationDTO implements Serializable { - private static final long serialVersionUID = -1237293571963636954L; - - private ConfigOperationType optType; - private String namespace; - private String dataId; - private String key; - private Object value; - - public ConfigOperationDTO() { - } - - public ConfigOperationDTO(ConfigOperationType optType, String namespace, String dataId, String key, Object value) { - this.optType = optType; - this.namespace = namespace; - this.dataId = dataId; - this.key = key; - this.value = value; - } - - public ConfigOperationType getOptType() { - return optType; - } - - public void setOptType(ConfigOperationType optType) { - this.optType = optType; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public String getKey() { - return key; - } - - public void setKey(String key) { - this.key = key; - } - - public Object getValue() { - return value; - } - - public void setValue(Object value) { - this.value = value; - } - - public static ConfigOperationDTO convertConfigRequest2Dto(ConfigOperationRequest request) { - return new ConfigOperationDTO(request.getOptType(), request.getNamespace(), request.getDataId(), request.getKey(), request.getValue()); - } - - @Override - public String toString() { - return "ConfigOperationDTO{" + - "optType=" + optType + - ", namespace='" + namespace + '\'' + - ", dataId='" + dataId + '\'' + - ", key='" + key + '\'' + - ", value=" + value + - '}'; - } -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java b/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java deleted file mode 100644 index 2d50d6249eb..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.raft.util; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -import com.alipay.sofa.jraft.Closure; -import com.alipay.sofa.jraft.entity.Task; -import org.apache.seata.core.exception.GlobalTransactionException; -import org.apache.seata.core.exception.TransactionException; -import org.apache.seata.core.exception.TransactionExceptionCode; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; -import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; -import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; - -/** - */ -public class RaftConfigTaskUtil { - public static boolean createTask(Closure done, Object data, CompletableFuture completableFuture) - throws TransactionException { - final Task task = new Task(); - if (data != null) { - RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); - raftSyncMessage.setBody(data); - try { - task.setData(ByteBuffer.wrap(RaftSyncMessageSerializer.encode(raftSyncMessage))); - } catch (IOException e) { - throw new TransactionException(e); - } - } - task.setDone(done == null ? status -> { - } : done); - RaftConfigServerManager.getRaftServer().getNode().apply(task); - if (completableFuture != null) { - return futureGet(completableFuture); - } - return true; - } - - public static boolean createTask(Closure done, CompletableFuture completableFuture) - throws TransactionException { - return createTask(done, null, completableFuture); - } - - public static boolean futureGet(CompletableFuture completableFuture) throws TransactionException { - try { - return completableFuture.get(); - } catch (InterruptedException e) { - throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, - "Fail to store global session: " + e.getMessage()); - } catch (ExecutionException e) { - if (e.getCause() instanceof TransactionException) { - throw (TransactionException)e.getCause(); - } else { - throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, - "Fail to store global session: " + e.getMessage()); - } - } - } - -} diff --git a/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java b/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java deleted file mode 100644 index d2dfcd6c725..00000000000 --- a/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * 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 org.apache.seata.server.cluster.watch; - -import static org.apache.seata.server.cluster.watch.Watcher.Protocol.HTTP; - -public class ConfigWatcher { - private String namespace; - - private String dataId; - - private volatile boolean done = false; - - private T asyncContext; - - private long timeout; - - - private String protocol = HTTP; - - public ConfigWatcher(String namespace, String dataId, T asyncContext, int timeout) { - this.namespace = namespace; - this.dataId = dataId; - this.asyncContext = asyncContext; - this.timeout = System.currentTimeMillis() + timeout; - } - - public String getNamespace() { - return namespace; - } - - public void setNamespace(String namespace) { - this.namespace = namespace; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public boolean isDone() { - return done; - } - - public void setDone(boolean done) { - this.done = done; - } - - public T getAsyncContext() { - return asyncContext; - } - - public void setAsyncContext(T asyncContext) { - this.asyncContext = asyncContext; - } - - public long getTimeout() { - return timeout; - } - - public void setTimeout(long timeout) { - this.timeout = timeout; - } - - public String getProtocol() { - return protocol; - } - - public void setProtocol(String protocol) { - this.protocol = protocol; - } -} diff --git a/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java b/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java deleted file mode 100644 index 06e6d6d7487..00000000000 --- a/server/src/main/java/org/apache/seata/server/config/ConfigurationProcessor.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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 org.apache.seata.server.config; - -import java.io.InputStream; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.seata.common.Constants; -import org.apache.seata.config.dto.ConfigurationItem; -import org.apache.seata.config.dto.ConfigurationItemMeta; -import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.core.io.ClassPathResource; -import org.yaml.snakeyaml.Yaml; - - -/** - * The configuration items processor - * - */ -public class ConfigurationProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); - private static final String ENCRYPT_STRING = "******"; - private static final String NOT_SEATA_CONFIG = "Not Seata configuration"; - private static final Map CONFIGURATION_ITEMS_META_MAP = new HashMap<>(); - private static final String META_FILE_NAME = Constants.CONFIGURATION_META_FILE_NAME; - private static final String CONFIG_META_KEY = "configuration-meta"; - private static final String META_KEY_KEY = "key"; - private static final String META_DESC_KEY = "desc"; - private static final String META_DEFAULT_VALUE_KEY = "defaultValue"; - private static final String META_ENCRYPT_KEY = "isEncrypt"; - - static { - loadConfigurationItemMeta(); - } - - /** - * load Configuration items meta from local yaml file. - */ - @SuppressWarnings("unchecked") - private static void loadConfigurationItemMeta() { - try (InputStream inputStream = new ClassPathResource(META_FILE_NAME).getInputStream()) { - Yaml yaml = new Yaml(); - Map map = yaml.load(inputStream); - List> configItemMetaList = (List>) map.get(CONFIG_META_KEY); - for (Map metaMap : configItemMetaList) { - ConfigurationItemMeta itemMeta = mapToConfigItemMeta(metaMap); - CONFIGURATION_ITEMS_META_MAP.put(itemMeta.getKey(), itemMeta); - } - } catch (Exception e) { - LOGGER.error("Failed to load configuration meta file", e); - } - } - - private static ConfigurationItemMeta mapToConfigItemMeta(Map configItemMap) { - String key = (String) configItemMap.get(META_KEY_KEY); - String desc = (String) configItemMap.get(META_DESC_KEY); - Object defaultValue = configItemMap.get(META_DEFAULT_VALUE_KEY); - Boolean isEncrypt = (Boolean) configItemMap.get(META_ENCRYPT_KEY); - return new ConfigurationItemMeta(key, desc, defaultValue, isEncrypt); - } - /** - * process configuration items map (fill description and default value ,or encrypt sensitive data). - */ - public static Map processConfigMap(Map configMap) { - return configMap.entrySet().stream() - .map(entry -> { - String key = entry.getKey(); - Object value = entry.getValue(); - return new HashMap.SimpleEntry<>(key, processConfigItem(key, value)); - }) - .collect(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), HashMap::putAll); - } - - /** - * Process ConfigurationItem - */ - public static ConfigurationItem processConfigItem(String key, Object value) { - ConfigurationItemMeta meta = CONFIGURATION_ITEMS_META_MAP.get(key); - ConfigurationItem item = new ConfigurationItem(); - item.setKey(key); - item.setDescription(meta == null ? NOT_SEATA_CONFIG : meta.getDescription()); - item.setDefaultValue(meta == null ? null : meta.getDefaultValue()); - if (meta != null && meta.getEncrypt()) { - item.setValue(ENCRYPT_STRING); - } else { - item.setValue(value); - } - return item; - } -} diff --git a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java index 888e9d667e7..816f6e3e250 100644 --- a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java +++ b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java @@ -16,66 +16,40 @@ */ package org.apache.seata.server.controller; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.Map; -import java.util.Properties; import java.util.Set; - import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.AsyncContext; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import com.alipay.sofa.jraft.RouteTable; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.PeerId; -import com.alipay.sofa.jraft.rpc.InvokeContext; -import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.metadata.MetadataResponse; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.result.Result; import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.ConfigType; import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.processor.ConfigProcessor; -import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerFactory; -import org.apache.seata.core.serializer.SerializerType; -import org.apache.seata.server.cluster.manager.ClusterConfigWatcherManager; import org.apache.seata.server.cluster.manager.ClusterWatcherManager; -import org.apache.seata.server.cluster.raft.RaftConfigServer; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.cluster.raft.RaftServer; import org.apache.seata.server.cluster.raft.RaftServerManager; -import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; -import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; -import org.apache.seata.server.cluster.watch.ConfigWatcher; import org.apache.seata.server.cluster.watch.Watcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.ApplicationContext; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import static org.apache.seata.common.ConfigurationKeys.SEATA_FILE_PREFIX_ROOT_CONFIG; import static org.apache.seata.common.ConfigurationKeys.STORE_MODE; -import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** @@ -89,33 +63,14 @@ public class ClusterController { @Resource private ClusterWatcherManager clusterWatcherManager; - @Resource - private ClusterConfigWatcherManager clusterConfigWatcherManager; - private ServerProperties serverProperties; - private ConfigStoreManager configStoreManager; @Resource ApplicationContext applicationContext; - private static final LinkedHashMap SUFFIX_MAP = new LinkedHashMap(8) { - { - put("txt", "properties"); - put("text", "properties"); - put("properties", "properties"); - put("yml", "yaml"); - put("yaml", "yaml"); - } - }; @PostConstruct private void init() { this.serverProperties = applicationContext.getBean(ServerProperties.class); - // only initialize configStoreManager in raft configuration. - String configType = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG - + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + ConfigurationKeys.FILE_ROOT_TYPE); - if (ConfigType.Raft.name().equalsIgnoreCase(configType)) { - configStoreManager = ConfigStoreManagerFactory.getInstance(); - } } @PostMapping("/changeCluster") @@ -134,21 +89,6 @@ public Result changeCluster(@RequestParam String raftClusterStr) { return result; } - @PostMapping("/changeConfigCluster") - public Result changeConfigCluster(@RequestParam String raftClusterStr) { - Result result = new Result<>(); - final Configuration newConf = new Configuration(); - if (!newConf.parse(raftClusterStr)) { - result.setMessage("fail to parse initConf:" + raftClusterStr); - } else { - String group = RaftConfigServerManager.getGroup(); - RaftConfigServerManager.getCliServiceInstance().changePeers(group, - RouteTable.getInstance().getConfiguration(group), newConf); - RouteTable.getInstance().updateConfiguration(group, newConf); - } - return result; - } - @GetMapping("/cluster") public MetadataResponse cluster(String group) { MetadataResponse metadataResponse = new MetadataResponse(); @@ -182,187 +122,6 @@ public MetadataResponse cluster(String group) { return metadataResponse; } - @GetMapping("/config/cluster") - public MetadataResponse configCluster() { - MetadataResponse metadataResponse = new MetadataResponse(); - RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); - if (raftServer != null) { - String configType = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG - + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + ConfigurationKeys.FILE_ROOT_TYPE); - metadataResponse.setConfigMode(configType); - RouteTable routeTable = RouteTable.getInstance(); - try { - routeTable.refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); - PeerId leader = routeTable.selectLeader(RAFT_CONFIG_GROUP); - if (leader != null) { - Set nodes = new HashSet<>(); - RaftClusterMetadata raftClusterMetadata = raftServer.getRaftStateMachine().getRaftLeaderMetadata(); - Node leaderNode = raftServer.getRaftStateMachine().getRaftLeaderMetadata().getLeader(); - leaderNode.setGroup(RAFT_CONFIG_GROUP); - nodes.add(leaderNode); - nodes.addAll(raftClusterMetadata.getLearner()); - nodes.addAll(raftClusterMetadata.getFollowers()); - metadataResponse.setTerm(raftClusterMetadata.getTerm()); - metadataResponse.setNodes(new ArrayList<>(nodes)); - } - } catch (Exception e) { - LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); - } - } - return metadataResponse; - } - - @GetMapping("/config/get") - public ConfigOperationResponse getConfig(String namespace, String dataId, String key) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - checkParam(key, "key"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildGetRequest(namespace, dataId, key); - return executeConfigOperationRequest(request); - } - - @PostMapping("/config/put") - public ConfigOperationResponse putConfig(String namespace, String dataId, String key, String value) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - checkParam(key, "key"); - checkParam(value, "value"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildPutRequest(namespace, dataId, key, value); - return executeConfigOperationRequest(request); - } - - @DeleteMapping("/config/delete") - public ConfigOperationResponse deleteConfig(String namespace, String dataId, String key) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - checkParam(key, "key"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildDeleteRequest(namespace, dataId, key); - return executeConfigOperationRequest(request); - } - - @DeleteMapping("/config/deleteAll") - public ConfigOperationResponse deleteAllConfig(String namespace, String dataId) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildDeleteAllRequest(namespace, dataId); - return executeConfigOperationRequest(request); - } - - @GetMapping("/config/getAll") - public ConfigOperationResponse getAllConfig(String namespace, String dataId) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildGetAllRequest(namespace, dataId); - return executeConfigOperationRequest(request); - } - - @PostMapping("/config/upload") - public ConfigOperationResponse uploadConfig(@RequestParam("namespace") String namespace, @RequestParam("dataId") String dataId, @RequestParam("file") MultipartFile file) { - try { - checkParam(namespace, "namespace"); - checkParam(dataId, "dataId"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - if (file == null || file.isEmpty()) { - return ConfigOperationResponse.fail("The configuration file cannot be empty!"); - } - String fileName = file.getOriginalFilename(); - String dataType = SUFFIX_MAP.get(getFileType(fileName)); - if (StringUtils.isEmpty(dataType)) { - return ConfigOperationResponse.fail("The configuration file type is not supported!"); - } - StringBuilder sb = new StringBuilder(); - Map configMap = new HashMap<>(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append('\n'); - } - Properties properties = ConfigProcessor.processConfig(sb.toString(), dataType); - for (String key : properties.stringPropertyNames()) { - String value = properties.getProperty(key); - // remove 'seata.' prefix compatible with the config under Spring Boot - if (key.startsWith(SEATA_FILE_PREFIX_ROOT_CONFIG)) { - key = key.substring(SEATA_FILE_PREFIX_ROOT_CONFIG.length()); - } - configMap.put(key, value); - } - } catch (IOException e) { - LOGGER.error("Failed to read config file: {}", e.getMessage()); - return ConfigOperationResponse.fail("Failed to read config file"); - } - ConfigOperationRequest request = ConfigOperationRequest.buildUploadRequest(namespace, dataId, configMap); - return executeConfigOperationRequest(request); - } - - private static String getFileType(String fileName) { - if (StringUtils.isEmpty(fileName)) { - return null; - } - return fileName.substring(fileName.lastIndexOf(".") + 1); - } - - @GetMapping("/config/getNamespaces") - public ConfigOperationResponse getNamespaces() { - ConfigOperationRequest request = ConfigOperationRequest.buildGetNamespaces(); - return executeConfigOperationRequest(request); - } - - @GetMapping("/config/getDataIds") - public ConfigOperationResponse getDataIds(String namespace) { - try { - checkParam(namespace, "namespace"); - } catch (IllegalArgumentException e) { - return ConfigOperationResponse.fail(e.getMessage()); - } - ConfigOperationRequest request = ConfigOperationRequest.buildGetDataIds(namespace); - return executeConfigOperationRequest(request); - } - - private ConfigOperationResponse executeConfigOperationRequest(ConfigOperationRequest request) { - PeerId leader = RaftConfigServerManager.getLeader(); - if (leader == null) { - return ConfigOperationResponse.fail("failed to get leader"); - } - InvokeContext invokeContext = new InvokeContext(); - invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, - SerializerType.JACKSON.getCode()); - CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); - try { - return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); - } catch (Exception e) { - LOGGER.error("Failed to execute request: {}", request.toString()); - return ConfigOperationResponse.fail(e.getMessage()); - } - } - - private void checkParam(final String param, final String key) { - if (StringUtils.isEmpty(param)) { - throw new IllegalArgumentException("Param '" + key + "' is required."); - } - } - @PostMapping("/watch") public void watch(HttpServletRequest request, @RequestParam Map groupTerms, @RequestParam(defaultValue = "28000") int timeout) { @@ -375,22 +134,4 @@ public void watch(HttpServletRequest request, @RequestParam Map }); } - @PostMapping("/config/watch") - public void configWatch(HttpServletRequest request, @RequestParam String namespace, @RequestParam String dataId, @RequestParam(required = false) Long version, - @RequestParam(defaultValue = "28000") int timeout) { - Long currentVersion = configStoreManager.getConfigVersion(namespace, dataId); - // if the config version of client is lower than the server, return directly - if (version == null || (currentVersion != null && version < currentVersion)) { - AsyncContext context = request.startAsync(); - HttpServletResponse httpServletResponse = (HttpServletResponse) context.getResponse(); - httpServletResponse.setStatus(HttpServletResponse.SC_OK); - context.complete(); - return; - } - AsyncContext context = request.startAsync(); - context.setTimeout(0L); - ConfigWatcher configWatcher = new ConfigWatcher<>(namespace, dataId, context, timeout); - clusterConfigWatcherManager.registryWatcher(configWatcher); - } - } diff --git a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java index 70b8daca678..974416b26c5 100644 --- a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java +++ b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java @@ -21,7 +21,6 @@ import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.config.FileConfiguration; import org.apache.seata.config.file.FileConfig; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.store.StoreConfig; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -75,8 +74,6 @@ public void initialize(ConfigurableApplicationContext applicationContext) { environment.getPropertySources().addLast(new PropertiesPropertySource("seataOldConfig", properties)); } // Load by priority - RaftConfigServerManager.init(); - RaftConfigServerManager.start(); System.setProperty("sessionMode", StoreConfig.getSessionMode().getName()); System.setProperty("lockMode", StoreConfig.getLockMode().getName()); } diff --git a/server/src/main/resources/configuration-meta.yml b/server/src/main/resources/configuration-meta.yml deleted file mode 100644 index c3cbe593e77..00000000000 --- a/server/src/main/resources/configuration-meta.yml +++ /dev/null @@ -1,625 +0,0 @@ -# -# 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. -# - -configuration-meta: - # common configuration - - key: 'transport.type' - desc: 'socket 通信方式' - defaultValue: TCP - isEncrypt: false - - - key: 'transport.server' - desc: 'socket 通道类型' - defaultValue: null - isEncrypt: false - - - key: 'transport.threadFactory.bossThreadSize' - desc: 'Netty 通信模型 Boss group 线程数' - defaultValue: 1 - isEncrypt: false - - - key: 'transport.threadFactory.workerThreadSize' - desc: 'Netty 通信模型 Worker group 线程数,线程的默认工作模式有 4 种: Auto(2*CPU 核数 + 1)、Pin(CPU 核数)、BusyPin(CPU 核数 + 1)、Default(2*CPU 核数)' - defaultValue: Default - isEncrypt: false - - - key: 'transport.shutdown.wait' - desc: '服务端 Netty 线程池关闭前等待服务下线时间' - defaultValue: 3 - isEncrypt: false - - - key: 'transport.serialization' - desc: 'client 和 server 通信编解码方式' - defaultValue: seata - isEncrypt: false - - - key: 'transport.compressor' - desc: 'client 和 server 通信数据压缩方式,支持 none、gzip、zip、sevenz、bzip2、lz4、deflater、zstd' - defaultValue: none - isEncrypt: false - - - key: 'transport.heartbeat' - desc: 'client 和 server 通信心跳检测开关' - defaultValue: true - isEncrypt: false - - - key: 'registry.type' - desc: '注册中心类型,支持 file 、nacos 、redis、eureka、zk、consul、etcd3、sofa、custom' - defaultValue: file - isEncrypt: false - - - key: 'config.type' - desc: '配置中心类型,支持 file、nacos 、apollo、zk、consul、etcd3、springcloud、custom' - defaultValue: file - isEncrypt: false - - # server configuration - - key: 'transport.enableTcServerBatchSendResponse' - desc: 'TC 批量发送回复消息开关' - defaultValue: false - isEncrypt: false - - - key: 'transport.rpcTcRequestTimeout' - desc: 'TC 二阶段下发请求超时时间' - defaultValue: 30 - isEncrypt: false - - - key: 'server.undo.logSaveDays' - desc: 'undo 保留天数,log_status=1 和未正常清理的 undo' - defaultValue: 7 - isEncrypt: false - - - key: 'server.undo.logDeletePeriod' - desc: 'undo 清理线程间隔时间' - defaultValue: 86400000 - isEncrypt: false - - - key: 'server.maxCommitRetryTimeout' - desc: '二阶段提交重试超时时长,单位 ms,s,m,h,d,默认值为-1 表示无限重试' - defaultValue: -1 - isEncrypt: false - - - key: 'server.maxRollbackRetryTimeout' - desc: '二阶段回滚重试超时时长,同 commit' - defaultValue: -1 - isEncrypt: false - - - key: 'server.recovery.committingRetryPeriod' - desc: '二阶段提交未完成状态全局事务重试提交线程间隔时间' - defaultValue: 1000 - isEncrypt: false - - - key: 'server.recovery.asynCommittingRetryPeriod' - desc: '二阶段异步提交状态重试提交线程间隔时间' - defaultValue: 1000 - isEncrypt: false - - - key: 'server.recovery.rollbackingRetryPeriod' - desc: '二阶段回滚状态重试回滚线程间隔时间' - defaultValue: 1000 - isEncrypt: false - - - key: 'server.recovery.timeoutRetryPeriod' - desc: '超时状态检测重试线程间隔时间,检测出超时将全局事务置入回滚会话管理器' - defaultValue: 1000 - isEncrypt: false - - - key: 'server.rollbackRetryTimeoutUnlockEnable' - desc: '二阶段回滚超时后是否释放锁' - defaultValue: false - isEncrypt: false - - - key: 'server.distributedLockExpireTime' - desc: 'Server 端事务管理全局锁超时时间' - defaultValue: 10000 - isEncrypt: false - - - key: 'server.server.xaerNotaRetryTimeout' - desc: '防止 XA 分支事务悬挂的重试超时时间' - defaultValue: 60000 - isEncrypt: false - - - key: 'server.session.branchAsyncQueueSize' - desc: '分支事务 Session 异步删除线程池队列大小' - defaultValue: 5000 - isEncrypt: false - - - key: 'server.session.enableBranchAsyncRemove' - desc: '分支事务 Session 异步删除开关' - defaultValue: false - isEncrypt: false - - - key: 'server.enableParallelRequestHandle' - desc: '对于批量请求消息的并行处理开关' - defaultValue: true - isEncrypt: false - - - key: 'server.enableParallelHandleBranch' - desc: '二阶段并行下发开关' - defaultValue: false - isEncrypt: false - - - key: 'server.applicationDataLimitCheck' - desc: '是否开启应用数据大小检查' - defaultValue: false - isEncrypt: false - - - key: 'server.applicationDataLimit' - desc: '应用数据大小限制' - defaultValue: 64000 - isEncrypt: false - - - key: 'server.raft.group' - desc: 'raft 存储模式下的 group,client 的事务分组值要与之对应' - defaultValue: default - isEncrypt: false - - - key: 'server.raft.server-addr' - desc: 'raft 集群列表如 192.168.0.111:9091,192.168.0.112:9091' - defaultValue: null - isEncrypt: false - - - key: 'server.raft.snapshot-interval' - desc: '间隔多久做一次内存快照,暂停状态机,但能提高停机恢复速度' - defaultValue: 600 - isEncrypt: false - - - key: 'server.raft.apply-batch' - desc: '任务累积批次后提交至 leader' - defaultValue: 32 - isEncrypt: false - - - key: 'server.raft.max-append-bufferSize' - desc: 'raft 日志存储缓冲区最大大小' - defaultValue: 256K - isEncrypt: false - - - key: 'server.raft.max-replicator-inflight-msgs' - desc: '启用 pipeline 请求情况下,最大 in-flight 请求数' - defaultValue: 256 - isEncrypt: false - - - key: 'server.raft.disruptor-buffer-size' - desc: '内部 disruptor buffer 大小,适当调高该值适应写入吞吐量高场景' - defaultValue: 16384 - isEncrypt: false - - - key: 'server.raft.election-timeout-ms' - desc: '超过多久没有 leader 心跳开始重选举' - defaultValue: 1000 - isEncrypt: false - - - key: 'server.raft.reporter-enabled' - desc: 'raft 自身的监控是否开启' - defaultValue: false - isEncrypt: false - - - key: 'server.raft.reporter-initial-delay' - desc: '监控输出间隔' - defaultValue: 60 - isEncrypt: false - - - key: 'server.raft.serialization' - desc: '序列化方式,仅支持 jackson' - defaultValue: jackson - isEncrypt: false - - - key: 'server.raft.compressor' - desc: 'raftlog 和 snapshot 的压缩方式,支持 gzip, zstd, lz4' - defaultValue: none - isEncrypt: false - - - key: 'server.raft.sync' - desc: 'raftlog 同步刷盘' - defaultValue: true - isEncrypt: false - - - key: 'store.mode' - desc: '事务会话信息存储方式' - defaultValue: file - isEncrypt: false - - - key: 'store.lock.mode' - desc: '事务锁信息存储方式' - defaultValue: file - isEncrypt: false - - - key: 'store.session.mode' - desc: '事务回话信息存储方式' - defaultValue: file - isEncrypt: false - - - key: 'store.publicKey' - desc: 'db 或 redis 存储密码解密公钥' - defaultValue: null - isEncrypt: true - - - key: 'store.file.dir' - desc: 'file 模式文件存储文件夹名' - defaultValue: sessionStore - isEncrypt: false - - - key: 'store.file.maxBranchSessionSize' - desc: 'file 模式文件存储分支 session 最大字节数' - defaultValue: 16384(16kb) - isEncrypt: false - - - key: 'store.file.maxGlobalSessionSize' - desc: 'file 模式文件存储全局 session 最大字节数' - defaultValue: 512b - isEncrypt: false - - - key: 'store.file.fileWriteBufferCacheSize' - desc: 'file 模式文件存储 buffer 最大缓存大小' - defaultValue: 16384(16kb) - isEncrypt: false - - - key: 'store.file.flushDiskMode' - desc: 'file 模式文件存储刷盘策略' - defaultValue: async - isEncrypt: false - - - key: 'store.file.sessionReloadReadSize' - desc: 'file 模式文件存储 Server 节点重启后从备份文件中恢复的 session 或 lock key 上限个数' - defaultValue: 100 - isEncrypt: false - - - key: 'store.db.datasource' - desc: 'db 模式数据源类型' - defaultValue: '' - isEncrypt: false - - - key: 'store.db.dbType' - desc: 'db 模式数据库类型' - defaultValue: '' - isEncrypt: false - - - key: 'store.db.driverClassName' - desc: 'db 模式数据库驱动' - defaultValue: '' - isEncrypt: false - - - key: 'store.db.url' - desc: 'db 模式数据库 url' - defaultValue: '' - isEncrypt: false - - - key: 'store.db.user' - desc: 'db 模式数据库账户' - defaultValue: '' - isEncrypt: false - - - key: 'store.db.password' - desc: 'db 模式数据库账户密码' - defaultValue: '' - isEncrypt: true - - - key: 'store.db.minConn' - desc: 'db 模式数据库初始连接数' - defaultValue: 1 - isEncrypt: false - - - key: 'store.db.maxConn' - desc: 'db 模式数据库最大连接数' - defaultValue: 20 - isEncrypt: false - - - key: 'store.db.maxWait' - desc: 'db 模式获取连接时最大等待时间' - defaultValue: 5000 - isEncrypt: false - - - key: 'store.db.globalTable' - desc: 'db 模式全局事务表名' - defaultValue: 'global_table' - isEncrypt: false - - - key: 'store.db.branchTable' - desc: 'db 模式分支事务表名' - defaultValue: 'branch_table' - isEncrypt: false - - - key: 'store.db.lockTable' - desc: 'db 模式全局锁表名' - defaultValue: 'lock_table' - isEncrypt: false - - - key: 'store.db.queryLimit' - desc: 'db 模式查询全局事务一次的最大条数' - defaultValue: 100 - isEncrypt: false - - - key: 'store.db.distributedLockTable' - desc: 'db 模式 Sever 端事务管理全局锁存储表名' - defaultValue: 'distributed_lock' - isEncrypt: false - - - key: 'store.redis.mode' - desc: 'redis 模式' - defaultValue: single - isEncrypt: false - - - key: 'store.redis.single.host' - desc: '单机模式下 redis 的 host' - defaultValue: '' - isEncrypt: false - - - key: 'store.redis.single.port' - desc: '单机模式下 redis 的 port' - defaultValue: '' - isEncrypt: false - - - key: 'store.redis.sentinel.masterName' - desc: 'sentinel 模式下 redis 的主库名称' - defaultValue: '' - isEncrypt: false - - - key: 'store.redis.sentinel.sentinelHosts' - desc: 'sentinel 模式下 sentinel 的 hosts' - defaultValue: '' - isEncrypt: false - - - key: 'store.redis.host' - desc: 'redis 模式 ip' - defaultValue: '127.0.0.1' - isEncrypt: false - - - key: 'store.redis.port' - desc: 'redis 模式端口' - defaultValue: 6379 - isEncrypt: false - - - key: 'store.redis.maxConn' - desc: 'redis 模式最大连接数' - defaultValue: 10 - isEncrypt: false - - - key: 'store.redis.minConn' - desc: 'redis 模式最小连接数' - defaultValue: 1 - isEncrypt: false - - - key: 'store.redis.database' - desc: 'redis 模式默认库' - defaultValue: 0 - isEncrypt: false - - - key: 'store.redis.password' - desc: 'redis 模式密码' - defaultValue: 'null' - isEncrypt: true - - - key: 'store.redis.queryLimit' - desc: 'redis 模式一次查询最大条数' - defaultValue: 100 - isEncrypt: false - - - key: 'store.redis.type' - desc: 'redis 模式主要使用的方式: lua, pippline' - defaultValue: 'pippline' - isEncrypt: false - - - key: 'metrics.enabled' - desc: '是否启用 Metrics' - defaultValue: false - isEncrypt: false - - - key: 'metrics.registryType' - desc: '指标注册器类型' - defaultValue: 'compact' - isEncrypt: false - - - key: 'metrics.exporterList' - desc: '指标结果 Measurement 数据输出器列表' - defaultValue: 'prometheus' - isEncrypt: false - - - key: 'metrics.exporterPrometheusPort' - desc: 'prometheus 输出器 Client 端口号' - defaultValue: 9898 - isEncrypt: false - - # client configuration - - key: 'seata.enabled' - desc: '是否开启 spring-boot 自动装配' - defaultValue: true - isEncrypt: false - - - key: 'seata.enableAutoDataSourceProxy' - desc: '是否开启数据源自动代理' - defaultValue: true - isEncrypt: false - - - key: 'seata.useJdkProxy' - desc: '是否使用 JDK 代理作为数据源自动代理的实现方式' - defaultValue: false - isEncrypt: false - - - key: 'transport.enableClientBatchSendRequest' - desc: '客户端事务消息请求是否批量合并发送' - defaultValue: true - isEncrypt: false - - - key: 'transport.enableTmClientChannelCheckFailFast' - desc: '客户端 TM 快速失败检查' - defaultValue: true - isEncrypt: false - - - key: 'transport.enableRmClientChannelCheckFailFast' - desc: '客户端 RM 快速失败检查' - defaultValue: true - isEncrypt: false - - - key: 'client.log.exceptionRate' - desc: '日志异常输出概率' - defaultValue: 100 - isEncrypt: false - - - key: 'service.vgroupMapping.my_test_tx_group' - desc: '事务群组(附录 1)' - defaultValue: '' - isEncrypt: false - - - key: 'service.default.grouplist' - desc: 'TC 服务列表(附录 2)' - defaultValue: '' - isEncrypt: false - - - key: 'service.disableGlobalTransaction' - desc: '全局事务开关' - defaultValue: false - isEncrypt: false - - - key: 'client.tm.degradeCheck' - desc: '降级开关' - defaultValue: false - isEncrypt: false - - - key: 'client.tm.degradeCheckAllowTimes' - desc: '升降级达标阈值' - defaultValue: 10 - isEncrypt: false - - - key: 'client.tm.degradeCheckPeriod' - desc: '服务自检周期,单位 ms,每 2 秒进行一次服务自检' - defaultValue: 2000 - isEncrypt: false - - - key: 'client.rm.reportSuccessEnable' - desc: '是否上报一阶段成功' - defaultValue: false - isEncrypt: false - - - key: 'client.rm.asyncCommitBufferLimit' - desc: '异步提交缓存队列长度' - defaultValue: 10000 - isEncrypt: false - - - key: 'client.rm.lock.retryInterval' - desc: '校验或占用全局锁重试间隔,单位毫秒' - defaultValue: 10 - isEncrypt: false - - - key: 'client.rm.lock.retryTimes' - desc: '校验或占用全局锁重试次数' - defaultValue: 30 - isEncrypt: false - - - key: 'client.rm.lock.retryPolicyBranchRollbackOnConflict' - desc: '分支事务与其它全局回滚事务冲突时锁策略' - defaultValue: true - isEncrypt: false - - - key: 'client.rm.reportRetryCount' - desc: '一阶段结果上报 TC 重试次数' - defaultValue: 5 - isEncrypt: false - - - key: 'client.rm.tableMetaCheckEnable' - desc: '自动刷新缓存中的表结构' - defaultValue: false - isEncrypt: false - - - key: 'client.rm.tableMetaCheckerInterval' - desc: '定时刷新缓存中表结构间隔时间,单位秒' - defaultValue: 60 - isEncrypt: false - - - key: 'client.rm.sagaBranchRegisterEnable' - desc: '是否开启 saga 分支注册' - defaultValue: false - isEncrypt: false - - - key: 'client.rm.sagaJsonParser' - desc: 'saga 模式中数据序列化方式' - defaultValue: fastjson - isEncrypt: false - - - key: 'client.rm.tccActionInterceptorOrder' - desc: 'tcc 拦截器顺序' - defaultValue: 'Ordered.HIGHEST_PRECEDENCE + 1000' - isEncrypt: false - - - key: 'client.rm.applicationDataLimitCheck' - desc: '客户端应用数据是否开启限制' - defaultValue: false - isEncrypt: false - - - key: 'client.rm.applicationDataLimit' - desc: '客户端应用数据上报限制' - defaultValue: 64000 - isEncrypt: false - - - key: 'client.tm.commitRetryCount' - desc: '一阶段全局提交结果上报 TC 重试次数' - defaultValue: 1 - isEncrypt: false - - - key: 'client.tm.rollbackRetryCount' - desc: '一阶段全局回滚结果上报 TC 重试次数' - defaultValue: 1 - isEncrypt: false - - - key: 'client.tm.defaultGlobalTransactionTimeout' - desc: '全局事务超时时间' - defaultValue: 60 - isEncrypt: false - - - key: 'client.tm.interceptorOrder' - desc: 'TM 全局事务拦截器顺序' - defaultValue: 'Ordered.HIGHEST_PRECEDENCE + 1000' - isEncrypt: false - - - key: 'client.undo.dataValidation' - desc: '二阶段回滚镜像校验' - defaultValue: true - isEncrypt: false - - - key: 'client.undo.logSerialization' - desc: 'undo 序列化方式' - defaultValue: jackson - isEncrypt: false - - - key: 'client.undo.logTable' - desc: '自定义 undo 表名' - defaultValue: undo_log - isEncrypt: false - - - key: 'client.undo.onlyCareUpdateColumns' - desc: '只生成被更新列的镜像' - defaultValue: true - isEncrypt: false - - - key: 'client.undo.compress.enable' - desc: 'undo log 压缩开关' - defaultValue: true - isEncrypt: false - - - key: 'client.undo.compress.type' - desc: 'undo log 压缩算法' - defaultValue: zip - isEncrypt: false - - - key: 'client.undo.compress.threshold' - desc: 'undo log 压缩阈值' - defaultValue: '64k' - isEncrypt: false - - - key: 'client.rm.sqlParserType' - desc: 'sql 解析类型' - defaultValue: druid - isEncrypt: false diff --git a/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java b/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java deleted file mode 100644 index 589f06ac49f..00000000000 --- a/server/src/test/java/org/apache/seata/server/config/ConfigurationProcessorTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 org.apache.seata.server.config; - - -import java.util.HashMap; -import java.util.Map; - -import org.apache.seata.config.dto.ConfigurationItem; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -class ConfigurationProcessorTest { - - @Test - void processConfigMap() { - String key1 = "transport.type"; - String value1 = "TCP"; - String key2 = "UNKNOWN"; - String value2 = "UNKNOWN"; - HashMap configMap = new HashMap<>(); - configMap.put(key1, value1); - configMap.put(key2, value2); - - Map itemMap = ConfigurationProcessor.processConfigMap(configMap); - - ConfigurationItem item1 = itemMap.get(key1); - ConfigurationItem item2 = itemMap.get(key2); - Assertions.assertEquals(2, itemMap.size()); - Assertions.assertEquals(key1, item1.getKey()); - Assertions.assertEquals(value1, item1.getValue()); - Assertions.assertNotNull(item1.getDefaultValue()); - Assertions.assertNotNull(item1.getDescription()); - - Assertions.assertEquals(key2, item2.getKey()); - Assertions.assertEquals(value2, item2.getValue()); - Assertions.assertNull(item2.getDefaultValue()); - Assertions.assertNotNull(item2.getDescription()); - - } -} diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java index a2ef8c3650e..a85de536066 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftServerTest.java @@ -23,7 +23,10 @@ import org.apache.seata.server.lock.LockerManagerFactory; import org.apache.seata.server.session.SessionHolder; import org.apache.seata.server.store.StoreConfig; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @@ -35,11 +38,6 @@ public static void setUp(ApplicationContext context) { LockerManagerFactory.destroy(); SessionHolder.destroy(); } - @BeforeEach - public void init() { - System.setProperty("server.raftPort", "0"); - System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR, ""); - } @AfterEach public void destroy() { diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java index 33d4334d2d4..e6f9c8f905d 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java @@ -29,18 +29,15 @@ import org.apache.seata.common.metadata.Node; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchType; -import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; -import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; -import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; -import org.apache.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; import org.apache.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; +import org.apache.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; import org.apache.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; -import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; import org.apache.seata.server.session.GlobalSession; @@ -222,34 +219,4 @@ public void testRaftClusterMetadataSerialize() throws IOException { Assertions.assertEquals(ClusterRole.LEARNER,learner1.getRole()); } - @Test - public void testConfigSnapshotSerialize() throws IOException{ - Map configMap = new HashMap<>(); - configMap.put("config.type","file"); - configMap.put("store","file"); - - RaftSnapshot raftSnapshot = new RaftSnapshot(); - raftSnapshot.setBody(configMap); - raftSnapshot.setType(RaftSnapshot.SnapshotType.config); - byte[] msg = RaftSnapshotSerializer.encode(raftSnapshot); - RaftSnapshot raftSnapshot1 = RaftSnapshotSerializer.decode(msg); - HashMap configMap1 = (HashMap) raftSnapshot1.getBody(); - Assertions.assertEquals(configMap,configMap1); - } - - @Test - public void testConfigMsgSerialize() throws IOException{ - RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); - ConfigOperationDTO configOperationDTO = new ConfigOperationDTO(ConfigOperationType.PUT, "namespace", "dataId", "key", "value"); - RaftConfigOperationSyncMsg configSyncMsg = new RaftConfigOperationSyncMsg(configOperationDTO); - raftSyncMessage.setBody(configSyncMsg); - byte[] msg = RaftSyncMessageSerializer.encode(raftSyncMessage); - RaftSyncMessage raftSyncMessage1 = RaftSyncMessageSerializer.decode(msg); - Assertions.assertEquals(configSyncMsg.getMsgType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getMsgType()); - Assertions.assertEquals(configSyncMsg.getConfigOperation().getKey(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getKey()); - Assertions.assertEquals(configSyncMsg.getConfigOperation().getValue(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getValue()); - Assertions.assertEquals(configSyncMsg.getConfigOperation().getNamespace(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getNamespace()); - Assertions.assertEquals(configSyncMsg.getConfigOperation().getDataId(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getDataId()); - Assertions.assertEquals(configSyncMsg.getConfigOperation().getOptType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getOptType()); - } } diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java deleted file mode 100644 index 0a7724ce165..00000000000 --- a/server/src/test/java/org/apache/seata/server/raft/execute/ConfigOperationExecuteTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 org.apache.seata.server.raft.execute; - -import javax.annotation.Resource; - -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.common.util.NetUtil; -import org.apache.seata.config.ConfigurationCache; -import org.apache.seata.server.cluster.raft.RaftConfigServerManager; -import org.apache.seata.server.controller.ClusterController; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.ApplicationContext; - -@SpringBootTest -class ConfigOperationExecuteTest { - @Resource - private ClusterController clusterController; - - private static final String NAMESPACE = "test"; - private static final String DATA_ID = "test"; - - @BeforeAll - public static void setUp(ApplicationContext context) { - RaftConfigServerManager.destroy(); - System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR, NetUtil.getLocalIp() + ":9191"); - System.setProperty("config.type", "raft"); - System.setProperty("registry.preferredNetworks", "*"); - System.setProperty("config.raft.db.type", "rocksdb"); - System.setProperty("config.raft.db.dir", "configStore"); - System.setProperty("config.raft.db.destroyOnShutdown", "true"); - RaftConfigServerManager.init(); - RaftConfigServerManager.start(); - } - - @AfterAll - public static void destroy() { - RaftConfigServerManager.destroy(); - ConfigurationCache.clear(); - System.setProperty(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR,""); - Assertions.assertNull(RaftConfigServerManager.getRaftServer()); - System.clearProperty("config.type"); - System.clearProperty("registry.preferredNetworks"); - System.clearProperty("config.raft.db.dir"); - } - - @Test - public void testCRUD() { - clusterController.deleteAllConfig(NAMESPACE, DATA_ID); - String key1 = "aaa"; - String value1 = "bbb"; - String key2 = "ccc"; - String value2 = "ddd"; - Assertions.assertTrue(clusterController.getConfig(NAMESPACE, DATA_ID, key1).isSuccess()); - Assertions.assertTrue(clusterController.getAllConfig(NAMESPACE, DATA_ID).isSuccess()); - Assertions.assertTrue(clusterController.putConfig(NAMESPACE, DATA_ID, key1, value1).isSuccess()); - Assertions.assertTrue(clusterController.putConfig(NAMESPACE, DATA_ID, key2, value2).isSuccess()); - Assertions.assertTrue(clusterController.deleteConfig(NAMESPACE, DATA_ID, key1).isSuccess()); - Assertions.assertTrue(clusterController.deleteAllConfig(NAMESPACE, DATA_ID).isSuccess()); - Assertions.assertTrue(clusterController.getNamespaces().isSuccess()); - Assertions.assertTrue(clusterController.getDataIds(NAMESPACE).isSuccess()); - } -} From 37b8418ae8cc67e331656d062f823bf2a8f30f60 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Tue, 29 Oct 2024 14:43:48 +0800 Subject: [PATCH 33/54] optimize: modify seata-http-jakarta's name and description in pom.xml (#6959) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + integration/http-jakarta/pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2d1b8ffe1fb..2becd817c3a 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -36,6 +36,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] Use the openjdk image of eclipse-temurin as the base image - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] Update online chat information in README.md - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id +- [[#6959](https://github.com/apache/incubator-seata/pull/6959)] update the naming and description for the `seata-http-jakarta` module ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 321e751c6a7..4329620e141 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -38,6 +38,7 @@ - [[#6918](https://github.com/apache/incubator-seata/pull/6918)] 使用eclipse-temurin的openjdk镜像作为基础镜像 - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] 更新 README.md 中的社区联系信息 - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] 移除JVM参数app.id +- [[#6959](https://github.com/apache/incubator-seata/pull/6959)] 修正 `seata-http-jakarta`的模块命名和描述 ### refactor: diff --git a/integration/http-jakarta/pom.xml b/integration/http-jakarta/pom.xml index 73e7042863d..e458d8af1d5 100644 --- a/integration/http-jakarta/pom.xml +++ b/integration/http-jakarta/pom.xml @@ -29,8 +29,8 @@ 4.0.0 seata-http-jakarta jar - seata-http ${project.version} - http-client integration for Seata built with Maven + seata-http-jakarta ${project.version} + http-jakarta-client integration for Seata built with Maven From bf0d11a3944cf0b641472e55899bd437f250bde1 Mon Sep 17 00:00:00 2001 From: GoodBoyCoder Date: Thu, 31 Oct 2024 10:49:21 +0800 Subject: [PATCH 34/54] feature: add fastjson2 serializer support (#6904) --- all/pom.xml | 5 + changes/en-us/2.x.md | 3 +- changes/zh-cn/2.x.md | 1 + .../serializer/SerializerServiceLoader.java | 3 +- .../seata/core/serializer/SerializerType.java | 7 + dependencies/pom.xml | 6 + serializer/pom.xml | 1 + serializer/seata-serializer-all/pom.xml | 5 + serializer/seata-serializer-fastjson2/pom.xml | 51 +++++++ .../Fastjson2Serializer.java | 35 +++++ .../Fastjson2SerializerFactory.java | 73 ++++++++++ ...rg.apache.seata.core.serializer.Serializer | 17 +++ .../fastjson2/Fastjson2SerializerTest.java | 85 +++++++++++ .../netty/mockserver/MockFastJson2Test.java | 132 ++++++++++++++++++ 14 files changed, 422 insertions(+), 2 deletions(-) create mode 100644 serializer/seata-serializer-fastjson2/pom.xml create mode 100644 serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2Serializer.java create mode 100644 serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2SerializerFactory.java create mode 100644 serializer/seata-serializer-fastjson2/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer create mode 100644 serializer/seata-serializer-fastjson2/src/test/java/org/apache/seata/serializer/fastjson2/Fastjson2SerializerTest.java create mode 100644 test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockFastJson2Test.java diff --git a/all/pom.xml b/all/pom.xml index 7e4eb9ab293..dfffb07feea 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -272,6 +272,11 @@ seata-serializer-hessian ${project.version} + + org.apache.seata + seata-serializer-fastjson2 + ${project.version} + org.apache.seata seata-compressor-gzip diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 2becd817c3a..768ce3ebc1b 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -3,11 +3,12 @@ Add changes here for all PR submitted to the 2.x branch. ### feature: - +- [[#6904](https://github.com/apache/incubator-seata/pull/6904)] add fastjson2 serializer support - [[#6876](https://github.com/apache/incubator-seata/pull/6876)] support kingbase - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] support grpc - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] support shentong database + ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] fix file.conf read failed after package - [[#6890](https://github.com/apache/incubator-seata/pull/6890)] fix designerJson to standardJson: subStateMachine compensateState cannot be recognized diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 4329620e141..7a5366ec660 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -3,6 +3,7 @@ ### feature: +- [[#6904](https://github.com/apache/incubator-seata/pull/6904)] 增加Fastjson2序列化Rpc消息支持 - [[#6876](https://github.com/apache/incubator-seata/pull/6876)] 支持人大金仓数据库(kingbase) - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] client和server支持grpc协议 - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] 支持神通数据库(oscar) diff --git a/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java b/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java index 0aa9bd340e8..359867b5104 100644 --- a/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java +++ b/core/src/main/java/org/apache/seata/core/serializer/SerializerServiceLoader.java @@ -30,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.seata.core.serializer.SerializerType.FASTJSON2; import static org.apache.seata.core.serializer.SerializerType.HESSIAN; import static org.apache.seata.core.serializer.SerializerType.KRYO; import static org.apache.seata.core.serializer.SerializerType.PROTOBUF; @@ -46,7 +47,7 @@ public final class SerializerServiceLoader { private static final Logger LOGGER = LoggerFactory.getLogger(SerializerServiceLoader.class); private static final Configuration CONFIG = ConfigurationFactory.getInstance(); - private static final SerializerType[] DEFAULT_SERIALIZER_TYPE = new SerializerType[]{SEATA, PROTOBUF, KRYO, HESSIAN}; + private static final SerializerType[] DEFAULT_SERIALIZER_TYPE = new SerializerType[]{SEATA, PROTOBUF, KRYO, HESSIAN, FASTJSON2}; private final static Map SERIALIZER_MAP = new HashMap<>(); diff --git a/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java b/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java index c60067e72bc..56fd8136d17 100644 --- a/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java +++ b/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java @@ -63,6 +63,13 @@ public enum SerializerType { * Math.pow(2, 5) */ JACKSON((byte)0x32), + + /** + * The fastjson2. + *

+ * Math.pow(2, 6) + */ + FASTJSON2((byte)0x64), ; private final byte code; diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 15178f14166..6b93148e613 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -81,6 +81,7 @@ 5.4.0 0.45 4.0.63 + 2.0.52 2.4.4 1.5.0-4 1.4.20 @@ -662,6 +663,11 @@ hessian ${hessian.version} + + com.alibaba.fastjson2 + fastjson2 + ${fastjson2.version} + org.apache.commons commons-compress diff --git a/serializer/pom.xml b/serializer/pom.xml index 6b06a80f4e9..246ba6c2757 100644 --- a/serializer/pom.xml +++ b/serializer/pom.xml @@ -37,6 +37,7 @@ seata-serializer-seata seata-serializer-kryo seata-serializer-hessian + seata-serializer-fastjson2 diff --git a/serializer/seata-serializer-all/pom.xml b/serializer/seata-serializer-all/pom.xml index a772be672aa..a0d816153bf 100644 --- a/serializer/seata-serializer-all/pom.xml +++ b/serializer/seata-serializer-all/pom.xml @@ -50,5 +50,10 @@ seata-serializer-hessian ${project.version} + + ${project.groupId} + seata-serializer-fastjson2 + ${project.version} + diff --git a/serializer/seata-serializer-fastjson2/pom.xml b/serializer/seata-serializer-fastjson2/pom.xml new file mode 100644 index 00000000000..b586ecac999 --- /dev/null +++ b/serializer/seata-serializer-fastjson2/pom.xml @@ -0,0 +1,51 @@ + + + + + org.apache.seata + seata-serializer + ${revision} + + 4.0.0 + seata-serializer-fastjson2 + jar + seata-serializer-fastjson2 ${project.version} + serializer-fastjson2 for Seata built with Maven + + + + ${project.groupId} + seata-common + ${project.version} + + + ${project.groupId} + seata-core + ${project.version} + + + com.alibaba.fastjson2 + fastjson2 + + + + \ No newline at end of file diff --git a/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2Serializer.java b/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2Serializer.java new file mode 100644 index 00000000000..9c9e081db0e --- /dev/null +++ b/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2Serializer.java @@ -0,0 +1,35 @@ +/* + * 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 org.apache.seata.serializer.fastjson2; + +import com.alibaba.fastjson2.JSONB; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.core.serializer.Serializer; + +@LoadLevel(name = "FASTJSON2") +public class Fastjson2Serializer implements Serializer { + + @Override + public byte[] serialize(T t) { + return JSONB.toBytes(t, Fastjson2SerializerFactory.getInstance().getJsonWriterFeatureList()); + } + + @Override + public T deserialize(byte[] bytes) { + return (T) JSONB.parseObject(bytes, Object.class, Fastjson2SerializerFactory.getInstance().getFilter(), Fastjson2SerializerFactory.getInstance().getJsonReaderFeatureList()); + } +} diff --git a/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2SerializerFactory.java b/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2SerializerFactory.java new file mode 100644 index 00000000000..5e2ef29de58 --- /dev/null +++ b/serializer/seata-serializer-fastjson2/src/main/java/org.apache.seata.serializer.fastjson2/Fastjson2SerializerFactory.java @@ -0,0 +1,73 @@ +/* + * 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 org.apache.seata.serializer.fastjson2; + +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import org.apache.seata.core.serializer.SerializerSecurityRegistry; + +public class Fastjson2SerializerFactory { + private Filter autoTypeFilter; + + private JSONReader.Feature[] jsonReaderFeature; + + private JSONWriter.Feature[] jsonWriterFeature; + private static final class InstanceHolder { + public static final Fastjson2SerializerFactory INSTANCE = new Fastjson2SerializerFactory(); + } + + public Fastjson2SerializerFactory() { + autoTypeFilter = JSONReader.autoTypeFilter(true, SerializerSecurityRegistry.getAllowClassType().toArray(new Class[]{})); + + jsonReaderFeature = new JSONReader.Feature[]{ + JSONReader.Feature.UseDefaultConstructorAsPossible, + // If not configured, it will be serialized based on public field and getter methods by default. + // After configuration, it will be deserialized based on non-static fields (including private). + // It will be safer under FieldBased configuration + JSONReader.Feature.FieldBased, + JSONReader.Feature.IgnoreAutoTypeNotMatch, + JSONReader.Feature.UseNativeObject + }; + + jsonWriterFeature = new JSONWriter.Feature[]{ + JSONWriter.Feature.WriteClassName, + JSONWriter.Feature.FieldBased, + JSONWriter.Feature.ReferenceDetection, + JSONWriter.Feature.WriteNulls, + JSONWriter.Feature.NotWriteDefaultValue, + JSONWriter.Feature.NotWriteHashMapArrayListClassName, + JSONWriter.Feature.WriteNameAsSymbol + }; + } + + public static Fastjson2SerializerFactory getInstance() { + return Fastjson2SerializerFactory.InstanceHolder.INSTANCE; + } + + public Filter getFilter() { + return autoTypeFilter; + } + + public JSONReader.Feature[] getJsonReaderFeatureList() { + return jsonReaderFeature; + } + + public JSONWriter.Feature[] getJsonWriterFeatureList() { + return jsonWriterFeature; + } +} diff --git a/serializer/seata-serializer-fastjson2/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer b/serializer/seata-serializer-fastjson2/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer new file mode 100644 index 00000000000..1dab3f811c0 --- /dev/null +++ b/serializer/seata-serializer-fastjson2/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.serializer.fastjson2.Fastjson2Serializer \ No newline at end of file diff --git a/serializer/seata-serializer-fastjson2/src/test/java/org/apache/seata/serializer/fastjson2/Fastjson2SerializerTest.java b/serializer/seata-serializer-fastjson2/src/test/java/org/apache/seata/serializer/fastjson2/Fastjson2SerializerTest.java new file mode 100644 index 00000000000..d0dc1314670 --- /dev/null +++ b/serializer/seata-serializer-fastjson2/src/test/java/org/apache/seata/serializer/fastjson2/Fastjson2SerializerTest.java @@ -0,0 +1,85 @@ +/* + * 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 org.apache.seata.serializer.fastjson2; + +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchCommitRequest; +import org.apache.seata.core.protocol.transaction.BranchCommitResponse; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class Fastjson2SerializerTest { + + private static Fastjson2Serializer fastjson2Serializer; + + @BeforeAll + public static void before() { + fastjson2Serializer = new Fastjson2Serializer(); + } + + + @Test + public void testBranchCommitRequest() { + + BranchCommitRequest branchCommitRequest = new BranchCommitRequest(); + branchCommitRequest.setBranchType(BranchType.AT); + branchCommitRequest.setXid("xid"); + branchCommitRequest.setResourceId("resourceId"); + branchCommitRequest.setBranchId(20190809); + branchCommitRequest.setApplicationData("app"); + + byte[] bytes = fastjson2Serializer.serialize(branchCommitRequest); + BranchCommitRequest t = fastjson2Serializer.deserialize(bytes); + + assertThat(t.getTypeCode()).isEqualTo(branchCommitRequest.getTypeCode()); + assertThat(t.getBranchType()).isEqualTo(branchCommitRequest.getBranchType()); + assertThat(t.getXid()).isEqualTo(branchCommitRequest.getXid()); + assertThat(t.getResourceId()).isEqualTo(branchCommitRequest.getResourceId()); + assertThat(t.getBranchId()).isEqualTo(branchCommitRequest.getBranchId()); + assertThat(t.getApplicationData()).isEqualTo(branchCommitRequest.getApplicationData()); + + } + + @Test + public void testBranchCommitResponse() { + + BranchCommitResponse branchCommitResponse = new BranchCommitResponse(); + branchCommitResponse.setTransactionExceptionCode(TransactionExceptionCode.BranchTransactionNotExist); + branchCommitResponse.setBranchId(20190809); + branchCommitResponse.setBranchStatus(BranchStatus.PhaseOne_Done); + branchCommitResponse.setMsg("20190809"); + branchCommitResponse.setXid("20190809"); + branchCommitResponse.setResultCode(ResultCode.Failed); + + byte[] bytes = fastjson2Serializer.serialize(branchCommitResponse); + BranchCommitResponse t = fastjson2Serializer.deserialize(bytes); + + assertThat(t.getTransactionExceptionCode()).isEqualTo(branchCommitResponse.getTransactionExceptionCode()); + assertThat(t.getBranchId()).isEqualTo(branchCommitResponse.getBranchId()); + assertThat(t.getBranchStatus()).isEqualTo(branchCommitResponse.getBranchStatus()); + assertThat(t.getMsg()).isEqualTo(branchCommitResponse.getMsg()); + assertThat(t.getResultCode()).isEqualTo(branchCommitResponse.getResultCode()); + + } + +} diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockFastJson2Test.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockFastJson2Test.java new file mode 100644 index 00000000000..4ad06ebe857 --- /dev/null +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/MockFastJson2Test.java @@ -0,0 +1,132 @@ +/* + * 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 org.apache.seata.core.rpc.netty.mockserver; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.ConfigurationTestHelper; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.TransactionManager; +import org.apache.seata.core.rpc.netty.RmNettyRemotingClient; +import org.apache.seata.core.rpc.netty.TmNettyRemotingClient; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.mockserver.MockCoordinator; +import org.apache.seata.mockserver.MockServer; +import org.apache.seata.rm.DefaultResourceManager; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * the type MockServerTest + */ +public class MockFastJson2Test { + + static String RESOURCE_ID = "mock-action"; + + Logger logger = LoggerFactory.getLogger(MockFastJson2Test.class); + + @BeforeAll + public static void before() { + ConfigurationFactory.reload(); + ConfigurationTestHelper.putConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL, String.valueOf(ProtocolTestConstants.MOCK_SERVER_PORT)); + //Enable it when testing is needed. The settings here will affect the global configuration. + //ConfigurationTestHelper.putConfig(ConfigurationKeys.SERIALIZE_FOR_RPC, String.valueOf(SerializerType.FASTJSON2)); + MockServer.start(ProtocolTestConstants.MOCK_SERVER_PORT); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + } + + @AfterAll + public static void after() { + //MockServer.close(); + ConfigurationTestHelper.removeConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL); + ConfigurationTestHelper.removeConfig(ConfigurationKeys.SERIALIZE_FOR_RPC); + TmNettyRemotingClient.getInstance().destroy(); + RmNettyRemotingClient.getInstance().destroy(); + } + + @Test + public void testCommit() throws TransactionException { + String xid = doTestCommit(0); + Assertions.assertEquals(1, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(0, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testCommitRetry() throws TransactionException { + String xid = doTestCommit(2); + Assertions.assertEquals(3, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(0, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testRollback() throws TransactionException { + String xid = doTestRollback(0); + Assertions.assertEquals(0, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(1, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testRollbackRetry() throws TransactionException { + String xid = doTestRollback(2); + Assertions.assertEquals(0, Action1Impl.getCommitTimes(xid)); + Assertions.assertEquals(3, Action1Impl.getRollbackTimes(xid)); + } + + @Test + public void testTm() throws Exception { + TmClientTest.testTm(); + } + + @Test + public void testRm() throws Exception { + RmClientTest.testRm(); + } + + private String doTestCommit(int times) throws TransactionException { + TransactionManager tm = TmClientTest.getTm(); + DefaultResourceManager rm = RmClientTest.getRm(RESOURCE_ID); + + String xid = tm.begin(ProtocolTestConstants.APPLICATION_ID, ProtocolTestConstants.SERVICE_GROUP, "test-commit", 60000); + MockCoordinator.getInstance().setExpectedRetry(xid, times); + Long branchId = rm.branchRegister(BranchType.TCC, RESOURCE_ID, "1", xid, "{\"mock\":\"mock\"}", "1"); + GlobalStatus commit = tm.commit(xid); + Assertions.assertEquals(GlobalStatus.Committed, commit); + return xid; + + } + + private String doTestRollback(int times) throws TransactionException { + TransactionManager tm = TmClientTest.getTm(); + DefaultResourceManager rm = RmClientTest.getRm(RESOURCE_ID); + + String xid = tm.begin(ProtocolTestConstants.APPLICATION_ID, ProtocolTestConstants.SERVICE_GROUP, "test-rollback", 60000); + logger.info("doTestRollback xid:{}", xid); + MockCoordinator.getInstance().setExpectedRetry(xid, times); + Long branchId = rm.branchRegister(BranchType.TCC, RESOURCE_ID, "1", xid, "{\"mock\":\"mock\"}", "1"); + GlobalStatus rollback = tm.rollback(xid); + Assertions.assertEquals(GlobalStatus.Rollbacked, rollback); + return xid; + + } +} From 5d2b1390e938190494358068c4e8a9d9b0ec91d2 Mon Sep 17 00:00:00 2001 From: funkye Date: Thu, 31 Oct 2024 14:03:45 +0800 Subject: [PATCH 35/54] optimize: splitting MergedWarpMessage enhances the server parallel processing capability (#6807) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../apache/seata/core/protocol/Version.java | 19 ++- .../netty/AbstractNettyRemotingClient.java | 17 ++- .../netty/AbstractNettyRemotingServer.java | 32 +++++ .../core/rpc/netty/NettyRemotingServer.java | 2 +- .../core/rpc/netty/RmNettyRemotingClient.java | 2 +- .../core/rpc/netty/TmNettyRemotingClient.java | 6 +- .../client/ClientOnResponseProcessor.java | 63 ++++++---- .../RedisVGroupMappingStoreManagerTest.java | 2 + .../core/rpc/netty/RmNettyClientTest.java | 116 ++++++++++++++++++ .../core/rpc/netty/TmNettyClientTest.java | 28 ++--- 12 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 test/src/test/java/org/apache/seata/core/rpc/netty/RmNettyClientTest.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 768ce3ebc1b..969b9578bea 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -31,6 +31,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] upgrade npmjs version in saga module - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] fix log argument mismatch issue - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] optimize readme docs +- [[#6807](https://github.com/apache/incubator-seata/pull/6807)] splitting MergedWarpMessage enhances the server parallel processing capability - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] remove incompatible licenses at build time - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2 dependency adds test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] fix some typos in project diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 7a5366ec660..873f126b64a 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -33,6 +33,7 @@ - [[#6879](https://github.com/apache/incubator-seata/pull/6879)] 修复日志参数不匹配问题 - [[#6898](https://github.com/apache/incubator-seata/pull/6898)] 升级 saga 模块 npmjs 版本 - [[#6902](https://github.com/apache/incubator-seata/pull/6900)] 优化 readme 文档 +- [[#6807](https://github.com/apache/incubator-seata/pull/6807)] 分离merge消息使其能完全并行处理 - [[#6905](https://github.com/apache/incubator-seata/pull/6905)] 移除构建期不兼容的 license - [[#6906](https://github.com/apache/incubator-seata/pull/6906)] h2依赖添加test scope - [[#6911](https://github.com/apache/incubator-seata/pull/6911)] 修正项目中的部分拼写错误 diff --git a/core/src/main/java/org/apache/seata/core/protocol/Version.java b/core/src/main/java/org/apache/seata/core/protocol/Version.java index 12178d8fe05..f32bb163a69 100644 --- a/core/src/main/java/org/apache/seata/core/protocol/Version.java +++ b/core/src/main/java/org/apache/seata/core/protocol/Version.java @@ -39,6 +39,7 @@ public class Version { private static final String CURRENT = VersionInfo.VERSION; private static final String VERSION_0_7_1 = "0.7.1"; private static final String VERSION_1_5_0 = "1.5.0"; + private static final String VERSION_2_3_0 = "2.3.0"; private static final int MAX_VERSION_DOT = 3; /** @@ -86,15 +87,21 @@ public static String getChannelVersion(Channel c) { * @return true: client version is above or equal version 1.5.0, false: on the contrary */ public static boolean isAboveOrEqualVersion150(String version) { - boolean isAboveOrEqualVersion150 = false; + return isAboveOrEqualVersion(version, VERSION_1_5_0); + } + + public static boolean isAboveOrEqualVersion230(String version) { + return isAboveOrEqualVersion(version, VERSION_2_3_0); + } + + public static boolean isAboveOrEqualVersion(String clientVersion, String divideVersion) { + boolean isAboveOrEqualVersion = false; try { - long clientVersion = convertVersion(version); - long divideVersion = convertVersion(VERSION_1_5_0); - isAboveOrEqualVersion150 = clientVersion >= divideVersion; + isAboveOrEqualVersion = convertVersion(clientVersion) >= convertVersion(divideVersion); } catch (Exception e) { - LOGGER.error("convert version error, clientVersion:{}", version, e); + LOGGER.error("convert version error, clientVersion:{}", clientVersion, e); } - return isAboveOrEqualVersion150; + return isAboveOrEqualVersion; } public static long convertVersion(String version) throws IncompatibleVersionException { diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java index 248e8f48f6d..bbbab50faa5 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingClient.java @@ -89,6 +89,8 @@ public abstract class AbstractNettyRemotingClient extends AbstractNettyRemoting */ protected final Map mergeMsgMap = new ConcurrentHashMap<>(); + protected final Map childToParentMap = new ConcurrentHashMap<>(); + /** * When batch sending is enabled, the message will be stored to basketMap * Send via asynchronous thread {@link AbstractNettyRemotingClient.MergedSendRunnable} @@ -203,8 +205,15 @@ public void sendAsyncRequest(Channel channel, Object msg) { RpcMessage rpcMessage = buildRequestMessage(msg, msg instanceof HeartbeatMessage ? ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST : ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY); - if (rpcMessage.getBody() instanceof MergeMessage) { - mergeMsgMap.put(rpcMessage.getId(), (MergeMessage) rpcMessage.getBody()); + Object body = rpcMessage.getBody(); + if (body instanceof MergeMessage) { + Integer parentId = rpcMessage.getId(); + mergeMsgMap.put(parentId, (MergeMessage)rpcMessage.getBody()); + if (body instanceof MergedWarpMessage) { + for (Integer msgId : ((MergedWarpMessage)rpcMessage.getBody()).msgIds) { + childToParentMap.put(msgId, parentId); + } + } } super.sendAsync(channel, rpcMessage); } @@ -370,6 +379,10 @@ public void run() { // fast fail for (Integer msgId : mergeMessage.msgIds) { MessageFuture messageFuture = futures.remove(msgId); + Integer parentId = childToParentMap.remove(msgId); + if (parentId != null) { + mergeMsgMap.remove(parentId); + } if (messageFuture != null) { messageFuture.setResultMessage( new RuntimeException(String.format("%s is unreachable", address), e)); diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingServer.java b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingServer.java index 4b79f20d95c..67df2ea8494 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingServer.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/AbstractNettyRemotingServer.java @@ -29,9 +29,13 @@ import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.core.protocol.AbstractMessage; import org.apache.seata.core.protocol.HeartbeatMessage; +import org.apache.seata.core.protocol.MergedWarpMessage; import org.apache.seata.core.protocol.ProtocolConstants; import org.apache.seata.core.protocol.RpcMessage; +import org.apache.seata.core.protocol.Version; import org.apache.seata.core.rpc.RemotingServer; import org.apache.seata.core.rpc.RpcContext; import org.apache.seata.core.rpc.processor.Pair; @@ -270,4 +274,32 @@ public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Excep } } + + @Override + protected void processMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { + Object body = rpcMessage.getBody(); + RpcContext rpcContext = ChannelManager.getContextFromIdentified(ctx.channel()); + // If the client is not version 2.3.0 or higher, splitting MergedWarpMessage will result in the client’s mergeMsgMap not being cleared + if (body instanceof MergedWarpMessage && (StringUtils.isNotBlank(rpcContext.getVersion()) + && Version.isAboveOrEqualVersion230(rpcContext.getVersion()))) { + MergedWarpMessage mergedWarpMessage = (MergedWarpMessage)body; + for (int i = 0; i < mergedWarpMessage.msgs.size(); i++) { + RpcMessage rpcMsg = + buildRequestMessage(mergedWarpMessage.msgs.get(i), rpcMessage, mergedWarpMessage.msgIds.get(i)); + super.processMessage(ctx, rpcMsg); + } + } else { + super.processMessage(ctx, rpcMessage); + } + } + + private RpcMessage buildRequestMessage(AbstractMessage msg, RpcMessage rpcMessage,int id) { + RpcMessage rpcMsg = new RpcMessage(); + rpcMsg.setId(id); + rpcMsg.setCodec(rpcMessage.getCodec()); + rpcMsg.setCompressor(rpcMessage.getCompressor()); + rpcMsg.setBody(msg); + return rpcMsg; + } + } diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java index 3e6ec63c15e..3010efc4ae2 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyRemotingServer.java @@ -45,7 +45,7 @@ public class NettyRemotingServer extends AbstractNettyRemotingServer { private final AtomicBoolean initialized = new AtomicBoolean(false); - private ThreadPoolExecutor branchResultMessageExecutor = new ThreadPoolExecutor(NettyServerConfig.getMinBranchResultPoolSize(), + private final ThreadPoolExecutor branchResultMessageExecutor = new ThreadPoolExecutor(NettyServerConfig.getMinBranchResultPoolSize(), NettyServerConfig.getMaxBranchResultPoolSize(), NettyServerConfig.getKeepAliveTime(), TimeUnit.SECONDS, new LinkedBlockingQueue<>(NettyServerConfig.getMaxTaskQueueSize()), new NamedThreadFactory("BranchResultHandlerThread", NettyServerConfig.getMaxBranchResultPoolSize()), new ThreadPoolExecutor.CallerRunsPolicy()); diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java b/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java index 92cbafd0a5d..0b9fd0e12bc 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/RmNettyRemotingClient.java @@ -323,7 +323,7 @@ private void registerProcessor() { super.registerProcessor(MessageType.TYPE_RM_DELETE_UNDOLOG, rmUndoLogProcessor, messageExecutor); // 4.registry TC response processor ClientOnResponseProcessor onResponseProcessor = - new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), getTransactionMessageHandler()); + new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), childToParentMap, getTransactionMessageHandler()); super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, null); super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER_RESULT, onResponseProcessor, null); super.registerProcessor(MessageType.TYPE_BRANCH_STATUS_REPORT_RESULT, onResponseProcessor, null); diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/TmNettyRemotingClient.java b/core/src/main/java/org/apache/seata/core/rpc/netty/TmNettyRemotingClient.java index 68ff739bbb0..28993b61f77 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/TmNettyRemotingClient.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/TmNettyRemotingClient.java @@ -46,6 +46,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.seata.common.util.StringUtils.isNotBlank; + /** * The rm netty client. * @@ -187,7 +189,7 @@ public void init() { registerProcessor(); if (initialized.compareAndSet(false, true)) { super.init(); - if (org.apache.seata.common.util.StringUtils.isNotBlank(transactionServiceGroup)) { + if (isNotBlank(transactionServiceGroup)) { initConnection(); } } @@ -247,7 +249,7 @@ protected Function getPoolKeyFunction() { private void registerProcessor() { // 1.registry TC response processor ClientOnResponseProcessor onResponseProcessor = - new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), getTransactionMessageHandler()); + new ClientOnResponseProcessor(mergeMsgMap, super.getFutures(), childToParentMap, getTransactionMessageHandler()); super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, null); super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN_RESULT, onResponseProcessor, null); super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT_RESULT, onResponseProcessor, null); diff --git a/core/src/main/java/org/apache/seata/core/rpc/processor/client/ClientOnResponseProcessor.java b/core/src/main/java/org/apache/seata/core/rpc/processor/client/ClientOnResponseProcessor.java index f7b44c2e563..bb13c664488 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/processor/client/ClientOnResponseProcessor.java +++ b/core/src/main/java/org/apache/seata/core/rpc/processor/client/ClientOnResponseProcessor.java @@ -69,7 +69,9 @@ public class ClientOnResponseProcessor implements RemotingProcessor { /** * The Merge msg map from org.apache.seata.core.rpc.netty.AbstractNettyRemotingClient#mergeMsgMap. */ - private Map mergeMsgMap; + private final Map mergeMsgMap; + + private final Map childToParentMap; /** * The Futures from org.apache.seata.core.rpc.netty.AbstractNettyRemoting#futures @@ -82,9 +84,10 @@ public class ClientOnResponseProcessor implements RemotingProcessor { private final TransactionMessageHandler transactionMessageHandler; public ClientOnResponseProcessor(Map mergeMsgMap, - ConcurrentHashMap futures, + ConcurrentHashMap futures, Map childToParentMap, TransactionMessageHandler transactionMessageHandler) { this.mergeMsgMap = mergeMsgMap; + this.childToParentMap = childToParentMap; this.futures = futures; this.transactionMessageHandler = transactionMessageHandler; } @@ -97,6 +100,8 @@ public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exc for (int i = 0; i < mergeMessage.msgs.size(); i++) { int msgId = mergeMessage.msgIds.get(i); MessageFuture future = futures.remove(msgId); + // The old version of the server will return MergeResultMessage, so it is necessary to remove the msgId from the childToParentMap. + childToParentMap.remove(msgId); if (future == null) { LOGGER.error("msg: {} is not found in futures, result message: {}", msgId,results.getMsgs()[i]); } else { @@ -104,33 +109,43 @@ public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exc } } } else if (rpcMessage.getBody() instanceof BatchResultMessage) { - try { - BatchResultMessage batchResultMessage = (BatchResultMessage) rpcMessage.getBody(); - for (int i = 0; i < batchResultMessage.getMsgIds().size(); i++) { - int msgId = batchResultMessage.getMsgIds().get(i); - MessageFuture future = futures.remove(msgId); - if (future == null) { - LOGGER.error("msg: {} is not found in futures, result message: {}", msgId, batchResultMessage.getResultMessages().get(i)); - } else { - future.setResultMessage(batchResultMessage.getResultMessages().get(i)); - } + BatchResultMessage batchResultMessage = (BatchResultMessage)rpcMessage.getBody(); + for (int i = 0; i < batchResultMessage.getMsgIds().size(); i++) { + int msgId = batchResultMessage.getMsgIds().get(i); + MessageFuture future = futures.remove(msgId); + // The old version of the server will return BatchResultMessage, so it is necessary to remove the msgId + // from the childToParentMap. + Integer parentId = childToParentMap.remove(msgId); + if (parentId != null) { + mergeMsgMap.remove(parentId); + } + if (future == null) { + LOGGER.error("msg: {} is not found in futures, result message: {}", msgId, + batchResultMessage.getResultMessages().get(i)); + } else { + future.setResultMessage(batchResultMessage.getResultMessages().get(i)); } - } finally { - // In order to be compatible with the old version, in the batch sending of version 1.5.0, - // batch messages will also be placed in the local cache of mergeMsgMap, - // but version 1.5.0 no longer needs to obtain batch messages from mergeMsgMap - mergeMsgMap.clear(); } } else { - MessageFuture messageFuture = futures.remove(rpcMessage.getId()); - if (messageFuture != null) { - messageFuture.setResultMessage(rpcMessage.getBody()); - } else { - if (rpcMessage.getBody() instanceof AbstractResultMessage) { - if (transactionMessageHandler != null) { - transactionMessageHandler.onResponse((AbstractResultMessage) rpcMessage.getBody(), null); + Integer id = rpcMessage.getId(); + try { + MessageFuture messageFuture = futures.remove(id); + if (messageFuture != null) { + messageFuture.setResultMessage(rpcMessage.getBody()); + } else { + if (rpcMessage.getBody() instanceof AbstractResultMessage) { + if (transactionMessageHandler != null) { + transactionMessageHandler.onResponse((AbstractResultMessage)rpcMessage.getBody(), null); + } } } + } finally { + // In version 2.3.0, the server does not return MergeResultMessage and BatchResultMessage + // so it is necessary to clear childToParentMap and mergeMsgMap here. + Integer parentId = childToParentMap.remove(id); + if (parentId != null) { + mergeMsgMap.remove(parentId); + } } } } diff --git a/server/src/test/java/org/apache/seata/server/storage/redis/store/RedisVGroupMappingStoreManagerTest.java b/server/src/test/java/org/apache/seata/server/storage/redis/store/RedisVGroupMappingStoreManagerTest.java index fac04d66de1..755c10759c6 100644 --- a/server/src/test/java/org/apache/seata/server/storage/redis/store/RedisVGroupMappingStoreManagerTest.java +++ b/server/src/test/java/org/apache/seata/server/storage/redis/store/RedisVGroupMappingStoreManagerTest.java @@ -21,11 +21,13 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.springframework.boot.test.context.SpringBootTest; import java.util.Map; +@EnabledIfSystemProperty(named = "redisCaseEnabled", matches = "true") @SpringBootTest public class RedisVGroupMappingStoreManagerTest { private RedisVGroupMappingStoreManager redisVGroupMappingStoreManager; diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/RmNettyClientTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/RmNettyClientTest.java new file mode 100644 index 00000000000..3d62a10214c --- /dev/null +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/RmNettyClientTest.java @@ -0,0 +1,116 @@ +/* + * 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 org.apache.seata.core.rpc.netty; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.ConfigurationTestHelper; +import org.apache.seata.common.XID; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.UUIDGenerator; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.BranchRegisterRequest; +import org.apache.seata.core.protocol.transaction.BranchRegisterResponse; +import org.apache.seata.rm.tcc.TCCResourceManager; +import org.apache.seata.saga.engine.db.AbstractServerTest; +import org.apache.seata.server.coordinator.DefaultCoordinator; +import org.apache.seata.server.session.SessionHolder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.channel.Channel; + +public class RmNettyClientTest extends AbstractServerTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(RmNettyClientTest.class); + + @BeforeAll + public static void init(){ + ConfigurationTestHelper.putConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL, "8091"); + } + @AfterAll + public static void after() { + ConfigurationTestHelper.removeConfig(ConfigurationKeys.SERVER_SERVICE_PORT_CAMEL); + } + + public static ThreadPoolExecutor initMessageExecutor() { + return new ThreadPoolExecutor(5, 5, 500, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(20000), new ThreadPoolExecutor.CallerRunsPolicy()); + } + + @Test + public void testMergeMsg() throws Exception { + ThreadPoolExecutor workingThreads = initMessageExecutor(); + NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(workingThreads); + new Thread(() -> { + SessionHolder.init(null); + nettyRemotingServer.setHandler(DefaultCoordinator.getInstance(nettyRemotingServer)); + // set registry + XID.setIpAddress(NetUtil.getLocalIp()); + XID.setPort(8091); + // init snowflake for transactionId, branchId + UUIDGenerator.init(1L); + nettyRemotingServer.init(); + }).start(); + Thread.sleep(3000); + + String applicationId = "app 1"; + String transactionServiceGroup = "default_tx_group"; + RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup); + rmNettyRemotingClient.setResourceManager(new TCCResourceManager()); + rmNettyRemotingClient.init(); + rmNettyRemotingClient.getClientChannelManager().initReconnect(transactionServiceGroup, true); + String serverAddress = "0.0.0.0:8091"; + Channel channel = RmNettyRemotingClient.getInstance().getClientChannelManager().acquireChannel(serverAddress); + Assertions.assertNotNull(channel); + + CountDownLatch latch = new CountDownLatch(3); + for (int i = 0; i < 3; i++) { + CompletableFuture.runAsync(()->{ + BranchRegisterRequest request = new BranchRegisterRequest(); + request.setXid("127.0.0.1:8091:1249853"); + request.setLockKey("lock key testSendMsgWithResponse"); + request.setResourceId("resoutceId1"); + BranchRegisterResponse branchRegisterResponse = null; + try { + branchRegisterResponse = (BranchRegisterResponse) rmNettyRemotingClient.sendSyncRequest(request); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + Assertions.assertNotNull(branchRegisterResponse); + Assertions.assertEquals(ResultCode.Failed, branchRegisterResponse.getResultCode()); + Assertions.assertEquals("TransactionException[Could not found global transaction xid = 127.0.0.1:8091:1249853, may be has finished.]", + branchRegisterResponse.getMsg()); + latch.countDown(); + }); + } + latch.await(10,TimeUnit.SECONDS); + nettyRemotingServer.destroy(); + rmNettyRemotingClient.destroy(); + } + +} diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java index 9e855645305..9a46210aee2 100644 --- a/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/TmNettyClientTest.java @@ -21,10 +21,9 @@ import org.apache.seata.common.ConfigurationTestHelper; import org.apache.seata.common.XID; import org.apache.seata.common.util.NetUtil; -import org.apache.seata.core.protocol.ResultCode; -import org.apache.seata.core.protocol.transaction.BranchRegisterRequest; -import org.apache.seata.core.protocol.transaction.BranchRegisterResponse; -import org.apache.seata.mockserver.MockServer; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.protocol.transaction.GlobalCommitRequest; +import org.apache.seata.core.protocol.transaction.GlobalCommitResponse; import org.apache.seata.saga.engine.db.AbstractServerTest; import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.server.coordinator.DefaultCoordinator; @@ -40,6 +39,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -58,7 +58,7 @@ public static void after() { } public static ThreadPoolExecutor initMessageExecutor() { - return new ThreadPoolExecutor(100, 500, 500, TimeUnit.SECONDS, + return new ThreadPoolExecutor(5, 5, 500, TimeUnit.SECONDS, new LinkedBlockingQueue(20000), new ThreadPoolExecutor.CallerRunsPolicy()); } @@ -176,16 +176,16 @@ public void testSendMsgWithResponse() throws Exception { String serverAddress = "0.0.0.0:8091"; Channel channel = TmNettyRemotingClient.getInstance().getClientChannelManager().acquireChannel(serverAddress); Assertions.assertNotNull(channel); - - BranchRegisterRequest request = new BranchRegisterRequest(); + GlobalCommitRequest request = new GlobalCommitRequest(); request.setXid("127.0.0.1:8091:1249853"); - request.setLockKey("lock key testSendMsgWithResponse"); - request.setResourceId("resourceId1"); - BranchRegisterResponse branchRegisterResponse = (BranchRegisterResponse) tmNettyRemotingClient.sendSyncRequest(request); - Assertions.assertNotNull(branchRegisterResponse); - Assertions.assertEquals(ResultCode.Failed, branchRegisterResponse.getResultCode()); - Assertions.assertEquals("TransactionException[Could not found global transaction xid = 127.0.0.1:8091:1249853, may be has finished.]", - branchRegisterResponse.getMsg()); + GlobalCommitResponse globalCommitResponse = null; + try { + globalCommitResponse = (GlobalCommitResponse)tmNettyRemotingClient.sendSyncRequest(request); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + Assertions.assertNotNull(globalCommitResponse); + Assertions.assertEquals(GlobalStatus.Finished, globalCommitResponse.getGlobalStatus()); nettyRemotingServer.destroy(); tmNettyRemotingClient.destroy(); } From 363e1792ba52e1dfeea049842ca5b71d373d49fe Mon Sep 17 00:00:00 2001 From: GoodBoyCoder Date: Mon, 4 Nov 2024 09:33:59 +0800 Subject: [PATCH 36/54] feature: add fastjson2 undolog parser (#6974) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + rm-datasource/pom.xml | 6 ++ .../undo/parser/Fastjson2UndoLogParser.java | 77 +++++++++++++++++++ ...che.seata.rm.datasource.undo.UndoLogParser | 3 +- .../parser/Fastjson2UndoLogParserTest.java | 36 +++++++++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParser.java create mode 100644 rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParserTest.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 969b9578bea..f0c1b9198a7 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -7,6 +7,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6876](https://github.com/apache/incubator-seata/pull/6876)] support kingbase - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] support grpc - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] support shentong database +- [[#6974](https://github.com/apache/incubator-seata/pull/6974)] support fastjson2 undolog parser ### bugfix: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 873f126b64a..ae9c1047dd9 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -7,6 +7,7 @@ - [[#6876](https://github.com/apache/incubator-seata/pull/6876)] 支持人大金仓数据库(kingbase) - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] client和server支持grpc协议 - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] 支持神通数据库(oscar) +- [[#6974](https://github.com/apache/incubator-seata/pull/6974)] 支持UndoLog的fastjson2序列化方式 ### bugfix: diff --git a/rm-datasource/pom.xml b/rm-datasource/pom.xml index ed466e2cdf1..af2c0d09f59 100644 --- a/rm-datasource/pom.xml +++ b/rm-datasource/pom.xml @@ -111,6 +111,12 @@ provided true + + com.alibaba.fastjson2 + fastjson2 + provided + true + com.alibaba druid diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParser.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParser.java new file mode 100644 index 00000000000..d26a041f704 --- /dev/null +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParser.java @@ -0,0 +1,77 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.parser; + +import com.alibaba.fastjson2.JSONB; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import org.apache.seata.common.executor.Initialize; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.rm.datasource.undo.BranchUndoLog; +import org.apache.seata.rm.datasource.undo.UndoLogParser; + +@LoadLevel(name = Fastjson2UndoLogParser.NAME) +public class Fastjson2UndoLogParser implements UndoLogParser, Initialize { + public static final String NAME = "fastjson2"; + + private JSONReader.Feature[] jsonReaderFeature; + private JSONWriter.Feature[] jsonWriterFeature; + @Override + public void init() { + jsonReaderFeature = new JSONReader.Feature[]{ + JSONReader.Feature.UseDefaultConstructorAsPossible, + // If not configured, it will be serialized based on public field and getter methods by default. + // After configuration, it will be deserialized based on non-static fields (including private). + // It will be safer under FieldBased configuration + JSONReader.Feature.FieldBased, + JSONReader.Feature.IgnoreAutoTypeNotMatch, + JSONReader.Feature.UseNativeObject, + JSONReader.Feature.SupportAutoType + }; + + jsonWriterFeature = new JSONWriter.Feature[]{ + JSONWriter.Feature.WriteClassName, + JSONWriter.Feature.FieldBased, + JSONWriter.Feature.ReferenceDetection, + JSONWriter.Feature.WriteNulls, + JSONWriter.Feature.NotWriteDefaultValue, + JSONWriter.Feature.NotWriteHashMapArrayListClassName, + JSONWriter.Feature.WriteNameAsSymbol + }; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public byte[] getDefaultContent() { + return encode(new BranchUndoLog()); + } + + @Override + public byte[] encode(BranchUndoLog branchUndoLog) { + return JSONB.toBytes(branchUndoLog, jsonWriterFeature); + } + + @Override + public BranchUndoLog decode(byte[] bytes) { + return JSONB.parseObject(bytes, BranchUndoLog.class, jsonReaderFeature); + } + +} diff --git a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogParser b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogParser index 26abca209ed..9a32cda00b5 100644 --- a/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogParser +++ b/rm-datasource/src/main/resources/META-INF/services/org.apache.seata.rm.datasource.undo.UndoLogParser @@ -17,4 +17,5 @@ org.apache.seata.rm.datasource.undo.parser.FastjsonUndoLogParser org.apache.seata.rm.datasource.undo.parser.JacksonUndoLogParser org.apache.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser -org.apache.seata.rm.datasource.undo.parser.KryoUndoLogParser \ No newline at end of file +org.apache.seata.rm.datasource.undo.parser.KryoUndoLogParser +org.apache.seata.rm.datasource.undo.parser.Fastjson2UndoLogParser \ No newline at end of file diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParserTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParserTest.java new file mode 100644 index 00000000000..21059c8a812 --- /dev/null +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/undo/parser/Fastjson2UndoLogParserTest.java @@ -0,0 +1,36 @@ +/* + * 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 org.apache.seata.rm.datasource.undo.parser; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.rm.datasource.undo.BaseUndoLogParserTest; +import org.apache.seata.rm.datasource.undo.UndoLogParser; + + +public class Fastjson2UndoLogParserTest extends BaseUndoLogParserTest { + + Fastjson2UndoLogParser parser = (Fastjson2UndoLogParser) EnhancedServiceLoader.load(UndoLogParser.class, Fastjson2UndoLogParser.NAME); + + @Override + public UndoLogParser getParser() { + return parser; + } + + @Override + public void testTimestampEncodeAndDecode() { + } +} From 78a070836188edf09dfb8b5f89e356beb172d478 Mon Sep 17 00:00:00 2001 From: psxjoy Date: Wed, 6 Nov 2024 11:26:31 +0800 Subject: [PATCH 37/54] bugfix: support building docker image on openjdk23 (#6984) --- build/pom.xml | 2 +- changes/en-us/2.x.md | 2 ++ changes/zh-cn/2.x.md | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/build/pom.xml b/build/pom.xml index 9e26a12f1a1..d54230f995d 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -120,7 +120,7 @@ 2.4.3 3.0.2 3.0.0 - 3.2.0 + 3.3.0 4.9.10 1.15.0 diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index f0c1b9198a7..5e7938a1981 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -21,6 +21,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] fix the conversion error for `convertBranchSession` in concurrent environment. - [[#6948](https://github.com/apache/incubator-seata/pull/6948)] Fix the CI build issue on the ARM64 platform - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] fix npe for nacos registry when look up address +- [[#6984](https://github.com/apache/incubator-seata/pull/6984)] support building docker image on openjdk23 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction @@ -41,6 +42,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] update the naming and description for the `seata-http-jakarta` module + ### refactor: ### security: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index ae9c1047dd9..87e3c059ab4 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -21,6 +21,7 @@ - [[#6943](https://github.com/apache/incubator-seata/pull/6943)] 修复并发状态下 `convertBranchSession` 转换报错问题 - [[#6948](https://github.com/apache/incubator-seata/pull/6948)] 修复在ARM64平台下CI构建出错的问题 - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] 修复nacos注册中心查询可用地址时的空指针问题 +- [[#6984](https://github.com/apache/incubator-seata/pull/6984)] 修复 openjdk23 版本下无法构建 docker 镜像的问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 From 835ef47448c1c0819552297bd3c45d61125edcd5 Mon Sep 17 00:00:00 2001 From: funkye Date: Sun, 10 Nov 2024 12:29:30 +0800 Subject: [PATCH 38/54] optimize: gRPC serialization default to Protobuf (#6991) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 5e7938a1981..8ba55503320 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -41,6 +41,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] Update online chat information in README.md - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] update the naming and description for the `seata-http-jakarta` module +- [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC serialization default to Protobuf ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 87e3c059ab4..915a039cbb9 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -43,6 +43,7 @@ - [[#6938](https://github.com/apache/incubator-seata/pull/6938)] 更新 README.md 中的社区联系信息 - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] 移除JVM参数app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] 修正 `seata-http-jakarta`的模块命名和描述 +- [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC协议序列化默认值为protobuf ### refactor: diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java index 71c9caf8be9..e227d5dc7c7 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java @@ -94,7 +94,8 @@ public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exc bodyBytes = compressor.decompress(bodyBytes); } String codecValue = headMap.get(GrpcHeaderEnum.CODEC_TYPE.header); - int codec = Integer.parseInt(codecValue); + int codec = StringUtils.isBlank(codecValue) ? SerializerType.PROTOBUF.getCode() + : Integer.parseInt(codecValue); SerializerType serializerType = SerializerType.getByCode(codec); rpcMsg.setCodec(serializerType.getCode()); Serializer serializer = SerializerServiceLoader.load(serializerType); From 60a81b1e93b6e2833df99c89ee21c5687512a2e7 Mon Sep 17 00:00:00 2001 From: yiqi <77573225+PleaseGiveMeTheCoke@users.noreply.github.com> Date: Sun, 10 Nov 2024 21:02:06 +0800 Subject: [PATCH 39/54] feature: add grpc serializer (#6992) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../core/rpc/netty/grpc/GrpcDecoder.java | 2 +- .../core/rpc/netty/grpc/GrpcEncoder.java | 4 +- .../seata/core/serializer/SerializerType.java | 9 ++- .../serializer/protobuf/GrpcSerializer.java | 60 +++++++++++++++++++ ...rg.apache.seata.core.serializer.Serializer | 3 +- test/pom.xml | 5 ++ .../core/rpc/netty/mockserver/GrpcTest.java | 13 ++-- ...rg.apache.seata.core.serializer.Serializer | 17 ++++++ 10 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/GrpcSerializer.java create mode 100644 test/src/test/resources/META-INF/services/org.apache.seata.core.serializer.Serializer diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 8ba55503320..cfd89f80cec 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -8,6 +8,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] support grpc - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] support shentong database - [[#6974](https://github.com/apache/incubator-seata/pull/6974)] support fastjson2 undolog parser +- [[#6992](https://github.com/apache/incubator-seata/pull/6992)] support grpc serializer ### bugfix: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 915a039cbb9..6aeb138b529 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -8,6 +8,7 @@ - [[#6881](https://github.com/apache/incubator-seata/pull/6881)] client和server支持grpc协议 - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] 支持神通数据库(oscar) - [[#6974](https://github.com/apache/incubator-seata/pull/6974)] 支持UndoLog的fastjson2序列化方式 +- [[#6992](https://github.com/apache/incubator-seata/pull/6992)] 支持grpc序列化器 ### bugfix: diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java index e227d5dc7c7..5544e994a55 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcDecoder.java @@ -94,7 +94,7 @@ public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exc bodyBytes = compressor.decompress(bodyBytes); } String codecValue = headMap.get(GrpcHeaderEnum.CODEC_TYPE.header); - int codec = StringUtils.isBlank(codecValue) ? SerializerType.PROTOBUF.getCode() + int codec = StringUtils.isBlank(codecValue) ? SerializerType.GRPC.getCode() : Integer.parseInt(codecValue); SerializerType serializerType = SerializerType.getByCode(codec); rpcMsg.setCodec(serializerType.getCode()); diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java index dbbbfe1be48..2601a2f0a6e 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/grpc/GrpcEncoder.java @@ -64,14 +64,14 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) ByteString dataBytes; if (messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST && messageType != ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) { - Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(SerializerType.PROTOBUF.getCode())); + Serializer serializer = SerializerServiceLoader.load(SerializerType.getByCode(SerializerType.GRPC.getCode())); byte[] serializedBytes = serializer.serialize(body); Compressor compressor = CompressorFactory.getCompressor(rpcMessage.getCompressor()); dataBytes = ByteString.copyFrom(compressor.compress(serializedBytes)); } else { dataBytes = ByteString.EMPTY; } - headMap.put(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.PROTOBUF.getCode())); + headMap.put(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())); headMap.put(GrpcHeaderEnum.COMPRESS_TYPE.header, String.valueOf(rpcMessage.getCompressor())); GrpcMessageProto.Builder builder = GrpcMessageProto.newBuilder() .putAllHeadMap(headMap) diff --git a/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java b/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java index 56fd8136d17..39772b34ce0 100644 --- a/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java +++ b/core/src/main/java/org/apache/seata/core/serializer/SerializerType.java @@ -70,7 +70,14 @@ public enum SerializerType { * Math.pow(2, 6) */ FASTJSON2((byte)0x64), - ; + + + /** + * The grpc + *

+ * Math.pow(2, 7) + */ + GRPC((byte) 0x128); private final byte code; diff --git a/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/GrpcSerializer.java b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/GrpcSerializer.java new file mode 100644 index 00000000000..2ef8eac784e --- /dev/null +++ b/serializer/seata-serializer-protobuf/src/main/java/org/apache/seata/serializer/protobuf/GrpcSerializer.java @@ -0,0 +1,60 @@ +/* + * 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 org.apache.seata.serializer.protobuf; + +import com.google.protobuf.Any; +import com.google.protobuf.Message; +import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.core.serializer.Serializer; +import org.apache.seata.serializer.protobuf.convertor.PbConvertor; +import org.apache.seata.serializer.protobuf.manager.ProtobufConvertManager; + +@LoadLevel(name = "GRPC") +public class GrpcSerializer implements Serializer { + @Override + public byte[] serialize(T t) { + PbConvertor pbConvertor = ProtobufConvertManager.getInstance() + .fetchConvertor(t.getClass().getName()); + Any grpcBody = Any.pack((Message) pbConvertor.convert2Proto(t)); + + return grpcBody.toByteArray(); + } + + @Override + public T deserialize(byte[] bytes) { + try { + Any body = Any.parseFrom(bytes); + final Class clazz = ProtobufConvertManager.getInstance().fetchProtoClass(getTypeNameFromTypeUrl(body.getTypeUrl())); + if (body.is(clazz)) { + Object ob = body.unpack(clazz); + PbConvertor pbConvertor = ProtobufConvertManager.getInstance().fetchReversedConvertor(clazz.getName()); + + return (T) pbConvertor.convert2Model(ob); + } + } catch (Throwable e) { + throw new ShouldNeverHappenException("GrpcSerializer deserialize error", e); + } + + return null; + } + + private String getTypeNameFromTypeUrl(String typeUri) { + int pos = typeUri.lastIndexOf('/'); + return pos == -1 ? "" : typeUri.substring(pos + 1); + } +} diff --git a/serializer/seata-serializer-protobuf/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer b/serializer/seata-serializer-protobuf/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer index 71098c53674..f6fbf709dea 100644 --- a/serializer/seata-serializer-protobuf/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer +++ b/serializer/seata-serializer-protobuf/src/main/resources/META-INF/services/org.apache.seata.core.serializer.Serializer @@ -14,4 +14,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.apache.seata.serializer.protobuf.ProtobufSerializer \ No newline at end of file +org.apache.seata.serializer.protobuf.ProtobufSerializer +org.apache.seata.serializer.protobuf.GrpcSerializer \ No newline at end of file diff --git a/test/pom.xml b/test/pom.xml index d35f25bad5e..e9991c688db 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -71,6 +71,11 @@ seata-tm ${project.version} + + org.apache.seata + seata-serializer-protobuf + ${project.version} + io.grpc grpc-alts diff --git a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java index 0d63d2eb70f..042160a9ba2 100644 --- a/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java +++ b/test/src/test/java/org/apache/seata/core/rpc/netty/mockserver/GrpcTest.java @@ -16,6 +16,7 @@ */ package org.apache.seata.core.rpc.netty.mockserver; +import com.google.protobuf.Any; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; @@ -25,6 +26,8 @@ import org.apache.seata.core.protocol.generated.GrpcMessageProto; import org.apache.seata.core.rpc.netty.RmNettyRemotingClient; import org.apache.seata.core.rpc.netty.TmNettyRemotingClient; +import org.apache.seata.core.rpc.netty.grpc.GrpcHeaderEnum; +import org.apache.seata.core.serializer.SerializerType; import org.apache.seata.mockserver.MockServer; import org.apache.seata.serializer.protobuf.generated.*; import org.apache.seata.core.protocol.generated.SeataServiceGrpc; @@ -69,7 +72,7 @@ private GrpcMessageProto getRegisterTMRequest() { .setAbstractIdentifyRequest(abstractIdentifyRequestProto) .build(); - return GrpcMessageProto.newBuilder().setBody(registerTMRequestProto.toByteString()).build(); + return GrpcMessageProto.newBuilder().putHeadMap(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())).setBody(Any.pack(registerTMRequestProto).toByteString()).build(); } private GrpcMessageProto getGlobalBeginRequest() { @@ -77,7 +80,7 @@ private GrpcMessageProto getGlobalBeginRequest() { .setTransactionName("test-transaction") .setTimeout(2000) .build(); - return GrpcMessageProto.newBuilder().setBody(globalBeginRequestProto.toByteString()).build(); + return GrpcMessageProto.newBuilder().putHeadMap(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())).setBody(Any.pack(globalBeginRequestProto).toByteString()).build(); } private GrpcMessageProto getBranchRegisterRequest() { @@ -89,7 +92,7 @@ private GrpcMessageProto getBranchRegisterRequest() { .setApplicationData("{\"mock\":\"mock\"}") .build(); - return GrpcMessageProto.newBuilder().setBody(branchRegisterRequestProto.toByteString()).build(); + return GrpcMessageProto.newBuilder().putHeadMap(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())).setBody(Any.pack(branchRegisterRequestProto).toByteString()).build(); } private GrpcMessageProto getGlobalCommitRequest() { @@ -100,7 +103,7 @@ private GrpcMessageProto getGlobalCommitRequest() { .setAbstractGlobalEndRequest(globalEndRequestProto) .build(); - return GrpcMessageProto.newBuilder().setBody(globalCommitRequestProto.toByteString()).build(); + return GrpcMessageProto.newBuilder().putHeadMap(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())).setBody(Any.pack(globalCommitRequestProto).toByteString()).build(); } private GrpcMessageProto getGlobalRollbackRequest() { @@ -111,7 +114,7 @@ private GrpcMessageProto getGlobalRollbackRequest() { .setAbstractGlobalEndRequest(globalEndRequestProto) .build(); - return GrpcMessageProto.newBuilder().setBody(globalRollbackRequestProto.toByteString()).build(); + return GrpcMessageProto.newBuilder().putHeadMap(GrpcHeaderEnum.CODEC_TYPE.header, String.valueOf(SerializerType.GRPC.getCode())).setBody(Any.pack(globalRollbackRequestProto).toByteString()).build(); } @Test diff --git a/test/src/test/resources/META-INF/services/org.apache.seata.core.serializer.Serializer b/test/src/test/resources/META-INF/services/org.apache.seata.core.serializer.Serializer new file mode 100644 index 00000000000..81c5235e259 --- /dev/null +++ b/test/src/test/resources/META-INF/services/org.apache.seata.core.serializer.Serializer @@ -0,0 +1,17 @@ +# +# 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. +# +org.apache.seata.serializer.protobuf.GrpcSerializer \ No newline at end of file From 4fae0626a8c9bfe0a48232d4cce00f79c820df2d Mon Sep 17 00:00:00 2001 From: jimin Date: Mon, 11 Nov 2024 12:27:59 +0800 Subject: [PATCH 40/54] optimize: upgrade outdate npmjs dependencies (#6995) --- README.md | 4 +-- changes/en-us/2.x.md | 2 +- changes/zh-cn/2.x.md | 2 +- .../static/console-fe/package-lock.json | 26 +++++++++---------- .../package-lock.json | 20 +++++++------- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 59209dacde9..bb8be9dca08 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Build Status](https://github.com/apache/incubator-seata/workflows/build/badge.svg?branch=develop)](https://github.com/apache/incubator-seata/actions) [![codecov](https://codecov.io/gh/apache/incubator-seata/graph/badge.svg?token=tbmHt2ZfxO)](https://codecov.io/gh/apache/incubator-seata) [![license](https://img.shields.io/github/license/apache/incubator-seata.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![maven](https://img.shields.io/maven-central/v/org.apache.seata/seata-all?versionSuffix=2.1.0)](https://central.sonatype.com/search?q=org.apache.seata%3Aseata-all) +[![maven](https://img.shields.io/maven-central/v/org.apache.seata/seata-all?versionSuffix=2.2.0)](https://central.sonatype.com/search?q=org.apache.seata%3Aseata-all) ## What is Seata? @@ -85,7 +85,7 @@ For more details about principle and design, please go to [Seata wiki page](http Depending on the scenario, choose one of the two dependencies: `org.apache.seata:seata-all` or `org.apache.seata:seata-spring-boot-starter`. ```xml - 2.1.0 + 2.2.0 diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index cfd89f80cec..8e6929e6ce1 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -43,11 +43,11 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] update the naming and description for the `seata-http-jakarta` module - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC serialization default to Protobuf +- [[#6995](https://github.com/apache/incubator-seata/pull/6995)] upgrade outdate npmjs dependencies ### refactor: -### security: ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] Add unit tests for the `seata-rocketmq` module diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 6aeb138b529..5b8fa36327c 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -9,7 +9,7 @@ - [[#6864](https://github.com/apache/incubator-seata/pull/6864)] 支持神通数据库(oscar) - [[#6974](https://github.com/apache/incubator-seata/pull/6974)] 支持UndoLog的fastjson2序列化方式 - [[#6992](https://github.com/apache/incubator-seata/pull/6992)] 支持grpc序列化器 - +- [[#6995](https://github.com/apache/incubator-seata/pull/6995)] 升级过时的 npmjs 依赖 ### bugfix: - [[#6899](https://github.com/apache/incubator-seata/pull/6899)] 修复file.conf打包后的读取 diff --git a/console/src/main/resources/static/console-fe/package-lock.json b/console/src/main/resources/static/console-fe/package-lock.json index 1b86e082a97..45ca71e4923 100644 --- a/console/src/main/resources/static/console-fe/package-lock.json +++ b/console/src/main/resources/static/console-fe/package-lock.json @@ -5331,9 +5331,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -6672,9 +6672,9 @@ "integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==" }, "node_modules/elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", + "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -8160,9 +8160,9 @@ "dev": true }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -8170,7 +8170,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -9582,9 +9582,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", diff --git a/saga/seata-saga-statemachine-designer/package-lock.json b/saga/seata-saga-statemachine-designer/package-lock.json index 2cb1f08c0d7..d6225eb2023 100644 --- a/saga/seata-saga-statemachine-designer/package-lock.json +++ b/saga/seata-saga-statemachine-designer/package-lock.json @@ -3602,9 +3602,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "engines": { "node": ">= 0.6" @@ -5069,9 +5069,9 @@ } }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, "dependencies": { "accepts": "~1.3.8", @@ -5079,7 +5079,7 @@ "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -5947,9 +5947,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", - "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", + "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", From 7930f31b0b3810288fb1fed4faae69445b172903 Mon Sep 17 00:00:00 2001 From: GoodBoyCoder Date: Mon, 11 Nov 2024 14:38:54 +0800 Subject: [PATCH 41/54] bugfix: fix the problem of building undoLog exception when update join does not update data (#6994) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 ++ .../exec/mysql/MySQLUpdateJoinExecutor.java | 9 +++++- .../exec/UpdateJoinExecutorTest.java | 30 +++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 8e6929e6ce1..cfbf1cbdd89 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -23,6 +23,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6948](https://github.com/apache/incubator-seata/pull/6948)] Fix the CI build issue on the ARM64 platform - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] fix npe for nacos registry when look up address - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] support building docker image on openjdk23 +- [[#6994](https://github.com/apache/incubator-seata/pull/6994)] fix the problem of building undoLog exception when update join does not update data ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 5b8fa36327c..f1ce8b59deb 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -23,6 +23,8 @@ - [[#6948](https://github.com/apache/incubator-seata/pull/6948)] 修复在ARM64平台下CI构建出错的问题 - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] 修复nacos注册中心查询可用地址时的空指针问题 - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] 修复 openjdk23 版本下无法构建 docker 镜像的问题 +- [[#6994](https://github.com/apache/incubator-seata/pull/6994)] 修复updateJoin语句未更新到数据时prepareUndoLog异常 + ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLUpdateJoinExecutor.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLUpdateJoinExecutor.java index b682105fa0a..bea122bcb4a 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLUpdateJoinExecutor.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/exec/mysql/MySQLUpdateJoinExecutor.java @@ -91,7 +91,11 @@ protected TableRecords beforeImage() throws SQLException { } String selectSQL = buildBeforeImageSQL(joinTable, tableItems[i], suffixCommonCondition, itemTableUpdateColumns); TableRecords tableRecords = buildTableRecords(getTableMeta(tableItems[i]), selectSQL, paramAppenderList); - beforeImagesMap.put(tableItems[i], tableRecords); + if (CollectionUtils.isNotEmpty(tableRecords.getRows())) { + //when building the after image, the table with empty records in before image is skipped + //link issue https://github.com/apache/incubator-seata/issues/6976 + beforeImagesMap.put(tableItems[i], tableRecords); + } } return null; } @@ -231,6 +235,9 @@ private List getItemUpdateColumns(TableMeta itemTableMeta, List @Override protected void prepareUndoLog(TableRecords beforeImage, TableRecords afterImage) throws SQLException { + if (CollectionUtils.isEmpty(beforeImagesMap) && CollectionUtils.isEmpty(afterImagesMap)) { + return; + } if (CollectionUtils.isEmpty(beforeImagesMap) || CollectionUtils.isEmpty(afterImagesMap)) { throw new IllegalStateException("images can not be null"); } diff --git a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/UpdateJoinExecutorTest.java b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/UpdateJoinExecutorTest.java index 3a8ce32ce72..c047decbd94 100644 --- a/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/UpdateJoinExecutorTest.java +++ b/rm-datasource/src/test/java/org/apache/seata/rm/datasource/exec/UpdateJoinExecutorTest.java @@ -76,6 +76,36 @@ public void testUpdateJoinUndoLog() throws SQLException { Assertions.assertDoesNotThrow(()->mySQLUpdateJoinExecutor.prepareUndoLog(beforeImage, afterImage)); } + @Test + public void testEmptyUpdateJoinUndoLog() throws SQLException { + List returnValueColumnLabels = Lists.newArrayList("id", "name"); + Object[][] columnMetas = new Object[][]{ + new Object[]{"", "", "t1", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[]{"", "", "t1", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + new Object[]{"", "", "t2", "id", Types.INTEGER, "INTEGER", 64, 0, 10, 1, "", "", 0, 0, 64, 1, "NO", "YES"}, + new Object[]{"", "", "t2", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + new Object[]{"", "", "t1 inner join t2 on t1.id = t2.id", "id", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + new Object[]{"", "", "t1 inner join t2 on t1.id = t2.id", "name", Types.VARCHAR, "VARCHAR", 64, 0, 10, 0, "", "", 0, 0, 64, 2, "YES", "NO"}, + }; + Object[][] indexMetas = new Object[][]{ + new Object[]{"PRIMARY", "id", false, "", 3, 1, "A", 34}, + }; + Object[][] beforeReturnValue = new Object[][]{}; + StatementProxy beforeMockStatementProxy = mockStatementProxy(returnValueColumnLabels, beforeReturnValue, columnMetas, indexMetas); + String sql = "update t1 inner join t2 on t1.id = t2.id set t1.name = 'WILL',t2.name = 'WILL'"; + List asts = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL); + MySQLUpdateRecognizer recognizer = new MySQLUpdateRecognizer(sql, asts.get(0)); + UpdateExecutor mySQLUpdateJoinExecutor = new MySQLUpdateJoinExecutor(beforeMockStatementProxy, (statement, args) -> { + return null; + }, recognizer); + TableRecords beforeImage = mySQLUpdateJoinExecutor.beforeImage(); + Object[][] afterReturnValue = new Object[][]{}; + StatementProxy afterMockStatementProxy = mockStatementProxy(returnValueColumnLabels, afterReturnValue, columnMetas, indexMetas); + mySQLUpdateJoinExecutor.statementProxy = afterMockStatementProxy; + TableRecords afterImage = mySQLUpdateJoinExecutor.afterImage(beforeImage); + Assertions.assertDoesNotThrow(()->mySQLUpdateJoinExecutor.prepareUndoLog(beforeImage, afterImage)); + } + private StatementProxy mockStatementProxy(List returnValueColumnLabels, Object[][] returnValue, Object[][] columnMetas, Object[][] indexMetas) { MockDriver mockDriver = new MockDriver(returnValueColumnLabels, returnValue, columnMetas, indexMetas); DruidDataSource dataSource = new DruidDataSource(); From f195eb9dc6664973c2343b3bf6b2652a1ab6e360 Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 14 Nov 2024 12:04:29 +0800 Subject: [PATCH 42/54] optimize: optimize transaction metrics (#6993) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../server/metrics/MeterIdConstants.java | 2 +- .../server/metrics/MetricsSubscriber.java | 155 ++++++++---------- .../seata/server/session/GlobalSession.java | 4 +- .../seata/server/session/SessionHelper.java | 4 +- .../session/SessionStatusValidator.java | 5 + 7 files changed, 82 insertions(+), 90 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index cfbf1cbdd89..9f5c0cec22e 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -44,6 +44,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] Remove JVM parameter app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] update the naming and description for the `seata-http-jakarta` module - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC serialization default to Protobuf +- [[#6993](https://github.com/apache/incubator-seata/pull/6993)] optimize transaction metrics - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] upgrade outdate npmjs dependencies diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index f1ce8b59deb..0c2069749c6 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -47,6 +47,7 @@ - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] 移除JVM参数app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] 修正 `seata-http-jakarta`的模块命名和描述 - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC协议序列化默认值为protobuf +- [[#6993](https://github.com/apache/incubator-seata/pull/6993)] 优化 metrics 指标 ### refactor: diff --git a/server/src/main/java/org/apache/seata/server/metrics/MeterIdConstants.java b/server/src/main/java/org/apache/seata/server/metrics/MeterIdConstants.java index 4babd4ca2e0..18787594f8f 100644 --- a/server/src/main/java/org/apache/seata/server/metrics/MeterIdConstants.java +++ b/server/src/main/java/org/apache/seata/server/metrics/MeterIdConstants.java @@ -85,7 +85,7 @@ public interface MeterIdConstants { .withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_TIMER) .withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_COMMITTED); - Id TIMER_ROLLBACK = new Id(IdConstants.SEATA_TRANSACTION) + Id TIMER_ROLLBACKED = new Id(IdConstants.SEATA_TRANSACTION) .withTag(IdConstants.ROLE_KEY, IdConstants.ROLE_VALUE_TC) .withTag(IdConstants.METER_KEY, IdConstants.METER_VALUE_TIMER) .withTag(IdConstants.STATUS_KEY, IdConstants.STATUS_VALUE_ROLLBACKED); diff --git a/server/src/main/java/org/apache/seata/server/metrics/MetricsSubscriber.java b/server/src/main/java/org/apache/seata/server/metrics/MetricsSubscriber.java index 8beed4ba543..2a937d0b98c 100644 --- a/server/src/main/java/org/apache/seata/server/metrics/MetricsSubscriber.java +++ b/server/src/main/java/org/apache/seata/server/metrics/MetricsSubscriber.java @@ -25,6 +25,7 @@ import org.apache.seata.core.event.ExceptionEvent; import org.apache.seata.core.event.GlobalTransactionEvent; import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.metrics.Id; import org.apache.seata.metrics.registry.Registry; import org.apache.seata.server.event.EventBusManager; import org.slf4j.Logger; @@ -48,21 +49,45 @@ public class MetricsSubscriber { public MetricsSubscriber(Registry registry) { this.registry = registry; - consumers = new HashMap<>(); - consumers.put(GlobalStatus.Begin.name(), this::processGlobalStatusBegin); - consumers.put(GlobalStatus.Committed.name(), this::processGlobalStatusCommitted); - consumers.put(GlobalStatus.Rollbacked.name(), this::processGlobalStatusRollbacked); + this.consumers = initializeConsumers(); + } + + private Map> initializeConsumers() { + Map> consumerMap = new HashMap<>(); + consumerMap.put(GlobalStatus.Begin.name(), this::processGlobalStatusBegin); + consumerMap.put(GlobalStatus.Committed.name(), this::processGlobalStatusCommitted); + consumerMap.put(GlobalStatus.Rollbacked.name(), this::processGlobalStatusRollbacked); + + consumerMap.put(GlobalStatus.CommitFailed.name(), this::processGlobalStatusCommitFailed); + consumerMap.put(GlobalStatus.RollbackFailed.name(), this::processGlobalStatusRollbackFailed); + consumerMap.put(GlobalStatus.TimeoutRollbacked.name(), this::processGlobalStatusTimeoutRollbacked); + consumerMap.put(GlobalStatus.TimeoutRollbackFailed.name(), this::processGlobalStatusTimeoutRollbackFailed); + + consumerMap.put(GlobalStatus.CommitRetryTimeout.name(), this::processGlobalStatusCommitRetryTimeout); + consumerMap.put(GlobalStatus.RollbackRetryTimeout.name(), this::processGlobalStatusTimeoutRollbackRetryTimeout); + + consumerMap.put(STATUS_VALUE_AFTER_COMMITTED_KEY, this::processAfterGlobalCommitted); + consumerMap.put(STATUS_VALUE_AFTER_ROLLBACKED_KEY, this::processAfterGlobalRollbacked); + return consumerMap; + } - consumers.put(GlobalStatus.CommitFailed.name(), this::processGlobalStatusCommitFailed); - consumers.put(GlobalStatus.RollbackFailed.name(), this::processGlobalStatusRollbackFailed); - consumers.put(GlobalStatus.TimeoutRollbacked.name(), this::processGlobalStatusTimeoutRollbacked); - consumers.put(GlobalStatus.TimeoutRollbackFailed.name(), this::processGlobalStatusTimeoutRollbackFailed); + private void increaseCounter(Id counterId, GlobalTransactionEvent event) { + registry.getCounter(counterId.withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).increase(1); + } + private void decreaseCounter(Id counterId, GlobalTransactionEvent event) { + registry.getCounter(counterId.withTag(APP_ID_KEY, event.getApplicationId()) + .withTag(GROUP_KEY, event.getGroup())).decrease(1); + } - consumers.put(GlobalStatus.CommitRetryTimeout.name(), this::processGlobalStatusCommitRetryTimeout); - consumers.put(GlobalStatus.RollbackRetryTimeout.name(), this::processGlobalStatusTimeoutRollbackRetryTimeout); + private void increaseSummary(Id summaryId, GlobalTransactionEvent event, long value) { + registry.getSummary( + summaryId.withTag(APP_ID_KEY, event.getApplicationId()).withTag(GROUP_KEY, event.getGroup())).increase(value); + } - consumers.put(STATUS_VALUE_AFTER_COMMITTED_KEY, this::processAfterGlobalCommitted); - consumers.put(STATUS_VALUE_AFTER_ROLLBACKED_KEY, this::processAfterGlobalRollbacked); + private void increaseTimer(Id timerId, GlobalTransactionEvent event) { + registry.getTimer( + timerId.withTag(APP_ID_KEY, event.getApplicationId()).withTag(GROUP_KEY, event.getGroup())).record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); } private void processGlobalStatusBegin(GlobalTransactionEvent event) { @@ -72,124 +97,84 @@ private void processGlobalStatusBegin(GlobalTransactionEvent event) { LOGGER.debug("subscribe:{},threadName:{}", object.toString(), Thread.currentThread().getName()); } } - registry.getCounter(MeterIdConstants.COUNTER_ACTIVE.withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); + increaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); } private void processGlobalStatusCommitted(GlobalTransactionEvent event) { if (event.isRetryGlobal()) { return; } - decreaseActive(event); - registry.getCounter(MeterIdConstants.COUNTER_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getSummary(MeterIdConstants.SUMMARY_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getTimer(MeterIdConstants.TIMER_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())) - .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); + increaseCounter(MeterIdConstants.COUNTER_COMMITTED, event); + increaseSummary(MeterIdConstants.SUMMARY_COMMITTED, event, 1); + increaseTimer(MeterIdConstants.TIMER_COMMITTED, event); } private void processGlobalStatusRollbacked(GlobalTransactionEvent event) { if (event.isRetryGlobal()) { return; } - decreaseActive(event); - registry.getCounter(MeterIdConstants.COUNTER_ROLLBACKED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getSummary(MeterIdConstants.SUMMARY_ROLLBACKED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getTimer(MeterIdConstants.TIMER_ROLLBACK - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())) - .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); + increaseCounter(MeterIdConstants.COUNTER_ROLLBACKED, event); + increaseSummary(MeterIdConstants.SUMMARY_ROLLBACKED, event, 1); + increaseTimer(MeterIdConstants.TIMER_ROLLBACKED, event); } private void processAfterGlobalRollbacked(GlobalTransactionEvent event) { if (event.isRetryGlobal() && event.isRetryBranch()) { - decreaseActive(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); } - registry.getCounter(MeterIdConstants.COUNTER_AFTER_ROLLBACKED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getSummary(MeterIdConstants.SUMMARY_AFTER_ROLLBACKED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getTimer(MeterIdConstants.TIMER_AFTER_ROLLBACKED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())) - .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + increaseCounter(MeterIdConstants.COUNTER_AFTER_ROLLBACKED, event); + increaseSummary(MeterIdConstants.SUMMARY_AFTER_ROLLBACKED, event, 1); + increaseTimer(MeterIdConstants.TIMER_AFTER_ROLLBACKED, event); } private void processAfterGlobalCommitted(GlobalTransactionEvent event) { if (event.isRetryGlobal() && event.isRetryBranch()) { - decreaseActive(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); } - registry.getCounter(MeterIdConstants.COUNTER_AFTER_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getSummary(MeterIdConstants.SUMMARY_AFTER_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getTimer(MeterIdConstants.TIMER_AFTER_COMMITTED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())) - .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); + increaseCounter(MeterIdConstants.COUNTER_AFTER_COMMITTED, event); + increaseSummary(MeterIdConstants.SUMMARY_AFTER_COMMITTED, event, 1); + increaseTimer(MeterIdConstants.TIMER_AFTER_COMMITTED, event); } private void processGlobalStatusCommitFailed(GlobalTransactionEvent event) { - decreaseActive(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); reportFailed(event); } private void processGlobalStatusRollbackFailed(GlobalTransactionEvent event) { - decreaseActive(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); reportFailed(event); } private void processGlobalStatusTimeoutRollbacked(GlobalTransactionEvent event) { - decreaseActive(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); } private void processGlobalStatusTimeoutRollbackFailed(GlobalTransactionEvent event) { - decreaseActive(event); - reportTwoPhaseTimeout(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); + increaseSummary(MeterIdConstants.SUMMARY_TWO_PHASE_TIMEOUT, event, 1); + reportFailed(event); } private void processGlobalStatusCommitRetryTimeout(GlobalTransactionEvent event) { - decreaseActive(event); - reportTwoPhaseTimeout(event); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); + increaseSummary(MeterIdConstants.SUMMARY_TWO_PHASE_TIMEOUT, event, 1); + //The phase 2 retry timeout state should be considered a transaction failed + reportFailed(event); } private void processGlobalStatusTimeoutRollbackRetryTimeout(GlobalTransactionEvent event) { - decreaseActive(event); - } - - private void decreaseActive(GlobalTransactionEvent event) { - registry.getCounter(MeterIdConstants.COUNTER_ACTIVE - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).decrease(1); + decreaseCounter(MeterIdConstants.COUNTER_ACTIVE, event); + increaseSummary(MeterIdConstants.SUMMARY_TWO_PHASE_TIMEOUT, event, 1); + //The phase 2 retry timeout state should be considered a transaction failed + reportFailed(event); } private void reportFailed(GlobalTransactionEvent event) { - registry.getSummary(MeterIdConstants.SUMMARY_FAILED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); - registry.getTimer(MeterIdConstants.TIMER_FAILED - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())) - .record(event.getEndTime() - event.getBeginTime(), TimeUnit.MILLISECONDS); - } - - private void reportTwoPhaseTimeout(GlobalTransactionEvent event) { - registry.getSummary(MeterIdConstants.SUMMARY_TWO_PHASE_TIMEOUT - .withTag(APP_ID_KEY, event.getApplicationId()) - .withTag(GROUP_KEY, event.getGroup())).increase(1); + increaseSummary(MeterIdConstants.SUMMARY_FAILED, event, 1); + increaseTimer(MeterIdConstants.TIMER_FAILED, event); } diff --git a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java index 8cfc0ecbc67..251b9876e5b 100644 --- a/server/src/main/java/org/apache/seata/server/session/GlobalSession.java +++ b/server/src/main/java/org/apache/seata/server/session/GlobalSession.java @@ -33,6 +33,7 @@ import org.apache.seata.common.XID; import org.apache.seata.common.util.BufferUtils; import org.apache.seata.common.util.StringUtils; +import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.core.exception.GlobalTransactionException; import org.apache.seata.core.exception.TransactionException; @@ -41,7 +42,6 @@ import org.apache.seata.core.model.BranchType; import org.apache.seata.core.model.GlobalStatus; import org.apache.seata.core.model.LockStatus; -import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.server.cluster.raft.RaftServerManager; import org.apache.seata.server.lock.LockerManagerFactory; import org.apache.seata.server.store.SessionStorable; @@ -793,7 +793,7 @@ public void queueToRetryCommit() throws TransactionException { public void queueToRetryRollback() throws TransactionException { GlobalStatus currentStatus = this.getStatus(); GlobalStatus newStatus; - if (SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) { + if (GlobalStatus.TimeoutRollbacking == currentStatus) { newStatus = GlobalStatus.TimeoutRollbackRetrying; } else { newStatus = GlobalStatus.RollbackRetrying; diff --git a/server/src/main/java/org/apache/seata/server/session/SessionHelper.java b/server/src/main/java/org/apache/seata/server/session/SessionHelper.java index 7ffab5f14be..17cd084505e 100644 --- a/server/src/main/java/org/apache/seata/server/session/SessionHelper.java +++ b/server/src/main/java/org/apache/seata/server/session/SessionHelper.java @@ -212,7 +212,7 @@ public static void endRollbacked(GlobalSession globalSession, boolean retryGloba boolean retryBranch = currentStatus == GlobalStatus.TimeoutRollbackRetrying || currentStatus == GlobalStatus.RollbackRetrying; if (!currentStatus.equals(GlobalStatus.TimeoutRollbacked) - && SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) { + && SessionStatusValidator.isTimeoutRollbacking(currentStatus)) { globalSession.changeGlobalStatus(GlobalStatus.TimeoutRollbacked); } else if (!globalSession.getStatus().equals(GlobalStatus.Rollbacked)) { globalSession.changeGlobalStatus(GlobalStatus.Rollbacked); @@ -255,7 +255,7 @@ public static void endRollbackFailed(GlobalSession globalSession, boolean retryG GlobalStatus currentStatus = globalSession.getStatus(); if (isRetryTimeout) { globalSession.changeGlobalStatus(GlobalStatus.RollbackRetryTimeout); - } else if (SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) { + } else if (SessionStatusValidator.isTimeoutRollbacking(currentStatus)) { globalSession.changeGlobalStatus(GlobalStatus.TimeoutRollbackFailed); } else { globalSession.changeGlobalStatus(GlobalStatus.RollbackFailed); diff --git a/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java b/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java index 52722a76d36..654af466245 100644 --- a/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java +++ b/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java @@ -36,6 +36,11 @@ public static boolean isTimeoutGlobalStatus(GlobalStatus status) { || status == GlobalStatus.TimeoutRollbackRetrying; } + public static boolean isTimeoutRollbacking(GlobalStatus status) { + return status == GlobalStatus.TimeoutRollbacking + || status == GlobalStatus.TimeoutRollbackRetrying; + } + /** * is rollback global status * From dbe095a39c9f751bcba293feee6234380d4bc58c Mon Sep 17 00:00:00 2001 From: jimin Date: Fri, 15 Nov 2024 09:32:25 +0800 Subject: [PATCH 43/54] optimize: optimize lock release logic in AT transaction mode (#6996) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 + .../seata/common/ConfigurationKeys.java | 6 + .../apache/seata/common/DefaultValues.java | 189 +++++++++++++++++- script/config-center/config.txt | 2 +- .../coordinator/DefaultCoordinator.java | 25 ++- .../seata/server/coordinator/DefaultCore.java | 8 +- .../session/AbstractSessionManager.java | 11 + .../main/resources/application.example.yml | 2 +- 9 files changed, 225 insertions(+), 21 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 9f5c0cec22e..22a961edcae 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -46,6 +46,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC serialization default to Protobuf - [[#6993](https://github.com/apache/incubator-seata/pull/6993)] optimize transaction metrics - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] upgrade outdate npmjs dependencies +- [[#6996](https://github.com/apache/incubator-seata/pull/6996)] optimize lock release logic in AT transaction mode ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 0c2069749c6..89bfb6438f6 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -47,8 +47,10 @@ - [[#6950](https://github.com/apache/incubator-seata/pull/6950)] 移除JVM参数app.id - [[#6959](https://github.com/apache/incubator-seata/pull/6959)] 修正 `seata-http-jakarta`的模块命名和描述 - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC协议序列化默认值为protobuf +- [[#6996](https://github.com/apache/incubator-seata/pull/6996)] 优化 AT 事务模式锁释放逻辑 - [[#6993](https://github.com/apache/incubator-seata/pull/6993)] 优化 metrics 指标 + ### refactor: diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index ff8436b6dc9..ef7304b1623 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -504,9 +504,15 @@ public interface ConfigurationKeys { /** * The constant ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE. + * This configuration is deprecated, please use {@link #ROLLBACK_FAILED_UNLOCK_ENABLE} instead. */ + @Deprecated String ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE = SERVER_PREFIX + "rollbackRetryTimeoutUnlockEnable"; + /** + * The constant ROLLBACK_FAILED_UNLOCK_ENABLE. + */ + String ROLLBACK_FAILED_UNLOCK_ENABLE = SERVER_PREFIX + "rollbackFailedUnlockEnable"; /** * the constant RETRY_DEAD_THRESHOLD */ diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index eb0d40bb308..68d06a76ff8 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -18,56 +18,160 @@ import java.time.Duration; +/** + * The interface Default values. + */ public interface DefaultValues { + /** + * The constant DEFAULT_CLIENT_LOCK_RETRY_INTERVAL. + */ int DEFAULT_CLIENT_LOCK_RETRY_INTERVAL = 10; + /** + * The constant DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES. + */ int DEFAULT_TM_DEGRADE_CHECK_ALLOW_TIMES = 10; + /** + * The constant DEFAULT_CLIENT_LOCK_RETRY_TIMES. + */ int DEFAULT_CLIENT_LOCK_RETRY_TIMES = 30; + /** + * The constant DEFAULT_CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT. + */ boolean DEFAULT_CLIENT_LOCK_RETRY_POLICY_BRANCH_ROLLBACK_ON_CONFLICT = true; + /** + * The constant DEFAULT_LOG_EXCEPTION_RATE. + */ int DEFAULT_LOG_EXCEPTION_RATE = 100; + /** + * The constant DEFAULT_CLIENT_ASYNC_COMMIT_BUFFER_LIMIT. + */ int DEFAULT_CLIENT_ASYNC_COMMIT_BUFFER_LIMIT = 10000; + /** + * The constant DEFAULT_TM_DEGRADE_CHECK_PERIOD. + */ int DEFAULT_TM_DEGRADE_CHECK_PERIOD = 2000; + /** + * The constant DEFAULT_CLIENT_REPORT_RETRY_COUNT. + */ int DEFAULT_CLIENT_REPORT_RETRY_COUNT = 5; + /** + * The constant DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE. + */ boolean DEFAULT_CLIENT_REPORT_SUCCESS_ENABLE = false; + /** + * The constant DEFAULT_CLIENT_TABLE_META_CHECK_ENABLE. + */ boolean DEFAULT_CLIENT_TABLE_META_CHECK_ENABLE = true; + /** + * The constant DEFAULT_TABLE_META_CHECKER_INTERVAL. + */ long DEFAULT_TABLE_META_CHECKER_INTERVAL = 60000L; + /** + * The constant DEFAULT_TM_DEGRADE_CHECK. + */ boolean DEFAULT_TM_DEGRADE_CHECK = false; + /** + * The constant DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE. + */ boolean DEFAULT_CLIENT_SAGA_BRANCH_REGISTER_ENABLE = false; /** * The default session store dir */ String DEFAULT_SESSION_STORE_FILE_DIR = "sessionStore"; + /** + * The constant DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE. + */ boolean DEFAULT_CLIENT_SAGA_RETRY_PERSIST_MODE_UPDATE = false; + /** + * The constant DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE. + */ boolean DEFAULT_CLIENT_SAGA_COMPENSATE_PERSIST_MODE_UPDATE = false; + /** + * The constant DEFAULT_RAFT_SERIALIZATION. + */ String DEFAULT_RAFT_SERIALIZATION = "jackson"; + /** + * The constant DEFAULT_RAFT_COMPRESSOR. + */ String DEFAULT_RAFT_COMPRESSOR = "none"; /** * Shutdown timeout default 3s */ int DEFAULT_SHUTDOWN_TIMEOUT_SEC = 13; + /** + * The constant DEFAULT_SELECTOR_THREAD_SIZE. + */ int DEFAULT_SELECTOR_THREAD_SIZE = 1; + /** + * The constant DEFAULT_BOSS_THREAD_SIZE. + */ int DEFAULT_BOSS_THREAD_SIZE = 1; - + /** + * The constant DEFAULT_SELECTOR_THREAD_PREFIX. + */ String DEFAULT_SELECTOR_THREAD_PREFIX = "NettyClientSelector"; + /** + * The constant DEFAULT_WORKER_THREAD_PREFIX. + */ String DEFAULT_WORKER_THREAD_PREFIX = "NettyClientWorkerThread"; + /** + * The constant DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST. + */ @Deprecated boolean DEFAULT_ENABLE_CLIENT_BATCH_SEND_REQUEST = true; + /** + * The constant DEFAULT_ENABLE_TM_CLIENT_BATCH_SEND_REQUEST. + */ boolean DEFAULT_ENABLE_TM_CLIENT_BATCH_SEND_REQUEST = false; + /** + * The constant DEFAULT_ENABLE_RM_CLIENT_BATCH_SEND_REQUEST. + */ boolean DEFAULT_ENABLE_RM_CLIENT_BATCH_SEND_REQUEST = true; + /** + * The constant DEFAULT_ENABLE_TC_SERVER_BATCH_SEND_RESPONSE. + */ boolean DEFAULT_ENABLE_TC_SERVER_BATCH_SEND_RESPONSE = false; + /** + * The constant DEFAULT_CLIENT_CHANNEL_CHECK_FAIL_FAST. + */ boolean DEFAULT_CLIENT_CHANNEL_CHECK_FAIL_FAST = true; + /** + * The constant DEFAULT_BOSS_THREAD_PREFIX. + */ String DEFAULT_BOSS_THREAD_PREFIX = "NettyBoss"; + /** + * The constant DEFAULT_NIO_WORKER_THREAD_PREFIX. + */ String DEFAULT_NIO_WORKER_THREAD_PREFIX = "NettyServerNIOWorker"; + /** + * The constant DEFAULT_EXECUTOR_THREAD_PREFIX. + */ String DEFAULT_EXECUTOR_THREAD_PREFIX = "NettyServerBizHandler"; + /** + * The constant DEFAULT_PROTOCOL. + */ String DEFAULT_PROTOCOL = "seata"; + /** + * The constant DEFAULT_TRANSPORT_HEARTBEAT. + */ boolean DEFAULT_TRANSPORT_HEARTBEAT = true; + /** + * The constant DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION. + */ boolean DEFAULT_TRANSACTION_UNDO_DATA_VALIDATION = true; + /** + * The constant DEFAULT_TRANSACTION_UNDO_LOG_SERIALIZATION. + */ String DEFAULT_TRANSACTION_UNDO_LOG_SERIALIZATION = "jackson"; + /** + * The constant DEFAULT_ONLY_CARE_UPDATE_COLUMNS. + */ boolean DEFAULT_ONLY_CARE_UPDATE_COLUMNS = true; /** * The constant DEFAULT_TRANSACTION_UNDO_LOG_TABLE. @@ -93,40 +197,97 @@ public interface DefaultValues { */ String DEFAULT_DISTRIBUTED_LOCK_DB_TABLE = "distributed_lock"; + /** + * The constant DEFAULT_TM_COMMIT_RETRY_COUNT. + */ int DEFAULT_TM_COMMIT_RETRY_COUNT = 5; + /** + * The constant DEFAULT_TM_ROLLBACK_RETRY_COUNT. + */ int DEFAULT_TM_ROLLBACK_RETRY_COUNT = 5; + /** + * The constant DEFAULT_GLOBAL_TRANSACTION_TIMEOUT. + */ int DEFAULT_GLOBAL_TRANSACTION_TIMEOUT = 60000; + /** + * The constant DEFAULT_TX_GROUP. + */ String DEFAULT_TX_GROUP = "default_tx_group"; + /** + * The constant DEFAULT_TX_GROUP_OLD. + */ @Deprecated String DEFAULT_TX_GROUP_OLD = "my_test_tx_group"; + /** + * The constant DEFAULT_TC_CLUSTER. + */ String DEFAULT_TC_CLUSTER = "default"; + /** + * The constant DEFAULT_GROUPLIST. + */ String DEFAULT_GROUPLIST = "127.0.0.1:8091"; + /** + * The constant DEFAULT_DATA_SOURCE_PROXY_MODE. + */ String DEFAULT_DATA_SOURCE_PROXY_MODE = "AT"; + /** + * The constant DEFAULT_DISABLE_GLOBAL_TRANSACTION. + */ boolean DEFAULT_DISABLE_GLOBAL_TRANSACTION = false; + /** + * The constant SERVICE_DEFAULT_PORT. + */ //currently not use and will be delete in the next version @Deprecated int SERVICE_DEFAULT_PORT = 8091; + /** + * The constant SERVICE_OFFSET_SPRING_BOOT. + */ int SERVICE_OFFSET_SPRING_BOOT = 1000; + /** + * The constant SERVER_PORT. + */ String SERVER_PORT = "seata.server.port"; + /** + * The constant SERVER_DEFAULT_STORE_MODE. + */ String SERVER_DEFAULT_STORE_MODE = "file"; + /** + * The constant DEFAULT_SAGA_JSON_PARSER. + */ String DEFAULT_SAGA_JSON_PARSER = "fastjson"; + /** + * The constant DEFAULT_TCC_BUSINESS_ACTION_CONTEXT_JSON_PARSER. + */ // default tcc business action context json parser String DEFAULT_TCC_BUSINESS_ACTION_CONTEXT_JSON_PARSER = "fastjson"; + /** + * The constant DEFAULT_SERVER_ENABLE_CHECK_AUTH. + */ boolean DEFAULT_SERVER_ENABLE_CHECK_AUTH = true; + /** + * The constant DEFAULT_LOAD_BALANCE. + */ String DEFAULT_LOAD_BALANCE = "XID"; + /** + * The constant VIRTUAL_NODES_DEFAULT. + */ int VIRTUAL_NODES_DEFAULT = 10; + /** + * The constant DEFAULT_SEATA_GROUP. + */ String DEFAULT_SEATA_GROUP = "default"; /** @@ -144,7 +305,6 @@ public interface DefaultValues { */ String DEFAULT_CLIENT_UNDO_COMPRESS_THRESHOLD = "64k"; - /** * the constant DEFAULT_RETRY_DEAD_THRESHOLD */ @@ -283,9 +443,9 @@ public interface DefaultValues { long DEFAULT_MAX_ROLLBACK_RETRY_TIMEOUT = -1L; /** - * the const DEFAULT_ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE + * The constant DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE. */ - boolean DEFAULT_ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE = false; + boolean DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE = false; /** * DEFAULT_DISTRIBUTED_LOCK_EXPIRE_TIME @@ -297,16 +457,34 @@ public interface DefaultValues { */ boolean DEFAULT_ENABLE_BRANCH_ASYNC_REMOVE = false; + /** + * The constant DEFAULT_DB_MAX_CONN. + */ int DEFAULT_DB_MAX_CONN = 100; + /** + * The constant DEFAULT_DB_MIN_CONN. + */ int DEFAULT_DB_MIN_CONN = 10; + /** + * The constant DEFAULT_REDIS_MAX_IDLE. + */ int DEFAULT_REDIS_MAX_IDLE = 100; + /** + * The constant DEFAULT_REDIS_MAX_TOTAL. + */ int DEFAULT_REDIS_MAX_TOTAL = 100; + /** + * The constant DEFAULT_REDIS_MIN_IDLE. + */ int DEFAULT_REDIS_MIN_IDLE = 10; + /** + * The constant DEFAULT_QUERY_LIMIT. + */ int DEFAULT_QUERY_LIMIT = 1000; /** @@ -314,5 +492,8 @@ public interface DefaultValues { */ String DRUID_LOCATION = "lib/sqlparser/druid.jar"; + /** + * The constant DEFAULT_ROCKET_MQ_MSG_TIMEOUT. + */ int DEFAULT_ROCKET_MQ_MSG_TIMEOUT = 60 * 1000; } diff --git a/script/config-center/config.txt b/script/config-center/config.txt index 8cf986f3f94..af831bc5d23 100644 --- a/script/config-center/config.txt +++ b/script/config-center/config.txt @@ -140,7 +140,7 @@ server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 -server.rollbackRetryTimeoutUnlockEnable=false +server.rollbackFailedUnlockEnable=false server.distributedLockExpireTime=10000 server.session.branchAsyncQueueSize=5000 server.session.enableBranchAsyncRemove=false diff --git a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java index 9003fe268aa..ddfd5f35d66 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCoordinator.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import io.netty.channel.Channel; +import org.apache.commons.lang.time.DateFormatUtils; import org.apache.seata.common.DefaultValues; import org.apache.seata.common.thread.NamedThreadFactory; import org.apache.seata.common.util.CollectionUtils; @@ -71,7 +72,6 @@ import org.apache.seata.server.session.SessionHelper; import org.apache.seata.server.session.SessionHolder; import org.apache.seata.server.store.StoreConfig; -import org.apache.commons.lang.time.DateFormatUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -90,7 +90,7 @@ import static org.apache.seata.common.DefaultValues.DEFAULT_MAX_COMMIT_RETRY_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_MAX_ROLLBACK_RETRY_TIMEOUT; import static org.apache.seata.common.DefaultValues.DEFAULT_ROLLBACKING_RETRY_PERIOD; -import static org.apache.seata.common.DefaultValues.DEFAULT_ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE; +import static org.apache.seata.common.DefaultValues.DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE; import static org.apache.seata.common.DefaultValues.DEFAULT_TIMEOUT_RETRY_PERIOD; import static org.apache.seata.common.DefaultValues.DEFAULT_UNDO_LOG_DELETE_PERIOD; @@ -159,7 +159,10 @@ public class DefaultCoordinator extends AbstractTCInboundHandler implements Tran ConfigurationKeys.MAX_ROLLBACK_RETRY_TIMEOUT, DEFAULT_MAX_ROLLBACK_RETRY_TIMEOUT); private static final boolean ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE = ConfigurationFactory.getInstance().getBoolean( - ConfigurationKeys.ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE, DEFAULT_ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE); + ConfigurationKeys.ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE, DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE); + + private static final boolean ROLLBACK_FAILED_UNLOCK_ENABLE = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.ROLLBACK_FAILED_UNLOCK_ENABLE, DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE); private static final int RETRY_DEAD_THRESHOLD = ConfigurationFactory.getInstance() .getInt(org.apache.seata.common.ConfigurationKeys.RETRY_DEAD_THRESHOLD, DefaultValues.DEFAULT_RETRY_DEAD_THRESHOLD); @@ -341,15 +344,15 @@ protected void doLockCheck(GlobalLockQueryRequest request, GlobalLockQueryRespon protected void timeoutCheck() { SessionCondition sessionCondition = new SessionCondition(GlobalStatus.Begin); sessionCondition.setLazyLoadBranch(true); - Collection beginGlobalsessions = + Collection beginGlobalSessions = SessionHolder.getRootSessionManager().findGlobalSessions(sessionCondition); - if (CollectionUtils.isEmpty(beginGlobalsessions)) { + if (CollectionUtils.isEmpty(beginGlobalSessions)) { return; } - if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) { - LOGGER.debug("Global transaction timeout check begin, size: {}", beginGlobalsessions.size()); + if (!beginGlobalSessions.isEmpty() && LOGGER.isDebugEnabled()) { + LOGGER.debug("Global transaction timeout check begin, size: {}", beginGlobalSessions.size()); } - SessionHelper.forEach(beginGlobalsessions, globalSession -> { + SessionHelper.forEach(beginGlobalSessions, globalSession -> { if (LOGGER.isDebugEnabled()) { LOGGER.debug( globalSession.getXid() + " " + globalSession.getStatus() + " " + globalSession.getBeginTime() + " " @@ -372,7 +375,7 @@ protected void timeoutCheck() { return true; }); }); - if (!beginGlobalsessions.isEmpty() && LOGGER.isDebugEnabled()) { + if (!beginGlobalSessions.isEmpty() && LOGGER.isDebugEnabled()) { LOGGER.debug("Global transaction timeout check end. "); } @@ -394,7 +397,7 @@ protected void handleRetryRollbacking() { SessionHelper.forEach(rollbackingSessions, rollbackingSession -> { try { if (isRetryTimeout(now, MAX_ROLLBACK_RETRY_TIMEOUT, rollbackingSession.getBeginTime())) { - if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE) { + if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE || ROLLBACK_FAILED_UNLOCK_ENABLE) { rollbackingSession.clean(); } @@ -520,7 +523,7 @@ protected void handleRollbackingByScheduled() { SessionHelper.forEach(needDoRollbackingSessions, rollbackingSession -> { try { if (isRetryTimeout(now, MAX_ROLLBACK_RETRY_TIMEOUT, rollbackingSession.getBeginTime())) { - if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE) { + if (ROLLBACK_RETRY_TIMEOUT_UNLOCK_ENABLE || ROLLBACK_FAILED_UNLOCK_ENABLE) { rollbackingSession.clean(); } diff --git a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java index d6c93532fa7..5acbc8988de 100644 --- a/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java +++ b/server/src/main/java/org/apache/seata/server/coordinator/DefaultCore.java @@ -57,7 +57,7 @@ public class DefaultCore implements Core { private static final int RETRY_XAER_NOTA_TIMEOUT = ConfigurationFactory.getInstance().getInt(XAER_NOTA_RETRY_TIMEOUT, DefaultValues.DEFAULT_XAER_NOTA_RETRY_TIMEOUT); - private static Map coreMap = new ConcurrentHashMap<>(); + private static final Map CORE_MAP = new ConcurrentHashMap<>(); private static final boolean PARALLEL_HANDLE_BRANCH = ConfigurationFactory.getInstance().getBoolean(ENABLE_PARALLEL_HANDLE_BRANCH_KEY, false); @@ -72,7 +72,7 @@ public DefaultCore(RemotingServer remotingServer) { new Class[] {RemotingServer.class}, new Object[] {remotingServer}); if (CollectionUtils.isNotEmpty(allCore)) { for (AbstractCore core : allCore) { - coreMap.put(core.getHandleBranchType(), core); + CORE_MAP.put(core.getHandleBranchType(), core); } } } @@ -84,7 +84,7 @@ public DefaultCore(RemotingServer remotingServer) { * @return the core */ public AbstractCore getCore(BranchType branchType) { - AbstractCore core = coreMap.get(branchType); + AbstractCore core = CORE_MAP.get(branchType); if (core == null) { throw new NotSupportYetException("unsupported type:" + branchType.name()); } @@ -98,7 +98,7 @@ public AbstractCore getCore(BranchType branchType) { * @param core the core */ public void mockCore(BranchType branchType, AbstractCore core) { - coreMap.put(branchType, core); + CORE_MAP.put(branchType, core); } @Override diff --git a/server/src/main/java/org/apache/seata/server/session/AbstractSessionManager.java b/server/src/main/java/org/apache/seata/server/session/AbstractSessionManager.java index 611993b1594..8b2e7095936 100644 --- a/server/src/main/java/org/apache/seata/server/session/AbstractSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/session/AbstractSessionManager.java @@ -16,6 +16,8 @@ */ package org.apache.seata.server.session; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.constants.ConfigurationKeys; import org.apache.seata.core.exception.BranchTransactionException; import org.apache.seata.core.exception.GlobalTransactionException; import org.apache.seata.core.exception.TransactionException; @@ -29,10 +31,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.seata.common.DefaultValues.DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE; + /** * The type Abstract session manager. */ public abstract class AbstractSessionManager implements SessionManager { + boolean rollbackFailedUnlockEnable = ConfigurationFactory.getInstance().getBoolean( + ConfigurationKeys.ROLLBACK_FAILED_UNLOCK_ENABLE, DEFAULT_ROLLBACK_FAILED_UNLOCK_ENABLE); /** * The constant LOGGER. @@ -157,6 +163,11 @@ public void onSuccessEnd(GlobalSession globalSession) throws TransactionExceptio @Override public void onFailEnd(GlobalSession globalSession) throws TransactionException { + if (rollbackFailedUnlockEnable) { + globalSession.clean(); + LOGGER.info("xid:{} fail end and remove lock, transaction:{}", globalSession.getXid(), globalSession); + return; + } LOGGER.info("xid:{} fail end, transaction:{}", globalSession.getXid(), globalSession); } diff --git a/server/src/main/resources/application.example.yml b/server/src/main/resources/application.example.yml index 5d92d069284..059312ae856 100644 --- a/server/src/main/resources/application.example.yml +++ b/server/src/main/resources/application.example.yml @@ -139,7 +139,7 @@ seata: service-port: 8091 #If not configured, the default is '${server.port} + 1000' max-commit-retry-timeout: -1 max-rollback-retry-timeout: -1 - rollback-retry-timeout-unlock-enable: false + rollback-failed-unlock-enable: false enable-check-auth: true enable-parallel-request-handle: true enable-parallel-handle-branch: false From 0a21deab960d0f0d44f68e9aeb2d418f1b1b4c87 Mon Sep 17 00:00:00 2001 From: funkye Date: Mon, 18 Nov 2024 13:43:02 +0800 Subject: [PATCH 44/54] bugfix: fix the Raft NPE issue caused by two-phase concurrency (#7005) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../branch/AddBranchSessionExecute.java | 11 +++++++++- .../branch/UpdateBranchSessionExecute.java | 22 +++++++++++++++++-- .../lock/BranchReleaseLockExecute.java | 12 ++++++++-- .../session/SessionStatusValidator.java | 5 +++++ 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 22a961edcae..864ec7b10b0 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -24,6 +24,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] fix npe for nacos registry when look up address - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] support building docker image on openjdk23 - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] fix the problem of building undoLog exception when update join does not update data +- [[#7005](https://github.com/apache/incubator-seata/pull/7005)] fix the Raft NPE issue caused by two-phase concurrency ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 89bfb6438f6..42b990699f3 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -24,6 +24,7 @@ - [[#6947](https://github.com/apache/incubator-seata/pull/6947)] 修复nacos注册中心查询可用地址时的空指针问题 - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] 修复 openjdk23 版本下无法构建 docker 镜像的问题 - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] 修复updateJoin语句未更新到数据时prepareUndoLog异常 +- [[#7005](https://github.com/apache/incubator-seata/pull/7005)] 修复Raft模式下两阶段并发可能导致NPE的问题 ### optimize: diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java index 0bf2f122215..9d3086ad7ae 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/AddBranchSessionExecute.java @@ -35,7 +35,16 @@ public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); BranchTransactionDTO branchTransactionDTO = sessionSyncMsg.getBranchSession(); - GlobalSession globalSession = raftSessionManager.findGlobalSession(branchTransactionDTO.getXid()); + String xid = branchTransactionDTO.getXid(); + GlobalSession globalSession = raftSessionManager.findGlobalSession(xid); + if (globalSession == null) { + if (logger.isWarnEnabled()) { + logger.warn( + "The transaction corresponding to the XID: {} does not exist, which may cause a two-phase concurrency issue, msg type: {}", + xid, syncMsg.getMsgType()); + } + return false; + } BranchSession branchSession = SessionConverter.convertBranchSession(branchTransactionDTO); branchSession.lock(); globalSession.add(branchSession); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java index c98f3820476..e096ea36cdc 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/branch/UpdateBranchSessionExecute.java @@ -33,8 +33,26 @@ public class UpdateBranchSessionExecute extends AbstractRaftMsgExecute { public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; RaftSessionManager raftSessionManager = (RaftSessionManager) SessionHolder.getRootSessionManager(sessionSyncMsg.getGroup()); - GlobalSession globalSession = raftSessionManager.findGlobalSession(sessionSyncMsg.getBranchSession().getXid()); - BranchSession branchSession = globalSession.getBranch(sessionSyncMsg.getBranchSession().getBranchId()); + String xid = sessionSyncMsg.getBranchSession().getXid(); + GlobalSession globalSession = raftSessionManager.findGlobalSession(xid); + if (globalSession == null) { + if (logger.isWarnEnabled()) { + logger.warn( + "The transaction corresponding to the XID: {} does not exist, which may cause a two-phase concurrency issue, msg type: {}", + xid, syncMsg.getMsgType()); + } + return false; + } + long branchId = sessionSyncMsg.getBranchSession().getBranchId(); + BranchSession branchSession = globalSession.getBranch(branchId); + if (branchSession == null) { + if (logger.isWarnEnabled()) { + logger.warn( + "The branch session corresponding to the branchId: {} does not exist, which may cause a two-phase concurrency issue, msg type: {}", + sessionSyncMsg.getBranchSession().getBranchId(), syncMsg.getMsgType()); + } + return false; + } BranchStatus status = BranchStatus.get(sessionSyncMsg.getBranchSession().getStatus()); branchSession.setStatus(status); if (logger.isDebugEnabled()) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java index 805ef1b7f7b..206fe77e3cd 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/lock/BranchReleaseLockExecute.java @@ -30,8 +30,16 @@ public class BranchReleaseLockExecute extends AbstractRaftMsgExecute { @Override public Boolean execute(RaftBaseMsg syncMsg) throws Throwable { RaftBranchSessionSyncMsg sessionSyncMsg = (RaftBranchSessionSyncMsg)syncMsg; - GlobalSession globalSession = - SessionHolder.getRootSessionManager().findGlobalSession(sessionSyncMsg.getBranchSession().getXid()); + String xid = sessionSyncMsg.getBranchSession().getXid(); + GlobalSession globalSession = SessionHolder.getRootSessionManager().findGlobalSession(xid); + if (globalSession == null) { + if (logger.isWarnEnabled()) { + logger.warn( + "The transaction corresponding to the XID: {} does not exist, which may cause a two-phase concurrency issue, msg type: {}", + xid, syncMsg.getMsgType()); + } + return false; + } BranchSession branchSession = globalSession.getBranch(sessionSyncMsg.getBranchSession().getBranchId()); if (branchSession != null) { if (logger.isDebugEnabled()) { diff --git a/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java b/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java index 654af466245..f6fa7f074aa 100644 --- a/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java +++ b/server/src/main/java/org/apache/seata/server/session/SessionStatusValidator.java @@ -54,6 +54,11 @@ public static boolean isRollbackGlobalStatus(GlobalStatus status) { || status == GlobalStatus.RollbackRetryTimeout; } + public static boolean isEndGlobalStatus(GlobalStatus status) { + return status == GlobalStatus.Rollbacked || status == GlobalStatus.TimeoutRollbacked + || status == GlobalStatus.Committed || status == GlobalStatus.Finished; + } + /** * is commit global status * From 03595afe7d3447205b165bc4bb8a4f9251cf86e6 Mon Sep 17 00:00:00 2001 From: whaon Date: Tue, 19 Nov 2024 10:43:17 +0800 Subject: [PATCH 45/54] bugfix:fix error while the "context" is key word in DM8 when delete undolog (#7010) --- changes/en-us/2.x.md | 2 + changes/zh-cn/2.x.md | 2 + .../datasource/undo/dm/DmUndoLogManager.java | 87 +++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 864ec7b10b0..5119c31a979 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -25,6 +25,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] support building docker image on openjdk23 - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] fix the problem of building undoLog exception when update join does not update data - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] fix the Raft NPE issue caused by two-phase concurrency +- [[#7010](https://github.com/apache/incubator-seata/pull/7010)] fix error while the "context" is key word in DM8 when delete undolog ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction @@ -73,6 +74,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [xingfudeshi](https://github.com/xingfudeshi) - [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) +- [whaon](https://github.com/whaon) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 42b990699f3..6451c585363 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -25,6 +25,7 @@ - [[#6984](https://github.com/apache/incubator-seata/pull/6984)] 修复 openjdk23 版本下无法构建 docker 镜像的问题 - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] 修复updateJoin语句未更新到数据时prepareUndoLog异常 - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] 修复Raft模式下两阶段并发可能导致NPE的问题 +- [[#7010](https://github.com/apache/incubator-seata/pull/7010)] 修复使用达梦数据库时删除undolog发生SQL语法错误 ### optimize: @@ -77,6 +78,7 @@ - [xingfudeshi](https://github.com/xingfudeshi) - [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) +- [whaon](https://github.com/whaon) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManager.java b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManager.java index e267e832131..0ff038fa5a7 100644 --- a/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManager.java +++ b/rm-datasource/src/main/java/org/apache/seata/rm/datasource/undo/dm/DmUndoLogManager.java @@ -18,9 +18,11 @@ import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.core.compressor.CompressorType; import org.apache.seata.core.constants.ClientTableColumnsName; import org.apache.seata.rm.datasource.undo.AbstractUndoLogManager; +import org.apache.seata.rm.datasource.undo.UndoLogConstants; import org.apache.seata.rm.datasource.undo.UndoLogParser; import org.apache.seata.sqlparser.util.JdbcConstants; import org.slf4j.Logger; @@ -30,6 +32,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Date; +import java.util.Set; @LoadLevel(name = JdbcConstants.DM) @@ -37,6 +40,8 @@ public class DmUndoLogManager extends AbstractUndoLogManager { private static final Logger LOGGER = LoggerFactory.getLogger(DmUndoLogManager.class); + protected static final String DELETE_SUB_UNDO_LOG_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE \"" + + ClientTableColumnsName.UNDO_LOG_CONTEXT.toUpperCase() + "\" = ? AND " + ClientTableColumnsName.UNDO_LOG_XID + " = ?"; private static final String INSERT_UNDO_LOG_SQL = "INSERT INTO " + UNDO_LOG_TABLE_NAME + " (" + ClientTableColumnsName.UNDO_LOG_BRANCH_XID + ", " @@ -48,6 +53,88 @@ public class DmUndoLogManager extends AbstractUndoLogManager { private static final String DELETE_UNDO_LOG_BY_CREATE_SQL = "DELETE FROM " + UNDO_LOG_TABLE_NAME + " WHERE " + ClientTableColumnsName.UNDO_LOG_LOG_CREATED + " <= ? and ROWNUM <= ?"; + /** + * Delete undo log. + * + * @param xid the xid + * @param branchId the branch id + * @param conn the conn + * @throws SQLException the sql exception + */ + @Override + public void deleteUndoLog(String xid, long branchId, Connection conn) throws SQLException { + try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_SQL); + PreparedStatement deleteSubPST = conn.prepareStatement(DELETE_SUB_UNDO_LOG_SQL)) { + deletePST.setLong(1, branchId); + deletePST.setString(2, xid); + deletePST.executeUpdate(); + + deleteSubPST.setString(1, UndoLogConstants.BRANCH_ID_KEY + CollectionUtils.KV_SPLIT + branchId); + deleteSubPST.setString(2, xid); + deleteSubPST.executeUpdate(); + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + /** + * batch Delete undo log. + * + * @param xids xid + * @param branchIds branch Id + * @param conn connection + */ + @Override + public void batchDeleteUndoLog(Set xids, Set branchIds, Connection conn) throws SQLException { + if (CollectionUtils.isEmpty(xids) || CollectionUtils.isEmpty(branchIds)) { + return; + } + int xidSize = xids.size(); + int branchIdSize = branchIds.size(); + String batchDeleteSql = toBatchDeleteUndoLogSql(xidSize, branchIdSize); + String batchDeleteSubSql = toBatchDeleteSubUndoLogSql(xidSize, branchIdSize); + try (PreparedStatement deletePST = conn.prepareStatement(batchDeleteSql); + PreparedStatement deleteSubPST = conn.prepareStatement(batchDeleteSubSql)) { + int paramsIndex = 1; + for (Long branchId : branchIds) { + deletePST.setLong(paramsIndex, branchId); + deleteSubPST.setString(paramsIndex, UndoLogConstants.BRANCH_ID_KEY + CollectionUtils.KV_SPLIT + branchId); + paramsIndex++; + } + for (String xid : xids) { + deletePST.setString(paramsIndex, xid); + deleteSubPST.setString(paramsIndex, xid); + paramsIndex++; + } + int deleteRows = deletePST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete undo log size {}", deleteRows); + } + int deleteSubRows = deleteSubPST.executeUpdate(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("batch delete sub undo log size {}", deleteSubRows); + } + } catch (Exception e) { + if (!(e instanceof SQLException)) { + e = new SQLException(e); + } + throw (SQLException) e; + } + } + + protected static String toBatchDeleteSubUndoLogSql(int xidSize, int branchIdSize) { + StringBuilder sqlBuilder = new StringBuilder(64); + sqlBuilder.append("DELETE FROM ").append(UNDO_LOG_TABLE_NAME).append(" WHERE \"").append( + ClientTableColumnsName.UNDO_LOG_CONTEXT.toUpperCase()).append("\" IN "); + appendInParam(branchIdSize, sqlBuilder); + sqlBuilder.append(" AND ").append(ClientTableColumnsName.UNDO_LOG_XID).append(" IN "); + appendInParam(xidSize, sqlBuilder); + return sqlBuilder.toString(); + } + @Override public int deleteUndoLogByLogCreated(Date logCreated, int limitRows, Connection conn) throws SQLException { try (PreparedStatement deletePST = conn.prepareStatement(DELETE_UNDO_LOG_BY_CREATE_SQL)) { From 289f07d9572745b18b556c763dd6f0b16cbe914f Mon Sep 17 00:00:00 2001 From: psxjoy Date: Thu, 21 Nov 2024 16:02:20 +0800 Subject: [PATCH 46/54] bugfix: modify the properties in the raft application file (#7022) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 +- server/src/main/resources/application.raft.example.yml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 5119c31a979..3da56d73a6a 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -26,6 +26,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] fix the problem of building undoLog exception when update join does not update data - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] fix the Raft NPE issue caused by two-phase concurrency - [[#7010](https://github.com/apache/incubator-seata/pull/7010)] fix error while the "context" is key word in DM8 when delete undolog +- [[#7022](https://github.com/apache/incubator-seata/pull/7022)] fix `store.mode` property in `application.raft.example.yml` ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 6451c585363..71029fce818 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -26,7 +26,7 @@ - [[#6994](https://github.com/apache/incubator-seata/pull/6994)] 修复updateJoin语句未更新到数据时prepareUndoLog异常 - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] 修复Raft模式下两阶段并发可能导致NPE的问题 - [[#7010](https://github.com/apache/incubator-seata/pull/7010)] 修复使用达梦数据库时删除undolog发生SQL语法错误 - +- [[#7022](https://github.com/apache/incubator-seata/pull/7022)] 修复 `application.raft.example.yml`的 `store.mode`属性 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/server/src/main/resources/application.raft.example.yml b/server/src/main/resources/application.raft.example.yml index 6c96b9e9fac..61c644749a5 100644 --- a/server/src/main/resources/application.raft.example.yml +++ b/server/src/main/resources/application.raft.example.yml @@ -116,7 +116,7 @@ seata: enable-branch-async-remove: false #enable to asynchronous remove branchSession store: # support: file - mode: file + mode: raft file: dir: sessionStore max-branch-session-size: 16384 From a3fd17ca89fcb9287f7279b34a7d4a433006a6d3 Mon Sep 17 00:00:00 2001 From: YvCeung <93440108+YvCeung@users.noreply.github.com> Date: Fri, 22 Nov 2024 09:23:56 +0800 Subject: [PATCH 47/54] test: improve unit test coverage of seata-tm moudle (#7018) --- changes/en-us/2.x.md | 2 + changes/zh-cn/2.x.md | 3 + .../tm/DefaultTransactionManagerTest.java | 168 ++++++++++++++++++ .../tm/TransactionManagerHolderTest.java | 12 +- .../tm/api/FailureHandlerHolderTest.java | 35 ++++ .../seata/tm/api/MockFailureHandlerImpl.java | 39 ++++ .../transaction/MockTransactionManager.java | 49 +++++ .../SuspendedResourcesHolderTest.java | 41 +++++ 8 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 tm/src/test/java/org/apache/seata/tm/DefaultTransactionManagerTest.java create mode 100644 tm/src/test/java/org/apache/seata/tm/api/FailureHandlerHolderTest.java create mode 100644 tm/src/test/java/org/apache/seata/tm/api/MockFailureHandlerImpl.java create mode 100644 tm/src/test/java/org/apache/seata/tm/api/transaction/MockTransactionManager.java create mode 100644 tm/src/test/java/org/apache/seata/tm/api/transaction/SuspendedResourcesHolderTest.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 3da56d73a6a..7641369d363 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -57,6 +57,7 @@ Add changes here for all PR submitted to the 2.x branch. ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] Add unit tests for the `seata-rocketmq` module +- [[#7018](https://github.com/apache/incubator-seata/pull/7018)] Add unit tests for the `seata-tm` module Thanks to these contributors for their code commits. Please report an unintended omission. @@ -76,6 +77,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) - [whaon](https://github.com/whaon) +- [YvCeung](https://github.com/YvCeung) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 71029fce818..1e5bfc9be1e 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -60,6 +60,8 @@ ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] 增加`seata-rocketmq`模块的测试用例 +- [[#7018](https://github.com/apache/incubator-seata/pull/7018)] 增加 `seata-tm` 模块的测试用例 + 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 @@ -79,6 +81,7 @@ - [o-jimin](https://github.com/o-jimin) - [lixingjia77](https://github.com/lixingjia77) - [whaon](https://github.com/whaon) +- [YvCeung](https://github.com/YvCeung) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/tm/src/test/java/org/apache/seata/tm/DefaultTransactionManagerTest.java b/tm/src/test/java/org/apache/seata/tm/DefaultTransactionManagerTest.java new file mode 100644 index 00000000000..1841e56b44e --- /dev/null +++ b/tm/src/test/java/org/apache/seata/tm/DefaultTransactionManagerTest.java @@ -0,0 +1,168 @@ +/* + * 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 org.apache.seata.tm; + +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.AbstractTransactionRequest; +import org.apache.seata.core.protocol.transaction.GlobalBeginRequest; +import org.apache.seata.core.protocol.transaction.GlobalBeginResponse; +import org.apache.seata.core.protocol.transaction.GlobalCommitRequest; +import org.apache.seata.core.protocol.transaction.GlobalCommitResponse; +import org.apache.seata.core.protocol.transaction.GlobalReportRequest; +import org.apache.seata.core.protocol.transaction.GlobalReportResponse; +import org.apache.seata.core.protocol.transaction.GlobalRollbackRequest; +import org.apache.seata.core.protocol.transaction.GlobalRollbackResponse; +import org.apache.seata.core.protocol.transaction.GlobalStatusRequest; +import org.apache.seata.core.protocol.transaction.GlobalStatusResponse; +import org.apache.seata.core.rpc.netty.TmNettyRemotingClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.TimeoutException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +/** + * the type DefaultTransactionManager + */ +public class DefaultTransactionManagerTest { + + private final static String DEFAULT_XID = "1234567890"; + + private DefaultTransactionManager defaultTransactionManager; + + private MockedStatic tmNettyRemotingClientMockedStatic; + + @Mock + private TmNettyRemotingClient tmNettyRemotingClient; + + + @BeforeEach + void init() { + MockitoAnnotations.openMocks(this); + tmNettyRemotingClientMockedStatic = Mockito.mockStatic(TmNettyRemotingClient.class); + tmNettyRemotingClientMockedStatic.when(TmNettyRemotingClient::getInstance).thenReturn(tmNettyRemotingClient); + defaultTransactionManager = new DefaultTransactionManager(); + } + + @AfterEach + void destory(){ + tmNettyRemotingClientMockedStatic.close(); + } + + @Test + void testBeginSuccess() throws Exception { + GlobalBeginResponse mockResponse = new GlobalBeginResponse(); + mockResponse.setResultCode(ResultCode.Success); + mockResponse.setXid(DEFAULT_XID); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalBeginRequest.class))).thenReturn(mockResponse); + + String xid = defaultTransactionManager.begin("appId", "txGroup", "testName", 1000); + + Assertions.assertEquals("1234567890", xid); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalBeginRequest.class)); + } + + @Test + void testBeginFailure() throws Exception { + GlobalBeginResponse mockResponse = new GlobalBeginResponse(); + mockResponse.setResultCode(ResultCode.Failed); + mockResponse.setMsg("Failed to begin transaction"); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalBeginRequest.class))).thenReturn(mockResponse); + + TransactionException exception = Assertions.assertThrows(TransactionException.class, + () -> defaultTransactionManager.begin("appId", "txGroup", "testName", 1000)); + + Assertions.assertTrue(exception.getMessage().contains("Failed to begin transaction")); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalBeginRequest.class)); + } + + @Test + void testCommitSuccess() throws Exception { + GlobalCommitResponse mockResponse = new GlobalCommitResponse(); + mockResponse.setGlobalStatus(GlobalStatus.Committed); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalCommitRequest.class))).thenReturn(mockResponse); + + GlobalStatus status = defaultTransactionManager.commit(DEFAULT_XID); + + Assertions.assertEquals(GlobalStatus.Committed, status); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalCommitRequest.class)); + } + + @Test + void testRollbackSuccess() throws Exception { + GlobalRollbackResponse mockResponse = new GlobalRollbackResponse(); + mockResponse.setGlobalStatus(GlobalStatus.Rollbacked); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalRollbackRequest.class))).thenReturn(mockResponse); + + GlobalStatus status = defaultTransactionManager.rollback(DEFAULT_XID); + + Assertions.assertEquals(GlobalStatus.Rollbacked, status); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalRollbackRequest.class)); + } + + @Test + void testGetStatusSuccess() throws Exception { + GlobalStatusResponse mockResponse = new GlobalStatusResponse(); + mockResponse.setGlobalStatus(GlobalStatus.Committing); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalStatusRequest.class))).thenReturn(mockResponse); + + GlobalStatus status = defaultTransactionManager.getStatus(DEFAULT_XID); + + Assertions.assertEquals(GlobalStatus.Committing, status); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalStatusRequest.class)); + } + + @Test + void testGlobalReportSuccess() throws Exception { + GlobalReportResponse mockResponse = new GlobalReportResponse(); + mockResponse.setGlobalStatus(GlobalStatus.Committed); + + when(tmNettyRemotingClient.sendSyncRequest(any(GlobalReportRequest.class))).thenReturn(mockResponse); + + GlobalStatus status = defaultTransactionManager.globalReport(DEFAULT_XID, GlobalStatus.Committed); + + Assertions.assertEquals(GlobalStatus.Committed, status); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(GlobalReportRequest.class)); + } + + @Test + void testSyncCallTimeout() throws Exception { + when(tmNettyRemotingClient.sendSyncRequest(any(AbstractTransactionRequest.class))) + .thenThrow(new TimeoutException("Timeout occurred")); + + TransactionException exception = Assertions.assertThrows(TransactionException.class, + () -> defaultTransactionManager.getStatus(DEFAULT_XID)); + + Assertions.assertTrue(exception.getMessage().contains("RPC timeout")); + Mockito.verify(tmNettyRemotingClient).sendSyncRequest(any(AbstractTransactionRequest.class)); + } +} diff --git a/tm/src/test/java/org/apache/seata/tm/TransactionManagerHolderTest.java b/tm/src/test/java/org/apache/seata/tm/TransactionManagerHolderTest.java index 2b673e1dac8..9ee20b30738 100644 --- a/tm/src/test/java/org/apache/seata/tm/TransactionManagerHolderTest.java +++ b/tm/src/test/java/org/apache/seata/tm/TransactionManagerHolderTest.java @@ -18,10 +18,11 @@ import org.apache.seata.common.exception.ShouldNeverHappenException; +import org.apache.seata.core.model.TransactionManager; +import org.apache.seata.tm.api.transaction.MockTransactionManager; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - class TransactionManagerHolderTest { @@ -31,4 +32,13 @@ void getTest() { TransactionManagerHolder.get();}); } + + @Test + void getInstanceTest() { + MockTransactionManager mockTransactionManager = new MockTransactionManager(); + TransactionManagerHolder.set(mockTransactionManager); + TransactionManager transactionManager = TransactionManagerHolder.get(); + Assertions.assertTrue(transactionManager instanceof MockTransactionManager); + } + } diff --git a/tm/src/test/java/org/apache/seata/tm/api/FailureHandlerHolderTest.java b/tm/src/test/java/org/apache/seata/tm/api/FailureHandlerHolderTest.java new file mode 100644 index 00000000000..0d28c5277fe --- /dev/null +++ b/tm/src/test/java/org/apache/seata/tm/api/FailureHandlerHolderTest.java @@ -0,0 +1,35 @@ +/* + * 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 org.apache.seata.tm.api; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the type FailureHandlerHolder + */ +public class FailureHandlerHolderTest { + + @Test + void testSetFailureHandlerWithCustomHandler() { + MockFailureHandlerImpl mockFailureHandlerImpl = new MockFailureHandlerImpl(); + + FailureHandlerHolder.setFailureHandler(mockFailureHandlerImpl); + + Assertions.assertEquals(mockFailureHandlerImpl, FailureHandlerHolder.getFailureHandler()); + } +} diff --git a/tm/src/test/java/org/apache/seata/tm/api/MockFailureHandlerImpl.java b/tm/src/test/java/org/apache/seata/tm/api/MockFailureHandlerImpl.java new file mode 100644 index 00000000000..a99173ce2c8 --- /dev/null +++ b/tm/src/test/java/org/apache/seata/tm/api/MockFailureHandlerImpl.java @@ -0,0 +1,39 @@ +/* + * 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 org.apache.seata.tm.api; + +public class MockFailureHandlerImpl implements FailureHandler{ + @Override + public void onBeginFailure(BaseTransaction tx, Throwable cause) { + + } + + @Override + public void onCommitFailure(BaseTransaction tx, Throwable cause) { + + } + + @Override + public void onRollbackFailure(BaseTransaction tx, Throwable originalException) { + + } + + @Override + public void onRollbacking(BaseTransaction tx, Throwable originalException) { + + } +} diff --git a/tm/src/test/java/org/apache/seata/tm/api/transaction/MockTransactionManager.java b/tm/src/test/java/org/apache/seata/tm/api/transaction/MockTransactionManager.java new file mode 100644 index 00000000000..2d2d41b820f --- /dev/null +++ b/tm/src/test/java/org/apache/seata/tm/api/transaction/MockTransactionManager.java @@ -0,0 +1,49 @@ +/* + * 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 org.apache.seata.tm.api.transaction; + + +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.TransactionManager; + +public class MockTransactionManager implements TransactionManager { + @Override + public String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException { + return null; + } + + @Override + public GlobalStatus commit(String xid) throws TransactionException { + return null; + } + + @Override + public GlobalStatus rollback(String xid) throws TransactionException { + return null; + } + + @Override + public GlobalStatus getStatus(String xid) throws TransactionException { + return null; + } + + @Override + public GlobalStatus globalReport(String xid, GlobalStatus globalStatus) throws TransactionException { + return null; + } +} diff --git a/tm/src/test/java/org/apache/seata/tm/api/transaction/SuspendedResourcesHolderTest.java b/tm/src/test/java/org/apache/seata/tm/api/transaction/SuspendedResourcesHolderTest.java new file mode 100644 index 00000000000..00ad2c9b999 --- /dev/null +++ b/tm/src/test/java/org/apache/seata/tm/api/transaction/SuspendedResourcesHolderTest.java @@ -0,0 +1,41 @@ +/* + * 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 org.apache.seata.tm.api.transaction; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * the type SuspendedResourcesHolder + */ +public class SuspendedResourcesHolderTest { + + private final static String DEFAULT_XID = "1234567890"; + + @Test + void testIllegalArgumentException() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + new SuspendedResourcesHolder(null); + }); + } + + @Test + void getXidTest() { + SuspendedResourcesHolder suspendedResourcesHolder = new SuspendedResourcesHolder(DEFAULT_XID); + Assertions.assertEquals(DEFAULT_XID, suspendedResourcesHolder.getXid()); + } +} From b6f5d688159f9b6c66be108d476c75964cbec603 Mon Sep 17 00:00:00 2001 From: ggbocoder <119659920+ggbocoder@users.noreply.github.com> Date: Fri, 22 Nov 2024 14:04:11 +0800 Subject: [PATCH 48/54] bugfix: fix vGroupMappingManager is NOT init (#7025) --- changes/en-us/2.x.md | 3 ++- changes/zh-cn/2.x.md | 1 + .../server/controller/VGroupMappingController.java | 13 ++----------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 7641369d363..7033f808019 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -27,7 +27,8 @@ Add changes here for all PR submitted to the 2.x branch. - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] fix the Raft NPE issue caused by two-phase concurrency - [[#7010](https://github.com/apache/incubator-seata/pull/7010)] fix error while the "context" is key word in DM8 when delete undolog - [[#7022](https://github.com/apache/incubator-seata/pull/7022)] fix `store.mode` property in `application.raft.example.yml` - +- [[#7025](https://github.com/apache/incubator-seata/pull/7025)] fix vGroupMappingManager is NOT init +- ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] remove the branch registration operation of the XA read-only transaction - [[#6874](https://github.com/apache/incubator-seata/pull/6874)] modify the version to 2.3.0-SNAPSHOT diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 1e5bfc9be1e..93eea2911d5 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -27,6 +27,7 @@ - [[#7005](https://github.com/apache/incubator-seata/pull/7005)] 修复Raft模式下两阶段并发可能导致NPE的问题 - [[#7010](https://github.com/apache/incubator-seata/pull/7010)] 修复使用达梦数据库时删除undolog发生SQL语法错误 - [[#7022](https://github.com/apache/incubator-seata/pull/7022)] 修复 `application.raft.example.yml`的 `store.mode`属性 +- [[#7025](https://github.com/apache/incubator-seata/pull/7025)] 修复vGroupMappingManager未初始化的问题 ### optimize: - [[#6826](https://github.com/apache/incubator-seata/pull/6826)] 移除只读XA事务的分支注册操作 diff --git a/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java b/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java index 348a7502830..09c3f8c788a 100644 --- a/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java +++ b/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java @@ -44,15 +44,6 @@ public class VGroupMappingController { protected static final Configuration CONFIG = ConfigurationFactory.getInstance(); - @PostConstruct - private void init() { - String type = - ConfigurationFactory.getInstance().getConfig(FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + FILE_ROOT_TYPE); - if (StringUtils.equals(type, NAMING_SERVER)) { - vGroupMappingStoreManager = SessionHolder.getRootVGroupMappingManager(); - } - } - /** * add vGroup in cluster * @@ -67,7 +58,7 @@ public Result addVGroup(@RequestParam String vGroup, @RequestParam String uni mappingDO.setCluster(Instance.getInstance().getClusterName()); mappingDO.setUnit(unit); mappingDO.setVGroup(vGroup); - boolean rst = vGroupMappingStoreManager.addVGroup(mappingDO); + boolean rst = SessionHolder.getRootVGroupMappingManager().addVGroup(mappingDO); Instance.getInstance().setTerm(System.currentTimeMillis()); if (!rst) { result.setCode("500"); @@ -85,7 +76,7 @@ public Result addVGroup(@RequestParam String vGroup, @RequestParam String uni @GetMapping("/removeVGroup") public Result removeVGroup(@RequestParam String vGroup) { Result result = new Result<>(); - boolean rst = vGroupMappingStoreManager.removeVGroup(vGroup); + boolean rst = SessionHolder.getRootVGroupMappingManager().removeVGroup(vGroup); Instance.getInstance().setTerm(System.currentTimeMillis()); if (!rst) { result.setCode("500"); From 34990ebf172d4df4632c9cf4b01be071f6f00909 Mon Sep 17 00:00:00 2001 From: jsbxyyx Date: Mon, 25 Nov 2024 14:33:41 +0800 Subject: [PATCH 49/54] optimize: fail fast, when all server channel not available (#7023) --- changes/en-us/2.x.md | 3 ++- changes/zh-cn/2.x.md | 5 ++++- .../seata/core/rpc/netty/NettyClientChannelManager.java | 2 ++ .../seata/server/controller/VGroupMappingController.java | 8 -------- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 7033f808019..6e97f629775 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -51,7 +51,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6993](https://github.com/apache/incubator-seata/pull/6993)] optimize transaction metrics - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] upgrade outdate npmjs dependencies - [[#6996](https://github.com/apache/incubator-seata/pull/6996)] optimize lock release logic in AT transaction mode - +- [[#7023](https://github.com/apache/incubator-seata/pull/7023)] optimize fail fast, when all server not available ### refactor: @@ -79,6 +79,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [lixingjia77](https://github.com/lixingjia77) - [whaon](https://github.com/whaon) - [YvCeung](https://github.com/YvCeung) +- [jsbxyyx](https://github.com/jsbxyyx) diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 93eea2911d5..b6b2262c574 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -52,7 +52,9 @@ - [[#6991](https://github.com/apache/incubator-seata/pull/6991)] gRPC协议序列化默认值为protobuf - [[#6996](https://github.com/apache/incubator-seata/pull/6996)] 优化 AT 事务模式锁释放逻辑 - [[#6993](https://github.com/apache/incubator-seata/pull/6993)] 优化 metrics 指标 - +- [[#6995](https://github.com/apache/incubator-seata/pull/6995)] 升级过时的 npmjs 依赖 +- [[#6996](https://github.com/apache/incubator-seata/pull/6996)] 优化 AT 事务模式锁释放逻辑 +- [[#7023](https://github.com/apache/incubator-seata/pull/7023)] 优化快速失败 ### refactor: @@ -83,6 +85,7 @@ - [lixingjia77](https://github.com/lixingjia77) - [whaon](https://github.com/whaon) - [YvCeung](https://github.com/YvCeung) +- [jsbxyyx](https://github.com/jsbxyyx) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 diff --git a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java index 7be0de2e729..1ebd36fe453 100644 --- a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java +++ b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientChannelManager.java @@ -247,6 +247,8 @@ void doReconnect(List availList, String transactionServiceGroup) { FrameworkErrorCode.NetConnect.getErrCode(), key, value.getMessage(), value); }); } + } + if (availList.size() == failedMap.size()) { String invalidAddress = StringUtils.join(failedMap.keySet().iterator(), ", "); throw new FrameworkException("can not connect to [" + invalidAddress + "]"); } diff --git a/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java b/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java index 09c3f8c788a..1d55855f5d8 100644 --- a/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java +++ b/server/src/main/java/org/apache/seata/server/controller/VGroupMappingController.java @@ -18,7 +18,6 @@ import org.apache.seata.common.metadata.namingserver.Instance; import org.apache.seata.common.result.Result; -import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.Configuration; import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.core.store.MappingDO; @@ -29,13 +28,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import javax.annotation.PostConstruct; - -import static org.apache.seata.common.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR; -import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_REGISTRY; -import static org.apache.seata.common.ConfigurationKeys.FILE_ROOT_TYPE; -import static org.apache.seata.common.ConfigurationKeys.NAMING_SERVER; - @RestController @RequestMapping("/vgroup/v1") public class VGroupMappingController { From c34aa4e1fa5155682750eee8af6611d4c04ee49d Mon Sep 17 00:00:00 2001 From: funkye Date: Wed, 27 Nov 2024 14:13:05 +0800 Subject: [PATCH 50/54] optimize: raft mode maintains the reload logic consistent with the file (#7027) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 1 + .../seata/server/session/SessionHolder.java | 18 +++-- .../file/session/FileSessionManager.java | 2 +- .../raft/session/RaftSessionManager.java | 27 ++++++- .../execute/BranchSessionExecuteTest.java | 75 +++++++++---------- .../server/raft/execute/LockExecuteTest.java | 37 ++++++--- 7 files changed, 103 insertions(+), 58 deletions(-) diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 6e97f629775..c44e9f0d63b 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -52,6 +52,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] upgrade outdate npmjs dependencies - [[#6996](https://github.com/apache/incubator-seata/pull/6996)] optimize lock release logic in AT transaction mode - [[#7023](https://github.com/apache/incubator-seata/pull/7023)] optimize fail fast, when all server not available +- [[#7027](https://github.com/apache/incubator-seata/pull/7027)] raft mode maintains the reload logic consistent with the file ### refactor: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index b6b2262c574..5b6dc03fe02 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -55,6 +55,7 @@ - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] 升级过时的 npmjs 依赖 - [[#6996](https://github.com/apache/incubator-seata/pull/6996)] 优化 AT 事务模式锁释放逻辑 - [[#7023](https://github.com/apache/incubator-seata/pull/7023)] 优化快速失败 +- [[#7027](https://github.com/apache/incubator-seata/pull/7027)] raft模式下reload行为于file保持一致 ### refactor: diff --git a/server/src/main/java/org/apache/seata/server/session/SessionHolder.java b/server/src/main/java/org/apache/seata/server/session/SessionHolder.java index aa32ea001e6..cd6f6273c7e 100644 --- a/server/src/main/java/org/apache/seata/server/session/SessionHolder.java +++ b/server/src/main/java/org/apache/seata/server/session/SessionHolder.java @@ -170,6 +170,7 @@ public static void reload(Collection allSessions, SessionMode sto public static void reload(Collection allSessions, SessionMode storeMode, boolean acquireLock) { if ((SessionMode.FILE == storeMode || SessionMode.RAFT == storeMode) && CollectionUtils.isNotEmpty(allSessions)) { + long currentTimeMillis = System.currentTimeMillis(); for (GlobalSession globalSession : allSessions) { GlobalStatus globalStatus = globalSession.getStatus(); switch (globalStatus) { @@ -227,12 +228,17 @@ public static void reload(Collection allSessions, SessionMode sto break; case Begin: if (Objects.equals(storeMode, SessionMode.RAFT)) { - try { - globalSession.changeGlobalStatus(GlobalStatus.RollbackRetrying); - LOGGER.info("change global status: {}, xid: {}", globalSession.getStatus(), - globalSession.getXid()); - } catch (TransactionException e) { - LOGGER.error("change global status fail: {}", e.getMessage(), e); + // Avoid rolling back the global session created after becoming the leader. + if (globalSession.getBeginTime() < currentTimeMillis) { + try { + globalSession.changeGlobalStatus(GlobalStatus.RollbackRetrying); + LOGGER.info("change global status: {}, xid: {}", globalSession.getStatus(), + globalSession.getXid()); + } catch (TransactionException e) { + LOGGER.error("change global status fail: {}", e.getMessage(), e); + } + } else { + globalSession.setActive(true); } } else { globalSession.setActive(true); diff --git a/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java b/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java index d7a097fed29..c58f046434e 100644 --- a/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/storage/file/session/FileSessionManager.java @@ -66,7 +66,7 @@ public class FileSessionManager extends AbstractSessionManager implements Reload /** * The Session map. */ - private Map sessionMap = new ConcurrentHashMap<>(64); + protected Map sessionMap = new ConcurrentHashMap<>(64); /** diff --git a/server/src/main/java/org/apache/seata/server/storage/raft/session/RaftSessionManager.java b/server/src/main/java/org/apache/seata/server/storage/raft/session/RaftSessionManager.java index b59c8d34ad8..32d56c8e670 100644 --- a/server/src/main/java/org/apache/seata/server/storage/raft/session/RaftSessionManager.java +++ b/server/src/main/java/org/apache/seata/server/storage/raft/session/RaftSessionManager.java @@ -17,10 +17,12 @@ package org.apache.seata.server.storage.raft.session; import java.io.IOException; +import java.util.List; import java.util.concurrent.CompletableFuture; import com.alipay.sofa.jraft.Closure; import org.apache.seata.common.loader.LoadLevel; import org.apache.seata.common.loader.Scope; +import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.exception.TransactionExceptionCode; import org.apache.seata.core.model.BranchStatus; @@ -80,7 +82,7 @@ public void onBegin(GlobalSession globalSession) throws TransactionException { "seata raft state machine exception: " + status.getErrorMsg())); } finally { try { - super.removeGlobalSession(globalSession); + removeGlobalSession(globalSession); } catch (TransactionException e) { completableFuture.completeExceptionally(e); } @@ -93,6 +95,23 @@ public void onBegin(GlobalSession globalSession) throws TransactionException { RaftTaskUtil.createTask(closure, raftSyncMsg, completableFuture); } + @Override + public void removeGlobalSession(GlobalSession session) throws TransactionException { + GlobalSession globalSession = sessionMap.remove(session.getXid()); + if (globalSession != null) { + List branchSessionList = globalSession.getBranchSessions(); + // For the follower, the following code will not be executed because when the follower receives the remove global session + // the branch session on the leader side has already been completely cleared. + if (CollectionUtils.isNotEmpty(branchSessionList)) { + for (BranchSession branchSession : branchSessionList) { + branchSession.unlock(); + onRemoveBranch(globalSession, branchSession); + } + end(globalSession); + } + } + } + @Override public void onStatusChange(GlobalSession globalSession, GlobalStatus globalStatus) throws TransactionException { CompletableFuture completableFuture = new CompletableFuture<>(); @@ -187,11 +206,15 @@ public void onRemoveBranch(GlobalSession globalSession, BranchSession branchSess @Override public void onSuccessEnd(GlobalSession globalSession) throws TransactionException { + end(globalSession); + } + + public void end(GlobalSession globalSession) throws TransactionException { CompletableFuture completableFuture = new CompletableFuture<>(); Closure closure = status -> { if (status.isOk()) { try { - super.removeGlobalSession(globalSession); + removeGlobalSession(globalSession); completableFuture.complete(true); } catch (TransactionException e) { completableFuture.completeExceptionally(e); diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java index ade3a25ef9c..7c499f410ce 100644 --- a/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/execute/BranchSessionExecuteTest.java @@ -16,7 +16,11 @@ */ package org.apache.seata.server.raft.execute; +import java.util.ArrayList; +import java.util.List; +import org.apache.seata.common.XID; import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.config.ConfigurationCache; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchStatus; @@ -33,10 +37,8 @@ import org.apache.seata.server.storage.SessionConverter; import org.apache.seata.server.store.StoreConfig; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; @@ -51,8 +53,6 @@ class BranchSessionExecuteTest { private static GlobalSession GLOBAL_SESSION; - private static final String XID = "123:123"; - private static final long BRANCH_ID = 0L; @BeforeAll @@ -61,10 +61,14 @@ public static void setUp(ApplicationContext context) throws TransactionException SessionHolder.init(StoreConfig.SessionMode.RAFT); LockerManagerFactory.destroy(); LockerManagerFactory.init(StoreConfig.LockMode.RAFT); + GLOBAL_SESSION = mockGlobalSession(); + SessionHolder.getRootSessionManager().addGlobalSession(GLOBAL_SESSION); } @AfterAll - public static void destroy() throws TransactionException { + public static void destroy() throws Throwable { + testRemove(); + SessionHolder.getRootSessionManager().removeGlobalSession(GLOBAL_SESSION); // Clear configuration ConfigurationCache.clear(); System.clearProperty("server.raft.serverAddr"); @@ -75,75 +79,66 @@ public static void destroy() throws TransactionException { LockerManagerFactory.destroy(); } - @BeforeEach - public void addGlobalSession() throws TransactionException { - GLOBAL_SESSION = mockGlobalSession(); - SessionHolder.getRootSessionManager().addGlobalSession(GLOBAL_SESSION); - } - @AfterEach - public void removeTestSession() throws TransactionException { - SessionHolder.getRootSessionManager().removeGlobalSession(GLOBAL_SESSION); - } @Test public void testAdd() throws Throwable { - BranchSession expected = mockBranchSession(); + BranchSession expected = mockBranchSession(GLOBAL_SESSION.getXid(), GLOBAL_SESSION.getTransactionId()); AddBranchSessionExecute execute = new AddBranchSessionExecute(); boolean success = execute.execute(convertToBranchSessionMsg(expected)); Assertions.assertTrue(success); - BranchSession branchSession = GLOBAL_SESSION.getBranch(BRANCH_ID); + BranchSession branchSession = GLOBAL_SESSION.getBranch(expected.getBranchId()); assertBranchSessionValid(expected, branchSession); } - @Test - public void testRemove() throws Throwable { - GLOBAL_SESSION.add(mockBranchSession()); - - BranchSession branchSession = GLOBAL_SESSION.getBranch(BRANCH_ID); - Assertions.assertNotNull(branchSession); + public static void testRemove() throws Throwable { + List list = new ArrayList<>(GLOBAL_SESSION.getBranchSessions()); + for (BranchSession branchSession : list) { + Assertions.assertNotNull(branchSession); - RemoveBranchSessionExecute execute = new RemoveBranchSessionExecute(); - boolean success = execute.execute(convertToBranchSessionMsg(branchSession)); - Assertions.assertTrue(success); + RemoveBranchSessionExecute execute = new RemoveBranchSessionExecute(); + boolean success = execute.execute(convertToBranchSessionMsg(branchSession)); + Assertions.assertTrue(success); - branchSession = GLOBAL_SESSION.getBranch(BRANCH_ID); - Assertions.assertNull(branchSession); + branchSession = GLOBAL_SESSION.getBranch(branchSession.getBranchId()); + Assertions.assertNull(branchSession); + } } @Test public void testUpdate() throws Throwable { - GLOBAL_SESSION.add(mockBranchSession()); + BranchSession branchSession = mockBranchSession(GLOBAL_SESSION.getXid(), GLOBAL_SESSION.getTransactionId()); + GLOBAL_SESSION.add(branchSession); - BranchSession branchSession = GLOBAL_SESSION.getBranch(BRANCH_ID); + branchSession = GLOBAL_SESSION.getBranch(branchSession.getBranchId()); Assertions.assertNotNull(branchSession); - BranchSession expected = mockBranchSession(); - expected.setStatus(BranchStatus.PhaseTwo_Committed); + branchSession.setStatus(BranchStatus.PhaseTwo_Committed); UpdateBranchSessionExecute execute = new UpdateBranchSessionExecute(); - boolean success = execute.execute(convertToBranchSessionMsg(expected)); + boolean success = execute.execute(convertToBranchSessionMsg(branchSession)); Assertions.assertTrue(success); - branchSession = GLOBAL_SESSION.getBranch(BRANCH_ID); - assertBranchSessionValid(expected, branchSession); + branchSession = GLOBAL_SESSION.getBranch(branchSession.getBranchId()); + assertBranchSessionValid(branchSession, branchSession); } private static GlobalSession mockGlobalSession() { + long txId = UUIDGenerator.generateUUID(); GlobalSession session = new GlobalSession("test", "test", "test", 5000); - session.setXid(XID); + session.setXid(XID.generateXID(txId)); session.setApplicationData("hello, world"); - session.setTransactionId(123); + session.setTransactionId(txId); session.setBeginTime(System.currentTimeMillis()); return session; } - private static BranchSession mockBranchSession() { + private static BranchSession mockBranchSession(String xid,long transactionId) { BranchSession session = new BranchSession(); - session.setXid(XID); - session.setTransactionId(123); - session.setBranchId(BRANCH_ID); + session.setXid(xid); + session.setTransactionId(transactionId); + session.setBranchId(UUIDGenerator.generateUUID()); session.setClientId("client"); session.setResourceGroupId(DEFAULT_TX_GROUP); session.setResourceId("resource"); diff --git a/server/src/test/java/org/apache/seata/server/raft/execute/LockExecuteTest.java b/server/src/test/java/org/apache/seata/server/raft/execute/LockExecuteTest.java index 36cbbaf0d8a..50aece25893 100644 --- a/server/src/test/java/org/apache/seata/server/raft/execute/LockExecuteTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/execute/LockExecuteTest.java @@ -16,10 +16,14 @@ */ package org.apache.seata.server.raft.execute; +import java.util.ArrayList; +import java.util.List; import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.config.ConfigurationCache; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchType; +import org.apache.seata.server.cluster.raft.execute.branch.RemoveBranchSessionExecute; import org.apache.seata.server.cluster.raft.execute.lock.BranchReleaseLockExecute; import org.apache.seata.server.cluster.raft.execute.lock.GlobalReleaseLockExecute; import org.apache.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; @@ -83,14 +87,29 @@ public void addGlobalSession() throws TransactionException { } @AfterEach - public void removeTestSession() throws TransactionException { + public void removeTestSession() throws Throwable { + testRemove(); SessionHolder.getRootSessionManager().removeGlobalSession(GLOBAL_SESSION); } + public void testRemove() throws Throwable { + List list = new ArrayList<>(GLOBAL_SESSION.getBranchSessions()); + for (BranchSession branchSession : list) { + Assertions.assertNotNull(branchSession); + + RemoveBranchSessionExecute execute = new RemoveBranchSessionExecute(); + boolean success = execute.execute(convertToBranchSessionMsg(branchSession)); + Assertions.assertTrue(success); + + branchSession = GLOBAL_SESSION.getBranch(branchSession.getBranchId()); + Assertions.assertNull(branchSession); + } + } + @Test public void testGlobalRelease() throws Throwable { - BranchSession branchSession1 = mockBranchSession("test:0"); - BranchSession branchSession2 = mockBranchSession("test:1"); + BranchSession branchSession1 = mockBranchSession(GLOBAL_SESSION.getXid(),GLOBAL_SESSION.getTransactionId(),"t1:53"); + BranchSession branchSession2 = mockBranchSession(GLOBAL_SESSION.getXid(),GLOBAL_SESSION.getTransactionId(),"t1:54"); GLOBAL_SESSION.add(branchSession1); GLOBAL_SESSION.add(branchSession2); @@ -109,7 +128,7 @@ public void testGlobalRelease() throws Throwable { @Test public void testBranchRelease() throws Throwable { - BranchSession branchSession = mockBranchSession("test:0"); + BranchSession branchSession = mockBranchSession(GLOBAL_SESSION.getXid(),GLOBAL_SESSION.getTransactionId(),"t1:55"); GLOBAL_SESSION.add(branchSession); LockManager lockerManager = LockerManagerFactory.getLockManager(); @@ -131,14 +150,14 @@ private static GlobalSession mockGlobalSession() { return session; } - private static BranchSession mockBranchSession(String lockKey) { + private static BranchSession mockBranchSession(String xid,long transactionId,String lockKey) { BranchSession session = new BranchSession(); - session.setXid(XID); - session.setTransactionId(123); - session.setBranchId(BRANCH_ID); + session.setXid(xid); + session.setTransactionId(transactionId); + session.setBranchId(UUIDGenerator.generateUUID()); session.setClientId("client"); session.setResourceGroupId(DEFAULT_TX_GROUP); - session.setResourceId("db"); + session.setResourceId("resource"); session.setLockKey(lockKey); session.setBranchType(BranchType.AT); session.setApplicationData("hello, world"); From 5fe6da6d9514492270219b6f935b4ef1214948ed Mon Sep 17 00:00:00 2001 From: jimin Date: Wed, 27 Nov 2024 16:48:50 +0800 Subject: [PATCH 51/54] optimize: add mysql to docker image (#7028) --- build/pom.xml | 1 + pom.xml | 1 + server/pom.xml | 50 +++++++++++++++++++++++++------------------------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/build/pom.xml b/build/pom.xml index d54230f995d..c8e53dd9a14 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -147,6 +147,7 @@ latest true + true diff --git a/pom.xml b/pom.xml index 958e9d497b1..ce4c0fa5848 100644 --- a/pom.xml +++ b/pom.xml @@ -181,6 +181,7 @@ false false + false 5.1.42 8.0.27 false diff --git a/server/pom.xml b/server/pom.xml index 63d34056a5d..5ba3e8dd9e9 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -362,31 +362,31 @@ ${dependencies.copy.skip} - - - - - - - - - - - - - - - - - - - - - - - - - + + copy-mysql + package + + copy + + + + + mysql + mysql-connector-java + ${mysql.jdbc.version} + + + mysql + mysql-connector-java + ${mysql8.jdbc.version} + + + + ${project.build.directory}/lib/jdbc + + ${mysql.copy.skip} + + From 556a7347ce46b130a1f544aa4a7ebaf9888c633f Mon Sep 17 00:00:00 2001 From: jimin Date: Thu, 28 Nov 2024 13:02:22 +0800 Subject: [PATCH 52/54] reactor: remove dependency on seata-server module (#7017) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 5 +- dependencies/pom.xml | 6 +- integration/grpc/pom.xml | 2 +- {test-mock-server => mock-server}/pom.xml | 56 +++- .../seata/mockserver/MockCoordinator.java | 173 ++++++++---- .../mockserver/MockNettyRemotingServer.java | 17 +- .../apache/seata/mockserver/MockServer.java | 12 +- .../apache/seata/mockserver/call/CallRm.java | 24 +- .../controller/MockHelpController.java | 0 .../mockserver/model/MockBranchSession.java | 234 +++++++++++++++ .../mockserver/model/MockGlobalSession.java | 266 ++++++++++++++++++ .../processor/MockHeartbeatProcessor.java | 0 .../processor/MockOnReqProcessor.java | 0 .../processor/MockOnRespProcessor.java | 0 .../processor/MockRegisterProcessor.java | 0 .../processor/MockRemotingProcessor.java | 1 - pom.xml | 2 +- .../src/main/resources/application.yml | 57 ---- .../src/main/resources/logback-spring.xml | 112 -------- .../src/test/java/io/seata/MockTest.java | 1 - .../io/seata/core/rpc/netty/Action1Impl.java | 19 +- .../io/seata/core/rpc/netty/TmClientTest.java | 4 - .../src/test/resources/registry.conf | 60 ---- test/pom.xml | 2 +- 25 files changed, 715 insertions(+), 339 deletions(-) rename {test-mock-server => mock-server}/pom.xml (76%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/MockCoordinator.java (61%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java (90%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/MockServer.java (90%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/call/CallRm.java (85%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java (100%) create mode 100644 mock-server/src/main/java/org/apache/seata/mockserver/model/MockBranchSession.java create mode 100644 mock-server/src/main/java/org/apache/seata/mockserver/model/MockGlobalSession.java rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/processor/MockHeartbeatProcessor.java (100%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/processor/MockOnReqProcessor.java (100%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/processor/MockOnRespProcessor.java (100%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java (100%) rename {test-mock-server => mock-server}/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java (99%) delete mode 100644 test-mock-server/src/main/resources/application.yml delete mode 100644 test-mock-server/src/main/resources/logback-spring.xml diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index c44e9f0d63b..d8de284eb89 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -55,6 +55,7 @@ Add changes here for all PR submitted to the 2.x branch. - [[#7027](https://github.com/apache/incubator-seata/pull/7027)] raft mode maintains the reload logic consistent with the file ### refactor: +- [[#7017](https://github.com/apache/incubator-seata/pull/7017)] remove dependency on seata-server module ### test: diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index 5b6dc03fe02..c64a1ec09c7 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -55,13 +55,12 @@ - [[#6995](https://github.com/apache/incubator-seata/pull/6995)] 升级过时的 npmjs 依赖 - [[#6996](https://github.com/apache/incubator-seata/pull/6996)] 优化 AT 事务模式锁释放逻辑 - [[#7023](https://github.com/apache/incubator-seata/pull/7023)] 优化快速失败 -- [[#7027](https://github.com/apache/incubator-seata/pull/7027)] raft模式下reload行为于file保持一致 +- [[#7027](https://github.com/apache/incubator-seata/pull/7027)] raft模式下reload行为与file保持一致 ### refactor: +- [[#7017](https://github.com/apache/incubator-seata/pull/7017)] 移除 seata-server 模块的依赖 -### security: - ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] 增加`seata-rocketmq`模块的测试用例 - [[#7018](https://github.com/apache/incubator-seata/pull/7018)] 增加 `seata-tm` 模块的测试用例 diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 6b93148e613..cd83dac7f9d 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -77,7 +77,7 @@ 4.0.3 1.6.7 3.25.4 - 1.66.0 + 1.55.1 5.4.0 0.45 4.0.63 @@ -529,6 +529,10 @@ com.google.guava guava + + io.grpc + grpc-core + diff --git a/integration/grpc/pom.xml b/integration/grpc/pom.xml index 04665dcc956..f3b6101fc77 100644 --- a/integration/grpc/pom.xml +++ b/integration/grpc/pom.xml @@ -84,7 +84,7 @@ com.google.protobuf:protoc:3.25.4:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.55.1:exe:${os.detected.classifier} diff --git a/test-mock-server/pom.xml b/mock-server/pom.xml similarity index 76% rename from test-mock-server/pom.xml rename to mock-server/pom.xml index 2c6eb9e4198..e74c5fc37a6 100644 --- a/test-mock-server/pom.xml +++ b/mock-server/pom.xml @@ -107,6 +107,32 @@ + + org.springframework.boot + spring-boot-starter-web + + + log4j-to-slf4j + org.apache.logging.log4j + + + org.apache.tomcat.embed + tomcat-embed-core + + + org.apache.tomcat.embed + tomcat-embed-websocket + + + org.apache.tomcat.embed + tomcat-embed-el + + + org.yaml + snakeyaml + + + org.apache.tomcat.embed tomcat-embed-core @@ -122,21 +148,25 @@ tomcat-embed-websocket ${tomcat-embed.version} - - org.apache.seata - seata-server + ${project.groupId} + seata-core + ${project.version} + + + ${project.groupId} + seata-discovery-all + ${project.version} + + + ${project.groupId} + seata-serializer-all + ${project.version} + + + ${project.groupId} + seata-compressor-all ${project.version} - - - slf4j-log4j12 - org.slf4j - - - log4j - log4j - - diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java b/mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java similarity index 61% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java rename to mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java index 181e824b15e..180a15e70c5 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java +++ b/mock-server/src/main/java/org/apache/seata/mockserver/MockCoordinator.java @@ -23,6 +23,7 @@ import java.util.stream.IntStream; import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.UUIDGenerator; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.exception.TransactionExceptionCode; import org.apache.seata.core.model.BranchStatus; @@ -30,7 +31,9 @@ import org.apache.seata.core.protocol.AbstractMessage; import org.apache.seata.core.protocol.AbstractResultMessage; import org.apache.seata.core.protocol.ResultCode; +import org.apache.seata.core.protocol.transaction.AbstractGlobalEndResponse; import org.apache.seata.core.protocol.transaction.AbstractTransactionRequestToTC; +import org.apache.seata.core.protocol.transaction.AbstractTransactionResponse; import org.apache.seata.core.protocol.transaction.BranchRegisterRequest; import org.apache.seata.core.protocol.transaction.BranchRegisterResponse; import org.apache.seata.core.protocol.transaction.BranchReportRequest; @@ -47,22 +50,21 @@ import org.apache.seata.core.protocol.transaction.GlobalRollbackResponse; import org.apache.seata.core.protocol.transaction.GlobalStatusRequest; import org.apache.seata.core.protocol.transaction.GlobalStatusResponse; +import org.apache.seata.core.protocol.transaction.TCInboundHandler; import org.apache.seata.core.rpc.Disposable; import org.apache.seata.core.rpc.RemotingServer; import org.apache.seata.core.rpc.RpcContext; import org.apache.seata.core.rpc.TransactionMessageHandler; import org.apache.seata.mockserver.call.CallRm; -import org.apache.seata.server.AbstractTCInboundHandler; -import org.apache.seata.common.util.UUIDGenerator; -import org.apache.seata.server.session.BranchSession; -import org.apache.seata.server.session.GlobalSession; +import org.apache.seata.mockserver.model.MockBranchSession; +import org.apache.seata.mockserver.model.MockGlobalSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Mock Coordinator **/ -public class MockCoordinator extends AbstractTCInboundHandler implements TransactionMessageHandler, Disposable { +public class MockCoordinator implements TCInboundHandler, TransactionMessageHandler, Disposable { protected static final Logger LOGGER = LoggerFactory.getLogger(MockCoordinator.class); @@ -75,12 +77,11 @@ public class MockCoordinator extends AbstractTCInboundHandler implements Transac private Map globalStatusMap; private Map expectedResultMap; private Map expectRetryTimesMap; - private Map> branchMap; + private Map> branchMap; private MockCoordinator() { } - public static MockCoordinator getInstance() { if (coordinator == null) { synchronized (MockCoordinator.class) { @@ -96,7 +97,6 @@ public static MockCoordinator getInstance() { return coordinator; } - @Override public void destroy() { @@ -107,7 +107,7 @@ public AbstractResultMessage onRequest(AbstractMessage request, RpcContext conte if (!(request instanceof AbstractTransactionRequestToTC)) { throw new IllegalArgumentException(); } - AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request; + AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC)request; transactionRequest.setTCInboundHandler(this); return transactionRequest.handle(context); @@ -118,68 +118,121 @@ public void onResponse(AbstractResultMessage response, RpcContext context) { response.setResultCode(ResultCode.Success); } + public void setRemotingServer(RemotingServer remotingServer) { + this.remotingServer = remotingServer; + } + + public void setExpectedResult(String xid, ResultCode expected) { + expectedResultMap.put(xid, expected); + } + + public void setExpectedRetry(String xid, int times) { + expectRetryTimesMap.put(xid, times); + } + + private void checkMockActionFail(String xid) throws TransactionException { + if (ResultCode.Failed == expectedResultMap.get(xid)) { + throw new TransactionException(TransactionExceptionCode.Broken, "mock action expect fail"); + } + } + + private T handleException(TransactionException e, T response, + ResultCode resultCode, String messagePrefix, + GlobalStatus... globalStatus) { + response.setTransactionExceptionCode(e.getCode()); + response.setResultCode(resultCode); + response.setMsg(messagePrefix + "[" + e.getMessage() + "]"); + if (response instanceof AbstractGlobalEndResponse) { + if (globalStatus != null && globalStatus.length > 0) { + ((AbstractGlobalEndResponse)response).setGlobalStatus(globalStatus[0]); + } + } + return response; + } + @Override - protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(AllBeginFailXid); - GlobalSession session = GlobalSession.createGlobalSession(rpcContext.getApplicationId(), - rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout()); + public GlobalBeginResponse handle(GlobalBeginRequest request, RpcContext rpcContext) { + GlobalBeginResponse response = new GlobalBeginResponse(); + try { + checkMockActionFail(AllBeginFailXid); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockBeginException"); + } + MockGlobalSession session = new MockGlobalSession(rpcContext.getApplicationId(), + rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout()); globalStatusMap.putIfAbsent(session.getXid(), GlobalStatus.Begin); response.setXid(session.getXid()); response.setResultCode(ResultCode.Success); + return response; } - @Override - protected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public GlobalCommitResponse handle(GlobalCommitRequest request, RpcContext rpcContext) { + GlobalCommitResponse response = new GlobalCommitResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockCommitException", GlobalStatus.CommitFailed); + } response.setGlobalStatus(GlobalStatus.Committed); response.setResultCode(ResultCode.Success); globalStatusMap.put(request.getXid(), GlobalStatus.Committed); int retry = expectRetryTimesMap.getOrDefault(request.getXid(), 0); - List branchSessions = branchMap.get(request.getXid()); + List branchSessions = branchMap.get(request.getXid()); if (CollectionUtils.isEmpty(branchSessions)) { LOGGER.warn("[doGlobalCommit]branchSessions is empty,XID=" + request.getXid()); - return; + return response; } branchSessions.forEach(branch -> { CallRm.branchCommit(remotingServer, branch); - IntStream.range(0, retry).forEach(i -> - CallRm.branchCommit(remotingServer, branch)); + IntStream.range(0, retry).forEach(i -> CallRm.branchCommit(remotingServer, branch)); }); branchMap.remove(request.getXid()); globalStatusMap.remove(request.getXid()); + return response; } @Override - protected void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public GlobalRollbackResponse handle(GlobalRollbackRequest request, RpcContext rpcContext) { + GlobalRollbackResponse response = new GlobalRollbackResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockRollbackException", + GlobalStatus.RollbackFailed); + } response.setGlobalStatus(GlobalStatus.Rollbacked); response.setResultCode(ResultCode.Success); globalStatusMap.put(request.getXid(), GlobalStatus.Rollbacked); int retry = expectRetryTimesMap.getOrDefault(request.getXid(), 0); - List branchSessions = branchMap.get(request.getXid()); + List branchSessions = branchMap.get(request.getXid()); if (CollectionUtils.isEmpty(branchSessions)) { LOGGER.warn("[doGlobalRollback]branchSessions is empty,XID=" + request.getXid()); - return; + return response; } branchSessions.forEach(branch -> { CallRm.branchRollback(remotingServer, branch); - IntStream.range(0, retry).forEach(i -> - CallRm.branchRollback(remotingServer, branch)); + IntStream.range(0, retry).forEach(i -> CallRm.branchRollback(remotingServer, branch)); }); branchMap.remove(request.getXid()); globalStatusMap.remove(request.getXid()); + return response; } @Override - protected void doBranchRegister(BranchRegisterRequest request, BranchRegisterResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); - BranchSession branchSession = new BranchSession(request.getBranchType()); + public BranchRegisterResponse handle(BranchRegisterRequest request, RpcContext rpcContext) { + BranchRegisterResponse response = new BranchRegisterResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockBranchRegisterException"); + } + MockBranchSession branchSession = new MockBranchSession(request.getBranchType()); String xid = request.getXid(); branchSession.setXid(xid); -// branchSession.setTransactionId(request.getTransactionId()); + // branchSession.setTransactionId(request.getTransactionId()); branchSession.setBranchId(UUIDGenerator.generateUUID()); branchSession.setResourceId(request.getResourceId()); branchSession.setLockKey(request.getLockKey()); @@ -196,11 +249,17 @@ protected void doBranchRegister(BranchRegisterRequest request, BranchRegisterRes response.setBranchId(branchSession.getBranchId()); response.setResultCode(ResultCode.Success); + return response; } @Override - protected void doBranchReport(BranchReportRequest request, BranchReportResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public BranchReportResponse handle(BranchReportRequest request, RpcContext rpcContext) { + BranchReportResponse response = new BranchReportResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockBranchReportException"); + } String xid = request.getXid(); branchMap.compute(xid, (key, val) -> { if (val != null) { @@ -211,51 +270,51 @@ protected void doBranchReport(BranchReportRequest request, BranchReportResponse return val; }); response.setResultCode(ResultCode.Success); + return response; } @Override - protected void doLockCheck(GlobalLockQueryRequest request, GlobalLockQueryResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public GlobalLockQueryResponse handle(GlobalLockQueryRequest request, RpcContext rpcContext) { + GlobalLockQueryResponse response = new GlobalLockQueryResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockLockQueryException"); + } response.setLockable(true); response.setResultCode(ResultCode.Success); + return response; } @Override - protected void doGlobalStatus(GlobalStatusRequest request, GlobalStatusResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public GlobalStatusResponse handle(GlobalStatusRequest request, RpcContext rpcContext) { + GlobalStatusResponse response = new GlobalStatusResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockStatusException", GlobalStatus.Finished); + } GlobalStatus globalStatus = globalStatusMap.get(request.getXid()); if (globalStatus == null) { globalStatus = GlobalStatus.Finished; } response.setGlobalStatus(globalStatus); response.setResultCode(ResultCode.Success); + return response; } @Override - protected void doGlobalReport(GlobalReportRequest request, GlobalReportResponse response, RpcContext rpcContext) throws TransactionException { - checkMockActionFail(request.getXid()); + public GlobalReportResponse handle(GlobalReportRequest request, RpcContext rpcContext) { + GlobalReportResponse response = new GlobalReportResponse(); + try { + checkMockActionFail(request.getXid()); + } catch (TransactionException e) { + return handleException(e, response, ResultCode.Failed, "MockReportException", GlobalStatus.Finished); + } GlobalStatus globalStatus = request.getGlobalStatus(); globalStatusMap.put(request.getXid(), globalStatus); response.setGlobalStatus(globalStatus); response.setResultCode(ResultCode.Success); - } - - public void setRemotingServer(RemotingServer remotingServer) { - this.remotingServer = remotingServer; - } - - - public void setExpectedResult(String xid, ResultCode expected) { - expectedResultMap.put(xid, expected); - } - - public void setExpectedRetry(String xid, int times) { - expectRetryTimesMap.put(xid, times); - } - - private void checkMockActionFail(String xid) throws TransactionException { - if (ResultCode.Failed == expectedResultMap.get(xid)) { - throw new TransactionException(TransactionExceptionCode.Broken, "mock action expect fail"); - } + return response; } } diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java b/mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java similarity index 90% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java rename to mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java index 3a871cb93e0..1ecc5bbe2d8 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java +++ b/mock-server/src/main/java/org/apache/seata/mockserver/MockNettyRemotingServer.java @@ -39,6 +39,11 @@ public class MockNettyRemotingServer extends AbstractNettyRemotingServer { private TransactionMessageHandler handler; + /** + * Sets handler. + * + * @param transactionMessageHandler the transaction message handler + */ public void setHandler(TransactionMessageHandler transactionMessageHandler) { this.handler = transactionMessageHandler; } @@ -56,7 +61,17 @@ public void init() { * @param messageExecutor the message executor */ public MockNettyRemotingServer(ThreadPoolExecutor messageExecutor) { - super(messageExecutor, new NettyServerConfig()); + this(messageExecutor, new NettyServerConfig()); + } + + /** + * Instantiates a new Mock netty remoting server. + * + * @param messageExecutor the message executor + * @param nettyServerConfig the netty server config + */ + public MockNettyRemotingServer(ThreadPoolExecutor messageExecutor, NettyServerConfig nettyServerConfig) { + super(messageExecutor, nettyServerConfig); } @Override diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java b/mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java similarity index 90% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java rename to mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java index 535d6d3dd06..fb40440d02c 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java +++ b/mock-server/src/main/java/org/apache/seata/mockserver/MockServer.java @@ -24,13 +24,16 @@ import org.apache.seata.common.XID; import org.apache.seata.common.thread.NamedThreadFactory; import org.apache.seata.common.util.NetUtil; -import org.apache.seata.server.ParameterParser; +import org.apache.seata.common.util.NumberUtils; import org.apache.seata.common.util.UUIDGenerator; +import org.apache.seata.core.rpc.netty.NettyServerConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import static org.apache.seata.common.ConfigurationKeys.ENV_SEATA_PORT_KEY; + /** * The type Mock Server. */ @@ -54,8 +57,7 @@ public class MockServer { public static void main(String[] args) { SpringApplication.run(MockServer.class, args); - ParameterParser parameterParser = new ParameterParser(args); - int port = parameterParser.getPort() > 0 ? parameterParser.getPort() : DEFAULT_PORT; + int port = NumberUtils.toInt(System.getenv(ENV_SEATA_PORT_KEY), DEFAULT_PORT); start(port); } @@ -68,7 +70,9 @@ public static void start(int port) { 50, 500, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20000), new NamedThreadFactory("ServerHandlerThread", 500), new ThreadPoolExecutor.CallerRunsPolicy()); - nettyRemotingServer = new MockNettyRemotingServer(workingThreads); + NettyServerConfig config = new NettyServerConfig(); + config.setServerListenPort(port); + nettyRemotingServer = new MockNettyRemotingServer(workingThreads, config); // set registry XID.setIpAddress(NetUtil.getLocalIp()); diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java b/mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java similarity index 85% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java rename to mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java index ffee3c7f969..8a347246f33 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java +++ b/mock-server/src/main/java/org/apache/seata/mockserver/call/CallRm.java @@ -29,7 +29,7 @@ import org.apache.seata.core.protocol.transaction.UndoLogDeleteRequest; import org.apache.seata.core.rpc.RemotingServer; import org.apache.seata.core.rpc.netty.ChannelManager; -import org.apache.seata.server.session.BranchSession; +import org.apache.seata.mockserver.model.MockBranchSession; /** * call rm @@ -42,32 +42,31 @@ public class CallRm { * @param remotingServer * @return */ - public static BranchStatus branchCommit(RemotingServer remotingServer, BranchSession branchSession) { + public static BranchStatus branchCommit(RemotingServer remotingServer, MockBranchSession branchSession) { BranchCommitRequest request = new BranchCommitRequest(); setReq(request, branchSession); try { - BranchCommitResponse response = (BranchCommitResponse) remotingServer.sendSyncRequest( - branchSession.getResourceId(), branchSession.getClientId(), request, false); + BranchCommitResponse response = (BranchCommitResponse)remotingServer.sendSyncRequest( + branchSession.getResourceId(), branchSession.getClientId(), request, false); return response.getBranchStatus(); } catch (TimeoutException e) { throw new RuntimeException(e); } } - /** * call branchRollback :TYPE_BRANCH_ROLLBACK = 5 , TYPE_BRANCH_ROLLBACK_RESULT = 6 * * @param remotingServer * @return */ - public static BranchStatus branchRollback(RemotingServer remotingServer, BranchSession branchSession) { + public static BranchStatus branchRollback(RemotingServer remotingServer, MockBranchSession branchSession) { BranchRollbackRequest request = new BranchRollbackRequest(); setReq(request, branchSession); try { - BranchRollbackResponse response = (BranchRollbackResponse) remotingServer.sendSyncRequest( - branchSession.getResourceId(), branchSession.getClientId(), request, false); + BranchRollbackResponse response = (BranchRollbackResponse)remotingServer.sendSyncRequest( + branchSession.getResourceId(), branchSession.getClientId(), request, false); return response.getBranchStatus(); } catch (TimeoutException e) { throw new RuntimeException(e); @@ -80,20 +79,21 @@ public static BranchStatus branchRollback(RemotingServer remotingServer, Branch * @param remotingServer * @return */ - public static void deleteUndoLog(RemotingServer remotingServer, BranchSession branchSession) { + public static void deleteUndoLog(RemotingServer remotingServer, MockBranchSession branchSession) { UndoLogDeleteRequest request = new UndoLogDeleteRequest(); request.setResourceId(branchSession.getResourceId()); - request.setSaveDays((short) 1); + request.setSaveDays((short)1); request.setBranchType(BranchType.TCC); try { - Channel channel = ChannelManager.getChannel(branchSession.getResourceId(), branchSession.getClientId(), false); + Channel channel = ChannelManager.getChannel(branchSession.getResourceId(), branchSession.getClientId(), + false); remotingServer.sendAsyncRequest(channel, request); } catch (Exception e) { throw new RuntimeException(e); } } - private static void setReq(AbstractBranchEndRequest request, BranchSession branchSession) { + private static void setReq(AbstractBranchEndRequest request, MockBranchSession branchSession) { request.setXid(branchSession.getXid()); request.setBranchId(branchSession.getBranchId()); request.setResourceId(branchSession.getResourceId()); diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java b/mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java similarity index 100% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java rename to mock-server/src/main/java/org/apache/seata/mockserver/controller/MockHelpController.java diff --git a/mock-server/src/main/java/org/apache/seata/mockserver/model/MockBranchSession.java b/mock-server/src/main/java/org/apache/seata/mockserver/model/MockBranchSession.java new file mode 100644 index 00000000000..0b5fcf1e828 --- /dev/null +++ b/mock-server/src/main/java/org/apache/seata/mockserver/model/MockBranchSession.java @@ -0,0 +1,234 @@ +/* + * 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 org.apache.seata.mockserver.model; + +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.BranchType; + +/** + * The type Mock branch session. + */ +public class MockBranchSession { + + private String xid; + + private long transactionId; + + private long branchId; + + private String resourceGroupId; + private String resourceId; + + private String lockKey; + + private BranchType branchType; + + private BranchStatus status = BranchStatus.Unknown; + + private String clientId; + + private String applicationData; + + /** + * Gets resource id. + * + * @return the resource id + */ + public String getResourceId() { + return resourceId; + } + + /** + * Sets resource id. + * + * @param resourceId the resource id + */ + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + /** + * Gets lock key. + * + * @return the lock key + */ + public String getLockKey() { + return lockKey; + } + + /** + * Sets lock key. + * + * @param lockKey the lock key + */ + public void setLockKey(String lockKey) { + this.lockKey = lockKey; + } + + /** + * Gets branch type. + * + * @return the branch type + */ + public BranchType getBranchType() { + return branchType; + } + + /** + * Sets branch type. + * + * @param branchType the branch type + */ + public void setBranchType(BranchType branchType) { + this.branchType = branchType; + } + + /** + * Gets status. + * + * @return the status + */ + public BranchStatus getStatus() { + return status; + } + + /** + * Sets status. + * + * @param status the status + */ + public void setStatus(BranchStatus status) { + this.status = status; + } + + /** + * Gets client id. + * + * @return the client id + */ + public String getClientId() { + return clientId; + } + + /** + * Sets client id. + * + * @param clientId the client id + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets branch id. + * + * @return the branch id + */ + public long getBranchId() { + return branchId; + } + + /** + * Sets branch id. + * + * @param branchId the branch id + */ + public void setBranchId(long branchId) { + this.branchId = branchId; + } + + /** + * Gets resource group id. + * + * @return the resource group id + */ + public String getResourceGroupId() { + return resourceGroupId; + } + + /** + * Sets resource group id. + * + * @param resourceGroupId the resource group id + */ + public void setResourceGroupId(String resourceGroupId) { + this.resourceGroupId = resourceGroupId; + } + + /** + * Instantiates a new Mock branch session. + * + * @param branchType the branch type + */ + public MockBranchSession(BranchType branchType) { + this.branchType = branchType; + } +} diff --git a/mock-server/src/main/java/org/apache/seata/mockserver/model/MockGlobalSession.java b/mock-server/src/main/java/org/apache/seata/mockserver/model/MockGlobalSession.java new file mode 100644 index 00000000000..1dd4e8f2121 --- /dev/null +++ b/mock-server/src/main/java/org/apache/seata/mockserver/model/MockGlobalSession.java @@ -0,0 +1,266 @@ +/* + * 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 org.apache.seata.mockserver.model; + +import java.util.List; + +import org.apache.seata.common.XID; +import org.apache.seata.common.util.UUIDGenerator; +import org.apache.seata.core.model.GlobalStatus; + +/** + * The type Mock global session. + */ +public class MockGlobalSession { + private String xid; + + private long transactionId; + + private volatile GlobalStatus status; + + private String applicationId; + + private String transactionServiceGroup; + + private String transactionName; + + private int timeout; + + private long beginTime; + + private String applicationData; + + private volatile boolean active = true; + + private List branchSessions; + + /** + * Instantiates a new Mock global session. + * + * @param applicationId the application id + * @param transactionServiceGroup the transaction service group + * @param transactionName the transaction name + * @param timeout the timeout + */ + public MockGlobalSession(String applicationId, String transactionServiceGroup, String transactionName, int timeout) { + this.transactionId = UUIDGenerator.generateUUID(); + this.status = GlobalStatus.Begin; + this.applicationId = applicationId; + this.transactionServiceGroup = transactionServiceGroup; + this.transactionName = transactionName; + this.timeout = timeout; + this.xid = XID.generateXID(transactionId); + } + + /** + * Gets xid. + * + * @return the xid + */ + public String getXid() { + return xid; + } + + /** + * Sets xid. + * + * @param xid the xid + */ + public void setXid(String xid) { + this.xid = xid; + } + + /** + * Gets transaction id. + * + * @return the transaction id + */ + public long getTransactionId() { + return transactionId; + } + + /** + * Sets transaction id. + * + * @param transactionId the transaction id + */ + public void setTransactionId(long transactionId) { + this.transactionId = transactionId; + } + + /** + * Gets status. + * + * @return the status + */ + public GlobalStatus getStatus() { + return status; + } + + /** + * Sets status. + * + * @param status the status + */ + public void setStatus(GlobalStatus status) { + this.status = status; + } + + /** + * Gets application id. + * + * @return the application id + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets application id. + * + * @param applicationId the application id + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Gets transaction service group. + * + * @return the transaction service group + */ + public String getTransactionServiceGroup() { + return transactionServiceGroup; + } + + /** + * Sets transaction service group. + * + * @param transactionServiceGroup the transaction service group + */ + public void setTransactionServiceGroup(String transactionServiceGroup) { + this.transactionServiceGroup = transactionServiceGroup; + } + + /** + * Gets transaction name. + * + * @return the transaction name + */ + public String getTransactionName() { + return transactionName; + } + + /** + * Sets transaction name. + * + * @param transactionName the transaction name + */ + public void setTransactionName(String transactionName) { + this.transactionName = transactionName; + } + + /** + * Gets timeout. + * + * @return the timeout + */ + public int getTimeout() { + return timeout; + } + + /** + * Sets timeout. + * + * @param timeout the timeout + */ + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * Gets begin time. + * + * @return the begin time + */ + public long getBeginTime() { + return beginTime; + } + + /** + * Sets begin time. + * + * @param beginTime the begin time + */ + public void setBeginTime(long beginTime) { + this.beginTime = beginTime; + } + + /** + * Gets application data. + * + * @return the application data + */ + public String getApplicationData() { + return applicationData; + } + + /** + * Sets application data. + * + * @param applicationData the application data + */ + public void setApplicationData(String applicationData) { + this.applicationData = applicationData; + } + + /** + * Is active boolean. + * + * @return the boolean + */ + public boolean isActive() { + return active; + } + + /** + * Sets active. + * + * @param active the active + */ + public void setActive(boolean active) { + this.active = active; + } + + /** + * Gets branch sessions. + * + * @return the branch sessions + */ + public List getBranchSessions() { + return branchSessions; + } + + /** + * Sets branch sessions. + * + * @param branchSessions the branch sessions + */ + public void setBranchSessions(List branchSessions) { + this.branchSessions = branchSessions; + } +} diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockHeartbeatProcessor.java b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockHeartbeatProcessor.java similarity index 100% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockHeartbeatProcessor.java rename to mock-server/src/main/java/org/apache/seata/mockserver/processor/MockHeartbeatProcessor.java diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnReqProcessor.java b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnReqProcessor.java similarity index 100% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnReqProcessor.java rename to mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnReqProcessor.java diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnRespProcessor.java b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnRespProcessor.java similarity index 100% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnRespProcessor.java rename to mock-server/src/main/java/org/apache/seata/mockserver/processor/MockOnRespProcessor.java diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java similarity index 100% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java rename to mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java diff --git a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java similarity index 99% rename from test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java rename to mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java index c24bf11b46e..00920bd555d 100644 --- a/test-mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java +++ b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRemotingProcessor.java @@ -43,7 +43,6 @@ public MockRemotingProcessor(RemotingServer remotingServer, TransactionMessageHa public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception { Object message = rpcMessage.getBody(); LOGGER.info("process message : " + message); - } diff --git a/pom.xml b/pom.xml index ce4c0fa5848..b905728f7b5 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ spring tcc test - test-mock-server + mock-server test-old-version tm metrics diff --git a/test-mock-server/src/main/resources/application.yml b/test-mock-server/src/main/resources/application.yml deleted file mode 100644 index 978398eb831..00000000000 --- a/test-mock-server/src/main/resources/application.yml +++ /dev/null @@ -1,57 +0,0 @@ -# -# 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. -# - -server: - port: 7091 - service-port: 8091 - -spring: - application: - name: seata-server - -logging: - config: classpath:logback-spring.xml - file: - path: ${log.home:${user.home}/logs/seata} - extend: - logstash-appender: - destination: 127.0.0.1:4560 - kafka-appender: - bootstrap-servers: 127.0.0.1:9092 - topic: logback_to_logstash - -console: - user: - username: seata - password: seata -seata: - config: - # support: nacos, consul, apollo, zk, etcd3 - type: file - registry: - # support: nacos, eureka, redis, zk, consul, etcd3, sofa - type: file - store: - # support: file 、 db 、 redis 、 raft - mode: file - # server: - # service-port: 8091 #If not configured, the default is '${server.port} + 1000' - security: - secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017 - tokenValidityInMilliseconds: 1800000 - ignore: - urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/** diff --git a/test-mock-server/src/main/resources/logback-spring.xml b/test-mock-server/src/main/resources/logback-spring.xml deleted file mode 100644 index 9650c0b56ee..00000000000 --- a/test-mock-server/src/main/resources/logback-spring.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - 0 - 2048 - true - - - - - true - 0 - 2048 - true - - - - true - 0 - 1024 - true - - - - true - 0 - 1024 - true - - - - - - - - - - - - - - - - - - - diff --git a/test-old-version/src/test/java/io/seata/MockTest.java b/test-old-version/src/test/java/io/seata/MockTest.java index 1a06b872d13..00c1029262a 100644 --- a/test-old-version/src/test/java/io/seata/MockTest.java +++ b/test-old-version/src/test/java/io/seata/MockTest.java @@ -46,7 +46,6 @@ public class MockTest { @BeforeAll public static void before() { - System.setProperty("server.servicePort", ProtocolTestConstants.MOCK_SERVER_PORT+""); MockServer.start(ProtocolTestConstants.MOCK_SERVER_PORT); } diff --git a/test-old-version/src/test/java/io/seata/core/rpc/netty/Action1Impl.java b/test-old-version/src/test/java/io/seata/core/rpc/netty/Action1Impl.java index c13a344043b..eb4f9ea27cf 100644 --- a/test-old-version/src/test/java/io/seata/core/rpc/netty/Action1Impl.java +++ b/test-old-version/src/test/java/io/seata/core/rpc/netty/Action1Impl.java @@ -16,11 +16,11 @@ */ package io.seata.core.rpc.netty; +import java.util.HashMap; +import java.util.Map; + import io.seata.rm.tcc.api.BusinessActionContext; import org.springframework.stereotype.Service; -import vlsi.utils.CompactHashMap; - -import java.util.Map; /** * The type Action1. @@ -28,8 +28,8 @@ @Service public class Action1Impl implements Action1 { - private static Map commitMap = new CompactHashMap<>(); - private static Map rollbackMap = new CompactHashMap<>(); + private static final Map COMMIT_MAP = new HashMap<>(); + private static final Map ROLLBACK_MAP = new HashMap<>(); @Override public String insert(Long reqId, Map params) { @@ -37,12 +37,11 @@ public String insert(Long reqId, Map params) { return "prepare"; } - @Override public boolean commitTcc(BusinessActionContext actionContext) { String xid = actionContext.getXid(); System.out.println("commitTcc:" + xid + "," + actionContext.getActionContext()); - commitMap.compute(xid, (k, v) -> v == null ? 1 : v + 1); + COMMIT_MAP.compute(xid, (k, v) -> v == null ? 1 : v + 1); return true; } @@ -50,15 +49,15 @@ public boolean commitTcc(BusinessActionContext actionContext) { public boolean cancel(BusinessActionContext actionContext) { String xid = actionContext.getXid(); System.out.println("cancelTcc:" + xid + "," + actionContext.getActionContext()); - rollbackMap.compute(xid, (k, v) -> v == null ? 1 : v + 1); + ROLLBACK_MAP.compute(xid, (k, v) -> v == null ? 1 : v + 1); return true; } public static int getCommitTimes(String xid) { - return commitMap.getOrDefault(xid, 0); + return COMMIT_MAP.getOrDefault(xid, 0); } public static int getRollbackTimes(String xid) { - return rollbackMap.getOrDefault(xid, 0); + return ROLLBACK_MAP.getOrDefault(xid, 0); } } diff --git a/test-old-version/src/test/java/io/seata/core/rpc/netty/TmClientTest.java b/test-old-version/src/test/java/io/seata/core/rpc/netty/TmClientTest.java index 254caffa873..7d02cbcc4ed 100644 --- a/test-old-version/src/test/java/io/seata/core/rpc/netty/TmClientTest.java +++ b/test-old-version/src/test/java/io/seata/core/rpc/netty/TmClientTest.java @@ -16,7 +16,6 @@ */ package io.seata.core.rpc.netty; -import io.netty.channel.Channel; import io.seata.core.model.GlobalStatus; import io.seata.core.model.TransactionManager; import io.seata.tm.DefaultTransactionManager; @@ -24,9 +23,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - /** * TmClient Test **/ diff --git a/test-old-version/src/test/resources/registry.conf b/test-old-version/src/test/resources/registry.conf index bab6e8ec0ef..961283a7f82 100644 --- a/test-old-version/src/test/resources/registry.conf +++ b/test-old-version/src/test/resources/registry.conf @@ -16,74 +16,14 @@ # registry { - # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "file" - - nacos { - serverAddr = "localhost" - namespace = "" - cluster = "default" - } - eureka { - serviceUrl = "http://localhost:8761/eureka" - application = "default" - weight = "1" - } - redis { - serverAddr = "localhost:6379" - db = "0" - } - zk { - cluster = "default" - serverAddr = "127.0.0.1:2181" - sessionTimeout = 6000 - connectTimeout = 2000 - } - consul { - cluster = "default" - serverAddr = "127.0.0.1:8500" - } - etcd3 { - cluster = "default" - serverAddr = "http://localhost:2379" - } - sofa { - serverAddr = "127.0.0.1:9603" - application = "default" - region = "DEFAULT_ZONE" - datacenter = "DefaultDataCenter" - cluster = "default" - group = "SEATA_GROUP" - addressWaitTime = "3000" - } file { name = "file.conf" } } config { - # file、nacos 、apollo、zk、consul、etcd3 type = "file" - - nacos { - serverAddr = "localhost" - namespace = "" - } - consul { - serverAddr = "127.0.0.1:8500" - } - apollo { - appId = "seata-server" - apolloMeta = "http://192.168.1.204:8801" - } - zk { - serverAddr = "127.0.0.1:2181" - sessionTimeout = 6000 - connectTimeout = 2000 - } - etcd3 { - serverAddr = "http://localhost:2379" - } file { name = "file.conf" } diff --git a/test/pom.xml b/test/pom.xml index e9991c688db..fa7ec063945 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -50,7 +50,7 @@ grpc-java - io.grpc:protoc-gen-grpc-java:1.66.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.55.1:exe:${os.detected.classifier} From 9a6ece1e3739bb81013c0a07cc646352b3715db4 Mon Sep 17 00:00:00 2001 From: Geng Zhang Date: Thu, 28 Nov 2024 20:31:54 +0800 Subject: [PATCH 53/54] test: add some test case (#7030) --- changes/en-us/2.x.md | 1 + changes/zh-cn/2.x.md | 2 + .../apache/seata/common/LockStrategyMode.java | 2 +- .../common/exception/ExceptionUtilTest.java | 41 +++++++++++++++++++ .../metadata/namingserver/InstanceTest.java | 23 +++++++---- .../namingserver/NamingServerNodeTest.java | 3 ++ .../seata/common/util/UUIDGeneratorTest.java | 31 ++++++++++++++ 7 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 common/src/test/java/org/apache/seata/common/exception/ExceptionUtilTest.java create mode 100644 common/src/test/java/org/apache/seata/common/util/UUIDGeneratorTest.java diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index d8de284eb89..dcfe966e122 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -61,6 +61,7 @@ Add changes here for all PR submitted to the 2.x branch. ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] Add unit tests for the `seata-rocketmq` module - [[#7018](https://github.com/apache/incubator-seata/pull/7018)] Add unit tests for the `seata-tm` module +- [[#7030](https://github.com/apache/incubator-seata/pull/7030)] Add unit tests for the `seata-common` module Thanks to these contributors for their code commits. Please report an unintended omission. diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index c64a1ec09c7..32ff3cc5941 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -64,6 +64,8 @@ ### test: - [[#6927](https://github.com/apache/incubator-seata/pull/6927)] 增加`seata-rocketmq`模块的测试用例 - [[#7018](https://github.com/apache/incubator-seata/pull/7018)] 增加 `seata-tm` 模块的测试用例 +- [[#7030](https://github.com/apache/incubator-seata/pull/7030)] 增加 `seata-common` 模块的测试用例 + 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 diff --git a/common/src/main/java/org/apache/seata/common/LockStrategyMode.java b/common/src/main/java/org/apache/seata/common/LockStrategyMode.java index d3f26b98ac5..f70e8883a74 100644 --- a/common/src/main/java/org/apache/seata/common/LockStrategyMode.java +++ b/common/src/main/java/org/apache/seata/common/LockStrategyMode.java @@ -17,7 +17,7 @@ package org.apache.seata.common; /** - * @funkye + * Lock Strategy Mode */ public enum LockStrategyMode { /** diff --git a/common/src/test/java/org/apache/seata/common/exception/ExceptionUtilTest.java b/common/src/test/java/org/apache/seata/common/exception/ExceptionUtilTest.java new file mode 100644 index 00000000000..f8b152b1a2d --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/exception/ExceptionUtilTest.java @@ -0,0 +1,41 @@ +/* + * 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 org.apache.seata.common.exception; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; + +/** + * + */ +class ExceptionUtilTest { + + @Test + public void unwrap() { + InvocationTargetException targetException = new InvocationTargetException(new RuntimeException("invocation")); + Assertions.assertInstanceOf(RuntimeException.class, ExceptionUtil.unwrap(targetException)); + + UndeclaredThrowableException exception = new UndeclaredThrowableException(new RuntimeException("undeclared")); + Assertions.assertInstanceOf(RuntimeException.class, ExceptionUtil.unwrap(exception)); + + RuntimeException runtimeException = new RuntimeException("runtime"); + Assertions.assertInstanceOf(RuntimeException.class, ExceptionUtil.unwrap(runtimeException)); + } +} \ No newline at end of file diff --git a/common/src/test/java/org/apache/seata/common/metadata/namingserver/InstanceTest.java b/common/src/test/java/org/apache/seata/common/metadata/namingserver/InstanceTest.java index 77ba5f4bd4c..fb9ddf559f6 100644 --- a/common/src/test/java/org/apache/seata/common/metadata/namingserver/InstanceTest.java +++ b/common/src/test/java/org/apache/seata/common/metadata/namingserver/InstanceTest.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; import org.junit.jupiter.api.Test; @@ -33,13 +34,21 @@ class InstanceTest { void toJsonString() throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Instance instance = Instance.getInstance(); - Map map = new HashMap<>(); - Map mmap = new HashMap<>(); - mmap.put("k","v"); - map.put("k",mmap); + Map map = new HashMap<>(); + Map mmap = new HashMap<>(); + mmap.put("k", "v"); + map.put("k", mmap); instance.setMetadata(map); - instance.setControl(new Node.Endpoint("1.1.1.1",888)); - instance.setTransaction(new Node.Endpoint("2.2.2.2",999)); - assertEquals(instance.toJsonString(objectMapper),objectMapper.writeValueAsString(instance)); + instance.setNamespace("namespace"); + instance.setClusterName("clustername"); + instance.setRole(ClusterRole.LEADER); + instance.setUnit("unit"); + instance.setWeight(100d); + instance.setHealthy(true); + instance.setTerm(100L); + instance.setTimestamp(System.currentTimeMillis()); + instance.setControl(new Node.Endpoint("1.1.1.1", 888)); + instance.setTransaction(new Node.Endpoint("2.2.2.2", 999)); + assertEquals(instance.toJsonString(objectMapper), objectMapper.writeValueAsString(instance)); } } \ No newline at end of file diff --git a/common/src/test/java/org/apache/seata/common/metadata/namingserver/NamingServerNodeTest.java b/common/src/test/java/org/apache/seata/common/metadata/namingserver/NamingServerNodeTest.java index 2b70cd26ba0..bd1316b6ddc 100644 --- a/common/src/test/java/org/apache/seata/common/metadata/namingserver/NamingServerNodeTest.java +++ b/common/src/test/java/org/apache/seata/common/metadata/namingserver/NamingServerNodeTest.java @@ -38,6 +38,9 @@ void toJsonString() throws JsonProcessingException { map.put("k","v"); node.setMetadata(map); node.setGroup("group"); + node.setUnit("unit"); + node.setHealthy(true); + node.setTerm(111L); node.setControl(new Node.Endpoint("1.1.1.1",888)); node.setTransaction(new Node.Endpoint("2.2.2.2",999)); assertEquals(node.toJsonString(objectMapper),objectMapper.writeValueAsString(node)); diff --git a/common/src/test/java/org/apache/seata/common/util/UUIDGeneratorTest.java b/common/src/test/java/org/apache/seata/common/util/UUIDGeneratorTest.java new file mode 100644 index 00000000000..b6b525cba76 --- /dev/null +++ b/common/src/test/java/org/apache/seata/common/util/UUIDGeneratorTest.java @@ -0,0 +1,31 @@ +/* + * 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 org.apache.seata.common.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * + */ +class UUIDGeneratorTest { + + @Test + void generateUUID() { + Assertions.assertTrue(UUIDGenerator.generateUUID() > 0); + } +} \ No newline at end of file From 7975aa5ab2b3b37a38cca7ac0a08d709e26dbdb8 Mon Sep 17 00:00:00 2001 From: Muluo-cyan <1217593253@qq.com> Date: Thu, 28 Nov 2024 20:54:45 +0800 Subject: [PATCH 54/54] feature: support ssl communication for raft nodes (#6926) --- .../seata/common/ConfigurationKeys.java | 51 +++++++++++++++++++ .../seata/server/cluster/raft/RaftServer.java | 31 +++++++++++ 2 files changed, 82 insertions(+) diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index ef7304b1623..7d73373fee1 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -894,6 +894,21 @@ public interface ConfigurationKeys { */ String SERVER_RAFT = SERVER_PREFIX + "raft."; + /** + * The constant SERVER_RAFT_SSL. + */ + String SERVER_RAFT_SSL = SERVER_RAFT + "ssl."; + + /** + * The constant SERVER_RAFT_SSL_CLIENT. + */ + String SERVER_RAFT_SSL_CLIENT = SERVER_RAFT_SSL + "client."; + + /** + * The constant SERVER_RAFT_SSL_SERVER. + */ + String SERVER_RAFT_SSL_SERVER = SERVER_RAFT_SSL + "server."; + /** * The constant SERVER_RAFT_SERVER_ADDR. */ @@ -924,6 +939,42 @@ public interface ConfigurationKeys { */ String SERVER_RAFT_SYNC = SERVER_RAFT + "sync"; + /** + * The constant SERVER_RAFT_SSL_ENABLED. + */ + String SERVER_RAFT_SSL_ENABLED = SERVER_RAFT_SSL + "enabled"; + + /** + * The constant SERVER_RAFT_SSL_SERVER_KEYSTORE. + */ + String SERVER_RAFT_SSL_SERVER_KEYSTORE = SERVER_RAFT_SSL_SERVER + "keystore"; + + /** + * The constant SERVER_RAFT_SSL_CLIENT_KEYSTORE. + */ + String SERVER_RAFT_SSL_CLIENT_KEYSTORE = SERVER_RAFT_SSL_CLIENT + "keystore"; + + /** + * The constant SERVER_RAFT_SSL_SERVER_KEYSTORE_PASSWORD. + */ + String SERVER_RAFT_SSL_SERVER_KEYSTORE_PASSWORD = SERVER_RAFT_SSL_SERVER + "keystore.password"; + + /** + * The constant SERVER_RAFT_SSL_CLIENT_KEYSTORE_PASSWORD. + */ + String SERVER_RAFT_SSL_CLIENT_KEYSTORE_PASSWORD = SERVER_RAFT_SSL_CLIENT + "keystore.password"; + + + /** + * The constant SERVER_RAFT_SSL_KEYSTORE_TYPE. + */ + String SERVER_RAFT_SSL_KEYSTORE_TYPE = SERVER_RAFT_SSL + "keystore.type"; + + /** + * The constant SERVER_RAFT_SSL_KMF_ALGORITHM. + */ + String SERVER_RAFT_SSL_KMF_ALGORITHM = SERVER_RAFT_SSL + "kmf.algorithm"; + /** * The constant SERVER_RAFT_MAX_APPEND_BUFFER_SIZE. */ diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServer.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServer.java index 37d18976144..a81ee333dc9 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServer.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServer.java @@ -28,6 +28,7 @@ import com.alipay.sofa.jraft.option.NodeOptions; import com.alipay.sofa.jraft.rpc.RpcServer; import com.codahale.metrics.Slf4jReporter; +import org.apache.seata.config.Configuration; import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.core.rpc.Disposable; import org.apache.commons.io.FileUtils; @@ -36,6 +37,13 @@ import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_ENABLED; import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_INITIAL_DELAY; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_CLIENT_KEYSTORE; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_CLIENT_KEYSTORE_PASSWORD; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_ENABLED; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_KEYSTORE_TYPE; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_KMF_ALGORITHM; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_SERVER_KEYSTORE; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_SSL_SERVER_KEYSTORE_PASSWORD; /** */ @@ -80,6 +88,11 @@ public void start() throws IOException { this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); this.node = this.raftGroupService.start(false); RouteTable.getInstance().updateConfiguration(groupId, node.getOptions().getInitialConf()); + // Enable SSL authentication for the Raft group if SSL is enabled. + boolean sslEnabled = ConfigurationFactory.getInstance().getBoolean(SERVER_RAFT_SSL_ENABLED, false); + if (sslEnabled) { + enableSSL(); + } if (reporterEnabled) { final Slf4jReporter reporter = Slf4jReporter.forRegistry(node.getNodeMetrics().getMetricRegistry()) .outputTo(logger).convertRatesTo(TimeUnit.SECONDS) @@ -119,4 +132,22 @@ public void destroy() { }); } + private void enableSSL() { + System.setProperty("bolt.server.ssl.enable", "true"); + System.setProperty("bolt.server.ssl.clientAuth", "true"); + System.setProperty("bolt.client.ssl.enable", "true"); + + Configuration instance = ConfigurationFactory.getInstance(); + System.setProperty("bolt.server.ssl.keystore", instance.getConfig(SERVER_RAFT_SSL_SERVER_KEYSTORE)); + System.setProperty("bolt.server.ssl.keystore.password", instance.getConfig(SERVER_RAFT_SSL_SERVER_KEYSTORE_PASSWORD)); + System.setProperty("bolt.server.ssl.keystore.type", instance.getConfig(SERVER_RAFT_SSL_KEYSTORE_TYPE)); + System.setProperty("bolt.server.ssl.kmf.algorithm", instance.getConfig(SERVER_RAFT_SSL_KMF_ALGORITHM)); + System.setProperty("bolt.client.ssl.keystore", instance.getConfig(SERVER_RAFT_SSL_CLIENT_KEYSTORE)); + System.setProperty("bolt.client.ssl.keystore.password", instance.getConfig(SERVER_RAFT_SSL_CLIENT_KEYSTORE_PASSWORD)); + System.setProperty("bolt.client.ssl.keystore.type", instance.getConfig(SERVER_RAFT_SSL_KEYSTORE_TYPE)); + System.setProperty("bolt.client.ssl.tmf.algorithm", instance.getConfig(SERVER_RAFT_SSL_KMF_ALGORITHM)); + + logger.info("Enable ssl communication between raft nodes"); + } + }