diff --git a/.asf.yaml b/.asf.yaml
index 16e358f62f..677adaeb0d 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -22,9 +22,12 @@ github:
- java
enabled_merge_buttons:
squash: true
+ squash_commit_message: PR_TITLE
merge: false
rebase: false
- autolink_jira: RATIS
+ autolink_jira:
+ - HDDS
+ - RATIS
notifications:
commits: commits@ratis.apache.org
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..4ada4d9821
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,47 @@
+# 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.
+
+version: 2
+
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ # 'daily' only runs on weekdays
+ interval: "cron"
+ cronjob: "15 9 * * *"
+ cooldown:
+ default-days: 7
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ # 'daily' only runs on weekdays
+ interval: "cron"
+ cronjob: "15 10 * * *"
+ cooldown:
+ default-days: 7
+ ignore:
+ # requires Java 11
+ - dependency-name: "com.github.spotbugs:spotbugs"
+ versions: [">=4.9.0"]
+ - dependency-name: "com.github.spotbugs:spotbugs-maven-plugin"
+ versions: [">=4.9.0.0"]
+ - dependency-name: "org.mockito:mockito-core"
+ versions: [">=5.0.0"]
+ # requires Java 17
+ - dependency-name: "org.apache.hadoop:*"
+ versions: [">=3.5.0"]
+ - dependency-name: "org.junit:junit-bom"
+ versions: [">=6.0.0"]
diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml
new file mode 100644
index 0000000000..a10c02f3d5
--- /dev/null
+++ b/.github/workflows/check.yaml
@@ -0,0 +1,212 @@
+# 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.
+
+# This reusable workflow executes a single check from `dev-support/checks/`.
+# Before and after the check, it performs various steps based on workflow inputs.
+
+name: ci-check
+
+on:
+ workflow_call:
+ inputs:
+ # REQUIRED
+ script:
+ type: string
+ description: "Test script to run from dev-support/checks, without .sh extension"
+ required: true
+
+ # OPTIONAL (ordered alphabetically)
+ java-version:
+ type: string
+ description: "Java version to set up (default: 17)"
+ default: '17'
+ required: false
+
+ needs-binary-tarball:
+ type: boolean
+ description: "Whether to download Ratis binary tarball created by build (default: no)"
+ default: false
+ required: false
+
+ needs-maven-repo:
+ type: boolean
+ description: "Whether to download Ratis jars created by build (default: no)"
+ default: false
+ required: false
+
+ needs-source-tarball:
+ type: boolean
+ description: "Whether to download Ratis source tarball created by build (default: no)"
+ default: false
+ required: false
+
+ runner:
+ type: string
+ description: "GitHub Actions runner to use"
+ default: 'ubuntu-24.04'
+ required: false
+
+ script-args:
+ type: string
+ description: "Arguments for the test script"
+ default: ''
+ required: false
+
+ split:
+ type: string
+ description: "Name of split for matrix jobs, only used in display name"
+ default: ''
+ required: false
+
+ timeout-minutes:
+ type: number
+ description: "Job timeout in minutes (default: 30)"
+ default: 30
+ required: false
+
+ secrets:
+ DEVELOCITY_ACCESS_KEY:
+ description: 'Token for submitting build scan to Develocity'
+ required: false
+
+env:
+ MAVEN_ARGS: --batch-mode --show-version
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
+ SCRIPT: ${{ inputs.script }}
+ WITH_COVERAGE: ${{ github.event_name == 'push' }}
+
+jobs:
+ check:
+ name: ${{ (inputs.split && format('{0} ({1})', inputs.script, inputs.split)) || inputs.script }}
+ runs-on: ${{ inputs.runner }}
+ timeout-minutes: ${{ inputs.timeout-minutes }}
+ steps:
+ - name: Checkout project
+ if: ${{ !inputs.needs-source-tarball }}
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Download source tarball
+ if: ${{ inputs.needs-source-tarball }}
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ratis-src
+
+ - name: Extract source tarball
+ if: ${{ inputs.needs-source-tarball }}
+ run: |
+ tar --strip-components 1 -xzvf ratis*-src.tar.gz
+
+ - name: Create cache for Maven dependencies
+ if: ${{ inputs.script == 'build' }}
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: |
+ ~/.m2/repository/*/*/*
+ !~/.m2/repository/org/apache/ratis
+ key: maven-repo-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ maven-repo-
+
+ - name: Restore cache for Maven dependencies
+ if: ${{ inputs.script != 'build' }}
+ uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: |
+ ~/.m2/repository/*/*/*
+ !~/.m2/repository/org/apache/ratis
+ key: maven-repo-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ maven-repo-
+
+ - name: Download Maven repo
+ id: download-maven-repo
+ if: ${{ inputs.needs-maven-repo }}
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: maven-repo
+ path: |
+ ~/.m2/repository/org/apache/ratis
+
+ - name: Download binary tarball
+ if: ${{ inputs.needs-binary-tarball }}
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ratis-bin
+
+ - name: Extract binary tarball
+ if: ${{ inputs.needs-binary-tarball }}
+ run: |
+ mkdir -p ratis-assembly/target
+ tar xzvf ratis-*-bin.tar.gz -C ratis-assembly/target
+
+ - name: Setup java ${{ inputs.java-version }}
+ if: ${{ inputs.java-version }}
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ with:
+ distribution: 'temurin'
+ java-version: ${{ inputs.java-version }}
+
+ - name: Execute tests
+ run: |
+ $COMMAND
+ env:
+ COMMAND: dev-support/checks/${{ inputs.script }}.sh ${{ inputs.script-args }}
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ - name: Summary of failures
+ if: ${{ failure() }}
+ run: |
+ if [[ -s "target/$SCRIPT/summary.txt" ]]; then
+ cat target/$SCRIPT/summary.txt
+ fi
+
+ - name: Archive build results
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ${{ (inputs.split && format('{0}-{1}', inputs.script, inputs.split)) || inputs.script }}
+ path: target/${{ inputs.script }}
+ continue-on-error: true
+
+ # The following steps are hard-coded to be run only for 'build' check,
+ # to avoid the need for 3 more inputs.
+ - name: Store binaries for tests
+ if: ${{ inputs.script == 'build' && !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ratis-bin
+ path: |
+ ratis-assembly/target/ratis-assembly-*-bin.tar.gz
+ retention-days: 1
+
+ - name: Store source tarball for compilation
+ if: ${{ inputs.script == 'build' && !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: ratis-src
+ path: |
+ ratis-assembly/target/ratis-assembly-*-src.tar.gz
+ retention-days: 1
+
+ - name: Store Maven repo for tests
+ if: ${{ inputs.script == 'build' && !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: maven-repo
+ path: |
+ ~/.m2/repository/org/apache/ratis
+ retention-days: 1
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000000..a00a07955f
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,166 @@
+# 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.
+
+name: CI
+
+on:
+ workflow_call:
+ inputs:
+ ref:
+ type: string
+ description: Ratis git ref (branch, tag or commit hash)
+ default: ''
+ required: false
+ secrets:
+ DEVELOCITY_ACCESS_KEY:
+ description: 'Token for submitting build scan to Develocity'
+ required: false
+ SONARCLOUD_TOKEN:
+ description: 'Token for submitting coverage data to SonarCloud'
+ required: false
+
+permissions: { }
+
+jobs:
+ build:
+ uses: ./.github/workflows/check.yaml
+ with:
+ script: build
+ script-args: -Prelease
+ timeout-minutes: 30
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ compile:
+ needs:
+ - build
+ strategy:
+ matrix:
+ java: [ 8, 11, 17, 21 ]
+ fail-fast: false
+ uses: ./.github/workflows/check.yaml
+ with:
+ java-version: ${{ matrix.java }}
+ needs-source-tarball: true
+ script: compile
+ script-args: -Dmaven.compiler.release=${{ matrix.java }}
+ split: ${{ matrix.java }}
+ timeout-minutes: 30
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ release:
+ uses: ./.github/workflows/check.yaml
+ with:
+ script: release
+ timeout-minutes: 30
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ repro:
+ needs:
+ - build
+ uses: ./.github/workflows/check.yaml
+ with:
+ needs-maven-repo: true
+ script: repro
+ script-args: -Prelease
+ timeout-minutes: 30
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ basic:
+ strategy:
+ matrix:
+ check:
+ - author
+ - checkstyle
+ - findbugs
+ - rat
+ fail-fast: false
+ uses: ./.github/workflows/check.yaml
+ with:
+ script: ${{ matrix.check }}
+ timeout-minutes: 30
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ unit:
+ strategy:
+ matrix:
+ profile:
+ - grpc
+ - server
+ - misc
+ - flaky
+ fail-fast: false
+ uses: ./.github/workflows/check.yaml
+ with:
+ script: unit
+ script-args: -P${{ matrix.profile }}-tests
+ split: ${{ matrix.profile }}
+ timeout-minutes: 60
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+
+ coverage:
+ needs:
+ - build
+ - unit
+ runs-on: ubuntu-24.04
+ timeout-minutes: 30
+ if: github.event_name != 'pull_request'
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - name: Cache for maven dependencies
+ uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: |
+ ~/.m2/repository
+ !~/.m2/repository/org/apache/ratis
+ key: maven-repo-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ maven-repo-
+ - name: Setup java 17
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ with:
+ distribution: 'temurin'
+ java-version: 17
+ - name: Download artifacts
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ path: target/artifacts
+ - name: Untar binaries
+ run: |
+ mkdir -p ratis-assembly/target
+ tar xzvf target/artifacts/ratis-bin/ratis-assembly-*.tar.gz -C ratis-assembly/target
+ - name: Calculate combined coverage
+ run: ./dev-support/checks/coverage.sh
+ - name: Upload coverage to Sonar
+ if: github.repository == 'apache/ratis'
+ run: ./dev-support/checks/sonar.sh
+ env:
+ SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Archive build results
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: always()
+ with:
+ name: ${{ github.job }}
+ path: target/${{ github.job }}
diff --git a/.github/workflows/close-stale-pr.yaml b/.github/workflows/close-stale-pr.yaml
new file mode 100644
index 0000000000..010f0c955d
--- /dev/null
+++ b/.github/workflows/close-stale-pr.yaml
@@ -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.
+name: close-stale-prs
+
+on:
+ schedule:
+ # We can run this only once a week on Sundays so contributors get sufficient time to follow up.
+ - cron: '0 0 * * 0'
+
+jobs:
+ close-stale-prs:
+ permissions:
+ pull-requests: write
+ runs-on: ubuntu-slim
+ steps:
+ - name: Close Stale PRs
+ uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
+ with:
+ stale-pr-label: 'stale'
+ exempt-draft-pr: false
+ days-before-issue-stale: -1
+ days-before-pr-stale: 60
+ days-before-pr-close: 30
+ remove-pr-stale-when-updated: true
+ operations-per-run: 500
+ stale-pr-message: 'This PR has been marked as stale due to 60 days of inactivity. Please comment or remove the stale label to keep it open. Otherwise, it will be automatically closed in ~30 days.'
+ close-pr-message: 'Thank you for your contribution. This PR is being closed due to inactivity. Please contact a maintainer if you would like to reopen it.'
diff --git a/.github/workflows/post-commit.yaml b/.github/workflows/post-commit.yaml
new file mode 100644
index 0000000000..4a946f8621
--- /dev/null
+++ b/.github/workflows/post-commit.yaml
@@ -0,0 +1,40 @@
+# 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.
+
+name: build-branch
+
+on:
+ push:
+ branches-ignore:
+ - 'dependabot/**'
+ tags:
+ - '**'
+ pull_request:
+
+concurrency:
+ group: ci-${{ github.event.pull_request.number || case(github.repository == 'apache/ratis', github.sha, github.ref_name) }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' || github.repository != 'apache/ratis' }}
+
+permissions: { }
+
+jobs:
+ CI:
+ if: github.event_name == 'pull_request'
+ || github.repository == 'apache/ratis'
+ || github.ref_name != 'master'
+ uses: ./.github/workflows/ci.yaml
+ secrets:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ SONARCLOUD_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
diff --git a/.github/workflows/post-commit.yml b/.github/workflows/post-commit.yml
deleted file mode 100644
index 2d3258ccc5..0000000000
--- a/.github/workflows/post-commit.yml
+++ /dev/null
@@ -1,265 +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.
-name: build-branch
-on:
- - push
- - pull_request
-env:
- WITH_COVERAGE: true
-jobs:
- build:
- runs-on: ubuntu-20.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Cache for maven dependencies
- uses: actions/cache@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Setup java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: 8
- - name: Run a full build
- run: ./dev-support/checks/build.sh -Prelease assembly:single
- - name: Store binaries for tests
- uses: actions/upload-artifact@v4
- with:
- name: ratis-bin
- path: |
- ratis-assembly/target/apache-ratis-*.tar.gz
- !ratis-assembly/target/apache-ratis-*-src.tar.gz
- retention-days: 1
- - name: Store source tarball for compilation
- uses: actions/upload-artifact@v4
- with:
- name: ratis-src
- path: ratis-assembly/target/apache-ratis-*-src.tar.gz
- retention-days: 1
- compile:
- needs:
- - build
- runs-on: ubuntu-20.04
- strategy:
- matrix:
- java: [ 11 ]
- fail-fast: false
- steps:
- - name: Download source tarball
- uses: actions/download-artifact@v4
- with:
- name: ratis-src
- - name: Untar sources
- run: |
- tar --strip-components 1 -xzvf apache-ratis-*-src.tar.gz
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Setup java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: ${{ matrix.java }}
- - name: Run a full build
- run: ./dev-support/checks/build.sh
- rat:
- name: rat
- runs-on: ubuntu-20.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Run tests
- run: ./dev-support/checks/rat.sh
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: rat
- path: target/rat
- author:
- name: author
- runs-on: ubuntu-20.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Run tests
- run: ./dev-support/checks/author.sh
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: author
- path: target/author
- unit:
- name: unit
- runs-on: ubuntu-20.04
- strategy:
- matrix:
- profile:
- - grpc
- - server
- - misc
- fail-fast: false
- steps:
- # TEMPORARY WHILE GITHUB FIXES https://github.com/actions/virtual-environments/issues/3185
- - name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
- run: |
- echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- # REMOVE CODE ABOVE WHEN ISSUE IS ADDRESSED!
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Setup java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: 8
- - name: Run tests
- run: ./dev-support/checks/unit.sh -P${{ matrix.profile }}-tests
- - name: Summary of failures
- run: cat target/${{ github.job }}/summary.txt
- if: ${{ !cancelled() }}
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: ${{ !cancelled() }}
- with:
- name: unit-${{ matrix.profile }}
- path: target/unit
- checkstyle:
- name: checkstyle
- runs-on: ubuntu-20.04
- steps:
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Run tests
- run: ./dev-support/checks/checkstyle.sh
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: checkstyle
- path: target/checkstyle
- findbugs:
- name: findbugs
- runs-on: ubuntu-20.04
- steps:
- - name: Setup java
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: 8
- - name: Checkout project
- uses: actions/checkout@v4
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Run tests
- run: ./dev-support/checks/findbugs.sh
- - name: Upload results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: findbugs
- path: target/findbugs
- coverage:
- needs:
- - build
- - unit
- runs-on: ubuntu-20.04
- if: (github.repository == 'apache/ratis' || github.repository == 'apache/incubator-ratis') && github.event_name != 'pull_request'
- steps:
- - name: Checkout project
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Cache for maven dependencies
- uses: actions/cache/restore@v4
- with:
- path: |
- ~/.m2/repository
- !~/.m2/repository/org/apache/ratis
- key: maven-repo-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- maven-repo-
- - name: Setup java 17
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: 17
- - name: Download artifacts
- uses: actions/download-artifact@v4
- with:
- path: target/artifacts
- - name: Untar binaries
- run: |
- mkdir -p ratis-assembly/target
- tar xzvf target/artifacts/ratis-bin/apache-ratis*.tar.gz -C ratis-assembly/target
- - name: Calculate combined coverage
- run: ./dev-support/checks/coverage.sh
- - name: Upload coverage to Sonar
- run: ./dev-support/checks/sonar.sh
- env:
- SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Archive build results
- uses: actions/upload-artifact@v4
- if: always()
- with:
- name: ${{ github.job }}
- path: target/${{ github.job }}
diff --git a/.github/workflows/repeat-test.yml b/.github/workflows/repeat-test.yaml
similarity index 80%
rename from .github/workflows/repeat-test.yml
rename to .github/workflows/repeat-test.yaml
index e3c05bec6a..4bfeebe7f1 100644
--- a/.github/workflows/repeat-test.yml
+++ b/.github/workflows/repeat-test.yaml
@@ -47,19 +47,24 @@ env:
TEST_METHOD: ${{ github.event.inputs.test-method }}
ITERATIONS: ${{ github.event.inputs.iterations }}
FAIL_FAST: ${{ github.event.inputs.fail-fast }}
+ SPLITS: ${{ github.event.inputs.splits }}
run-name: ${{ github.event_name == 'workflow_dispatch' && format('{0}#{1}[{2}]-{3}x{4}', inputs.test-class, inputs.test-method, inputs.ref, inputs.splits, inputs.iterations) || '' }}
+
+permissions: { }
+
jobs:
prepare:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
test-spec: ${{ steps.test-spec.outputs.test-spec }}
+ ref: ${{ steps.ref.outputs.ref }}
steps:
- id: generate
name: Generate test matrix
run: |
splits=()
- for ((i = 1; i <= ${{ github.event.inputs.splits }}; i++)); do
+ for ((i = 1; i <= $SPLITS; i++)); do
splits+=("$i")
done
printf -v x "%s," "${splits[@]}"
@@ -75,11 +80,17 @@ jobs:
fi
echo "Test to be run: $test_spec"
echo "test-spec=$test_spec" >> $GITHUB_OUTPUT
+ - name: Define checkout ref
+ id: ref
+ run: |
+ echo "ref=$REF" >> $GITHUB_OUTPUT
+ env:
+ REF: ${{ github.event.inputs.ref }}
test:
if: ${{ always() }}
needs:
- prepare
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
env:
TEST_SPEC: ${{ needs.prepare.outputs.test-spec }}
strategy:
@@ -87,11 +98,12 @@ jobs:
split: ${{ fromJson(needs.prepare.outputs.matrix) }}
fail-fast: ${{ fromJson(github.event.inputs.fail-fast) }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
- ref: ${{ github.event.inputs.ref }}
+ persist-credentials: false
+ ref: ${{ needs.prepare.outputs.ref }}
- name: Cache for maven dependencies
- uses: actions/cache@v4
+ uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
~/.m2/repository
@@ -100,7 +112,7 @@ jobs:
restore-keys: |
maven-repo-
- name: Setup java
- uses: actions/setup-java@v4
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: 8
@@ -113,18 +125,18 @@ jobs:
run: dev-support/checks/_summary.sh target/unit/summary.txt
if: ${{ !cancelled() }}
- name: Archive build results
- uses: actions/upload-artifact@v4
- if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: ${{ failure() }}
with:
- name: result-${{ env.TEST_CLASS }}-split-${{ matrix.split }}
+ name: result-${{ github.run_number }}-${{ github.run_id }}-split-${{ matrix.split }}
path: target/unit
count-failures:
- if: ${{ always() }}
+ if: ${{ failure() }}
needs: test
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
steps:
- name: Download build results
- uses: actions/download-artifact@v4
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- name: Count failures
run: |
failures=$(find . -name 'summary.txt' | grep -v 'iteration' | xargs grep -v 'exit code: 0' | wc -l)
diff --git a/.github/workflows/vulnerability-check.yaml b/.github/workflows/vulnerability-check.yaml
new file mode 100644
index 0000000000..307ef97185
--- /dev/null
+++ b/.github/workflows/vulnerability-check.yaml
@@ -0,0 +1,69 @@
+# 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.
+
+name: vulnerability-check
+
+on:
+ schedule:
+ # Run at 16:00 UTC every Sunday (Monday 00:00 CST)
+ - cron: "0 16 * * 0"
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ MAVEN_OPTS: -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -Dmaven.wagon.http.retryHandler.class=standard -Dmaven.wagon.http.retryHandler.count=3
+ MAVEN_ARGS: --batch-mode --no-transfer-progress
+
+permissions: { }
+
+jobs:
+ dependency-check:
+ if: ${{ github.event_name == 'workflow_dispatch' || github.repository == 'apache/ratis' }}
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Set up JDK 11
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
+ with:
+ distribution: corretto
+ java-version: 11
+
+ - name: Do Maven install
+ shell: bash
+ run: mvn $MAVEN_ARGS clean install -DskipTests
+
+ - name: Do the dependency-check:aggregate
+ shell: bash
+ run: mvn $MAVEN_ARGS org.owasp:dependency-check-maven:aggregate -DossIndexUsername=${{ secrets.OSS_INDEX_USER }} -DossIndexPassword=${{ secrets.OSS_INDEX_TOKEN }} -DnvdApiKey=${{ secrets.NVD_API_KEY }}
+
+ - name: Generate report date for artifact name
+ run: |
+ target_time=$(TZ=Asia/Shanghai date -d "$utc_time" +"%Y-%m-%d")
+ echo "REPORT_DATE=$target_time" >> $GITHUB_ENV
+ env:
+ utc_time: ${{ github.run_started_at }}
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: vulnerability-check-result-${{ env.REPORT_DATE }}
+ path: target/dependency-check-report.html
+ retention-days: 15
diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml
new file mode 100644
index 0000000000..6b7263f91d
--- /dev/null
+++ b/.github/workflows/zizmor.yml
@@ -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.
+
+name: zizmor
+
+on:
+ push:
+ pull_request:
+
+permissions: { }
+
+jobs:
+ zizmor:
+ runs-on: ubuntu-latest
+ permissions:
+ security-events: write
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Run zizmor
+ uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
diff --git a/.gitignore b/.gitignore
index 9379453102..cf5943b0df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,9 +8,11 @@
*.sdf
*.suo
*.vcxproj.user
+.dev-tools/
.hugo_build.lock
.idea
.classpath
+.mvn/.develocity/
.project
.settings
target
diff --git a/.mvn/develocity.xml b/.mvn/develocity.xml
new file mode 100644
index 0000000000..3bef395946
--- /dev/null
+++ b/.mvn/develocity.xml
@@ -0,0 +1,53 @@
+
+
+
+ ratis
+
+ https://develocity.apache.org
+ false
+
+
+
+ true
+ true
+ true
+ false
+
+ #{isFalse(env['GITHUB_ACTIONS'])}
+
+
+
+
+ #{{'0.0.0.0'}}
+
+
+
+
+ #{isFalse(env['GITHUB_ACTIONS'])}
+
+
+ false
+
+
+
diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml
new file mode 100644
index 0000000000..597996803a
--- /dev/null
+++ b/.mvn/extensions.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ com.gradle
+ develocity-maven-extension
+ 2.4.0
+
+
+ com.gradle
+ common-custom-user-data-maven-extension
+ 2.2.0
+
+
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index 08ea486aa5..d58dfb70ba 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -14,5 +14,6 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
diff --git a/dev-support/checks/_lib.sh b/dev-support/checks/_lib.sh
new file mode 100644
index 0000000000..fd30f756fb
--- /dev/null
+++ b/dev-support/checks/_lib.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+check_name="$(basename "${BASH_SOURCE[1]}")"
+check_name="${check_name%.sh}"
+
+: ${TOOLS_DIR:=$(pwd)/.dev-tools} # directory for tools
+: ${RATIS_PREFER_LOCAL_TOOL:=true} # skip install if tools are already available (eg. via package manager)
+
+## @description Install a dependency. Only first argument is mandatory.
+## @param name of the tool
+## @param the directory for binaries, relative to the tool directory; added to PATH.
+## @param the directory for the tool, relative to TOOLS_DIR
+## @param name of the executable, for testing if it is already installed
+## @param name of the function that performs actual installation steps
+_install_tool() {
+ local tool bindir dir bin func
+
+ tool="$1"
+ bindir="${2:-}"
+ dir="${TOOLS_DIR}"/"${3:-"${tool}"}"
+ bin="${4:-"${tool}"}"
+ func="${5:-"_install_${tool}"}"
+
+ if [[ "${RATIS_PREFER_LOCAL_TOOL}" == "true" ]] && which "${bin}" >& /dev/null; then
+ echo "Skip installing $bin, as it's already available on PATH."
+ return
+ fi
+
+ if [[ ! -d "${dir}" ]]; then
+ mkdir -pv "${dir}"
+ _do_install "${tool}" "${dir}" "${func}"
+ fi
+
+ if [[ -n "${bindir}" ]]; then
+ _add_to_path "${dir}"/"${bindir}"
+
+ if ! which "${bin}" >& /dev/null; then
+ _do_install "${tool}" "${dir}" "${func}"
+ _add_to_path "${dir}"/"${bindir}"
+ fi
+ fi
+}
+
+_do_install() {
+ local tool="$1"
+ local dir="$2"
+ local func="$3"
+
+ pushd "${dir}"
+ if eval "${func}"; then
+ echo "Installed ${tool} in ${dir}"
+ popd
+ else
+ popd
+ msg="Failed to install ${tool}"
+ echo "${msg}" >&2
+ if [[ -n "${REPORT_FILE}" ]]; then
+ echo "${msg}" >> "${REPORT_FILE}"
+ fi
+ exit 1
+ fi
+}
+
+_add_to_path() {
+ local bindir="$1"
+
+ if [[ -d "${bindir}" ]]; then
+ if [[ "${RATIS_PREFER_LOCAL_TOOL}" == "true" ]]; then
+ export PATH="${PATH}:${bindir}"
+ else
+ export PATH="${bindir}:${PATH}"
+ fi
+ fi
+}
diff --git a/dev-support/checks/build.sh b/dev-support/checks/build.sh
index 6add1ae605..ee8d3f3d7e 100755
--- a/dev-support/checks/build.sh
+++ b/dev-support/checks/build.sh
@@ -20,7 +20,7 @@ source "${DIR}/../find_maven.sh"
: ${WITH_COVERAGE:="false"}
-MAVEN_OPTIONS='-V -B -Dmaven.javadoc.skip=true -DskipTests --no-transfer-progress'
+MAVEN_OPTIONS='-V -B -Dmaven.javadoc.skip=true -DskipTests'
if [[ "${WITH_COVERAGE}" != "true" ]]; then
MAVEN_OPTIONS="${MAVEN_OPTIONS} -Djacoco.skip"
diff --git a/dev-support/checks/checkstyle.sh b/dev-support/checks/checkstyle.sh
index a2ee427380..473035bf11 100755
--- a/dev-support/checks/checkstyle.sh
+++ b/dev-support/checks/checkstyle.sh
@@ -23,19 +23,20 @@ REPORT_DIR=${OUTPUT_DIR:-"$DIR/../../target/checkstyle"}
mkdir -p "$REPORT_DIR"
REPORT_FILE="$REPORT_DIR/summary.txt"
-MAVEN_OPTIONS='-B -fae --no-transfer-progress -Dcheckstyle.failOnViolation=false'
+MAVEN_OPTIONS='-B -fae -Dcheckstyle.failOnViolation=false'
declare -i rc
-${MVN} ${MAVEN_OPTIONS} checkstyle:check | tee "${REPORT_DIR}/output.log"
+${MVN} ${MAVEN_OPTIONS} checkstyle:check > "${REPORT_DIR}/output.log"
rc=$?
if [[ ${rc} -ne 0 ]]; then
- ${MVN} ${MAVEN_OPTIONS} clean test-compile checkstyle:check
+ ${MVN} ${MAVEN_OPTIONS} clean test-compile checkstyle:check > output.log
rc=$?
mkdir -p "$REPORT_DIR" # removed by mvn clean
-else
- cat "${REPORT_DIR}/output.log"
+ mv output.log "${REPORT_DIR}"/
fi
+cat "${REPORT_DIR}/output.log"
+
#Print out the exact violations with parsing XML results with sed
find "." -name checkstyle-result.xml -print0 \
| xargs -0 sed '$!N; //dev/null 2>&1 && pwd )"
cd "$DIR/../.." || exit 1
-REPORT_DIR=${OUTPUT_DIR:-"$DIR/../../target/shellcheck"}
-mkdir -p "$REPORT_DIR"
-REPORT_FILE="$REPORT_DIR/summary.txt"
+source "${DIR}/../find_maven.sh"
-echo "" > "$OUTPUT_FILE"
-if [[ "$(uname -s)" = "Darwin" ]]; then
- find . -type f -perm '-500'
-else
- find . -type f -executable
-fi \
- | grep -v -e target/ -e node_modules/ -e '\.\(ico\|py\|yml\)$' \
- | xargs -n1 shellcheck \
- | tee "$REPORT_FILE"
+: ${WITH_COVERAGE:="false"}
-wc -l "$REPORT_FILE" | awk '{print $1}'> "$REPORT_DIR/failures"
+MAVEN_OPTIONS='-V -B -Dmaven.javadoc.skip=true -DskipTests'
-if [[ -s "${REPORT_FILE}" ]]; then
- exit 1
+if [[ "${WITH_COVERAGE}" != "true" ]]; then
+ MAVEN_OPTIONS="${MAVEN_OPTIONS} -Djacoco.skip"
fi
+
+export MAVEN_OPTS="-Xmx4096m"
+${MVN} ${MAVEN_OPTIONS} clean verify "$@"
+exit $?
diff --git a/dev-support/checks/coverage.sh b/dev-support/checks/coverage.sh
index a2fab9b32a..ff0aef1a48 100755
--- a/dev-support/checks/coverage.sh
+++ b/dev-support/checks/coverage.sh
@@ -29,7 +29,7 @@ mkdir -p "$REPORT_DIR"
JACOCO_VERSION=$(${MVN} help:evaluate -Dexpression=jacoco.version -q -DforceStdout)
#Install jacoco cli
-${MVN} --non-recursive --no-transfer-progress \
+${MVN} --non-recursive \
org.apache.maven.plugins:maven-dependency-plugin:3.6.1:copy \
-Dartifact=org.jacoco:org.jacoco.cli:${JACOCO_VERSION}:jar:nodeps
diff --git a/dev-support/checks/findbugs.sh b/dev-support/checks/findbugs.sh
index 17c669b8d5..93d3ef936f 100755
--- a/dev-support/checks/findbugs.sh
+++ b/dev-support/checks/findbugs.sh
@@ -13,6 +13,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
+
+set -u -o pipefail
+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd "$DIR/../.." || exit 1
@@ -20,28 +23,27 @@ source "${DIR}/../find_maven.sh"
: ${WITH_COVERAGE:="false"}
-MAVEN_OPTIONS='-B -fae --no-transfer-progress'
+REPORT_DIR=${OUTPUT_DIR:-"$DIR/../../target/findbugs"}
+mkdir -p "$REPORT_DIR"
+REPORT_FILE="$REPORT_DIR/summary.txt"
+
+source "${DIR}/_lib.sh"
+source "${DIR}/install/spotbugs.sh"
-if ! type unionBugs >/dev/null 2>&1 || ! type convertXmlToText >/dev/null 2>&1; then
- #shellcheck disable=SC2086
- ${MVN} ${MAVEN_OPTIONS} test-compile spotbugs:check
- exit $?
-fi
+MAVEN_OPTIONS='-B -fae'
if [[ "${WITH_COVERAGE}" != "true" ]]; then
MAVEN_OPTIONS="${MAVEN_OPTIONS} -Djacoco.skip"
fi
#shellcheck disable=SC2086
-${MVN} ${MAVEN_OPTIONS} test-compile spotbugs:spotbugs
+${MVN} ${MAVEN_OPTIONS} test-compile spotbugs:spotbugs "$@" | tee "${REPORT_DIR}/output.log"
rc=$?
-REPORT_DIR=${OUTPUT_DIR:-"$DIR/../../target/findbugs"}
-mkdir -p "$REPORT_DIR"
-REPORT_FILE="$REPORT_DIR/summary.txt"
+touch "$REPORT_FILE"
find ratis* -name spotbugsXml.xml -print0 | xargs -0 unionBugs -output "${REPORT_DIR}"/summary.xml
-convertXmlToText "${REPORT_DIR}"/summary.xml | tee "${REPORT_FILE}"
+convertXmlToText "${REPORT_DIR}"/summary.xml | tee -a "${REPORT_FILE}"
convertXmlToText -html:fancy-hist.xsl "${REPORT_DIR}"/summary.xml "${REPORT_DIR}"/summary.html
wc -l "$REPORT_FILE" | awk '{print $1}'> "$REPORT_DIR/failures"
diff --git a/dev-support/checks/install/spotbugs.sh b/dev-support/checks/install/spotbugs.sh
new file mode 100644
index 0000000000..337ba2b94d
--- /dev/null
+++ b/dev-support/checks/install/spotbugs.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script installs SpotBugs.
+# Requires _install_tool from _lib.sh. Use `source` for both scripts, because it modifies $PATH.
+
+_get_spotbugs_version() {
+ MAVEN_ARGS='' ${MVN} -q -DforceStdout -Dscan=false help:evaluate -Dexpression=spotbugs.version 2>/dev/null || echo '4.8.6'
+}
+
+if [[ -z "${SPOTBUGS_VERSION:-}" ]]; then
+ SPOTBUGS_VERSION="$(_get_spotbugs_version)"
+fi
+
+_install_spotbugs() {
+ echo "https://repo.maven.apache.org/maven2/com/github/spotbugs/spotbugs/${SPOTBUGS_VERSION}/spotbugs-${SPOTBUGS_VERSION}.tgz"
+ curl -LSs "https://repo.maven.apache.org/maven2/com/github/spotbugs/spotbugs/${SPOTBUGS_VERSION}/spotbugs-${SPOTBUGS_VERSION}.tgz" | tar -xz -f - || exit 1
+ find "spotbugs-${SPOTBUGS_VERSION}"/bin -type f -print0 | xargs -0 --no-run-if-empty chmod +x
+}
+
+_install_tool spotbugs "spotbugs-${SPOTBUGS_VERSION}/bin"
diff --git a/dev-support/checks/rat.sh b/dev-support/checks/rat.sh
index 34d8a25854..9b55878eff 100755
--- a/dev-support/checks/rat.sh
+++ b/dev-support/checks/rat.sh
@@ -23,7 +23,7 @@ mkdir -p "$REPORT_DIR"
REPORT_FILE="$REPORT_DIR/summary.txt"
-${MVN} -B -fn --no-transfer-progress org.apache.rat:apache-rat-plugin:0.13:check
+${MVN} -B -fn org.apache.rat:apache-rat-plugin:0.13:check
cd "$DIR/../.." || exit 1
diff --git a/dev-support/checks/release.sh b/dev-support/checks/release.sh
new file mode 100755
index 0000000000..1297b36b0c
--- /dev/null
+++ b/dev-support/checks/release.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e -u -o pipefail
+
+# This script tests the local part of the release process. It does not publish anything.
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+cd "$DIR/../.." || exit 1
+
+: "${RATISVERSION:="0.0.1"}"
+: "${RC:="-ci-test"}"
+: "${STAGING_REPO_DIR:="/tmp/ratis.staging-repo"}"
+: "${SVNDISTDIR:="/tmp/ratis.svn"}"
+: "${USERID:="ratis-ci-not-for-release"}"
+
+MVN_REPO_DIR="${HOME}/.m2/repository"
+
+mkdir -p "${SVNDISTDIR}"
+
+if [[ -z "${CODESIGNINGKEY:-}" ]]; then
+ gpg --batch --passphrase '' --pinentry-mode loopback --quick-generate-key "${USERID}" rsa4096 default 1d
+ CODESIGNINGKEY=$(gpg --list-keys --with-colons "${USERID}" | grep '^pub:' | cut -f5 -d:)
+fi
+
+git config user.email || git config user.email 'test@example.com'
+git config user.name || git config user.name 'Test User'
+
+export CODESIGNINGKEY MVN_REPO_DIR RATISVERSION RC SVNDISTDIR
+
+export MAVEN_ARGS="--batch-mode"
+
+dev-support/make_rc.sh 1-prepare-src
+dev-support/make_rc.sh 2-verify-bin
+dev-support/make_rc.sh 3-publish-mvn -DaltDeploymentRepository="local::default::file://${STAGING_REPO_DIR}"
+dev-support/make_rc.sh 4-assembly
diff --git a/dev-support/checks/repro.sh b/dev-support/checks/repro.sh
new file mode 100755
index 0000000000..88941bc286
--- /dev/null
+++ b/dev-support/checks/repro.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+cd "$DIR/../.." || exit 1
+
+source "${DIR}/../find_maven.sh"
+
+: ${WITH_COVERAGE:="false"}
+
+MAVEN_OPTIONS='-V -B -Dmaven.javadoc.skip=true -DskipTests'
+
+if [[ "${WITH_COVERAGE}" != "true" ]]; then
+ MAVEN_OPTIONS="${MAVEN_OPTIONS} -Djacoco.skip"
+fi
+
+export MAVEN_OPTS="-Xmx4096m"
+${MVN} ${MAVEN_OPTIONS} clean verify artifact:compare "$@"
+exit $?
diff --git a/dev-support/checks/sonar.sh b/dev-support/checks/sonar.sh
index 55a46cfec7..55edbdaaea 100755
--- a/dev-support/checks/sonar.sh
+++ b/dev-support/checks/sonar.sh
@@ -23,7 +23,7 @@ if [ ! "$SONAR_TOKEN" ]; then
exit 1
fi
-${MVN} -B verify -DskipShade -DskipTests --no-transfer-progress \
- org.sonarsource.scanner.maven:sonar-maven-plugin:3.6.0.1398:sonar \
+${MVN} -B verify -DskipShade -DskipTests \
+ sonar:sonar \
-Dsonar.coverage.jacoco.xmlReportPaths="$(pwd)/target/coverage/all.xml" \
-Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=apache -Dsonar.projectKey=apache-ratis
diff --git a/dev-support/checks/unit.sh b/dev-support/checks/unit.sh
index f7a4f3017e..c0369898e7 100755
--- a/dev-support/checks/unit.sh
+++ b/dev-support/checks/unit.sh
@@ -34,7 +34,11 @@ REPORT_DIR=${OUTPUT_DIR:-"$DIR/../../target/unit"}
mkdir -p "$REPORT_DIR"
export MAVEN_OPTS="-Xmx4096m"
-MAVEN_OPTIONS='-V -B --no-transfer-progress'
+MAVEN_OPTIONS='-V -B'
+
+if [[ "$@" =~ "-Pflaky-tests" ]]; then
+ MAVEN_OPTIONS="${MAVEN_OPTIONS} -Dsurefire.rerunFailingTestsCount=5 -Dsurefire.timeout=1200"
+fi
if [[ "${FAIL_FAST}" == "true" ]]; then
MAVEN_OPTIONS="${MAVEN_OPTIONS} --fail-fast -Dsurefire.skipAfterFailureCount=1"
@@ -65,6 +69,12 @@ for i in $(seq 1 ${ITERATIONS}); do
fi
if [[ ${ITERATIONS} -gt 1 ]]; then
+ if ! grep -q "Running .*Test" "${REPORT_DIR}/output.log"; then
+ echo "No tests were run" >> "${REPORT_DIR}/summary.txt"
+ irc=1
+ FAIL_FAST=true
+ fi
+
if [[ ${irc} == 0 ]]; then
rm -fr "${REPORT_DIR}"
fi
diff --git a/dev-support/checkstyle.xml b/dev-support/checkstyle.xml
index 6f8ac9f96f..db4954fb49 100644
--- a/dev-support/checkstyle.xml
+++ b/dev-support/checkstyle.xml
@@ -55,6 +55,10 @@
+
+
+
+
diff --git a/dev-support/find_maven.sh b/dev-support/find_maven.sh
index 20b6462b1e..2067ff5152 100644
--- a/dev-support/find_maven.sh
+++ b/dev-support/find_maven.sh
@@ -17,7 +17,7 @@
# limitations under the License.
function find_maven() {
- if [ "$MAVEN" != "" ]; then
+ if [[ -n "${MAVEN:-}" ]]; then
echo "${MAVEN}"
else
local DIR
diff --git a/dev-support/make_rc.sh b/dev-support/make_rc.sh
index b5bec51dde..7317729acb 100755
--- a/dev-support/make_rc.sh
+++ b/dev-support/make_rc.sh
@@ -36,7 +36,7 @@ fi
mvnGet() {
${MVN} -q -Dexec.executable="echo" -Dexec.args="\${${1}}" --non-recursive \
- org.codehaus.mojo:exec-maven-plugin:1.6.0:exec 2>/dev/null
+ org.codehaus.mojo:exec-maven-plugin:exec 2>/dev/null
}
@@ -91,12 +91,12 @@ mvnFun() {
MAVEN_OPTS="${mvnopts}" ${MVN} -Dmaven.repo.local="${repodir}" "$@"
}
-prepare-src() {
+1-prepare-src() {
cd "$projectdir"
git reset --hard
git clean -fdx
- mvnFun versions:set -DnewVersion="$RATISVERSION"
- git commit -a -m "Change version for the version $RATISVERSION $RC"
+ mvnFun versions:set -DnewVersion="$RATISVERSION" -DprocessAllModules
+ git commit --allow-empty -a -m "Change version for the version $RATISVERSION $RC"
git config user.signingkey "${CODESIGNINGKEY}"
git tag -s -m "Release $RATISVERSION $RC" ratis-"${RATISVERSION}${RC}"
@@ -106,69 +106,73 @@ prepare-src() {
#grep -r SNAPSHOT --include=pom.xml
- mvnFun clean install assembly:single -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}"
+ mvnFun clean install -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}"
}
-prepare-bin() {
+2-verify-bin() {
echo "Cleaning up workingdir $WORKINGDIR"
rm -rf "$WORKINGDIR"
mkdir -p "$WORKINGDIR"
cd "$WORKINGDIR"
- tar zvxf "$projectdir/ratis-assembly/target/apache-ratis-${RATISVERSION}-src.tar.gz"
+ tar zvxf "$projectdir/ratis-assembly/target/ratis-assembly-${RATISVERSION}-src.tar.gz"
mv "apache-ratis-${RATISVERSION}-src" "apache-ratis-${RATISVERSION}"
cd "apache-ratis-${RATISVERSION}"
- mvnFun clean install assembly:single -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}"
+ mvnFun clean verify -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}" "$@"
}
-assembly() {
+3-publish-mvn() {
+ cd "$projectdir"
+ mvnFun verify artifact:compare deploy:deploy -DdeployAtEnd=true -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}" "$@"
+}
+
+4-assembly() {
cd "$SVNDISTDIR"
RCDIR="$SVNDISTDIR/${RATISVERSION}/${RC#-}"
mkdir -p "$RCDIR"
cd "$RCDIR"
- cp "$WORKINGDIR/apache-ratis-${RATISVERSION}/ratis-assembly/target/apache-ratis-${RATISVERSION}-bin.tar.gz" "apache-ratis-${RATISVERSION}-bin.tar.gz"
- cp "$projectdir/ratis-assembly/target/apache-ratis-${RATISVERSION}-src.tar.gz" "apache-ratis-${RATISVERSION}-src.tar.gz"
+ cp "$projectdir/ratis-assembly/target/ratis-assembly-${RATISVERSION}-bin.tar.gz" "apache-ratis-${RATISVERSION}-bin.tar.gz"
+ cp "$projectdir/ratis-assembly/target/ratis-assembly-${RATISVERSION}-src.tar.gz" "apache-ratis-${RATISVERSION}-src.tar.gz"
for i in *.tar.gz; do gpg -u "${CODESIGNINGKEY}" --armor --output "${i}.asc" --detach-sig "${i}"; done
for i in *.tar.gz; do gpg --print-md SHA512 "${i}" > "${i}.sha512"; done
for i in *.tar.gz; do gpg --print-mds "${i}" > "${i}.mds"; done
cd "$SVNDISTDIR"
- svn add "${RATISVERSION}" || svn add "${RATISVERSION}/${RC#-}"
+ # skip svn add in CI
+ if [[ -z "${CI:-}" ]]; then
+ svn add "${RATISVERSION}" || svn add "${RATISVERSION}/${RC#-}"
+ fi
}
-publish-git(){
+5-publish-git(){
cd "$projectdir"
+ git push apache HEAD:"release-${RATISVERSION}"
git push apache "ratis-${RATISVERSION}${RC}"
}
-publish-svn() {
+6-publish-svn() {
cd "${SVNDISTDIR}"
svn commit -m "Publish proposed version of the next Ratis release ${RATISVERSION}${RC}"
}
-publish-mvn(){
- cd "$projectdir"
- mvnFun -X clean deploy assembly:single -DskipTests=true -Prelease -Papache-release -Dgpg.keyname="${CODESIGNINGKEY}"
-}
-
-if [ "$#" -ne 1 ]; then
+if [ "$#" -lt 1 ]; then
cat << EOF
-Please choose from available phases (eg. make_rc.sh prepare-src):
+Please choose from available phases (eg. make_rc.sh 1-prepare-src):
- 1. prepare-src: This is the first step. It modifies the mvn version, creates the git tag and
+ 1-prepare-src: This is the first step. It modifies the mvn version, creates the git tag and
builds the project to create the source artifacts.
IT INCLUDES A GIT RESET + CLEAN. ALL THE LOCAL CHANGES WILL BE LOST!
- 2. prepare-bin: The source artifact is copied to the $WORKINGDIR and the binary artifact is created from the source.
+ 2-verify-bin: The source artifact is copied to the $WORKINGDIR and the binary artifact is created from the source.
This is an additional check as the the released source artifact should be enough to build the whole project.
- 3. assembly : This step copies all the required artifacts to the svn directory and ($SVNDISTDIR) creates the signatures/checksum files.
+ 3-publish-mvn: Performs the final build, and uploads the artifacts to the maven staging repository
- 4. publish-git: The first remote step, only do it if everything is fine. It pushes the rc tag to the repository.
+ 4-assembly: This step copies all the required artifacts to the svn directory and ($SVNDISTDIR) creates the signatures/checksum files.
- 5. publish-svn: Uploads the artifacts to the apache dev staging area to start the vote.
+ 5-publish-git: Only do it if everything is fine. It pushes the rc tag and release branch to the repository.
- 6. publish-mvn: Uploads the artifacts to the maven staging repository
+ 6-publish-svn: Uploads the artifacts to the apache dev staging area to start the vote.
The next steps of the release process are not scripted:
@@ -189,5 +193,7 @@ The next steps of the release process are not scripted:
EOF
else
set -x
- eval "$1"
+ func="$1"
+ shift
+ eval "$func" "$@"
fi
diff --git a/mvnw b/mvnw
index 8d937f4c14..19529ddf8c 100755
--- a/mvnw
+++ b/mvnw
@@ -19,290 +19,241 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Apache Maven Wrapper startup batch script, version 3.2.0
-#
-# Required ENV vars:
-# ------------------
-# JAVA_HOME - location of a JDK home dir
+# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
-# MAVEN_OPTS - parameters passed to the Java VM when running Maven
-# e.g. to debug Maven itself, use
-# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
-if [ -z "$MAVEN_SKIP_RC" ] ; then
-
- if [ -f /usr/local/etc/mavenrc ] ; then
- . /usr/local/etc/mavenrc
- fi
-
- if [ -f /etc/mavenrc ] ; then
- . /etc/mavenrc
- fi
-
- if [ -f "$HOME/.mavenrc" ] ; then
- . "$HOME/.mavenrc"
- fi
-
-fi
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
-# OS specific support. $var _must_ be set to either true or false.
-cygwin=false;
-darwin=false;
-mingw=false
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
case "$(uname)" in
- CYGWIN*) cygwin=true ;;
- MINGW*) mingw=true;;
- Darwin*) darwin=true
- # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
- # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
- if [ -z "$JAVA_HOME" ]; then
- if [ -x "/usr/libexec/java_home" ]; then
- JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
- else
- JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
- fi
- fi
- ;;
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
esac
-if [ -z "$JAVA_HOME" ] ; then
- if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=$(java-config --jre-home)
- fi
-fi
-
-# For Cygwin, ensure paths are in UNIX format before anything is touched
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
-fi
-
-# For Mingw, ensure paths are in UNIX format before anything is touched
-if $mingw ; then
- [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
- JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
-fi
-
-if [ -z "$JAVA_HOME" ]; then
- javaExecutable="$(which javac)"
- if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
- # readlink(1) is not available as standard on Solaris 10.
- readLink=$(which readlink)
- if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
- if $darwin ; then
- javaHome="$(dirname "\"$javaExecutable\"")"
- javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
- else
- javaExecutable="$(readlink -f "\"$javaExecutable\"")"
- fi
- javaHome="$(dirname "\"$javaExecutable\"")"
- javaHome=$(expr "$javaHome" : '\(.*\)/bin')
- JAVA_HOME="$javaHome"
- export JAVA_HOME
- fi
- fi
-fi
-
-if [ -z "$JAVACMD" ] ; then
- if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
fi
else
- JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
- fi
-fi
-
-if [ ! -x "$JAVACMD" ] ; then
- echo "Error: JAVA_HOME is not defined correctly." >&2
- echo " We cannot execute $JAVACMD" >&2
- exit 1
-fi
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
-if [ -z "$JAVA_HOME" ] ; then
- echo "Warning: JAVA_HOME environment variable is not set."
-fi
-
-# traverses directory structure from process work directory to filesystem root
-# first directory with .mvn subdirectory is considered project base directory
-find_maven_basedir() {
- if [ -z "$1" ]
- then
- echo "Path not specified to find_maven_basedir"
- return 1
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
fi
+}
- basedir="$1"
- wdir="$1"
- while [ "$wdir" != '/' ] ; do
- if [ -d "$wdir"/.mvn ] ; then
- basedir=$wdir
- break
- fi
- # workaround for JBEAP-8937 (on Solaris 10/Sparc)
- if [ -d "${wdir}" ]; then
- wdir=$(cd "$wdir/.." || exit 1; pwd)
- fi
- # end of workaround
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
done
- printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+ printf %x\\n $h
}
-# concatenates all lines of a file
-concat_lines() {
- if [ -f "$1" ]; then
- # Remove \r in case we run on Windows within Git Bash
- # and check out the repository with auto CRLF management
- # enabled. Otherwise, we may read lines that are delimited with
- # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
- # splitting rules.
- tr -s '\r\n' ' ' < "$1"
- fi
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
}
-log() {
- if [ "$MVNW_VERBOSE" = true ]; then
- printf '%s\n' "$1"
- fi
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
-BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
-if [ -z "$BASE_DIR" ]; then
- exit 1;
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
fi
-MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
-log "$MAVEN_PROJECTBASEDIR"
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
-##########################################################################################
-# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-# This allows using the maven wrapper in projects that prohibit checking in binary data.
-##########################################################################################
-wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
-if [ -r "$wrapperJarPath" ]; then
- log "Found $wrapperJarPath"
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
else
- log "Couldn't find $wrapperJarPath, downloading it ..."
+ die "cannot create temp dir"
+fi
- if [ -n "$MVNW_REPOURL" ]; then
- wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- else
- wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- fi
- while IFS="=" read -r key value; do
- # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
- safeValue=$(echo "$value" | tr -d '\r')
- case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
- esac
- done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
- log "Downloading from: $wrapperUrl"
+mkdir -p -- "${MAVEN_HOME%/*}"
- if $cygwin; then
- wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
- fi
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
- if command -v wget > /dev/null; then
- log "Found wget ... using wget"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- else
- wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
- fi
- elif command -v curl > /dev/null; then
- log "Found curl ... using curl"
- [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
- if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- else
- curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
- fi
- else
- log "Falling back to using Java to download"
- javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
- javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
- # For Cygwin, switch paths to Windows format before running javac
- if $cygwin; then
- javaSource=$(cygpath --path --windows "$javaSource")
- javaClass=$(cygpath --path --windows "$javaClass")
- fi
- if [ -e "$javaSource" ]; then
- if [ ! -e "$javaClass" ]; then
- log " - Compiling MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/javac" "$javaSource")
- fi
- if [ -e "$javaClass" ]; then
- log " - Running MavenWrapperDownloader.java ..."
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
- fi
- fi
- fi
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
fi
-##########################################################################################
-# End of extension
-##########################################################################################
-# If specified, validate the SHA-256 sum of the Maven wrapper jar file
-wrapperSha256Sum=""
-while IFS="=" read -r key value; do
- case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
- esac
-done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
-if [ -n "$wrapperSha256Sum" ]; then
- wrapperSha256Result=false
- if command -v sha256sum > /dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
- wrapperSha256Result=true
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
- elif command -v shasum > /dev/null; then
- if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
- wrapperSha256Result=true
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
fi
else
- echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
- echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
- if [ $wrapperSha256Result = false ]; then
- echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
- echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
- echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
-MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin; then
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
- [ -n "$CLASSPATH" ] &&
- CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
- [ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
-# Provide a "standardized" way to retrieve the CLI args that will
-# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
-export MAVEN_CMD_LINE_ARGS
-
-WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-# shellcheck disable=SC2086 # safe args
-exec "$JAVACMD" \
- $MAVEN_OPTS \
- $MAVEN_DEBUG_OPTS \
- -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
- ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
+clean || :
+exec_maven "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index f80fbad3e7..b150b91ed5 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -1,3 +1,4 @@
+<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@@ -18,188 +19,131 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Apache Maven Wrapper startup batch script, version 3.2.0
-@REM
-@REM Required ENV vars:
-@REM JAVA_HOME - location of a JDK home dir
+@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
-@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
-@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
-@REM e.g. to debug Maven itself, use
-@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
-@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
-@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
-@echo off
-@REM set title of command window
-title %0
-@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
-@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
-
-@REM set %HOME% to equivalent of $HOME
-if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
-
-@REM Execute a user defined script before this one
-if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
-@REM check for pre script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
-if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
-:skipRcPre
-
-@setlocal
-
-set ERROR_CODE=0
-
-@REM To isolate internal variables from possible post scripts, we use another setlocal
-@setlocal
-
-@REM ==== START VALIDATION ====
-if not "%JAVA_HOME%" == "" goto OkJHome
-
-echo.
-echo Error: JAVA_HOME not found in your environment. >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-:OkJHome
-if exist "%JAVA_HOME%\bin\java.exe" goto init
-
-echo.
-echo Error: JAVA_HOME is set to an invalid directory. >&2
-echo JAVA_HOME = "%JAVA_HOME%" >&2
-echo Please set the JAVA_HOME variable in your environment to match the >&2
-echo location of your Java installation. >&2
-echo.
-goto error
-
-@REM ==== END VALIDATION ====
-
-:init
-
-@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
-@REM Fallback to current working directory if not found.
-
-set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
-IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
-
-set EXEC_DIR=%CD%
-set WDIR=%EXEC_DIR%
-:findBaseDir
-IF EXIST "%WDIR%"\.mvn goto baseDirFound
-cd ..
-IF "%WDIR%"=="%CD%" goto baseDirNotFound
-set WDIR=%CD%
-goto findBaseDir
-
-:baseDirFound
-set MAVEN_PROJECTBASEDIR=%WDIR%
-cd "%EXEC_DIR%"
-goto endDetectBaseDir
-
-:baseDirNotFound
-set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
-cd "%EXEC_DIR%"
-
-:endDetectBaseDir
-
-IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
-
-@setlocal EnableExtensions EnableDelayedExpansion
-for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
-@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
-
-:endReadAdditionalConfig
-
-SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
-set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-
-set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
-
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
-)
-
-@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
-@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
-if exist %WRAPPER_JAR% (
- if "%MVNW_VERBOSE%" == "true" (
- echo Found %WRAPPER_JAR%
- )
-) else (
- if not "%MVNW_REPOURL%" == "" (
- SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
- )
- if "%MVNW_VERBOSE%" == "true" (
- echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %WRAPPER_URL%
- )
-
- powershell -Command "&{"^
- "$webclient = new-object System.Net.WebClient;"^
- "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
- "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
- "}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
- "}"
- if "%MVNW_VERBOSE%" == "true" (
- echo Finished downloading %WRAPPER_JAR%
- )
-)
-@REM End of extension
-
-@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
-SET WRAPPER_SHA_256_SUM=""
-FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
-IF NOT %WRAPPER_SHA_256_SUM%=="" (
- powershell -Command "&{"^
- "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
- "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
- " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
- " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
- " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
- " exit 1;"^
- "}"^
- "}"
- if ERRORLEVEL 1 goto error
-)
-
-@REM Provide a "standardized" way to retrieve the CLI args that will
-@REM work with both Windows and non-Windows executions.
-set MAVEN_CMD_LINE_ARGS=%*
-
-%MAVEN_JAVA_EXE% ^
- %JVM_CONFIG_MAVEN_PROPS% ^
- %MAVEN_OPTS% ^
- %MAVEN_DEBUG_OPTS% ^
- -classpath %WRAPPER_JAR% ^
- "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
- %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
-if ERRORLEVEL 1 goto error
-goto end
-
-:error
-set ERROR_CODE=1
-
-:end
-@endlocal & set ERROR_CODE=%ERROR_CODE%
-
-if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
-@REM check for post script, once with legacy .bat ending and once with .cmd ending
-if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
-if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
-:skipRcPost
-
-@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
-if "%MAVEN_BATCH_PAUSE%"=="on" pause
-
-if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
-
-cmd /C exit /B %ERROR_CODE%
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
+}
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/pom.xml b/pom.xml
index 0b3eedee12..4ffed3fdc2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,13 +18,13 @@
org.apacheapache
- 25
+ 37ratisorg.apache.ratis
- 3.1.0-SNAPSHOT
+ 3.3.0-SNAPSHOTApache Ratispom
@@ -46,21 +46,6 @@
-
-
- ${distMgmtSnapshotsId}
- ${distMgmtSnapshotsName}
- ${distMgmtSnapshotsUrl}
-
-
- repository.jboss.org
- https://repository.jboss.org/nexus/content/groups/public/
-
- false
-
-
-
-
Apache License, Version 2.0
@@ -82,22 +67,15 @@
ratis-testratis-examples
- ratis-replicated-mapratis-metrics-apiratis-metrics-defaultratis-metrics-dropwizard3ratis-toolsratis-shellratis-assembly
+ ratis-bom
-
-
- apache.snapshots
- https://repository.apache.org/snapshots/
-
-
-
scm:git:git://git.apache.org/ratis.gitscm:git:https://git-wip-us.apache.org/repos/asf/ratis.git
@@ -162,9 +140,6 @@
- 2023-11-19T14:23:30Z
- UTF-8
- UTF-8
${project.build.directory}/maven-shared-archive-resources/META-INF/LICENSE
@@ -172,57 +147,54 @@
false
- 5.1.8
- 3.3.0
- 4.0.6
- 1.6.1
- 3.0.0
+ 6.0.23.5.3
-
+ 3.4.3
+ 5.5.0.6356
- 3.3.0
- 3.1.0
- 1.6.1
- 2.4.0
- 2.2.0
+ 3.6.1
+ 3.6.3
+ 1.12.0
+ 3.0.0
+ 2.7.10.6.1
- 1.0
+ 2.9.1
- 4.2.1
- 4.2.0
+ 4.8.6
+ 4.8.6.8
- apache.snapshots.https
- Apache Development Snapshot Repository
- https://repository.apache.org/content/repositories/snapshots
- apache.staging.https
- Apache Release Distribution Repository
- https://repository.apache.org/service/local/staging/deploy/maven2
+ ${distMgmtReleasesId}
+ ${distMgmtReleasesName}
+ ${distMgmtReleasesUrl}bash
-
- 1.8
- ${javac.version}
- 3.3.9
+
+ ${javaVersion}
-
- 1.0.5
+
+ 1.0.11
- 3.24.4
- 1.58.0
+ 3.25.8
+ 1.77.1
+
+ 1.60.1
+ 1.40.0true_4
- 2.0.7
- 5.10.1
- 0.8.11
+ 2.0.17
+ 5.14.3
+ 4.11.0
+ 0.8.14
+ flaky | org.apache.ratis.test.tag.FlakyTest
@@ -348,19 +320,6 @@
${project.version}
-
- ratis-replicated-map
- org.apache.ratis
- ${project.version}
-
-
- ratis-replicated-map
- org.apache.ratis
- ${project.version}
- test-jar
- test
-
-
ratis-server-apiorg.apache.ratis
@@ -416,31 +375,47 @@
test${slf4j.version}
-
-
- junit
- junit
- 4.13.2
- org.junitjunit-bom
- ${junit.jupiter.version}
+ ${junit-bom.version}pomimportorg.mockitomockito-core
- 4.3.1
+ ${mockito.version}
+
+
+
+
+ io.opentelemetry
+ opentelemetry-api
+ ${opentelemetry.version}
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+ ${opentelemetry.version}
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+ ${opentelemetry.version}
+
+
+ io.opentelemetry.semconv
+ opentelemetry-semconv
+ ${opentelemetry-semconv.version}
- org.apache.tomcat
- annotations-api
- 6.0.53
- provided
+ io.opentelemetry
+ opentelemetry-context
+ ${opentelemetry.version}
+
@@ -448,12 +423,17 @@
kr.motd.mavenos-maven-plugin
- 1.5.0.Final
+ 1.7.1
+
+ org.apache.hadoop
+ hadoop-maven-plugins
+ ${hadoop-maven-plugins.version}
+ org.xolstice.maven.pluginsprotobuf-maven-plugin
@@ -469,22 +449,15 @@
license-maven-plugin${license-maven-plugin.version}
-
- com.coderplus.maven.plugins
- copy-rename-maven-plugin
- ${copy-rename-maven-plugin.version}
-
-
- org.apache.maven.plugins
- maven-install-plugin
- org.apache.maven.pluginsmaven-javadoc-plugin8
+ -J-Duser.language=en
+ -J-Duser.country=US-Xmaxwarns10000-Xdoclint:-missing
@@ -512,30 +485,15 @@
exec-maven-plugin${exec-maven-plugin.version}
-
- org.apache.maven.plugins
- maven-pdf-plugin
- ${maven-pdf-plugin.version}
- org.apache.maven.pluginsmaven-enforcer-plugin
-
-
-
- [${maven.min.version},)
-
-
- [${java.min.version},)
-
-
- de.skuzzle.enforcerrestrict-imports-enforcer-rule
- ${restrict-imports-enforcer-rules.version}
+ ${restrict-imports-enforcer-rule.version}
@@ -573,8 +531,6 @@
org.apache.maven.pluginsmaven-compiler-plugin
- ${javac.version}
- ${javac.version}true512m2048m
@@ -585,21 +541,6 @@
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
- prepare-package
-
- jar-no-fork
-
-
-
-
@@ -628,15 +569,15 @@
org.apache.maven.pluginsmaven-surefire-plugin
- ${maven-surefire-plugin.version}falsefalsefalsefalse
+ all600
- -Xmx2048m -XX:+HeapDumpOnOutOfMemoryError @{argLine}
+ -Xmx2g -XX:+HeapDumpOnOutOfMemoryError @{argLine}${project.build.directory}/log${project.build.directory}/tmp
@@ -650,12 +591,6 @@
**/Test*$*.java${test.exclude.pattern}
-
-
- listener
- org.apache.ratis.JUnitRunListener
-
-
@@ -676,10 +611,6 @@
-
- org.apache.maven.plugins
- maven-deploy-plugin
- org.apache.ratapache-rat-plugin
@@ -692,10 +623,6 @@
-
- org.apache.maven.plugins
- maven-antrun-plugin
- org.apache.maven.pluginsmaven-site-plugin
@@ -707,11 +634,6 @@
-
- com.atlassian.maven.plugins
- maven-clover2-plugin
- ${maven-clover2-plugin.version}
- org.apache.felixmaven-bundle-plugin
@@ -723,6 +645,16 @@
jacoco-maven-plugin${jacoco.version}
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+ ${cyclonedx.version}
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ ${sonar-maven-plugin.version}
+
@@ -732,91 +664,44 @@
spotbugs-maven-plugin
- org.apache.maven.plugins
- maven-enforcer-plugin
- false
+ org.apache.felix
+ maven-bundle-plugin
+ true
+ true
+
+
+ org.apache.hadoop
+ hadoop-maven-plugins
- clean
-
- enforce
-
- pre-clean
-
-
- default
+ version-info
+ generate-resources
- enforce
+ version-info
- validate
-
-
-
- [${maven.min.version},)
- Maven is out of date.
- Ratis requires at least version ${maven.min.version} of Maven to properly build from source.
- You appear to be using an older version. You can use either "mvn -version" or
- "mvn enforcer:display-info" to verify what version is active.
-
-
-
-
- [${java.min.version},)
- Java is out of date.
- Ratis requires at least version ${java.min.version} of the JDK to properly build from source.
- You appear to be using an older version. You can use either "mvn -version" or
- "mvn enforcer:display-info" to verify what version is active.
-
-
-
+
+ ${project.basedir}
+
+ */src/main/java/**/*.java
+ */src/main/proto/*.proto
+
+
-
- org.apache.felix
- maven-bundle-plugin
- true
- true
- org.apache.maven.pluginsmaven-checkstyle-plugin
- ${maven-checkstyle-plugin.version}
+
+
dev-support/checkstyle.xmltruefalse
-
- org.apache.maven.plugins
- maven-pdf-plugin
-
- ${project.reporting.outputDirectory}
-
- false
-
-
-
- org.codehaus.mojo
- buildnumber-maven-plugin
- 1.4
-
-
- generate-resources
-
- create-metadata
-
-
- target/classes
- ratis-version.properties
- Unknown
-
-
-
- org.codehaus.mojobuild-helper-maven-plugin
@@ -861,7 +746,25 @@
+
+ org.owasp
+ dependency-check-maven
+ 12.2.0
+
+
+
+ ${project.basedir}/src/main/resources
+ false
+
+
+ ${project.basedir}/../src/main/resources
+
+ ratis-version.properties
+
+ true
+
+
@@ -931,9 +834,6 @@
jar
-
- ${project.build.directory}
-
@@ -943,7 +843,7 @@
- ratis-java-sources
+ attach-sourcespackagejar-no-fork
@@ -990,13 +890,11 @@
- ${java.min.version}
+ ${maven.compiler.release}Ratis has unsupported dependencies.
- Ratis requires that all dependencies be compiled with version ${java.min.version} or earlier
+ Ratis requires that all dependencies be compiled with version ${maven.compiler.release} or earlier
of the JDK to properly build from source. You appear to be using a newer dependency. You can use
either "mvn -version" or "mvn enforcer:display-info" to verify what version is active.
- Non-release builds can temporarily build with a newer JDK version by setting the
- 'javac.source' property (eg. mvn -Djavac.source=1.8 clean package).
@@ -1052,6 +950,18 @@
+
+ org.cyclonedx
+ cyclonedx-maven-plugin
+
+
+ package
+
+ makeBom
+
+
+
+
@@ -1067,6 +977,7 @@
org.apache.ratis.grpc.**
+ ${flaky-test-groups}
@@ -1084,6 +995,7 @@
org.apache.ratis.datastream.**org.apache.ratis.server.**
+ ${flaky-test-groups}
@@ -1102,6 +1014,21 @@
org.apache.ratis.grpc.**org.apache.ratis.server.**
+ ${flaky-test-groups}
+
+
+
+
+
+
+ flaky-tests
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ ${flaky-test-groups}
diff --git a/ratis-assembly/pom.xml b/ratis-assembly/pom.xml
index 98826e142a..0558693039 100644
--- a/ratis-assembly/pom.xml
+++ b/ratis-assembly/pom.xml
@@ -17,7 +17,7 @@
ratisorg.apache.ratis
- 3.1.0-SNAPSHOT
+ 3.3.0-SNAPSHOTratis-assembly
@@ -33,6 +33,8 @@
${project.build.directory}/test-classes${project.build.directory}/test-classestrue
+
+ true
@@ -120,8 +122,6 @@
org.apache.maven.pluginsmaven-assembly-plugin
-
- apache-ratis-${project.version}falsetruegnu
@@ -137,25 +137,18 @@
src/main/assembly/src.xml
- apache-ratis-${project.version}-src
- false
- default-cli
+ binpackagesingle
- src/main/assembly/examples-bin.xml
- src/main/assembly/shell-bin.xmlsrc/main/assembly/bin.xml
- src/main/assembly/bin-pkg.xml
- apache-ratis-${project.version}-bin
- false
@@ -280,16 +273,6 @@
test-jar
-
- ratis-replicated-map
- org.apache.ratis
-
-
- ratis-replicated-map
- org.apache.ratis
- test-jar
-
-
org.apache.ratisratis-metrics-api
diff --git a/ratis-assembly/src/main/assembly/bin-pkg.xml b/ratis-assembly/src/main/assembly/bin-pkg.xml
deleted file mode 100644
index 4d89869578..0000000000
--- a/ratis-assembly/src/main/assembly/bin-pkg.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
- bin-pkg
-
- tar.gz
-
-
-
- ${project.basedir}/target/apache-ratis-${project.version}-bin/apache-ratis-${project.version}-bin
- ..
-
- bin/ratis
- libexec/*.sh
- examples/bin/*.sh
-
-
-
- ${project.basedir}/target/apache-ratis-${project.version}-bin/apache-ratis-${project.version}-bin
- ..
-
- bin/ratis
- libexec/*.sh
- examples/bin/*.sh
-
- 0755
-
-
-
diff --git a/ratis-assembly/src/main/assembly/bin.xml b/ratis-assembly/src/main/assembly/bin.xml
index 7be3d01d61..12161b00a8 100644
--- a/ratis-assembly/src/main/assembly/bin.xml
+++ b/ratis-assembly/src/main/assembly/bin.xml
@@ -22,9 +22,18 @@
*/
-->
bin
+ apache-ratis-${project.version}-bin
- dir
+ tar.gz
+
+
+
+ org.apache.ratis:ratis-examples
+
+ examples/lib
+
+ true
@@ -35,13 +44,13 @@
org.apache.ratis:ratis-grpcorg.apache.ratis:ratis-nettyorg.apache.ratis:ratis-proto
- org.apache.ratis:ratis-replicated-maporg.apache.ratis:ratis-server-apiorg.apache.ratis:ratis-serverorg.apache.ratis:ratis-testorg.apache.ratis:ratis-metrics-apiorg.apache.ratis:ratis-metrics-defaultorg.apache.ratis:ratis-metrics-dropwizard3
+ org.apache.ratis:ratis-shellorg.apache.ratis:ratis-toolsorg.apache.ratis:ratis-resource-bundle
@@ -63,6 +72,16 @@
0644
+
+
+ ${project.basedir}/../target
+ .
+
+ bom.json
+ bom.xml
+
+ 0644
+ ${project.basedir}/../ratis-docs/target/classes/docs
@@ -70,5 +89,50 @@
06440755
+
+ ${project.basedir}/../ratis-shell/src/main/bin
+ bin
+ 0755
+
+
+ ${project.basedir}/../ratis-shell/src/main/libexec
+ libexec
+ 0755
+ 0755
+
+
+ ${project.basedir}/../ratis-shell/src/main/conf
+ conf
+ 644
+
+
+ ${project.basedir}/../ratis-shell/target/lib/
+ jars
+
+
+ ${project.basedir}/../ratis-examples
+ examples
+
+ README.md
+
+ 0644
+
+
+ ${project.basedir}/../ratis-examples/src/main/bin
+ examples/bin
+
+ *.*
+
+ 0755
+
+
+ ${project.basedir}/../ratis-examples/src/main/resources
+ examples/conf
+
+ conf.properties
+ log4j.properties
+
+ 644
+
diff --git a/ratis-assembly/src/main/assembly/examples-bin.xml b/ratis-assembly/src/main/assembly/examples-bin.xml
deleted file mode 100644
index 21cc7eced2..0000000000
--- a/ratis-assembly/src/main/assembly/examples-bin.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- examples-bin
-
- dir
-
-
-
-
- org.apache.ratis:ratis-examples
-
- examples/lib
-
-
-
-
- ${project.basedir}/src/main/resources
- .
-
- README.md
- LICENSE
- NOTICE
-
- 0644
-
-
- ${project.basedir}/../ratis-examples
- examples
-
- README.md
-
- 0644
-
-
- ${project.basedir}/../ratis-examples/src/main/bin
- examples/bin
-
- *.*
-
- 0755
-
-
- ${project.basedir}/../ratis-examples/src/main/resources
- examples/conf
-
- conf.properties
- log4j.properties
-
- 644
-
-
-
diff --git a/ratis-assembly/src/main/assembly/shell-bin.xml b/ratis-assembly/src/main/assembly/shell-bin.xml
deleted file mode 100644
index 470870f41c..0000000000
--- a/ratis-assembly/src/main/assembly/shell-bin.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
- shell
-
- dir
-
-
-
- ${project.basedir}/../ratis-shell/target/
- jars
-
- ratis-shell-${project.version}.jar
-
-
-
- ${project.basedir}/../ratis-shell/target/lib/
- jars
-
-
- ${project.basedir}/src/main/resources
- .
-
- README.md
- LICENSE
- NOTICE
-
- 0644
-
-
- ${project.basedir}/../ratis-shell/src/main/bin
- bin
- 0755
-
-
- ${project.basedir}/../ratis-shell/src/main/libexec
- libexec
- 0755
- 0755
-
-
- ${project.basedir}/../ratis-shell/src/main/conf
- conf
- 644
-
-
-
diff --git a/ratis-assembly/src/main/assembly/src.xml b/ratis-assembly/src/main/assembly/src.xml
index 98e06c5739..e20770d54d 100644
--- a/ratis-assembly/src/main/assembly/src.xml
+++ b/ratis-assembly/src/main/assembly/src.xml
@@ -22,6 +22,7 @@
*/
-->
src
+ apache-ratis-${project.version}-srctar.gz
@@ -30,6 +31,7 @@
trueorg.apache.ratis:ratis-assembly
+ org.apache.ratis:ratis-bomorg.apache.ratis:ratis-clientorg.apache.ratis:ratis-commonorg.apache.ratis:ratis-examples
@@ -37,7 +39,6 @@
org.apache.ratis:ratis-nettyorg.apache.ratis:ratis-protoorg.apache.ratis:ratis-docs
- org.apache.ratis:ratis-replicated-maporg.apache.ratis:ratis-server-apiorg.apache.ratis:ratis-serverorg.apache.ratis:ratis-shell
@@ -102,6 +103,7 @@
README.mdmvnw.cmdpom.xml
+ src/**start-build-env.sh0644
diff --git a/ratis-bom/pom.xml b/ratis-bom/pom.xml
new file mode 100644
index 0000000000..3a046d9ff3
--- /dev/null
+++ b/ratis-bom/pom.xml
@@ -0,0 +1,153 @@
+
+
+
+ 4.0.0
+
+
+ org.apache
+ apache
+ 37
+
+
+
+ org.apache.ratis
+ ratis-bom
+ 3.3.0-SNAPSHOT
+ Apache Ratis BOM
+ Apache Ratis Bill of Materials (BOM)
+
+ pom
+
+
+
+ ${distMgmtStagingId}
+ ${distMgmtStagingName}
+ ${distMgmtStagingUrl}
+
+
+ ${distMgmtSnapshotsId}
+ ${distMgmtSnapshotsName}
+ ${distMgmtSnapshotsUrl}
+
+
+
+
+ ${distMgmtReleasesId}
+ ${distMgmtReleasesName}
+ ${distMgmtReleasesUrl}
+
+ 1.0.11
+
+ true
+
+
+
+
+
+ org.apache.ratis
+ ratis-client
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-common
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-docs
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-examples
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-experiments
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-grpc
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-metrics-api
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-metrics-default
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-metrics-dropwizard3
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-netty
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-proto
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-server
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-server-api
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-shell
+ ${project.version}
+
+
+ org.apache.ratis
+ ratis-thirdparty-misc
+ ${ratis.thirdparty.version}
+
+
+ org.apache.ratis
+ ratis-tools
+ ${project.version}
+
+
+
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+ 4.8.6.8
+
+ true
+
+
+
+
+
diff --git a/ratis-client/dev-support/findbugsExcludeFile.xml b/ratis-client/dev-support/findbugsExcludeFile.xml
new file mode 100644
index 0000000000..3a808c4486
--- /dev/null
+++ b/ratis-client/dev-support/findbugsExcludeFile.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ratis-client/pom.xml b/ratis-client/pom.xml
index d1b08d5a6a..26b2034983 100644
--- a/ratis-client/pom.xml
+++ b/ratis-client/pom.xml
@@ -17,12 +17,17 @@
ratisorg.apache.ratis
- 3.1.0-SNAPSHOT
+ 3.3.0-SNAPSHOTratis-clientApache Ratis Client
+
+
+ true
+
+
org.apache.ratis
@@ -42,5 +47,27 @@
org.slf4jslf4j-api
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+ ${basedir}/dev-support/findbugsExcludeFile.xml
+
+
+
+
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/RaftClientConfigKeys.java b/ratis-client/src/main/java/org/apache/ratis/client/RaftClientConfigKeys.java
index 7360a9cadb..925324c21c 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/RaftClientConfigKeys.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/RaftClientConfigKeys.java
@@ -42,7 +42,7 @@ interface Rpc {
String PREFIX = RaftClientConfigKeys.PREFIX + ".rpc";
String REQUEST_TIMEOUT_KEY = PREFIX + ".request.timeout";
- TimeDuration REQUEST_TIMEOUT_DEFAULT = TimeDuration.valueOf(3000, TimeUnit.MILLISECONDS);
+ TimeDuration REQUEST_TIMEOUT_DEFAULT = TimeDuration.valueOf(3, TimeUnit.SECONDS);
static TimeDuration requestTimeout(RaftProperties properties) {
return getTimeDuration(properties.getTimeDuration(REQUEST_TIMEOUT_DEFAULT.getUnit()),
REQUEST_TIMEOUT_KEY, REQUEST_TIMEOUT_DEFAULT, getDefaultLog());
@@ -52,8 +52,7 @@ static void setRequestTimeout(RaftProperties properties, TimeDuration timeoutDur
}
String WATCH_REQUEST_TIMEOUT_KEY = PREFIX + ".watch.request.timeout";
- TimeDuration WATCH_REQUEST_TIMEOUT_DEFAULT =
- TimeDuration.valueOf(10000, TimeUnit.MILLISECONDS);
+ TimeDuration WATCH_REQUEST_TIMEOUT_DEFAULT = TimeDuration.valueOf(10, TimeUnit.SECONDS);
static TimeDuration watchRequestTimeout(RaftProperties properties) {
return getTimeDuration(properties.getTimeDuration(WATCH_REQUEST_TIMEOUT_DEFAULT.getUnit()),
WATCH_REQUEST_TIMEOUT_KEY, WATCH_REQUEST_TIMEOUT_DEFAULT, getDefaultLog());
@@ -125,7 +124,7 @@ static void setFlushRequestBytesMin(RaftProperties properties, SizeInBytes flush
}
String REQUEST_TIMEOUT_KEY = PREFIX + ".request.timeout";
- TimeDuration REQUEST_TIMEOUT_DEFAULT = TimeDuration.valueOf(10000, TimeUnit.MILLISECONDS);
+ TimeDuration REQUEST_TIMEOUT_DEFAULT = TimeDuration.valueOf(10, TimeUnit.SECONDS);
static TimeDuration requestTimeout(RaftProperties properties) {
return getTimeDuration(properties.getTimeDuration(REQUEST_TIMEOUT_DEFAULT.getUnit()),
REQUEST_TIMEOUT_KEY, REQUEST_TIMEOUT_DEFAULT, getDefaultLog());
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java b/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
index edd0475442..359763fe90 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/api/SnapshotManagementApi.java
@@ -27,6 +27,24 @@
*/
public interface SnapshotManagementApi {
- /** trigger create snapshot file. */
- RaftClientReply create(long timeoutMs) throws IOException;
+ /** The same as create(0, timeoutMs). */
+ default RaftClientReply create(long timeoutMs) throws IOException {
+ return create(0, timeoutMs);
+ }
+
+ /** The same as create(force? 1 : 0, timeoutMs). */
+ default RaftClientReply create(boolean force, long timeoutMs) throws IOException {
+ return create(force? 1 : 0, timeoutMs);
+ }
+
+ /**
+ * Trigger to create a snapshot.
+ *
+ * @param creationGap When (creationGap > 0) and (lastAppliedIndex - lastSnapshotIndex < creationGap),
+ * return lastSnapshotIndex; otherwise, take a new snapshot and then return its index.
+ * When creationGap == 0, use the server configured value as the creationGap.
+ * @return a reply. When {@link RaftClientReply#isSuccess()} is true,
+ * {@link RaftClientReply#getLogIndex()} is the snapshot index fulfilling the operation.
+ */
+ RaftClientReply create(long creationGap, long timeoutMs) throws IOException;
}
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/AsyncImpl.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/AsyncImpl.java
index 8547ce2665..01329fa7d6 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/AsyncImpl.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/AsyncImpl.java
@@ -27,6 +27,7 @@
import org.apache.ratis.protocol.RaftClientReply;
import org.apache.ratis.protocol.RaftClientRequest;
import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.trace.TraceClient;
/** Async api implementations. */
class AsyncImpl implements AsyncRpcApi {
@@ -38,7 +39,8 @@ class AsyncImpl implements AsyncRpcApi {
CompletableFuture send(
RaftClientRequest.Type type, Message message, RaftPeerId server) {
- return client.getOrderedAsync().send(type, message, server);
+ return TraceClient.asyncSend(
+ () -> client.getOrderedAsync(server).send(type, message, server), type, server);
}
@Override
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
index db19831955..d2146a521f 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/ClientProtoUtils.java
@@ -128,6 +128,7 @@ static RaftRpcRequestProto.Builder toRaftRpcRequestProtoBuilder(RaftClientReques
Optional.ofNullable(request.getSlidingWindowEntry()).ifPresent(b::setSlidingWindowEntry);
Optional.ofNullable(request.getRoutingTable()).map(RoutingTable::toProto).ifPresent(b::setRoutingTable);
+ Optional.ofNullable(request.getSpanContext()).ifPresent(b::setSpanContext);
return b.setCallId(request.getCallId())
.setToLeader(request.isToLeader())
@@ -179,15 +180,18 @@ static RaftClientRequest toRaftClientRequest(RaftClientRequestProto p) {
final RaftClientRequest.Builder b = RaftClientRequest.newBuilder();
- final RaftPeerId perrId = RaftPeerId.valueOf(request.getReplyId());
+ final RaftPeerId peerId = RaftPeerId.valueOf(request.getReplyId());
if (request.getToLeader()) {
- b.setLeaderId(perrId);
+ b.setLeaderId(peerId);
} else {
- b.setServerId(perrId);
+ b.setServerId(peerId);
}
if (request.hasSlidingWindowEntry()) {
b.setSlidingWindowEntry(request.getSlidingWindowEntry());
}
+ if (request.hasSpanContext()) {
+ b.setSpanContext(request.getSpanContext());
+ }
return b.setClientId(ClientId.valueOf(request.getRequestorId()))
.setGroupId(ProtoUtils.toRaftGroupId(request.getRaftGroupId()))
.setCallId(request.getCallId())
@@ -204,9 +208,13 @@ static ByteBuffer toRaftClientRequestProtoByteBuffer(RaftClientRequest request)
}
static RaftClientRequestProto toRaftClientRequestProto(RaftClientRequest request) {
+ return toRaftClientRequestProto(request, true);
+ }
+
+ static RaftClientRequestProto toRaftClientRequestProto(RaftClientRequest request, boolean withMsg) {
final RaftClientRequestProto.Builder b = RaftClientRequestProto.newBuilder()
.setRpcRequest(toRaftRpcRequestProtoBuilder(request));
- if (request.getMessage() != null) {
+ if (withMsg && request.getMessage() != null) {
b.setMessage(toClientMessageEntryProtoBuilder(request.getMessage()));
}
@@ -364,6 +372,7 @@ static GroupInfoReplyProto toGroupInfoReplyProto(GroupInfoReply reply) {
b.setIsRaftStorageHealthy(reply.isRaftStorageHealthy());
b.setRole(reply.getRoleInfoProto());
b.addAllCommitInfos(reply.getCommitInfos());
+ b.setLogInfo(reply.getLogInfoProto());
}
}
return b.build();
@@ -397,7 +406,8 @@ static RaftClientReply toRaftClientReply(RaftClientReplyProto replyProto) {
e = new NotLeaderException(serverMemberId, suggestedLeader, peers);
} else if (replyProto.getExceptionDetailsCase() == NOTREPLICATEDEXCEPTION) {
final NotReplicatedExceptionProto nre = replyProto.getNotReplicatedException();
- e = new NotReplicatedException(nre.getCallId(), nre.getReplication(), nre.getLogIndex());
+ e = new NotReplicatedException(nre.getCallId(), nre.getReplication(), nre.getLogIndex(),
+ replyProto.getCommitInfosList());
} else if (replyProto.getExceptionDetailsCase().equals(STATEMACHINEEXCEPTION)) {
e = toStateMachineException(serverMemberId, replyProto.getStateMachineException());
} else if (replyProto.getExceptionDetailsCase().equals(DATASTREAMEXCEPTION)) {
@@ -506,7 +516,8 @@ static GroupInfoReply toGroupInfoReply(GroupInfoReplyProto replyProto) {
ProtoUtils.toRaftGroup(replyProto.getGroup()),
replyProto.getRole(),
replyProto.getIsRaftStorageHealthy(),
- replyProto.hasConf()? replyProto.getConf(): null);
+ replyProto.hasConf()? replyProto.getConf(): null,
+ replyProto.getLogInfo());
}
static Message toMessage(final ClientMessageEntryProto p) {
@@ -657,7 +668,8 @@ static SnapshotManagementRequest toSnapshotManagementRequest(SnapshotManagementR
switch(p.getOpCase()) {
case CREATE:
return SnapshotManagementRequest.newCreate(clientId, serverId,
- ProtoUtils.toRaftGroupId(m.getRaftGroupId()), m.getCallId(), m.getTimeoutMs());
+ ProtoUtils.toRaftGroupId(m.getRaftGroupId()), m.getCallId(), m.getTimeoutMs(),
+ p.getCreate().getCreationGap());
default:
throw new IllegalArgumentException("Unexpected op " + p.getOpCase() + " in " + p);
}
@@ -669,7 +681,7 @@ static SnapshotManagementRequestProto toSnapshotManagementRequestProto(
.setRpcRequest(toRaftRpcRequestProtoBuilder(request));
final SnapshotManagementRequest.Create create = request.getCreate();
if (create != null) {
- b.setCreate(SnapshotCreateRequestProto.newBuilder().build());
+ b.setCreate(SnapshotCreateRequestProto.newBuilder().setCreationGap(create.getCreationGap()).build());
}
return b.build();
}
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/DataStreamClientImpl.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/DataStreamClientImpl.java
index 26d01c356f..313131cbda 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/DataStreamClientImpl.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/DataStreamClientImpl.java
@@ -40,6 +40,7 @@
import org.apache.ratis.protocol.RaftPeer;
import org.apache.ratis.protocol.exceptions.AlreadyClosedException;
import org.apache.ratis.rpc.CallId;
+import org.apache.ratis.thirdparty.io.netty.buffer.ByteBuf;
import org.apache.ratis.util.IOUtils;
import org.apache.ratis.protocol.*;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
@@ -152,6 +153,9 @@ private CompletableFuture combineHeader(CompletableFuture writeAsyncImpl(Object data, long length, Iterable options) {
if (isClosed()) {
+ if (data instanceof ByteBuf) {
+ ((ByteBuf) data).release();
+ }
return JavaUtils.completeExceptionally(new AlreadyClosedException(
clientId + ": stream already closed, request=" + header));
}
@@ -169,6 +173,10 @@ private CompletableFuture writeAsyncImpl(Object data, long leng
return f;
}
+ public CompletableFuture writeAsync(ByteBuf src, Iterable options) {
+ return writeAsyncImpl(src, src.readableBytes(), options);
+ }
+
@Override
public CompletableFuture writeAsync(ByteBuffer src, Iterable options) {
return writeAsyncImpl(src, src.remaining(), options);
@@ -235,7 +243,7 @@ public DataStreamClientRpc getClientRpc() {
}
@Override
- public DataStreamOutputRpc stream(RaftClientRequest request) {
+ public DataStreamOutputImpl stream(RaftClientRequest request) {
return new DataStreamOutputImpl(request);
}
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedAsync.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedAsync.java
index 09c6cd4ac9..50e8093a13 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedAsync.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedAsync.java
@@ -59,6 +59,8 @@
public final class OrderedAsync {
public static final Logger LOG = LoggerFactory.getLogger(OrderedAsync.class);
+ public static final Message DUMMY = Message.valueOf("DUMMY");
+
private enum BatchLogKey implements BatchLogger.Key {
SEND_REQUEST_EXCEPTION
}
@@ -117,12 +119,12 @@ public String toString() {
}
}
- static OrderedAsync newInstance(RaftClientImpl client, RaftProperties properties) {
+ static OrderedAsync newInstance(RaftClientImpl client, RaftPeerId server, RaftProperties properties) {
final OrderedAsync ordered = new OrderedAsync(client, properties);
// send a dummy watch request to establish the connection
// TODO: this is a work around, it is better to fix the underlying RPC implementation
if (RaftClientConfigKeys.Async.Experimental.sendDummyRequest(properties)) {
- ordered.send(RaftClientRequest.watchRequestType(), null, null);
+ ordered.send(RaftClientRequest.watchRequestType(), DUMMY, server);
}
return ordered;
}
@@ -176,9 +178,9 @@ CompletableFuture send(RaftClientRequest.Type type, Message mes
).whenComplete((r, e) -> {
if (e != null) {
if (e.getCause() instanceof AlreadyClosedException) {
- LOG.error("Failed to send request, message=" + message + " due to " + e);
+ LOG.error("Failed to send request, message={} due to {}", message, e.toString());
} else {
- LOG.error("Failed to send request, message=" + message, e);
+ LOG.error("Failed to send request, message={}", message, e);
}
}
requestSemaphore.release();
@@ -199,7 +201,7 @@ private void sendRequestWithRetry(PendingOrderedRequest pending) {
return;
}
- if (getSlidingWindow((RaftPeerId) null).isFirst(pending.getSeqNum())) {
+ if (getSlidingWindow(request).isFirst(pending.getSeqNum())) {
pending.setFirstRequest();
}
LOG.debug("{}: send* {}", client.getId(), request);
@@ -213,12 +215,31 @@ private void sendRequestWithRetry(PendingOrderedRequest pending) {
final Throwable exception = e;
final String key = client.getId() + "-" + request.getCallId() + "-" + exception;
final Consumer op = suffix -> LOG.error("{} {}: Failed* {}", suffix, client.getId(), request, exception);
- BatchLogger.warn(BatchLogKey.SEND_REQUEST_EXCEPTION, key, op);
+ BatchLogger.print(BatchLogKey.SEND_REQUEST_EXCEPTION, key, op);
handleException(pending, request, e);
return null;
});
}
+ private void logError(String prefix, RaftClientRequest request, Throwable e) {
+ final Class>[] knownExceptionClasses = {AlreadyClosedException.class, NotLeaderException.class};
+ for(Class> known : knownExceptionClasses) {
+ if (logError(prefix, request, e, known)) {
+ return;
+ }
+ }
+ LOG.error("{} {}: Failed* {}", prefix, client.getId(), request, e);
+ }
+
+ private boolean logError(String prefix, RaftClientRequest request, Throwable e, Class> cause) {
+ if (JavaUtils.unwrapCompletionException(e).getClass().isAssignableFrom(cause)) {
+ LOG.error("{} {}: Failed* {} due to {} caused by {}",
+ prefix, client.getId(), request, e, cause.getSimpleName());
+ return true;
+ }
+ return false;
+ }
+
private void handleException(PendingOrderedRequest pending, RaftClientRequest request, Throwable e) {
final RetryPolicy retryPolicy = client.getRetryPolicy();
if (client.isClosed()) {
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedStreamAsync.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedStreamAsync.java
index 989c00cbbc..275755514f 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedStreamAsync.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/OrderedStreamAsync.java
@@ -21,12 +21,14 @@
import org.apache.ratis.client.RaftClientConfigKeys;
import org.apache.ratis.conf.RaftProperties;
import org.apache.ratis.datastream.impl.DataStreamPacketByteBuffer;
+import org.apache.ratis.datastream.impl.DataStreamRequestByteBuf;
import org.apache.ratis.datastream.impl.DataStreamRequestByteBuffer;
import org.apache.ratis.datastream.impl.DataStreamRequestFilePositionCount;
import org.apache.ratis.io.FilePositionCount;
import org.apache.ratis.protocol.DataStreamReply;
import org.apache.ratis.protocol.DataStreamRequest;
import org.apache.ratis.protocol.DataStreamRequestHeader;
+import org.apache.ratis.thirdparty.io.netty.buffer.ByteBuf;
import org.apache.ratis.util.IOUtils;
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.SlidingWindow;
@@ -56,6 +58,8 @@ static class DataStreamWindowRequest implements SlidingWindow.ClientSideRequest<
DataStreamRequest getDataStreamRequest() {
if (header.getDataLength() == 0) {
return new DataStreamRequestByteBuffer(header, DataStreamPacketByteBuffer.EMPTY_BYTE_BUFFER);
+ } else if (data instanceof ByteBuf) {
+ return new DataStreamRequestByteBuf(header, (ByteBuf)data);
} else if (data instanceof ByteBuffer) {
return new DataStreamRequestByteBuffer(header, (ByteBuffer)data);
} else if (data instanceof FilePositionCount) {
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/RaftClientImpl.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/RaftClientImpl.java
index 1b82709daf..f24360f62b 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/RaftClientImpl.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/RaftClientImpl.java
@@ -43,9 +43,11 @@
import org.apache.ratis.retry.RetryPolicy;
import org.apache.ratis.thirdparty.com.google.common.cache.Cache;
import org.apache.ratis.thirdparty.com.google.common.cache.CacheBuilder;
+import org.apache.ratis.trace.TraceUtils;
import org.apache.ratis.util.CollectionUtils;
import org.apache.ratis.util.IOUtils;
import org.apache.ratis.util.JavaUtils;
+import org.apache.ratis.util.MemoizedFunction;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.TimeDuration;
@@ -172,13 +174,14 @@ private synchronized Set getAndReset() {
private final RaftGroupId groupId;
private final RetryPolicy retryPolicy;
+ @SuppressWarnings({"squid:S3077"}) // Suppress volatile for generic type
private volatile RaftPeerId leaderId;
/** The callIds of the replied requests. */
private final RepliedCallIds repliedCallIds;
private final TimeoutExecutor scheduler = TimeoutExecutor.getInstance();
- private final Supplier orderedAsync;
+ private final MemoizedFunction orderedAsync;
private final Supplier asyncApi;
private final Supplier blockingApi;
private final Supplier messageStreamApi;
@@ -207,7 +210,7 @@ private synchronized Set getAndReset() {
clientRpc.addRaftPeers(group.getPeers());
this.clientRpc = clientRpc;
- this.orderedAsync = JavaUtils.memoize(() -> OrderedAsync.newInstance(this, properties));
+ this.orderedAsync = MemoizedFunction.valueOf(server -> OrderedAsync.newInstance(this, server, properties));
this.messageStreamApi = JavaUtils.memoize(() -> MessageStreamImpl.newInstance(this, properties));
this.asyncApi = JavaUtils.memoize(() -> new AsyncImpl(this));
this.blockingApi = JavaUtils.memoize(() -> new BlockingImpl(this));
@@ -217,6 +220,7 @@ private synchronized Set getAndReset() {
.setParameters(parameters)
.build());
this.adminApi = JavaUtils.memoize(() -> new AdminImpl(this));
+ TraceUtils.setTracerWhenEnabled(properties);
}
@Override
@@ -274,8 +278,8 @@ TimeoutExecutor getScheduler() {
return scheduler;
}
- OrderedAsync getOrderedAsync() {
- return orderedAsync.get();
+ OrderedAsync getOrderedAsync(RaftPeerId server) {
+ return orderedAsync.apply(server);
}
RaftClientRequest newRaftClientRequest(
diff --git a/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java b/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
index 1762dc0e49..65c54d0f21 100644
--- a/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
+++ b/ratis-client/src/main/java/org/apache/ratis/client/impl/SnapshotManagementImpl.java
@@ -37,9 +37,10 @@ class SnapshotManagementImpl implements SnapshotManagementApi {
}
@Override
- public RaftClientReply create(long timeoutMs) throws IOException {
+ public RaftClientReply create(long creationGap, long timeoutMs) throws IOException {
final long callId = CallId.getAndIncrement();
return client.io().sendRequestWithRetry(() -> SnapshotManagementRequest.newCreate(client.getId(),
- Optional.ofNullable(server).orElseGet(client::getLeaderId), client.getGroupId(), callId, timeoutMs));
+ Optional.ofNullable(server).orElseGet(client::getLeaderId),
+ client.getGroupId(), callId, timeoutMs, creationGap));
}
}
diff --git a/ratis-common/dev-support/findbugsExcludeFile.xml b/ratis-common/dev-support/findbugsExcludeFile.xml
new file mode 100644
index 0000000000..882f08b7fa
--- /dev/null
+++ b/ratis-common/dev-support/findbugsExcludeFile.xml
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ratis-common/pom.xml b/ratis-common/pom.xml
index 9205e81c2c..ba19c73e33 100644
--- a/ratis-common/pom.xml
+++ b/ratis-common/pom.xml
@@ -17,7 +17,7 @@
ratisorg.apache.ratis
- 3.1.0-SNAPSHOT
+ 3.3.0-SNAPSHOTratis-common
@@ -39,9 +39,24 @@
- junit
- junit
- test
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-context
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-sdk-testing
+
+
+ io.opentelemetry.semconv
+ opentelemetry-semconv
@@ -54,11 +69,6 @@
junit-jupiter-enginetest
-
- org.junit.vintage
- junit-vintage-engine
- test
- org.junit.platformjunit-platform-launcher
@@ -70,4 +80,15 @@
test
+
+
+
+ com.github.spotbugs
+ spotbugs-maven-plugin
+
+ ${basedir}/dev-support/findbugsExcludeFile.xml
+
+
+
+
diff --git a/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java b/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
index c1fb9268c9..43706faabc 100644
--- a/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
+++ b/ratis-common/src/main/java/org/apache/ratis/conf/ConfUtils.java
@@ -18,7 +18,6 @@
package org.apache.ratis.conf;
import org.apache.ratis.security.TlsConf;
-import org.apache.ratis.thirdparty.com.google.common.base.Objects;
import org.apache.ratis.util.NetUtils;
import org.apache.ratis.util.SizeInBytes;
import org.apache.ratis.util.TimeDuration;
@@ -33,6 +32,9 @@
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -41,10 +43,24 @@
public interface ConfUtils {
Logger LOG = LoggerFactory.getLogger(ConfUtils.class);
+ class Utils {
+ private static final ConcurrentMap CACHE = new ConcurrentHashMap<>();
+
+ private static boolean isNew(String key, T value) {
+ if (value == null) {
+ final Object previous = CACHE.remove(key);
+ return previous != null;
+ } else {
+ final Object previous = CACHE.put(key, value);
+ return !value.equals(previous);
+ }
+ }
+ }
+
static void logGet(String key, T value, T defaultValue, Consumer logger) {
- if (logger != null) {
+ if (logger != null && Utils.isNew(key, value)) {
logger.accept(String.format("%s = %s (%s)", key, value,
- Objects.equal(value, defaultValue)? "default": "custom"));
+ Objects.equals(value, defaultValue)? "default": "custom"));
}
}
diff --git a/ratis-netty/src/main/java/org/apache/ratis/netty/server/DataStreamRequestByteBuf.java b/ratis-common/src/main/java/org/apache/ratis/datastream/impl/DataStreamRequestByteBuf.java
similarity index 96%
rename from ratis-netty/src/main/java/org/apache/ratis/netty/server/DataStreamRequestByteBuf.java
rename to ratis-common/src/main/java/org/apache/ratis/datastream/impl/DataStreamRequestByteBuf.java
index 2542b1ec6f..1873bec9b4 100644
--- a/ratis-netty/src/main/java/org/apache/ratis/netty/server/DataStreamRequestByteBuf.java
+++ b/ratis-common/src/main/java/org/apache/ratis/datastream/impl/DataStreamRequestByteBuf.java
@@ -16,9 +16,8 @@
* limitations under the License.
*/
-package org.apache.ratis.netty.server;
+package org.apache.ratis.datastream.impl;
-import org.apache.ratis.datastream.impl.DataStreamPacketImpl;
import org.apache.ratis.io.WriteOption;
import org.apache.ratis.proto.RaftProtos.DataStreamPacketHeaderProto.Type;
import org.apache.ratis.protocol.ClientId;
diff --git a/ratis-common/src/main/java/org/apache/ratis/io/MD5Hash.java b/ratis-common/src/main/java/org/apache/ratis/io/MD5Hash.java
index e60bef9652..71fd39f34b 100644
--- a/ratis-common/src/main/java/org/apache/ratis/io/MD5Hash.java
+++ b/ratis-common/src/main/java/org/apache/ratis/io/MD5Hash.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -18,155 +18,73 @@
package org.apache.ratis.io;
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
-import java.io.InputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-
-public class MD5Hash {
- public static final int MD5_LEN = 16;
-
- private static final ThreadLocal DIGESTER_FACTORY =
- ThreadLocal.withInitial(() -> {
- try {
- return MessageDigest.getInstance("MD5");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- });
-
- private byte[] digest;
-
- /** Constructs an MD5Hash. */
- public MD5Hash() {
- this.digest = new byte[MD5_LEN];
- }
-
- /** Constructs an MD5Hash from a hex string. */
- public MD5Hash(String hex) {
- setDigest(hex);
- }
-
- /** Constructs an MD5Hash with a specified value. */
- public MD5Hash(byte[] digest) {
- if (digest.length != MD5_LEN) {
- throw new IllegalArgumentException("Wrong length: " + digest.length);
- }
- this.digest = digest.clone();
- }
-
- public void readFields(DataInput in) throws IOException {
- in.readFully(digest);
- }
-
- /** Constructs, reads and returns an instance. */
- public static MD5Hash read(DataInput in) throws IOException {
- MD5Hash result = new MD5Hash();
- result.readFields(in);
- return result;
- }
-
- public void write(DataOutput out) throws IOException {
- out.write(digest);
- }
-
- /** Copy the contents of another instance into this instance. */
- public void set(MD5Hash that) {
- System.arraycopy(that.digest, 0, this.digest, 0, MD5_LEN);
- }
-
- /** Returns the digest bytes. */
- public byte[] getDigest() {
- return digest.clone();
- }
-
- /** Construct a hash value for a byte array. */
- public static MD5Hash digest(byte[] data) {
- return digest(data, 0, data.length);
- }
+import org.apache.ratis.util.MemoizedSupplier;
+import org.apache.ratis.util.Preconditions;
- /**
- * Create a thread local MD5 digester
- */
- public static MessageDigest getDigester() {
- MessageDigest digester = DIGESTER_FACTORY.get();
- digester.reset();
- return digester;
- }
-
- /** Construct a hash value for the content from the InputStream. */
- public static MD5Hash digest(InputStream in) throws IOException {
- final byte[] buffer = new byte[4*1024];
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Supplier;
- final MessageDigest digester = getDigester();
- for(int n; (n = in.read(buffer)) != -1; ) {
- digester.update(buffer, 0, n);
+/**
+ * A MD5 hash value.
+ *
+ * This is a value-based class.
+ */
+public final class MD5Hash {
+ public static final int MD5_LENGTH = 16;
+
+ /** @return an instance with the given digest in a (case-insensitive) hexadecimals. */
+ public static MD5Hash newInstance(String digestHexadecimals) {
+ Objects.requireNonNull(digestHexadecimals, "digestHexadecimals == null");
+ Preconditions.assertSame(2 * MD5_LENGTH, digestHexadecimals.length(), "digestHexadecimals");
+
+ final byte[] digest = new byte[MD5_LENGTH];
+ for (int i = 0; i < MD5_LENGTH; i++) {
+ final int j = i << 1;
+ digest[i] = (byte) (charToNibble(digestHexadecimals, j) << 4 |
+ charToNibble(digestHexadecimals, j + 1));
}
-
- return new MD5Hash(digester.digest());
- }
-
- /** Construct a hash value for a byte array. */
- public static MD5Hash digest(byte[] data, int start, int len) {
- byte[] digest;
- MessageDigest digester = getDigester();
- digester.update(data, start, len);
- digest = digester.digest();
return new MD5Hash(digest);
}
- /** Construct a hash value for an array of byte array. */
- public static MD5Hash digest(byte[][] dataArr, int start, int len) {
- byte[] digest;
- MessageDigest digester = getDigester();
- for (byte[] data : dataArr) {
- digester.update(data, start, len);
- }
- digest = digester.digest();
- return new MD5Hash(digest);
+ /** @return an instance with the given digest. */
+ public static MD5Hash newInstance(byte[] digest) {
+ Objects.requireNonNull(digest, "digest == null");
+ Preconditions.assertSame(MD5_LENGTH, digest.length, "digest");
+ return new MD5Hash(digest.clone());
}
- /** Construct a half-sized version of this MD5. Fits in a long **/
- public long halfDigest() {
- long value = 0;
- for (int i = 0; i < 8; i++) {
- value |= ((digest[i] & 0xffL) << (8*(7-i)));
- }
- return value;
+ private final byte[] digest;
+ private final Supplier digestString;
+
+ private MD5Hash(byte[] digest) {
+ this.digest = digest;
+ this.digestString = MemoizedSupplier.valueOf(() -> digestToString(digest));
}
- /**
- * Return a 32-bit digest of the MD5.
- * @return the first 4 bytes of the md5
- */
- public int quarterDigest() {
- int value = 0;
- for (int i = 0; i < 4; i++) {
- value |= ((digest[i] & 0xff) << (8*(3-i)));
- }
- return value;
+ /** @return the digest wrapped by a read-only {@link ByteBuffer}. */
+ public ByteBuffer getDigest() {
+ return ByteBuffer.wrap(digest).asReadOnlyBuffer();
}
- /** Returns true iff o is an MD5Hash whose digest contains the
- * same values. */
@Override
- public boolean equals(Object o) {
- if (!(o instanceof MD5Hash)) {
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ } else if (!(object instanceof MD5Hash)) {
return false;
}
- MD5Hash other = (MD5Hash)o;
- return Arrays.equals(this.digest, other.digest);
+ final MD5Hash that = (MD5Hash) object;
+ return Arrays.equals(this.digest, that.digest);
}
- /** Returns a hash code value for this object.
- * Only uses the first 4 bytes, since md5s are evenly distributed.
- */
@Override
public int hashCode() {
- return quarterDigest();
+ return ((digest[0] & 0xFF) << 24)
+ | ((digest[1] & 0xFF) << 16)
+ | ((digest[2] & 0xFF) << 8)
+ | (digest[3] & 0xFF);
}
private static final char[] HEX_DIGITS =
@@ -175,8 +93,12 @@ public int hashCode() {
/** Returns a string representation of this object. */
@Override
public String toString() {
- StringBuilder buf = new StringBuilder(MD5_LEN*2);
- for (int i = 0; i < MD5_LEN; i++) {
+ return digestString.get();
+ }
+
+ static String digestToString(byte[] digest) {
+ StringBuilder buf = new StringBuilder(MD5_LENGTH *2);
+ for (int i = 0; i < MD5_LENGTH; i++) {
int b = digest[i];
buf.append(HEX_DIGITS[(b >> 4) & 0xf]);
buf.append(HEX_DIGITS[b & 0xf]);
@@ -184,20 +106,8 @@ public String toString() {
return buf.toString();
}
- /** Sets the digest value from a hex string. */
- public void setDigest(String hex) {
- if (hex.length() != MD5_LEN*2) {
- throw new IllegalArgumentException("Wrong length: " + hex.length());
- }
- this.digest = new byte[MD5_LEN];
- for (int i = 0; i < MD5_LEN; i++) {
- int j = i << 1;
- this.digest[i] = (byte)(charToNibble(hex.charAt(j)) << 4 |
- charToNibble(hex.charAt(j+1)));
- }
- }
-
- private static int charToNibble(char c) {
+ private static int charToNibble(String hexadecimals, int i) {
+ final char c = hexadecimals.charAt(i);
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'a' && c <= 'f') {
@@ -205,7 +115,8 @@ private static int charToNibble(char c) {
} else if (c >= 'A' && c <= 'F') {
return 0xA + (c - 'A');
} else {
- throw new RuntimeException("Not a hex character: " + c);
+ throw new IllegalArgumentException(
+ "Found a non-hexadecimal character '" + c + "' at index " + i + " in \"" + hexadecimals + "\"");
}
}
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/ClientId.java b/ratis-common/src/main/java/org/apache/ratis/protocol/ClientId.java
index 4de615730c..09b77e6e81 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/ClientId.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/ClientId.java
@@ -18,6 +18,7 @@
package org.apache.ratis.protocol;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.apache.ratis.util.WeakValueCache;
import java.util.UUID;
@@ -26,13 +27,17 @@
* to correctly identify retry requests from the same client.
*/
public final class ClientId extends RaftId {
- private static final Factory FACTORY = new Factory() {
+ private static final Factory FACTORY = new Factory(ClientId.class) {
@Override
ClientId newInstance(UUID uuid) {
return new ClientId(uuid);
}
};
+ static WeakValueCache getCache() {
+ return FACTORY.getCache();
+ }
+
public static ClientId emptyClientId() {
return FACTORY.emptyId();
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/GroupInfoReply.java b/ratis-common/src/main/java/org/apache/ratis/protocol/GroupInfoReply.java
index 632fa65293..bfac81a2b0 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/GroupInfoReply.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/GroupInfoReply.java
@@ -19,6 +19,7 @@
import org.apache.ratis.proto.RaftProtos.RaftConfigurationProto;
import org.apache.ratis.proto.RaftProtos.CommitInfoProto;
+import org.apache.ratis.proto.RaftProtos.LogInfoProto;
import org.apache.ratis.proto.RaftProtos.RoleInfoProto;
import java.util.Collection;
@@ -33,25 +34,27 @@ public class GroupInfoReply extends RaftClientReply {
private final RoleInfoProto roleInfoProto;
private final boolean isRaftStorageHealthy;
private final RaftConfigurationProto conf;
+ private final LogInfoProto logInfoProto;
public GroupInfoReply(RaftClientRequest request, Collection commitInfos,
RaftGroup group, RoleInfoProto roleInfoProto, boolean isRaftStorageHealthy,
- RaftConfigurationProto conf) {
+ RaftConfigurationProto conf, LogInfoProto logInfoProto) {
this(request.getClientId(), request.getServerId(), request.getRaftGroupId(),
request.getCallId(), commitInfos,
- group, roleInfoProto, isRaftStorageHealthy, conf);
+ group, roleInfoProto, isRaftStorageHealthy, conf, logInfoProto);
}
@SuppressWarnings("parameternumber")
public GroupInfoReply(ClientId clientId, RaftPeerId serverId, RaftGroupId groupId, long callId,
Collection commitInfos,
RaftGroup group, RoleInfoProto roleInfoProto, boolean isRaftStorageHealthy,
- RaftConfigurationProto conf) {
+ RaftConfigurationProto conf, LogInfoProto logInfoProto) {
super(clientId, serverId, groupId, callId, true, null, null, 0L, commitInfos);
this.group = group;
this.roleInfoProto = roleInfoProto;
this.isRaftStorageHealthy = isRaftStorageHealthy;
this.conf = conf;
+ this.logInfoProto = logInfoProto;
}
public RaftGroup getGroup() {
@@ -69,4 +72,8 @@ public boolean isRaftStorageHealthy() {
public Optional getConf() {
return Optional.ofNullable(conf);
}
+
+ public LogInfoProto getLogInfoProto() {
+ return logInfoProto;
+ }
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/Message.java b/ratis-common/src/main/java/org/apache/ratis/protocol/Message.java
index e7ea97ca4e..55fcd064d2 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/Message.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/Message.java
@@ -19,6 +19,7 @@
import org.apache.ratis.thirdparty.com.google.protobuf.AbstractMessage;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.apache.ratis.thirdparty.com.google.protobuf.TextFormat;
import org.apache.ratis.util.MemoizedSupplier;
import org.apache.ratis.util.StringUtils;
@@ -47,11 +48,11 @@ public String toString() {
}
static Message valueOf(AbstractMessage abstractMessage) {
- return valueOf(abstractMessage.toByteString(), abstractMessage::toString);
+ return valueOf(abstractMessage.toByteString(), () -> TextFormat.shortDebugString(abstractMessage));
}
static Message valueOf(ByteString bytes) {
- return valueOf(bytes, () -> "Message:" + StringUtils.bytes2HexShortString(bytes));
+ return valueOf(bytes, () -> "Message:" + StringUtils.bytes2ShortString(bytes));
}
static Message valueOf(String string) {
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientAsynchronousProtocol.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientAsynchronousProtocol.java
index 1985bbe667..428bdaf18d 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientAsynchronousProtocol.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientAsynchronousProtocol.java
@@ -1,4 +1,4 @@
-/*
+/**
* 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
@@ -46,11 +46,11 @@ default CompletableFuture submitClientRequestAsync(
ReferenceCountedObject requestRef) {
try {
// for backward compatibility
- return submitClientRequestAsync(requestRef.retain())
- .whenComplete((r, e) -> requestRef.release());
+ return submitClientRequestAsync(requestRef.retain());
} catch (Exception e) {
- requestRef.release();
return JavaUtils.completeExceptionally(e);
+ } finally {
+ requestRef.release();
}
}
}
\ No newline at end of file
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientMessage.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientMessage.java
index 8d3104a73d..92ae77ce21 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientMessage.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientMessage.java
@@ -18,7 +18,8 @@
package org.apache.ratis.protocol;
import org.apache.ratis.util.JavaUtils;
-import org.apache.ratis.util.Preconditions;
+
+import java.util.Objects;
public abstract class RaftClientMessage implements RaftRpcMessage {
private final ClientId clientId;
@@ -27,9 +28,9 @@ public abstract class RaftClientMessage implements RaftRpcMessage {
private final long callId;
RaftClientMessage(ClientId clientId, RaftPeerId serverId, RaftGroupId groupId, long callId) {
- this.clientId = Preconditions.assertNotNull(clientId, "clientId");
- this.serverId = Preconditions.assertNotNull(serverId, "serverId");
- this.groupId = Preconditions.assertNotNull(groupId, "groupId");
+ this.clientId = Objects.requireNonNull(clientId, "clientId == null");
+ this.serverId = Objects.requireNonNull(serverId, "serverId == null");
+ this.groupId = Objects.requireNonNull(groupId, "groupId == null");
this.callId = callId;
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
index ed41f1ea2c..85ede62e8c 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftClientRequest.java
@@ -24,6 +24,7 @@
import org.apache.ratis.proto.RaftProtos.ReadRequestTypeProto;
import org.apache.ratis.proto.RaftProtos.ReplicationLevel;
import org.apache.ratis.proto.RaftProtos.SlidingWindowEntry;
+import org.apache.ratis.proto.RaftProtos.SpanContextProto;
import org.apache.ratis.proto.RaftProtos.StaleReadRequestTypeProto;
import org.apache.ratis.proto.RaftProtos.WatchRequestTypeProto;
import org.apache.ratis.proto.RaftProtos.WriteRequestTypeProto;
@@ -305,6 +306,7 @@ public static class Builder {
private SlidingWindowEntry slidingWindowEntry;
private RoutingTable routingTable;
private long timeoutMs;
+ private SpanContextProto spanContext;
public RaftClientRequest build() {
return new RaftClientRequest(this);
@@ -366,6 +368,11 @@ public Builder setTimeoutMs(long timeoutMs) {
this.timeoutMs = timeoutMs;
return this;
}
+
+ public Builder setSpanContext(SpanContextProto spanContext) {
+ this.spanContext = spanContext;
+ return this;
+ }
}
public static Builder newBuilder() {
@@ -397,6 +404,8 @@ public static RaftClientRequest toWriteRequest(RaftClientRequest r, Message mess
private final boolean toLeader;
+ private final SpanContextProto spanContext;
+
/** Construct a request for sending to the given server. */
protected RaftClientRequest(ClientId clientId, RaftPeerId serverId, RaftGroupId groupId, long callId, Type type) {
this(newBuilder()
@@ -429,6 +438,7 @@ private RaftClientRequest(Builder b) {
this.slidingWindowEntry = b.slidingWindowEntry;
this.routingTable = b.routingTable;
this.timeoutMs = b.timeoutMs;
+ this.spanContext = b.spanContext;
}
@Override
@@ -472,9 +482,19 @@ public long getTimeoutMs() {
return timeoutMs;
}
+ public SpanContextProto getSpanContext() {
+ return spanContext;
+ }
+
@Override
public String toString() {
- return super.toString() + ", seq=" + ProtoUtils.toString(slidingWindowEntry) + ", "
- + type + ", " + getMessage();
+ return toStringShort() + ", " + getMessage();
+ }
+
+ /**
+ * @return a short string which does not include {@link #message}.
+ */
+ public String toStringShort() {
+ return super.toString() + ", seq=" + ProtoUtils.toString(slidingWindowEntry) + ", " + type;
}
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroup.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroup.java
index 0612a16f9d..5cf970afc3 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroup.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroup.java
@@ -19,7 +19,13 @@
import org.apache.ratis.util.Preconditions;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
/**
* Description of a raft group, which has a unique {@link RaftGroupId} and a collection of {@link RaftPeer}.
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroupId.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroupId.java
index 9caedf7574..af40746918 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroupId.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftGroupId.java
@@ -18,6 +18,7 @@
package org.apache.ratis.protocol;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.apache.ratis.util.WeakValueCache;
import java.util.UUID;
@@ -27,13 +28,17 @@
* This is a value-based class.
*/
public final class RaftGroupId extends RaftId {
- private static final Factory FACTORY = new Factory() {
+ private static final Factory FACTORY = new Factory(RaftGroupId.class) {
@Override
RaftGroupId newInstance(UUID uuid) {
return new RaftGroupId(uuid);
}
};
+ static WeakValueCache getCache() {
+ return FACTORY.getCache();
+ }
+
public static RaftGroupId emptyGroupId() {
return FACTORY.emptyId();
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftId.java b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftId.java
index 9c2a83ffa3..d089c7d3cb 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/RaftId.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/RaftId.java
@@ -17,17 +17,15 @@
*/
package org.apache.ratis.protocol;
-import org.apache.ratis.thirdparty.com.google.common.cache.Cache;
-import org.apache.ratis.thirdparty.com.google.common.cache.CacheBuilder;
import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
import org.apache.ratis.thirdparty.com.google.protobuf.UnsafeByteOperations;
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.Preconditions;
+import org.apache.ratis.util.WeakValueCache;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.UUID;
-import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
/** Unique identifier implemented using {@link UUID}. */
@@ -53,18 +51,20 @@ static ByteString toByteString(UUID uuid) {
}
abstract static class Factory {
- private final Cache cache = CacheBuilder.newBuilder()
- .weakValues()
- .build();
+ private final WeakValueCache cache;
+
+ Factory(Class clazz) {
+ this.cache = new WeakValueCache<>(clazz.getSimpleName() + "_UUID", this::newInstance);
+ }
abstract ID newInstance(UUID uuid);
+ WeakValueCache getCache() {
+ return cache;
+ }
+
final ID valueOf(UUID uuid) {
- try {
- return cache.get(uuid, () -> newInstance(uuid));
- } catch (ExecutionException e) {
- throw new IllegalStateException("Failed to valueOf(" + uuid + ")", e);
- }
+ return cache.getOrCreate(uuid);
}
final ID valueOf(ByteString bytes) {
@@ -85,7 +85,7 @@ ID randomId() {
private final Supplier uuidString;
RaftId(UUID uuid) {
- this.uuid = Preconditions.assertNotNull(uuid, "uuid");
+ this.uuid = Objects.requireNonNull(uuid, "uuid == null");
this.uuidBytes = JavaUtils.memoize(() -> toByteString(uuid));
this.uuidString = JavaUtils.memoize(() -> createUuidString(uuid));
Preconditions.assertTrue(ZERO_UUID == uuid || !uuid.equals(ZERO_UUID),
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java b/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
index 2ea2059b51..269fdfc591 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/SnapshotManagementRequest.java
@@ -24,7 +24,16 @@ public final class SnapshotManagementRequest extends RaftClientRequest {
public abstract static class Op {
}
- public static class Create extends Op {
+
+ public static final class Create extends Op {
+ private final long creationGap;
+ private Create(long creationGap) {
+ this.creationGap = creationGap;
+ }
+
+ public long getCreationGap() {
+ return creationGap;
+ }
@Override
public String toString() {
@@ -35,8 +44,13 @@ public String toString() {
public static SnapshotManagementRequest newCreate(ClientId clientId,
RaftPeerId serverId, RaftGroupId groupId, long callId, long timeoutMs) {
+ return newCreate(clientId, serverId, groupId, callId, timeoutMs, 0);
+ }
+
+ public static SnapshotManagementRequest newCreate(ClientId clientId,
+ RaftPeerId serverId, RaftGroupId groupId, long callId, long timeoutMs, long creationGap) {
return new SnapshotManagementRequest(clientId,
- serverId, groupId, callId, timeoutMs,new SnapshotManagementRequest.Create());
+ serverId, groupId, callId, timeoutMs, new SnapshotManagementRequest.Create(creationGap));
}
private final Op op;
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotLeaderException.java b/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotLeaderException.java
index 8d5c2cb4e9..c7dc6a3961 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotLeaderException.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotLeaderException.java
@@ -30,7 +30,8 @@ public class NotLeaderException extends RaftException {
private final Collection peers;
public NotLeaderException(RaftGroupMemberId memberId, RaftPeer suggestedLeader, Collection peers) {
- super("Server " + memberId + " is not the leader" + (suggestedLeader != null? " " + suggestedLeader: ""));
+ super("Server " + memberId + " is not the leader" +
+ (suggestedLeader != null ? ", suggested leader is: " + suggestedLeader : ""));
this.suggestedLeader = suggestedLeader;
this.peers = peers != null? Collections.unmodifiableCollection(peers): Collections.emptyList();
Preconditions.assertUnique(this.peers);
diff --git a/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotReplicatedException.java b/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotReplicatedException.java
index 5f48654eec..37ff816245 100644
--- a/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotReplicatedException.java
+++ b/ratis-common/src/main/java/org/apache/ratis/protocol/exceptions/NotReplicatedException.java
@@ -17,12 +17,17 @@
*/
package org.apache.ratis.protocol.exceptions;
+import org.apache.ratis.proto.RaftProtos.CommitInfoProto;
import org.apache.ratis.proto.RaftProtos.ReplicationLevel;
+import java.util.Collection;
+
public class NotReplicatedException extends RaftException {
private final long callId;
private final ReplicationLevel requiredReplication;
private final long logIndex;
+ /** This is only populated on client-side since RaftClientReply already has commitInfos */
+ private Collection commitInfos;
public NotReplicatedException(long callId, ReplicationLevel requiredReplication, long logIndex) {
super("Request with call Id " + callId + " and log index " + logIndex
@@ -32,6 +37,12 @@ public NotReplicatedException(long callId, ReplicationLevel requiredReplication,
this.logIndex = logIndex;
}
+ public NotReplicatedException(long callId, ReplicationLevel requiredReplication, long logIndex,
+ Collection commitInfos) {
+ this(callId, requiredReplication, logIndex);
+ this.commitInfos = commitInfos;
+ }
+
public long getCallId() {
return callId;
}
@@ -43,4 +54,8 @@ public ReplicationLevel getRequiredReplication() {
public long getLogIndex() {
return logIndex;
}
+
+ public Collection getCommitInfos() {
+ return commitInfos;
+ }
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/retry/ExponentialBackoffRetry.java b/ratis-common/src/main/java/org/apache/ratis/retry/ExponentialBackoffRetry.java
index bb2f50e43a..90c7efbf8a 100644
--- a/ratis-common/src/main/java/org/apache/ratis/retry/ExponentialBackoffRetry.java
+++ b/ratis-common/src/main/java/org/apache/ratis/retry/ExponentialBackoffRetry.java
@@ -20,6 +20,7 @@
import org.apache.ratis.util.Preconditions;
import org.apache.ratis.util.TimeDuration;
+import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
/**
@@ -31,7 +32,6 @@
* in the range [s*0.5, s*1.5).
*/
public final class ExponentialBackoffRetry implements RetryPolicy {
-
public static final class Builder {
private Builder() {}
@@ -56,7 +56,7 @@ public Builder setMaxSleepTime(TimeDuration maxSleepTime) {
}
public ExponentialBackoffRetry build() {
- Preconditions.assertNotNull(baseSleepTime, "baseSleepTime");
+ Objects.requireNonNull(baseSleepTime, "baseSleepTime == null");
return new ExponentialBackoffRetry(baseSleepTime, maxSleepTime,
maxAttempts);
}
@@ -67,6 +67,14 @@ public ExponentialBackoffRetry build() {
private final int maxAttempts;
private ExponentialBackoffRetry(TimeDuration baseSleepTime, TimeDuration maxSleepTime, int maxAttempts) {
+ Objects.requireNonNull(baseSleepTime, "baseSleepTime == null");
+ Preconditions.assertTrue(baseSleepTime.isPositive(), () -> "baseSleepTime = " + baseSleepTime + " <= 0");
+ if (maxSleepTime != null) {
+ Preconditions.assertTrue(maxSleepTime.compareTo(baseSleepTime) >= 0,
+ () -> "maxSleepTime = " + maxSleepTime + " < baseSleepTime = " + baseSleepTime);
+ }
+ Preconditions.assertTrue(maxAttempts >= 0, () -> "maxAttempts = " + maxAttempts + " < 0");
+
this.baseSleepTime = baseSleepTime;
this.maxSleepTime = maxSleepTime;
this.maxAttempts = maxAttempts;
diff --git a/ratis-common/src/main/java/org/apache/ratis/retry/MultipleLinearRandomRetry.java b/ratis-common/src/main/java/org/apache/ratis/retry/MultipleLinearRandomRetry.java
index bc453f5bea..9cceb6bc5b 100644
--- a/ratis-common/src/main/java/org/apache/ratis/retry/MultipleLinearRandomRetry.java
+++ b/ratis-common/src/main/java/org/apache/ratis/retry/MultipleLinearRandomRetry.java
@@ -19,8 +19,6 @@
import org.apache.ratis.util.JavaUtils;
import org.apache.ratis.util.TimeDuration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
@@ -34,28 +32,19 @@
* Given pairs of number of retries and sleep time (n0, t0), (n1, t1), ...,
* the first n0 retries sleep t0 milliseconds on average,
* the following n1 retries sleep t1 milliseconds on average, and so on.
- *
+ *
* For all the sleep, the actual sleep time is randomly uniform distributed
* in the close interval [0.5t, 1.5t], where t is the sleep time specified.
- *
+ *
* The objects of this class are immutable.
*/
public final class MultipleLinearRandomRetry implements RetryPolicy {
- static final Logger LOG = LoggerFactory.getLogger(MultipleLinearRandomRetry.class);
-
/** Pairs of numRetries and sleepSeconds */
- private static class Pair {
+ private static final class Pair {
private final int numRetries;
private final TimeDuration sleepTime;
Pair(int numRetries, TimeDuration sleepTime) {
- if (numRetries < 0) {
- throw new IllegalArgumentException("numRetries = " + numRetries+" < 0");
- }
- if (sleepTime.isNegative()) {
- throw new IllegalArgumentException("sleepTime = " + sleepTime + " < 0");
- }
-
this.numRetries = numRetries;
this.sleepTime = sleepTime;
}
@@ -76,9 +65,6 @@ public String toString() {
private final Supplier myString;
private MultipleLinearRandomRetry(List pairs) {
- if (pairs == null || pairs.isEmpty()) {
- throw new IllegalArgumentException("pairs must be neither null nor empty.");
- }
this.pairs = Collections.unmodifiableList(pairs);
this.myString = JavaUtils.memoize(() -> JavaUtils.getClassSimpleName(getClass()) + pairs);
}
@@ -131,30 +117,22 @@ public String toString() {
* @return the parsed object, or null if the parsing fails.
*/
public static MultipleLinearRandomRetry parseCommaSeparated(String input) {
- final String[] elements = input.split(",");
- if (elements.length == 0) {
- LOG.warn("Illegal value: there is no element in \"{}\".", input);
- return null;
+ input = input.trim();
+ if (input.isEmpty()) {
+ throw new IllegalArgumentException("Failed to parse \"" + input + "\": no elements found");
}
+ final String[] elements = input.split(",");
if (elements.length % 2 != 0) {
- LOG.warn("Illegal value: the number of elements in \"{}\" is {} but an even number of elements is expected.",
- input, elements.length);
- return null;
+ throw new IllegalArgumentException("Failed to parse \"" + input
+ + "\": number of elements (" + elements.length + ") is old");
}
final List pairs = new ArrayList<>();
for(int i = 0; i < elements.length; ) {
- //parse the i-th sleep-time
- final TimeDuration sleep = parseElement(elements, i++, input, MultipleLinearRandomRetry::parsePositiveTime);
- if (sleep == null) {
- return null; //parse fails
- }
- //parse the i-th number-of-retries
- final Integer retries = parseElement(elements, i++, input, MultipleLinearRandomRetry::parsePositiveInt);
- if (retries == null) {
- return null; //parse fails
- }
-
+ final TimeDuration sleep = parseElement("sleep-time", elements, i++, input,
+ MultipleLinearRandomRetry::parsePositiveTime);
+ final Integer retries = parseElement("retries", elements, i++, input,
+ MultipleLinearRandomRetry::parsePositiveInt);
pairs.add(new Pair(retries, sleep));
}
return new MultipleLinearRandomRetry(pairs);
@@ -176,13 +154,13 @@ private static int parsePositiveInt(String trimmed) {
return n;
}
- private static E parseElement(String[] elements, int i, String input, Function parser) {
+ private static E parseElement(String name, String[] elements, int i, String input, Function parser) {
final String s = elements[i].trim().replace("_", "");
try {
return parser.apply(s);
- } catch(Exception t) {
- LOG.warn("Failed to parse \"{}\", which is the index {} element in \"{}\"", s, i, input, t);
- return null;
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "Failed to parse \"" + s + "\" as " + name + " (element " + i + " in \"" + input + "\")", e);
}
}
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/retry/RetryPolicy.java b/ratis-common/src/main/java/org/apache/ratis/retry/RetryPolicy.java
index 1de07f19e1..0885e0a44a 100644
--- a/ratis-common/src/main/java/org/apache/ratis/retry/RetryPolicy.java
+++ b/ratis-common/src/main/java/org/apache/ratis/retry/RetryPolicy.java
@@ -19,6 +19,10 @@
import org.apache.ratis.util.TimeDuration;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
/**
* Policy abstract for retrying.
*/
@@ -72,4 +76,57 @@ default Throwable getCause() {
* @return the action it should take.
*/
Action handleAttemptFailure(Event event);
+
+ static RetryPolicy parse(String commaSeparated, String name) {
+ try {
+ return parse(commaSeparated);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse " + name + ": \"" + commaSeparated + "\"", e);
+ }
+ }
+
+ static RetryPolicy parse(String commaSeparated) {
+ Objects.requireNonNull(commaSeparated, "commaSeparated == null");
+ final String[] args = commaSeparated.split(",");
+ if (args.length < 1) {
+ throw new IllegalArgumentException("Failed to parse RetryPolicy: empty comma separated string");
+ }
+ final String classname = args[0].trim();
+ if (classname.equals(ExponentialBackoffRetry.class.getSimpleName())) {
+ if (args.length != 4) {
+ throw new IllegalArgumentException("Failed to parse ExponentialBackoffRetry: args.length = "
+ + args.length + " != 4 for " + commaSeparated);
+ }
+ return ExponentialBackoffRetry.newBuilder()
+ .setBaseSleepTime(TimeDuration.valueOf(args[1], TimeUnit.MILLISECONDS))
+ .setMaxSleepTime(TimeDuration.valueOf(args[2], TimeUnit.MILLISECONDS))
+ .setMaxAttempts(Integer.parseInt(args[3].trim()))
+ .build();
+ }
+ if (classname.equals(MultipleLinearRandomRetry.class.getSimpleName())) {
+ if (args.length == 1) {
+ throw new IllegalArgumentException(
+ "Failed to parse MultipleLinearRandomRetry: the parameter list is empty for " + commaSeparated);
+ }
+ final String params = String.join(",", Arrays.copyOfRange(args, 1, args.length));
+ return MultipleLinearRandomRetry.parseCommaSeparated(params);
+ }
+ // Backward compatibility: legacy config omits class name and starts with a duration (e.g. "1ms").
+ if (isLegacyMultipleLinearRandomRetryParams(classname)) {
+ return MultipleLinearRandomRetry.parseCommaSeparated(commaSeparated);
+ }
+ // If a class name is present but unknown, fail fast to surface config errors.
+ throw new IllegalArgumentException("Failed to parse RetryPolicy: unknown class "
+ + classname + " for " + commaSeparated);
+ }
+
+ static boolean isLegacyMultipleLinearRandomRetryParams(String firstElement) {
+ // The legacy format starts with a duration token, not a class name.
+ try {
+ final TimeDuration t = TimeDuration.valueOf(firstElement, TimeUnit.MILLISECONDS);
+ return t.isPositive();
+ } catch (RuntimeException e) {
+ return false;
+ }
+ }
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/trace/RatisAttributes.java b/ratis-common/src/main/java/org/apache/ratis/trace/RatisAttributes.java
new file mode 100644
index 0000000000..3c3be83e79
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/trace/RatisAttributes.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.ratis.trace;
+
+import io.opentelemetry.api.common.AttributeKey;
+
+/**
+ * The constants in this class correspond with the guidance outlined by the OpenTelemetry Semantic
+ * Conventions.
+ */
+public final class RatisAttributes {
+ public static final AttributeKey CLIENT_ID = AttributeKey.stringKey("raft.client.id");
+ public static final AttributeKey MEMBER_ID = AttributeKey.stringKey("raft.member.id");
+ public static final AttributeKey CALL_ID = AttributeKey.stringKey("raft.call.id");
+
+ public static final AttributeKey PEER_ID = AttributeKey.stringKey("raft.peer.id");
+ public static final AttributeKey OPERATION_NAME = AttributeKey.stringKey("raft.operation.name");
+ public static final AttributeKey OPERATION_TYPE = AttributeKey.stringKey("raft.operation.type");
+
+
+ private RatisAttributes() {
+ }
+}
diff --git a/ratis-common/src/main/java/org/apache/ratis/trace/TraceClient.java b/ratis-common/src/main/java/org/apache/ratis/trace/TraceClient.java
new file mode 100644
index 0000000000..0ab34e689e
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/trace/TraceClient.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.ratis.trace;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import org.apache.ratis.protocol.RaftClientRequest;
+import org.apache.ratis.protocol.RaftPeerId;
+import org.apache.ratis.util.Preconditions;
+import org.apache.ratis.util.function.CheckedSupplier;
+
+import java.util.concurrent.CompletableFuture;
+
+/** Client-side OpenTelemetry helpers. */
+public final class TraceClient {
+ private static final String LEADER = "LEADER";
+
+ private TraceClient() {
+ }
+
+ /**
+ * Traces an asynchronous client send ({@code Async::send}) when tracing is enabled.
+ */
+ public static CompletableFuture asyncSend(
+ CheckedSupplier, THROWABLE> action,
+ RaftClientRequest.Type type, RaftPeerId server) throws THROWABLE {
+ if (!TraceUtils.isEnabled()) {
+ return action.get();
+ }
+ return TraceUtils.traceAsyncMethod(action,
+ () -> createClientOperationSpan(type, server, "Async::send"));
+ }
+
+ private static Span createClientOperationSpan(RaftClientRequest.Type type, RaftPeerId server,
+ String spanName) {
+ Preconditions.assertNotNull(spanName, () -> "Span name cannot be null");
+ Preconditions.assertTrue(!spanName.isEmpty(), "Span name should not be empty");
+ String peerId = server == null ? LEADER : String.valueOf(server);
+ final Span span = TraceUtils.getGlobalTracer()
+ .spanBuilder(spanName)
+ .setSpanKind(SpanKind.CLIENT)
+ .startSpan();
+ span.setAttribute(RatisAttributes.PEER_ID, peerId);
+ span.setAttribute(RatisAttributes.OPERATION_NAME, spanName);
+ span.setAttribute(RatisAttributes.OPERATION_TYPE, String.valueOf(type));
+ return span;
+ }
+}
diff --git a/ratis-common/src/main/java/org/apache/ratis/trace/TraceConfigKeys.java b/ratis-common/src/main/java/org/apache/ratis/trace/TraceConfigKeys.java
new file mode 100644
index 0000000000..b0a1cbd9b1
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/trace/TraceConfigKeys.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.ratis.trace;
+
+import org.apache.ratis.conf.RaftProperties;
+
+import java.util.function.Consumer;
+
+import static org.apache.ratis.conf.ConfUtils.getBoolean;
+import static org.apache.ratis.conf.ConfUtils.setBoolean;
+
+public interface TraceConfigKeys {
+ String PREFIX = "raft.otel.tracing";
+
+ String ENABLED_KEY = PREFIX + ".enabled";
+ boolean ENABLED_DEFAULT = false;
+
+ static boolean enabled(RaftProperties properties, Consumer logger) {
+ return getBoolean(properties::getBoolean, ENABLED_KEY, ENABLED_DEFAULT, logger);
+ }
+
+ static boolean enabled(RaftProperties properties) {
+ return enabled(properties, null);
+ }
+
+ static void setEnabled(RaftProperties properties, boolean enabled) {
+ setBoolean(properties::setBoolean, ENABLED_KEY, enabled);
+ }
+}
+
diff --git a/ratis-common/src/main/java/org/apache/ratis/trace/TraceServer.java b/ratis-common/src/main/java/org/apache/ratis/trace/TraceServer.java
new file mode 100644
index 0000000000..9670f0d763
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/trace/TraceServer.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.ratis.trace;
+
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.context.Context;
+import org.apache.ratis.protocol.RaftClientRequest;
+import org.apache.ratis.util.function.CheckedSupplier;
+
+import java.util.concurrent.CompletableFuture;
+
+/** Server-side OpenTelemetry helpers. */
+public final class TraceServer {
+ private TraceServer() {
+ }
+
+ /**
+ * Traces {@code submitClientRequestAsync} when tracing is enabled.
+ */
+ public static CompletableFuture traceAsyncMethod(
+ CheckedSupplier, THROWABLE> action,
+ RaftClientRequest request, String memberId, String spanName) throws THROWABLE {
+ if (!TraceUtils.isEnabled()) {
+ return action.get();
+ }
+ return TraceUtils.traceAsyncMethod(action,
+ () -> createServerSpanFromClientRequest(request, memberId, spanName));
+ }
+
+ private static Span createServerSpanFromClientRequest(RaftClientRequest request, String memberId,
+ String spanName) {
+ final Context remoteContext = TraceUtils.extractContextFromProto(request.getSpanContext());
+ final Span span = TraceUtils.getGlobalTracer()
+ .spanBuilder(spanName)
+ .setParent(remoteContext)
+ .setSpanKind(SpanKind.SERVER)
+ .startSpan();
+ span.setAttribute(RatisAttributes.CLIENT_ID, String.valueOf(request.getClientId()));
+ span.setAttribute(RatisAttributes.CALL_ID, String.valueOf(request.getCallId()));
+ span.setAttribute(RatisAttributes.MEMBER_ID, memberId);
+ return span;
+ }
+}
diff --git a/ratis-common/src/main/java/org/apache/ratis/trace/TraceUtils.java b/ratis-common/src/main/java/org/apache/ratis/trace/TraceUtils.java
new file mode 100644
index 0000000000..f350ca8884
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/trace/TraceUtils.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.ratis.trace;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.context.propagation.TextMapPropagator;
+import io.opentelemetry.context.propagation.TextMapGetter;
+import org.apache.ratis.conf.RaftProperties;
+import org.apache.ratis.proto.RaftProtos.SpanContextProto;
+import org.apache.ratis.util.JavaUtils;
+import org.apache.ratis.util.function.CheckedSupplier;
+import org.apache.ratis.util.VersionInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
+/** Common OpenTelemetry utilities shared by {@link TraceClient} and {@link TraceServer}. */
+public final class TraceUtils {
+
+ private static final AtomicReference TRACER = new AtomicReference<>();
+ private static final Logger LOG = LoggerFactory.getLogger(TraceUtils.class);
+
+ private TraceUtils() {
+ }
+
+ public static Tracer getGlobalTracer() {
+ return TRACER.get();
+ }
+
+ /**
+ * Initializes the global tracer from configuration when tracing is enabled, or clears it when
+ * disabled. Call from {@link org.apache.ratis.server.RaftServer} and
+ * {@link org.apache.ratis.client.RaftClient} construction so tracing follows
+ * {@link TraceConfigKeys}.
+ *
+ * @param properties raft configuration; tracing is on when {@link TraceConfigKeys#enabled} is true
+ */
+ public static void setTracerWhenEnabled(RaftProperties properties) {
+ setTracerWhenEnabled(TraceConfigKeys.enabled(properties));
+ }
+
+ /**
+ * Enables or disables the tracer without reading {@link RaftProperties}. Intended for tests and
+ * simple toggles; production code should prefer {@link #setTracerWhenEnabled(RaftProperties)}.
+ *
+ * @param enabled when true, lazily obtains the OpenTelemetry tracer; when false, clears it
+ */
+ public static void setTracerWhenEnabled(boolean enabled) {
+ if (enabled) {
+ TRACER.updateAndGet(previous -> previous != null ? previous
+ : GlobalOpenTelemetry.getTracer("org.apache.ratis", VersionInfo.getSoftwareInfoVersion()));
+ } else {
+ TRACER.set(null);
+ }
+ }
+
+ static boolean isEnabled() {
+ return TRACER.get() != null;
+ }
+
+ /**
+ * Traces an asynchronous operation represented by a {@link CompletableFuture}. The returned future
+ * completes with the same outcome as the supplied future; the span is ended when that future
+ * completes.
+ */
+ static CompletableFuture traceAsyncMethod(
+ CheckedSupplier, THROWABLE> action, Supplier spanSupplier) throws THROWABLE {
+ final Span span = spanSupplier.get();
+ try (Scope ignored = span.makeCurrent()) {
+ final CompletableFuture future;
+ try {
+ future = action.get();
+ } catch (RuntimeException | Error e) {
+ setError(span, e);
+ span.end();
+ throw e;
+ } catch (Throwable t) {
+ setError(span, t);
+ span.end();
+ throw JavaUtils.cast(t);
+ }
+ endSpan(future, span);
+ return future;
+ }
+ }
+
+ private static void endSpan(CompletableFuture> future, Span span) {
+ if (span == null) {
+ LOG.debug("Span is null, cannot trace the future {}", future);
+ return;
+ }
+ addListener(future, (resp, error) -> {
+ try {
+ if (error != null) {
+ setError(span, error);
+ } else {
+ span.setStatus(StatusCode.OK);
+ }
+ } catch (Throwable t) {
+ LOG.error("Error setting span status, ending span anyway", t);
+ } finally {
+ span.end();
+ }
+ });
+ }
+
+ public static void setError(Span span, Throwable error) {
+ span.recordException(error);
+ span.setStatus(StatusCode.ERROR);
+ }
+
+ /**
+ * This is method is used when you just want to add a listener to the given future. We will call
+ * {@link CompletableFuture#whenComplete(BiConsumer)} to register the {@code action} to the
+ * {@code future}. Ignoring the return value of a Future is considered as a bad practice as it may
+ * suppress exceptions thrown from the code that completes the future, and this method will catch
+ * all the exception thrown from the {@code action} to catch possible code bugs.
+ *
+ * And the error phone check will always report FutureReturnValueIgnored because every method in
+ * the {@link CompletableFuture} class will return a new {@link CompletableFuture}, so you always
+ * have one future that has not been checked. So we introduce this method and add a suppression
+ * warnings annotation here.
+ */
+ @SuppressWarnings("FutureReturnValueIgnored")
+ private static void addListener(CompletableFuture future,
+ BiConsumer super T, ? super Throwable> action) {
+ future.whenComplete((resp, error) -> {
+ try {
+ // https://s.apache.org/completionexception — unwrap CompletionException for callers
+ action.accept(resp, error == null ? null : JavaUtils.unwrapCompletionException(error));
+ } catch (Throwable t) {
+ LOG.error("Unexpected error caught when processing CompletableFuture", t);
+ }
+ });
+ }
+
+ private static final TextMapPropagator PROPAGATOR =
+ GlobalOpenTelemetry.getPropagators().getTextMapPropagator();
+
+ public static SpanContextProto injectContextToProto(Context context) {
+ Map carrier = new TreeMap<>();
+ PROPAGATOR.inject(context, carrier, (map, key, value) -> map.put(key, value));
+ return SpanContextProto.newBuilder().putAllContext(carrier).build();
+ }
+
+ public static Context extractContextFromProto(SpanContextProto proto) {
+ if (proto == null || proto.getContextMap().isEmpty()) {
+ return Context.current();
+ }
+ final TextMapGetter getter = SpanContextGetter.INSTANCE;
+ return PROPAGATOR.extract(Context.current(), proto, getter);
+ }
+}
+
+class SpanContextGetter implements TextMapGetter {
+ static final SpanContextGetter INSTANCE = new SpanContextGetter();
+
+ @Override
+ public Iterable keys(SpanContextProto carrier) {
+ return carrier.getContextMap().keySet();
+ }
+
+ @Override
+ public String get(SpanContextProto carrier, String key) {
+ return Optional.ofNullable(carrier).map(SpanContextProto::getContextMap)
+ .map(map -> map.get(key)).orElse(null);
+ }
+
+}
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/AtomicFileOutputStream.java b/ratis-common/src/main/java/org/apache/ratis/util/AtomicFileOutputStream.java
index 530eb383c7..ec0eda94f5 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/AtomicFileOutputStream.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/AtomicFileOutputStream.java
@@ -17,6 +17,10 @@
*/
package org.apache.ratis.util;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -24,7 +28,6 @@
import java.io.FilterOutputStream;
import java.io.IOException;
import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -42,7 +45,7 @@
* NOTE that on Windows platforms, the output file, if it exists, is deleted
* before the temporary file is moved.
*/
-public class AtomicFileOutputStream extends FilterOutputStream {
+public final class AtomicFileOutputStream extends FilterOutputStream {
static final Logger LOG = LoggerFactory.getLogger(AtomicFileOutputStream.class);
public static final String TMP_EXTENSION = ".tmp";
@@ -60,11 +63,16 @@ public AtomicFileOutputStream(File outFile) throws IOException {
}
public AtomicFileOutputStream(File outFile, File tmpFile) throws IOException {
- super(FileUtils.newOutputStreamForceAtClose(tmpFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE));
+ super(FileUtils.newOutputStreamForceAtClose(tmpFile, CREATE, TRUNCATE_EXISTING, WRITE));
this.outFile = outFile.getAbsoluteFile();
this.tmpFile = tmpFile.getAbsoluteFile();
}
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ out.write(b, off, len);
+ }
+
public boolean isClosed() {
return isClosed.get();
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/AutoCloseableLock.java b/ratis-common/src/main/java/org/apache/ratis/util/AutoCloseableLock.java
index 8a5409bafe..9581e925a5 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/AutoCloseableLock.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/AutoCloseableLock.java
@@ -17,6 +17,7 @@
*/
package org.apache.ratis.util;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
@@ -45,6 +46,13 @@ public static AutoCloseableLock acquire(final Lock lock, Runnable preUnlock) {
return new AutoCloseableLock(lock, preUnlock);
}
+ public static AutoCloseableLock tryAcquire(final Lock lock, Runnable preUnlock, TimeDuration timeout)
+ throws InterruptedException {
+ Objects.requireNonNull(timeout, "timeout == null");
+ final boolean locked = lock.tryLock(timeout.getDuration(), timeout.getUnit());
+ return locked? new AutoCloseableLock(lock, preUnlock): null;
+ }
+
private final Lock underlying;
private final AtomicBoolean closed = new AtomicBoolean(false);
private final Runnable preUnlock;
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/BatchLogger.java b/ratis-common/src/main/java/org/apache/ratis/util/BatchLogger.java
index 38dad5c499..b57bed704c 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/BatchLogger.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/BatchLogger.java
@@ -45,9 +45,9 @@ default TimeDuration getBatchDuration() {
private static final class UniqueId {
private final Key key;
- private final String name;
+ private final Object name;
- private UniqueId(Key key, String name) {
+ private UniqueId(Key key, Object name) {
this.key = Objects.requireNonNull(key, "key == null");
this.name = name;
}
@@ -99,15 +99,15 @@ private synchronized boolean tryStartBatch(Consumer op) {
private static final TimeoutExecutor SCHEDULER = TimeoutExecutor.getInstance();
private static final ConcurrentMap LOG_CACHE = new ConcurrentHashMap<>();
- public static void warn(Key key, String name, Consumer op) {
- warn(key, name, op, key.getBatchDuration(), true);
+ public static void print(Key key, Object name, Consumer op) {
+ print(key, name, op, key.getBatchDuration(), true);
}
- public static void warn(Key key, String name, Consumer op, TimeDuration batchDuration) {
- warn(key, name, op, batchDuration, true);
+ public static void print(Key key, Object name, Consumer op, TimeDuration batchDuration) {
+ print(key, name, op, batchDuration, true);
}
- public static void warn(Key key, String name, Consumer op, TimeDuration batchDuration, boolean shouldBatch) {
+ public static void print(Key key, Object name, Consumer op, TimeDuration batchDuration, boolean shouldBatch) {
if (!shouldBatch || batchDuration.isNonPositive()) {
op.accept("");
return;
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/BiWeakValueCache.java b/ratis-common/src/main/java/org/apache/ratis/util/BiWeakValueCache.java
new file mode 100644
index 0000000000..52940ed883
--- /dev/null
+++ b/ratis-common/src/main/java/org/apache/ratis/util/BiWeakValueCache.java
@@ -0,0 +1,144 @@
+/*
+ * 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.ratis.util;
+
+import org.apache.ratis.thirdparty.com.google.common.collect.MapMaker;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+
+/**
+ * Weak Value Cache: ({@link OUTER}, {@link INNER}) -> {@link T}.
+ *
+ * Note that the cached values are weakly referenced.
+ * A cached value could be garage-collected (i.e. evicted from the cache)
+ * when there are no external (strong) references.
+ *
+ * @param the type of the outer keys.
+ * @param the type of the inner keys.
+ * @param the type to be cached.
+ */
+public final class BiWeakValueCache {
+ static ConcurrentMap newMap() {
+ return new MapMaker().weakValues().makeMap();
+ }
+
+ private final String outerName;
+ private final String innerName;
+ private final String name;
+
+ /** For constructing {@link T} values from ({@link OUTER}, {@link INNER}) keys. */
+ private final BiFunction constructor;
+ /** Count the number of {@link T} values constructed. */
+ private final AtomicInteger valueCount = new AtomicInteger(0);
+
+ /**
+ * Actual map {@link OUTER} -> ({@link INNER} -> {@link T})
+ * for the logical view ({@link OUTER}, {@link INNER}) -> {@link T}.
+ */
+ private final ConcurrentMap> map = new ConcurrentHashMap<>();
+
+ /**
+ * Create a cache for mapping ({@link OUTER}, {@link INNER}) keys to {@link T} values.
+ *
+ * @param outerName the name of the outer long.
+ * @param innerName the name of the inner long.
+ * @param constructor for constructing {@link T} values.
+ */
+ public BiWeakValueCache(String outerName, String innerName, BiFunction constructor) {
+ this.outerName = outerName;
+ this.innerName = innerName;
+ this.name = "(" + outerName + ", " + innerName + ")-cache";
+ this.constructor = constructor;
+ }
+
+ private T construct(OUTER outer, INNER inner) {
+ final T constructed = constructor.apply(outer, inner);
+ Objects.requireNonNull(constructed, "constructed == null");
+ valueCount.incrementAndGet();
+ return constructed;
+ }
+
+ /**
+ * If the key ({@link OUTER}, {@link INNER}) is in the cache, return the cached values.
+ * Otherwise, create a new value and then return it.
+ */
+ public T getOrCreate(OUTER outer, INNER inner) {
+ Objects.requireNonNull(outer, () -> outerName + " (outer) == null");
+ Objects.requireNonNull(inner, () -> innerName + " (inner) == null");
+ final ConcurrentMap innerMap = map.computeIfAbsent(outer, k -> newMap());
+ final T computed = innerMap.computeIfAbsent(inner, i -> construct(outer, i));
+ if ((valueCount.get() & 0xFFF) == 0) {
+ cleanupEmptyInnerMaps(); // cleanup empty maps once in a while
+ }
+ return computed;
+ }
+
+ /** @return the value count for the given outer key. */
+ int count(OUTER outer) {
+ final ConcurrentMap innerMap = map.get(outer);
+ if (innerMap == null) {
+ return 0;
+ }
+
+ // size() may return incorrect result; see Guava MapMaker javadoc
+ int n = 0;
+ for (INNER ignored : innerMap.keySet()) {
+ n++;
+ }
+ return n;
+ }
+
+ void cleanupEmptyInnerMaps() {
+ // isEmpty() may return incorrect result; see Guava MapMaker javadoc
+ map.values().removeIf(e -> !e.entrySet().iterator().hasNext());
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /** The cache content for debugging. */
+ int dump(Consumer out) {
+ out.accept(name + ":\n");
+ int emptyCount = 0;
+ for (Map.Entry> entry : map.entrySet()) {
+ final OUTER outer = entry.getKey();
+ final ConcurrentMap innerMap = entry.getValue();
+ final int count = count(outer);
+ if (count == 0) {
+ emptyCount++;
+ }
+
+ out.accept(" " + outerName + ":" + outer);
+ out.accept(", " + innerName + ":" + innerMap.keySet());
+ out.accept(", count=" + count);
+ out.accept(", size=" + innerMap.size());
+ out.accept("\n");
+ }
+ out.accept(" emptyCount=" + emptyCount);
+ out.accept("\n");
+ return emptyCount;
+ }
+}
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/CodeInjectionForTesting.java b/ratis-common/src/main/java/org/apache/ratis/util/CodeInjectionForTesting.java
index a7d36ac0eb..112f6bd250 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/CodeInjectionForTesting.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/CodeInjectionForTesting.java
@@ -68,4 +68,9 @@ public static boolean execute(String injectionPoint, Object localId,
}
return code.execute(localId, remoteId, args);
}
+
+ /** Remove an injection point. */
+ public static void remove(String injectionPoint) {
+ INJECTION_POINTS.remove(injectionPoint);
+ }
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/CollectionUtils.java b/ratis-common/src/main/java/org/apache/ratis/util/CollectionUtils.java
index 11f484608a..2615c2659c 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/CollectionUtils.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/CollectionUtils.java
@@ -17,7 +17,14 @@
*/
package org.apache.ratis.util;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.BiFunction;
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/DataBlockingQueue.java b/ratis-common/src/main/java/org/apache/ratis/util/DataBlockingQueue.java
index 842b8f1549..fb0f0715c5 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/DataBlockingQueue.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/DataBlockingQueue.java
@@ -29,6 +29,7 @@
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
import java.util.function.ToLongFunction;
/**
@@ -46,6 +47,8 @@ public class DataBlockingQueue extends DataQueue {
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
+ private boolean closed = false;
+
public DataBlockingQueue(Object name, SizeInBytes byteLimit, int elementLimit, ToLongFunction getNumBytes) {
super(name, byteLimit, elementLimit, getNumBytes);
}
@@ -72,10 +75,34 @@ public void clear() {
}
}
+ /** Apply the given handler to each element and then {@link #clear()}. */
+ public void clear(Consumer handler) {
+ try(AutoCloseableLock auto = AutoCloseableLock.acquire(lock)) {
+ for(E e : this) {
+ handler.accept(e);
+ }
+ super.clear();
+ }
+ }
+
+ /**
+ * Close this queue to stop accepting new elements, i.e. the offer(…) methods always return false.
+ * Note that closing the queue will not clear the existing elements.
+ * The existing elements can be peeked, polled or cleared after close.
+ */
+ public void close() {
+ try(AutoCloseableLock ignored = AutoCloseableLock.acquire(lock)) {
+ closed = true;
+ }
+ }
+
@Override
public boolean offer(E element) {
Objects.requireNonNull(element, "element == null");
try(AutoCloseableLock auto = AutoCloseableLock.acquire(lock)) {
+ if (closed) {
+ return false;
+ }
if (super.offer(element)) {
notEmpty.signal();
return true;
@@ -95,6 +122,9 @@ public boolean offer(E element, TimeDuration timeout) throws InterruptedExceptio
long nanos = timeout.toLong(TimeUnit.NANOSECONDS);
try(AutoCloseableLock auto = AutoCloseableLock.acquire(lock)) {
for(;;) {
+ if (closed) {
+ return false;
+ }
if (super.offer(element)) {
notEmpty.signal();
return true;
@@ -162,4 +192,11 @@ public List pollList(long timeoutM
return results;
}
}
+
+ @Override
+ public E peek() {
+ try(AutoCloseableLock auto = AutoCloseableLock.acquire(lock)) {
+ return super.peek();
+ }
+ }
}
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/DataQueue.java b/ratis-common/src/main/java/org/apache/ratis/util/DataQueue.java
index 3db06f56e6..38762caa17 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/DataQueue.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/DataQueue.java
@@ -154,6 +154,11 @@ public E poll() {
return polled;
}
+ /** Peek the head element from this queue. */
+ public E peek() {
+ return q.peek();
+ }
+
/** The same as {@link java.util.Collection#remove(Object)}. */
public boolean remove(E e) {
final boolean removed = q.remove(e);
diff --git a/ratis-common/src/main/java/org/apache/ratis/util/FileUtils.java b/ratis-common/src/main/java/org/apache/ratis/util/FileUtils.java
index d5141e9171..79c00b5a35 100644
--- a/ratis-common/src/main/java/org/apache/ratis/util/FileUtils.java
+++ b/ratis-common/src/main/java/org/apache/ratis/util/FileUtils.java
@@ -27,12 +27,27 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
-import java.nio.file.*;
+import java.nio.file.AtomicMoveNotSupportedException;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import java.util.function.Supplier;
+import java.util.stream.Stream;
public interface FileUtils {
Logger LOG = LoggerFactory.getLogger(FileUtils.class);
@@ -49,6 +64,39 @@ static T attempt(CheckedSupplier op, Supplier> name) throw
}
}
+ /** @return true iff the given dir is an ancestor of the given sub path. */
+ static boolean isAncestor(File dir, File sub) throws IOException {
+ Objects.requireNonNull(dir, "dir == null");
+ Objects.requireNonNull(sub, "sub == null");
+
+ String dirPath = dir.getCanonicalPath();
+ final String subPath = sub.getCanonicalPath();
+ if (dirPath.equals(subPath)) {
+ return true;
+ } else if (!dirPath.endsWith(File.separator)) {
+ dirPath += File.separator;
+ }
+ LOG.debug("dirPath: {}", dirPath);
+ LOG.debug("subPath: {}", subPath);
+ return subPath.startsWith(dirPath);
+ }
+
+ /**
+ * Resolve the full path from the given dir and sub,
+ * where dir is supposed to be an ancestor of the resolved path.
+ *
+ * @return the full path
+ * @throws IOException if the dir is not an ancestor of the resolved path.
+ */
+ static File resolveFullPath(File dir, String sub) throws IOException {
+ final File full = new File(dir, sub);
+ if (!isAncestor(dir, full)) {
+ throw new IOException("The dir is not an ancestor of the full path: dir=" + dir
+ + ", sub=" + sub + ", full=" + full);
+ }
+ return full;
+ }
+
static void truncateFile(File f, long target) throws IOException {
final long original = f.length();
LogUtils.runAndLog(LOG,
@@ -201,8 +249,10 @@ static File move(File src, String suffix) throws IOException {
}
/** The same as passing f.toPath() to {@link #delete(Path)}. */
- static void deleteFile(File f) throws IOException {
- delete(f.toPath());
+ static Path deleteFile(File f) throws IOException {
+ final Path path = f.toPath();
+ delete(path);
+ return path;
}
/**
@@ -336,4 +386,26 @@ public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOExce
}
});
}
+
+ static void listDir(File dir, Consumer