diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ff3ab1aec336..a214b69e0465 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,10 +30,10 @@ name: "CodeQL" on: push: - branches: [ develop ] + branches: [ support/2.0 ] pull_request: # The branches below must be a subset of the branches above - branches: [ develop ] + branches: [ support/2.0 ] schedule: - cron: '22 22 * * 2' diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8d5a5e36df7a..be35375c0197 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -19,9 +19,9 @@ name: develop on: push: - branches: [ "develop" ] + branches: [ "support/2.0" ] pull_request: - branches: [ "develop" ] + branches: [ "support/2.0" ] permissions: contents: read @@ -32,14 +32,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: java-version: '17' distribution: 'liberica' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run 'build install javadoc spotlessCheck rat checkPom resolveDependencies pmdMain' with Gradle @@ -57,9 +57,9 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: | @@ -96,15 +96,15 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK (include all 3 JDKs in env) - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: | 17 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Set JAVA_TEST_PATH to 17 @@ -127,7 +127,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ test --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: unit-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -145,15 +145,15 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: | 17 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run integration tests @@ -174,7 +174,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ integrationTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: integration-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -192,14 +192,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run acceptance tests @@ -218,7 +218,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ acceptanceTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: acceptance-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -236,14 +236,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run wan distributed tests @@ -264,7 +264,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ geode-wan:distributedTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: wan-distributed-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -282,14 +282,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run cq distributed tests @@ -308,7 +308,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ geode-cq:distributedTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: cq-distributed-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -326,14 +326,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run lucene distributed tests @@ -354,7 +354,7 @@ jobs: -PtestJVMVer=${JAVA_TEST_VERSION} \ -PtestJava17Home=${JAVA_HOME_17_X64} \ geode-lucene:distributedTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: lucene-distributed-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -372,14 +372,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run gfsh, web-mgmt, web distributed tests @@ -401,7 +401,7 @@ jobs: geode-gfsh:distributedTest \ geode-web:distributedTest \ geode-web-management:distributedTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: mgmt-distributed-test-reports-${{ matrix.os }}-${{ matrix.java }} @@ -420,14 +420,14 @@ jobs: env: DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/actions/setup-gradle@v5 + uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c with: gradle-version: wrapper - name: Run assembly, connectors, old-client, extensions distributed tests @@ -452,7 +452,7 @@ jobs: geode-old-client:distributedTest \ extensions:geode-modules:distributedTest \ extensions:geode-modules-tomcat10:distributedTest --console=plain --no-daemon - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: failure() with: name: assembly-distributed-test-reports-${{ matrix.os }}-${{ matrix.java }} diff --git a/KEYS b/KEYS index de81511da563..3b2ca7c376ae 100644 --- a/KEYS +++ b/KEYS @@ -1806,3 +1806,63 @@ ReHLdfM+xSILW9t8kCkcx28kYYDh9OYUPA== =MdLY -----END PGP PUBLIC KEY BLOCK----- +pub rsa4096 2025-08-28 [SC] [expires: 2029-08-28] + 5C3DA8FBB1052F4DF1DEB1EF62F7DA41B7D8F26C +uid [ultimate] Jinwoo Hwang +sig 3 62F7DA41B7D8F26C 2025-08-28 Jinwoo Hwang +sub rsa4096 2025-08-28 [E] [expires: 2029-08-28] +sig 62F7DA41B7D8F26C 2025-08-28 Jinwoo Hwang + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGiwjWgBEACoozACx4fb8PRYrJ33AhRLOVCEiCg3JskUZgBJuk9pEcVMQp6S +/3/C7ig2Denb5SLjBIKWItmr8fOoBw8+CW8XLt43AIMvKxjBvLDrEkZIeyzQmgiU +5MLJQRmM/Fs1o1bzYRIXULDkgvQe2QMyBd/n7rvrObeVyF5QjTi61LOeB1xG2tiL +fV5ASpfhLYjy/U6dkzHA/BjiZac0Lxmw6z2rBqCA8s2Milaaz5i7jH7o6WIQcbTo +gHLf/2XPUIfIfcs0wi1jPWO6jgCZ/NWHg3b3kGL8pT3TzF/GxwNJ4aKgOdgUk2KU +22+sdfdwcZ8pxIbnKJS1PCDVfTxdsyeHQtpAIn30dxs9XnaLh4oILFHqzMRqUSJf +F7/4RjACqL8vGnO+NAbyJ0QIpJjQJmHAgiydjtFbLEtFP4HTYdNsLZaIUZ0+I4Kh +Y6F33rCNHxdKHgFzFLpzdIDMow1nrDwVNTPDLujel+mlZxtqckEidKKg96b+TuK9 +gQ/3+Ct12ZUGifjovGsxExE3PLFNKsiKgtlfsheMnRhPdbDSbjTUxh9fhwpm2lrF +XaLQ7JD72wAMyX4H4JoHbrBmmpbXvOrdrn68ZuCCC7UfNo0m4yMmkvxx9zHQLxPC +hWC5lvOE29obmST7Sfisu17I1NprtFS1wOzZ4JKPxpWOlySW5NuEdBiQRQARAQAB +tCBKaW53b28gSHdhbmcgPGppbndvb0BhcGFjaGUub3JnPokCVAQTAQgAPhYhBFw9 +qPuxBS9N8d6x72L32kG32PJsBQJosI1oAhsDBQkHhh4zBQsJCAcCBhUKCQgLAgQW +AgMBAh4BAheAAAoJEGL32kG32PJs+0cQAJzOubE2cqGEVfcAIg6rqL4GyELB553u +3ID3rjTI1vZSIN/EbB3ANK5IwX3KEPepqH6sxug13fKSMBbhw1OYDbo6xxVBobAR +tGPlUpWpFKEyP3p8B1EE8NmJRoHmKRSnpo6/U2owRdXg7WbjaQ95tOb0IqCBt0Jx +vJUHvAJJMmmxSHH2Gw8Ztx8G6BSkVADhGBSwad5I39xzAe1jN7blDCxMIGKP68HB +TiyTgIj+N76TBA6OMnCacFXdNXpf4U6/18yKB8VtTCy1HdA9IbHbN2eWrFb1+0Dg +dTVWzvK1pT5fNLPu+ueZFTCnKBe+6u8OQn0s3t7RhA+8hqbb3xstf+hbugHo+3nK +Jrg5AitJNv3fM/Lo733y+lgezj5H1b4Pmp0dThRh13JT5iPnUYyRBhuUn/SbZJIu +/Rx+TXf7RdwXpH71inn51N2ZTcCgYVxY5FNACIkcozYNEQZ6AuPJUBp0OYS4VYlj +U3p4CQjRzWPBYpf1FPD+PJbxZDpR5ZTKXrQ5b8wzucvQt+HgPwD/Ccz4yjyEGNfZ +dC7gMROaV0RJLyiDCMIttci/JUkdZU0gmsoYQQG7/Nx4B7xCtSVla+9UHWFcsps+ +OT4pbqVgK9ED3Ef3JxJx55NSKE5t4FpK2nh4b/xAHbCOm5dgwvribTN3okDE/ldf +IGKjWvoZiAQ2uQINBGiwjWgBEADaMacdZjaxyj1VM2fd3sxlamPizon9Qn3NieC8 +ayY163MgdGQdRWbSChIG1djiN2QG0KT1M0PZGZQXrP7FUD8dLiM8JxpTLb2wrtLP +OMAWaps4fcyvT757G1nDki3w9B3m1uC6SyFIcpMJ+Oc6sp2e7a2FsZTgxk+hi8gs +yePBpyd1ipwlaKmhbUGeYApxTipVxLukkNEY9tpjFmZwYnNDCKoI/Wzjnwjpqw0k +LqN9vBuh/l5zXI6x5LUFrW9NDi+pFtpIcQxfg+itflixmblvqaZO7LY8Q4AyBMKp +UuPGz62+IaV+37pUPvHx1fdNrCBseLrxvRIzEZBk/ZhtUEJpRoKAwC35yt7/iTVC +TJvPK2uL6EdW2/CiJO8Bm4ELBJhGug+H/JlT1ToEcqKQFVxDo8h/gyHhu0VQcT5F +6dH/sjeQMbTpoIj3NZvKVGgTJa9o2PvADyQncwJrWFcje1iW9zpeHTbcm9RoZk/v +J6HfV4uPPLDwoFbNeizP9neYCCn9NFNAfs3lH/YqHNjsKxDoCHs10v4Y275xv5I0 +Qq+NZNnVJ9perrsrd0ppXdD1nsLhNQhGPM0ZV+LIyPPuIu9WJC+N9rNOTp8d3Hyq +SXTbD9no4YhXqradpL0NFS4Ez2RZMUtDQXXYThKAjYQN9N8HJ4a0z/todvNMhCiS +TCQppwARAQABiQI8BBgBCAAmFiEEXD2o+7EFL03x3rHvYvfaQbfY8mwFAmiwjWgC +GwwFCQeGHjMACgkQYvfaQbfY8mwSmBAAoagKdXzFPmtXUc2TgWk0won9JxQK00SH +cuR3h5RI/hEpWY/wDNhY8UBTQgD7TWqdmpmb7cyQ+woQy+WIa3L4f3ToJPXXG7St +jwyircasjNgxotH1ijy/Z0ti9r8v2BWlodiyZKnZrVBvp2UQulL2YNe9pJu+NOgC +NkrIq5v9BilomeFcmuaoY+BDJpJodntvlbU6etu0+mqFxjyX66b8bA3ttDdTnvxF +yup1DSTxQCLgoXUzkf6oQsu7w17scujLYxvmfh153oJsrridcL5Y/8IggEBFsEx9 +FnnZQFW1+kdaVFfLSjPXpM3gAs0CkI5tWHT4DNFeaSBX2DR4BItRJq54oAUwiPiD +xEyn/heueBgZSlUx9GAE3ImUSlgsoAm/jFe6NAKKoSLPnctwK5Q14N8hUZIi2qhm +gIRvrvF9Cw2W/UomSp0gQrdRu/bFtUnVhrbSQLf3fp2hG4DJP4VY8HFyavasw33S +ydlCm4mEB5wxcojgDSVuMik1OZjnpnZAQUFVHPTVf7h5+mmeSA2TdRSB7ZUpDCXm +Nwt0s21Y4w7FqWBcaHV8/WBssiW5bAyUy8VMPSW+qsFfd1hFKaK5eXKqGnq4YJvK +oBmNUQimrn+QHgNk/XrL6MUDbcM9/Cy2YZqfiTuiKCfaM8KeyObj4PCy1fLfoj+A +xpn4UB+I3HA= +=VxEu +-----END PGP PUBLIC KEY BLOCK----- + diff --git a/NOTICE b/NOTICE index 2d5edd3ae106..f67a3560a8be 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache Geode -Copyright 2016-2025 The Apache Software Foundation. +Copyright 2016-2026 The Apache Software Foundation. This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index 4dd74940bb61..c74236bb4953 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Apache Geode logo](https://geode.apache.org/img/Apache_Geode_logo.png)](http://geode.apache.org) [![Build Status](https://concourse.apachegeode-ci.info/api/v1/teams/main/pipelines/apache-develop-main/badge)](https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-develop-main) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.geode/geode-core/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.geode%22) [![homebrew](https://img.shields.io/homebrew/v/apache-geode.svg)](https://formulae.brew.sh/formula/apache-geode) [![Docker Pulls](https://img.shields.io/docker/pulls/apachegeode/geode.svg)](https://hub.docker.com/r/apachegeode/geode/) [![Total alerts](https://img.shields.io/lgtm/alerts/g/apache/geode.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/apache/geode/alerts/) [![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/apache/geode.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/apache/geode/context:java) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/apache/geode.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/apache/geode/context:javascript) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/apache/geode.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/apache/geode/context:python) - ## Contents diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml index 3d59bbebbab6..84d481c32760 100644 --- a/boms/geode-all-bom/src/test/resources/expected-pom.xml +++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml @@ -195,7 +195,7 @@ io.micrometer micrometer-core - 1.14.0 + 1.16.7 io.swagger.core.v3 @@ -415,7 +415,7 @@ org.slf4j slf4j-api - 2.0.17 + 2.0.18 org.springframework.hateoas @@ -470,27 +470,27 @@ com.fasterxml.jackson.core jackson-annotations - 2.17.0 + 2.21 com.fasterxml.jackson.core jackson-core - 2.17.0 + 2.21.6 com.fasterxml.jackson.core jackson-databind - 2.17.0 + 2.21.6 com.fasterxml.jackson.datatype jackson-datatype-joda - 2.17.0 + 2.21.6 com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.17.0 + 2.21.6 com.jayway.jsonpath @@ -530,27 +530,27 @@ org.apache.logging.log4j log4j-api - 2.17.2 + 2.25.5 org.apache.logging.log4j log4j-core - 2.17.2 + 2.25.5 org.apache.logging.log4j log4j-jcl - 2.17.2 + 2.25.5 org.apache.logging.log4j log4j-jul - 2.17.2 + 2.25.5 org.apache.logging.log4j log4j-slf4j-impl - 2.17.2 + 2.25.5 org.apache.lucene @@ -715,17 +715,27 @@ org.springframework.boot spring-boot-starter - 2.6.7 + 3.3.13 org.springframework.boot spring-boot-starter-jetty - 2.6.7 + 3.3.13 + + + org.springframework.boot + spring-boot-starter-validation + 3.3.13 org.springframework.boot spring-boot-starter-web - 2.6.7 + 3.3.13 + + + org.springframework.boot + spring-boot-autoconfigure + 3.3.13 org.jetbrains diff --git a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy index d8c75391ae22..483208269cae 100644 --- a/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy +++ b/build-tools/geode-dependency-management/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy @@ -46,24 +46,30 @@ class DependencyConstraints { deps.put("jakarta.annotation.version", "2.1.1") deps.put("jakarta.ejb.version", "4.0.1") deps.put("jgroups.version", "3.6.20.Final") - deps.put("log4j.version", "2.17.2") + deps.put("log4j.version", "2.25.5") deps.put("log4j-slf4j2-impl.version", "2.23.1") - deps.put("micrometer.version", "1.14.0") - deps.put("shiro.version", "1.13.0") - deps.put("slf4j-api.version", "2.0.17") + deps.put("micrometer.version", "1.16.7") + deps.put("shiro.version", "3.0.0") + // GEODE-10583: Pin Bouncy Castle (transitive via shiro-crypto-hash) to a fixed version + deps.put("bouncycastle.version", "1.85") + deps.put("slf4j-api.version", "2.0.18") deps.put("jakarta.transaction-api.version", "2.0.1") deps.put("jboss-modules.version", "1.11.0.Final") - deps.put("jackson.version", "2.17.0") - deps.put("jackson.databind.version", "2.17.0") + deps.put("jackson.version", "2.21.6") + deps.put("jackson.annotations.version", "2.21") + deps.put("jackson.databind.version", "2.21.6") // Spring Framework 6.x Migration deps.put("springshell.version", "3.3.3") - deps.put("springframework.version", "6.1.14") - deps.put("springboot.version", "3.3.5") + deps.put("springframework.version", "6.1.21") + deps.put("springboot.version", "3.3.13") deps.put("springsecurity.version", "6.3.4") deps.put("springhateoas.version", "2.3.3") deps.put("springldap.version", "3.2.7") deps.put("springdoc.version", "2.6.0") + // Pin Reactor Core (transitive via spring-shell-core) to a fixed version + deps.put("reactor-core.version", "3.8.7") + // These version numbers are used in testing various versions of tomcat and are consumed explicitly // in will be called explicitly in the relevant extensions module, and respective configurations // in geode-assembly.gradle. Moreover, dependencyManagement does not seem to play nicely when @@ -82,7 +88,7 @@ class DependencyConstraints { // at o.a.g.sessions.tests.GenericAppServerInstall.java // Jetty 12.0.x for Jakarta EE 10 (Servlet 6.0) compatibility // Jetty 12 reorganized modules under ee10, ee9, ee8 packages - deps.put("jetty.version", "12.0.27") + deps.put("jetty.version", "12.0.37") // These versions are referenced in test.gradle, which is aggressively injected into all projects. deps.put("junit.version", "4.13.2") @@ -105,6 +111,8 @@ class DependencyConstraints { project.dependencies { constraints { + api('org.jline:jline-terminal-jni:3.26.3') + api('org.jline:jline-terminal-ffm:3.26.3') // informal, inter-group dependencySet api(group: 'antlr', name: 'antlr', version: get('antlr.version')) api(group: 'cglib', name: 'cglib', version: get('cglib.version')) @@ -122,7 +130,7 @@ class DependencyConstraints { // Pinning transitive dependency from spring-security-oauth2 to clean up our licenses. api(group: 'com.nimbusds', name: 'oauth2-oidc-sdk', version: '8.9') api(group: 'jakarta.activation', name: 'jakarta.activation-api', version: get('jakarta.activation.version')) - api(group: 'com.sun.istack', name: 'istack-commons-runtime', version: '4.0.1') + api(group: 'com.sun.istack', name: 'istack-commons-runtime', version: '4.1.1') api(group: 'jakarta.mail', name: 'jakarta.mail-api', version: get('jakarta.mail.version')) api(group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: get('jakarta.xml.bind.version')) api(group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '4.0.2') @@ -144,6 +152,8 @@ class DependencyConstraints { api(group: 'io.github.resilience4j', name: 'resilience4j-retry', version: '1.7.1') api(group: 'io.lettuce', name: 'lettuce-core', version: '6.1.8.RELEASE') api(group: 'io.micrometer', name: 'micrometer-core', version: get('micrometer.version')) + // Pin Reactor Core (pulled in via spring-shell-core) to 3.8.7 + api(group: 'io.projectreactor', name: 'reactor-core', version: get('reactor-core.version')) api(group: 'io.swagger.core.v3', name: 'swagger-annotations', version: '2.2.22') api(group: 'org.hdrhistogram', name: 'HdrHistogram', version: '2.2.2') api(group: 'it.unimi.dsi', name: 'fastutil', version: get('fastutil.version')) @@ -171,13 +181,15 @@ class DependencyConstraints { api(group: 'org.apache.commons', name: 'commons-text', version: 1.9) api(group: 'org.apache.derby', name: 'derby', version: '10.14.2.0') // Apache HttpComponents 5.x - Modern HTTP client with HTTP/2 support - api(group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.4.4') - api(group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '5.3.4') - api(group: 'org.apache.httpcomponents.core5', name: 'httpcore5-h2', version: '5.3.4') + api(group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.6.4') + api(group: 'org.apache.httpcomponents.core5', name: 'httpcore5', version: '5.4.3') + api(group: 'org.apache.httpcomponents.core5', name: 'httpcore5-h2', version: '5.4.3') // Legacy HttpComponents 4.x (keep temporarily during migration, remove after complete) api(group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13') api(group: 'org.apache.httpcomponents', name: 'httpcore', version: '4.4.15') api(group: 'org.apache.shiro', name: 'shiro-core', version: get('shiro.version')) + // GEODE-10583: Pin Bouncy Castle provider (pulled in via shiro-crypto-hash) to 1.84 + api(group: 'org.bouncycastle', name: 'bcprov-jdk18on', version: get('bouncycastle.version')) api(group: 'org.assertj', name: 'assertj-core', version: '3.22.0') api(group: 'org.awaitility', name: 'awaitility', version: '4.2.0') api(group: 'org.buildobjects', name: 'jproc', version: '2.8.0') @@ -218,8 +230,11 @@ class DependencyConstraints { entry('mockito-junit-jupiter') } - dependencySet(group: 'com.fasterxml.jackson.core', version: get('jackson.version')) { + dependencySet(group: 'com.fasterxml.jackson.core', version: get('jackson.annotations.version')) { entry('jackson-annotations') + } + + dependencySet(group: 'com.fasterxml.jackson.core', version: get('jackson.version')) { entry('jackson-core') } @@ -232,6 +247,10 @@ class DependencyConstraints { entry('jackson-datatype-jsr310') } + dependencySet(group: 'com.fasterxml.jackson.dataformat', version: get('jackson.version')) { + entry('jackson-dataformat-yaml') + } + dependencySet(group: 'com.jayway.jsonpath', version: '2.7.0') { entry('json-path-assert') entry('json-path') @@ -254,6 +273,7 @@ class DependencyConstraints { dependencySet(group: 'org.apache.logging.log4j', version: get('log4j.version')) { entry('log4j-api') entry('log4j-core') + entry('log4j-core-test') entry('log4j-jcl') entry('log4j-jul') entry('log4j-slf4j-impl') diff --git a/build-tools/scripts/src/main/groovy/geode-test.gradle b/build-tools/scripts/src/main/groovy/geode-test.gradle index 93488986e512..602f0b731651 100644 --- a/build-tools/scripts/src/main/groovy/geode-test.gradle +++ b/build-tools/scripts/src/main/groovy/geode-test.gradle @@ -182,7 +182,6 @@ gradle.taskGraph.whenReady({ graph -> if (project.hasProperty('testJVMVer') && testJVMVer.toInteger() >= 9) { jvmArgs += [ "--add-opens=java.base/java.io=ALL-UNNAMED", - "--add-opens=java.base/java.lang=ALL-UNNAMED", "--add-opens=java.base/java.lang.annotation=ALL-UNNAMED", "--add-opens=java.base/java.lang.module=ALL-UNNAMED", "--add-opens=java.base/java.lang.ref=ALL-UNNAMED", diff --git a/build.gradle b/build.gradle index 59c2e0a2ed9a..f18bc03e23a1 100755 --- a/build.gradle +++ b/build.gradle @@ -80,8 +80,11 @@ allprojects { // Exclude 'log4j-to-slf4j' globally. Geode's logging architecture requires Log4j Core to be the primary logging implementation, // with SLF4J calls being routed TO Log4j (via log4j-slf4j-impl), not the other way around. // + // Logback is unused (transitive from spring-boot-starter-logging). Geode uses Log4j 2 for all logging. + // configurations.all { exclude group: 'org.apache.logging.log4j', module: 'log4j-to-slf4j' + exclude group: 'ch.qos.logback' } buildRoot = buildRoot.trim() diff --git a/ci/docker/cache_dependencies.sh b/ci/docker/cache_dependencies.sh index 0b72de826c0d..4088a58980b5 100644 --- a/ci/docker/cache_dependencies.sh +++ b/ci/docker/cache_dependencies.sh @@ -20,7 +20,7 @@ set -e WORK_DIR=$(mktemp -d) pushd ${WORK_DIR} - git clone -b develop --depth 1 https://github.com/apache/geode.git geode + git clone -b support/2.0 --depth 1 https://github.com/apache/geode.git geode pushd geode ./gradlew --no-daemon --console=plain --info resolveDependencies diff --git a/ci/images/google-geode-builder/scripts/cache_dependencies.sh b/ci/images/google-geode-builder/scripts/cache_dependencies.sh index ef56172a9b74..e41f225230e7 100644 --- a/ci/images/google-geode-builder/scripts/cache_dependencies.sh +++ b/ci/images/google-geode-builder/scripts/cache_dependencies.sh @@ -28,7 +28,7 @@ if [ -z ${JAVA_HOME} ]; then fi pushd ${WORK_DIR} - git clone -b develop --depth 1 https://github.com/apache/geode.git geode + git clone -b support/2.0 --depth 1 https://github.com/apache/geode.git geode pushd geode ./gradlew --no-daemon --console=plain --info resolveDependencies diff --git a/ci/pipelines/shared/jinja.variables.yml b/ci/pipelines/shared/jinja.variables.yml index a5d455af2347..7979f1d84145 100644 --- a/ci/pipelines/shared/jinja.variables.yml +++ b/ci/pipelines/shared/jinja.variables.yml @@ -17,7 +17,7 @@ benchmarks: baseline_branch_default: '' - baseline_version_default: '1.15.1' + baseline_version_default: '2.0.0' benchmark_branch: ((geode-build-branch)) flavors: - title: 'base' diff --git a/dev-tools/release/README.md b/dev-tools/release/README.md index a5a51e20bb15..4bc08cde7dfb 100644 --- a/dev-tools/release/README.md +++ b/dev-tools/release/README.md @@ -1,31 +1 @@ -# geode/dev-tools/release -This directory contains scripts to help create a release of Geode and manage branches. - -Not all release steps have scripts. Please follow all instructions as documented in the wiki: [Releasing Apache Geode](https://cwiki.apache.org/confluence/display/GEODE/Releasing+Apache+Geode). - -These scripts are intended to be run from the parent directory of your geode develop checkout, e.g.: - - $ cd .. - $ geode/dev-tools/release/foo.sh - -## Overview of scripts - -**license\_review.sh** compares versions with a previous release and/or checks that all bundled dependencies are noted in appropriate LICENSE file - -**create\_support\_branches.sh** cuts support/x.y from develop for all projects and walks you through creating pipelines and setting version numbers - -**set\_copyright.sh** updates the copyright year - -**set\_versions.sh** updates files that need to contain the version number planned for the next release from this support branch - -**prepare\_rc.sh** Checks out the various geode repos, builds a release candidate, and publishes to nexus staging repo - -**commit\_rc.sh** Pushes the tags and artifacts staged by prepare\_rc.sh and then runs print\_rc\_email.sh - -**print\_rc\_email.sh** Generates an email to send to the dev list announcing a release candidate - -**promote\_rc.sh** Tags an RC as the final release, builds docker images, merges to master, uploads to mirrors, and starts the brew process - -**print\_annouce\_email.sh** Generates an email to send to all lists announcing a release - -**end\_of\_support.sh** cleans up pipelines and branches after N-2 support lifetime is reached +See [Releasing Apache Geode](https://cwiki.apache.org/confluence/display/GEODE/Releasing+Apache+Geode) diff --git a/dev-tools/release/commit_rc.sh b/dev-tools/release/commit_rc.sh deleted file mode 100755 index eb454795bf75..000000000000 --- a/dev-tools/release/commit_rc.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/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 - -usage() { - echo "Usage: commit_rc.sh -j ticket -v version_number -m maven_repo_id" - echo " -j The GEODE-nnnnn Jira identifier for this release" - echo " -v The #.#.#.RC# version number" - echo " -m The 4 digit id of the nexus maven repo" - exit 1 -} - -JIRA="" -FULL_VERSION="" -MAVEN="" - -while getopts ":j:v:m:" opt; do - case ${opt} in - j ) - JIRA=$OPTARG - ;; - v ) - FULL_VERSION=$OPTARG - ;; - m ) - MAVEN=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${JIRA} == "" ]] || [[ ${FULL_VERSION} == "" ]] || [[ ${MAVEN} == "" ]]; then - usage -fi - -if [[ $FULL_VERSION =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.(RC[0-9]+)$ ]]; then - VERSION=${BASH_REMATCH[1]} -else - echo "Malformed version number ${FULL_VERSION}. Example valid version: 1.9.0.RC1" - exit 1 -fi - -VERSION_MM=${VERSION%.*} - -set -x -WORKSPACE=$PWD/release-${VERSION}-workspace -GEODE=$WORKSPACE/geode -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_NATIVE=$WORKSPACE/geode-native -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -SVN_DIR=$WORKSPACE/dist/dev/geode -set +x - -if [ -d "$GEODE" ] && [ -d "$GEODE_EXAMPLES" ] && [ -d "$GEODE_NATIVE" ] && [ -d "$GEODE_BENCHMARKS" ] && [ -d "$SVN_DIR" ] ; then - true -else - echo "Please run this script from the same working directory as you initially ran prepare_rc.sh" - exit 1 -fi - - -function failMsg { - errln=$1 - echo "ERROR: script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 87-$(( errln - 1 ))) and try again" -} -trap 'failMsg $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Publishing artifacts to apache release location..." -echo "============================================================" -set -x -cd ${SVN_DIR} -svn commit -m "$JIRA: Release Apache Geode ${FULL_VERSION} - -Publish the source, binary, and checksum artifacts to ASF svn server, -from which they will be picked up and published within 15 minutes to -the URLs on https://geode.apache.org/releases/" -set +x - - -echo "" -echo "============================================================" -echo "Adding temporary commit for geode-examples to build against staged ${FULL_VERSION}..." -echo "============================================================" -set -x -cd ${GEODE_EXAMPLES} -set +x -sed -e 's#^geodeRepositoryUrl *=.*#geodeRepositoryUrl = https://repository.apache.org/content/repositories/orgapachegeode-'"${MAVEN}#" \ - -e 's#^geodeReleaseUrl *=.*#geodeReleaseUrl = https://dist.apache.org/repos/dist/dev/geode/'"${FULL_VERSION}#" -i.bak gradle.properties -rm gradle.properties.bak -set -x -git add gradle.properties -git diff --staged --color | cat -git commit -m "$JIRA: Set temporary staging repo - -This serves two purposes: it gives the RC pipeline a way to get the -nexus staging repo id needed for various tests, and it gives the -Jenkins server a valid configuration during the voting period." -git push -set +x - - -echo "" -echo "============================================================" -echo "Keeping -build.0 suffix" -echo "============================================================" -cd ${GEODE}/../.. -set -x -${0%/*}/set_versions.sh -j $JIRA -v ${VERSION} -s -n -w "${WORKSPACE}" -set +x - - -echo "" -echo "============================================================" -echo "Pushing copyrights, versions, and tags..." -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git pull -r - git push -u origin - git push origin rel/v${FULL_VERSION} - set +x -done - - -echo "" -echo "============================================================" -echo "Done publishing the release candidate! Next steps:" -echo "============================================================" -cd ${GEODE}/../.. -echo "1. In a separate terminal window, ${0%/*}/deploy_rc_pipeline.sh -v ${VERSION_MM}" -echo "2. Monitor https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-support-${VERSION_MM//./-}-rc until all green" -echo "3. If you haven't already, add a ${VERSION} section to https://cwiki.apache.org/confluence/display/GEODE/Release+Notes" -JIRABASE=https://issues.apache.org/jira/secure -jiraverid=$(curl -fs $JIRABASE'/ConfigureReleaseNote.jspa?projectId=12318420' | tr -d ' \n' | tr '<' '\n'| awk '/optionvalue.*'$VERSION'$/{sub(/optionvalue="/,"");sub(/">.*/,"");print}') -echo " The 'full list' link will be $JIRABASE/ReleaseNote.jspa?projectId=12318420&version=$jiraverid" -echo "4. Send the following email to announce the RC:" -echo "To: dev@geode.apache.org" -echo "Subject: [VOTE] Apache Geode ${FULL_VERSION}" -${0%/*}/print_rc_email.sh -v ${FULL_VERSION} -m ${MAVEN} -echo "" -which pbcopy >/dev/null && ${0%/*}/print_rc_email.sh -v ${FULL_VERSION} -m ${MAVEN} | pbcopy && echo "(copied to clipboard)" diff --git a/dev-tools/release/create_support_branches.sh b/dev-tools/release/create_support_branches.sh deleted file mode 100755 index c13e4111012d..000000000000 --- a/dev-tools/release/create_support_branches.sh +++ /dev/null @@ -1,292 +0,0 @@ -#!/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 - -usage() { - echo "Usage: create_support_branches.sh -j ticket -v version_number -g your_github_username" - echo " -j The GEODE-nnnnn Jira identifier for this release" - echo " -v The #.# version number of the support branch to create" - echo " -g Your github username" - exit 1 -} - -JIRA="" -VERSION_MM="" -GITHUB_USER="" - -while getopts ":j:v:g:" opt; do - case ${opt} in - j ) - JIRA=$OPTARG - ;; - v ) - VERSION_MM=$OPTARG - ;; - g ) - GITHUB_USER=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${JIRA} == "" ]] || [[ ${VERSION_MM} == "" ]] || [[ ${GITHUB_USER} == "" ]] ; then - usage -fi - -if [[ $VERSION_MM =~ ^([0-9]+\.[0-9]+)$ ]]; then - true -else - echo "Malformed version number ${VERSION_MM}. Example valid version: 1.9" - exit 1 -fi - -MAJOR=${VERSION_MM%.*} -MINOR=${VERSION_MM#*.} - -#tip: hardcode NEWMAJOR and NEWMINOR as needed if jumping to a new major -NEWMAJOR=${MAJOR} -NEWMINOR=$((MINOR + 1)) - -NEWVERSION_MM=${NEWMAJOR}.${NEWMINOR} -NEWVERSION_MM_NODOT=${NEWVERSION_MM//./} -NEWVERSION=${NEWVERSION_MM}.0 - -set -x -WORKSPACE=$PWD/support-${VERSION_MM}-workspace -GEODE=$WORKSPACE/geode -GEODE_DEVELOP=$WORKSPACE/geode-develop -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_EXAMPLES_DEVELOP=$WORKSPACE/geode-examples-develop -GEODE_NATIVE=$WORKSPACE/geode-native -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -set +x - - -function failMsg1 { - echo "ERROR: script did NOT complete successfully. Please try again." -} -trap failMsg1 ERR - - -echo "" -echo "============================================================" -echo "Cleaning workspace directory..." -echo "============================================================" -set -x -rm -rf $WORKSPACE -mkdir -p $WORKSPACE -set +x - - -echo "" -echo "============================================================" -echo "Cloning repositories..." -echo "============================================================" -set -x -cd ${WORKSPACE} -git clone --single-branch --branch develop git@github.com:apache/geode.git -#(cd geode; git reset --hard $desired_sha) #uncomment if latest commit is not the desired branchpoint -git clone --single-branch --branch develop git@github.com:apache/geode.git geode-develop -git clone --single-branch --branch develop git@github.com:apache/geode-examples.git -git clone --single-branch --branch develop git@github.com:apache/geode-examples.git geode-examples-develop -git clone --single-branch --branch develop git@github.com:apache/geode-native.git -git clone --single-branch --branch develop git@github.com:apache/geode-benchmarks.git -set +x - - -function failMsg2 { - errln=$1 - echo "ERROR: script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 88-$(( errln - 1 ))) and try again" -} -trap 'failMsg2 $LINENO' ERR - - -cd ${GEODE}/../.. -set -x -${0%/*}/set_copyright.sh -j $JIRA ${GEODE} ${GEODE_DEVELOP} ${GEODE_EXAMPLES} ${GEODE_EXAMPLES_DEVELOP} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} -set +x - - -echo "" -echo "============================================================" -echo "Creating support/${VERSION_MM} branches" -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git checkout -b support/${VERSION_MM} - git push -u origin support/${VERSION_MM} - git remote set-branches --add origin support/${VERSION_MM} - set +x -done - - -echo "" -echo "============================================================" -echo "Bumping version on develop to ${NEWVERSION}" -echo "============================================================" -set -x -cd ${GEODE_DEVELOP} -git pull -r -git remote add myfork git@github.com:${GITHUB_USER}/geode.git || true -git checkout -b roll-develop-to-${NEWVERSION} -set +x - -#version = 1.13.0-build.0 -sed -e "s/^version =.*/version = ${NEWVERSION}-build.0/" -i.bak gradle.properties - -# initial_version: 1.13.0-((stuff)).0 -sed -e "s/^ initial_version:[^-]*\(-[^.0-9]*\)[.0-9]*/ initial_version: ${NEWVERSION}\1.0/" -i.bak ./ci/pipelines/shared/jinja.variables.yml - -VER=geode-serialization/src/main/java/org/apache/geode/internal/serialization/KnownVersion.java -[ -r $VER ] || VER=geode-serialization/src/main/java/org/apache/geode/internal/serialization/Version.java -#add the new ordinal and KnownVersion constants and set them as current&highest -CURORD=$(cat $VER | awk '/private static final short GEODE_.*_ORDINAL/{print $NF}' | tr -d ';' | sort -n | tail -1) -NEWORD=$(( CURORD + 10 )) -sed -e "s#/. NOTE: when adding a new version#private static final short GEODE_${NEWMAJOR}_${NEWMINOR}_0_ORDINAL = ${NEWORD};\\ -\\ - @Immutable\\ - public static final KnownVersion GEODE_${NEWMAJOR}_${NEWMINOR}_0 =\\ - new KnownVersion("'"'"GEODE"'"'", "'"'"${NEWMAJOR}.${NEWMINOR}.0"'"'", (byte) ${NEWMAJOR}, (byte) ${NEWMINOR}, (byte) 0, (byte) 0,\\ - GEODE_${NEWMAJOR}_${NEWMINOR}_0_ORDINAL);\\ -\\ - /* NOTE: when adding a new version#" \ - -e "/public static final KnownVersion CURRENT/s#GEODE[0-9_]*#GEODE_${NEWMAJOR}_${NEWMINOR}_0#" \ - -e "/public static final int HIGHEST_VERSION/s# = [0-9]*# = ${NEWORD}#" \ - -i.bak $VER - -# directory: docs/guide/113 -# product_version: '1.13' -# product_version_nodot: '113' -# product_version_geode: '1.13' -# product_version_old_minor: '1.12' -sed -E \ - -e "s#docs/guide/[0-9]+#docs/guide/${NEWVERSION_MM_NODOT}#" \ - -e "s#product_version: '[0-9.]+'#product_version: '${NEWVERSION_MM}'#" \ - -e "s#version_nodot: '[0-9]+'#version_nodot: '${NEWVERSION_MM_NODOT}'#" \ - -e "s#product_version_geode: '[0-9.]+'#product_version_geode: '${NEWVERSION_MM}'#" \ - -e "s#product_version_old_minor: '[0-9.]+'#product_version_old_minor: '${VERSION_MM}'#" \ - -i.bak geode-book/config.yml - -#rewrite '/', '/docs/guide/113/about_geode.html' -#rewrite '/index.html', '/docs/guide/113/about_geode.html' -sed -E -e "s#docs/guide/[0-9]+#docs/guide/${NEWVERSION_MM_NODOT}#" -i.bak geode-book/redirects.rb - -rm gradle.properties.bak ci/pipelines/shared/jinja.variables.yml.bak geode-book/config.yml.bak geode-book/redirects.rb.bak $VER.bak* -set -x -git add . -git diff --staged --color | cat - -./gradlew updateExpectedPom - -git commit -a -m "$JIRA: Roll develop to ${NEWVERSION} - -Now that support/${VERSION_MM} has been created, develop advances." -git push -u myfork -set +x - - -echo "" -echo "============================================================" -echo "Bumping examples version on develop to ${NEWVERSION}" -echo "============================================================" -set -x -cd ${GEODE_EXAMPLES_DEVELOP} -git pull -r -set +x - -#version = 1.13.0-build.0 -#geodeVersion = 1.13.0-build+ -sed \ - -e "s/^version =.*/version = ${NEWVERSION}-build.0/" \ - -e "s/^geodeVersion =.*/geodeVersion = ${NEWVERSION_MM}.+/" \ - -i.bak gradle.properties -rm gradle.properties.bak -set -x -git add gradle.properties -git diff --staged --color | cat -git commit -m "$JIRA: Update examples version - -Now that support/${VERSION_MM} has been created, -pair develop examples with ${NEWVERSION}" -git push -u origin -set +x - - -echo "" -echo "============================================================" -echo "Removing CODEOWNERS and duplicate scripts from support/${VERSION_MM}" -echo "============================================================" -set -x -cd ${GEODE}/dev-tools/release -git pull -r -git rm *.sh -cat << EOF > README.md -See [Releasing Apache Geode](https://cwiki.apache.org/confluence/display/GEODE/Releasing+Apache+Geode) -EOF -git add README.md -cd ${GEODE} -[ ! -r CODEOWNERS ] || git rm CODEOWNERS -[ ! -r CODEWATCHERS ] || git rm CODEWATCHERS -git commit -m "$JIRA: Remove unneeded scripts - -Remove likely-to-become-outdated copies of release scripts to ensure -they are not run by accident from a branch (they should always be run -from develop). - -Also remove CODEOWNERS to avoid the confusion of GitHub showing owner -names like on develop, but codeowner reviews not actually being -required (due to lack of branch protection or minimum review count on -support branches)" -git push -u origin -set +x - - -echo "" -echo "============================================================" -echo "Setting version on support/${VERSION_MM}" -echo "============================================================" -cd ${GEODE}/../.. -set -x -${0%/*}/set_versions.sh -j $JIRA -v ${VERSION_MM}.0 -s -w "${WORKSPACE}" -set +x - - -echo "" -echo "============================================================" -echo "Logging you in to concourse" -echo "============================================================" -set -x -fly -t concourse.apachegeode-ci.info-main login --team-name main --concourse-url https://concourse.apachegeode-ci.info/ -set +x - - -echo "" -echo "============================================================" -echo "Done creating support branches" -echo "============================================================" -cd ${GEODE}/../.. -echo "Next steps:" -echo "1. Go to https://github.com/${GITHUB_USER}/geode/pull/new/roll-develop-to-${NEWVERSION} and create the pull request" -echo "2. Plus the BumpMinor job at https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-develop-main?group=semver-management" -echo "3. Add ${NEWVERSION} to Jira at https://issues.apache.org/jira/projects/GEODE?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page" -echo "4. (cd ${GEODE}/ci/pipelines/meta && ./deploy_meta.sh) #takes 1-2 hours. keep re-running until successful." -echo "5. That's it for now. Once all needed fixes have been proposed and cherry-picked to support/${VERSION_MM} and https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-support-${VERSION_MM/./-}-main is green, come back and run ${0%/*}/prepare_rc.sh -v ${VERSION_MM}.0.RC1" diff --git a/dev-tools/release/deploy_rc_pipeline.sh b/dev-tools/release/deploy_rc_pipeline.sh deleted file mode 100755 index b257d12f7029..000000000000 --- a/dev-tools/release/deploy_rc_pipeline.sh +++ /dev/null @@ -1,655 +0,0 @@ -#!/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 - -usage() { - echo "Usage: deploy_rc_pipeline -v version_number" - echo " -v The #.# version number" - exit 1 -} - -VERSION_MM="" - -while getopts ":v:" opt; do - case ${opt} in - v ) - VERSION_MM=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${VERSION_MM} == "" ]]; then - usage -fi - -if [[ $VERSION_MM =~ ^([0-9]+\.[0-9]+)$ ]]; then - true -else - echo "Malformed version number ${VERSION_MM}. Example valid version: 1.9" - exit 1 -fi - -if [[ $VERSION_MM =~ ^(1\.1[0-4])$ ]]; then - needscmake="" -else - needscmake="-e s/echo.skipping.latest.cmake.//" -fi - -PIPEYML=$PWD/rc-pipeline.yml -cat << "EOF" | sed -e "s//${VERSION_MM}/" $needscmake > $PIPEYML ---- - -resources: -- name: geode - type: git - source: - branch: support/ - tag_filter: rel/v.*.RC* - uri: https://github.com/apache/geode.git -- name: geode-develop - type: git - source: - branch: develop - uri: https://github.com/apache/geode.git -- name: geode-examples - type: git - source: - branch: support/ - uri: https://github.com/apache/geode-examples.git -- name: geode-native - type: git - source: - branch: support/ - tag_filter: rel/v.*.RC* - uri: https://github.com/apache/geode-native.git -- name: geode-benchmarks - type: git - source: - branch: support/ - tag_filter: rel/v.*.RC* - uri: https://github.com/apache/geode-benchmarks.git -- name: upthewaterspout-tests - type: git - source: - branch: master - uri: https://github.com/upthewaterspout/geode-release-check.git - -jobs: - - name: build-geode-from-tag - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - SHA=$(cd geode && git rev-parse HEAD) - java -version - cd geode - ./gradlew test installDist - gfsh=geode-assembly/build/install/apache-geode/bin/gfsh - $gfsh version --full | grep "^Source-Revision: ${SHA}$" - $gfsh version --full | grep "^Product-Version: ${VERSION}-build" - ! $gfsh version --full | grep Oracle - - name: build-geode-from-src-tgz - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - SHA=$(cd geode && git rev-parse HEAD) - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-${VERSION}-src.tgz > src.tgz - tar xzf src.tgz - cd apache-geode-${VERSION}-src - java -version - ./gradlew test installDist - gfsh=geode-assembly/build/install/apache-geode/bin/gfsh - $gfsh version --full | grep "^Source-Revision: ${SHA}$" - $gfsh version --full | grep "^Product-Version: ${VERSION}-build" - ! $gfsh version --full | grep Oracle - - name: run-gfsh-from-tgz - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends git - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - SHA=$(cd geode && git rev-parse HEAD) - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-${VERSION}.tgz > bin.tgz - tar xzf bin.tgz - cd apache-geode-${VERSION} - java -version - gfsh=bin/gfsh - $gfsh version --full | grep "^Source-Revision: ${SHA}$" - $gfsh version --full | grep "^Product-Version: ${VERSION}$" - ! $gfsh version --full | grep Oracle - - name: run-geode-examples-jdk11 - serial: true - public: true - plan: - - in_parallel: - - get: geode-examples - trigger: true - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 11 - inputs: - - name: geode-examples - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip - cd geode-examples - java -version - ./gradlew runAll - - name: run-geode-examples-from-src-tgz-jdk8 - serial: true - public: true - plan: - - in_parallel: - - get: geode-examples - trigger: true - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode-examples - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git - FULL_VERSION=$(cd geode-examples && git fetch && git describe --tags | sed -e 's#^rel/v##' -e 's#-.*##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - if [ "${FULL_VERSION}" = "${VERSION}" ] ; then - GRADLE_ARGS="" - curl -fLs https://downloads.apache.org/geode/${VERSION}/apache-geode-examples-${VERSION}-src.tgz > src.tgz - else - STAGING_MAVEN=$(cat geode-examples/gradle.properties | grep geodeRepositoryUrl | awk '{print $3}') - GRADLE_ARGS="-PgeodeReleaseUrl=https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION} -PgeodeRepositoryUrl=${STAGING_MAVEN}" - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-examples-${VERSION}-src.tgz > src.tgz - fi - tar xzf src.tgz - cd apache-geode-examples-${VERSION}-src - java -version - ./gradlew ${GRADLE_ARGS} build runAll - - name: build-geode-native-from-tag - serial: true - public: true - plan: - - in_parallel: - - get: geode-native - trigger: true - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: adoptopenjdk/openjdk8 - tag: slim - inputs: - - name: geode-native - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends git - FULL_VERSION=$(cd geode-native && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - #use geode from binary dist - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-${VERSION}.tgz > geode-bin.tgz - tar xzf geode-bin.tgz - apt-get update || true - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y cmake openssl doxygen build-essential libssl-dev zlib1g-dev - - echo skipping latest cmake tmp=`mktemp` - echo skipping latest cmake curl -fo ${tmp} -v -L https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-linux-x86_64.sh - echo skipping latest cmake bash ${tmp} --skip-license --prefix=/usr/local - echo skipping latest cmake rm -f ${tmp} - - #cmake wrongly assumes javah wasn't removed until JDK10, but adoptopenjdk removed it in JDK8 - echo '/opt/java/openjdk/bin/javac -h "$@"' > /opt/java/openjdk/bin/javah - chmod +x /opt/java/openjdk/bin/javah - cd geode-native - mkdir build - cd build - cmake .. -DGEODE_ROOT=$PWD/../../apache-geode-${VERSION} - cmake --build . -- -j 4 - cmake --build . --target docs -- -j 4 - cmake --build . --target install -- -j 4 - - name: build-geode-native-from-src-tgz - serial: true - public: true - plan: - - in_parallel: - - get: geode-native - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: adoptopenjdk/openjdk8 - tag: slim - inputs: - - name: geode-native - - name: geode - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git - FULL_VERSION=$(cd geode-native && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - # build geode from source - cd geode - ./gradlew build -x test -x javadoc -x rat -x pmdMain - cd .. - apt-get update || true - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y cmake openssl doxygen build-essential libssl-dev zlib1g-dev - - echo skipping latest cmake tmp=`mktemp` - echo skipping latest cmake curl -fo ${tmp} -v -L https://github.com/Kitware/CMake/releases/download/v3.21.2/cmake-3.21.2-linux-x86_64.sh - echo skipping latest cmake bash ${tmp} --skip-license --prefix=/usr/local - echo skipping latest cmake rm -f ${tmp} - - #cmake wrongly assumes javah wasn't removed until JDK10, but adoptopenjdk removed it in JDK8 - echo '/opt/java/openjdk/bin/javac -h "$@"' > /opt/java/openjdk/bin/javah - chmod +x /opt/java/openjdk/bin/javah - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-native-${VERSION}-src.tgz > src.tgz - tar xzf src.tgz - cd apache-geode-native-${VERSION}-src - mkdir build - cd build - cmake .. -DGEODE_ROOT=$PWD/../../geode/geode-assembly/build/install/apache-geode - cmake --build . -- -j 4 - cmake --build . --target docs -- -j 4 - cmake --build . --target install -- -j 4 - - name: upthewaterspout - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - get: upthewaterspout-tests - - get: geode-examples - - task: validate - timeout: 2h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - - name: upthewaterspout-tests - - name: geode-examples - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git gpg gpg-agent wget - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - STAGING_MAVEN=$(cat geode-examples/gradle.properties | grep geodeRepositoryUrl | awk '{print $3}') - cd upthewaterspout-tests - curl -fLs https://dist.apache.org/repos/dist/dev/geode/KEYS > KEYS - gpg --import KEYS - java -version - ./gradlew build -PmavenURL=${STAGING_MAVEN} -PdownloadURL=https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/ -Pversion=${FULL_VERSION} - - name: benchmarks-test - serial: true - public: true - plan: - - get: geode-benchmarks - trigger: true - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode-benchmarks - platform: linux - run: - path: /bin/sh - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git keychain - FULL_VERSION=$(cd geode-benchmarks && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - curl -fLs https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/apache-geode-benchmarks-${VERSION}-src.tgz > src.tgz - tar xzf src.tgz - cd apache-geode-benchmarks-${VERSION}-src - java -version - mkdir -p ~/.ssh - ssh-keygen -m PEM -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N "" - cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys - apt-get update || true - apt-get install openssh-server --no-install-recommends -y - echo "StrictHostKeyChecking no" >> /etc/ssh/ssh_config - service ssh start - echo 'tasks.withType(JavaCompile) { options.encoding = "UTF-8" }' >> geode-benchmarks/build.gradle - ./gradlew build test - - name: verify-expected-files-and-keys - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - platform: linux - run: - path: /bin/bash - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends git gpg gpg-agent - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - curl -fLs https://dist.apache.org/repos/dist/dev/geode/KEYS > KEYS - gpg --import KEYS - url=https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION} - function verifyArtifactSizeSignatureLicenseNoticeAndCopyright { - tld=$1 - file=${tld}.tgz - minfilesize=$2 - maxfilesize=$3 - echo Verifying $file... - asc=${file}.asc - sha=${file}.sha256 - sum=sha256sum - curl -fLs $url/$file > $file - actualfilesize=$(wc -c < $file) - if [ $actualfilesize -lt $minfilesize ] ; then - echo "File size of $file is only $actualfilesize bytes, expected at least $minfilesize" - return 1 - fi - if [ $actualfilesize -gt $maxfilesize ] ; then - echo "File size of $file is $actualfilesize, expected no more than $maxfilesize bytes" - return 1 - fi - curl -fLs $url/$asc > $asc - curl -fLs $url/$sha > $sha - gpg --verify $asc - $sum -c $sha - echo $file >> exp - echo $asc >> exp - echo $sha >> exp - #check that each archive contains all content below a top-level-directory with the same name as the file (sans .tgz) - ! tar tvzf $file | grep -v " ${tld}/" - #check that each archive contains LICENSE and NOTICE - tar tvzf $file | grep " ${tld}/LICENSE" - tar tvzf $file | grep " ${tld}/NOTICE" - #check that NOTICE contains current copyright year and correctly assigns copyright to ASF - tar xzf $file "${tld}/NOTICE" - year=$(date +%Y) - grep "Copyright" "${tld}/NOTICE" - grep -q "Copyright.*${year}.*Apache Software Foundation" "${tld}/NOTICE" - #check that the declared license is of the correct type - tar xzf $file "${tld}/LICENSE" - head -1 "${tld}/LICENSE" | grep -q "Apache License" - } - verifyArtifactSizeSignatureLicenseNoticeAndCopyright apache-geode-${VERSION}-src 16000000 20000000 - verifyArtifactSizeSignatureLicenseNoticeAndCopyright apache-geode-${VERSION} 120000000 137000000 - verifyArtifactSizeSignatureLicenseNoticeAndCopyright apache-geode-examples-${VERSION}-src 840000 900000 - verifyArtifactSizeSignatureLicenseNoticeAndCopyright apache-geode-native-${VERSION}-src 2400000 3200000 - verifyArtifactSizeSignatureLicenseNoticeAndCopyright apache-geode-benchmarks-${VERSION}-src 85000 125000 - curl -fLs ${url}/ | awk '/>../{gsub(/ *<[^>]*>/,"");print}' | sort > actual-file-list - sort < exp > expected-file-list - set +x - echo "" - if diff -q expected-file-list actual-file-list ; then - echo "The file list at $url matches what is expected and all signatures were verified :)" - else - echo "Expected:" - cat expected-file-list - echo "" - echo "Actual:" - cat actual-file-list - echo "" - echo "Diff:" - diff expected-file-list actual-file-list - exit 1 - fi - - name: verify-no-binaries - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - platform: linux - run: - path: /bin/bash - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends git - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - VERSION=$(echo $FULL_VERSION|sed -e 's/\.RC.*//') - url=https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION} - BINARY_EXTENSIONS="jar|war|class|exe|dll|o|so|obj|bin|out|pyc" - echo "Source artifacts should not contain any files ending in$(echo "|${BINARY_EXTENSIONS}"|sed 's/[^a-z]/ ./g')" - echo "" - function verifyNoBinaries { - file=$1 - echo "" - echo Checking $file... - curl -fLs $url/$file | tar tvzf - | egrep '\.('"${BINARY_EXTENSIONS}"')$' | tee -a bins - } - verifyNoBinaries apache-geode-${VERSION}-src.tgz - verifyNoBinaries apache-geode-examples-${VERSION}-src.tgz - verifyNoBinaries apache-geode-native-${VERSION}-src.tgz - verifyNoBinaries apache-geode-benchmarks-${VERSION}-src.tgz - echo "" - echo "" - if grep -q . bins ; then - echo Binary files were found! - exit 1 - else - echo All good - fi - - name: verify-license - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: [verify-expected-files-and-keys] - - get: geode-develop - - task: validate - timeout: 1h - config: - image_resource: - type: docker-image - source: - repository: bellsoft/liberica-openjdk-debian - tag: 8 - inputs: - - name: geode - - name: geode-develop - platform: linux - run: - path: /bin/bash - args: - - -ecx - - | - apt update -q - apt install -qq -y --no-install-recommends unzip git - FULL_VERSION=$(cd geode && git fetch && git describe --tags | sed -e 's#^rel/v##') - ./geode-develop/dev-tools/release/license_review.sh -v $FULL_VERSION - - name: all-passed - serial: true - public: true - plan: - - in_parallel: - - get: geode - trigger: true - passed: - - verify-license - - upthewaterspout - - run-gfsh-from-tgz - - verify-no-binaries - - build-geode-from-tag - - build-geode-from-src-tgz - - get: geode-examples - passed: - - run-geode-examples-jdk11 - - run-geode-examples-from-src-tgz-jdk8 - - get: geode-native - passed: - - build-geode-native-from-tag - - build-geode-native-from-src-tgz - - get: geode-benchmarks - passed: - - benchmarks-test -EOF -fly -t concourse.apachegeode-ci.info-main login --team-name main --concourse-url https://concourse.apachegeode-ci.info/ -fly -t concourse.apachegeode-ci.info-main set-pipeline -p apache-support-${VERSION_MM//./-}-rc -c $PIPEYML -fly -t concourse.apachegeode-ci.info-main unpause-pipeline -p apache-support-${VERSION_MM//./-}-rc -fly -t concourse.apachegeode-ci.info-main expose-pipeline -p apache-support-${VERSION_MM//./-}-rc -rm $PIPEYML diff --git a/dev-tools/release/end_of_support.sh b/dev-tools/release/end_of_support.sh deleted file mode 100755 index 38f69b9ace5b..000000000000 --- a/dev-tools/release/end_of_support.sh +++ /dev/null @@ -1,155 +0,0 @@ -#!/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 - -usage() { - echo "Usage: end_of_support.sh -v version_number" - echo " -v The #.# version number of the support branch that is no longer supported" - exit 1 -} - -VERSION_MM="" - -while getopts ":v:" opt; do - case ${opt} in - v ) - VERSION_MM=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${VERSION_MM} == "" ]] ; then - usage -fi - -if [[ $VERSION_MM =~ ^([0-9]+\.[0-9]+)$ ]]; then - true -else - echo "Malformed version number ${VERSION_MM}. Example valid version: 1.9" - exit 1 -fi - -set -x -WORKSPACE=$PWD/support-${VERSION_MM}-workspace -GEODE=$WORKSPACE/geode -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_NATIVE=$WORKSPACE/geode-native -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -set +x - -function failMsg1 { - echo "ERROR: script did NOT complete successfully. Please try again." -} -trap failMsg1 ERR - - -echo "" -echo "============================================================" -echo "Cleaning workspace directory..." -echo "============================================================" -set -x -rm -rf $WORKSPACE -mkdir -p $WORKSPACE -cd $WORKSPACE -set +x - - -echo "" -echo "============================================================" -echo "Cloning repositories..." -echo "============================================================" -set -x -git clone --branch support/${VERSION_MM} git@github.com:apache/geode.git -git clone --branch support/${VERSION_MM} git@github.com:apache/geode-examples.git -git clone --branch support/${VERSION_MM} git@github.com:apache/geode-native.git -git clone --branch support/${VERSION_MM} git@github.com:apache/geode-benchmarks.git -set +x - - -echo "" -echo "============================================================" -echo "Confirming end of support" -echo "============================================================" -cd ${GEODE} -echo "Geode support branches should be kept for at least 9 months after initial release." -echo "Geode ${VERSION_MM}.0 was released about $(git log -1 --format=%ar rel/v${VERSION_MM}.0)" -read -p "To continue and delete support/${VERSION_MM} and all associated pipelines, type YES if you are sure: " -if [ "$REPLY" != "YES" ] ; then - echo cancelled - exit 1 -fi - - -function failMsg2 { - errln=$1 - echo "ERROR: script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 65-$(( errln - 1 ))) and try again" -} -trap 'failMsg2 $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Destroying pipelines" -echo "============================================================" -set -x -cd ${0%/*}/../../ci/pipelines/meta -DEVELOP_META=$(pwd) -cd ${GEODE} -fly -t concourse.apachegeode-ci.info-main login --team-name main --concourse-url https://concourse.apachegeode-ci.info/ -${DEVELOP_META}/destroy_pipelines.sh -set +x - - -echo "" -echo "============================================================" -echo "Destroying support branches" -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git checkout develop - git push origin --delete support/${VERSION_MM} - git branch -D support/${VERSION_MM} - set +x -done - - -echo "" -echo "============================================================" -echo "Cleaning up RC tags" -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git tag | grep "^rel/v${VERSION_MM}.*RC" | while read RCTAG ; do - git push origin --delete "$RCTAG" - done - set +x -done - - -echo "" -echo "============================================================" -echo 'Done shutting down the support branch!' -echo "============================================================" -echo "Don't forget to remove the JIRA Release placeholder for any future ${VERSION_MM}.x" -echo "Probably also a good idea to announce on the dev list that support/${VERSION_MM} has expired" diff --git a/dev-tools/release/license_review.sh b/dev-tools/release/license_review.sh deleted file mode 100755 index 1ce58225fba3..000000000000 --- a/dev-tools/release/license_review.sh +++ /dev/null @@ -1,377 +0,0 @@ -#!/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 - -usage() { - echo "Usage: license_review.sh -v version_number_or_tgz [-p previous_version_number_or_tgz]" - echo " -v The #.#.#.RC# or #.#.# version number to review -or- a path or URL to .tgz -or- 'HEAD'" - echo " -p The #.#.#.RC# or #.#.# version number to compare against -or- a path or URL to .tgz" - echo " -n No license check (useful if you just want the version comparison)" - echo " -s No source license check (just check the binary license)" - exit 1 -} - - -while getopts ":v:p:ns" opt; do - case ${opt} in - v ) - NEW_VERSION=$OPTARG - ;; - p ) - OLD_VERSION=$OPTARG - ;; - n ) - SKIP_LICENSES=true - ;; - s ) - SKIP_SRC_LICENSE=true - ;; - \? ) - usage - ;; - esac -done - -if [ -z "${NEW_VERSION}" ] ; then - usage -fi - -WORKSPACE=$(pwd)/license_tmp -DOWNLOAD=${WORKSPACE}/download -EXTRACT=${WORKSPACE}/extracted -mkdir -p ${DOWNLOAD} -mkdir -p ${EXTRACT} -root=$0 -root=${root%/dev-tools*} - -if [ "$NEW_VERSION" = "HEAD" ] ; then - licFromWs=true - rm -Rf $root/geode-assembly/build/distributions -fi - - -function resolve() { - [ -n "$1" ] || return - spec=$1 - suffix=$2 - if [ "HEAD" = "$spec" ] ; then - [ "${suffix}" = "-src" ] && target=srcDistTar || target=distTar - (cd $root && ./gradlew ${target} 1>&2) - spec=$root/geode-assembly/build/distributions/$(cd $root/geode-assembly/build/distributions && ls -t | grep -v sha256 | grep "apache-geode-.*-build.[0-9][0-9]*${suffix}.tgz" | tail -1) - [ -r "$spec" ] || echo "Build not found: $spec" 1>&2 - [ -r "$spec" ] - fi - - if [[ $spec =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.(RC[0-9]+)$ ]]; then - mmp=$(echo $spec | sed 's/.RC.*//') - #bare RC version -> RC url - spec=https://dist.apache.org/repos/dist/dev/geode/${spec}/apache-geode-${mmp}${suffix}.tgz - elif [[ $spec =~ ^([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then - #bare released version -> release url - spec=https://archive.apache.org/dist/geode/${spec}/apache-geode-${spec}${suffix}.tgz - elif echo "$spec" | grep -q '^http.*tgz$' ; then - #tgz url - echo "$spec" | grep -q -- "${suffix}.tgz$" || return - elif [ -r "$spec" ] && echo "$spec" | grep -q 'tgz$' ; then - #tgz file present locally - echo "$spec" | grep -q -- "${suffix}.tgz$" || return - else - #unsupported - return - fi - - #download if url (and not already downloaded) - if echo "$spec" | grep -q '^http.*tgz$' ; then - filename=$(echo $spec | sed 's#.*/##') - [ -r ${DOWNLOAD}/$filename ] || curl -fL "$spec" > ${DOWNLOAD}/$filename - spec=${DOWNLOAD}/$filename - fi - - #extract it (if not already extracted) - dirname=$(echo $spec | sed -e 's#.*/##' -e 's#.tgz$##') - [ "${licFromWs}" = "true" ] && rm -Rf ${EXTRACT}/$dirname || true - [ -d ${EXTRACT}/$dirname ] || tar xzf $spec -C ${EXTRACT} - [ -d ${EXTRACT}/$dirname ] && echo ${EXTRACT}/$dirname || true -} - -NEW_DIR=$(resolve $NEW_VERSION) - -if [ -z "${NEW_DIR}" ] || [ ! -d "${NEW_DIR}" ] ; then - usage -fi - -if [ "${licFromWs}" = "true" ] && ! [ "$SKIP_LICENSES" = "true" ] && ! [ "$SKIP_SRC_LICENSE" = "true" ] ; then - NEW_SRC_DIR=$(resolve $NEW_VERSION -src) -fi - -function banner() { - echo "" - echo "$@" | sed 's/./=/g' - echo "$@" - echo "$@" | sed 's/./=/g' -} - -function listJarsInWar() { - war=$1 - jar tvf $war | awk '/.jar$/{print "'"$war"'/"$8}' -} - -function extractLicense() { - [ "$SKIP_LICENSES" != "true" ] || return 0 - war=$1 - rm -Rf tmpl - mkdir tmpl - cd tmpl - jar xf ../$war META-INF/LICENSE - cd .. - cp tmpl/META-INF/LICENSE $2 - rm -Rf tmpl -} - -function generateList() { - dir=$1 - banner "Listing 3rd-party deps in ${dir##*/}" - - #also extract geode jar licenses for later checking - (cd $dir; find . -name '*.jar' | egrep '(geode|gfsh)-' | sort | sed 's#^./##' | while read geodejar ; do - extractLicense $geodejar ${geodejar%.jar}.LICENSE - done) - - echo "**** ${dir##*/} jars ****" | tr '[:lower:]-' '[:upper:] ' > $dir/report1 - (cd $dir; find . -name '*.jar' | grep -v geode- | grep -v gfsh- | sort | sed 's#^./##' | tee -a report1) - - echo "**** ${dir##*/} wars ****" | tr '[:lower:]-' '[:upper:] ' > $dir/report2 - (cd $dir; find . -name '*.war' | sort | sed 's#^./##' | while read war ; do - listJarsInWar $war | sed 's#-[v0-9][-0-9.SNAPSHOTbuild]*[.]#.#' | sort - extractLicense $war ${war%.war}.LICENSE - done | tee -a report2) -} - -generateList $NEW_DIR -if [ -n "${OLD_VERSION}" ] ; then - OLD_DIR=$(resolve $OLD_VERSION) - generateList $OLD_DIR - - banner "Diffing 3rd-party deps changes from ${OLD_DIR##*/} to ${NEW_DIR##*/}" - for REPORT in report1 report2 ; do - diff -y -W $(tput cols) $OLD_DIR/$REPORT $NEW_DIR/$REPORT | grep '[<|>]' - done -fi - -[ "$SKIP_LICENSES" = "true" ] && exit 0 - -banner "Checking that all binary licenses are identical" -sizes=$(find $NEW_DIR -name '*LICENSE' | xargs wc -c | grep -v total | awk '{print $1}' | sort -u | wc -l) -if [ $sizes -gt 1 ] ; then - echo "NOT all LICENSES are the same:" - (cd $NEW_DIR; find * -name '*LICENSE' | xargs wc -c | grep -v total | sort) - result=1 -else - echo 'All Good!' -fi - -function isApache2() { - apache="HikariCP -accessors-smart -byte-buddy -classmate -commons-beanutils -commons-codec -commons-collections -commons-digester -commons-fileupload -commons-io -commons-lang3 -commons-logging -commons-math3 -commons-modeler -commons-text -commons-validator -content-type -error_prone_annotations -failureaccess -fastutil -findbugs-annotations -geo -guava -grumpy- -httpclient -httpcore -j2objc-annotations -jackson- -jcip-annotations -jna -json-path -json-smart -jsr305 -jetty- -jgroups -jna- -joda-time -lang-tag -listenablefuture -log4j- -lucene- -mapstruct -micrometer-core -netty- -nimbus-jose-jwt -oauth2-oidc-sdk -rmiio -shiro- -snakeyaml -snappy -spring- -springdoc- -swagger-annotations -swagger-core -swagger-models -swagger-ui" - echo "$1" | egrep -q "(mx4j-remote|jaxb-api|$(echo -n "$apache" | tr '\n' '|'))" -} -function shortenDep() { - echo "$1" | sed \ - -e 's/-api//' \ - -e 's/-impl//' \ - -e 's/-java//' \ - -e 's/shiro-.*/shiro-*/' \ - -e 's/jackson-.*/jackson-*/' \ - -e 's/jetty-.*/jetty-*/' \ - -e 's/jna-.*/jna-*/' \ - -e 's/lucene-.*/lucene-*/' \ - -e 's/log4j-.*/log4j-*/' \ - -e 's/mx4j-.*/mx4j*/' \ - -e 's/spring-.*/spring-*/' \ - -e 's/springdoc-.*/springdoc-*/' -} -for REPORT in report1 report2 ; do - [ "$REPORT" = "report1" ] && topic=JAR || topic=WAR - if [ "${licFromWs}" = "true" ] ; then - LICENSE=${root}/geode-assembly/src/main/dist/LICENSE - else - [ "$REPORT" = "report1" ] && LICENSE=${NEW_DIR}/LICENSE || LICENSE=${NEW_DIR}/tools/Pulse/$(cd ${NEW_DIR}/tools/Pulse; ls | grep LICENSE) - fi - LICENSE=${LICENSE#./} - banner "Comparing $topic dep versions in ${NEW_DIR##*/} to $LICENSE" - rm -f missing-$REPORT apache-$REPORT - touch missing-$REPORT apache-$REPORT - tail -n +2 $NEW_DIR/$REPORT | sed -e 's#.*/##' -e 's/\.jar//' | sed 's/-\([0-9]\)/ \1/' | sort -u | grep -v '^ra$' | while read dep ver; do - if isApache2 $dep ; then - echo $dep $ver >> apache-$REPORT - else - echo $(shortenDep $dep) $ver - fi - done | sort -u | while read dep ver ; do - if grep -qi "${dep//-/.}.*$ver" $LICENSE ; then - echo "$dep $ver Found (and version matches)" - elif grep -qi $dep $LICENSE ; then - match="$(grep -i $dep $LICENSE | grep -v License | head -1)" - if echo $match | grep -q '[0-9][0-9]*[.][0-9][0-9]*' ; then - echo "$dep FOUND WITH A DIFFERENT VERSION, PLEASE UPDATE TO $ver:" >> missing-$REPORT - echo "$match" >> missing-$REPORT - else - echo "$dep $ver probably found (without version):" - echo "$match" - fi - else - echo "$LICENSE FAILS TO MENTION $dep v$ver" >> missing-$REPORT - fi - done - echo $(wc -l < apache-$REPORT) "deps are licensed under Apache 2.0 (no need to mention individually)" - rm apache-$REPORT - if [ $(wc -l < missing-$REPORT) -eq 0 ] ; then - echo 'All Good!' - else - cat missing-$REPORT - rm missing-$REPORT - result=1 - fi -done - -function checkMissing() { - rm -f missing - touch missing - grep '^ - ' | sed -e 's/^ - //' -e 's/, .*//' -e 's/ (.*//' -e 's/s* v.*//' -e 's/ /.?/g' | while read f; do - if (cd ${root} && git grep -Eqi "$f" -- ':!LICENSE' ':!**/LICENSE' ':!NOTICE' ':!**/NOTICE') ; then - true - #echo "${f//\?/} found" - else - echo "${f//\?/} appears to be unused. Please remove from $1" >> missing - fi - done - if [ $(wc -l < missing) -eq 0 ] ; then - echo 'All Good!' - rm missing - else - cat missing - rm missing - return 1 - fi -} - -if [ "${licFromWs}" = "true" ] ; then - banner "Checking that binary license is a superset of src license" - SLICENSE=${root}/LICENSE - BLICENSE=${root}/geode-assembly/src/main/dist/LICENSE - if diff $SLICENSE $BLICENSE | grep -q '^<' ; then - echo $(diff $SLICENSE $BLICENSE | grep '^<' | wc -l) "lines appear in $SLICENSE that were not found in $BLICENSE." - echo "Please ensure the binary license is a strict superset of the source license." - echo "(diff $SLICENSE $BLICENSE)" - result=1 - else - echo 'All Good!' - fi - - banner "Checking that binary license is correct" - if diff -q ${BLICENSE} ${NEW_DIR}/LICENSE ; then - echo 'All Good!' - else - echo "Incorrect LICENSE in binary distribution" - echo "Expected:" $(wc -c ${BLICENSE}) - echo "Actual:" $(wc -c ${NEW_DIR}/LICENSE) - result=1 - fi - - if ! [ "$SKIP_SRC_LICENSE" = "true" ] ; then - banner "Checking that source license is correct" - if diff -q ${SLICENSE} ${NEW_SRC_DIR}/LICENSE ; then - echo 'All Good!' - else - echo "Incorrect LICENSE in source distribution" - echo "Expected:" $(wc -c ${SLICENSE}) - echo "Actual:" $(wc -c ${NEW_SRC_DIR}/LICENSE) - result=1 - fi - - banner "Checking references in source license" - cat $SLICENSE | checkMissing $SLICENSE || result=1 - - - banner "Checking references in binary license" - cat $SLICENSE $SLICENSE $BLICENSE | sort | uniq -u | checkMissing $BLICENSE || result=1 - fi -fi - -if [ "$result" == 1 ] ; then - banner "ERRORS WERE FOUND" - echo "review each section above for details" - exit 1 -else - banner "SUMMARY" - echo 'ALL GOOD!' -fi diff --git a/dev-tools/release/prepare_rc.sh b/dev-tools/release/prepare_rc.sh deleted file mode 100755 index 90ed0f9365af..000000000000 --- a/dev-tools/release/prepare_rc.sh +++ /dev/null @@ -1,374 +0,0 @@ -#!/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 - -usage() { - echo "Usage: prepare_rc.sh -j ticket -v version_number -k signing_key -a apache_ldap_username" - echo " -j The GEODE-nnnnn Jira identifier for this release" - echo " -v The #.#.#.RC# version number" - echo " -k Your 8 digit GPG key id (the last 8 digits of your gpg fingerprint)" - echo " -a Your apache LDAP username (that you use to log in to https://id.apache.org)" - exit 1 -} - -checkCommand() { - COMMAND=$1 - if ! [[ -x "$(command -v $COMMAND)" ]]; then - echo "$COMMAND must be installed" - exit 1 - fi -} - -JIRA="" -FULL_VERSION="" -SIGNING_KEY="" -APACHE_USERNAME="" - -while getopts ":j:v:k:a:" opt; do - case ${opt} in - j ) - JIRA=$OPTARG - ;; - v ) - FULL_VERSION=$OPTARG - ;; - k ) - SIGNING_KEY=$OPTARG - ;; - a ) - APACHE_USERNAME=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${JIRA} == "" ]] || [[ ${FULL_VERSION} == "" ]] || [[ ${SIGNING_KEY} == "" ]] || [[ ${APACHE_USERNAME} == "" ]]; then - usage -fi - -if [[ $SIGNING_KEY =~ ^[0-9A-Fa-f]{8}$ ]]; then - true -else - echo "Malformed signing key ${SIGNING_KEY}. Example valid key: ABCD1234" - exit 1 -fi - -if [[ $FULL_VERSION =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.(RC[0-9]+)$ ]]; then - VERSION=${BASH_REMATCH[1]} -else - echo "Malformed version number ${FULL_VERSION}. Example valid version: 1.9.0.RC1" - exit 1 -fi - -VERSION_MM=${VERSION%.*} - -checkCommand gpg -checkCommand cmake -checkCommand svn -checkCommand doxygen - - -echo "" -echo "============================================================" -echo "Checking java..." -echo "============================================================" -[ -z "$JAVA_HOME" ] && JAVA=java || JAVA=$JAVA_HOME/bin/java -if ! $JAVA -XshowSettings:properties -version 2>&1 | grep 'java.specification.version = 1.8' ; then - echo "Please set JAVA_HOME to use JDK 8 to compile Geode for release" - exit 1 -fi -if $JAVA -XshowSettings:properties -version 2>&1 | grep 'java.vm.vendor = Oracle' ; then - echo "Please set JAVA_HOME to use an Open JDK 8 such as from https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot to compile Geode for release" - exit 1 -else - $JAVA -XshowSettings:properties -version 2>&1 | grep 'java.vm.vendor = ' -fi - - -echo "" -echo "============================================================" -echo "Checking gpg... (you will be prompted to enter passphrase)" -echo "============================================================" -SECRING=~/.gnupg/secring.gpg -! [ -r $SECRING ] || SECRING=/dev/null -if gpg --export-secret-keys > ${SECRING} && echo "1234" | gpg -o /dev/null --local-user ${SIGNING_KEY} -as - ; then - echo "You entered the correct passphrase; proceeding." - echo "Please note, you will still need to enter it a few more times." - echo "PLEASE NOTE, the very last prompt will be for your apache password (not gpg). Pay attention as the prompts look very similar." -else - echo "Hmm, gpg seems unhappy. Check that you entered correct passphrase or refer to release wiki for troubleshooting." - exit 1 -fi -if ! gpg --list-keys ${SIGNING_KEY} | grep -q "${APACHE_USERNAME}@apache.org" ; then - echo "Please specify a gpg key that is associated with your apache email address." - echo "Expected: ${APACHE_USERNAME}@apache.org" - echo "Found: $(gpg --list-keys ${SIGNING_KEY} | grep ^uid | sed -e 's/.*.*//')" - exit 1 -fi - - -set -x -WORKSPACE=$PWD/release-${VERSION}-workspace -GEODE=$WORKSPACE/geode -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_NATIVE=$WORKSPACE/geode-native -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -BREW_DIR=$WORKSPACE/homebrew-core -SVN_DIR=$WORKSPACE/dist/dev/geode -if which shasum >/dev/null; then - SHASUM="shasum -a 256" -else - SHASUM=sha256sum -fi -set +x - - -function failMsg1 { - echo "ERROR: script did NOT complete successfully. Please try again." -} -trap failMsg1 ERR - - -echo "" -echo "============================================================" -echo "Cleaning workspace directory..." -echo "============================================================" -set -x -rm -rf $WORKSPACE -mkdir -p $WORKSPACE -cd $WORKSPACE -set +x - - -echo "" -echo "============================================================" -echo "Cloning repositories..." -echo "============================================================" -set -x -git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode.git -#if you attempt to reset to a prior SHA here, skip ${GEODE} in set_copyright.sh or it may backfire -#(cd geode; git reset --hard $desired_sha) #uncomment if latest commit is not the desired sha -git clone git@github.com:apache/geode.git geode-develop -git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode-examples.git -git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode-native.git -git clone --single-branch --branch develop git@github.com:apache/geode-native.git geode-native-develop -git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode-benchmarks.git -git clone --single-branch --branch develop git@github.com:apache/geode-benchmarks.git geode-benchmarks-develop -git clone --single-branch --branch master git@github.com:Homebrew/homebrew-core.git - -svn checkout https://dist.apache.org/repos/dist --depth empty -svn update --set-depth immediates --parents dist/release/geode -svn update --set-depth infinity --parents dist/dev/geode -set +x - -for REPO in ${GEODE} ${WORKSPACE}/geode-develop ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ${BREW_DIR} ; do - cd ${REPO} - git config user.email "${APACHE_USERNAME}@apache.org" -done - -cd ${GEODE}/../.. -set -x -${0%/*}/set_copyright.sh -j $JIRA ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} -set +x - - -echo "" -echo "============================================================" -echo "Keeping -build.0 suffix" -echo "============================================================" -cd ${GEODE}/../.. -set -x -${0%/*}/set_versions.sh -j $JIRA -v ${VERSION} -n -w ${WORKSPACE} -set +x - - -echo "" -echo "============================================================" -echo "Building geode..." -echo "============================================================" -set -x -cd ${GEODE} -git clean -fdx && ./gradlew build -x test publishToMavenLocal -Pversion=${VERSION} -Paskpass -Psigning.keyId=${SIGNING_KEY} -Psigning.secretKeyRingFile=${HOME}/.gnupg/secring.gpg -set +x - - -if [ "${FULL_VERSION##*.RC}" -gt 1 ] ; then - echo "" - echo "============================================================" - echo "Removing previous RC's temporary commit from geode-examples..." - echo "============================================================" - set -x - cd ${GEODE_EXAMPLES} - git pull - set +x - sed -e 's#^geodeRepositoryUrl *=.*#geodeRepositoryUrl =#' \ - -e 's#^geodeReleaseUrl *=.*#geodeReleaseUrl =#' -i.bak gradle.properties - rm gradle.properties.bak - set -x - git add gradle.properties - if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "Revert "'"'"$JIRA: Set temporary staging repo"'"' - fi - set +x -fi - - -echo "" -echo "============================================================" -echo "Building geode-examples..." -echo "============================================================" -set -x -cd ${GEODE_EXAMPLES} -git clean -dxf && ./gradlew -Pversion=${VERSION} -PsignArchives -PgeodeReleaseUrl="file://${GEODE}/geode-assembly/build/geode-assembly/build/distributions/apache-geode-${VERSION}" -PgeodeRepositoryUrl="file://${HOME}/.m2/repository" -Psigning.keyId=${SIGNING_KEY} -Psigning.secretKeyRingFile=${HOME}/.gnupg/secring.gpg build -set +x - - -echo "" -echo "============================================================" -echo "Building geode-native..." -echo "============================================================" -set -x -cd ${GEODE_NATIVE} -mkdir build -which brew >/dev/null && OPENSSL_ROOT_DIR=$(brew --prefix openssl) || OPENSSL_ROOT_DIR=$(which openssl) -cd ${GEODE_NATIVE}/build -cmake .. -DPRODUCT_VERSION=${VERSION} -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -DGEODE_ROOT=${GEODE}/geode-assembly/build/install/apache-geode -cpack -G TGZ --config CPackSourceConfig.cmake -NCOUT=apache-geode-native-${VERSION}-src.tar.gz -NCTGZ=apache-geode-native-${VERSION}-src.tgz -mkdir repkg-temp -cd repkg-temp -tar xzf ../${NCOUT} -rm ../${NCOUT}* -mv apache-geode-native apache-geode-native-${VERSION}-src -tar czf ../${NCTGZ} * -cd .. -rm -Rf repkg-temp -gpg --armor -u ${SIGNING_KEY} -b ${NCTGZ} -${SHASUM} ${NCTGZ} > ${NCTGZ}.sha256 -set +x - - -echo "" -echo "============================================================" -echo "Building geode-benchmarks..." -echo "============================================================" -set -x -cd ${GEODE_BENCHMARKS} -BMDIR=apache-geode-benchmarks-${VERSION}-src -BMTAR=${BMDIR}.tgz -git clean -dxf -mkdir ../${BMDIR} -cp -r .travis.yml * ../${BMDIR} -tar czf ${BMTAR} -C .. ${BMDIR} -rm -Rf ../${BMDIR} -gpg --armor -u ${SIGNING_KEY} -b ${BMTAR} -${SHASUM} ${BMTAR} > ${BMTAR}.sha256 -set +x - - -function failMsg2 { - errln=$1 - echo "ERROR: script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 149-$(( errln - 1 ))) and try again" - echo "For this script only (prepare_rc.sh), it's also safe to just try again from the top" -} -trap 'failMsg2 $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Tagging the release candidate in each repository. The tags will not be pushed yet..." -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git tag -s -u ${SIGNING_KEY} rel/v${FULL_VERSION} -m "Release candidate ${FULL_VERSION}" - set +x -done - - -echo "" -echo "============================================================" -echo "Copying artifacts to svn directory for publication. The artifacts will not be committed..." -echo "============================================================" -set -x -cd ${SVN_DIR} -svn rm ${VERSION}.RC* &>/dev/null || true -cp ${GEODE}/KEYS . -mkdir ${FULL_VERSION} -cp ${GEODE}/geode-assembly/build/distributions/* ${FULL_VERSION} -cp ${GEODE_EXAMPLES}/build/distributions/* ${FULL_VERSION} -cp ${GEODE_NATIVE}/build/apache-geode-native-${VERSION}* ${FULL_VERSION} -cp ${GEODE_BENCHMARKS}/apache-geode-benchmarks-${VERSION}* ${FULL_VERSION} -set +x - -# verify all files are signed. sometimes gradle "forgets" to make the .asc file -for f in ${FULL_VERSION}/*.tgz ; do - if ! [ -r $f.sha256 ] ; then - echo missing $f.sha256 - exit 1 - fi - if ! [ -r $f.asc ] ; then - set -x - gpg --armor -u ${SIGNING_KEY} -b $f - set +x - if ! [ -r $f.asc ] ; then - echo missing $f.asc - exit 1 - fi - fi - size=$(ls -l $f | awk '{print $5}') - if [ $size -lt 10000 ] ; then - echo $f file size is only $size bytes, that seems suspicious. - exit 1 - fi -done - -set -x -svn add ${FULL_VERSION} -set +x - - -echo "" -echo "============================================================" -echo "Publishing artifacts to nexus staging manager..." -echo "PLEASE NOTE, the 2nd prompt will be for your apache (not gpg) password. Pay attention as the prompts look very similar." -echo "============================================================" -publishcmd="./gradlew publish --no-parallel -Pversion=${VERSION} -Paskpass -Psigning.keyId=${SIGNING_KEY} -Psigning.secretKeyRingFile=${HOME}/.gnupg/secring.gpg -PmavenUsername=${APACHE_USERNAME}" -set -x -cd ${GEODE} -sh -c "$publishcmd" -set +x - - -echo "" -echo "============================================================" -echo "Done preparing the release and staging to nexus! Next steps:" -echo "============================================================" -cd ${GEODE}/../.. -echo "1. Go to https://repository.apache.org, login as ${APACHE_USERNAME}, and click on Staging Repositories" -echo "2. If there is a prior ${VERSION} RC, select it and click Drop." -echo "2b.If publication got split between two staging repos, drop one of them then run: pushd ${GEODE}; $publishcmd; popd" -echo '3. Make a note of the 4-digit ID of the current ("implicitly created") staging repo.' -echo '4. Select the current staging repo and click Close.' -echo '5. Wait ~10 seconds and then refresh the page to confirm that status has become "Closed"' -echo "6. Run ${0%/*}/commit_rc.sh -j $JIRA -v ${FULL_VERSION} -m <4-DIGIT-ID-NOTED-ABOVE>" diff --git a/dev-tools/release/print_announce_email.sh b/dev-tools/release/print_announce_email.sh deleted file mode 100755 index 1faf9e150dfa..000000000000 --- a/dev-tools/release/print_announce_email.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/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 - -usage() { - echo "Usage: print_announce_email.sh -v version_number -f latest_version_number" - echo " -v The #.#.# version number" - echo " -f The #.#.# version number of the latest and greatest, if other than above" - exit 1 -} - -VERSION="" -LATER="" - -while getopts ":v:f:" opt; do - case ${opt} in - v ) - VERSION=$OPTARG - ;; - f ) - LATER=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${VERSION} == "" ]] ; then - usage -fi - -if [[ $VERSION =~ ^([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then - true -else - echo "Malformed version number ${VERSION}. Example valid number - 1.9.0" - exit 1 -fi - -if [ -z "$FLAGSHIP" ] || [[ "$FLAGSHIP" =~ ^([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then - true -else - echo "Malformed version number ${FLAGSHIP}. Example valid number - 1.9.0" - exit 1 -fi - -VERSION_MM=${VERSION%.*} - -#support mac or linux date arithmetic syntax -DEADLINE=$(date --date '+5 days' '+%a, %B %d %Y' 2>/dev/null || date -v +5d "+%a, %B %d %Y" 2>/dev/null || echo "<5 days from now>") - -if [ -n "${LATER}" ] && [ "${VERSION}" != "${LATER}" ] ; then - LATEST="Users are encouraged to upgrade to the latest ${LATER%.*}.x release (currently $LATER)." -else - LATEST="Users are encouraged to upgrade to this latest release." -fi - -if echo $VERSION | grep -q '\.0$' ; then - IMPROV=" improvements and" -else - IMPROV="" -fi - -cat << EOF -To: user@geode.apache.org, announce@apache.org, dev@geode.apache.org -Subject: [ANNOUNCE] Apache Geode ${VERSION} - -The Apache Geode community is pleased to announce the availability of -Apache Geode ${VERSION}. - -Geode is a data management platform that provides a database-like consistency -model, reliable transaction processing and a shared-nothing architecture -to maintain very low latency performance with high concurrency processing. - -Apache Geode ${VERSION} contains a number of${IMPROV} bug fixes. -$LATEST -For the full list of changes please review the release notes at: -https://cwiki.apache.org/confluence/display/GEODE/Release+Notes#ReleaseNotes-${VERSION} - -Release artifacts and documentation can be found at the project website: -https://geode.apache.org/releases/ -https://geode.apache.org/docs/guide/${VERSION_MM//./}/about_geode.html - -We would like to thank all the contributors that made the release possible. -Regards, -$(git config --get user.name) on behalf of the Apache Geode team -EOF diff --git a/dev-tools/release/print_rc_email.sh b/dev-tools/release/print_rc_email.sh deleted file mode 100755 index 29e45feb7683..000000000000 --- a/dev-tools/release/print_rc_email.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/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 - -usage() { - echo "Usage: print_rc_email.sh -v version_number -m maven_repo_id" - echo " -v The #.#.#.RC# version number" - echo " -m The 4 digit id of the nexus maven repo" - exit 1 -} - -FULL_VERSION="" -MAVEN="" - -while getopts ":v:m:" opt; do - case ${opt} in - v ) - FULL_VERSION=$OPTARG - ;; - m ) - MAVEN=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${FULL_VERSION} == "" ]] || [[ ${MAVEN} == "" ]]; then - usage -fi - -if [[ $FULL_VERSION =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.(RC[0-9]+)$ ]]; then - VERSION=${BASH_REMATCH[1]} -else - echo "Malformed version number ${FULL_VERSION}. Example valid number - 1.9.0.RC1" - exit 1 -fi - -VERSION_MM=${VERSION%.*} - -#support mac or linux date arithmetic syntax -DEADLINE=$(date --date '+5 days' '+%a, %B %d %Y' 2>/dev/null || date -v +5d "+%a, %B %d %Y" 2>/dev/null || echo "<5 days from now>") - -cat << EOF -Hello Geode Dev Community, - -This is a release candidate for Apache Geode version ${FULL_VERSION}. -Thanks to all the community members for their contributions to this release! - -Please do a review and give your feedback, including the checks you performed. - -Voting deadline: -3PM PST ${DEADLINE}. - -Please note that we are voting upon the source tag: -rel/v${FULL_VERSION} - -Release notes: -https://cwiki.apache.org/confluence/display/GEODE/Release+Notes#ReleaseNotes-${VERSION} - -Source and binary distributions: -https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION}/ - -Maven staging repo: -https://repository.apache.org/content/repositories/orgapachegeode-${MAVEN} - -GitHub: -https://github.com/apache/geode/tree/rel/v${FULL_VERSION} -https://github.com/apache/geode-examples/tree/rel/v${FULL_VERSION} -https://github.com/apache/geode-native/tree/rel/v${FULL_VERSION} -https://github.com/apache/geode-benchmarks/tree/rel/v${FULL_VERSION} - -Pipelines: -https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-support-${VERSION_MM//./-}-main -https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-support-${VERSION_MM//./-}-rc - -Geode's KEYS file containing PGP keys we use to sign the release: -https://github.com/apache/geode/blob/develop/KEYS - -Command to run geode-examples: -./gradlew -PgeodeReleaseUrl=https://dist.apache.org/repos/dist/dev/geode/${FULL_VERSION} -PgeodeRepositoryUrl=https://repository.apache.org/content/repositories/orgapachegeode-${MAVEN} build runAll - -Regards -$(git config --get user.name) -EOF diff --git a/dev-tools/release/promote_rc.sh b/dev-tools/release/promote_rc.sh deleted file mode 100755 index 9d8459a443de..000000000000 --- a/dev-tools/release/promote_rc.sh +++ /dev/null @@ -1,703 +0,0 @@ -#!/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 - -usage() { - echo "Usage: promote_rc.sh -j ticket -v version_number -k your_full_gpg_public_key -g your_github_username" - echo " -j The GEODE-nnnnn Jira identifier for this release" - echo " -v The #.#.#.RC# version number to ship" - echo " -k Your 8 digit GPG key id (the last 8 digits of your gpg fingerprint)" - echo " -g Your github username" - exit 1 -} - -JIRA="" -FULL_VERSION="" -SIGNING_KEY="" -GITHUB_USER="" - -while getopts ":j:v:k:g:" opt; do - case ${opt} in - j ) - JIRA=$OPTARG - ;; - v ) - FULL_VERSION=$OPTARG - ;; - k ) - SIGNING_KEY=$OPTARG - ;; - g ) - GITHUB_USER=$OPTARG - ;; - \? ) - usage - ;; - esac -done - -if [[ ${JIRA} == "" ]] || [[ ${FULL_VERSION} == "" ]] || [[ ${SIGNING_KEY} == "" ]] || [[ ${GITHUB_USER} == "" ]]; then - usage -fi - -SIGNING_KEY=$(gpg --fingerprint "${SIGNING_KEY}" | tr -d ' ' | grep "${SIGNING_KEY}" | sed 's/Keyfingerprint=//' | tail -1) - -if [[ $SIGNING_KEY =~ ^[0-9A-Fa-f]{40}$ ]]; then - true -else - echo "Malformed signing key ${SIGNING_KEY}. Example valid key: ABCD1234" - exit 1 -fi - -if [[ $FULL_VERSION =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.(RC[0-9]+)$ ]]; then - VERSION=${BASH_REMATCH[1]} -else - echo "Malformed version number ${FULL_VERSION}. Example valid version: 1.9.0.RC1" - exit 1 -fi - -VERSION_MM=${VERSION%.*} - -set -x -WORKSPACE=$PWD/release-${VERSION}-workspace -GEODE=$WORKSPACE/geode -GEODE_DEVELOP=$WORKSPACE/geode-develop -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_NATIVE=$WORKSPACE/geode-native -GEODE_NATIVE_DEVELOP=$WORKSPACE/geode-native-develop -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -GEODE_BENCHMARKS_DEVELOP=$WORKSPACE/geode-benchmarks-develop -BREW_DIR=$WORKSPACE/homebrew-core -SVN_DIR=$WORKSPACE/dist/dev/geode -set +x - -if [ -d "$GEODE" ] && [ -d "$GEODE_DEVELOP" ] && [ -d "$GEODE_EXAMPLES" ] && [ -d "$GEODE_NATIVE" ] && [ -d "$GEODE_NATIVE_DEVELOP" ] && [ -d "$GEODE_BENCHMARKS" ] && [ -d "$GEODE_BENCHMARKS_DEVELOP" ] && [ -d "$BREW_DIR" ] && [ -d "$SVN_DIR" ] ; then - true -else - echo "Please run this script from the same working directory as you initially ran prepare_rc.sh" - exit 1 -fi - - -echo "" -echo "============================================================" -echo "Checking docker..." -echo "============================================================" -if ! docker images >/dev/null ; then - echo "Make sure docker daemon is running and try again." - exit 1 -fi - - -PATCH=${VERSION##*.} -if [ $PATCH -ne 0 ] ; then - echo "" - echo "============================================================" - echo "Checking if serialization version has changed" - echo "============================================================" - cd ${GEODE} - PREV=${VERSION%.*}.$(( PATCH - 1 )) - VER=geode-serialization/src/main/java/org/apache/geode/internal/serialization/KnownVersion.java - [ -r $VER ] || VER=geode-serialization/src/main/java/org/apache/geode/internal/serialization/Version.java - set -x - prevsver=$(git show "rel/v${PREV}:${VER}" | awk '/CURRENT =/{print $NF}') - cursver=$(cat ${VER} | awk '/CURRENT =/{print $NF}') - set +x - [ "$cursver" = "$prevsver" ] && echo "No change: $VERSION will replace $PREV in old-versions list" || echo "Changed: old-versions list will include both $PREV and $VERSION" -fi - - -function failMsg { - errln=$1 - echo "ERROR: script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 151-$(( errln - 1 ))) and try again" -} -trap 'failMsg $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Checking for later versions..." -echo "============================================================" -cd ${GEODE_DEVELOP} -latestnv=$(git tag| grep '^rel/v' | grep -v RC | cut -c6- | egrep '^[0-9]+\.[0-9]+\.[0-9]+$' | awk -F. '{print 1000000*$1+1000*$2+$3,$1"."$2"."$3}' | sort -n | tail -1) -latestn=$(echo $latestnv | awk '{print $1}') -latestv=$(echo $latestnv | awk '{print $2}') -thisre=$(echo $VERSION | awk -F. '{print 1000000*$1+1000*$2+$3}') -if [ $latestn -gt $thisre ] ; then - LATER="$latestv" - echo "Later version $LATER found; $VERSION will not be merged to master or tagged as 'latest' in docker." -else - LATER="" - echo "No later versions found; $VERSION will be tagged as 'latest' in docker and merged to master" -fi - - -echo "" -echo "============================================================" -echo "Releasing artifacts to mirror sites..." -echo "(note: must be logged in to svn as a PMC member or this will fail)" -echo "============================================================" -set -x -cd ${SVN_DIR}/../.. -svn update -svn mv dev/geode/${FULL_VERSION} release/geode/${VERSION} -cp dev/geode/KEYS release/geode/KEYS -svn commit -m "$JIRA: Release Apache Geode ${VERSION} - -Publish the source, binary, and checksum artifacts to ASF svn server, -from which they will be picked up and published within 15 minutes to -the URLs on https://geode.apache.org/releases/" -set +x - - -echo "" -echo "============================================================" -echo "Tagging ${FULL_VERSION} as ${VERSION} and pushing tags..." -echo "============================================================" -for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git tag -s -u ${SIGNING_KEY} rel/v${VERSION} -m "Apache Geode v${VERSION} release" rel/v${FULL_VERSION}^{} - git push origin rel/v${VERSION} - set +x -done - - -function waitforserver { - server="$1" - msg="$2" - file=apache-geode-${VERSION}.tgz - baseurl=https://${server}/${VERSION}/${file} - echo "" - echo "============================================================" - echo "Waiting for ${baseurl} to appear..." - if echo "${server}" | grep -q repo1 ; then - echo "(may take up to one hour after clicking 'Release' on http://repository.apache.org/ )" - elif echo "${server}" | grep -q dlcdn ; then - echo "(may take a few hours)" - else - echo "(may take up to 15 minutes)" - fi - [ -z "$msg" ] || echo "$msg" - echo "============================================================" - for suffix in "" .asc .sha256 ; do - if [ "${suffix}" = ".sha256" ] && echo "${server}" | grep -q repo1 ; then - continue; - fi - url=${baseurl}${suffix} - expectedsize=$(cd ${SVN_DIR}/../../release/geode/${VERSION}; ls -l ${file}${suffix} | awk '{print $5}') - if [ -z "$expectedsize" ] ; then - echo "internal error: unable to get size of ${SVN_DIR}/../../release/geode/${VERSION}/${file}${suffix}" - exit 1 - fi - actualsize=0 - while [ "$expectedsize" -ne "$actualsize" ] ; do - while ! curl -sk --output /dev/null --head --fail "$url"; do - echo -n . - sleep 12 - done - actualsize=$(curl -fsk --head "$url" | grep -i "Content-Length" | awk '{print $2}' | tr -d '\r') - done - echo "$url exists and is correct size" - done -} - -waitforserver "downloads.apache.org/geode" - - -echo "" -echo "============================================================" -GEODE_SHA=$(awk '{print $1}' < $WORKSPACE/dist/release/geode/${VERSION}/apache-geode-${VERSION}.tgz.sha256) -if [ -n "$LATER" ] ; then - echo "NOT updating brew to avoid overwriting newer version $LATER" - echo "============================================================" -else - echo "Updating brew" - echo "============================================================" - set -x - cd ${BREW_DIR}/Formula - git pull - git remote add myfork git@github.com:${GITHUB_USER}/homebrew-core.git || true - if ! git fetch myfork ; then - echo "Please fork https://github.com/Homebrew/homebrew-core" - exit 1 - fi - git checkout -b apache-geode-${VERSION} - set +x - sed -e 's# *url ".*# url "https://www.apache.org/dyn/closer.lua?path=geode/'"${VERSION}"'/apache-geode-'"${VERSION}"'.tgz"#' \ - -e '/ *mirror ".*www.*/d' \ - -e '/ *mirror ".*downloads.*/d' \ - -e 's# *mirror ".*archive.*# mirror "https://archive.apache.org/dist/geode/'"${VERSION}"'/apache-geode-'"${VERSION}"'.tgz"\ - mirror "https://downloads.apache.org/geode/'"${VERSION}"'/apache-geode-'"${VERSION}"'.tgz"#' \ - -e 's/ *sha256 ".*/ sha256 "'"${GEODE_SHA}"'"/' \ - -i.bak apache-geode.rb - rm apache-geode.rb.bak - set -x - git add apache-geode.rb - git diff --staged --color | cat - git commit -m "$JIRA: Brew apache-geode ${VERSION} - -This is the latest and greatest release of Apache Geode." - git push -u myfork - set +x -fi - - -echo "" -echo "============================================================" -echo "Updating Geode Dockerfile" -echo "============================================================" -set -x -cd ${GEODE}/docker -git pull -r -set +x -sed -e "s/^ENV GEODE_GPG.*/ENV GEODE_GPG ${SIGNING_KEY}/" \ - -e "s/^ENV GEODE_VERSION.*/ENV GEODE_VERSION ${VERSION}/" \ - -e "s/^ENV GEODE_SHA256.*/ENV GEODE_SHA256 ${GEODE_SHA}/" \ - -e "s/ha.pool.sks-keyservers.net/keyserver.ubuntu.com/" \ - -e '/This is a TEMPLATE/d' \ - -i.bak Dockerfile -rm Dockerfile.bak -set -x -git add Dockerfile -git diff --staged --color | cat -git commit -m "$JIRA: update Dockerfile to apache-geode ${VERSION} - -The Dockerfile is updated _after_ the release is already tagged, -because it needs to embed the sha256 of the release" -git push -set +x - - -echo "" -echo "============================================================" -echo "Updating Native Dockerfile and other variables" -echo "============================================================" -set -x -cd ${GEODE_NATIVE} -git pull -r -set +x -if [ -r .travis.yml ] ; then - sed -e "s/geode-native-build:[latest0-9.]*/geode-native-build:${VERSION}/" \ - -i.bak .travis.yml -fi -sed -e "s/GEODE_VERSION=.*/GEODE_VERSION=${VERSION}/" \ - -e "s/^ENV GEODE_VERSION.*/ENV GEODE_VERSION ${VERSION}/" \ - -i.bak $(git grep -l GEODE_VERSION= ; git grep -l 'ENV GEODE_VERSION') -rm $(find . -name '*.bak') -set -x -git add . -git diff --staged --color | cat -git commit -m "$JIRA: Update Dockerfile and vars - -Native client hardcodes Geode version to test with in several places. -Update native Dockerfile and other variables to apache-geode ${VERSION}" -git push -set +x - - -echo "" -echo "============================================================" -echo "Building Geode docker image" -echo "============================================================" -set -x -cd ${GEODE}/docker -sed -e '/www.apache.org.dyn.closer/d' -i.backup Dockerfile -if ! docker build . ; then - echo retrying in 1 minute... - sleep 60 - if ! docker build . ; then - echo retrying in 4 minutes... - sleep 240 - if ! docker build . ; then - echo retrying in 7 minutes... - sleep 420 - if ! docker build . ; then - echo "Hmm, the geode Dockerfile doesn't seem to want to build..." - echo "Try instrumenting it with some echo's to track down where it's failing..." - exit 7 - fi - fi - fi -fi -mv Dockerfile.backup Dockerfile -docker build -t apachegeode/geode:${VERSION} . -[ -n "$LATER" ] || docker build -t apachegeode/geode:latest . -set +x - - -echo "" -echo "============================================================" -echo "Building Native docker image" -echo "============================================================" -set -x -cd ${GEODE_NATIVE}/docker -docker build . || docker build . || docker build . -docker build -t apachegeode/geode-native-build:${VERSION} . -[ -n "$LATER" ] || docker build -t apachegeode/geode-native-build:latest . -set +x - - -echo "" -echo "============================================================" -echo "Publishing Geode docker image" -echo "============================================================" -set -x -cd ${GEODE}/docker -docker login -docker push apachegeode/geode:${VERSION} -[ -n "$LATER" ] || docker push apachegeode/geode:latest -set +x - - -echo "" -echo "============================================================" -echo "Publishing Native docker image" -echo "============================================================" -set -x -cd ${GEODE_NATIVE}/docker -docker push apachegeode/geode-native-build:${VERSION} -[ -n "$LATER" ] || docker push apachegeode/geode-native-build:latest -set +x - - -if [ -z "$LATER" ] ; then - echo "" - echo "============================================================" - echo "Setting Geode version for geode-native develop" - echo "============================================================" - set -x - cd ${GEODE_NATIVE_DEVELOP} - git pull - git remote add myfork git@github.com:${GITHUB_USER}/geode-native.git || true - git checkout -b update-to-geode-${VERSION} - set +x - - sed -e "s/GEODE_VERSION=[0-9.]*/GEODE_VERSION=${VERSION}/" \ - -e "s/^ENV GEODE_VERSION.*/ENV GEODE_VERSION ${VERSION}/" \ - -i.bak $(git grep -l GEODE_VERSION= ; git grep -l 'ENV GEODE_VERSION') - - rm $(find . -name '*.bak') - set -x - git add . - if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "$JIRA: Bump Geode version to ${VERSION} - -Native client hardcodes Geode version to test with in several places. -Update those variables to latest-and-greatest apache-geode ${VERSION}" - git push -u myfork - fi - set +x -fi - - -echo "" -echo "============================================================" -echo "Removing temporary commit from geode-examples..." -echo "============================================================" -set -x -cd ${GEODE_EXAMPLES} -git pull -set +x -sed -e 's#^geodeRepositoryUrl *=.*#geodeRepositoryUrl =#' \ - -e 's#^geodeReleaseUrl *=.*#geodeReleaseUrl =#' -i.bak gradle.properties -rm gradle.properties.bak -set -x -git add gradle.properties -git diff --staged --color | cat -git commit -m "Revert "'"'"$JIRA: Set temporary staging repo"'"'" - -The staging repo no longest exists, so set this back to search the -default location (mavencentral)" -git push -set +x - - -echo "" -echo "============================================================" -if [ -n "$LATER" ] ; then - echo "NOT merging to master to avoid overwriting newer version $LATER" - echo "============================================================" -else - echo "Merging to master" - echo "============================================================" - for DIR in ${GEODE} ${GEODE_EXAMPLES} ${GEODE_NATIVE} ${GEODE_BENCHMARKS} ; do - set -x - cd ${DIR} - git remote set-branches --add origin master - git fetch origin - git checkout support/${VERSION_MM} - git checkout -b release/${VERSION} rel/v${VERSION} - #this creates a merge commit that will then be ff-merged to master, so word it from that perspective - git merge -s ours origin/master -m "Replacing master with contents of rel/v${VERSION}" - git checkout master - git merge release/${VERSION} - git push origin master - set +x - done -fi - - -echo "" -echo "============================================================" -echo "Updating 'old' versions and Benchmarks baseline on develop" -echo "============================================================" -set -x -cd ${GEODE_DEVELOP} -git pull -git remote add myfork git@github.com:${GITHUB_USER}/geode.git || true -git checkout -b add-${VERSION}-to-old-versions -set +x -#add at the end if this is a new minor or a patch to the latest minor, otherwise add after it's predecessor -if [ $PATCH -eq 0 ] || grep -q "'${PREV}'].each" settings.gradle ; then - #before: - # '1.9.0'].each { - #after: - # '1.9.0', - # '1.10.0'].each { - sed -e "s/].each/,\\ - '${VERSION}'].each/" \ - -i.bak settings.gradle -else - #before: - # '1.9.0', - #after: - # '1.9.0', - # '1.9.1', - sed -e "s/'${PREV}'/'${PREV}',\\ - '${VERSION}'/" \ - -i.bak settings.gradle -fi -rm settings.gradle.bak - -action="Add" -ser="" -if [ $PATCH -ne 0 ] ; then - #if the serialization version has not changed, we can drop the previous patch - if [ "$cursver" = "$prevsver" ] ; then - sed -e "/'${PREV}'/d" -i.bak settings.gradle - rm settings.gradle.bak - action="Replace ${PREV} with" - ser=" - -The serialization version has not changed between ${PREV} and ${VERSION}, -so there should be no need to keep both" - fi -fi - -if [ -z "$LATER" ] ; then - #also update benchmark baseline for develop to this new minor - sed \ - -e "s/^ baseline_version_default:.*/ baseline_version_default: '${VERSION}'/" \ - -e "s/^ baseline_branch_default:.*/ baseline_branch_default: ''/" \ - -i.bak ci/pipelines/shared/jinja.variables.yml - rm ci/pipelines/shared/jinja.variables.yml.bak - BENCHMSG=" and set as default Benchmarks baseline" - #if custom baseline on develop is newer [than release branch cut date], resetting might - #be the wrong choice. but, assuming it's older, a new minor is the time to un-custom it - if [ $PATCH = 0 ] ; then - sed \ - -e "s/^ baseline_version:.*/ baseline_version: '${VERSION}'/" \ - -e "s/^ baseline_branch:.*/ baseline_branch: ''/" \ - -i.bak ci/pipelines/shared/jinja.variables.yml - rm ci/pipelines/shared/jinja.variables.yml.bak - BENCHMSG=" and set as Benchmarks baseline" - fi - set -x - git add ci/pipelines/shared/jinja.variables.yml -fi -set -x -git add settings.gradle -git diff --staged --color | cat -git commit -m "$JIRA: ${action} ${VERSION} as old version - -${action} ${VERSION} in old versions${BENCHMSG} on develop -to enable rolling upgrade tests from ${VERSION}${ser}" -git push -u myfork -set +x - - -echo "" -echo "============================================================" -echo "Updating 'old' versions and Benchmarks baseline on support/$VERSION_MM" -echo "============================================================" -set -x -cd ${GEODE} -git checkout support/${VERSION_MM} -git pull -set +x -#add at the end as this release will always be the latest on this branch -sed -e "s/].each/,\\ - '${VERSION}'].each/" \ - -i.bak settings.gradle -rm settings.gradle.bak -action="Add" -ser="" -if [ $PATCH -ne 0 ] ; then - #if the serialization version has not changed, we can drop the previous patch - if [ "$cursver" = "$prevsver" ] ; then - sed -e "/'${PREV}'/d" -i.bak settings.gradle - rm settings.gradle.bak - action="Replace ${PREV} with" - ser=" - -The serialization version has not changed between ${PREV} and ${VERSION}, -so there should be no need to keep both" - fi -else - #also update benchmark baseline for support branch to its new minor - sed \ - -e "s/^ baseline_version:.*/ baseline_version: '${VERSION}'/" \ - -e "s/^ baseline_version:.*/ baseline_version: '${VERSION}'/" \ - -e "s/^ baseline_version_default:.*/ baseline_version_default: '${VERSION}'/" \ - -e "s/^ baseline_branch:.*/ baseline_branch: ''/" \ - -e "s/^ baseline_branch_default:.*/ baseline_branch_default: ''/" \ - -i.bak ci/pipelines/shared/jinja.variables.yml - rm ci/pipelines/shared/jinja.variables.yml.bak - BENCHMSG2=" and set as Benchmarks baseline" - set -x - git add ci/pipelines/shared/jinja.variables.yml -fi -set -x -git add settings.gradle -git diff --staged --color | cat -git commit -m "$JIRA: ${action} ${VERSION} as old version - -${action} ${VERSION} in old versions${BENCHMSG2} on support/$VERSION_MM -to enable rolling upgrade tests from ${VERSION}${ser}" -git push -set +x - - -if [ -z "$LATER" ] ; then - for branch in develop support/$VERSION_MM ; do - echo "" - echo "============================================================" - echo "Updating default benchmark baseline on $branch" - echo "============================================================" - set -x - [ "develop" = "$branch" ] && BENCH=${GEODE_BENCHMARKS_DEVELOP} || BENCH=${GEODE_BENCHMARKS} - [ "develop" = "$branch" ] && BASEL=${VERSION} || BASEL=${VERSION_MM}.0 - cd ${BENCH} - git checkout $branch - git pull - set +x - #DEFAULT_BASELINE_VERSION=1.14.0 - sed -e "s/^DEFAULT_BASELINE_VERSION=.*/DEFAULT_BASELINE_VERSION=${BASEL}/" \ - -i.bak infrastructure/scripts/aws/run_against_baseline.sh - rm infrastructure/scripts/aws/run_against_baseline.sh.bak - set -x - git add infrastructure/scripts/aws/run_against_baseline.sh - if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "$JIRA: Update benchmark baseline - -Updates the default benchmark baseline on $branch to ${BASEL}" - git push - fi - set +x - done -fi - - -echo "" -echo "============================================================" -echo "Removing old Geode versions from mirrors" -echo "============================================================" -set -x -cd ${SVN_DIR}/../../release/geode -svn update -#identify the latest patch release for "N-2" (the latest 3 major.minor releases), remove anything else from mirrors (all releases remain available on non-mirrored archive site) -RELEASES_TO_KEEP=3 -set +x -ls | awk -F. '/^[0-9]/{print 1000000*$1+1000*$2+$3,$1"."$2"."$3}'| sort -n | awk '{mm=$2;sub(/\.[^.]*$/,"",mm);V[mm]=$2}END{for(v in V){print V[v]}}'|tail -$RELEASES_TO_KEEP > ../keep -echo Keeping releases: $(cat ../keep) -rm -f ../did.remove -(ls | grep '^[0-9]'; cat ../keep ../keep)|sort|uniq -u|while read oldVersion; do - set -x - svn rm $oldVersion - svn commit -m "$JIRA: Remove $oldVersion from mirrors - -ASF requests that we keep preferably one, and definitely fewer than 5 -releases on the mirrors, so aim for 3 to match N-2 support policy. - -Note: it is still archived at http://archive.apache.org/dist/geode" - set +x - [ ! -r ../did.remove ] || echo -n " and " >> ../did.remove - echo -n $oldVersion >> ../did.remove -done -touch ../did.remove -DID_REMOVE=$(cat ../did.remove) -rm ../keep ../did.remove - - -waitforserver "repo1.maven.org/maven2/org/apache/geode/apache-geode" - -echo "" -NEWVERSION="${VERSION_MM}.$(( PATCH + 1 ))" -echo "============================================================" -echo -n "Bumping version to ${NEWVERSION}" -cd "${WORKSPACE}/.." -${0%/*}/set_versions.sh -j $JIRA -v ${NEWVERSION} -s -w "${WORKSPACE}" - - -echo "" -echo "============================================================" -echo 'Done promoting Release Candidate to Official Release!' -echo "============================================================" -MAJOR="${VERSION_MM%.*}" -MINOR="${VERSION_MM#*.}" -PATCH="${VERSION##*.}" -cd ${GEODE}/../.. -echo "Final steps (some gaps in numbering is normal since not all steps apply to all releases):" -[ -n "$LATER" ] || echo "2. Go to https://github.com/${GITHUB_USER}/homebrew-core/pull/new/apache-geode-${VERSION} and submit the pull request" -echo "3. Go to https://github.com/${GITHUB_USER}/geode/pull/new/add-${VERSION}-to-old-versions and create the pull request" -[ -n "$LATER" ] || echo "3b.Go to https://github.com/${GITHUB_USER}/geode-native/pull/new/update-to-geode-${VERSION} and create the pull request" -[ -n "$LATER" ] && tag=":${VERSION}" || tag="" -echo "4. Validate docker image: docker run -it apachegeode/geode${tag}" -[ -n "$LATER" ] && caveat=" (UNLESS they are still unreleased on a later patch branch)" -echo "5. Mark ${VERSION} as Released in Jira and Bulk-transition JIRA issues fixed in this release to Closed${caveat}" -echo "5b.Publish to GitHub ( https://github.com/apache/geode/tags then Create Release from the 2nd ... menu ), filling out the form as follows:" -echo " Upload apache-geode-${VERSION}.tgz from: open ${GEODE}/geode-assembly/build/distributions/" -echo " Release Title: Apache Geode ${VERSION}" -echo " Release Description:" -[ "${PATCH}" -ne 0 ] && echo "This patch release includes a few bug fixes." || echo " This release includes a significant number of bug fixes and improvements." -echo "" -echo "sha256 for apache-geode-${VERSION}.tgz is $(awk '{print $1}' < ${GEODE}/geode-assembly/build/distributions/apache-geode-${VERSION}.tgz.sha256)" -echo "" -echo "See full release notes at https://cwiki.apache.org/confluence/display/GEODE/Release+Notes#ReleaseNotes-${VERSION}" -echo "" -echo "8. Check that ${VERSION} documentation has been published to https://geode.apache.org/docs/" -[ -z "$DID_REMOVE" ] || DID_REMOVE=" and ${DID_REMOVE} info has been removed" -echo "9. Check that ${VERSION} download info has been published to https://geode.apache.org/releases/${DID_REMOVE}" -[ "${PATCH}" -ne 0 ] || echo "10. If 3rd-party dependencies haven't been bumped in awhile, ask on the dev list for a volunteer (details in dev-tools/dependencies/README.md)" -[ "${PATCH}" -ne 0 ] || [ "${MINOR}" -lt 15 ] || echo "11. In accordance with Geode's N-2 support policy, propose on the dev list that the time has come to ${0%/*}/end_of_support.sh -v ${MAJOR}.$((MINOR - 3))" -[ "${PATCH}" -ne 0 ] || [ -n "$LATER" ] || echo "12. Log in to https://hub.docker.com/repository/docker/apachegeode/geode and update the latest Dockerfile linktext and url to ${VERSION_MM}" -[ -n "$LATER" ] || andnative=", geode-benchmarks, and geode-native" -echo "If there are any support branches between ${VERSION_MM} and develop, manually cherry-pick '${VERSION}' bump from develop to those branches of geode${andnative}." -echo "Bump support pipeline to ${VERSION_MM}.$(( PATCH + 1 )) by plussing BumpPatch in https://concourse.apachegeode-ci.info/teams/main/pipelines/apache-support-${VERSION_MM//./-}-main?group=semver-management" -[ "${PATCH}" -ne 0 ] || echo "Run cd ${GEODE} && geode-management/src/test/script/update-management-wiki.sh" -[ -n "$LATER" ] || echo "Make a note to confirm tomorrow morning that your homebrew PR passed its PR checks and was merged to master" -echo 'Send email! Note: MUST be sent from your @apache.org email address (see https://infra.apache.org/committer-email.html) and MUST be sent as Plain text (in gmail click three dots at bottom of compose window then Plain text mode)' -${0%/*}/print_announce_email.sh -v "${VERSION}" -f "${LATER}" -echo "" -which pbcopy >/dev/null && ${0%/*}/print_announce_email.sh -v "${VERSION}" -f "${LATER}" | pbcopy && echo "(copied to clipboard)" -waitforserver "dlcdn.apache.org/geode" "Please wait for this to complete before sending the above [ANNOUNCE] email. -All other tasks above can be completed now (while you wait for dlcdn)." diff --git a/dev-tools/release/set_copyright.sh b/dev-tools/release/set_copyright.sh deleted file mode 100755 index 74df49d655de..000000000000 --- a/dev-tools/release/set_copyright.sh +++ /dev/null @@ -1,68 +0,0 @@ -#!/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 - -usage() { - echo "Usage: set_copyright.sh -j ticket dirs" - echo " -j The GEODE-nnnnn Jira identifier for this chore" - echo " dirs one or more directories to search for copyrights" - exit 1 -} - -if [[ "$3" == "" ]] ; then - usage -fi - -if [[ "$1" != "-j" ]] ; then - usage -fi - -JIRA=$2 -shift 2 - -function failMsg { - errln=$1 - echo "ERROR: set_copyright script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 46-$(( errln - 1 ))) and try again" -} -trap 'failMsg $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Checking Copyright NOTICE and updating year if necessary" -echo "============================================================" -set -x -year=$(date +%Y) -for DIR in $@ ; do - cd ${DIR} - git grep -l '^Copyright.*Apache' | grep NOTICE | while read NOTICE ; do - sed \ - -e "2s/ \(20[0-9][0-9]\) / \1-${year} /" \ - -e "2s/-20[0-9][0-9] /-${year} /" \ - -e "2s/${year}-${year}/${year}/" \ - -i.bak $NOTICE - rm -f $NOTICE.bak - git add $NOTICE - done - if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -a -m "$JIRA: Bumping copyright year to ${year}" - fi -done -set +x diff --git a/dev-tools/release/set_versions.sh b/dev-tools/release/set_versions.sh deleted file mode 100755 index afb65a6dc7f6..000000000000 --- a/dev-tools/release/set_versions.sh +++ /dev/null @@ -1,215 +0,0 @@ -#!/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 - -usage() { - echo "Usage: set_versions.sh -j ticket -v version_number [-s]" - echo " -j The GEODE-nnnnn Jira identifier for this release" - echo " -v The #.#.# version number for the next release" - echo " -s configure examples to use latest snapshot instead of release" - exit 1 -} - -JIRA="" -FULL_VERSION="" - -while getopts ":j:v:snw:" opt; do - case ${opt} in - j ) - JIRA=$OPTARG - ;; - v ) - VERSION=$OPTARG - ;; - s ) - EXAMPLES_USE_SNAPSHOTS=true - ;; - n ) - NOPUSH=true - ;; - w ) - WORKSPACE="$OPTARG" - CLEAN=false - ;; - \? ) - usage - ;; - esac -done - -if [[ ${JIRA} == "" ]] || [[ ${VERSION} == "" ]] ; then - usage -fi - -if ! [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Malformed version number ${VERSION}. Example valid version: 1.9.0" - exit 1 -fi - -BUILDSUFFIX="-build.0" -VERSION_MM=${VERSION%.*} - -if [ "${EXAMPLES_USE_SNAPSHOTS}" = "true" ] ; then - GEODEFOREXAMPLES="${VERSION_MM}.+" -else - GEODEFOREXAMPLES="${VERSION}" -fi - -set -x -[ -n "${WORKSPACE}" ] || WORKSPACE=$PWD/release-${VERSION}-workspace -GEODE=$WORKSPACE/geode -GEODE_EXAMPLES=$WORKSPACE/geode-examples -GEODE_BENCHMARKS=$WORKSPACE/geode-benchmarks -set +x - - -function failMsg1 { - echo "ERROR: set_versions script did NOT complete successfully. Please try again." -} -trap failMsg1 ERR - - -if [ "${CLEAN}" != "false" ] ; then - echo "" - echo "============================================================" - echo "Cleaning workspace directory..." - echo "============================================================" - set -x - rm -rf $WORKSPACE - mkdir -p $WORKSPACE - cd $WORKSPACE - set +x - - - echo "" - echo "============================================================" - echo "Cloning repositories..." - echo "============================================================" - set -x - git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode.git - git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode-examples.git - git clone --single-branch --branch support/${VERSION_MM} git@github.com:apache/geode-benchmarks.git - set +x -fi - - -function failMsg2 { - errln=$1 - echo "ERROR: set_versions script did NOT complete successfully" - echo "Comment out any steps that already succeeded (approximately lines 87-$(( errln - 1 ))) and try again" -} -trap 'failMsg2 $LINENO' ERR - - -echo "" -echo "============================================================" -echo "Setting Geode versions" -echo "============================================================" -set -x -cd ${GEODE} -set +x - -#version = 1.13.0-build.0 -sed -e "s/^version =.*/version = ${VERSION}${BUILDSUFFIX}/" -i.bak gradle.properties - -# product_version: '1.13' -sed -E \ - -e "s#product_version: '[0-9.]+'#product_version: '${VERSION_MM}'#" \ - -i.bak geode-book/config.yml - -#git clone -b branch --depth 1 https://github.com/apache/geode.git geode -sed -e "s#clone -b [ds][evlopurt/0-9.]*#clone -b support/${VERSION_MM}#" \ - -i.bak \ - ci/docker/cache_dependencies.sh \ - ci/images/google-geode-builder/scripts/cache_dependencies.sh - -rm -f gradle.properties.bak geode-book/config.yml.bak ci/docker/cache_dependencies.sh.bak ci/images/google-geode-builder/scripts/cache_dependencies.sh.bak -set -x -git add gradle.properties geode-book/config.yml ci/docker/cache_dependencies.sh ci/images/google-geode-builder/scripts/cache_dependencies.sh -if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "$JIRA: Bump version to ${VERSION} - -As part of the Geode Release Process, the build number must -be rolled forward so work can begin on the next release" - [ "$NOPUSH" = "true" ] || git push -u origin -fi -set +x - - -echo "" -echo "============================================================" -echo "Setting geode-examples version" -echo "============================================================" -set -x -cd ${GEODE_EXAMPLES} -git pull -set +x - -#version = 1.12.0-build.0 -#geodeVersion = 1.12.+ -sed -e "s/^version = .*/version = ${VERSION}${BUILDSUFFIX}/" \ - -e "s/^geodeVersion = .*/geodeVersion = ${GEODEFOREXAMPLES}/" \ - -i.bak gradle.properties - -rm gradle.properties.bak -set -x -git add . -if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "$JIRA: Bump version to ${VERSION} - -As part of the Geode Release Process, the geode-examples build number -must be rolled forward as work begins on the next release" - [ "$NOPUSH" = "true" ] || git push -u origin -fi -set +x - - -echo "" -echo "============================================================" -echo "Setting geode-benchmarks version" -echo "============================================================" -set -x -cd ${GEODE_BENCHMARKS} -git pull -set +x - -#versionNumber = 1.14.0 -sed -e "s/^versionNumber = .*/versionNumber = ${VERSION}/" \ - -i.bak gradle.properties - -rm gradle.properties.bak -set -x -git add gradle.properties -if [ $(git diff --staged | wc -l) -gt 0 ] ; then - git diff --staged --color | cat - git commit -m "$JIRA: Bump version to ${VERSION} - -As part of the Geode Release Process, the geode-benchmarks build number -must be rolled forward as work begins on the next release" - [ "$NOPUSH" = "true" ] || git push -u origin -fi -set +x - - -echo "" -echo "============================================================" -echo 'Done setting support versions!' -echo "============================================================" -cd ${GEODE}/../.. diff --git a/docker/Dockerfile b/docker/Dockerfile index be0a2010799a..28c309ca8f8d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,23 +16,22 @@ FROM bellsoft/liberica-openjdk-alpine:17 -RUN echo "This is a TEMPLATE, DO NOT build from this Dockerfile. Instead checkout master or any released support/x.y branch." ; exit 1 # runtime dependencies RUN apk add --no-cache \ bash \ ncurses -ENV GEODE_GPG $$PLACEHOLDER$$ +ENV GEODE_GPG 5C3DA8FBB1052F4DF1DEB1EF62F7DA41B7D8F26C ENV GEODE_HOME /geode ENV PATH $PATH:$GEODE_HOME/bin # https://geode.apache.org/releases/ -ENV GEODE_VERSION $$PLACEHOLDER$$ +ENV GEODE_VERSION 2.0.2 # Binaries TGZ SHA-256 # https://dist.apache.org/repos/dist/release/geode/VERSION/apache-geode-VERSION.tgz.sha256 -ENV GEODE_SHA256 $$PLACEHOLDER$$ +ENV GEODE_SHA256 a1a875d5df88d80ae2953def1b0811ee8a466010a5b897682356740bdf19bb7c # http://apache.org/dyn/closer.cgi/geode/1.3.0/apache-geode-1.3.0.tgz @@ -47,7 +46,6 @@ RUN set -eux; \ ; do \ target="$(basename "$file")"; \ for url in \ - "https://www.apache.org/dyn/closer.lua/$file?action=download" \ "https://downloads.apache.org/$file" \ "https://archive.apache.org/dist/$file" \ ; do \ diff --git a/extensions/geode-modules-session-internal/src/main/java/org/apache/geode/modules/session/internal/filter/GemfireHttpSession.java b/extensions/geode-modules-session-internal/src/main/java/org/apache/geode/modules/session/internal/filter/GemfireHttpSession.java index 89fd9386b9c9..8e81b59d52ba 100644 --- a/extensions/geode-modules-session-internal/src/main/java/org/apache/geode/modules/session/internal/filter/GemfireHttpSession.java +++ b/extensions/geode-modules-session-internal/src/main/java/org/apache/geode/modules/session/internal/filter/GemfireHttpSession.java @@ -20,6 +20,7 @@ import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Collections; @@ -78,6 +79,13 @@ public class GemfireHttpSession implements HttpSession, DataSerializable, Delta private ServletContext context; + /** + * Cached ObjectInputFilter to avoid recreating on every deserialization. + * Initialized lazily on first use with double-checked locking. + */ + private volatile ObjectInputFilter cachedFilter; + private volatile boolean filterLogged = false; + /** * A session becomes invalid if it is explicitly invalidated or if it expires. */ @@ -107,6 +115,34 @@ public DataSerializable newInstance() { }); } + /** + * Gets or creates the cached ObjectInputFilter. Uses double-checked locking to avoid + * unnecessary synchronization after initialization. + * + * @return the cached ObjectInputFilter, or null if no filter is configured + */ + private ObjectInputFilter getOrCreateFilter() { + if (cachedFilter == null && !filterLogged) { + synchronized (this) { + if (cachedFilter == null && !filterLogged) { + String filterPattern = getServletContext() + .getInitParameter("serializable-object-filter"); + + if (filterPattern != null) { + cachedFilter = ObjectInputFilter.Config.createFilter(filterPattern); + LOG.info("ObjectInputFilter configured with pattern: {}", filterPattern); + } else { + LOG.warn("No ObjectInputFilter configured. Session deserialization is not protected " + + "against malicious payloads. Configure 'serializable-object-filter' in web.xml " + + "to enable deserialization security."); + } + filterLogged = true; + } + } + } + return cachedFilter; + } + /** * Constructor used for de-serialization */ @@ -144,8 +180,11 @@ public Object getAttribute(String name) { oos.writeObject(obj); oos.close(); + // Get or create cached filter for secure deserialization + ObjectInputFilter filter = getOrCreateFilter(); + ObjectInputStream ois = new ClassLoaderObjectInputStream( - new ByteArrayInputStream(baos.toByteArray()), loader); + new ByteArrayInputStream(baos.toByteArray()), loader, filter); tmpObj = ois.readObject(); } catch (IOException | ClassNotFoundException e) { LOG.error("Exception while recreating attribute '" + name + "'", e); diff --git a/extensions/geode-modules-tomcat10/src/test/resources/expected-pom.xml b/extensions/geode-modules-tomcat10/src/test/resources/expected-pom.xml index 1b3957f9ed07..b1feb8cbe62c 100644 --- a/extensions/geode-modules-tomcat10/src/test/resources/expected-pom.xml +++ b/extensions/geode-modules-tomcat10/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/ClassLoaderObjectInputStream.java b/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/ClassLoaderObjectInputStream.java index 6368bf6b4a5f..8acb35b54e67 100644 --- a/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/ClassLoaderObjectInputStream.java +++ b/extensions/geode-modules/src/main/java/org/apache/geode/modules/util/ClassLoaderObjectInputStream.java @@ -16,16 +16,43 @@ import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; /** * This class is used when session attributes need to be reconstructed with a new classloader. + * It now supports ObjectInputFilter for secure deserialization. */ public class ClassLoaderObjectInputStream extends ObjectInputStream { private final ClassLoader loader; + /** + * Constructs a ClassLoaderObjectInputStream with an ObjectInputFilter for secure deserialization. + * + * @param in the input stream to read from + * @param loader the ClassLoader to use for class resolution + * @param filter the ObjectInputFilter to validate deserialized classes (required for security) + * @throws IOException if an I/O error occurs + */ + public ClassLoaderObjectInputStream(InputStream in, ClassLoader loader, ObjectInputFilter filter) + throws IOException { + super(in); + this.loader = loader; + if (filter != null) { + setObjectInputFilter(filter); + } + } + + /** + * Legacy constructor for backward compatibility. + * + * @deprecated Use + * {@link #ClassLoaderObjectInputStream(InputStream, ClassLoader, ObjectInputFilter)} + * with a filter for secure deserialization + */ + @Deprecated public ClassLoaderObjectInputStream(InputStream in, ClassLoader loader) throws IOException { super(in); this.loader = loader; diff --git a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/ClassLoaderObjectInputStreamTest.java b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/ClassLoaderObjectInputStreamTest.java index b0851dca0080..3a5c0ebf6e20 100644 --- a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/ClassLoaderObjectInputStreamTest.java +++ b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/ClassLoaderObjectInputStreamTest.java @@ -21,6 +21,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InvalidClassException; +import java.io.ObjectInputFilter; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; @@ -162,4 +164,142 @@ File getTempFile() { return null; } } + + @Test + public void filterRejectsUnauthorizedClasses() throws Exception { + // Arrange: Create filter that only allows java.lang and java.util classes + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("java.lang.*;java.util.*;!*"); + TestSerializable testObject = new TestSerializable("test"); + byte[] serializedData = serialize(testObject); + + // Act & Assert: Deserialization should be rejected by filter + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedData), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class); + } + + @Test + public void filterAllowsAuthorizedClasses() throws Exception { + // Arrange: Create filter that allows this test class package + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter( + "java.lang.*;java.util.*;org.apache.geode.modules.util.**;!*"); + TestSerializable testObject = new TestSerializable("test data"); + byte[] serializedData = serialize(testObject); + + // Act: Deserialize with filter + Object deserialized; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedData), + Thread.currentThread().getContextClassLoader(), + filter)) { + deserialized = ois.readObject(); + } + + // Assert: Object should be successfully deserialized + assertThat(deserialized).isInstanceOf(TestSerializable.class); + assertThat(((TestSerializable) deserialized).getData()).isEqualTo("test data"); + } + + @Test + public void nullFilterAllowsAllClasses() throws Exception { + // Arrange: Null filter means no filtering (backward compatibility) + TestSerializable testObject = new TestSerializable("unfiltered data"); + byte[] serializedData = serialize(testObject); + + // Act: Deserialize with null filter + Object deserialized; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedData), + Thread.currentThread().getContextClassLoader(), + null)) { + deserialized = ois.readObject(); + } + + // Assert: Object should be successfully deserialized + assertThat(deserialized).isInstanceOf(TestSerializable.class); + assertThat(((TestSerializable) deserialized).getData()).isEqualTo("unfiltered data"); + } + + @Test + public void deprecatedConstructorStillWorks() throws Exception { + // Arrange: Use deprecated constructor without filter + TestSerializable testObject = new TestSerializable("legacy code"); + byte[] serializedData = serialize(testObject); + + // Act: Deserialize using deprecated constructor + Object deserialized; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedData), + Thread.currentThread().getContextClassLoader())) { + deserialized = ois.readObject(); + } + + // Assert: Object should be successfully deserialized (backward compatibility) + assertThat(deserialized).isInstanceOf(TestSerializable.class); + assertThat(((TestSerializable) deserialized).getData()).isEqualTo("legacy code"); + } + + @Test + public void filterEnforcesResourceLimits() throws Exception { + // Arrange: Create filter with very low depth limit + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("maxdepth=2;*"); + NestedSerializable nested = new NestedSerializable( + new NestedSerializable( + new NestedSerializable(null))); // Depth of 3 + byte[] serializedData = serialize(nested); + + // Act & Assert: Should reject due to depth limit + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedData), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class); + } + + /** + * Helper method to serialize an object to byte array + */ + private byte[] serialize(Object obj) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(obj); + } + return baos.toByteArray(); + } + + /** + * Test class for serialization testing + */ + static class TestSerializable implements Serializable { + private static final long serialVersionUID = 1L; + private final String data; + + TestSerializable(String data) { + this.data = data; + } + + String getData() { + return data; + } + } + + /** + * Nested test class for depth limit testing + */ + static class NestedSerializable implements Serializable { + private static final long serialVersionUID = 1L; + private final NestedSerializable nested; + + NestedSerializable(NestedSerializable nested) { + this.nested = nested; + } + } } diff --git a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/DeserializationSecurityTest.java b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/DeserializationSecurityTest.java new file mode 100644 index 000000000000..cf803aa6ef37 --- /dev/null +++ b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/DeserializationSecurityTest.java @@ -0,0 +1,484 @@ +/* + * 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.geode.modules.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InvalidClassException; +import java.io.ObjectInputFilter; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +import org.junit.Test; + +/** + * Security tests proving that ObjectInputFilter configuration via web.xml + * fixes the same deserialization vulnerabilities as PR-7941 (CVE, CVSS 9.8). + * + * These tests demonstrate: + * 1. Blocking known gadget chain classes (RCE prevention) + * 2. Whitelist-based class filtering + * 3. Resource exhaustion prevention (depth, array size, references) + * 4. Package-level access control + */ +public class DeserializationSecurityTest { + + /** + * TEST 1: Blocks known gadget chain classes used in deserialization attacks + * + * Simulates attack scenario: Attacker sends serialized gadget chain object + * Expected: ObjectInputFilter rejects dangerous classes + * + * Common gadget classes in real attacks: + * - org.apache.commons.collections.functors.InvokerTransformer + * - org.apache.commons.collections.functors.ChainedTransformer + * - com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl + */ + @Test + public void blocksKnownGadgetChainClasses() throws Exception { + // Arrange: Filter that blocks commons-collections (known gadget source) + String filterPattern = "java.lang.*;java.util.*;!org.apache.commons.collections.**;!*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Simulated gadget object (using HashMap as stand-in for actual gadget) + GadgetSimulator gadget = new GadgetSimulator("malicious-payload"); + byte[] serializedGadget = serialize(gadget); + + // Act & Assert: Deserialization should be blocked + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serializedGadget), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + } + + /** + * TEST 2: Enforces whitelist-only deserialization + * + * Security best practice: Only allow explicitly approved classes + * This prevents zero-day gadget chains in unknown libraries + */ + @Test + public void enforcesWhitelistOnlyDeserialization() throws Exception { + // Arrange: Strict whitelist - only java.lang and java.util allowed + String filterPattern = "java.lang.*;java.util.*;!*"; // !* rejects everything else + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Try to deserialize application class (not in whitelist) + UnauthorizedClass unauthorized = new UnauthorizedClass("sneaky-data"); + byte[] serialized = serialize(unauthorized); + + // Act & Assert: Should reject non-whitelisted class + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + } + + /** + * TEST 3: Allows only whitelisted application packages + * + * Demonstrates proper configuration for session attributes: + * - Allow JDK classes (java.*, javax.*) + * - Allow application-specific packages + * - Block everything else + */ + @Test + public void allowsWhitelistedApplicationPackages() throws Exception { + // Arrange: Whitelist includes this test package + String filterPattern = "java.lang.*;java.util.*;org.apache.geode.modules.util.**;!*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Serialize allowed application class + AllowedSessionAttribute allowed = new AllowedSessionAttribute("user-data", 42); + byte[] serialized = serialize(allowed); + + // Act: Deserialize whitelisted class + Object deserialized; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + deserialized = ois.readObject(); + } + + // Assert: Should successfully deserialize + assertThat(deserialized).isInstanceOf(AllowedSessionAttribute.class); + AllowedSessionAttribute result = (AllowedSessionAttribute) deserialized; + assertThat(result.getName()).isEqualTo("user-data"); + assertThat(result.getValue()).isEqualTo(42); + } + + /** + * TEST 4: Prevents depth-based DoS attacks + * + * Attack: Deeply nested objects cause stack overflow + * Defense: maxdepth limit prevents excessive recursion + */ + @Test + public void preventsDepthBasedDoSAttack() throws Exception { + // Arrange: Limit object graph depth to 10 + String filterPattern = "maxdepth=10;*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Create deeply nested object (depth > 10) + DeepObject deep = createDeeplyNestedObject(15); + byte[] serialized = serialize(deep); + + // Act & Assert: Should reject due to depth limit + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + } + + /** + * TEST 5: Prevents array-based memory exhaustion + * + * Attack: Large arrays consume excessive memory + * Defense: maxarray limit prevents allocation bombs + */ + @Test + public void preventsArrayBasedMemoryExhaustion() throws Exception { + // Arrange: Limit array size to 1000 elements + String filterPattern = "maxarray=1000;*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Create large array (exceeds limit) + byte[] largeArray = new byte[10000]; + ArrayContainer container = new ArrayContainer(largeArray); + byte[] serialized = serialize(container); + + // Act & Assert: Should reject due to array size limit + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + } + + /** + * TEST 6: Demonstrates reference limit configuration + * + * Note: maxrefs tracking depends on JVM implementation details. + * This test verifies the filter accepts reasonable reference counts. + */ + @Test + public void allowsReasonableReferenceCount() throws Exception { + // Arrange: Set reasonable reference limit + String filterPattern = "maxrefs=1000;*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Create object graph with moderate references + ReferenceContainer container = createManyReferences(50); + byte[] serialized = serialize(container); + + // Act: Should succeed with reasonable references + Object result; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + result = ois.readObject(); + } + + // Assert: Object successfully deserialized + assertThat(result).isInstanceOf(ReferenceContainer.class); + } + + /** + * TEST 7: Allows controlled stream sizes within limits + * + * Demonstrates: maxbytes parameter tracks cumulative bytes read + * Note: maxbytes is checked during deserialization, allowing moderate payloads + */ + @Test + public void allowsModerateStreamSizes() throws Exception { + // Arrange: Reasonable stream size limit + String filterPattern = "maxbytes=50000;*"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Create moderate-sized object + byte[] data = new byte[1000]; + LargeObject obj = new LargeObject(data); + byte[] serialized = serialize(obj); + + // Act: Should succeed with reasonable size + Object result; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + result = ois.readObject(); + } + + // Assert: Object successfully deserialized + assertThat(result).isInstanceOf(LargeObject.class); + } + + /** + * TEST 8: Combined real-world security configuration + * + * Demonstrates production-ready filter combining all protections: + * - Whitelist of safe packages + * - Blacklist of dangerous packages + * - Resource limits for DoS prevention + */ + @Test + public void appliesComprehensiveSecurityConfiguration() throws Exception { + // Arrange: Production-grade filter configuration (typical web.xml setting) + // Use specific class names instead of package wildcards for tighter control + String filterPattern = + "java.lang.*;java.util.*;java.time.*;javax.servlet.**;" + // JDK classes + "org.apache.geode.modules.util.DeserializationSecurityTest$AllowedSessionAttribute;" + // Specific + // allowed + // class + "org.apache.geode.modules.session.**;" + // Session classes + "!org.apache.commons.collections.**;" + // Block gadgets + "!org.springframework.beans.**;" + // Block gadgets + "!com.sun.org.apache.xalan.**;" + // Block gadgets + "!*;" + // Block all others + "maxdepth=50;maxrefs=10000;maxarray=10000;maxbytes=100000"; // Resource limits + + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Test 1: Specifically allowed class succeeds + AllowedSessionAttribute allowed = new AllowedSessionAttribute("session-key", 123); + byte[] allowedSerialized = serialize(allowed); + + Object allowedResult; + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(allowedSerialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + allowedResult = ois.readObject(); + } + assertThat(allowedResult).isInstanceOf(AllowedSessionAttribute.class); + + // Test 2: Non-whitelisted class is blocked (even in same package) + UnauthorizedClass unauthorized = new UnauthorizedClass("attack-payload"); + byte[] unauthorizedSerialized = serialize(unauthorized); + + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(unauthorizedSerialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + + // Test 3: Resource limits are configured + assertThat(filterPattern).contains("maxdepth=50"); + assertThat(filterPattern).contains("maxrefs=10000"); + assertThat(filterPattern).contains("maxarray=10000"); + } + + /** + * TEST 9: Standard JDK collections are allowed + * + * Common session attributes (HashMap, ArrayList, etc.) should work + */ + @Test + public void allowsStandardJDKCollections() throws Exception { + // Arrange: Standard whitelist + String filterPattern = "java.lang.*;java.util.*;!*;maxdepth=50"; + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(filterPattern); + + // Test various standard collections + HashMap map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + + ArrayList list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + + HashSet set = new HashSet<>(); + set.add("item1"); + set.add("item2"); + + // Act & Assert: All should deserialize successfully + Object mapResult = deserializeWithFilter(map, filter); + assertThat(mapResult).isInstanceOf(HashMap.class); + assertThat((HashMap) mapResult).hasSize(2); + + Object listResult = deserializeWithFilter(list, filter); + assertThat(listResult).isInstanceOf(ArrayList.class); + assertThat((ArrayList) listResult).hasSize(3); + + Object setResult = deserializeWithFilter(set, filter); + assertThat(setResult).isInstanceOf(HashSet.class); + assertThat((HashSet) setResult).hasSize(2); + } + + // ==================== Helper Methods ==================== + + private byte[] serialize(Object obj) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(obj); + } + return baos.toByteArray(); + } + + private Object deserializeWithFilter(Object obj, ObjectInputFilter filter) throws Exception { + byte[] serialized = serialize(obj); + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + return ois.readObject(); + } + } + + private DeepObject createDeeplyNestedObject(int depth) { + if (depth <= 0) { + return null; + } + return new DeepObject(createDeeplyNestedObject(depth - 1)); + } + + private ReferenceContainer createManyReferences(int count) { + LinkedList list = new LinkedList<>(); + for (int i = 0; i < count; i++) { + list.add("ref-" + i); + } + return new ReferenceContainer(list); + } + + // ==================== Test Classes ==================== + + /** + * Simulates a gadget chain class (like InvokerTransformer) + */ + static class GadgetSimulator implements Serializable { + private static final long serialVersionUID = 1L; + private final String payload; + + GadgetSimulator(String payload) { + this.payload = payload; + } + } + + /** + * Represents an unauthorized class not in whitelist + */ + static class UnauthorizedClass implements Serializable { + private static final long serialVersionUID = 1L; + private final String data; + + UnauthorizedClass(String data) { + this.data = data; + } + } + + /** + * Represents a legitimate session attribute in whitelisted package + */ + static class AllowedSessionAttribute implements Serializable { + private static final long serialVersionUID = 1L; + private final String name; + private final int value; + + AllowedSessionAttribute(String name, int value) { + this.name = name; + this.value = value; + } + + String getName() { + return name; + } + + int getValue() { + return value; + } + } + + /** + * Deeply nested object for depth testing + */ + static class DeepObject implements Serializable { + private static final long serialVersionUID = 1L; + private final DeepObject nested; + + DeepObject(DeepObject nested) { + this.nested = nested; + } + } + + /** + * Container with large array for array size testing + */ + static class ArrayContainer implements Serializable { + private static final long serialVersionUID = 1L; + private final byte[] data; + + ArrayContainer(byte[] data) { + this.data = data; + } + } + + /** + * Container with many references for reference count testing + */ + static class ReferenceContainer implements Serializable { + private static final long serialVersionUID = 1L; + private final LinkedList references; + + ReferenceContainer(LinkedList references) { + this.references = references; + } + } + + /** + * Large object for byte size testing + */ + static class LargeObject implements Serializable { + private static final long serialVersionUID = 1L; + private final byte[] data; + + LargeObject(byte[] data) { + this.data = data; + } + } +} diff --git a/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/GadgetChainSecurityTest.java b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/GadgetChainSecurityTest.java new file mode 100644 index 000000000000..cfc4b4ddeefd --- /dev/null +++ b/extensions/geode-modules/src/test/java/org/apache/geode/modules/util/GadgetChainSecurityTest.java @@ -0,0 +1,621 @@ +/* + * 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.geode.modules.util; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InvalidClassException; +import java.io.ObjectInputFilter; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +import org.junit.Test; + +/** + * Security tests proving that web.xml configuration blocks 26 specific gadget classes + * and 10 dangerous package patterns used in deserialization attacks. + * + * These tests demonstrate protection against real-world exploit chains including: + * - Apache Commons Collections gadgets (InvokerTransformer, ChainedTransformer) + * - Spring Framework exploits (ObjectFactory, AutowireCapableBeanFactory) + * - Java RMI attacks (UnicastRemoteObject, RemoteObjectInvocationHandler) + * - Template injection (TemplatesImpl, ScriptEngine) + * - Groovy exploits (MethodClosure, ConvertedClosure) + * - JNDI injection vectors + * - JMX exploitation classes + * + * Web.xml configuration tested: + * + * serializable-object-filter + * + * java.lang.*;java.util.*; + * !org.apache.commons.collections.functors.*; + * !org.apache.commons.collections4.functors.*; + * !org.springframework.beans.factory.*; + * !java.rmi.*; + * !javax.management.*; + * !com.sun.org.apache.xalan.internal.xsltc.trax.*; + * !org.codehaus.groovy.runtime.*; + * !javax.naming.*; + * !javax.script.*; + * !*; + * + * + */ +public class GadgetChainSecurityTest { + + /** + * Production-grade security filter that blocks all known gadget chains + */ + private static final String COMPREHENSIVE_SECURITY_FILTER = + "java.lang.*;java.util.*;java.time.*;java.math.*;" + + // Block Apache Commons Collections gadgets + "!org.apache.commons.collections.functors.*;" + + "!org.apache.commons.collections.keyvalue.*;" + + "!org.apache.commons.collections.map.*;" + + "!org.apache.commons.collections4.functors.*;" + + "!org.apache.commons.collections4.comparators.*;" + + // Block Spring Framework exploits + "!org.springframework.beans.factory.*;" + + "!org.springframework.context.support.*;" + + "!org.springframework.core.serializer.*;" + + // Block Java RMI attacks + "!java.rmi.*;" + + "!sun.rmi.*;" + + // Block JMX exploitation + "!javax.management.*;" + + "!com.sun.jmx.*;" + + // Block XSLT template injection + "!com.sun.org.apache.xalan.internal.xsltc.trax.*;" + + "!com.sun.org.apache.xalan.internal.xsltc.runtime.*;" + + // Block Groovy exploits + "!org.codehaus.groovy.runtime.*;" + + "!groovy.lang.*;" + + // Block JNDI injection + "!javax.naming.*;" + + "!com.sun.jndi.*;" + + // Block scripting engines + "!javax.script.*;" + + // Block C3P0 JNDI exploits + "!com.mchange.v2.c3p0.*;" + + // Default deny + "!*;" + + // Resource limits + "maxdepth=50;maxrefs=10000;maxarray=10000;maxbytes=100000"; + + // ==================== APACHE COMMONS COLLECTIONS GADGETS ==================== + + /** + * TEST 1: Block InvokerTransformer (most common gadget) + * + * InvokerTransformer allows arbitrary method invocation via reflection. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksInvokerTransformer() { + String className = "org.apache.commons.collections.functors.InvokerTransformer"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 2: Block ChainedTransformer + * + * Chains multiple transformers together to build exploit chains. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksChainedTransformer() { + String className = "org.apache.commons.collections.functors.ChainedTransformer"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 3: Block ConstantTransformer + * + * Returns constant value, used as first step in gadget chains. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksConstantTransformer() { + String className = "org.apache.commons.collections.functors.ConstantTransformer"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 4: Block InstantiateTransformer + * + * Instantiates arbitrary classes with arbitrary constructors. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksInstantiateTransformer() { + String className = "org.apache.commons.collections.functors.InstantiateTransformer"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 5: Block Commons Collections 4.x gadgets + * + * Same gadgets but in newer package structure. + */ + @Test + public void blocksCommonsCollections4Gadgets() { + String className = "org.apache.commons.collections4.functors.InvokerTransformer"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 6: Block TransformedMap + * + * Map that transforms entries, used as trigger point. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksTransformedMap() { + String className = "org.apache.commons.collections.map.TransformedMap"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 7: Block LazyMap + * + * Map that lazily creates values, used as trigger point. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksLazyMap() { + String className = "org.apache.commons.collections.map.LazyMap"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 8: Block TiedMapEntry + * + * Used to trigger gadget chains during deserialization. + * Used in: Apache Commons Collections exploit chain + */ + @Test + public void blocksTiedMapEntry() { + String className = "org.apache.commons.collections.keyvalue.TiedMapEntry"; + assertGadgetClassBlocked(className); + } + + // ==================== SPRING FRAMEWORK EXPLOITS ==================== + + /** + * TEST 9: Block ObjectFactory + * + * Factory that can instantiate arbitrary objects. + * Used in: Spring Framework exploit chain + */ + @Test + public void blocksSpringObjectFactory() { + String className = "org.springframework.beans.factory.ObjectFactory"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 10: Block AutowireCapableBeanFactory + * + * Spring factory that can autowire beans with arbitrary dependencies. + * Used in: Spring Framework exploit chain + */ + @Test + public void blocksAutowireCapableBeanFactory() { + String className = "org.springframework.beans.factory.config.AutowireCapableBeanFactory"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 11: Block DefaultListableBeanFactory + * + * Spring bean factory implementation that can be exploited. + * Used in: Spring Framework exploit chain + */ + @Test + public void blocksDefaultListableBeanFactory() { + String className = "org.springframework.beans.factory.support.DefaultListableBeanFactory"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 12: Block FileSystemXmlApplicationContext + * + * Spring context that loads beans from filesystem XML. + * Used in: Spring Framework exploit chain + */ + @Test + public void blocksFileSystemXmlApplicationContext() { + String className = "org.springframework.context.support.FileSystemXmlApplicationContext"; + assertGadgetClassBlocked(className); + } + + // ==================== XSLT TEMPLATE INJECTION ==================== + + /** + * TEST 13: Block TemplatesImpl + * + * XSLT template that can load arbitrary bytecode. + * Used in: Template injection attacks + */ + @Test + public void blocksTemplatesImpl() { + String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 14: Block TransformerImpl + * + * XSLT transformer that can execute arbitrary code. + * Used in: Template injection attacks + */ + @Test + public void blocksTransformerImpl() { + String className = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 15: Block AbstractTranslet + * + * Base class for XSLT templates that can execute code. + * Used in: Template injection attacks + */ + @Test + public void blocksAbstractTranslet() { + String className = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"; + assertGadgetClassBlocked(className); + } + + // ==================== GROOVY EXPLOITS ==================== + + /** + * TEST 16: Block MethodClosure + * + * Groovy closure that wraps method invocation. + * Used in: Groovy exploit chain + */ + @Test + public void blocksGroovyMethodClosure() { + String className = "org.codehaus.groovy.runtime.MethodClosure"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 17: Block ConvertedClosure + * + * Groovy closure that can invoke arbitrary methods. + * Used in: Groovy exploit chain + */ + @Test + public void blocksGroovyConvertedClosure() { + String className = "org.codehaus.groovy.runtime.ConvertedClosure"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 18: Block GroovyShell + * + * Groovy shell that can execute arbitrary Groovy code. + * Used in: Groovy exploit chain + */ + @Test + public void blocksGroovyShell() { + String className = "groovy.lang.GroovyShell"; + assertGadgetClassBlocked(className); + } + + // ==================== JAVA RMI ATTACKS ==================== + + /** + * TEST 19: Block UnicastRemoteObject + * + * RMI remote object that can trigger network callbacks. + * Used in: RMI deserialization attacks + */ + @Test + public void blocksUnicastRemoteObject() { + String className = "java.rmi.server.UnicastRemoteObject"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 20: Block RemoteObjectInvocationHandler + * + * RMI invocation handler used in proxy-based attacks. + * Used in: RMI deserialization attacks + */ + @Test + public void blocksRemoteObjectInvocationHandler() { + String className = "java.rmi.server.RemoteObjectInvocationHandler"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 21: Block RMIConnectionImpl + * + * JMX RMI connection implementation. + * Used in: JMX exploitation via RMI + */ + @Test + public void blocksRMIConnectionImpl() { + String className = "javax.management.remote.rmi.RMIConnectionImpl"; + assertGadgetClassBlocked(className); + } + + // ==================== JMX EXPLOITATION ==================== + + /** + * TEST 22: Block BadAttributeValueExpException + * + * JMX exception that triggers toString() during deserialization. + * Used in: JMX exploit chain + */ + @Test + public void blocksBadAttributeValueExpException() { + String className = "javax.management.BadAttributeValueExpException"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 23: Block MBeanServerInvocationHandler + * + * JMX invocation handler for MBean proxies. + * Used in: JMX exploit chain + */ + @Test + public void blocksMBeanServerInvocationHandler() { + String className = "javax.management.MBeanServerInvocationHandler"; + assertGadgetClassBlocked(className); + } + + // ==================== JNDI INJECTION ==================== + + /** + * TEST 24: Block Reference + * + * JNDI reference that can load arbitrary classes. + * Used in: JNDI injection attacks + */ + @Test + public void blocksJndiReference() { + String className = "javax.naming.Reference"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 25: Block InitialContext + * + * JNDI initial context for naming lookups. + * Used in: JNDI injection attacks + */ + @Test + public void blocksJndiInitialContext() { + String className = "javax.naming.InitialContext"; + assertGadgetClassBlocked(className); + } + + /** + * TEST 26: Block C3P0 JndiRefForwardingDataSource + * + * C3P0 datasource that performs JNDI lookups. + * Used in: C3P0 JNDI injection attacks + */ + @Test + public void blocksC3P0JndiDataSource() { + String className = "com.mchange.v2.c3p0.JndiRefForwardingDataSource"; + assertGadgetClassBlocked(className); + } + + // ==================== DANGEROUS PACKAGE PATTERNS ==================== + + /** + * TEST 27: Block entire Commons Collections functors package + */ + @Test + public void blocksCommonsCollectionsFunctorsPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + assertThat(filter).isNotNull(); + + // Pattern !org.apache.commons.collections.functors.* blocks all classes in package + SimulatedGadget gadget = new SimulatedGadget( + "org.apache.commons.collections.functors.AnyGadgetClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 28: Block entire Spring beans factory package + */ + @Test + public void blocksSpringBeansFactoryPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !org.springframework.beans.factory.* blocks all classes + SimulatedGadget gadget = new SimulatedGadget( + "org.springframework.beans.factory.AnySpringClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 29: Block entire Java RMI package + */ + @Test + public void blocksJavaRmiPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !java.rmi.* blocks all RMI classes + SimulatedGadget gadget = new SimulatedGadget("java.rmi.AnyRmiClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 30: Block entire JMX package + */ + @Test + public void blocksJavaxManagementPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !javax.management.* blocks all JMX classes + SimulatedGadget gadget = new SimulatedGadget("javax.management.AnyJmxClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 31: Block entire Xalan XSLTC package + */ + @Test + public void blocksXalanXsltcPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern blocks Xalan template injection + SimulatedGadget gadget = new SimulatedGadget( + "com.sun.org.apache.xalan.internal.xsltc.trax.AnyXalanClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 32: Block entire Groovy runtime package + */ + @Test + public void blocksGroovyRuntimePackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !org.codehaus.groovy.runtime.* blocks all Groovy exploits + SimulatedGadget gadget = new SimulatedGadget( + "org.codehaus.groovy.runtime.AnyGroovyClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 33: Block entire JNDI naming package + */ + @Test + public void blocksJavaxNamingPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !javax.naming.* blocks JNDI injection + SimulatedGadget gadget = new SimulatedGadget("javax.naming.AnyJndiClass"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 34: Block entire scripting engine package + */ + @Test + public void blocksJavaxScriptPackage() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !javax.script.* blocks script engine exploits + SimulatedGadget gadget = new SimulatedGadget("javax.script.ScriptEngine"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 35: Block C3P0 package + */ + @Test + public void blocksC3P0Package() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + // Pattern !com.mchange.v2.c3p0.* blocks C3P0 exploits + SimulatedGadget gadget = new SimulatedGadget("com.mchange.v2.c3p0.AnyC3P0Class"); + assertPatternBlocks(gadget, filter); + } + + /** + * TEST 36: Comprehensive protection test - blocks all gadgets simultaneously + */ + @Test + public void comprehensiveGadgetProtection() { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + + String[] gadgetClasses = { + "org.apache.commons.collections.functors.InvokerTransformer", + "org.springframework.beans.factory.ObjectFactory", + "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl", + "org.codehaus.groovy.runtime.MethodClosure", + "java.rmi.server.UnicastRemoteObject", + "javax.management.BadAttributeValueExpException", + "javax.naming.Reference", + "com.mchange.v2.c3p0.JndiRefForwardingDataSource" + }; + + for (String gadgetClass : gadgetClasses) { + SimulatedGadget gadget = new SimulatedGadget(gadgetClass); + assertPatternBlocks(gadget, filter); + } + } + + // ==================== HELPER METHODS ==================== + + /** + * Assert that a specific gadget class name is blocked by the filter + */ + private void assertGadgetClassBlocked(String className) { + ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(COMPREHENSIVE_SECURITY_FILTER); + assertThat(filter).isNotNull(); + + SimulatedGadget gadget = new SimulatedGadget(className); + assertPatternBlocks(gadget, filter); + } + + /** + * Assert that a pattern blocks the simulated gadget + */ + private void assertPatternBlocks(SimulatedGadget gadget, ObjectInputFilter filter) { + try { + byte[] serialized = serialize(gadget); + assertThatThrownBy(() -> { + try (ClassLoaderObjectInputStream ois = new ClassLoaderObjectInputStream( + new ByteArrayInputStream(serialized), + Thread.currentThread().getContextClassLoader(), + filter)) { + ois.readObject(); + } + }).isInstanceOf(InvalidClassException.class) + .hasMessageContaining("filter status: REJECTED"); + } catch (Exception e) { + throw new RuntimeException("Failed to test gadget: " + gadget.simulatedClassName, e); + } + } + + private byte[] serialize(Object obj) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(obj); + } + return baos.toByteArray(); + } + + // ==================== TEST CLASSES ==================== + + /** + * Simulates a gadget class for testing. + * The actual gadget classes don't need to be on classpath - + * the filter blocks based on class name patterns. + */ + static class SimulatedGadget implements Serializable { + private static final long serialVersionUID = 1L; + private final String simulatedClassName; + + SimulatedGadget(String simulatedClassName) { + this.simulatedClassName = simulatedClassName; + } + } +} diff --git a/extensions/geode-modules/src/test/resources/expected-pom.xml b/extensions/geode-modules/src/test/resources/expected-pom.xml index c97e5872d641..8228c5485692 100644 --- a/extensions/geode-modules/src/test/resources/expected-pom.xml +++ b/extensions/geode-modules/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/extensions/session-testing-war/src/main/webapp/WEB-INF/web.xml b/extensions/session-testing-war/src/main/webapp/WEB-INF/web.xml index 42afa864bd39..66acb8248fc3 100644 --- a/extensions/session-testing-war/src/main/webapp/WEB-INF/web.xml +++ b/extensions/session-testing-war/src/main/webapp/WEB-INF/web.xml @@ -27,6 +27,12 @@ limitations under the License. Test war file for geode session management + + + serializable-object-filter + java.lang.*;java.util.*;java.time.*;javax.servlet.**;org.apache.geode.modules.session.**;!org.apache.commons.collections.**;!org.springframework.beans.**;!*;maxdepth=50;maxrefs=10000;maxarray=10000;maxbytes=100000 + + Some test servlet diff --git a/geode-assembly/build.gradle b/geode-assembly/build.gradle index 0102ccea40c8..95ee82ece5b9 100755 --- a/geode-assembly/build.gradle +++ b/geode-assembly/build.gradle @@ -438,6 +438,9 @@ distributions { contents { from rootProject.tasks.writeBuildInfo from (rootDir) { + // Provided by the writeBuildInfo task above; the root copy is present only when + // building from an unpacked source distribution. + exclude '.buildinfo' exclude 'KEYS' exclude '**/gradlew' exclude '**/gradlew.bat' diff --git a/geode-assembly/src/acceptanceTest/resources/gradle-test-projects/management/build.gradle b/geode-assembly/src/acceptanceTest/resources/gradle-test-projects/management/build.gradle index 10af76ab0a91..7c0ca6a2a88f 100644 --- a/geode-assembly/src/acceptanceTest/resources/gradle-test-projects/management/build.gradle +++ b/geode-assembly/src/acceptanceTest/resources/gradle-test-projects/management/build.gradle @@ -25,7 +25,7 @@ repositories { dependencies { implementation("${project.group}:geode-core:${project.version}") - runtimeOnly('org.apache.logging.log4j:log4j-slf4j-impl:2.17.2') + runtimeOnly('org.apache.logging.log4j:log4j-slf4j-impl:2.25.5') } application { diff --git a/geode-assembly/src/distributedTest/java/org/apache/geode/session/tests/GenericAppServerInstall.java b/geode-assembly/src/distributedTest/java/org/apache/geode/session/tests/GenericAppServerInstall.java index 4e5e13ff5dea..59edba382180 100644 --- a/geode-assembly/src/distributedTest/java/org/apache/geode/session/tests/GenericAppServerInstall.java +++ b/geode-assembly/src/distributedTest/java/org/apache/geode/session/tests/GenericAppServerInstall.java @@ -34,7 +34,7 @@ * specific code outside of the {@link GenericAppServerVersion}. */ public class GenericAppServerInstall extends ContainerInstall { - private static final String JETTY_VERSION = "12.0.27"; + private static final String JETTY_VERSION = "12.0.37"; /** * Get the version number, download URL, and container name of a generic app server using diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java index 2a9bc83f35e1..7a0a97fe7242 100644 --- a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java +++ b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityIntegrationTest.java @@ -107,6 +107,8 @@ public void testPostQuery() { assertResponse(restClient.doPost("/queries?id=0&q=", "user", "user", "")) .hasStatusCode(403); assertResponse(restClient.doPost("/queries?id=0&q=", "dataRead", "dataRead", "")) + .hasStatusCode(403); + assertResponse(restClient.doPost("/queries?id=0&q=", "dataWrite", "dataWrite", "")) .hasStatusCode(500); } @@ -127,6 +129,8 @@ public void testPutQuery() { assertResponse(restClient.doPut("/queries/id", "user", "user", "{\"id\" : \"foo\"}")) .hasStatusCode(403); assertResponse(restClient.doPut("/queries/id", "dataRead", "dataRead", "{\"id\" : \"foo\"}")) + .hasStatusCode(403); + assertResponse(restClient.doPut("/queries/id", "dataWrite", "dataWrite", "{\"id\" : \"foo\"}")) .hasStatusCode(404); } diff --git a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java index a30855ac3631..cd5a3fd73004 100644 --- a/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java +++ b/geode-assembly/src/integrationTest/java/org/apache/geode/rest/internal/web/RestSecurityPostProcessorTest.java @@ -158,7 +158,7 @@ public void namedQuery() throws Exception { // Install the named query assertResponse( restClient.doPost("/queries?id=selectCustomer&q=" + URLEncoder.encode(namedQuery, "UTF-8"), - "dataReader", "1234567", "")) + "dataUser", "1234567", "")) .hasStatusCode(201); // Verify the query has been installed diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt index bcfefacec471..c2c6ae29523f 100644 --- a/geode-assembly/src/integrationTest/resources/assembly_content.txt +++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt @@ -920,10 +920,10 @@ lib/ST4-4.3.3.jar lib/angus-activation-2.0.0.jar lib/antlr-2.7.7.jar lib/antlr-runtime-3.5.2.jar -lib/asm-9.8.jar -lib/asm-commons-9.8.jar -lib/asm-tree-9.8.jar -lib/byte-buddy-1.14.9.jar +lib/asm-9.10.1.jar +lib/asm-commons-9.10.1.jar +lib/asm-tree-9.10.1.jar +lib/bcprov-jdk18on-1.85.jar lib/classgraph-4.8.147.jar lib/classmate-1.5.1.jar lib/commons-beanutils-1.11.0.jar @@ -959,18 +959,17 @@ lib/geode-tcp-server-0.0.0.jar lib/geode-unsafe-0.0.0.jar lib/geode-wan-0.0.0.jar lib/gfsh-dependencies.jar -lib/hibernate-validator-8.0.1.Final.jar -lib/httpclient5-5.4.4.jar -lib/httpcore5-5.3.4.jar -lib/httpcore5-h2-5.3.4.jar -lib/istack-commons-runtime-4.0.1.jar +lib/hibernate-validator-8.0.2.Final.jar +lib/httpclient5-5.6.4.jar +lib/httpcore5-5.4.3.jar +lib/httpcore5-h2-5.4.3.jar lib/istack-commons-runtime-4.1.1.jar -lib/jackson-annotations-2.17.0.jar -lib/jackson-core-2.17.0.jar -lib/jackson-databind-2.17.0.jar -lib/jackson-dataformat-yaml-2.17.0.jar -lib/jackson-datatype-joda-2.17.0.jar -lib/jackson-datatype-jsr310-2.17.0.jar +lib/jackson-annotations-2.21.jar +lib/jackson-core-2.21.6.jar +lib/jackson-databind-2.21.6.jar +lib/jackson-dataformat-yaml-2.21.6.jar +lib/jackson-datatype-joda-2.21.6.jar +lib/jackson-datatype-jsr310-2.21.6.jar lib/jakarta.activation-api-2.1.3.jar lib/jakarta.annotation-api-2.1.1.jar lib/jakarta.el-api-5.0.0.jar @@ -987,20 +986,20 @@ lib/jakarta.xml.bind-api-4.0.2.jar lib/jaxb-core-4.0.2.jar lib/jaxb-runtime-4.0.2.jar lib/jboss-logging-3.4.3.Final.jar -lib/jetty-ee-12.0.27.jar -lib/jetty-ee10-annotations-12.0.27.jar -lib/jetty-ee10-plus-12.0.27.jar -lib/jetty-ee10-servlet-12.0.27.jar -lib/jetty-ee10-webapp-12.0.27.jar -lib/jetty-http-12.0.27.jar -lib/jetty-io-12.0.27.jar -lib/jetty-jndi-12.0.27.jar -lib/jetty-plus-12.0.27.jar -lib/jetty-security-12.0.27.jar -lib/jetty-server-12.0.27.jar -lib/jetty-session-12.0.27.jar -lib/jetty-util-12.0.27.jar -lib/jetty-xml-12.0.27.jar +lib/jetty-ee-12.0.37.jar +lib/jetty-ee10-annotations-12.0.37.jar +lib/jetty-ee10-plus-12.0.37.jar +lib/jetty-ee10-servlet-12.0.37.jar +lib/jetty-ee10-webapp-12.0.37.jar +lib/jetty-http-12.0.37.jar +lib/jetty-io-12.0.37.jar +lib/jetty-jndi-12.0.37.jar +lib/jetty-plus-12.0.37.jar +lib/jetty-security-12.0.37.jar +lib/jetty-server-12.0.37.jar +lib/jetty-session-12.0.37.jar +lib/jetty-util-12.0.37.jar +lib/jetty-xml-12.0.37.jar lib/jgroups-3.6.20.Final.jar lib/jline-builtins-3.26.3.jar lib/jline-console-3.26.3.jar @@ -1008,66 +1007,69 @@ lib/jline-native-3.26.3.jar lib/jline-reader-3.26.3.jar lib/jline-style-3.26.3.jar lib/jline-terminal-3.26.3.jar +lib/jline-terminal-ffm-3.26.3.jar +lib/jline-terminal-jni-3.26.3.jar lib/jna-5.11.0.jar lib/jna-platform-5.11.0.jar lib/joda-time-2.12.7.jar lib/jopt-simple-5.0.4.jar -lib/jul-to-slf4j-2.0.16.jar -lib/log4j-api-2.17.2.jar -lib/log4j-core-2.17.2.jar -lib/log4j-jcl-2.17.2.jar -lib/log4j-jul-2.17.2.jar -lib/log4j-slf4j-impl-2.17.2.jar -lib/logback-classic-1.5.11.jar -lib/logback-core-1.5.11.jar +lib/jspecify-1.0.1.jar +lib/jul-to-slf4j-2.0.17.jar +lib/log4j-api-2.25.5.jar +lib/log4j-core-2.25.5.jar +lib/log4j-jcl-2.25.5.jar +lib/log4j-jul-2.25.5.jar +lib/log4j-slf4j-impl-2.25.5.jar lib/lucene-analysis-common-9.12.3.jar lib/lucene-analysis-phonetic-9.12.3.jar lib/lucene-core-9.12.3.jar lib/lucene-queries-9.12.3.jar lib/lucene-queryparser-9.12.3.jar -lib/micrometer-commons-1.14.0.jar -lib/micrometer-core-1.14.0.jar -lib/micrometer-observation-1.14.0.jar +lib/micrometer-commons-1.16.7.jar +lib/micrometer-core-1.16.7.jar +lib/micrometer-observation-1.16.7.jar lib/mx4j-3.0.2.jar lib/mx4j-remote-3.0.2.jar lib/mx4j-tools-3.0.1.jar lib/ra.jar lib/reactive-streams-1.0.4.jar -lib/reactor-core-3.6.10.jar +lib/reactor-core-3.8.7.jar lib/rmiio-2.1.2.jar -lib/shiro-cache-1.13.0.jar -lib/shiro-config-core-1.13.0.jar -lib/shiro-config-ogdl-1.13.0.jar -lib/shiro-core-1.13.0.jar -lib/shiro-crypto-cipher-1.13.0.jar -lib/shiro-crypto-core-1.13.0.jar -lib/shiro-crypto-hash-1.13.0.jar -lib/shiro-event-1.13.0.jar -lib/shiro-lang-1.13.0.jar -lib/slf4j-api-2.0.17.jar -lib/snakeyaml-2.2.jar +lib/shiro-cache-3.0.0.jar +lib/shiro-config-core-3.0.0.jar +lib/shiro-config-ogdl-3.0.0.jar +lib/shiro-core-3.0.0.jar +lib/shiro-crypto-cipher-3.0.0.jar +lib/shiro-crypto-core-3.0.0.jar +lib/shiro-crypto-hash-3.0.0.jar +lib/shiro-event-3.0.0.jar +lib/shiro-hashes-argon2-3.0.0.jar +lib/shiro-hashes-bcrypt-3.0.0.jar +lib/shiro-lang-3.0.0.jar +lib/slf4j-api-2.0.18.jar +lib/snakeyaml-2.5.jar lib/snappy-0.5.jar -lib/spring-aop-6.1.14.jar -lib/spring-beans-6.1.14.jar -lib/spring-boot-3.3.5.jar -lib/spring-boot-autoconfigure-3.3.5.jar -lib/spring-boot-starter-3.3.5.jar -lib/spring-boot-starter-logging-3.3.5.jar -lib/spring-boot-starter-validation-3.3.5.jar -lib/spring-context-6.1.14.jar -lib/spring-core-6.1.14.jar -lib/spring-expression-6.1.14.jar -lib/spring-jcl-6.1.14.jar -lib/spring-messaging-6.1.14.jar +lib/spring-aop-6.1.21.jar +lib/spring-beans-6.1.21.jar +lib/spring-boot-3.3.13.jar +lib/spring-boot-autoconfigure-3.3.13.jar +lib/spring-boot-starter-3.3.13.jar +lib/spring-boot-starter-logging-3.3.13.jar +lib/spring-boot-starter-validation-3.3.13.jar +lib/spring-context-6.1.21.jar +lib/spring-core-6.1.21.jar +lib/spring-expression-6.1.21.jar +lib/spring-jcl-6.1.21.jar +lib/spring-messaging-6.1.21.jar lib/spring-shell-autoconfigure-3.3.3.jar lib/spring-shell-core-3.3.3.jar lib/spring-shell-standard-3.3.3.jar lib/spring-shell-standard-commands-3.3.3.jar lib/spring-shell-starter-3.3.3.jar lib/spring-shell-table-3.3.3.jar -lib/spring-web-6.1.14.jar +lib/spring-web-6.1.21.jar lib/swagger-annotations-2.2.22.jar -lib/tomcat-embed-el-10.1.31.jar +lib/tomcat-embed-el-10.1.42.jar lib/txw2-4.0.2.jar tools/Extensions/geode-web-0.0.0.war tools/Extensions/geode-web-api-0.0.0.war diff --git a/geode-assembly/src/integrationTest/resources/expected_jars.txt b/geode-assembly/src/integrationTest/resources/expected_jars.txt index f2023163ef6a..37c04b61a0b8 100644 --- a/geode-assembly/src/integrationTest/resources/expected_jars.txt +++ b/geode-assembly/src/integrationTest/resources/expected_jars.txt @@ -9,7 +9,7 @@ antlr-runtime asm asm-commons asm-tree -byte-buddy +bcprov-jdk18on classgraph classmate commons-beanutils @@ -71,12 +71,15 @@ jline-native jline-reader jline-style jline-terminal +jline-terminal-ffm +jline-terminal-jni jna jna-platform joda-time jopt-simple json-path json-smart +jspecify jul-to-slf4j lang-tag log4j-api @@ -84,8 +87,6 @@ log4j-core log4j-jcl log4j-jul log4j-slf4j-impl -logback-classic -logback-core lucene-analysis-common lucene-analysis-phonetic lucene-core @@ -111,6 +112,8 @@ shiro-crypto-cipher shiro-crypto-core shiro-crypto-hash shiro-event +shiro-hashes-argon +shiro-hashes-bcrypt shiro-lang slf4j-api snakeyaml diff --git a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt index e2dd99e34361..17273bef851c 100644 --- a/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt +++ b/geode-assembly/src/integrationTest/resources/gfsh_dependency_classpath.txt @@ -18,40 +18,41 @@ geode-common-0.0.0.jar geode-unsafe-0.0.0.jar geode-deployment-legacy-0.0.0.jar spring-shell-starter-3.3.3.jar -spring-web-6.1.14.jar +spring-web-6.1.21.jar commons-lang3-3.18.0.jar rmiio-2.1.2.jar -jackson-datatype-joda-2.17.0.jar -jackson-annotations-2.17.0.jar -jackson-dataformat-yaml-2.17.0.jar -jackson-core-2.17.0.jar -jackson-datatype-jsr310-2.17.0.jar -jackson-databind-2.17.0.jar +jackson-datatype-jsr310-2.21.6.jar +jackson-datatype-joda-2.21.6.jar +jackson-core-2.21.6.jar +jackson-dataformat-yaml-2.21.6.jar +jackson-databind-2.21.6.jar swagger-annotations-2.2.22.jar jaxb-runtime-4.0.2.jar jaxb-core-4.0.2.jar jakarta.xml.bind-api-4.0.2.jar jopt-simple-5.0.4.jar -log4j-slf4j-impl-2.17.2.jar -log4j-core-2.17.2.jar -log4j-jcl-2.17.2.jar -log4j-jul-2.17.2.jar -log4j-api-2.17.2.jar -spring-aop-6.1.14.jar +log4j-slf4j-impl-2.25.5.jar +log4j-core-2.25.5.jar +log4j-jcl-2.25.5.jar +log4j-jul-2.25.5.jar +log4j-api-2.25.5.jar +spring-aop-6.1.21.jar spring-shell-autoconfigure-3.3.3.jar spring-shell-standard-commands-3.3.3.jar spring-shell-standard-3.3.3.jar spring-shell-core-3.3.3.jar spring-shell-table-3.3.3.jar -spring-boot-starter-validation-3.3.5.jar -spring-boot-starter-3.3.5.jar -spring-messaging-6.1.14.jar -spring-boot-autoconfigure-3.3.5.jar -spring-boot-3.3.5.jar -spring-context-6.1.14.jar -spring-beans-6.1.14.jar -spring-expression-6.1.14.jar -spring-core-6.1.14.jar +spring-boot-starter-validation-3.3.13.jar +spring-boot-starter-3.3.13.jar +spring-messaging-6.1.21.jar +spring-boot-autoconfigure-3.3.13.jar +spring-boot-3.3.13.jar +spring-context-6.1.21.jar +spring-beans-6.1.21.jar +spring-expression-6.1.21.jar +spring-core-6.1.21.jar +jline-terminal-jni-3.26.3.jar +jline-terminal-ffm-3.26.3.jar angus-activation-2.0.0.jar jakarta.activation-api-2.1.3.jar lucene-analysis-phonetic-9.12.3.jar @@ -59,15 +60,15 @@ lucene-analysis-common-9.12.3.jar lucene-queryparser-9.12.3.jar lucene-queries-9.12.3.jar lucene-core-9.12.3.jar -httpclient5-5.4.4.jar -httpcore5-h2-5.3.4.jar -httpcore5-5.3.4.jar +httpclient5-5.6.4.jar +httpcore5-h2-5.4.3.jar +httpcore5-5.4.3.jar HikariCP-4.0.3.jar antlr-2.7.7.jar istack-commons-runtime-4.1.1.jar commons-validator-1.7.jar -shiro-core-1.13.0.jar -shiro-config-ogdl-1.13.0.jar +shiro-core-3.0.0.jar +shiro-config-ogdl-3.0.0.jar commons-beanutils-1.11.0.jar commons-codec-1.15.jar commons-collections-3.2.2.jar @@ -75,71 +76,73 @@ commons-digester-2.1.jar commons-io-2.19.0.jar commons-logging-1.3.5.jar classgraph-4.8.147.jar -micrometer-core-1.14.0.jar +micrometer-core-1.16.7.jar +HdrHistogram-2.2.2.jar fastutil-8.5.8.jar jakarta.resource-api-2.1.0.jar -jetty-ee10-annotations-12.0.27.jar -jetty-ee10-plus-12.0.27.jar +jetty-ee10-annotations-12.0.37.jar +jetty-ee10-plus-12.0.37.jar jakarta.enterprise.cdi-api-4.0.1.jar jakarta.interceptor-api-2.1.0.jar jakarta.annotation-api-2.1.1.jar -jetty-ee10-webapp-12.0.27.jar -jetty-ee10-servlet-12.0.27.jar +jetty-ee10-webapp-12.0.37.jar +jetty-ee10-servlet-12.0.37.jar jakarta.servlet-api-6.0.0.jar jakarta.transaction-api-2.0.1.jar joda-time-2.12.7.jar jna-platform-5.11.0.jar jna-5.11.0.jar -jetty-ee-12.0.27.jar -jetty-session-12.0.27.jar -jetty-plus-12.0.27.jar -jetty-security-12.0.27.jar -jetty-server-12.0.27.jar +shiro-hashes-argon2-3.0.0.jar +shiro-hashes-bcrypt-3.0.0.jar +shiro-crypto-hash-3.0.0.jar +bcprov-jdk18on-1.85.jar +jetty-ee-12.0.37.jar +jetty-session-12.0.37.jar +jetty-plus-12.0.37.jar +jetty-security-12.0.37.jar +jetty-server-12.0.37.jar snappy-0.5.jar jgroups-3.6.20.Final.jar -shiro-cache-1.13.0.jar -shiro-crypto-hash-1.13.0.jar -shiro-crypto-cipher-1.13.0.jar -shiro-config-core-1.13.0.jar -shiro-event-1.13.0.jar -shiro-crypto-core-1.13.0.jar -shiro-lang-1.13.0.jar -jetty-xml-12.0.27.jar -jetty-http-12.0.27.jar -jetty-io-12.0.27.jar -spring-boot-starter-logging-3.3.5.jar -logback-classic-1.5.11.jar -jul-to-slf4j-2.0.16.jar -jetty-jndi-12.0.27.jar -jetty-util-12.0.27.jar -slf4j-api-2.0.17.jar -byte-buddy-1.14.9.jar -micrometer-observation-1.14.0.jar -spring-jcl-6.1.14.jar -micrometer-commons-1.14.0.jar -HdrHistogram-2.2.2.jar -LatencyUtils-2.0.3.jar -reactor-core-3.6.10.jar +shiro-cache-3.0.0.jar +shiro-crypto-cipher-3.0.0.jar +shiro-config-core-3.0.0.jar +shiro-event-3.0.0.jar +shiro-crypto-core-3.0.0.jar +shiro-lang-3.0.0.jar +jetty-xml-12.0.37.jar +jetty-http-12.0.37.jar +jetty-io-12.0.37.jar +spring-boot-starter-logging-3.3.13.jar +jul-to-slf4j-2.0.17.jar +jetty-jndi-12.0.37.jar +jetty-util-12.0.37.jar +slf4j-api-2.0.18.jar +jackson-annotations-2.21.jar jline-console-3.26.3.jar jline-builtins-3.26.3.jar jline-reader-3.26.3.jar jline-style-3.26.3.jar jline-terminal-3.26.3.jar +jline-native-3.26.3.jar +micrometer-observation-1.16.7.jar +spring-jcl-6.1.21.jar +micrometer-commons-1.16.7.jar +jspecify-1.0.1.jar +LatencyUtils-2.0.3.jar +snakeyaml-2.5.jar +reactor-core-3.8.7.jar ST4-4.3.3.jar txw2-4.0.2.jar -snakeyaml-2.2.jar -asm-commons-9.8.jar -asm-tree-9.8.jar -asm-9.8.jar +asm-commons-9.10.1.jar +asm-tree-9.10.1.jar +asm-9.10.1.jar reactive-streams-1.0.4.jar -jline-native-3.26.3.jar antlr-runtime-3.5.2.jar -tomcat-embed-el-10.1.31.jar -hibernate-validator-8.0.1.Final.jar +tomcat-embed-el-10.1.42.jar +hibernate-validator-8.0.2.Final.jar jakarta.enterprise.lang-model-4.0.1.jar jakarta.validation-api-3.0.2.jar jboss-logging-3.4.3.Final.jar classmate-1.5.1.jar -logback-core-1.5.11.jar jakarta.el-api-5.0.0.jar jakarta.inject-api-2.0.1.jar diff --git a/geode-assembly/src/main/dist/LICENSE b/geode-assembly/src/main/dist/LICENSE index 010654b3cb03..56386e284383 100644 --- a/geode-assembly/src/main/dist/LICENSE +++ b/geode-assembly/src/main/dist/LICENSE @@ -217,12 +217,14 @@ The BSD 3-Clause License (http://opensource.org/licenses/BSD-3-Clause) Apache Geode bundles the following files under the BSD 3-Clause License: + - angus-activation v2.0.0 (https://github.com/eclipse-ee4j/angus-activation) - ANSIBuffer (http://jline.sourceforge.net/apidocs/jline/ANSIBuffer.html), Copyright (c) 2002-2007 Marc Prud'hommeaux. - Antlr v2.7.7 (http://www.antlr.org), Copyright (c) 2012 Terrence Parr and Sam Harwell - - ASM v9.1 (https://asm.ow2.io) Copyright (c) 2000-2011 INRIA, France + - ASM v9.8 (https://asm.ow2.io) Copyright (c) 2000-2011 INRIA, France Telecom + - jakarta.activation v2.1.3 (https://github.com/jakartaee/jaf-api) - JLine v2.12 (http://jline.sourceforge.net), Copyright (c) 2002-2006, Marc Prud'hommeaux - jQuery Sparklines v2.0 (http://omnipotent.net/jquery.sparkline/), @@ -259,16 +261,6 @@ POSSIBILITY OF SUCH DAMAGE. The CDDL Version 1.1 (https://javaee.github.io/glassfish/LICENSE) --------------------------------------------------------------------------- -Apache Geode bundles the following files under the Common Development and -Distribution License: - - - javax.activation v1.2.0 - (https://www.oracle.com/technetwork/java/javase/jaf-135115.html) - - javax.mail v1.6.2 (http://www.oracle.com/) - - javax.resource v 1.7.1 (https://glassfish.java.net/) - - javax.servlet v3.1.0 (https://glassfish.java.net/) - - javax.transaction v1.3 (https://glassfish.java.net/) - - jaxb v2.3.2 (https://javaee.github.io/jaxb-v2/) 1. Definitions. @@ -1022,10 +1014,11 @@ The EDL 1.0 License (http://www.eclipse.org/org/documents/edl-v10.php) Apache Geode bundles the following file under the EDL 1.0 License: - - istack-commons-runtime v4.0.1 - - jakarta.activation v1.2.1 - - jakarta.validation v2.0.2 - - jakarta.xml.bind v2.3.2 + - istack-commons-runtime v4.1.1 + - jakarta.xml.bind v4.0.2 + - jaxb-core v4.0.2 + - jaxb-runtime v4.0.2 + - txw2 v4.0.2 Eclipse Distribution License - v 1.0 @@ -1059,6 +1052,24 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +The EPL 2.0 License (https://www.eclipse.org/legal/epl-2.0/) +--------------------------------------------------------------------------- + +Apache Geode bundles the following files under the Eclipse Public License 2.0 +with the Secondary License of GPL-2.0 with Classpath Exception: + + - jakarta.annotation v2.1.1 (https://github.com/jakartaee/common-annotations-api) + - jakarta.el v5.0.0 (https://github.com/jakartaee/expression-language) + - jakarta.interceptor v2.1.0 (https://github.com/jakartaee/interceptors) + - jakarta.mail v2.1.2 (https://github.com/jakartaee/mail-api) + - jakarta.resource v2.1.0 (https://github.com/jakartaee/connectors) + - jakarta.servlet v6.0.0 (https://github.com/jakartaee/servlet) + - jakarta.transaction v2.0.1 (https://github.com/jakartaee/transactions) + +For the full EPL 2.0 license text, see: +https://www.eclipse.org/legal/epl-2.0/ + --------------------------------------------------------------------------- The MIT License (http://opensource.org/licenses/mit-license.html) --------------------------------------------------------------------------- @@ -1097,7 +1108,7 @@ Apache Geode bundles the following files under the MIT License: - Normalize.css v2.1.0 (https://necolas.github.io/normalize.css/), Copyright (c) Nicolas Gallagher and Jonathan Neal - Sizzle.js (http://sizzlejs.com/), Copyright (c) 2011, The Dojo Foundation - - SLF4J API v1.7.36 (http://www.slf4j.org), Copyright (c) 2004-2025 QOS.ch + - SLF4J API v2.0.17 (http://www.slf4j.org), Copyright (c) 2004-2025 QOS.ch - Split.js (https://github.com/nathancahill/Split.js), Copyright (c) 2015 Nathan Cahill - TableDnD v0.5 (https://github.com/isocra/TableDnD), Copyright (c) 2012 diff --git a/geode-assembly/src/main/dist/NOTICE b/geode-assembly/src/main/dist/NOTICE index 669158e0c443..273cf8f9afd7 100644 --- a/geode-assembly/src/main/dist/NOTICE +++ b/geode-assembly/src/main/dist/NOTICE @@ -1,5 +1,5 @@ Apache Geode -Copyright 2016-2025 The Apache Software Foundation. +Copyright 2016-2026 The Apache Software Foundation. This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/geode-book/config.yml b/geode-book/config.yml index c156d7e965c2..a311be02b4bf 100644 --- a/geode-book/config.yml +++ b/geode-book/config.yml @@ -21,19 +21,19 @@ public_host: localhost sections: - repository: name: geode-docs - directory: docs/guide/115 + directory: docs/guide/20 subnav_template: geode-subnav template_variables: product_name_long: Apache Geode product_name: Geode product_name_lowercase: geode - product_version: '1.15' - product_version_nodot: '115' - product_version_old_minor: '1.14' - product_version_geode: '1.15' - min_java_version: '8' - min_java_update: '121' + product_version: '2.0' + product_version_nodot: '20' + product_version_old_minor: '1.15' + product_version_geode: '2.0' + min_java_version: '17' + min_java_update: '16' support_url: http://geode.apache.org/community product_url: http://geode.apache.org/ book_title: Apache Geode Documentation diff --git a/geode-book/master_middleman/source/subnavs/geode-subnav.erb b/geode-book/master_middleman/source/subnavs/geode-subnav.erb index b4ba7467a4ce..f06a8914d460 100644 --- a/geode-book/master_middleman/source/subnavs/geode-subnav.erb +++ b/geode-book/master_middleman/source/subnavs/geode-subnav.erb @@ -2147,26 +2147,6 @@ limitations under the License.
  • Configuring Non-Sticky Sessions
  • -
  • - HTTP Session Management Module for Pivotal tc Server - -
  • HTTP Session Management Module for Tomcat
      diff --git a/geode-common/src/test/resources/expected-pom.xml b/geode-common/src/test/resources/expected-pom.xml index 374eda1da262..17d47feafb19 100644 --- a/geode-common/src/test/resources/expected-pom.xml +++ b/geode-common/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-concurrency-test/src/test/resources/expected-pom.xml b/geode-concurrency-test/src/test/resources/expected-pom.xml index 2940e8b56ce4..ea8edc97293e 100644 --- a/geode-concurrency-test/src/test/resources/expected-pom.xml +++ b/geode-concurrency-test/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-connectors/src/test/resources/expected-pom.xml b/geode-connectors/src/test/resources/expected-pom.xml index 6a30589edb13..e45d0295697c 100644 --- a/geode-connectors/src/test/resources/expected-pom.xml +++ b/geode-connectors/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -143,6 +175,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -154,6 +190,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -165,6 +205,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true @@ -177,6 +221,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + cglib * @@ -212,6 +260,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-core/build.gradle b/geode-core/build.gradle index 5ffa6f7d1254..ee5ac51b11bb 100755 --- a/geode-core/build.gradle +++ b/geode-core/build.gradle @@ -274,6 +274,10 @@ dependencies { //The resource-API is used by the JCA support. api('jakarta.resource:jakarta.resource-api') + // Explicitly declare jakarta.annotation-api to override the 2.1.0 version + // transitively brought by jakarta.resource-api:2.1.0, ensuring consistency + // with Spring Boot 3.3.4 which requires 2.1.1 + api('jakarta.annotation:jakarta.annotation-api') api('jakarta.transaction:jakarta.transaction-api') diff --git a/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/RegisterInterestPolicyPartIntegrationTest.java b/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/RegisterInterestPolicyPartIntegrationTest.java new file mode 100644 index 000000000000..639e0fd89deb --- /dev/null +++ b/geode-core/src/integrationTest/java/org/apache/geode/cache/client/internal/RegisterInterestPolicyPartIntegrationTest.java @@ -0,0 +1,162 @@ +/* + * 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.geode.cache.client.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.cache.client.PoolFactory; +import org.apache.geode.cache.client.PoolManager; +import org.apache.geode.internal.cache.tier.InterestType; +import org.apache.geode.internal.cache.tier.MessageType; +import org.apache.geode.internal.cache.tier.sockets.ChunkedMessage; +import org.apache.geode.internal.cache.tier.sockets.Message; +import org.apache.geode.internal.serialization.KnownVersion; +import org.apache.geode.test.junit.categories.ClientServerTest; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +/** + * Exercises, over a real client connection to a running server, how the register-interest command + * reads the message part that carries its interest result policy. + * + *

      + * A client op builds a register-interest request whose policy part carries a type other than the + * policy argument, and sends it. The helper type records whether an instance of it is created on + * the server while the part is read. The server must read the part only as its expected policy type + * and refuse a part carrying any other type. + */ +@Category({ClientServerTest.class}) +public class RegisterInterestPolicyPartIntegrationTest { + + private static final String REGION_NAME = "region"; + + @Rule + public ServerStarterRule server = + new ServerStarterRule().withRegion(RegionShortcut.REPLICATE, REGION_NAME).withAutoStart(); + + private PoolImpl pool; + + @Before + public void setUp() { + OtherPartType.reset(); + final PoolFactory poolFactory = PoolManager.createFactory(); + poolFactory.addServer("localhost", server.getPort()); + poolFactory.setReadTimeout(10_000); + poolFactory.setMinConnections(1); + pool = (PoolImpl) poolFactory.create("testPool"); + } + + @After + public void tearDown() { + if (pool != null) { + pool.destroy(); + } + } + + @Test + public void serverDoesNotProduceAnotherTypeFromThePolicyPart() { + try { + pool.execute(new PolicyPartOfAnotherTypeOp(REGION_NAME)); + } catch (final Exception ignored) { + // The request does not complete: the point of interest is which type the server produced + // while reading the part, which is recorded independently below. + } + + assertThat(OtherPartType.instantiated) + .as("reading the policy part must not produce a type other than the policy on the server") + .isFalse(); + } + + /** + * A register-interest request whose policy part carries a type other than the policy argument. + * Sends the request and does not attempt to interpret the response. + */ + private static class PolicyPartOfAnotherTypeOp extends AbstractOp { + + PolicyPartOfAnotherTypeOp(final String region) { + super(MessageType.REGISTER_INTEREST, 7); + getMessage().addStringPart(region, true); + getMessage().addIntPart(InterestType.KEY.ordinal()); + getMessage().addObjPart(new OtherPartType()); + getMessage().addBytesPart(new byte[] {(byte) 0x00}); + getMessage().addStringOrObjPart("key"); + getMessage().addBytesPart(new byte[] {(byte) 0x00}); + getMessage().addBytesPart(new byte[] {(byte) DataPolicy.REPLICATE.ordinal(), (byte) 0x01}); + } + + @Override + protected Message createResponseMessage() { + return new ChunkedMessage(1, KnownVersion.CURRENT); + } + + @Override + protected Object processResponse(final Message msg) throws Exception { + // Drain the whole response so this op does not return until the server has finished + // handling the request. + final ChunkedMessage chunkedMessage = (ChunkedMessage) msg; + chunkedMessage.readHeader(); + do { + chunkedMessage.receiveChunk(); + } while (!chunkedMessage.isLastChunk()); + return null; + } + + @Override + protected boolean isErrorResponse(final MessageType msgType) { + return false; + } + + @Override + protected long startAttempt(final ConnectionStats stats) { + return 0; + } + + @Override + protected void endSendAttempt(final ConnectionStats stats, final long start) {} + + @Override + protected void endAttempt(final ConnectionStats stats, final long start) {} + } + + /** + * A serializable type other than the register-interest policy argument. It records whether an + * instance of it is created, so a test can tell which type a part produced. + */ + public static class OtherPartType implements Serializable { + private static final long serialVersionUID = 1L; + + static volatile boolean instantiated = false; + + static void reset() { + instantiated = false; + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + instantiated = true; + } + } +} diff --git a/geode-core/src/main/antlr/org/apache/geode/cache/query/internal/parse/oql.g b/geode-core/src/main/antlr/org/apache/geode/cache/query/internal/parse/oql.g index cdd1623333e5..5ae8b4e4a79e 100644 --- a/geode-core/src/main/antlr/org/apache/geode/cache/query/internal/parse/oql.g +++ b/geode-core/src/main/antlr/org/apache/geode/cache/query/internal/parse/oql.g @@ -571,11 +571,16 @@ projectionAttributes : projection!{ AST node = null;}: - lb1:identifier TOK_COLON! ( tok1:aggregateExpr{node = #tok1;} | tok2:expr{node = #tok2;}) + // Use syntactic predicate to resolve nondeterminism between aggregateExpr and expr. + // The predicate checks for aggregate function keywords (sum, avg, min, max, count) followed by '('. + // Without this, the parser cannot determine which alternative to choose when it sees these keywords, + // since they can also be used as identifiers in regular expressions. + lb1:identifier TOK_COLON! ( (("sum"|"avg"|"min"|"max"|"count") TOK_LPAREN)=> tok1:aggregateExpr{node = #tok1;} | tok2:expr{node = #tok2;}) { #projection = #([PROJECTION, "projection", "org.apache.geode.cache.query.internal.parse.ASTProjection"], node, #lb1); } | - (tok3:aggregateExpr{node = #tok3;} | tok4:expr{node = #tok4;}) + // Same syntactic predicate as above to handle projections without a label (identifier:) + ((("sum"|"avg"|"min"|"max"|"count") TOK_LPAREN)=> tok3:aggregateExpr{node = #tok3;} | tok4:expr{node = #tok4;}) ( "as" lb2: identifier @@ -958,7 +963,10 @@ collectionExpr : aggregateExpr { int aggFunc = -1; boolean distinctOnly = false; }: !("sum" {aggFunc = SUM;} | "avg" {aggFunc = AVG;} ) - TOK_LPAREN ("distinct"! {distinctOnly = true;} ) ? tokExpr1:expr TOK_RPAREN + // Use greedy option to resolve nondeterminism with optional 'distinct' keyword. + // Greedy tells the parser to match 'distinct' whenever it appears, rather than + // being ambiguous about whether to match it or skip directly to the expression. + TOK_LPAREN (options {greedy=true;}: "distinct"! {distinctOnly = true;} ) ? tokExpr1:expr TOK_RPAREN { #aggregateExpr = #([AGG_FUNC, "aggregate", "org.apache.geode.cache.query.internal.parse.ASTAggregateFunc"], #tokExpr1); ((ASTAggregateFunc)#aggregateExpr).setAggregateFunctionType(aggFunc); @@ -975,8 +983,9 @@ aggregateExpr { int aggFunc = -1; boolean distinctOnly = false; }: | "count"^ + // Same greedy option as above for count's optional 'distinct' keyword TOK_LPAREN! ( TOK_STAR - | ("distinct"! {distinctOnly = true;} ) ? expr ) TOK_RPAREN! + | (options {greedy=true;}: "distinct"! {distinctOnly = true;} ) ? expr ) TOK_RPAREN! { ((ASTAggregateFunc)#aggregateExpr).setAggregateFunctionType(COUNT); #aggregateExpr.setText("aggregate"); diff --git a/geode-core/src/main/java/org/apache/geode/distributed/internal/deadlock/UnsafeThreadLocal.java b/geode-core/src/main/java/org/apache/geode/distributed/internal/deadlock/UnsafeThreadLocal.java index 17872c29cb65..afabb84722d2 100644 --- a/geode-core/src/main/java/org/apache/geode/distributed/internal/deadlock/UnsafeThreadLocal.java +++ b/geode-core/src/main/java/org/apache/geode/distributed/internal/deadlock/UnsafeThreadLocal.java @@ -14,72 +14,68 @@ */ package org.apache.geode.distributed.internal.deadlock; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.Map; +import java.util.WeakHashMap; /** - * Most of this thread local is safe to use, except for the getValue(Thread) method. That is not - * guaranteed to be correct. But for our deadlock detection tool I think it's good enough, and this - * class provides a very low overhead way for us to record what thread holds a particular resource. + * A ThreadLocal implementation that allows reading values from arbitrary threads, useful for + * deadlock detection. This implementation uses a WeakHashMap to track values per thread without + * requiring reflection or JVM internal access. * + *

      + * Unlike standard ThreadLocal, this class maintains an additional mapping that allows querying the + * value for any thread, not just the current thread. This is useful for deadlock detection where + * we need to inspect what resources other threads are holding. + *

      * + *

      + * The implementation uses WeakHashMap with Thread keys to ensure threads can be garbage collected + * when they terminate, preventing memory leaks. + *

      */ public class UnsafeThreadLocal extends ThreadLocal { /** - * Dangerous method. Uses reflection to extract the thread local for a given thread. - * - * Unlike get(), this method does not set the initial value if none is found - * + * Maps threads to their values. Uses WeakHashMap so terminated threads can be GC'd. Synchronized + * to ensure thread-safe access. */ - public T get(Thread thread) { - return (T) get(this, thread); - } + private final Map threadValues = + java.util.Collections.synchronizedMap(new WeakHashMap<>()); - private static Object get(ThreadLocal threadLocal, Thread thread) { - try { - Object threadLocalMap = - invokePrivate(threadLocal, "getMap", new Class[] {Thread.class}, new Object[] {thread}); - - if (threadLocalMap != null) { - Object entry = invokePrivate(threadLocalMap, "getEntry", new Class[] {ThreadLocal.class}, - new Object[] {threadLocal}); - if (entry != null) { - return getPrivate(entry, "value"); - } - } - return null; - } catch (Exception e) { - throw new RuntimeException("Unable to extract thread local", e); + /** + * Sets the value for the current thread and records it in the cross-thread map. + */ + @Override + public void set(T value) { + super.set(value); + if (value != null) { + threadValues.put(Thread.currentThread(), value); + } else { + threadValues.remove(Thread.currentThread()); } } - private static Object getPrivate(Object object, String fieldName) throws SecurityException, - NoSuchFieldException, IllegalArgumentException, IllegalAccessException { - Field field = object.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - return field.get(object); + /** + * Removes the value for the current thread from both the ThreadLocal and the cross-thread map. + */ + @Override + public void remove() { + super.remove(); + threadValues.remove(Thread.currentThread()); } - private static Object invokePrivate(Object object, String methodName, Class[] argTypes, - Object[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, - IllegalAccessException, InvocationTargetException { - - Method method = null; - Class clazz = object.getClass(); - while (method == null) { - try { - method = clazz.getDeclaredMethod(methodName, argTypes); - } catch (NoSuchMethodException e) { - clazz = clazz.getSuperclass(); - if (clazz == null) { - throw e; - } - } - } - method.setAccessible(true); - Object result = method.invoke(object, args); - return result; + /** + * Gets the value for an arbitrary thread, useful for deadlock detection. + * + *

      + * Unlike get(), this method does not set the initial value if none is found. Returns null if the + * specified thread has no value set. + *

      + * + * @param thread the thread whose value to retrieve + * @return the value for the specified thread, or null if none exists + */ + public T get(Thread thread) { + return threadValues.get(thread); } } diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/BaseCommand.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/BaseCommand.java index 9000a5503c00..8ca2c7daea8e 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/BaseCommand.java +++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/BaseCommand.java @@ -82,6 +82,8 @@ import org.apache.geode.internal.offheap.OffHeapHelper; import org.apache.geode.internal.security.SecurityService; import org.apache.geode.internal.sequencelog.EntryLogger; +import org.apache.geode.internal.serialization.DSCODE; +import org.apache.geode.internal.serialization.DataSerializableFixedID; import org.apache.geode.logging.internal.log4j.api.LogService; import org.apache.geode.security.GemFireSecurityException; import org.apache.geode.util.internal.GeodeGlossary; @@ -92,6 +94,9 @@ public abstract class BaseCommand implements Command { @Immutable private static final byte[] OK_BYTES = new byte[] {0}; + /** Length of the serialized form of an interest result policy: code, identifier, ordinal. */ + private static final int INTEREST_RESULT_POLICY_FORM_LENGTH = 3; + public static final int MAXIMUM_CHUNK_SIZE = Integer.getInteger("BridgeServer.MAXIMUM_CHUNK_SIZE", 100); @@ -873,6 +878,39 @@ static Message readRequest(final @NotNull ServerConnection servConn) { return requestMsg; } + /** + * Reads the interest result policy carried by the given message part. + * + *

      + * The policy is written by the client in the fixed-identifier form of + * {@link InterestResultPolicy}. Only that form is accepted here, so the part is read as a policy + * and a part in any other form is refused. + * + * @param policyPart the message part holding the interest result policy + * @return the policy the part describes + * @throws IOException if the part is not in the expected form + */ + protected static @NotNull InterestResultPolicy readInterestResultPolicy( + final @NotNull Part policyPart) throws IOException, ClassNotFoundException { + if (!hasInterestResultPolicyForm(policyPart)) { + throw new IOException("The interest result policy part is not in the expected form."); + } + return (InterestResultPolicy) policyPart.getObject(); + } + + private static boolean hasInterestResultPolicyForm(final @NotNull Part policyPart) { + if (!policyPart.isObject()) { + return false; + } + final byte[] serializedForm = policyPart.getSerializedForm(); + return serializedForm != null + && serializedForm.length == INTEREST_RESULT_POLICY_FORM_LENGTH + && serializedForm[0] == DSCODE.DS_FIXED_ID_BYTE.toByte() + && serializedForm[1] == DataSerializableFixedID.INTEREST_RESULT_POLICY + && serializedForm[2] >= InterestResultPolicy.NONE.getOrdinal() + && serializedForm[2] <= InterestResultPolicy.KEYS_VALUES.getOrdinal(); + } + protected static void fillAndSendRegisterInterestResponseChunks( final @Nullable LocalRegion region, final @NotNull Object riKey, final @NotNull InterestType interestType, diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61.java index 86984deb1640..b8953abf6250 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61.java +++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61.java @@ -78,7 +78,7 @@ public void cmdExecute(final @NotNull Message clientMessage, final InterestResultPolicy policy; try { - policy = (InterestResultPolicy) clientMessage.getPart(2).getObject(); + policy = readInterestResultPolicy(clientMessage.getPart(2)); } catch (Exception e) { writeChunkedException(clientMessage, e, serverConnection); serverConnection.setAsTrue(RESPONDED); @@ -113,6 +113,10 @@ public void cmdExecute(final @NotNull Message clientMessage, Object key; try { final Part keyPart = clientMessage.getPart(4); + if (interestType == InterestType.REGULAR_EXPRESSION && keyPart.isObject()) { + throw new IOException( + "The key part of a regular expression request is not in the expected form."); + } key = keyPart.getStringOrObject(); } catch (Exception e) { writeChunkedException(clientMessage, e, serverConnection); diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66.java index a64197eb80da..c4d06db5ad3a 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66.java +++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66.java @@ -79,7 +79,7 @@ public void cmdExecute(final @NotNull Message clientMessage, // Retrieve the InterestResultPolicy final InterestResultPolicy policy; try { - policy = (InterestResultPolicy) clientMessage.getPart(1).getObject(); + policy = readInterestResultPolicy(clientMessage.getPart(1)); } catch (Exception e) { writeChunkedException(clientMessage, e, serverConnection); serverConnection.setAsTrue(RESPONDED); diff --git a/geode-core/src/main/java/org/apache/geode/internal/net/BufferAttachmentTracker.java b/geode-core/src/main/java/org/apache/geode/internal/net/BufferAttachmentTracker.java new file mode 100644 index 000000000000..67bc775c2622 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/internal/net/BufferAttachmentTracker.java @@ -0,0 +1,103 @@ +/* + * 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.geode.internal.net; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Tracks the relationship between sliced ByteBuffers and their original parent buffers. + * This replaces the need to access internal JDK implementation classes, using only + * public Java APIs instead. + * + * When ByteBuffer.slice() is called, it creates a new buffer that shares content with + * the original. We need to track this relationship so that when returning buffers to + * the pool, we return the original pooled buffer, not the slice. + * + * This class uses IdentityHashMap (synchronized) which provides thread-safe access + * using object identity rather than equals(). This is critical because ByteBuffer.equals() + * compares buffer content and can throw IndexOutOfBoundsException if buffer position/limit + * is modified after being used as a map key. Callers must explicitly call removeTracking() + * to clean up entries when buffers are returned to the pool. + */ +class BufferAttachmentTracker { + + /** + * Maps sliced buffers to their original parent buffers using object identity. + * Uses synchronized IdentityHashMap for thread-safe access without relying on + * ByteBuffer.equals() or hashCode(), which can be problematic when buffer state changes. + * Entries must be explicitly removed via removeTracking() to prevent memory leaks. + * + * Note: This static mutable field is intentionally designed for global buffer tracking + * across the application. The PMD.StaticFieldsMustBeImmutable warning is suppressed + * because: + * 1. Mutable shared state is required to track buffer relationships across all threads + * 2. IdentityHashMap uses object identity (==) avoiding equals()/hashCode() issues + * 3. Collections.synchronizedMap provides thread-safe operations + * 4. This is the most efficient design for this use case + */ + @SuppressWarnings("PMD.StaticFieldsMustBeImmutable") + private static final Map sliceToOriginal = + Collections.synchronizedMap(new IdentityHashMap<>()); + + /** + * Records that a slice buffer was created from an original buffer. + * + * @param slice the sliced ByteBuffer + * @param original the original ByteBuffer that was sliced + */ + static void recordSlice(ByteBuffer slice, ByteBuffer original) { + sliceToOriginal.put(slice, original); + } + + /** + * Retrieves the original buffer for a given buffer, which may be a slice. + * If the buffer is not a slice (not tracked), returns the buffer itself. + * + * @param buffer the buffer to look up, which may be a slice + * @return the original pooled buffer, or the buffer itself if not a slice + */ + static ByteBuffer getOriginal(ByteBuffer buffer) { + ByteBuffer original = sliceToOriginal.get(buffer); + return original != null ? original : buffer; + } + + /** + * Removes tracking for a buffer. Should be called when returning a buffer + * to the pool to avoid memory leaks in the tracking map. + * + * @param buffer the buffer to stop tracking + */ + static void removeTracking(ByteBuffer buffer) { + sliceToOriginal.remove(buffer); + } + + /** + * For testing: returns the current size of the tracking map. + */ + static int getTrackingMapSize() { + return sliceToOriginal.size(); + } + + /** + * For testing: clears all tracking entries. + */ + static void clearTracking() { + sliceToOriginal.clear(); + } +} diff --git a/geode-core/src/main/java/org/apache/geode/internal/net/BufferPool.java b/geode-core/src/main/java/org/apache/geode/internal/net/BufferPool.java index 56c0b7328c0b..09a1b1796858 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/net/BufferPool.java +++ b/geode-core/src/main/java/org/apache/geode/internal/net/BufferPool.java @@ -22,13 +22,11 @@ import org.jetbrains.annotations.NotNull; -import org.apache.geode.InternalGemFireException; import org.apache.geode.annotations.VisibleForTesting; import org.apache.geode.distributed.internal.DMStats; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.internal.Assert; import org.apache.geode.internal.tcp.Connection; -import org.apache.geode.unsafe.internal.sun.nio.ch.DirectBuffer; import org.apache.geode.util.internal.GeodeGlossary; public class BufferPool { @@ -111,8 +109,11 @@ private ByteBuffer acquireDirectBuffer(int size, boolean send) { result = acquireLargeBuffer(send, size); } if (result.capacity() > size) { + ByteBuffer original = result; result.position(0).limit(size); result = result.slice(); + // Track the slice-to-original mapping to support buffer pool return + BufferAttachmentTracker.recordSlice(result, original); } return result; } @@ -159,19 +160,14 @@ private ByteBuffer acquirePredefinedFixedBuffer(boolean send, int size) { // it was garbage collected updateBufferStats(-defaultSize, ref.getSend(), true); } else { + // Reset the buffer to full capacity - clear() resets position and sets limit to capacity bb.clear(); - if (defaultSize > size) { - bb.limit(size); - } return bb; } ref = bufferTempQueue.poll(); } result = ByteBuffer.allocateDirect(defaultSize); updateBufferStats(defaultSize, send, true); - if (defaultSize > size) { - result.limit(size); - } return result; } @@ -267,17 +263,51 @@ ByteBuffer expandWriteBufferIfNeeded(BufferType type, ByteBuffer existing, } ByteBuffer acquireDirectBuffer(BufferPool.BufferType type, int capacity) { + // This method is used by NioPlainEngine and NioSslEngine which need full-capacity buffers + // that can be reused for multiple read/write operations. We should NOT create slices here. switch (type) { case UNTRACKED: return ByteBuffer.allocate(capacity); case TRACKED_SENDER: - return acquireDirectSenderBuffer(capacity); + return acquireDirectSenderBufferNonSliced(capacity); case TRACKED_RECEIVER: - return acquireDirectReceiveBuffer(capacity); + return acquireDirectReceiveBufferNonSliced(capacity); } throw new IllegalArgumentException("Unexpected buffer type " + type); } + /** + * Acquire a direct sender buffer without slicing - returns a buffer with capacity >= requested + * size + */ + private ByteBuffer acquireDirectSenderBufferNonSliced(int size) { + if (!useDirectBuffers) { + return ByteBuffer.allocate(size); + } + + if (size <= MEDIUM_BUFFER_SIZE) { + return acquirePredefinedFixedBuffer(true, size); + } else { + return acquireLargeBuffer(true, size); + } + } + + /** + * Acquire a direct receive buffer without slicing - returns a buffer with capacity >= requested + * size + */ + private ByteBuffer acquireDirectReceiveBufferNonSliced(int size) { + if (!useDirectBuffers) { + return ByteBuffer.allocate(size); + } + + if (size <= MEDIUM_BUFFER_SIZE) { + return acquirePredefinedFixedBuffer(false, size); + } else { + return acquireLargeBuffer(false, size); + } + } + ByteBuffer acquireNonDirectBuffer(BufferPool.BufferType type, int capacity) { switch (type) { case UNTRACKED: @@ -310,11 +340,13 @@ void releaseBuffer(BufferPool.BufferType type, @NotNull ByteBuffer buffer) { */ private void releaseBuffer(ByteBuffer buffer, boolean send) { if (buffer.isDirect()) { - buffer = getPoolableBuffer(buffer); - BBSoftReference bbRef = new BBSoftReference(buffer, send); - if (buffer.capacity() <= SMALL_BUFFER_SIZE) { + ByteBuffer original = getPoolableBuffer(buffer); + // Clean up tracking for this buffer to prevent memory leaks + BufferAttachmentTracker.removeTracking(buffer); + BBSoftReference bbRef = new BBSoftReference(original, send); + if (original.capacity() <= SMALL_BUFFER_SIZE) { bufferSmallQueue.offer(bbRef); - } else if (buffer.capacity() <= MEDIUM_BUFFER_SIZE) { + } else if (original.capacity() <= MEDIUM_BUFFER_SIZE) { bufferMiddleQueue.offer(bbRef); } else { bufferLargeQueue.offer(bbRef); @@ -328,25 +360,14 @@ private void releaseBuffer(ByteBuffer buffer, boolean send) { * If we hand out a buffer that is larger than the requested size we create a * "slice" of the buffer having the requested capacity and hand that out instead. * When we put the buffer back in the pool we need to find the original, non-sliced, - * buffer. This is held in DirectBuffer in its "attachment" field. + * buffer. This is tracked using BufferAttachmentTracker. * * This method is visible for use in debugging and testing. For debugging, invoke this method if * you need to see the non-sliced buffer for some reason, such as logging its hashcode. */ @VisibleForTesting ByteBuffer getPoolableBuffer(final ByteBuffer buffer) { - final Object attachment = DirectBuffer.attachment(buffer); - - if (null == attachment) { - return buffer; - } - - if (attachment instanceof ByteBuffer) { - return (ByteBuffer) attachment; - } - - throw new InternalGemFireException("direct byte buffer attachment was not a byte buffer but a " - + attachment.getClass().getName()); + return BufferAttachmentTracker.getOriginal(buffer); } /** diff --git a/geode-core/src/main/java/org/apache/geode/internal/offheap/AddressableMemoryManager.java b/geode-core/src/main/java/org/apache/geode/internal/offheap/AddressableMemoryManager.java index 7429c978786b..473beebf8e6e 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/offheap/AddressableMemoryManager.java +++ b/geode-core/src/main/java/org/apache/geode/internal/offheap/AddressableMemoryManager.java @@ -14,13 +14,9 @@ */ package org.apache.geode.internal.offheap; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.nio.ByteBuffer; import org.apache.geode.annotations.Immutable; -import org.apache.geode.annotations.internal.MakeNotStatic; import org.apache.geode.internal.JvmSizeUtils; import org.apache.geode.unsafe.internal.sun.misc.Unsafe; @@ -174,114 +170,68 @@ public static void fill(long addr, int size, byte fill) { unsafe.setMemory(addr, size, fill); } - @SuppressWarnings("rawtypes") - @MakeNotStatic - private static volatile Class dbbClass = null; - @SuppressWarnings("rawtypes") - @MakeNotStatic - private static volatile Constructor dbbCtor = null; - @MakeNotStatic - private static volatile boolean dbbCreateFailed = false; - @MakeNotStatic - private static volatile Method dbbAddressMethod = null; - @MakeNotStatic - private static volatile boolean dbbAddressFailed = false; - /** - * Returns the address of the Unsafe memory for the first byte of a direct ByteBuffer. If the - * buffer is not direct or the address can not be obtained return 0. + * Returns the address of the Unsafe memory for the first byte of a direct ByteBuffer. + * + * This implementation uses Unsafe to access the ByteBuffer's 'address' field directly, + * which eliminates the need for reflection with setAccessible() and therefore does not + * require the --add-opens=java.base/java.nio=ALL-UNNAMED JVM flag. + * + * If the buffer is not direct or the address cannot be obtained, returns 0. + * + * @param bb the ByteBuffer to get the address from + * @return the native memory address, or 0 if not available */ - @SuppressWarnings({"rawtypes", "unchecked"}) public static long getDirectByteBufferAddress(ByteBuffer bb) { if (!bb.isDirect()) { return 0L; } - if (dbbAddressFailed) { + if (unsafe == null) { return 0L; } - Method m = dbbAddressMethod; - if (m == null) { - Class c = dbbClass; - if (c == null) { - try { - c = Class.forName("java.nio.DirectByteBuffer"); - } catch (ClassNotFoundException e) { - // throw new IllegalStateException("Could not find java.nio.DirectByteBuffer", e); - dbbCreateFailed = true; - dbbAddressFailed = true; - return 0L; - } - dbbClass = c; - } - try { - m = c.getDeclaredMethod("address"); - } catch (NoSuchMethodException | SecurityException e) { - // throw new IllegalStateException("Could not get method DirectByteBuffer.address()", e); - dbbClass = null; - dbbAddressFailed = true; - return 0L; - } - m.setAccessible(true); - dbbAddressMethod = m; - } try { - return (Long) m.invoke(bb); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - // throw new IllegalStateException("Could not create an invoke DirectByteBuffer.address()", - // e); - dbbClass = null; - dbbAddressMethod = null; - dbbAddressFailed = true; + return unsafe.getBufferAddress(bb); + } catch (Exception e) { + // If Unsafe access fails, return 0 to indicate failure return 0L; } } /** - * Create a direct byte buffer given its address and size. The returned ByteBuffer will be direct - * and use the memory at the given address. + * Create a direct byte buffer given its address and size. + * + * This implementation creates a standard DirectByteBuffer and then modifies its internal + * 'address' field to point to the given memory address using Unsafe. This approach uses + * field-level access via Unsafe which does not require --add-opens flags. * - * @return the created direct byte buffer or null if it could not be created. + * The resulting ByteBuffer directly wraps the memory at the given address without copying, + * making it a zero-copy operation suitable for off-heap memory management. + * + * @param address the native memory address to wrap + * @param size the size of the buffer + * @return the created direct byte buffer wrapping the address, or null if creation failed */ - @SuppressWarnings({"rawtypes", "unchecked"}) static ByteBuffer createDirectByteBuffer(long address, int size) { - if (dbbCreateFailed) { + if (unsafe == null) { return null; } - Constructor ctor = dbbCtor; - if (ctor == null) { - Class c = dbbClass; - if (c == null) { - try { - c = Class.forName("java.nio.DirectByteBuffer"); - } catch (ClassNotFoundException e) { - // throw new IllegalStateException("Could not find java.nio.DirectByteBuffer", e); - dbbCreateFailed = true; - dbbAddressFailed = true; - return null; - } - dbbClass = c; - } - try { - ctor = c.getDeclaredConstructor(long.class, int.class); - } catch (NoSuchMethodException | SecurityException e) { - // throw new IllegalStateException("Could not get constructor DirectByteBuffer(long, int)", - // e); - dbbClass = null; - dbbCreateFailed = true; - return null; - } - ctor.setAccessible(true); - dbbCtor = ctor; - } try { - return (ByteBuffer) ctor.newInstance(address, size); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - // throw new IllegalStateException("Could not create an instance using DirectByteBuffer(long, - // int)", e); - dbbClass = null; - dbbCtor = null; - dbbCreateFailed = true; + // Allocate a small DirectByteBuffer using standard public API + // We'll reuse this buffer's structure but change its address and capacity + ByteBuffer buffer = ByteBuffer.allocateDirect(1); + + // Use Unsafe to modify the buffer's internal fields to point to our address + // This is similar to calling the private DirectByteBuffer(long, int) constructor + // but using field access instead of constructor reflection + unsafe.setBufferAddress(buffer, address); + unsafe.setBufferCapacity(buffer, size); + + // Reset position and limit to match the new capacity + buffer.clear(); + buffer.limit(size); + + return buffer; + } catch (Exception e) { return null; } } diff --git a/geode-core/src/main/java/org/apache/geode/internal/security/IntegratedSecurityService.java b/geode-core/src/main/java/org/apache/geode/internal/security/IntegratedSecurityService.java index 98fae4aa53ea..d2e204e6ab60 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/security/IntegratedSecurityService.java +++ b/geode-core/src/main/java/org/apache/geode/internal/security/IntegratedSecurityService.java @@ -28,8 +28,10 @@ import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; -import org.apache.shiro.ShiroException; import org.apache.shiro.UnavailableSecurityManagerException; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.config.ConfigurationException; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.SubjectThreadState; @@ -173,7 +175,7 @@ public Subject login(final Properties credentials) { currentUser.login(token); } catch (UnavailableSecurityManagerException e) { throw new CacheClosedException("Cache is closed."); - } catch (ShiroException e) { + } catch (AuthenticationException | ConfigurationException e) { logger.info("error logging in: " + token.getPrincipal()); Throwable cause = e.getCause(); if (cause == null) { @@ -199,7 +201,7 @@ public void logout() { try { logger.debug("Logging out " + currentUser.getPrincipal()); currentUser.logout(); - } catch (ShiroException e) { + } catch (AuthenticationException e) { logger.info("error logging out: " + currentUser.getPrincipal()); throw new GemFireSecurityException(e.getMessage(), e); } @@ -286,7 +288,7 @@ public void authorize(ResourcePermission context, Subject currentUser) { try { currentUser.checkPermission(context); - } catch (ShiroException e) { + } catch (AuthorizationException e) { String message = currentUser.getPrincipal() + " not authorized for " + context; logger.info("NotAuthorizedException: {}", message); throw new NotAuthorizedException(message, e); diff --git a/geode-core/src/main/java/org/apache/geode/internal/security/SecurityServiceFactory.java b/geode-core/src/main/java/org/apache/geode/internal/security/SecurityServiceFactory.java index 346955b0e1f4..7fbaa939d1d6 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/security/SecurityServiceFactory.java +++ b/geode-core/src/main/java/org/apache/geode/internal/security/SecurityServiceFactory.java @@ -18,6 +18,7 @@ import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_PEER_AUTHENTICATOR; import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_SHIRO_INIT; +import java.lang.reflect.Method; import java.util.Properties; import org.apache.commons.lang3.StringUtils; @@ -88,9 +89,20 @@ public static SecurityService create(Properties securityProps, private static boolean isShiroInUse() { // Don't import Shiro otherwise clients must include on classpath + // Use reflective lookup without initializing the class and be defensive about + // ClassNotFound/NoClassDef/Linkage errors which can occur when the webapp + // classloader does not provide Shiro runtime. If any such error occurs, + // treat Shiro as not in use to avoid hard failures during webapp startup. try { - return null != Class.forName("org.apache.shiro.SecurityUtils").getMethod("getSecurityManager") - .invoke(null); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Class securityUtils = Class.forName("org.apache.shiro.SecurityUtils", false, cl); + Method getSecurityManager = securityUtils.getMethod("getSecurityManager"); + Object sm = getSecurityManager.invoke(null); + return sm != null; + } catch (ClassNotFoundException e) { + return false; + } catch (LinkageError e) { + return false; } catch (Exception e) { return false; } diff --git a/geode-core/src/main/java/org/apache/geode/internal/security/shiro/SecurityManagerProvider.java b/geode-core/src/main/java/org/apache/geode/internal/security/shiro/SecurityManagerProvider.java index 5d286439a27d..447ba6b57811 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/security/shiro/SecurityManagerProvider.java +++ b/geode-core/src/main/java/org/apache/geode/internal/security/shiro/SecurityManagerProvider.java @@ -19,9 +19,9 @@ import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.config.Ini; -import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.session.mgt.DefaultSessionManager; import org.apache.shiro.session.mgt.SessionManager; @@ -41,14 +41,43 @@ public SecurityManagerProvider() { public SecurityManagerProvider(String shiroConfig) { securityManager = null; - IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:" + shiroConfig); - // we will need to make sure that shiro uses a case sensitive permission resolver - Ini.Section main = factory.getIni().addSection("main"); + // Shiro 2.1.0: IniSecurityManagerFactory is removed. Use Ini and DefaultSecurityManager + // directly. Create an IniRealm from the Ini so realms are properly configured. + Ini ini = new Ini(); + ini.loadFromPath("classpath:" + shiroConfig); + Ini.Section main = ini.getSection("main"); + if (main == null) { + main = ini.addSection("main"); + } main.put("geodePermissionResolver", GeodePermissionResolver.class.getName()); if (!main.containsKey("iniRealm.permissionResolver")) { main.put("iniRealm.permissionResolver", "$geodePermissionResolver"); } - shiroManager = factory.getInstance(); + + // Build an IniRealm from the loaded Ini and set GeodePermissionResolver explicitly. + // Create the realm first, set the GeodePermissionResolver, then attach the Ini + // so the realm parses roles/permissions using our resolver. + IniRealm iniRealm = new IniRealm(); + iniRealm.setPermissionResolver(new GeodePermissionResolver()); + iniRealm.setIni(ini); + // If the realm exposes an init method, ensure it is initialized (defensive). + try { + java.lang.reflect.Method init = iniRealm.getClass().getMethod("init"); + if (init != null) { + init.invoke(iniRealm); + } + } catch (Throwable t) { + // Not critical if method is absent or invocation fails, but log for diagnostics. + logger.debug("IniRealm init invocation failed; continuing without init", t); + } + + // Create a DefaultSecurityManager backed by the IniRealm so realms exist. + shiroManager = new DefaultSecurityManager((Realm) iniRealm); + + // try to increase global session timeout similar to other provider constructors + if (shiroManager instanceof DefaultSecurityManager) { + increaseShiroGlobalSessionTimeout((DefaultSecurityManager) shiroManager); + } } diff --git a/geode-core/src/main/java/org/apache/geode/internal/stats50/VMStats50.java b/geode-core/src/main/java/org/apache/geode/internal/stats50/VMStats50.java index a2d25dadeb0b..b223ab3ecbcf 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/stats50/VMStats50.java +++ b/geode-core/src/main/java/org/apache/geode/internal/stats50/VMStats50.java @@ -20,10 +20,8 @@ import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; -import java.lang.management.OperatingSystemMXBean; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -31,6 +29,8 @@ import java.util.List; import java.util.Map; +import com.sun.management.OperatingSystemMXBean; +import com.sun.management.UnixOperatingSystemMXBean; import org.apache.logging.log4j.Logger; import org.apache.geode.StatisticDescriptor; @@ -41,7 +41,6 @@ import org.apache.geode.SystemFailure; import org.apache.geode.annotations.Immutable; import org.apache.geode.annotations.internal.MakeNotStatic; -import org.apache.geode.internal.classloader.ClassPathLoader; import org.apache.geode.internal.statistics.StatisticsTypeFactoryImpl; import org.apache.geode.internal.statistics.VMStatsContract; import org.apache.geode.logging.internal.log4j.api.LogService; @@ -61,20 +60,24 @@ public class VMStats50 implements VMStatsContract { private static final ClassLoadingMXBean clBean; @Immutable private static final MemoryMXBean memBean; - @Immutable - private static final OperatingSystemMXBean osBean; + /** - * This is actually an instance of UnixOperatingSystemMXBean but this class is not available on - * Windows so needed to make this a runtime check. + * Platform-specific OperatingSystemMXBean providing extended metrics beyond the standard + * java.lang.management.OperatingSystemMXBean. This interface is in the exported + * com.sun.management package and provides processCpuTime and other platform metrics. + * Available on all platforms. */ @Immutable - private static final Object unixBean; - @Immutable - private static final Method getMaxFileDescriptorCount; - @Immutable - private static final Method getOpenFileDescriptorCount; + private static final OperatingSystemMXBean platformOsBean; + + /** + * Unix-specific OperatingSystemMXBean providing file descriptor metrics. + * Only available on Unix-like platforms (Linux, macOS, Solaris, etc.). + * Gracefully null on Windows and other non-Unix platforms. + */ @Immutable - private static final Method getProcessCpuTime; + private static final UnixOperatingSystemMXBean unixOsBean; + @Immutable private static final ThreadMXBean threadBean; @@ -150,52 +153,53 @@ public class VMStats50 implements VMStatsContract { static { clBean = ManagementFactory.getClassLoadingMXBean(); memBean = ManagementFactory.getMemoryMXBean(); - osBean = ManagementFactory.getOperatingSystemMXBean(); - { - Method m1 = null; - Method m2 = null; - Method m3 = null; - Object bean = null; - try { - Class c = - ClassPathLoader.getLatest().forName("com.sun.management.UnixOperatingSystemMXBean"); - if (c.isInstance(osBean)) { - m1 = c.getMethod("getMaxFileDescriptorCount"); - m2 = c.getMethod("getOpenFileDescriptorCount"); - bean = osBean; - } else { - // leave them null - } - // Always set ProcessCpuTime - m3 = osBean.getClass().getMethod("getProcessCpuTime"); - if (m3 != null) { - m3.setAccessible(true); - } - } catch (VirtualMachineError err) { - SystemFailure.initiateFailure(err); - // If this ever returns, rethrow the error. We're poisoned - // now, so don't let this thread continue. - throw err; - } catch (Throwable ex) { - // Whenever you catch Error or Throwable, you must also - // catch VirtualMachineError (see above). However, there is - // _still_ a possibility that you are dealing with a cascading - // error condition, so you also need to check to see if the JVM - // is still usable: - logger.warn(ex.getMessage()); - SystemFailure.checkFailure(); - // must be on a platform that does not support unix mxbean - bean = null; - m1 = null; - m2 = null; - m3 = null; - } finally { - unixBean = bean; - getMaxFileDescriptorCount = m1; - getOpenFileDescriptorCount = m2; - getProcessCpuTime = m3; + + // Initialize platform-specific MXBeans using direct interface casting. + // This approach eliminates the need for reflection and --add-opens flags. + // The com.sun.management package is exported by jdk.management module, + // making these interfaces accessible without module violations. + OperatingSystemMXBean tempPlatformBean = null; + UnixOperatingSystemMXBean tempUnixBean = null; + + try { + // Get the standard OperatingSystemMXBean + java.lang.management.OperatingSystemMXBean stdOsBean = + ManagementFactory.getOperatingSystemMXBean(); + + // Cast to com.sun.management.OperatingSystemMXBean for extended metrics + // This interface is in the exported com.sun.management package + if (stdOsBean instanceof OperatingSystemMXBean) { + tempPlatformBean = (OperatingSystemMXBean) stdOsBean; } + + // Check for Unix-specific interface + // This is only available on Unix-like platforms (Linux, macOS, Solaris) + if (stdOsBean instanceof UnixOperatingSystemMXBean) { + tempUnixBean = (UnixOperatingSystemMXBean) stdOsBean; + } + } catch (VirtualMachineError err) { + SystemFailure.initiateFailure(err); + // If this ever returns, rethrow the error. We're poisoned + // now, so don't let this thread continue. + throw err; + } catch (Throwable ex) { + // Whenever you catch Error or Throwable, you must also + // catch VirtualMachineError (see above). However, there is + // _still_ a possibility that you are dealing with a cascading + // error condition, so you also need to check to see if the JVM + // is still usable: + logger.warn( + "Unable to access platform OperatingSystemMXBean for statistics collection. " + + "This affects monitoring metrics but does not impact core Geode functionality: {}", + ex.getMessage()); + SystemFailure.checkFailure(); + tempPlatformBean = null; + tempUnixBean = null; + } finally { + platformOsBean = tempPlatformBean; + unixOsBean = tempUnixBean; } + threadBean = ManagementFactory.getThreadMXBean(); if (THREAD_STATS_ENABLED) { if (threadBean.isThreadCpuTimeSupported()) { @@ -242,7 +246,7 @@ public class VMStats50 implements VMStatsContract { true)); sds.add(f.createLongCounter("processCpuTime", "CPU timed used by the process in nanoseconds.", "nanoseconds")); - if (unixBean != null) { + if (unixOsBean != null) { sds.add(f.createLongGauge("fdLimit", "Maximum number of file descriptors", "fds", true)); sds.add(f.createLongGauge("fdsOpen", "Current number of open file descriptors", "fds")); } @@ -260,7 +264,7 @@ public class VMStats50 implements VMStatsContract { totalMemoryId = vmType.nameToId("totalMemory"); maxMemoryId = vmType.nameToId("maxMemory"); processCpuTimeId = vmType.nameToId("processCpuTime"); - if (unixBean != null) { + if (unixOsBean != null) { unix_fdLimitId = vmType.nameToId("fdLimit"); unix_fdsOpenId = vmType.nameToId("fdsOpen"); } else { @@ -585,7 +589,9 @@ private void refreshGC() { public void refresh() { Runtime rt = Runtime.getRuntime(); vmStats.setInt(pendingFinalizationCountId, memBean.getObjectPendingFinalizationCount()); - vmStats.setInt(cpusId, osBean.getAvailableProcessors()); + if (platformOsBean != null) { + vmStats.setInt(cpusId, platformOsBean.getAvailableProcessors()); + } vmStats.setInt(threadsId, threadBean.getThreadCount()); vmStats.setInt(daemonThreadsId, threadBean.getDaemonThreadCount()); vmStats.setInt(peakThreadsId, threadBean.getPeakThreadCount()); @@ -596,32 +602,38 @@ public void refresh() { vmStats.setLong(totalMemoryId, rt.totalMemory()); vmStats.setLong(maxMemoryId, rt.maxMemory()); - // Compute processCpuTime separately, if not accessible ignore - try { - if (getProcessCpuTime != null) { - Object v = getProcessCpuTime.invoke(osBean); - vmStats.setLong(processCpuTimeId, (Long) v); + // Collect process CPU time using public com.sun.management API. + // No reflection or setAccessible() required - this is a properly + // exported interface method from the jdk.management module. + if (platformOsBean != null) { + try { + long cpuTime = platformOsBean.getProcessCpuTime(); + vmStats.setLong(processCpuTimeId, cpuTime); + } catch (VirtualMachineError err) { + SystemFailure.initiateFailure(err); + // If this ever returns, rethrow the error. We're poisoned + // now, so don't let this thread continue. + throw err; + } catch (Throwable ex) { + // Whenever you catch Error or Throwable, you must also + // catch VirtualMachineError (see above). However, there is + // _still_ a possibility that you are dealing with a cascading + // error condition, so you also need to check to see if the JVM + // is still usable: + SystemFailure.checkFailure(); } - } catch (VirtualMachineError err) { - SystemFailure.initiateFailure(err); - // If this ever returns, rethrow the error. We're poisoned - // now, so don't let this thread continue. - throw err; - } catch (Throwable ex) { - // Whenever you catch Error or Throwable, you must also - // catch VirtualMachineError (see above). However, there is - // _still_ a possibility that you are dealing with a cascading - // error condition, so you also need to check to see if the JVM - // is still usable: - SystemFailure.checkFailure(); } - if (unixBean != null) { + // Collect Unix-specific file descriptor metrics. + // This interface is only implemented on Unix-like platforms; + // gracefully null on Windows. + if (unixOsBean != null) { try { - Object v = getMaxFileDescriptorCount.invoke(unixBean); - vmStats.setLong(unix_fdLimitId, (Long) v); - v = getOpenFileDescriptorCount.invoke(unixBean); - vmStats.setLong(unix_fdsOpenId, (Long) v); + long maxFd = unixOsBean.getMaxFileDescriptorCount(); + vmStats.setLong(unix_fdLimitId, maxFd); + + long openFd = unixOsBean.getOpenFileDescriptorCount(); + vmStats.setLong(unix_fdsOpenId, openFd); } catch (VirtualMachineError err) { SystemFailure.initiateFailure(err); // If this ever returns, rethrow the error. We're poisoned diff --git a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java index 89ebe3bf8448..82f908459f6a 100755 --- a/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java +++ b/geode-core/src/main/java/org/apache/geode/management/internal/ManagementAgent.java @@ -296,7 +296,14 @@ private void loadWebApplications() { // we need to pass in the sllConfig to pulse because it needs it to make jmx connection if (agentUtil.isAnyWarFileAvailable(pulseWar)) { System.setProperty(PULSE_EMBEDDED_PROP, "true"); - System.setProperty(PULSE_HOST_PROP, "" + config.getJmxManagerBindAddress()); + // When JmxManagerBindAddress is empty (bind all interfaces), Pulse must connect to + // localhost rather than the empty string. An empty host in the JMX URL resolves to + // InetAddress.getLocalHost(), which on Linux/Docker gives the container's bridge IP + // (e.g. 172.17.0.2) instead of 127.0.0.1. The embedded keystore only contains + // IPAddress:127.0.0.1 as a SAN, so the SSL handshake fails on any non-loopback IP. + String jmxBindAddress = config.getJmxManagerBindAddress(); + System.setProperty(PULSE_HOST_PROP, + jmxBindAddress.isEmpty() ? "localhost" : jmxBindAddress); System.setProperty(PULSE_PORT_PROP, "" + config.getJmxManagerPort()); final SocketCreator jmxSocketCreator = diff --git a/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java b/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java index bd70d1d99240..b93f069a6bc4 100644 --- a/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java +++ b/geode-core/src/main/java/org/apache/geode/metrics/internal/NoopMeterRegistry.java @@ -39,13 +39,10 @@ import io.micrometer.core.instrument.noop.NoopLongTaskTimer; import io.micrometer.core.instrument.noop.NoopMeter; import io.micrometer.core.instrument.noop.NoopTimer; -import io.micrometer.core.lang.NonNullApi; -import io.micrometer.core.lang.Nullable; import org.apache.geode.annotations.Immutable; import org.apache.geode.annotations.VisibleForTesting; -@NonNullApi public class NoopMeterRegistry extends MeterRegistry { @Immutable @@ -71,7 +68,7 @@ private NoopMeterRegistry(Clock clock) { } @Override - protected Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction valueFunction) { + protected Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction valueFunction) { return new NoopGauge(id); } diff --git a/geode-core/src/test/java/org/apache/geode/cache/query/internal/AbstractCompiledValueTestJUnitTest.java b/geode-core/src/test/java/org/apache/geode/cache/query/internal/AbstractCompiledValueTestJUnitTest.java index 8f1e4f4d28ca..b996f872b11f 100644 --- a/geode-core/src/test/java/org/apache/geode/cache/query/internal/AbstractCompiledValueTestJUnitTest.java +++ b/geode-core/src/test/java/org/apache/geode/cache/query/internal/AbstractCompiledValueTestJUnitTest.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes; import org.apache.geode.cache.query.internal.types.CollectionTypeImpl; import org.apache.geode.test.junit.runners.GeodeParamsRunner; @@ -47,7 +48,13 @@ private CompiledValue[] getCompiledValuesWhichDoNotImplementGetReceiver() { new LinkedHashMap<>()), new CompiledIn(compiledValue1, compiledValue2), new CompiledIteratorDef("test", new CollectionTypeImpl(), compiledValue1), - new CompiledJunction(new CompiledValue[] {compiledValue1, compiledValue2}, 89), + // Changed from hardcoded value 89 to OQLLexerTokenTypes.LITERAL_or constant. + // The hardcoded value 89 was the token number for LITERAL_or in the original grammar, + // but after adding syntactic predicates to fix nondeterminism warnings, the token + // numbering changed (LITERAL_or is now 94). Using the constant ensures this test + // remains correct regardless of future grammar changes. + new CompiledJunction(new CompiledValue[] {compiledValue1, compiledValue2}, + OQLLexerTokenTypes.LITERAL_or), new CompiledLike(compiledValue1, compiledValue2), new CompiledLiteral(compiledValue1), new CompiledMod(compiledValue1, compiledValue2), diff --git a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java index 64f34159dc03..eb255c6a4098 100644 --- a/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java +++ b/geode-core/src/test/java/org/apache/geode/internal/InternalDataSerializerShiroAcceptListTest.java @@ -25,15 +25,16 @@ import java.io.IOException; import java.util.Properties; -import org.apache.shiro.ShiroException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authz.AuthorizationException; -import org.apache.shiro.codec.CodecException; import org.apache.shiro.config.ConfigurationException; import org.apache.shiro.crypto.UnknownAlgorithmException; import org.apache.shiro.dao.InvalidResourceUsageException; import org.apache.shiro.env.RequiredTypeException; -import org.apache.shiro.io.SerializationException; +import org.apache.shiro.lang.ShiroException; +import org.apache.shiro.lang.codec.CodecException; +import org.apache.shiro.lang.io.SerializationException; +import org.apache.shiro.lang.util.InstantiationException; import org.apache.shiro.ldap.UnsupportedAuthenticationMechanismException; import org.apache.shiro.session.SessionException; import org.apache.shiro.session.StoppedSessionException; @@ -91,7 +92,7 @@ public void acceptsExecutionException() throws IOException, ClassNotFoundExcepti @Test public void acceptsInstantiationException() throws IOException, ClassNotFoundException { - trySerializingObject(new org.apache.shiro.util.InstantiationException("testing"), + trySerializingObject(new InstantiationException("testing"), propertiesWithoutFilter()); } diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/RegisterInterestObjectPartTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/RegisterInterestObjectPartTest.java new file mode 100644 index 000000000000..b439d0a6f44a --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/RegisterInterestObjectPartTest.java @@ -0,0 +1,107 @@ +/* + * 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.geode.internal.cache.tier.sockets; + +import static org.apache.geode.internal.cache.tier.sockets.BaseCommand.readInterestResultPolicy; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.InterestResultPolicy; +import org.apache.geode.internal.util.BlobHelper; +import org.apache.geode.test.junit.categories.ClientServerTest; + +/** + * Verifies how the register-interest commands read the message {@link Part} that carries the + * interest result policy. + * + *

      + * The part is read as an {@link InterestResultPolicy}: it is accepted only in the form the client + * writes it in, and a part carrying any other type is refused and that type is not produced. The + * helper type below records whether an instance of it is created while a part is read. + */ +@Category({ClientServerTest.class}) +public class RegisterInterestObjectPartTest { + + @Before + public void setUp() { + OtherPartType.reset(); + } + + @Test + public void policyPartOfAnotherTypeIsRefusedWithoutProducingThatType() throws Exception { + final byte[] objectPartBytes = BlobHelper.serializeToBlob(new OtherPartType()); + + final Part part = new Part(); + part.setPartState(objectPartBytes, true); + + assertThat(catchThrowable(() -> readInterestResultPolicy(part))) + .as("a policy part holding another type is refused") + .isInstanceOf(IOException.class); + + assertThat(OtherPartType.instantiated) + .as("reading the policy part must not produce a type other than the policy") + .isFalse(); + } + + @Test + public void nonObjectPolicyPartIsRefused() { + final Part part = new Part(); + part.setPartState(new byte[] {0x01, 0x25, 0x02}, false); + + assertThat(catchThrowable(() -> readInterestResultPolicy(part))) + .as("a policy part that is not object typed is refused") + .isInstanceOf(IOException.class); + } + + @Test + public void eachPolicyValueRoundTripsThroughThePart() throws Exception { + for (final InterestResultPolicy expected : new InterestResultPolicy[] { + InterestResultPolicy.NONE, InterestResultPolicy.KEYS, InterestResultPolicy.KEYS_VALUES}) { + final Part part = new Part(); + part.setPartState(BlobHelper.serializeToBlob(expected), true); + + assertThat(readInterestResultPolicy(part)) + .as("policy %s survives a write and read of the policy part", expected) + .isSameAs(expected); + } + } + + /** + * A serializable type other than the register-interest policy argument. It records whether an + * instance of it is created, so a test can tell which type a part produced. + */ + public static class OtherPartType implements Serializable { + private static final long serialVersionUID = 1L; + + static volatile boolean instantiated = false; + + static void reset() { + instantiated = false; + } + + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + instantiated = true; + } + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61Test.java b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61Test.java index 6559f856cef5..cfec6dbf98fd 100644 --- a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61Test.java +++ b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterest61Test.java @@ -33,6 +33,7 @@ import org.mockito.MockitoAnnotations; import org.apache.geode.CancelCriterion; +import org.apache.geode.cache.InterestResultPolicy; import org.apache.geode.cache.operations.RegisterInterestOperationContext; import org.apache.geode.internal.cache.InternalCache; import org.apache.geode.internal.cache.LocalRegion; @@ -46,6 +47,7 @@ import org.apache.geode.internal.security.AuthorizeRequest; import org.apache.geode.internal.security.SecurityService; import org.apache.geode.internal.serialization.KnownVersion; +import org.apache.geode.internal.util.BlobHelper; import org.apache.geode.security.NotAuthorizedException; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; @@ -98,6 +100,9 @@ public void setUp() throws Exception { when(cache.getRegion(isA(String.class))).thenReturn(uncheckedCast(mock(LocalRegion.class))); when(cache.getCancelCriterion()).thenReturn(mock(CancelCriterion.class)); + final Part policyPart = new Part(); + policyPart.setPartState(BlobHelper.serializeToBlob(InterestResultPolicy.KEYS_VALUES), true); + when(durablePart.getObject()).thenReturn(DURABLE); when(interestTypePart.getInt()).thenReturn(0); @@ -107,7 +112,7 @@ public void setUp() throws Exception { when(message.getNumberOfParts()).thenReturn(6); when(message.getPart(eq(0))).thenReturn(regionNamePart); when(message.getPart(eq(1))).thenReturn(interestTypePart); - when(message.getPart(eq(2))).thenReturn(mock(Part.class)); + when(message.getPart(eq(2))).thenReturn(policyPart); when(message.getPart(eq(3))).thenReturn(durablePart); when(message.getPart(eq(4))).thenReturn(keyPart); when(message.getPart(eq(5))).thenReturn(notifyPart); diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66Test.java b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66Test.java index a3a6a0f6f501..3d4f1fc34eb6 100644 --- a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66Test.java +++ b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/command/RegisterInterestList66Test.java @@ -35,6 +35,7 @@ import org.mockito.MockitoAnnotations; import org.apache.geode.CancelCriterion; +import org.apache.geode.cache.InterestResultPolicy; import org.apache.geode.cache.operations.RegisterInterestOperationContext; import org.apache.geode.internal.cache.InternalCache; import org.apache.geode.internal.cache.LocalRegion; @@ -47,6 +48,7 @@ import org.apache.geode.internal.security.AuthorizeRequest; import org.apache.geode.internal.security.SecurityService; import org.apache.geode.internal.serialization.KnownVersion; +import org.apache.geode.internal.util.BlobHelper; import org.apache.geode.security.NotAuthorizedException; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; @@ -103,6 +105,9 @@ public void setUp() throws Exception { when(cache.getRegion(isA(String.class))).thenReturn(uncheckedCast(mock(LocalRegion.class))); when(cache.getCancelCriterion()).thenReturn(mock(CancelCriterion.class)); + final Part policyPart = new Part(); + policyPart.setPartState(BlobHelper.serializeToBlob(InterestResultPolicy.KEYS_VALUES), true); + when(durablePart.getObject()).thenReturn(DURABLE); when(interestTypePart.getInt()).thenReturn(0); @@ -111,7 +116,7 @@ public void setUp() throws Exception { when(message.getNumberOfParts()).thenReturn(6); when(message.getPart(eq(0))).thenReturn(regionNamePart); - when(message.getPart(eq(1))).thenReturn(interestTypePart); + when(message.getPart(eq(1))).thenReturn(policyPart); when(message.getPart(eq(2))).thenReturn(durablePart); when(message.getPart(eq(3))).thenReturn(keyPart); when(message.getPart(eq(4))).thenReturn(notifyPart); diff --git a/geode-core/src/test/java/org/apache/geode/internal/net/BufferAttachmentTrackerTest.java b/geode-core/src/test/java/org/apache/geode/internal/net/BufferAttachmentTrackerTest.java new file mode 100644 index 000000000000..aa37d9b64aa9 --- /dev/null +++ b/geode-core/src/test/java/org/apache/geode/internal/net/BufferAttachmentTrackerTest.java @@ -0,0 +1,236 @@ +/* + * 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.geode.internal.net; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.After; +import org.junit.Test; + +/** + * Unit tests for BufferAttachmentTracker. + */ +public class BufferAttachmentTrackerTest { + + @After + public void tearDown() { + // Clean up after each test + BufferAttachmentTracker.clearTracking(); + } + + @Test + public void getOriginal_returnsOriginalBufferForSlice() { + ByteBuffer original = ByteBuffer.allocateDirect(1024); + original.position(0).limit(512); + ByteBuffer slice = original.slice(); + + BufferAttachmentTracker.recordSlice(slice, original); + + ByteBuffer result = BufferAttachmentTracker.getOriginal(slice); + + assertThat(result).isSameAs(original); + } + + @Test + public void getOriginal_returnsBufferItselfWhenNotTracked() { + ByteBuffer buffer = ByteBuffer.allocateDirect(1024); + + ByteBuffer result = BufferAttachmentTracker.getOriginal(buffer); + + assertThat(result).isSameAs(buffer); + } + + @Test + public void removeTracking_removesSliceMapping() { + ByteBuffer original = ByteBuffer.allocateDirect(1024); + original.position(0).limit(512); + ByteBuffer slice = original.slice(); + + BufferAttachmentTracker.recordSlice(slice, original); + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(1); + + BufferAttachmentTracker.removeTracking(slice); + + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(0); + assertThat(BufferAttachmentTracker.getOriginal(slice)).isSameAs(slice); + } + + @Test + public void trackingMapSize_reflectsCurrentMappings() { + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(0); + + ByteBuffer original1 = ByteBuffer.allocateDirect(1024); + ByteBuffer slice1 = original1.slice(); + BufferAttachmentTracker.recordSlice(slice1, original1); + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(1); + + ByteBuffer original2 = ByteBuffer.allocateDirect(2048); + ByteBuffer slice2 = original2.slice(); + BufferAttachmentTracker.recordSlice(slice2, original2); + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(2); + } + + @Test + public void clearTracking_removesAllMappings() { + ByteBuffer original1 = ByteBuffer.allocateDirect(1024); + ByteBuffer slice1 = original1.slice(); + BufferAttachmentTracker.recordSlice(slice1, original1); + + ByteBuffer original2 = ByteBuffer.allocateDirect(2048); + ByteBuffer slice2 = original2.slice(); + BufferAttachmentTracker.recordSlice(slice2, original2); + + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(2); + + BufferAttachmentTracker.clearTracking(); + + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(0); + } + + @Test + public void recordSlice_canOverwriteExistingMapping() { + ByteBuffer original1 = ByteBuffer.allocateDirect(1024); + ByteBuffer original2 = ByteBuffer.allocateDirect(2048); + ByteBuffer slice = original1.slice(); + + BufferAttachmentTracker.recordSlice(slice, original1); + assertThat(BufferAttachmentTracker.getOriginal(slice)).isSameAs(original1); + + BufferAttachmentTracker.recordSlice(slice, original2); + assertThat(BufferAttachmentTracker.getOriginal(slice)).isSameAs(original2); + } + + @Test + public void worksWithHeapBuffers() { + ByteBuffer original = ByteBuffer.allocate(1024); + original.position(0).limit(512); + ByteBuffer slice = original.slice(); + + BufferAttachmentTracker.recordSlice(slice, original); + + ByteBuffer result = BufferAttachmentTracker.getOriginal(slice); + + assertThat(result).isSameAs(original); + } + + @Test + public void simpleThreadSafetyTest() { + // Create a single original and slice + ByteBuffer original = ByteBuffer.allocateDirect(1024); + ByteBuffer slice = original.slice(); + + // Record it + BufferAttachmentTracker.recordSlice(slice, original); + + // Immediately retrieve it + ByteBuffer result = BufferAttachmentTracker.getOriginal(slice); + + // Should get back the exact same original + assertThat(result).isSameAs(original); + assertThat(result).isNotSameAs(slice); + + System.out.println("Original identity: " + System.identityHashCode(original)); + System.out.println("Slice identity: " + System.identityHashCode(slice)); + System.out.println("Result identity: " + System.identityHashCode(result)); + } + + /** + * Thread-safety test: Concurrent reads and writes on the same slice. + * This verifies that race conditions don't cause incorrect mappings. + */ + @Test + public void concurrentAccessToSameSlice_isThreadSafe() throws InterruptedException { + final int numThreads = 10; + final int iterations = 1000; + final ExecutorService executor = Executors.newFixedThreadPool(numThreads); + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(numThreads); + final AtomicInteger errors = new AtomicInteger(0); + + ByteBuffer original = ByteBuffer.allocateDirect(1024); + ByteBuffer slice = original.slice(); + + for (int i = 0; i < numThreads; i++) { + executor.submit(() -> { + try { + startLatch.await(); + + for (int j = 0; j < iterations; j++) { + // Record the mapping + BufferAttachmentTracker.recordSlice(slice, original); + + // Immediately retrieve it + ByteBuffer retrieved = BufferAttachmentTracker.getOriginal(slice); + + // Should always get the original back + if (retrieved != original) { + errors.incrementAndGet(); + } + } + } catch (Exception e) { + errors.incrementAndGet(); + e.printStackTrace(); + } finally { + doneLatch.countDown(); + } + }); + } + + startLatch.countDown(); + boolean completed = doneLatch.await(30, TimeUnit.SECONDS); + executor.shutdown(); + + assertThat(completed).isTrue(); + assertThat(errors.get()).isEqualTo(0); + } + + /** + * Memory safety test: Verifies that WeakHashMap allows slice buffers to be + * garbage collected without causing memory leaks. + */ + @Test + public void weakHashMap_allowsGarbageCollection() { + ByteBuffer original = ByteBuffer.allocateDirect(1024); + ByteBuffer slice = original.slice(); + + BufferAttachmentTracker.recordSlice(slice, original); + assertThat(BufferAttachmentTracker.getTrackingMapSize()).isEqualTo(1); + + // Remove reference to slice (but not original) + slice = null; + + // Force garbage collection + System.gc(); + System.runFinalization(); + + // Give GC time to clean up weak references + // The WeakHashMap should eventually remove the entry when the slice is GC'd + // Note: This is non-deterministic, so we can't assert on size without + // potentially making the test flaky. The important thing is that it + // doesn't prevent GC. + + // What we can verify is that having null'd the slice doesn't break anything + ByteBuffer result = BufferAttachmentTracker.getOriginal(original); + assertThat(result).isSameAs(original); // Original still works + } +} diff --git a/geode-core/src/test/java/org/apache/geode/internal/security/IntegratedSecurityServiceTest.java b/geode-core/src/test/java/org/apache/geode/internal/security/IntegratedSecurityServiceTest.java index fbe90a448149..3588e3ff4cd4 100644 --- a/geode-core/src/test/java/org/apache/geode/internal/security/IntegratedSecurityServiceTest.java +++ b/geode-core/src/test/java/org/apache/geode/internal/security/IntegratedSecurityServiceTest.java @@ -24,8 +24,8 @@ import java.util.Properties; -import org.apache.shiro.ShiroException; import org.apache.shiro.UnavailableSecurityManagerException; +import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.session.Session; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; @@ -53,7 +53,7 @@ public class IntegratedSecurityServiceTest { private org.apache.shiro.mgt.SecurityManager shiroManager; private IntegratedSecurityService securityService; - private ShiroException shiroException; + private AuthenticationException shiroException; private Properties properties; @Before @@ -68,7 +68,7 @@ public void before() throws Exception { when(mockSubject.getPrincipal()).thenReturn("principal"); when(mockSubject.getSession()).thenReturn(mock(Session.class)); - shiroException = mock(ShiroException.class); + shiroException = mock(AuthenticationException.class); properties = new Properties(); securityService = new IntegratedSecurityService(provider, null); @@ -189,7 +189,7 @@ public void login_when_ShiroException_hasNoCause() throws Exception { doThrow(shiroException).when(mockSubject).login(any(GeodeAuthenticationToken.class)); assertThatThrownBy(() -> securityService.login(properties)) .isInstanceOf(AuthenticationFailedException.class) - .hasCauseInstanceOf(ShiroException.class) + .hasCauseInstanceOf(AuthenticationException.class) .hasMessageContaining("Authentication error. Please check your credentials"); } diff --git a/geode-core/src/test/resources/expected-pom.xml b/geode-core/src/test/resources/expected-pom.xml index 4b0caecf2602..f8a4cf1f007a 100644 --- a/geode-core/src/test/resources/expected-pom.xml +++ b/geode-core/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,25 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + + + + + jakarta.annotation + jakarta.annotation-api + compile + + + log4j-to-slf4j + org.apache.logging.log4j + + + * + ch.qos.logback + @@ -88,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +175,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -143,6 +190,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -154,6 +205,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -165,6 +220,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -176,6 +235,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -187,6 +250,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -198,6 +265,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -209,6 +280,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -220,6 +295,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -231,6 +310,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -242,6 +325,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -253,6 +340,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -264,6 +355,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -275,6 +370,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true @@ -287,6 +386,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true @@ -299,6 +402,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -310,6 +417,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -321,6 +432,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -332,6 +447,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -343,6 +462,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -354,6 +477,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -365,6 +492,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -376,6 +507,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -387,6 +522,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -398,6 +537,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -409,6 +552,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + * * @@ -424,6 +571,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -435,6 +586,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true @@ -447,6 +602,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-cq/src/test/resources/expected-pom.xml b/geode-cq/src/test/resources/expected-pom.xml index 3238e6c3c48f..8c9fa2712188 100644 --- a/geode-cq/src/test/resources/expected-pom.xml +++ b/geode-cq/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-deployment/geode-deployment-legacy/src/test/resources/expected-pom.xml b/geode-deployment/geode-deployment-legacy/src/test/resources/expected-pom.xml index 70dd4cd8828d..19f3cb437857 100644 --- a/geode-deployment/geode-deployment-legacy/src/test/resources/expected-pom.xml +++ b/geode-deployment/geode-deployment-legacy/src/test/resources/expected-pom.xml @@ -1,5 +1,5 @@ - + -You set up and use the module by modifying the Tomcat's `server.xml` and `context.xml` files. +You set up and use the module by modifying Tomcat's `server.xml` and `context.xml` files. -For instructions specific to SpringSource tc Server templates, refer to [HTTP Session Management Module for Pivotal tc Server](session_mgmt_tcserver.html). +**Note:** Geode only supports Tomcat 10.1 and later versions (Jakarta EE). Support for Tomcat 7, 8, 9, and Pivotal tc Server has been discontinued. For tc Server users, migration to Tomcat 10.1 or later is required. - **[Installing the HTTP Module for Tomcat](../../tools_modules/http_session_mgmt/tomcat_installing_the_module.html)** diff --git a/geode-docs/tools_modules/http_session_mgmt/session_mgmt_weblogic.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/session_mgmt_weblogic.html.md.erb index 0ef18684ba71..a8bc272a2703 100644 --- a/geode-docs/tools_modules/http_session_mgmt/session_mgmt_weblogic.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/session_mgmt_weblogic.html.md.erb @@ -21,7 +21,7 @@ limitations under the License. You implement session caching with the HTTP Session Management Module for AppServers with a special filter, defined in the `web.xml`, which is configured to intercept and wrap all requests. -You can use this HTTP module with a variety of application servers. Wrapping each request allows the interception of `getSession()` calls to be handled by <%=vars.product_name%> instead of the native container. This approach is a generic solution, which is supported by any container that implements the Servlet 3.1 specification. +You can use this HTTP module with a variety of application servers. Wrapping each request allows the interception of `getSession()` calls to be handled by <%=vars.product_name%> instead of the native container. This approach is a generic solution, which is supported by any container that implements the Jakarta Servlet 6.0 specification (Jakarta EE 10). - **[Setting Up the HTTP Module for AppServers](../../tools_modules/http_session_mgmt/weblogic_setting_up_the_module.html)** diff --git a/geode-docs/tools_modules/http_session_mgmt/session_security_filter.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/session_security_filter.html.md.erb new file mode 100644 index 000000000000..2632826cc8d7 --- /dev/null +++ b/geode-docs/tools_modules/http_session_mgmt/session_security_filter.html.md.erb @@ -0,0 +1,325 @@ +--- +title: Securing HTTP Session Deserialization +--- + + + +This topic describes how to configure session deserialization security using ObjectInputFilter (JEP 290) to protect against deserialization vulnerabilities. + +## Overview + +Apache Geode HTTP Session Management uses Java serialization to store session attributes in the distributed cache. To protect against deserialization attacks, you can configure an ObjectInputFilter that controls which classes are allowed to be deserialized. + +**Key Benefits:** + +- **Application-Level Security**: Each web application defines its own security policy +- **Zero-Downtime Configuration**: Changes take effect on WAR deployment, no cluster restart required +- **Defense in Depth**: Explicit allowlist prevents gadget chain attacks +- **Backward Compatible**: Existing applications continue to work without configuration + +## Security Warning + +**Without a configured filter, session deserialization has NO restrictions.** Any serializable class can be deserialized, leaving your application vulnerable to: + +- Remote Code Execution (RCE) +- Denial of Service (DoS) +- Arbitrary object instantiation attacks + +**Always configure a deserialization filter for production deployments.** + +## Basic Configuration + +### Step 1: Add Filter Pattern to web.xml + +Add a context parameter to your application's `web.xml`: + +``` xml + + + serializable-object-filter + com.myapp.model.**;java.lang.**;!* + + + + + gemfire-session-filter + org.apache.geode.modules.session.filter.SessionCachingFilter + + + +``` + +### Step 2: Deploy WAR File + +Deploy or redeploy your WAR file to the application server. The filter takes effect immediately—no cluster restart required. + +## Pattern Syntax + +The filter pattern follows [JEP 290](https://openjdk.org/jeps/290) syntax: + +| Pattern | Meaning | +|---------|---------| +| `com.myapp.**` | Allow all classes in `com.myapp` package and subpackages | +| `com.myapp.model.User` | Allow specific class only | +| `java.lang.**` | Allow all classes in `java.lang` package | +| `!com.dangerous.**` | Explicitly reject package (takes precedence) | +| `!*` | Reject everything else (default deny) | + +**Pattern Evaluation Order:** + +1. Patterns are evaluated left-to-right +2. Rejection patterns (`!`) take precedence over allowlist patterns +3. First matching pattern determines the result +4. Always end with `!*` for default deny + +## Configuration Examples + +### Minimal Configuration + +Allow only your application models and essential Java classes: + +``` xml + + com.myapp.model.**; + java.lang.**;java.util.**; + !* + +``` + +### E-Commerce Application + +``` xml + + com.shop.model.**; + com.shop.cart.**; + com.payment.dto.**; + java.lang.**;java.util.**;java.time.**; + !* + +``` + +### Multi-Module Application + +``` xml + + com.company.common.**; + com.company.customer.**; + com.company.order.**; + java.lang.**;java.util.**;java.math.BigDecimal; + !com.company.internal.**; + !* + +``` + +### Rejecting Specific Classes + +``` xml + + com.myapp.**; + !com.myapp.deprecated.**; + !com.myapp.legacy.OldClass; + java.lang.**;java.util.**; + !* + +``` + +## Multi-Application Deployments + +Each web application has its own isolated security policy: + +**Application 1 (E-commerce):** +``` xml + + com.shop.model.**; + com.payment.**; + java.lang.**;java.util.**; + !* + +``` + +**Application 2 (Analytics):** +``` xml + + com.analytics.**; + com.ml.models.**; + java.lang.**;java.util.**; + !* + +``` + +**Application 3 (CMS):** +``` xml + + com.cms.content.**; + java.lang.**;java.util.**; + !* + +``` + +Each application's sessions can only deserialize classes allowed by its specific filter pattern. + +## Best Practices + +### 1. Use Explicit Allowlists + +**Don't:** +``` xml +* +``` + +**Do:** +``` xml + + com.myapp.safe.**; + java.lang.**;java.util.**; + !* + +``` + +### 2. Always End with `!*` + +This creates a default-deny policy where only explicitly allowed classes can be deserialized. + +### 3. Be Specific with Package Names + +**Less secure:** +``` xml +com.**;!* +``` + +**More secure:** +``` xml +com.myapp.model.**;!* +``` + +### 4. Include Essential Java Packages + +Most applications need these: +``` xml +java.lang.**; +java.util.**; +java.time.**; +``` + +### 5. Test Thoroughly + +After configuring the filter: + +1. Test all session operations (create, read, update, delete) +2. Verify session attributes deserialize correctly +3. Test session failover scenarios +4. Monitor logs for `ObjectInputFilter` rejections + +## Troubleshooting + +### ClassNotFoundException or Deserialization Failures + +**Symptom:** Session attributes fail to deserialize after adding filter + +**Solution:** Add the missing class package to your filter pattern: + +``` xml + + com.myapp.model.**; + com.thirdparty.library.**; + java.lang.**;java.util.**; + !* + +``` + +### Filter Not Taking Effect + +**Symptom:** Filter pattern changes don't apply + +**Solution:** + +1. Verify `web.xml` is packaged correctly in the WAR +2. Redeploy the WAR file completely +3. Check application server logs for errors +4. Verify parameter name is exactly `serializable-object-filter` + +### Session Attribute Classes Rejected + +**Symptom:** Logs show "ObjectInputFilter rejected class: com.myapp.NewClass" + +**Solution:** Add the class or package to your allowlist: + +``` xml + + com.myapp.model.**; + com.myapp.NewClass; + java.lang.**;java.util.**; + !* + +``` + +## Migration Guide + +### For Existing Applications + +1. **Identify Session Attribute Classes** + - List all classes stored in HTTP sessions + - Include transitive dependencies (classes referenced by session objects) + +2. **Create Filter Pattern** + - Start with your application packages + - Add essential Java packages + - End with `!*` + +3. **Test in Development** + - Deploy with filter enabled + - Exercise all session operations + - Fix any deserialization failures + +4. **Deploy to Production** + - Add filter to `web.xml` + - Redeploy WAR file (zero downtime) + - Monitor logs for unexpected rejections + +### Backward Compatibility + +**Without Filter Configuration:** +- Sessions continue to work as before +- No breaking changes +- No security protection (vulnerable) + +**With Filter Configuration:** +- Explicit security policy enforced +- Only allowed classes can be deserialized +- Protected against deserialization attacks + +## Security Reference + +### JEP 290 + +The filter implementation uses Java's [JEP 290: Filter Incoming Serialization Data](https://openjdk.org/jeps/290), which provides: + +- Per-stream filtering capability +- Pattern-based class allowlists/denylists +- Built-in protection against known gadget chains + +### Additional Resources + +- [OWASP Deserialization Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html) +- [Java Serialization Security Best Practices](https://www.oracle.com/java/technologies/javase/seccodeguide.html#8) + +## Related Topics + +- [Setting Up the HTTP Module for Tomcat](tomcat_setting_up_the_module.html) +- [Setting Up the HTTP Module for tc Server](tc_setting_up_the_module.html) +- [HTTP Session Management Quick Start](quick_start.html) diff --git a/geode-docs/tools_modules/http_session_mgmt/tc_additional_info.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/tc_additional_info.html.md.erb index 43bf40357732..47cf2ce74c86 100644 --- a/geode-docs/tools_modules/http_session_mgmt/tc_additional_info.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/tc_additional_info.html.md.erb @@ -45,7 +45,7 @@ To acquire <%=vars.product_name%> module version information, look in the web se ``` pre INFO: Initializing <%=vars.product_name%> Modules Java version: 1.0.0 user1 041216 2016-11-12 11:18:37 -0700 - javac 1.<%=vars.min_java_version%>.0_<%=vars.min_java_update%> + javac <%=vars.min_java_version%>.0.<%=vars.min_java_update%> Native version: native code unavailable Source revision: 857bb75916640a066eb832b43b3c805f0dd7ed0b Source repository: develop diff --git a/geode-docs/tools_modules/http_session_mgmt/tc_setting_up_the_module.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/tc_setting_up_the_module.html.md.erb index 68802881d350..4757397cad29 100644 --- a/geode-docs/tools_modules/http_session_mgmt/tc_setting_up_the_module.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/tc_setting_up_the_module.html.md.erb @@ -71,8 +71,8 @@ With a similar environment to this example that is for a client/server set up, ``` pre TC_VER=tomcat-8.0.30.C.RELEASE INSTANCE=geode-cs -CLASSPATH=$PWD/$INSTANCE/lib/geode-modules-1.0.0.jar:\ -$PWD/$INSTANCE/lib/geode-modules-tomcat8-1.0.0.jar:\ +CLASSPATH=$PWD/$INSTANCE/lib/geode-modules-2.0.0.jar:\ +$PWD/$INSTANCE/lib/geode-modules-tomcat8-2.0.0.jar:\ $PWD/$TC_VER/lib/servlet-api.jar:\ $PWD/$TC_VER/lib/catalina.jar:\ $PWD/$TC_VER/lib/tomcat-util.jar:\ @@ -111,7 +111,7 @@ lifecycleEvent INFO: Initializing <%=vars.product_name%> Modules Modules version: 1.0.0 Java version: 1.0.0 user1 032916 2016-11-29 07:49:26 -0700 -javac 1.<%=vars.min_java_version%>.0_<%=vars.min_java_update%> +javac <%=vars.min_java_version%>.0.<%=vars.min_java_update%> Native version: native code unavailable Source revision: c36591b73243c7ee3a0186710338453d12efe364 Source repository: develop diff --git a/geode-docs/tools_modules/http_session_mgmt/tomcat_changing_gf_default_cfg.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/tomcat_changing_gf_default_cfg.html.md.erb index 418671d82894..633e4f335b96 100644 --- a/geode-docs/tools_modules/http_session_mgmt/tomcat_changing_gf_default_cfg.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/tomcat_changing_gf_default_cfg.html.md.erb @@ -82,7 +82,7 @@ To edit <%=vars.product_name%> cache properties such as the name and the charact ``` pre ``` +**Note:** For Tomcat 10.1 and later, use `Tomcat10DeltaSessionManager`. Support for Tomcat 7, 8, and 9 has been discontinued. + The following parameters are the cache configuration parameters that can be added to Tomcat's `context.xml` file.

      **CommitSessionValve**
      diff --git a/geode-docs/tools_modules/http_session_mgmt/tomcat_installing_the_module.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/tomcat_installing_the_module.html.md.erb index 9590dff4ba5d..79ef6bcea6b6 100644 --- a/geode-docs/tools_modules/http_session_mgmt/tomcat_installing_the_module.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/tomcat_installing_the_module.html.md.erb @@ -21,12 +21,12 @@ limitations under the License. This topic describes how to install the HTTP session management module for Tomcat. -1. If you have not already installed Tomcat, download the desired version from the [Apache Website](http://tomcat.apache.org/) and install it. +1. If you have not already installed Tomcat, download version 10.1 or later from the [Apache Website](http://tomcat.apache.org/) and install it. **Note:** Geode only supports Tomcat 10.1 and later versions (Jakarta EE). Support for Tomcat 7, 8, and 9 has been discontinued. 2. Following the Apache Tomcat convention, this page assumes the CATALINA_HOME environment variable is set to the root directory of the "binary" Tomcat distribution. - For example, if Apache Tomcat is installed in `/usr/bin/apache-tomcat-9.0.62` then + For example, if Apache Tomcat is installed in `/opt/apache-tomcat-10.1.30` then ``` - CATALINA_HOME=/usr/bin/apache-tomcat-9.0.62 + CATALINA_HOME=/opt/apache-tomcat-10.1.30 ``` Define $CATALINA_HOME if it is not already defined. @@ -48,12 +48,15 @@ This adds jar files to the `lib` subdirectory and XML files to the `conf` subdir unzip $GEODE_HOME/tools/Modules/Apache_Geode_Modules-SERVER-VERSION-Tomcat.zip ``` - -6. Copy all of the jar files from the <%=vars.product_name%> `lib` subdirectory to the `lib` subdirectory of your Tomcat server (`$CATALINA_HOME/lib`): +6. **CRITICAL:** Copy all of the jar files from the <%=vars.product_name%> `lib` subdirectory to the `lib` subdirectory of your Tomcat server (`$CATALINA_HOME/lib`). + + **The module zip file alone does not contain all required dependencies.** You must copy all Geode libraries including `geode-core`, `geode-common`, Jakarta Transaction API, and other runtime dependencies: ``` cd $CATALINA_HOME/lib cp $GEODE_HOME/lib/*.jar . ``` + + **Note:** Without these libraries, Tomcat will fail to start with `ClassNotFoundException` errors for Geode classes. The Geode session management module requires the complete Geode runtime, not just the module JARs included in the zip file. Proceed to [Setting Up the HTTP Module for Tomcat](./tomcat_setting_up_the_module.html) to complete your Tomcat configuration. diff --git a/geode-docs/tools_modules/http_session_mgmt/tomcat_setting_up_the_module.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/tomcat_setting_up_the_module.html.md.erb index 1698795c3edb..0524c37c7514 100644 --- a/geode-docs/tools_modules/http_session_mgmt/tomcat_setting_up_the_module.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/tomcat_setting_up_the_module.html.md.erb @@ -27,32 +27,37 @@ Configuration is slightly different depending on the topology you are setting up -To run <%=vars.product_name%> in a peer-to-peer configuration, add the following line to Tomcat's `$CATALINA_HOME$/conf/server.xml` within the `` tag: +To run <%=vars.product_name%> in a peer-to-peer configuration, you must first start a <%=vars.product_name%> locator, then configure Tomcat to join the cluster as a peer member. + +### Starting the Locator + +Start a <%=vars.product_name%> locator using `gfsh`: ``` pre - +$ gfsh start locator --name=locator1 --port=10334 ``` -Depending on the version of Tomcat you are using, add one of the following lines to `$CATALINA_HOME$/conf/context.xml` within the `` tag: +The locator coordinates membership in the peer-to-peer cache. -For Tomcat 7.0: +### Configuring Tomcat -``` pre - -``` -For Tomcat 8.0 and 8.5: +Add the following line to Tomcat's `$CATALINA_HOME$/conf/server.xml` within the `` tag: ``` pre - + ``` -For Tomcat 9.0: +Add the following line to `$CATALINA_HOME$/conf/context.xml` within the `` tag: + +For Tomcat 10.1 and later (Jakarta EE 10): ``` pre - + ``` +**Note:** Tomcat 10.1+ implements Jakarta EE 10 with Servlet 6.0 specification and uses the Jakarta EE namespace (`jakarta.servlet.*`) instead of the legacy `javax.servlet.*` namespace. Ensure your application has been migrated to Jakarta EE 10 before using this module. Support for Tomcat 7, 8, and 9 has been discontinued. + ## Client/Server Setup @@ -63,41 +68,37 @@ To run <%=vars.product_name%> in a client/server configuration, the application ``` -Depending on the version of Tomcat you are using, add one of the following lines to `$CATALINA_HOME$/conf/context.xml` within the `` tag: +Add the following line to `$CATALINA_HOME$/conf/context.xml` within the `` tag: -For Tomcat 7.0: +For Tomcat 10.1 and later (Jakarta EE 10): ``` pre - + ``` -For Tomcat 8.0 and 8.5: +**Note:** Tomcat 10.1+ implements Jakarta EE 10 with Servlet 6.0 specification and uses the Jakarta EE namespace (`jakarta.servlet.*`) instead of the legacy `javax.servlet.*` namespace. Ensure your application has been migrated to Jakarta EE 10 before using this module. Support for Tomcat 7, 8, and 9 has been discontinued. -``` pre - -``` +The application server operates as a <%=vars.product_name%> client in this configuration. -For Tomcat 9.0: +### Setting the CLASSPATH + +Set the CLASSPATH environment variable to include Tomcat and <%=vars.product_name%> module libraries. This CLASSPATH is required when starting the locator and server. + +For a client/server setup using Apache Tomcat v10.1+ and Geode v2.x, the CLASSPATH should include: ``` pre - +export CLASSPATH=$CATALINA_HOME/lib/servlet-api.jar:$CATALINA_HOME/lib/catalina.jar:$CATALINA_HOME/bin/tomcat-juli.jar:$GEODE_HOME/lib/geode-modules-2.x.x.jar:$GEODE_HOME/lib/geode-modules-tomcat10-2.x.x.jar ``` -The application server operates as a <%=vars.product_name%> client in this configuration. - -Set the CLASSPATH environment variable. For a client/server set up using Apache Tomcat v9 and Geode v1.13, -the CLASSPATH setting should be similar to the following. Adjust filenames and version numbers as needed for your implementation. +Example with explicit paths: ``` pre -CLASSPATH="$CATALINA_HOME/lib/geode-modules-1.13.3.jar:\ -$CATALINA_HOME/lib/geode-modules-tomcat9-1.13.3.jar:\ -$CATALINA_HOME/lib/servlet-api.jar:\ -$CATALINA_HOME/lib/catalina.jar:\ -$CATALINA_HOME/lib/tomcat-util.jar:\ -$CATALINA_HOME/bin/tomcat-juli.jar" +CLASSPATH=/opt/apache-tomcat-10.1.x/lib/servlet-api.jar:/opt/apache-tomcat-10.1.x/lib/catalina.jar:/opt/apache-tomcat-10.1.x/bin/tomcat-juli.jar:/opt/geode-2.x/lib/geode-modules-2.x.x.jar:/opt/geode-2.x/lib/geode-modules-tomcat10-2.x.x.jar ``` -Start the locator and server using `gfsh`: +### Starting the Locator and Server + +Start the locator and server using `gfsh` with the configured CLASSPATH: ``` pre $ gfsh start locator --name=locator1 --classpath=$CLASSPATH @@ -107,7 +108,7 @@ $ gfsh start server --name=server1 --locators=localhost[10334] --server-port=0 \ ## Starting the Application Server -Once you've updated the configuration, you are now ready to start your tc Server or Tomcat instance. Refer to your application server documentation for starting the application server. Once started, <%=vars.product_name%> will automatically launch within the application server process. +Once you've updated the XML configuration files, you are now ready to start your Tomcat instance. Refer to your application server documentation for starting the application server. Once started, <%=vars.product_name%> will automatically launch within the application server process. **Note:** <%=vars.product_name%> session state management provides its own clustering functionality. If you are using <%=vars.product_name%>, you should NOT turn on Tomcat clustering as well. @@ -117,8 +118,65 @@ Once you've updated the configuration, you are now ready to start your tc Server You can verify that <%=vars.product_name%> has successfully started by inspecting the Tomcat log file. For example: ``` pre -15-Jul-2021 10:25:11.483 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/user/workspace/apache-tomcat-9.0.62/webapps/host-manager] has finished in [1,688] ms -15-Jul-2021 10:25:11.486 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] -15-Jul-2021 10:25:11.493 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [11682] milliseconds +15-Jul-2025 10:25:11.483 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/Users/user/workspace/apache-tomcat-10.1.x/webapps/host-manager] has finished in [1,688] ms +15-Jul-2025 10:25:11.486 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] +15-Jul-2025 10:25:11.493 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [11682] milliseconds +``` + +### Verifying Cluster Topology + +You can verify the cluster configuration by using `gfsh` to list cluster members. + +**For Peer-to-Peer Configuration:** + +``` pre +$ gfsh -e "connect --locator=localhost[10334]" -e "list members" +``` + +You should see two members: the locator and the Tomcat server. The Tomcat server appears as a full member of the <%=vars.product_name%> distributed system. + +``` pre +Member Count : 2 + + Name | Id +----------- | ------------------------------------- +locator1 | 192.168.1.100(locator1:12345:locator) +TomcatNode | 192.168.1.100(67890) +``` + +**For Client/Server Configuration:** + +``` pre +$ gfsh -e "connect --locator=localhost[10334]" -e "list members" +``` + +You should see two members: the locator and the cache server. The Tomcat server does NOT appear in the member list because it operates as a lightweight client. + +``` pre +Member Count : 2 + + Name | Id +----------- | ------------------------------------- +locator1 | 192.168.1.100(locator1:12345:locator) +server1 | 192.168.1.100(server1:67890) ``` +## Troubleshooting + +**Problem:** Tomcat logs show `ClassNotFoundException: org.apache.geode.modules.util.BootstrappingFunction` (client/server only) + +**Solution:** Ensure you started the locator and server with the `--classpath` option as shown in the client/server configuration. The <%=vars.product_name%> server must have access to the session module classes. + +--- + +**Problem:** Tomcat fails with "Connection refused" when connecting to locator (peer-to-peer only) + +**Solution:** Ensure the <%=vars.product_name%> locator is running before starting Tomcat. Use `gfsh list members` or `lsof -i :10334` to verify the locator is listening on the configured port. + +--- + +**Problem:** Web applications fail to deploy with session manager errors + +**Solution:** Check that you completed all installation steps, including copying all JAR files from `$GEODE_HOME/lib` to `$CATALINA_HOME/lib` as described in [Installing the HTTP Module for Tomcat](./tomcat_installing_the_module.html). + + diff --git a/geode-docs/tools_modules/http_session_mgmt/weblogic_setting_up_the_module.html.md.erb b/geode-docs/tools_modules/http_session_mgmt/weblogic_setting_up_the_module.html.md.erb index 084db231743b..cf516e18b68b 100644 --- a/geode-docs/tools_modules/http_session_mgmt/weblogic_setting_up_the_module.html.md.erb +++ b/geode-docs/tools_modules/http_session_mgmt/weblogic_setting_up_the_module.html.md.erb @@ -73,7 +73,7 @@ To modify your war or ear file manually, make the following updates: - geode-serialization jar - geode-membership jar - geode-tcp-server jar - - javax.transaction-api jar + - jakarta.transaction-api jar - jgroups jar - log4j-api jar - log4j-core jar @@ -89,28 +89,28 @@ If you are deploying an ear file: ``` pre Manifest-Version: 1.0 Built-By: joe - Build-Jdk: 1.8.0_77 + Build-Jdk: 17.0.16 Created-By: Apache Maven Archiver-Version: Plexus Archiver - Class-Path: lib/geode-modules-1.0.0.jar - lib/geode-modules-session-internal-1.0.0.jar - lib/geode-modules-session-1.0.0.jar - lib/slf4j-api-1.7.7.jar - lib/slf4j-jdk14-1.7.7.jar + Class-Path: lib/geode-modules-2.0.0.jar + lib/geode-modules-session-internal-2.0.0.jar + lib/geode-modules-session-2.0.0.jar + lib/slf4j-api-2.0.17.jar + lib/slf4j-jdk14-2.0.17.jar lib/antlr-2.7.7.jar - lib/geode-membership.1.0.0.jar - lib/geode-tcp.1.0.0.jar - lib/fastutil-7.0.2.jar - lib/geode-core-1.0.0.jar - lib/geode-common.1.0.0.jar - lib/geode-management.1.0.0.jar - lib/geode-logging.1.0.0.jar - lib/geode-serialization.1.0.0.jar - lib/javax.transaction-api-1.3.jar - lib/jgroups-3.6.8.Final.jar - lib/log4j-api-2.5.jar - lib/log4j-core-2.5.jar - lib/log4j-jul-2.5.jar + lib/geode-membership-2.0.0.jar + lib/geode-tcp-server-2.0.0.jar + lib/fastutil-8.5.8.jar + lib/geode-core-2.0.0.jar + lib/geode-common-2.0.0.jar + lib/geode-management-2.0.0.jar + lib/geode-logging-2.0.0.jar + lib/geode-serialization-2.0.0.jar + lib/jakarta.transaction-api-2.0.1.jar + lib/jgroups-3.6.20.Final.jar + lib/log4j-api-2.25.4.jar + lib/log4j-core-2.25.4.jar + lib/log4j-jul-2.25.4.jar ``` ## Peer-to-Peer Setup @@ -182,8 +182,8 @@ $ gfsh start server \ --name=server1 \ --server-port=0 \ --locators=localhost[10334] \ - --classpath=/lib/geode-modules-1.0.0.jar:\ -/lib/geode-modules-session-internal-1.0.0.jar + --classpath=/lib/geode-modules-2.0.0.jar:\ +/lib/geode-modules-session-internal-2.0.0.jar ``` Once the application server is started, the <%=vars.product_name%> client will automatically launch within the application server process. @@ -193,10 +193,10 @@ Once the application server is started, the <%=vars.product_name%> client will a You can verify that <%=vars.product_name%> has successfully started by inspecting the application server log file. For example: ``` pre -info 2016/04/18 10:04:18.685 PDT tid=0x1a] +info 2025/04/18 10:04:18.685 PDT tid=0x1a] Initializing <%=vars.product_name%> Modules -Java version: 1.0.0 user1 041816 2016-11-18 08:46:17 -0700 -javac 1.<%=vars.min_java_version%>.0_<%=vars.min_java_update%> +Java version: 2.0.0 user1 041816 2025-11-18 08:46:17 -0700 +javac <%=vars.min_java_version%>.0.<%=vars.min_java_update%> Native version: native code unavailable Source revision: 19dd8eb1907e0beb2aa3e0a17d5f12c6cbec6968 Source repository: develop diff --git a/geode-dunit/src/test/resources/expected-pom.xml b/geode-dunit/src/test/resources/expected-pom.xml index d33bf896f47f..e79012b8a427 100644 --- a/geode-dunit/src/test/resources/expected-pom.xml +++ b/geode-dunit/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + geode-core * @@ -70,6 +74,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -81,6 +89,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -92,6 +104,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -103,6 +119,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -114,6 +134,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -125,6 +149,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -136,6 +164,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -147,6 +179,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + geode-core * @@ -162,6 +198,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -173,6 +213,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -184,6 +228,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -195,6 +243,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -206,6 +258,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -217,6 +273,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + cglib * @@ -257,6 +317,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -268,6 +332,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + junit-dep * @@ -283,6 +351,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -294,6 +366,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -305,6 +381,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -316,6 +396,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -327,6 +411,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + hamcrest * diff --git a/geode-gfsh/build.gradle b/geode-gfsh/build.gradle index 3c814b10e02c..f731cfff12e9 100644 --- a/geode-gfsh/build.gradle +++ b/geode-gfsh/build.gradle @@ -40,6 +40,9 @@ dependencies { implementation('com.fasterxml.jackson.core:jackson-databind') implementation('io.swagger.core.v3:swagger-annotations') + runtimeOnly('org.jline:jline-terminal-jni') + runtimeOnly('org.jline:jline-terminal-ffm') + // JAXB dependencies needed for Java 11+ implementation('jakarta.xml.bind:jakarta.xml.bind-api') runtimeOnly('org.glassfish.jaxb:jaxb-runtime') diff --git a/geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPermissionsDUnitTest.java b/geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPermissionsDUnitTest.java new file mode 100644 index 000000000000..92324e04b34b --- /dev/null +++ b/geode-gfsh/src/distributedTest/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPermissionsDUnitTest.java @@ -0,0 +1,227 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER; +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.examples.SimpleSecurityManager; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.management.internal.security.ResourceConstants; +import org.apache.geode.test.dunit.IgnoredException; +import org.apache.geode.test.dunit.rules.ClusterStartupRule; +import org.apache.geode.test.dunit.rules.MemberVM; +import org.apache.geode.test.junit.categories.SecurityTest; +import org.apache.geode.test.junit.rules.GfshCommandRule; + +/** + * Tests which principals may run {@code export data} in a secured cluster. + * + *

      + * {@link SimpleSecurityManager} authorizes a user for exactly those permissions whose string form + * starts with the user name, and treats a comma separated user name as a set of roles. So + * "dataRead" holds DATA:READ alone, while "dataRead,clusterWrite" is the operator {@code + * export data} requires. + */ +@Category(SecurityTest.class) +public class ExportDataCommandPermissionsDUnitTest implements Serializable { + + private static final String REGION_NAME = "testRegion"; + private static final String READ_ONLY_USER = "dataRead"; + private static final String EXPORT_OPERATOR = "dataRead,clusterWrite"; + + @ClassRule + public static ClusterStartupRule cluster = new ClusterStartupRule(); + + @Rule + public GfshCommandRule gfsh = new GfshCommandRule(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private static MemberVM locator; + private static MemberVM server; + + /** The directory the server has been configured to permit exports into. */ + private Path permittedDir; + + /** Any other location on the server host. */ + private Path otherDir; + + @BeforeClass + public static void beforeClass() { + Properties locatorProps = new Properties(); + locatorProps.setProperty(SECURITY_MANAGER, SimpleSecurityManager.class.getName()); + locator = cluster.startLocatorVM(0, locatorProps); + + Properties serverProps = new Properties(); + serverProps.setProperty(ResourceConstants.USER_NAME, "clusterManage"); + serverProps.setProperty(ResourceConstants.PASSWORD, "clusterManage"); + server = cluster.startServerVM(1, serverProps, locator.getPort()); + + server.invoke(() -> { + InternalCache cache = ClusterStartupRule.getCache(); + assertThat(cache).isNotNull(); + cache.createRegionFactory(RegionShortcut.REPLICATE).create(REGION_NAME).put("key", "value"); + }); + } + + @Before + public void configurePermittedExportDirectory() throws Exception { + // Refusing an export is logged at error level on the member; that is the expected outcome of + // most of these tests, not a symptom of one going wrong. + IgnoredException.addIgnoredException("Cannot export to"); + + permittedDir = temporaryFolder.newFolder("permitted").toPath(); + otherDir = temporaryFolder.newFolder("other").toPath(); + + String permitted = permittedDir.toString(); + server.invoke(() -> System.setProperty(EXPORT_DATA_DIRS_PROPERTY, permitted)); + } + + @After + public void clearPermittedExportDirectory() { + server.invoke(() -> System.clearProperty(EXPORT_DATA_DIRS_PROPERTY)); + } + + private void connectAs(String user) throws Exception { + gfsh.secureConnectAndVerify(locator.getPort(), GfshCommandRule.PortType.locator, user, user); + } + + private String exportTo(String option, Path path) { + return "export data --member=" + server.getName() + " --region=" + REGION_NAME + " --" + + option + "=" + path; + } + + /** + * Read access to region data on its own does not permit an export. + */ + @Test + public void dataReadUserCannotExport() throws Exception { + connectAs(READ_ONLY_USER); + Path target = permittedDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(exportTo("file", target)) + .statusIsError() + .containsOutput("not authorized for CLUSTER:WRITE"); + + assertThat(target).doesNotExist(); + } + + /** + * Permissions are checked before the path, so the target directory makes no difference. + */ + @Test + public void dataReadUserIsRefusedForAnyDirectory() throws Exception { + connectAs(READ_ONLY_USER); + Path target = otherDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(exportTo("file", target)) + .statusIsError() + .containsOutput("not authorized for CLUSTER:WRITE"); + + assertThat(target).doesNotExist(); + } + + /** + * The command works for a principal holding both permissions. + */ + @Test + public void operatorWithClusterWriteCanExportIntoThePermittedDirectory() throws Exception { + connectAs(EXPORT_OPERATOR); + Path target = permittedDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(exportTo("file", target)) + .statusIsSuccess() + .containsOutput("Data successfully exported"); + + assertThat(target).exists(); + } + + /** + * The directory restriction applies independently of the permission: even a permitted operator + * cannot place the snapshot anywhere it likes. + */ + @Test + public void operatorCannotExportOutsideThePermittedDirectory() throws Exception { + connectAs(EXPORT_OPERATOR); + Path target = otherDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(exportTo("file", target)).statusIsError(); + + assertThat(target).doesNotExist(); + } + + /** + * Nor can the operator leave the permitted directory with "../". + */ + @Test + public void operatorCannotLeavePermittedDirectoryWithParentReference() throws Exception { + connectAs(EXPORT_OPERATOR); + Path withParentReference = permittedDir.resolve("..").resolve("other"); + + gfsh.executeAndAssertThat(exportTo("dir", withParentReference)).statusIsError(); + + assertThat(otherDir.resolve(REGION_NAME + ".gfd")).doesNotExist(); + } + + /** + * An existing file outside the permitted directory survives an export aimed at it. + */ + @Test + public void existingFileOutsideThePermittedDirectoryIsNotOverwritten() throws Exception { + connectAs(EXPORT_OPERATOR); + Path existingFile = otherDir.resolve("existing.gfd"); + String originalContent = "existing content"; + Files.write(existingFile, originalContent.getBytes(StandardCharsets.UTF_8)); + + gfsh.executeAndAssertThat(exportTo("file", existingFile)).statusIsError(); + + assertThat(new String(Files.readAllBytes(existingFile), StandardCharsets.UTF_8)) + .isEqualTo(originalContent); + } + + /** + * Confirms the read only grant really is read only, so the refusals above are the permission + * check taking effect rather than a misconfigured principal. + */ + @Test + public void readOnlyUserCanStillReadData() throws Exception { + connectAs(READ_ONLY_USER); + + gfsh.executeAndAssertThat("get --region=" + REGION_NAME + " --key=key").statusIsSuccess(); + gfsh.executeAndAssertThat("put --region=" + REGION_NAME + " --key=k --value=v") + .statusIsError() + .containsOutput("dataRead not authorized for DATA:WRITE"); + } +} diff --git a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java index 80082f15f167..86e800d56058 100644 --- a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java +++ b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataIntegrationTest.java @@ -17,6 +17,7 @@ package org.apache.geode.management.internal.cli.commands; import static org.apache.geode.cache.Region.SEPARATOR; +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertFalse; @@ -31,6 +32,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TemporaryFolder; import org.apache.geode.DataSerializable; @@ -58,6 +60,9 @@ public class ExportDataIntegrationTest { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + private Region region; private Path snapshotFile; private Path snapshotDir; @@ -87,6 +92,8 @@ public void setup() throws Exception { region = server.getCache().getRegion(TEST_REGION_NAME); loadRegion("value"); Path basePath = tempDir.getRoot().toPath(); + // configure the test's temporary folder as an export destination + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, basePath.toString()); snapshotFile = basePath.resolve(SNAPSHOT_FILE); snapshotDir = basePath.resolve(SNAPSHOT_DIR); } diff --git a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataPathValidationIntegrationTest.java b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataPathValidationIntegrationTest.java new file mode 100644 index 000000000000..6b6add92a128 --- /dev/null +++ b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ExportDataPathValidationIntegrationTest.java @@ -0,0 +1,211 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.IntStream; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.rules.TemporaryFolder; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.RegionShortcut; +import org.apache.geode.management.internal.cli.util.CommandStringBuilder; +import org.apache.geode.management.internal.i18n.CliStrings; +import org.apache.geode.test.junit.rules.GfshCommandRule; +import org.apache.geode.test.junit.rules.ServerStarterRule; + +/** + * End to end tests of the directories {@code export data} writes into: a live server exports into + * the directories it is configured to permit, and refuses paths that resolve outside them. + */ +public class ExportDataPathValidationIntegrationTest { + private static final String TEST_REGION_NAME = "testRegion"; + private static final int DATA_POINTS = 10; + + @ClassRule + public static ServerStarterRule server = new ServerStarterRule().withJMXManager() + .withRegion(RegionShortcut.PARTITION, TEST_REGION_NAME).withEmbeddedLocator(); + + @Rule + public GfshCommandRule gfsh = new GfshCommandRule(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + /** The directory an operator has permitted this member to export into. */ + private Path permittedDir; + + /** Any other location on the server host. */ + private Path otherDir; + + private Region region; + + @Before + public void setup() throws Exception { + gfsh.connectAndVerify(server.getEmbeddedLocatorPort(), GfshCommandRule.PortType.locator); + region = server.getCache().getRegion(TEST_REGION_NAME); + IntStream.range(0, DATA_POINTS).forEach(i -> region.put("key" + i, "value" + i)); + + permittedDir = temporaryFolder.newFolder("permitted").toPath(); + otherDir = temporaryFolder.newFolder("other").toPath(); + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, permittedDir.toString()); + } + + /** The gfsh table wraps long messages, so compare against whitespace normalized output. */ + private String normalizedOutput() { + return gfsh.getGfshOutput().replaceAll("\\s+", " "); + } + + /** + * Exports into a permitted directory work normally. + */ + @Test + public void exportIntoThePermittedDirectorySucceeds() { + Path target = permittedDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__FILE, target.toString()) + .getCommandString()).statusIsSuccess(); + + assertThat(target).exists(); + assertThat(target.toFile().length()).isGreaterThan(0L); + } + + /** The --dir form works the same way. */ + @Test + public void exportIntoThePermittedDirectoryWithDirOptionSucceeds() { + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__DIR, permittedDir.toString()) + .getCommandString()).statusIsSuccess(); + + assertThat(permittedDir.resolve(TEST_REGION_NAME + ".gfd")).exists(); + } + + /** + * An absolute path outside the permitted directories does not produce a file. + */ + @Test + public void exportToAnAbsolutePathOutsideThePermittedDirectoryIsRefused() { + Path target = otherDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__FILE, target.toString()) + .getCommandString()).statusIsError(); + + assertThat(normalizedOutput()).contains("export directories configured for this member"); + assertThat(target).doesNotExist(); + } + + /** + * A "../" in --dir is refused, and nothing is written at the location it points to. + */ + @Test + public void exportWithParentReferenceInDirOptionIsRefused() { + String dirWithParentReference = permittedDir.resolve("..").resolve("other").toString(); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__DIR, dirWithParentReference) + .getCommandString()).statusIsError(); + + assertThat(normalizedOutput()).contains("path segment"); + assertThat(otherDir.resolve(TEST_REGION_NAME + ".gfd")).doesNotExist(); + } + + /** + * The same for --file: it is caught before the export is sent to the member. + */ + @Test + public void exportWithParentReferenceInFileOptionIsRefused() { + String fileWithParentReference = permittedDir.resolve("..").resolve("other") + .resolve("snapshot.gfd").toString(); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__FILE, fileWithParentReference) + .getCommandString()).statusIsError(); + + assertThat(normalizedOutput()).contains("path segment"); + assertThat(otherDir.resolve("snapshot.gfd")).doesNotExist(); + } + + /** + * An existing file outside the permitted directories keeps its contents. + */ + @Test + public void existingFileOutsideThePermittedDirectoryIsNotOverwritten() throws Exception { + Path existingFile = otherDir.resolve("existing.gfd"); + String originalContent = "existing content"; + Files.write(existingFile, originalContent.getBytes(StandardCharsets.UTF_8)); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__FILE, existingFile.toString()) + .getCommandString()).statusIsError(); + + assertThat(new String(Files.readAllBytes(existingFile), StandardCharsets.UTF_8)) + .isEqualTo(originalContent); + } + + /** + * A parallel export does not create a directory tree outside the permitted directories. + */ + @Test + public void parallelExportOutsideThePermittedDirectoryCreatesNoDirectories() { + Path newTree = otherDir.resolve("created/by/export"); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__DIR, newTree.toString()) + .addOption(CliStrings.EXPORT_DATA__PARALLEL, "true") + .getCommandString()).statusIsError(); + + assertThat(newTree).doesNotExist(); + } + + /** + * Without configuration the member permits only its own working directory. + */ + @Test + public void withoutConfigurationExportOutsideTheWorkingDirectoryIsRefused() { + System.clearProperty(EXPORT_DATA_DIRS_PROPERTY); + Path target = otherDir.resolve("snapshot.gfd"); + + gfsh.executeAndAssertThat(baseCommand() + .addOption(CliStrings.EXPORT_DATA__FILE, target.toString()) + .getCommandString()).statusIsError(); + + assertThat(target).doesNotExist(); + assertThat(normalizedOutput()) + .contains(new File(System.getProperty("user.dir")).getName()); + } + + private CommandStringBuilder baseCommand() { + return new CommandStringBuilder(CliStrings.EXPORT_DATA) + .addOption(CliStrings.MEMBER, server.getName()) + .addOption(CliStrings.EXPORT_DATA__REGION, TEST_REGION_NAME); + } +} diff --git a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java index 63fb1461bf4c..2317ce847c91 100644 --- a/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java +++ b/geode-gfsh/src/integrationTest/java/org/apache/geode/management/internal/cli/commands/ImportDataIntegrationTest.java @@ -17,6 +17,7 @@ package org.apache.geode.management.internal.cli.commands; import static org.apache.geode.cache.Region.SEPARATOR; +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @@ -30,6 +31,7 @@ import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TemporaryFolder; import org.apache.geode.cache.Region; @@ -55,6 +57,9 @@ public class ImportDataIntegrationTest { @Rule public TemporaryFolder tempDir = new TemporaryFolder(); + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + private Region region; private Path snapshotFile; private Path snapshotDir; @@ -65,6 +70,8 @@ public void setup() throws Exception { region = server.getCache().getRegion(TEST_REGION_NAME); loadRegion("value"); Path basePath = tempDir.getRoot().toPath(); + // configure the test's temporary folder as an export destination + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, basePath.toString()); snapshotFile = basePath.resolve(SNAPSHOT_FILE); snapshotDir = basePath.resolve(SNAPSHOT_DIR); } diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java index 9892ceef5f3d..385d2c3e5242 100644 --- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java +++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/ExportDataCommand.java @@ -16,6 +16,8 @@ package org.apache.geode.management.internal.cli.commands; import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; import java.util.Optional; @@ -33,6 +35,7 @@ import org.apache.geode.management.internal.cli.result.model.ResultModel; import org.apache.geode.management.internal.functions.CliFunctionResult; import org.apache.geode.management.internal.i18n.CliStrings; +import org.apache.geode.security.ResourcePermission; import org.apache.geode.security.ResourcePermission.Operation; import org.apache.geode.security.ResourcePermission.Resource; @@ -54,6 +57,7 @@ public ResultModel exportData( help = CliStrings.EXPORT_DATA__PARALLEL_HELP) boolean parallel) { authorize(Resource.DATA, Operation.READ, regionName); + authorize(Resource.CLUSTER, Operation.WRITE, ResourcePermission.ALL); final DistributedMember targetMember = getMember(memberNameOrId); Optional validationResult = validatePath(filePath, dirPath, parallel); @@ -100,6 +104,28 @@ private Optional validatePath(String filePath, String dirPath, bool return Optional.of(ResultModel.createError(CliStrings.format( CliStrings.INVALID_FILE_EXTENSION, CliStrings.GEODE_DATA_FILE_EXTENSION))); } + + if (filePath != null && containsParentDirectorySegment(filePath)) { + return Optional.of(invalidPathError(CliStrings.EXPORT_DATA__FILE, filePath)); + } + if (dirPath != null && containsParentDirectorySegment(dirPath)) { + return Optional.of(invalidPathError(CliStrings.EXPORT_DATA__DIR, dirPath)); + } + return Optional.empty(); } + + private static boolean containsParentDirectorySegment(String path) { + for (Path element : Paths.get(path)) { + if ("..".equals(element.toString())) { + return true; + } + } + return false; + } + + private static ResultModel invalidPathError(String option, String path) { + return ResultModel.createError(String.format( + "Option \"%s\" must not contain a \"..\" path segment: %s", option, path)); + } } diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/MemberJvmOptions.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/MemberJvmOptions.java index dbfc1ee40043..fd9116cedeff 100644 --- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/MemberJvmOptions.java +++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/commands/MemberJvmOptions.java @@ -28,17 +28,10 @@ import org.apache.geode.distributed.internal.deadlock.UnsafeThreadLocal; import org.apache.geode.internal.offheap.AddressableMemoryManager; -import org.apache.geode.internal.stats50.VMStats50; import org.apache.geode.unsafe.internal.com.sun.jmx.remote.security.MBeanServerAccessController; -import org.apache.geode.unsafe.internal.sun.nio.ch.DirectBuffer; public class MemberJvmOptions { static final int CMS_INITIAL_OCCUPANCY_FRACTION = 60; - /** - * export needed by {@link DirectBuffer} - */ - private static final String SUN_NIO_CH_EXPORT = - "--add-exports=java.base/sun.nio.ch=ALL-UNNAMED"; /** * export needed by {@link MBeanServerAccessController} */ @@ -52,16 +45,9 @@ public class MemberJvmOptions { * open needed by {@link AddressableMemoryManager} */ private static final String JAVA_NIO_OPEN = "--add-opens=java.base/java.nio=ALL-UNNAMED"; - /** - * open needed by {@link VMStats50} - */ - private static final String COM_SUN_MANAGEMENT_INTERNAL_OPEN = - "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED"; static final List JAVA_11_OPTIONS = Arrays.asList( COM_SUN_JMX_REMOTE_SECURITY_EXPORT, - SUN_NIO_CH_EXPORT, - COM_SUN_MANAGEMENT_INTERNAL_OPEN, JAVA_LANG_OPEN, JAVA_NIO_OPEN); diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java index 0c83d40a8ae6..2f0a18174721 100644 --- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java +++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/cli/functions/ExportDataFunction.java @@ -15,6 +15,9 @@ package org.apache.geode.management.internal.cli.functions; import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import org.apache.geode.cache.Cache; import org.apache.geode.cache.Region; @@ -27,12 +30,15 @@ import org.apache.geode.management.cli.CliFunction; import org.apache.geode.management.internal.functions.CliFunctionResult; import org.apache.geode.management.internal.i18n.CliStrings; +import org.apache.geode.util.internal.GeodeGlossary; /*** * Function which carries out the export of a region to a file on a member. Uses the * RegionSnapshotService to export the data * - * + *

      + * Export destinations are resolved to their canonical form and must be within the export + * directories configured for this member. */ public class ExportDataFunction extends CliFunction { private static final long serialVersionUID = 1L; @@ -40,6 +46,18 @@ public class ExportDataFunction extends CliFunction { private static final String ID = "org.apache.geode.management.internal.cli.functions.ExportDataFunction"; + /** + * System property naming additional directories this member writes {@code export data} snapshots + * into. Several directories may be listed, separated by {@link File#pathSeparator}. Exports into + * sub-directories of a configured directory are included. + * + *

      + * The member's working directory is always configured, since that is where a relative export + * path resolves to, so when this property is not set it is the only export destination. + */ + public static final String EXPORT_DATA_DIRS_PROPERTY = + GeodeGlossary.GEMFIRE_PREFIX + "export.data.dirs"; + @Override public String getId() { return ID; @@ -62,7 +80,7 @@ public CliFunctionResult executeFunction(FunctionContext context) thro String hostName = cache.getDistributedSystem().getDistributedMember().getHost(); if (region != null) { RegionSnapshotService snapshotService = region.getSnapshotService(); - final File exportFile = new File(fileName); + final File exportFile = resolveExportFile(fileName); if (parallel) { SnapshotOptions options = new SnapshotOptionsImpl<>().setParallelMode(true); snapshotService.save(exportFile, SnapshotFormat.GEODE, options); @@ -81,4 +99,42 @@ public CliFunctionResult executeFunction(FunctionContext context) thro return result; } + + /** + * Resolves the requested export path against the export directories configured for this member. + * + * @param fileName the path requested by the caller, which may be relative or absolute + * @return the canonical file to export to + * @throws IllegalArgumentException if the path is not within a configured export directory + */ + static File resolveExportFile(String fileName) throws IOException { + File exportFile = new File(fileName).getCanonicalFile(); + List exportDirs = configuredExportDirs(); + + for (File exportDir : exportDirs) { + if (exportFile.toPath().startsWith(exportDir.toPath())) { + return exportFile; + } + } + + throw new IllegalArgumentException(String.format( + "Cannot export to %s: the path is not within the export directories configured for this member (%s). Use the %s system property to configure additional directories.", + exportFile, exportDirs, EXPORT_DATA_DIRS_PROPERTY)); + } + + private static List configuredExportDirs() throws IOException { + List exportDirs = new ArrayList<>(); + exportDirs.add(new File(System.getProperty("user.dir")).getCanonicalFile()); + + String configuredDirs = System.getProperty(EXPORT_DATA_DIRS_PROPERTY); + if (configuredDirs != null) { + for (String configuredDir : configuredDirs.split(File.pathSeparator)) { + if (!configuredDir.trim().isEmpty()) { + exportDirs.add(new File(configuredDir.trim()).getCanonicalFile()); + } + } + } + + return exportDirs; + } } diff --git a/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java b/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java index bc70f5f4be64..c81202f1f82f 100644 --- a/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java +++ b/geode-gfsh/src/main/java/org/apache/geode/management/internal/web/shell/HttpOperationInvoker.java @@ -17,6 +17,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.util.Base64; import java.util.Properties; import java.util.Set; import java.util.concurrent.Executors; @@ -40,7 +41,6 @@ import org.apache.geode.management.internal.cli.CommandRequest; import org.apache.geode.management.internal.cli.shell.Gfsh; import org.apache.geode.management.internal.cli.shell.OperationInvoker; -import org.apache.geode.management.internal.web.domain.QueryParameterSource; import org.apache.geode.management.internal.web.http.support.HttpRequester; import org.apache.geode.management.internal.web.shell.support.HttpMBeanProxyFactory; @@ -383,8 +383,14 @@ public Object invoke(final String resourceName, final String operationName, fina public Set queryNames(final ObjectName objectName, final QueryExp queryExpression) { final URI link = HttpRequester.createURI(baseUrl, "/mbean/query"); - Object content = new QueryParameterSource(objectName, queryExpression); try { + final MultiValueMap content = new LinkedMultiValueMap(); + content.add("objectName", objectName.toString()); + if (queryExpression != null) { + content.add("queryExpression", + Base64.getEncoder().encodeToString(IOUtils.serializeObject(queryExpression))); + } + return (Set) IOUtils .deserializeObject(httpRequester.post(link, content, byte[].class)); } catch (Exception e) { diff --git a/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPathValidationTest.java b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPathValidationTest.java new file mode 100644 index 000000000000..da3868806414 --- /dev/null +++ b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandPathValidationTest.java @@ -0,0 +1,215 @@ +/* + * 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.geode.management.internal.cli.commands; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.io.File; +import java.util.Collections; + +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import org.apache.geode.cache.execute.ResultCollector; +import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.management.internal.functions.CliFunctionResult; +import org.apache.geode.security.NotAuthorizedException; +import org.apache.geode.security.ResourcePermission; +import org.apache.geode.security.ResourcePermission.Operation; +import org.apache.geode.security.ResourcePermission.Resource; +import org.apache.geode.test.junit.rules.GfshParserRule; + +/** + * Tests the path validation and the authorization {@code export data} applies before it sends any + * work to a member. + * + *

      + * The directory configuration is applied on the member; the command checks the option for a parent + * directory reference and asks for the permissions the operation needs. + * + * @see ExportDataCommandPermissionsDUnitTest for the permissions end to end in a secured + * cluster + */ +public class ExportDataCommandPathValidationTest { + + @ClassRule + public static GfshParserRule parser = new GfshParserRule(); + + private static final String REGION = "testRegion"; + /** + * On this branch the option carries no ConverterHint.REGION_PATH, so the command method sees the + * region name exactly as typed (on support/1.15 the converter prepends the separator). + */ + private static final String REGION_PATH = REGION; + + private ExportDataCommand command; + private ArgumentCaptor functionArgsCaptor; + + @Before + public void before() { + command = spy(ExportDataCommand.class); + + doNothing().when(command).authorize(any(Resource.class), any(Operation.class), anyString()); + doReturn(mock(DistributedMember.class)).when(command).getMember(anyString()); + + CliFunctionResult okResult = + new CliFunctionResult("server1", CliFunctionResult.StatusState.OK, "exported"); + ResultCollector collector = mock(ResultCollector.class); + doReturn(Collections.singletonList(okResult)).when(collector).getResult(); + + functionArgsCaptor = ArgumentCaptor.forClass(Object.class); + doReturn(collector).when(command).executeFunction(any(), functionArgsCaptor.capture(), + any(DistributedMember.class)); + } + + private String capturedExportPath() { + Object args = functionArgsCaptor.getValue(); + assertThat(args).isInstanceOf(String[].class); + return ((String[]) args)[1]; + } + + private void verifyNoExportWasRequested() { + verify(command, never()).executeFunction(any(), any(), any(DistributedMember.class)); + } + + /** + * A "../" element in --file is refused before anything is sent to a member. + */ + @Test + public void parentReferenceInFileOptionIsRejected() { + parser + .executeAndAssertThat(command, "export data --member=server1 --region=" + REGION + + " --file=../../../../var/tmp/snapshot.gfd") + .statusIsError() + .containsOutput("must not contain a \"..\" path segment"); + + verifyNoExportWasRequested(); + } + + /** + * A "../" buried in the middle of an otherwise absolute --file is refused too - the check looks + * at every element of the path, not just its start. + */ + @Test + public void parentReferenceInsideAnAbsoluteFilePathIsRejected() { + parser + .executeAndAssertThat(command, "export data --member=server1 --region=" + REGION + + " --file=/var/tmp/subdir/../../snapshot.gfd") + .statusIsError() + .containsOutput("must not contain a \"..\" path segment"); + + verifyNoExportWasRequested(); + } + + /** + * The --dir option is checked as well, even though its file name is generated rather than + * supplied. + */ + @Test + public void parentReferenceInDirOptionIsRejected() { + parser + .executeAndAssertThat(command, + "export data --member=server1 --region=" + REGION + " --dir=/tmp/subdir/../../var/tmp") + .statusIsError() + .containsOutput("must not contain a \"..\" path segment"); + + verifyNoExportWasRequested(); + } + + /** + * An ordinary path is forwarded unchanged, for the member to resolve. + */ + @Test + public void ordinaryPathIsForwardedToTheMember() { + parser + .executeAndAssertThat(command, + "export data --member=server1 --region=" + REGION + " --file=/var/tmp/snapshot.gfd") + .statusIsSuccess(); + + assertThat(capturedExportPath()).isEqualTo("/var/tmp/snapshot.gfd"); + } + + /** + * Same for --dir, with the generated file name appended. + */ + @Test + public void ordinaryDirectoryIsForwardedToTheMember() { + parser + .executeAndAssertThat(command, + "export data --member=server1 --region=" + REGION + " --dir=/var/tmp") + .statusIsSuccess(); + + assertThat(capturedExportPath()).isEqualTo(new File("/var/tmp", REGION + ".gfd").getPath()); + } + + /** + * The extension check still applies. + */ + @Test + public void fileExtensionIsValidated() { + parser + .executeAndAssertThat(command, + "export data --member=server1 --region=" + REGION + " --file=/var/tmp/snapshot.txt") + .statusIsError() + .containsOutput("Invalid file type, the file extension must be \".gfd\""); + + verifyNoExportWasRequested(); + } + + /** + * Writing a file on a member's host needs a cluster write permission, alongside read access to + * the data being exported. + */ + @Test + public void exportRequiresClusterWriteAndDataRead() { + parser + .executeAndAssertThat(command, + "export data --member=server1 --region=" + REGION + " --file=/var/tmp/snapshot.gfd") + .statusIsSuccess(); + + verify(command).authorize(Resource.DATA, Operation.READ, REGION_PATH); + verify(command).authorize(Resource.CLUSTER, Operation.WRITE, ResourcePermission.ALL); + } + + /** + * Permissions are checked before any work is done, so the export is not sent to the member. + */ + @Test + public void clusterWriteIsCheckedBeforeTheExportIsSent() { + doThrow(new NotAuthorizedException("dataRead not authorized for CLUSTER:WRITE")) + .when(command).authorize(eq(Resource.CLUSTER), eq(Operation.WRITE), anyString()); + + assertThatThrownBy( + () -> command.exportData("server1", REGION_PATH, "/var/tmp/snapshot.gfd", null, false)) + .isInstanceOf(NotAuthorizedException.class) + .hasMessageContaining("CLUSTER:WRITE"); + + verifyNoExportWasRequested(); + } +} diff --git a/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandTest.java b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandTest.java index dd9a1f1e1370..61e8b655499e 100644 --- a/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandTest.java +++ b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/commands/ExportDataCommandTest.java @@ -15,10 +15,18 @@ package org.apache.geode.management.internal.cli.commands; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; +import org.apache.geode.distributed.DistributedMember; import org.apache.geode.test.junit.rules.GfshParserRule; @@ -34,6 +42,14 @@ public void setUp() { command = new ExportDataCommand(); } + /** A command whose option values are checked without contacting a member. */ + private ExportDataCommand commandWithMember() { + ExportDataCommand withMember = spy(ExportDataCommand.class); + doNothing().when(withMember).authorize(any(), any(), anyString()); + doReturn(mock(DistributedMember.class)).when(withMember).getMember(anyString()); + return withMember; + } + @Test public void missingMember() throws Exception { // Command parses successfully but fails during execution because cache is null @@ -41,4 +57,20 @@ public void missingMember() throws Exception { .statusIsError() .containsOutput("cache"); } + + @Test + public void fileOptionWithParentDirectorySegmentIsRejected() { + gfsh.executeAndAssertThat(commandWithMember(), + "export data --member=server1 --region=regionA --file=exports/../regionA.gfd") + .statusIsError() + .containsOutput("must not contain a \"..\" path segment"); + } + + @Test + public void dirOptionWithParentDirectorySegmentIsRejected() { + gfsh.executeAndAssertThat(commandWithMember(), + "export data --member=server1 --region=regionA --dir=exports/../elsewhere") + .statusIsError() + .containsOutput("must not contain a \"..\" path segment"); + } } diff --git a/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataDirectoryConfigTest.java b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataDirectoryConfigTest.java new file mode 100644 index 000000000000..29e790a4f452 --- /dev/null +++ b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataDirectoryConfigTest.java @@ -0,0 +1,169 @@ +/* + * 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.geode.management.internal.cli.functions; + +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * Tests the export directories configured for a member, and which destinations resolve within + * them. + */ +public class ExportDataDirectoryConfigTest { + + private static final String SNAPSHOT = "testRegion.gfd"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private String originalProperty; + private Path configuredDir; + private Path otherDir; + + @Before + public void before() throws Exception { + originalProperty = System.getProperty(EXPORT_DATA_DIRS_PROPERTY); + configuredDir = temporaryFolder.newFolder("exports").toPath().toRealPath(); + otherDir = temporaryFolder.newFolder("elsewhere").toPath().toRealPath(); + configure(configuredDir); + } + + @After + public void after() { + if (originalProperty == null) { + System.clearProperty(EXPORT_DATA_DIRS_PROPERTY); + } else { + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, originalProperty); + } + } + + private void configure(Path... dirs) { + StringBuilder value = new StringBuilder(); + for (Path dir : dirs) { + if (value.length() > 0) { + value.append(File.pathSeparator); + } + value.append(dir); + } + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, value.toString()); + } + + @Test + public void exportIntoConfiguredDirectorySucceeds() throws Exception { + Path destination = configuredDir.resolve(SNAPSHOT); + + File resolved = ExportDataFunction.resolveExportFile(destination.toString()); + + assertThat(resolved.toPath()).isEqualTo(destination); + } + + @Test + public void exportIntoSubdirectoryOfConfiguredDirectorySucceeds() throws Exception { + Path destination = configuredDir.resolve("daily").resolve(SNAPSHOT); + + File resolved = ExportDataFunction.resolveExportFile(destination.toString()); + + assertThat(resolved.toPath()).isEqualTo(destination); + } + + @Test + public void exportOutsideConfiguredDirectoriesIsRejected() { + Path destination = otherDir.resolve(SNAPSHOT); + + assertThatThrownBy(() -> ExportDataFunction.resolveExportFile(destination.toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("not within the export directories configured") + .hasMessageContaining(EXPORT_DATA_DIRS_PROPERTY); + } + + @Test + public void directoryWithMatchingNamePrefixIsNotIncluded() throws Exception { + Path sibling = temporaryFolder.newFolder("exports-archive").toPath().toRealPath(); + Path destination = sibling.resolve(SNAPSHOT); + + assertThatThrownBy(() -> ExportDataFunction.resolveExportFile(destination.toString())) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void severalDirectoriesCanBeConfigured() throws Exception { + configure(configuredDir, otherDir); + + assertThat(ExportDataFunction.resolveExportFile(configuredDir.resolve(SNAPSHOT).toString())) + .isNotNull(); + assertThat(ExportDataFunction.resolveExportFile(otherDir.resolve(SNAPSHOT).toString())) + .isNotNull(); + } + + @Test + public void emptyEntriesInThePropertyAreIgnored() throws Exception { + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, + File.pathSeparator + configuredDir + File.pathSeparator + File.pathSeparator); + + File resolved = + ExportDataFunction.resolveExportFile(configuredDir.resolve(SNAPSHOT).toString()); + + assertThat(resolved.toPath()).isEqualTo(configuredDir.resolve(SNAPSHOT)); + } + + @Test + public void workingDirectoryIsUsedWhenThePropertyIsNotSet() throws Exception { + System.clearProperty(EXPORT_DATA_DIRS_PROPERTY); + Path workingDir = new File(System.getProperty("user.dir")).getCanonicalFile().toPath(); + + File resolved = ExportDataFunction.resolveExportFile(workingDir.resolve(SNAPSHOT).toString()); + + assertThat(resolved.toPath()).isEqualTo(workingDir.resolve(SNAPSHOT)); + assertThatThrownBy(() -> ExportDataFunction.resolveExportFile(otherDir.resolve(SNAPSHOT) + .toString())).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void relativePathResolvesInsideTheWorkingDirectory() throws Exception { + System.clearProperty(EXPORT_DATA_DIRS_PROPERTY); + Path workingDir = new File(System.getProperty("user.dir")).getCanonicalFile().toPath(); + + File resolved = ExportDataFunction.resolveExportFile(SNAPSHOT); + + assertThat(resolved.toPath()).isEqualTo(workingDir.resolve(SNAPSHOT)); + } + + @Test + public void linkedDirectoryResolvesToItsTarget() throws Exception { + Path link = configuredDir.resolve("archive"); + try { + Files.createSymbolicLink(link, otherDir); + } catch (IOException | UnsupportedOperationException e) { + Assume.assumeNoException("filesystem does not support links", e); + } + + assertThatThrownBy(() -> ExportDataFunction.resolveExportFile(link.resolve(SNAPSHOT) + .toString())).isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataFunctionPathValidationTest.java b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataFunctionPathValidationTest.java new file mode 100644 index 000000000000..23a2a89b41fe --- /dev/null +++ b/geode-gfsh/src/test/java/org/apache/geode/management/internal/cli/functions/ExportDataFunctionPathValidationTest.java @@ -0,0 +1,248 @@ +/* + * 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.geode.management.internal.cli.functions; + +import static org.apache.geode.management.internal.cli.functions.ExportDataFunction.EXPORT_DATA_DIRS_PROPERTY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; + +import org.apache.geode.cache.Region; +import org.apache.geode.cache.execute.FunctionContext; +import org.apache.geode.cache.snapshot.RegionSnapshotService; +import org.apache.geode.cache.snapshot.SnapshotOptions.SnapshotFormat; +import org.apache.geode.distributed.internal.InternalDistributedSystem; +import org.apache.geode.distributed.internal.membership.InternalDistributedMember; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.internal.cache.InternalCacheForClientAccess; +import org.apache.geode.management.internal.functions.CliFunctionResult; + +/** + * Tests the directories a member permits {@code export data} to write into. + */ +public class ExportDataFunctionPathValidationTest { + + private static final String REGION = "testRegion"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + private ExportDataFunction function; + private RegionSnapshotService snapshotService; + private FunctionContext context; + + @Before + @SuppressWarnings("unchecked") + public void before() { + function = new ExportDataFunction(); + + snapshotService = mock(RegionSnapshotService.class); + Region region = mock(Region.class); + when(region.getSnapshotService()).thenReturn(snapshotService); + + InternalCacheForClientAccess clientCache = mock(InternalCacheForClientAccess.class); + when(clientCache.getRegion(REGION)).thenReturn(region); + + InternalCache cache = mock(InternalCache.class); + when(cache.getCacheForProcessingClientRequests()).thenReturn(clientCache); + + InternalDistributedMember member = mock(InternalDistributedMember.class); + when(member.getHost()).thenReturn("localhost"); + InternalDistributedSystem system = mock(InternalDistributedSystem.class); + when(system.getDistributedMember()).thenReturn(member); + when(clientCache.getDistributedSystem()).thenReturn(system); + + context = mock(FunctionContext.class); + when(context.getCache()).thenReturn(cache); + when(context.getMemberName()).thenReturn("server1"); + } + + /** Permits exports into the temporary folder, in addition to the working directory. */ + private Path permitTemporaryFolder() { + Path permitted = temporaryFolder.getRoot().toPath(); + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, permitted.toString()); + return permitted; + } + + private CliFunctionResult export(String requestedPath) throws Exception { + when(context.getArguments()) + .thenReturn(new String[] {REGION, requestedPath, Boolean.toString(false)}); + return function.executeFunction(context); + } + + private File captureExportFile() throws Exception { + ArgumentCaptor fileCaptor = ArgumentCaptor.forClass(File.class); + verify(snapshotService).save(fileCaptor.capture(), eq(SnapshotFormat.GEODE)); + return fileCaptor.getValue(); + } + + private void verifyNothingWasWritten() throws Exception { + verify(snapshotService, never()).save(any(File.class), eq(SnapshotFormat.GEODE)); + } + + /** + * An export into a permitted directory works normally. + */ + @Test + public void exportIntoAPermittedDirectorySucceeds() throws Exception { + Path permitted = permitTemporaryFolder(); + + CliFunctionResult result = export(permitted.resolve("snapshot.gfd").toString()); + + assertThat(result.isSuccessful()).isTrue(); + assertThat(captureExportFile().toPath()) + .isEqualTo(permitted.toRealPath().resolve("snapshot.gfd")); + } + + /** Sub-directories of a permitted directory are permitted too. */ + @Test + public void exportIntoASubdirectoryOfAPermittedDirectorySucceeds() throws Exception { + Path permitted = permitTemporaryFolder(); + + CliFunctionResult result = export(permitted.resolve("nested/snapshot.gfd").toString()); + + assertThat(result.isSuccessful()).isTrue(); + } + + /** + * An absolute path outside every permitted directory is refused. + */ + @Test + public void exportToAnAbsolutePathOutsideEveryPermittedDirectoryIsRefused() throws Exception { + permitTemporaryFolder(); + + assertThatThrownBy(() -> export("/var/tmp/snapshot.gfd")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("not within the export directories configured for this member"); + + verifyNothingWasWritten(); + } + + /** + * A "../" element that climbs out of a permitted directory is refused: the path is canonicalized + * before it is compared, so the comparison uses the location actually written to. + */ + @Test + public void parentReferenceOutOfAPermittedDirectoryIsRefused() throws Exception { + Path permitted = permitTemporaryFolder(); + + assertThatThrownBy(() -> export(permitted.resolve("../escaped.gfd").toString())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("not within the export directories configured for this member"); + + verifyNothingWasWritten(); + } + + /** + * A "../" element that stays inside a permitted directory is honoured, and is resolved before + * the write, so no "../" reaches the snapshot service. + */ + @Test + public void parentReferenceInsideAPermittedDirectoryIsResolvedBeforeTheWrite() + throws Exception { + Path permitted = permitTemporaryFolder(); + + CliFunctionResult result = + export(permitted.resolve("nested/../snapshot.gfd").toString()); + + assertThat(result.isSuccessful()).isTrue(); + File exportFile = captureExportFile(); + assertThat(exportFile.getPath()).doesNotContain(".."); + assertThat(exportFile.toPath()).isEqualTo(permitted.toRealPath().resolve("snapshot.gfd")); + } + + /** + * With no configuration, the only permitted directory is the member's working directory - which + * is where a relative export path lands. + */ + @Test + public void withoutConfigurationOnlyTheMemberWorkingDirectoryIsPermitted() throws Exception { + System.clearProperty(EXPORT_DATA_DIRS_PROPERTY); + Path workingDir = Paths.get(System.getProperty("user.dir")).toRealPath(); + + assertThat(export(workingDir.resolve("snapshot.gfd").toString()).isSuccessful()).isTrue(); + + assertThatThrownBy(() -> export(temporaryFolder.getRoot().toPath().resolve("x.gfd").toString())) + .isInstanceOf(IllegalArgumentException.class); + } + + /** + * The member's working directory stays permitted when other directories are configured, so a + * relative export path keeps working. + */ + @Test + public void theWorkingDirectoryRemainsPermittedWhenOtherDirectoriesAreConfigured() + throws Exception { + permitTemporaryFolder(); + + assertThat(export("snapshot.gfd").isSuccessful()).isTrue(); + assertThat(captureExportFile().toPath()) + .isEqualTo(Paths.get(System.getProperty("user.dir")).toRealPath().resolve("snapshot.gfd")); + } + + /** + * More than one directory can be permitted, which is how a deployment that exports to a + * dedicated backup location configures the member. + */ + @Test + public void severalDirectoriesCanBePermitted() throws Exception { + File backup = temporaryFolder.newFolder("backup"); + File other = temporaryFolder.newFolder("other"); + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, + backup.getAbsolutePath() + File.pathSeparator + other.getAbsolutePath()); + + assertThat(export(new File(backup, "snapshot.gfd").getPath()).isSuccessful()).isTrue(); + assertThat(export(new File(other, "snapshot.gfd").getPath()).isSuccessful()).isTrue(); + + assertThatThrownBy(() -> export(temporaryFolder.getRoot().toPath().resolve("x.gfd").toString())) + .isInstanceOf(IllegalArgumentException.class); + } + + /** + * A directory whose name merely starts with a permitted directory's name is not inside it - the + * check compares path elements, not string prefixes. + */ + @Test + public void aSiblingDirectoryWithAMatchingNamePrefixIsNotPermitted() throws Exception { + File permitted = temporaryFolder.newFolder("exports"); + File sibling = temporaryFolder.newFolder("exports-archive"); + System.setProperty(EXPORT_DATA_DIRS_PROPERTY, permitted.getAbsolutePath()); + + assertThatThrownBy(() -> export(new File(sibling, "snapshot.gfd").getPath())) + .isInstanceOf(IllegalArgumentException.class); + + verifyNothingWasWritten(); + } +} diff --git a/geode-gfsh/src/test/resources/expected-pom.xml b/geode-gfsh/src/test/resources/expected-pom.xml index c0a34b4cbaa2..579bdc657801 100644 --- a/geode-gfsh/src/test/resources/expected-pom.xml +++ b/geode-gfsh/src/test/resources/expected-pom.xml @@ -1,6 +1,5 @@ - - 4.0.0 - org.apache.geode - geode-gfsh - ${version} - Apache Geode - Apache Geode provides a database-like consistency model, reliable transaction processing and a shared-nothing architecture to maintain very low latency performance with high concurrency processing - http://geode.apache.org - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - scm:git:https://github.com:apache/geode.git - scm:git:https://github.com:apache/geode.git - https://github.com/apache/geode - - - - - org.apache.geode - geode-all-bom - ${version} - pom - import - - - - - - org.apache.geode - geode-core - compile - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.geode - geode-common - compile - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.springframework.shell - spring-shell-starter - compile - - - log4j-to-slf4j - org.apache.logging.log4j - - - + * + ch.qos.logback + + cglib - * - - - asm - * - - - spring-aop - * - - - guava - * - - - aopalliance - * - - - spring-context-support - * - - - - - org.apache.geode - geode-logging - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.geode - geode-membership - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.geode - geode-serialization - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.geode - geode-unsafe - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.springframework - spring-web - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - - + * + ch.qos.logback + + spring-core - * - - - commons-logging - * - - - - - org.apache.commons - commons-lang3 - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - com.healthmarketscience.rmiio - rmiio - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - com.fasterxml.jackson.core - jackson-databind - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - io.swagger.core.v3 - swagger-annotations - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - jakarta.xml.bind - jakarta.xml.bind-api - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - net.sf.jopt-simple - jopt-simple - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.logging.log4j - log4j-api - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.geode - geode-log4j - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.springframework - spring-core - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - true - - - org.springframework - spring-aop - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + + + + + org.jline + jline-terminal-jni + runtime + + + log4j-to-slf4j + org.apache.logging.log4j + + + * + ch.qos.logback + + + + + org.jline + jline-terminal-ffm + runtime + + + log4j-to-slf4j + org.apache.logging.log4j + + + * + ch.qos.logback + - - - org.glassfish.jaxb - jaxb-runtime - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - jakarta.activation - jakarta.activation-api - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - org.apache.logging.log4j - log4j-jul - runtime - - - log4j-to-slf4j - org.apache.logging.log4j - - + + * + ch.qos.logback + - - - diff --git a/geode-http-service/src/test/resources/expected-pom.xml b/geode-http-service/src/test/resources/expected-pom.xml index b768efe732db..56b53d0ebb26 100644 --- a/geode-http-service/src/test/resources/expected-pom.xml +++ b/geode-http-service/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-jmh/src/test/resources/expected-pom.xml b/geode-jmh/src/test/resources/expected-pom.xml index be1bcecb491b..b75ba9403a7d 100644 --- a/geode-jmh/src/test/resources/expected-pom.xml +++ b/geode-jmh/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-junit/src/main/java/org/apache/geode/management/internal/security/TestCommand.java b/geode-junit/src/main/java/org/apache/geode/management/internal/security/TestCommand.java index ebbd8c950c43..0199985546af 100644 --- a/geode-junit/src/main/java/org/apache/geode/management/internal/security/TestCommand.java +++ b/geode-junit/src/main/java/org/apache/geode/management/internal/security/TestCommand.java @@ -135,7 +135,7 @@ private static void init() { // Data Commands createTestCommand("rebalance --include-region=RegionA", ResourcePermissions.DATA_MANAGE); createTestCommand("export data --region=RegionA --file=export.txt --member=exportMember", - regionARead); + regionARead, ResourcePermissions.CLUSTER_WRITE); createTestCommand("import data --region=RegionA --file=import.txt --member=importMember", regionAWrite); createTestCommand("put --key=key1 --value=value1 --region=RegionA", regionAWrite); diff --git a/geode-junit/src/test/resources/expected-pom.xml b/geode-junit/src/test/resources/expected-pom.xml index 2c6a64729c78..87dbcf8aa1c1 100644 --- a/geode-junit/src/test/resources/expected-pom.xml +++ b/geode-junit/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + hamcrest * @@ -70,6 +74,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -81,6 +89,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -92,6 +104,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -103,6 +119,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -114,6 +134,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -125,6 +149,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -136,6 +164,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -147,6 +179,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -158,6 +194,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + junit-dep * @@ -173,6 +213,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -184,6 +228,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -195,6 +243,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -206,6 +258,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -217,6 +273,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -228,6 +288,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -239,6 +303,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -250,6 +318,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -261,6 +333,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -272,6 +348,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -283,6 +363,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -294,6 +378,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-log4j/build.gradle b/geode-log4j/build.gradle index d2501c2a7a7e..e3908f84c4b6 100644 --- a/geode-log4j/build.gradle +++ b/geode-log4j/build.gradle @@ -21,6 +21,24 @@ plugins { id 'jmh' } +// GEODE-10543: Configure GraalVM annotation processor options for Log4j 2.25.3 +// Log4j 2.25.3 includes a GraalVM Reachability Metadata annotation processor that generates +// plugin descriptors for native image compilation. Without these options, the processor emits +// warnings about missing Maven coordinates, which are treated as compilation errors by Gradle. +// +// These options specify the Maven coordinates (groupId:artifactId) for the generated plugin +// descriptors, suppressing the warnings and allowing compilation to succeed. +// +// Apply only to main source compilation, as integration tests don't trigger the annotation processor. +// +// Reference: https://issues.apache.org/jira/browse/LOG4J2-3642 +tasks.named('compileJava').configure { + options.compilerArgs += [ + '-Alog4j.graalvm.groupId=org.apache.geode', + '-Alog4j.graalvm.artifactId=geode-log4j' + ] +} + dependencies { api(platform(project(':boms:geode-all-bom'))) @@ -63,8 +81,15 @@ dependencies { exclude module: 'geode-core' } integrationTestImplementation('junit:junit') - integrationTestImplementation('org.apache.logging.log4j:log4j-core::tests') - integrationTestImplementation('org.apache.logging.log4j:log4j-core::test-sources') + // Log4j 2.20.0+ moved test utilities to log4j-core-test with new package names: + // org.apache.logging.log4j.junit → org.apache.logging.log4j.core.test.junit + // org.apache.logging.log4j.test → org.apache.logging.log4j.core.test + // log4j-core-test 2.25.4 transitively depends on assertj-core 3.27.3, but Geode's + // custom AssertJ assertions were built against 3.22.0. Force 3.22.0 to avoid + // NoSuchMethodError: CommonValidations.failIfEmptySinceActualIsNotEmpty + integrationTestImplementation('org.apache.logging.log4j:log4j-core-test') { + exclude group: 'org.assertj', module: 'assertj-core' + } integrationTestImplementation('org.assertj:assertj-core') distributedTestImplementation(project(':geode-junit')) { diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/alerting/log4j/internal/impl/AlertAppenderIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/alerting/log4j/internal/impl/AlertAppenderIntegrationTest.java index 1a43d58917ec..0bd54f409023 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/alerting/log4j/internal/impl/AlertAppenderIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/alerting/log4j/internal/impl/AlertAppenderIntegrationTest.java @@ -36,7 +36,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/BothLogWriterAppendersIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/BothLogWriterAppendersIntegrationTest.java index 2f347145a439..b93a506ff672 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/BothLogWriterAppendersIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/BothLogWriterAppendersIntegrationTest.java @@ -26,7 +26,7 @@ import java.net.URL; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/CacheWithCustomLogConfigIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/CacheWithCustomLogConfigIntegrationTest.java index 4e4098ae896a..17663784046f 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/CacheWithCustomLogConfigIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/CacheWithCustomLogConfigIntegrationTest.java @@ -30,8 +30,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConfigurationWithLogLevelChangesIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConfigurationWithLogLevelChangesIntegrationTest.java index 1d4773144673..ea1bd4db3b74 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConfigurationWithLogLevelChangesIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConfigurationWithLogLevelChangesIntegrationTest.java @@ -29,7 +29,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConsoleAppenderWithLoggerContextRuleIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConsoleAppenderWithLoggerContextRuleIntegrationTest.java index 9b73b57d23db..41dfb704a690 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConsoleAppenderWithLoggerContextRuleIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/ConsoleAppenderWithLoggerContextRuleIntegrationTest.java @@ -28,7 +28,7 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.DefaultErrorHandler; import org.apache.logging.log4j.core.appender.OutputStreamManager; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithBothLogWriterAppendersIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithBothLogWriterAppendersIntegrationTest.java index 5834692a2f54..ab57b6aa24d1 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithBothLogWriterAppendersIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithBothLogWriterAppendersIntegrationTest.java @@ -27,7 +27,7 @@ import java.util.Properties; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithLogLevelChangesIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithLogLevelChangesIntegrationTest.java index b404b5d1754c..62ef3caed9de 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithLogLevelChangesIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/DistributedSystemWithLogLevelChangesIntegrationTest.java @@ -31,7 +31,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/FastLoggerIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/FastLoggerIntegrationTest.java index e624d4d599fe..391fcfae85a6 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/FastLoggerIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/FastLoggerIntegrationTest.java @@ -30,7 +30,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterAcceptIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterAcceptIntegrationTest.java index e25ebfe32e04..19f759b2a3ca 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterAcceptIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterAcceptIntegrationTest.java @@ -24,8 +24,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterDenyIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterDenyIntegrationTest.java index 1d02f5ed8234..c7cbc9bab532 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterDenyIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GemfireVerboseMarkerFilterDenyIntegrationTest.java @@ -23,8 +23,8 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderIntegrationTest.java index 9ea97fa3ff63..3177f9ab1009 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderIntegrationTest.java @@ -28,7 +28,7 @@ import org.apache.logging.log4j.core.appender.ConsoleAppender; import org.apache.logging.log4j.core.appender.DefaultErrorHandler; import org.apache.logging.log4j.core.appender.OutputStreamManager; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithCacheIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithCacheIntegrationTest.java index c26056d736a3..1c1a46eb5c11 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithCacheIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithCacheIntegrationTest.java @@ -27,7 +27,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithSystemOutRuleIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithSystemOutRuleIntegrationTest.java index d4aff795238d..0a5a80e00aa3 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithSystemOutRuleIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeConsoleAppenderWithSystemOutRuleIntegrationTest.java @@ -21,7 +21,7 @@ import java.net.URL; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterAcceptIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterAcceptIntegrationTest.java index 0a69499a778e..02adc6269365 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterAcceptIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterAcceptIntegrationTest.java @@ -24,8 +24,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterDenyIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterDenyIntegrationTest.java index f369f0a0dbbf..007017d708aa 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterDenyIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/GeodeVerboseMarkerFilterDenyIntegrationTest.java @@ -24,8 +24,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogServiceWithCustomLogConfigIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogServiceWithCustomLogConfigIntegrationTest.java index c4084d85f92a..d01ad2b74cb5 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogServiceWithCustomLogConfigIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogServiceWithCustomLogConfigIntegrationTest.java @@ -25,8 +25,8 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; -import org.apache.logging.log4j.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.appender.ListAppender; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderIntegrationTest.java index 8926636c13c5..5a24646b0c73 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderIntegrationTest.java @@ -31,7 +31,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderShutdownIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderShutdownIntegrationTest.java index 8453713f6272..9719bffe98d9 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderShutdownIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderShutdownIntegrationTest.java @@ -26,7 +26,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithLimitsIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithLimitsIntegrationTest.java index 71f90b4c4bdd..1afc5892e5ba 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithLimitsIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithLimitsIntegrationTest.java @@ -24,7 +24,7 @@ import java.io.File; import java.net.URL; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithMemberNameInXmlIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithMemberNameInXmlIntegrationTest.java index afc197790a21..5f64d1f5381a 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithMemberNameInXmlIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/LogWriterAppenderWithMemberNameInXmlIntegrationTest.java @@ -34,7 +34,7 @@ import java.util.regex.Pattern; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; diff --git a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/SecurityLogWriterAppenderIntegrationTest.java b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/SecurityLogWriterAppenderIntegrationTest.java index 00ae368a332d..0d6b0c69e170 100644 --- a/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/SecurityLogWriterAppenderIntegrationTest.java +++ b/geode-log4j/src/integrationTest/java/org/apache/geode/logging/log4j/internal/impl/SecurityLogWriterAppenderIntegrationTest.java @@ -25,7 +25,7 @@ import java.net.URL; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.junit.LoggerContextRule; +import org.apache.logging.log4j.core.test.junit.LoggerContextRule; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; diff --git a/geode-log4j/src/test/resources/expected-pom.xml b/geode-log4j/src/test/resources/expected-pom.xml index 1dd30357b2a9..5400ae355c8e 100644 --- a/geode-log4j/src/test/resources/expected-pom.xml +++ b/geode-log4j/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + slf4j-api * @@ -126,6 +150,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true @@ -138,6 +166,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + true diff --git a/geode-logging/src/test/resources/expected-pom.xml b/geode-logging/src/test/resources/expected-pom.xml index b2528f6ba474..baaff2b89a65 100644 --- a/geode-logging/src/test/resources/expected-pom.xml +++ b/geode-logging/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-lucene/src/test/resources/expected-pom.xml b/geode-lucene/src/test/resources/expected-pom.xml index 4899bb2f4fef..9f12b2acc391 100644 --- a/geode-lucene/src/test/resources/expected-pom.xml +++ b/geode-lucene/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + lucene-sandbox * @@ -147,6 +179,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -158,6 +194,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -169,6 +209,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -180,6 +224,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -191,6 +239,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-management/src/main/java/org/apache/geode/management/api/RestTemplateClusterManagementServiceTransport.java b/geode-management/src/main/java/org/apache/geode/management/api/RestTemplateClusterManagementServiceTransport.java index dcb01f468945..6381001215fb 100644 --- a/geode-management/src/main/java/org/apache/geode/management/api/RestTemplateClusterManagementServiceTransport.java +++ b/geode-management/src/main/java/org/apache/geode/management/api/RestTemplateClusterManagementServiceTransport.java @@ -25,12 +25,14 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.client5.http.ssl.HostnameVerificationPolicy; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -164,13 +166,13 @@ public void configureConnection(ConnectionConfig connectionConfig) { // Configure SSL context and hostname verifier (HttpClient 5.x approach) // Only configure SSL if we have a non-null SSL context if (connectionConfig.getSslContext() != null) { - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + DefaultClientTlsStrategy sslSocketFactory = createTlsStrategy( connectionConfig.getSslContext(), connectionConfig.getHostnameVerifier()); HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslSocketFactory) + .setTlsSocketStrategy(sslSocketFactory) .build(); clientBuilder.setConnectionManager(connectionManager); @@ -178,13 +180,13 @@ public void configureConnection(ConnectionConfig connectionConfig) { // If only hostname verifier is set without SSL context, we need to use the default SSL // context try { - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory( + DefaultClientTlsStrategy sslSocketFactory = createTlsStrategy( SSLContext.getDefault(), connectionConfig.getHostnameVerifier()); HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslSocketFactory) + .setTlsSocketStrategy(sslSocketFactory) .build(); clientBuilder.setConnectionManager(connectionManager); @@ -197,6 +199,25 @@ public void configureConnection(ConnectionConfig connectionConfig) { restTemplate.setRequestFactory(requestFactory); } + /** + * Builds the TLS strategy used for HTTPS connections. + * + *

      + * When the caller supplies a {@link HostnameVerifier}, that verifier alone decides whether the + * peer's certificate matches the endpoint, so the strategy is created with + * {@link HostnameVerificationPolicy#CLIENT}. Without an explicit verifier the strategy keeps the + * library's own endpoint identification. + *

      + */ + private static DefaultClientTlsStrategy createTlsStrategy(SSLContext sslContext, + HostnameVerifier hostnameVerifier) { + if (hostnameVerifier == null) { + return new DefaultClientTlsStrategy(sslContext); + } + return new DefaultClientTlsStrategy(sslContext, HostnameVerificationPolicy.CLIENT, + hostnameVerifier); + } + @Override public > ClusterManagementRealizationResult submitMessage( T configMessage, CommandType command) { diff --git a/geode-management/src/test/resources/expected-pom.xml b/geode-management/src/test/resources/expected-pom.xml index 738c52522175..9ed324528709 100644 --- a/geode-management/src/test/resources/expected-pom.xml +++ b/geode-management/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -143,6 +175,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-membership/src/test/resources/expected-pom.xml b/geode-membership/src/test/resources/expected-pom.xml index 3fd2e3cf4e3c..95a47c531ecc 100644 --- a/geode-membership/src/test/resources/expected-pom.xml +++ b/geode-membership/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -143,6 +175,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-memcached/src/test/resources/expected-pom.xml b/geode-memcached/src/test/resources/expected-pom.xml index b9ea313d1ed7..99b7c529140d 100644 --- a/geode-memcached/src/test/resources/expected-pom.xml +++ b/geode-memcached/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-old-client-support/src/test/resources/expected-pom.xml b/geode-old-client-support/src/test/resources/expected-pom.xml index 4b50e9620ae0..8746c9027f71 100644 --- a/geode-old-client-support/src/test/resources/expected-pom.xml +++ b/geode-old-client-support/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/RegionDetailErrorMessageIntegrationTest.java b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/RegionDetailErrorMessageIntegrationTest.java new file mode 100644 index 000000000000..94aa800e6e61 --- /dev/null +++ b/geode-pulse/src/integrationTest/java/org/apache/geode/tools/pulse/controllers/RegionDetailErrorMessageIntegrationTest.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.tools.pulse.controllers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.mockito.quality.Strictness.LENIENT; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.parseMediaType; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.security.Principal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import org.apache.geode.test.junit.categories.PulseTest; +import org.apache.geode.tools.pulse.internal.data.Cluster; +import org.apache.geode.tools.pulse.internal.data.Repository; + +/** + * Covers the region-detail error message end to end, from the {@code /pulseUpdate} request the + * Pulse UI posts through to the JSON it receives back. + */ +@Category({PulseTest.class}) +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration("classpath*:WEB-INF/pulse-servlet.xml") +@ActiveProfiles({"pulse.controller.test"}) +public class RegionDetailErrorMessageIntegrationTest { + + private static final String PATH_WITH_SPECIAL_CHARACTERS = "/orders<2026>&archive"; + private static final String ENCODED_MESSAGE = + "Region [/orders<2026>&archive] is not available"; + + private static final MediaType JSON_MEDIA_TYPE = parseMediaType(APPLICATION_JSON_VALUE); + private static final Principal PRINCIPAL = () -> "test-user"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule().strictness(LENIENT); + + @Autowired + private WebApplicationContext wac; + + @Autowired + private Repository repository; + + @Mock + Cluster cluster; + + private MockMvc mockMvc; + + @Before + public void setup() { + when(repository.getCluster()).thenReturn(cluster); + when(cluster.getServerName()).thenReturn("mock-cluster"); + // The requested path resolves to no region, so the services take the error branch. + when(cluster.getClusterRegion(anyString())).thenReturn(null); + + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); + } + + @Test + public void pulseUpdateEncodesSpecialCharactersForClusterSelectedRegion() throws Exception { + MvcResult result = mockMvc + .perform(post("/pulseUpdate") + .with(csrf()) + .param("pulseData", pulseData("ClusterSelectedRegion", PATH_WITH_SPECIAL_CHARACTERS)) + .principal(PRINCIPAL) + .accept(JSON_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.errorOnRegion") + .value(ENCODED_MESSAGE)) + .andReturn(); + + assertThat(result.getResponse().getContentAsString()) + .contains("/orders<2026>&archive"); + } + + @Test + public void pulseUpdateEncodesSpecialCharactersForClusterSelectedRegionsMember() + throws Exception { + MvcResult result = mockMvc + .perform(post("/pulseUpdate") + .with(csrf()) + .param("pulseData", + pulseData("ClusterSelectedRegionsMember", PATH_WITH_SPECIAL_CHARACTERS)) + .principal(PRINCIPAL) + .accept(JSON_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.ClusterSelectedRegionsMember.selectedRegionsMembers.errorOnRegion") + .value(ENCODED_MESSAGE)) + .andReturn(); + + assertThat(result.getResponse().getContentAsString()) + .contains("/orders<2026>&archive"); + } + + @Test + public void pulseUpdateLeavesOrdinaryRegionPathUnchanged() throws Exception { + mockMvc + .perform(post("/pulseUpdate") + .with(csrf()) + .param("pulseData", pulseData("ClusterSelectedRegion", "/mock-region")) + .principal(PRINCIPAL) + .accept(JSON_MEDIA_TYPE)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.ClusterSelectedRegion.selectedRegion.errorOnRegion") + .value("Region [/mock-region] is not available")); + } + + /** Builds the {@code pulseData} body the Pulse frontend posts for the region-detail page. */ + private static String pulseData(String service, String regionFullPath) { + ObjectNode parameters = MAPPER.createObjectNode(); + parameters.put("regionFullPath", regionFullPath); + ObjectNode root = MAPPER.createObjectNode(); + root.set(service, parameters); + return root.toString(); + } +} diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java index f99c50155045..74f55416ee1b 100644 --- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java +++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionService.java @@ -30,6 +30,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -222,7 +223,9 @@ private ObjectNode getSelectedRegionJson(Cluster cluster, String selectedRegionF return regionJSON; } else { ObjectNode responseJSON = mapper.createObjectNode(); - responseJSON.put("errorOnRegion", "Region [" + selectedRegionFullPath + "] is not available"); + responseJSON.put("errorOnRegion", + "Region [" + StringEscapeUtils.escapeHtml4(selectedRegionFullPath) + + "] is not available"); return responseJSON; } } diff --git a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java index 238cdabc898c..6d1af501d2c2 100644 --- a/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java +++ b/geode-pulse/src/main/java/org/apache/geode/tools/pulse/internal/service/ClusterSelectedRegionsMemberService.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.text.StringEscapeUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; @@ -146,7 +147,9 @@ private ObjectNode getSelectedRegionsMembersJson(Cluster cluster, String selecte return regionMemberJSON; } else { ObjectNode responseJSON = mapper.createObjectNode(); - responseJSON.put("errorOnRegion", "Region [" + selectedRegionFullPath + "] is not available"); + responseJSON.put("errorOnRegion", + "Region [" + StringEscapeUtils.escapeHtml4(selectedRegionFullPath) + + "] is not available"); return responseJSON; } } diff --git a/geode-pulse/src/main/webapp/META-INF/NOTICE b/geode-pulse/src/main/webapp/META-INF/NOTICE index ef6b223a686a..21d657180d04 100644 --- a/geode-pulse/src/main/webapp/META-INF/NOTICE +++ b/geode-pulse/src/main/webapp/META-INF/NOTICE @@ -1,5 +1,5 @@ Apache Geode -Copyright 2016-2025 The Apache Software Foundation. +Copyright 2016-2026 The Apache Software Foundation. This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/service/RegionErrorMessageEncodingTest.java b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/service/RegionErrorMessageEncodingTest.java new file mode 100644 index 000000000000..2d1b08674442 --- /dev/null +++ b/geode-pulse/src/test/java/org/apache/geode/tools/pulse/internal/service/RegionErrorMessageEncodingTest.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.tools.pulse.internal.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.security.Principal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.Before; +import org.junit.Test; + +import org.apache.geode.tools.pulse.internal.data.Cluster; +import org.apache.geode.tools.pulse.internal.data.Repository; + +/** + * Tests the {@code errorOnRegion} message the region-detail services produce when the requested + * region path does not resolve. + * + *

      + * Paths containing characters such as {@code <} or {@code &} are encoded so the message displays + * as written. Lookup is unaffected and uses the path as supplied. + */ +public class RegionErrorMessageEncodingTest { + + private static final String PATH_WITH_SPECIAL_CHARACTERS = "/orders<2026>&archive"; + private static final String ENCODED_MESSAGE = + "Region [/orders<2026>&archive] is not available"; + private static final String ORDINARY_PATH = "/mock-region"; + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private Repository repository; + private Cluster cluster; + private HttpServletRequest request; + + @Before + public void setUp() { + repository = mock(Repository.class); + cluster = mock(Cluster.class); + request = mock(HttpServletRequest.class); + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn("admin"); + when(request.getUserPrincipal()).thenReturn(principal); + when(repository.getCluster()).thenReturn(cluster); + when(cluster.getServerName()).thenReturn("mock-cluster"); + // No region resolves, so every call below takes the error branch. + when(cluster.getClusterRegion(anyString())).thenReturn(null); + } + + @Test + public void clusterSelectedRegionEncodesSpecialCharactersInErrorMessage() throws Exception { + assertThat(selectedRegionError(PATH_WITH_SPECIAL_CHARACTERS)).isEqualTo(ENCODED_MESSAGE); + } + + @Test + public void clusterSelectedRegionsMemberEncodesSpecialCharactersInErrorMessage() + throws Exception { + assertThat(selectedRegionsMemberError(PATH_WITH_SPECIAL_CHARACTERS)).isEqualTo(ENCODED_MESSAGE); + } + + @Test + public void clusterSelectedRegionLeavesOrdinaryPathUnchanged() throws Exception { + assertThat(selectedRegionError(ORDINARY_PATH)) + .isEqualTo("Region [" + ORDINARY_PATH + "] is not available"); + } + + @Test + public void clusterSelectedRegionsMemberLeavesOrdinaryPathUnchanged() throws Exception { + assertThat(selectedRegionsMemberError(ORDINARY_PATH)) + .isEqualTo("Region [" + ORDINARY_PATH + "] is not available"); + } + + @Test + public void clusterSelectedRegionLooksTheRegionUpByTheSuppliedPath() throws Exception { + selectedRegionError(PATH_WITH_SPECIAL_CHARACTERS); + + verify(cluster).getClusterRegion(PATH_WITH_SPECIAL_CHARACTERS); + } + + @Test + public void clusterSelectedRegionsMemberLooksTheRegionUpByTheSuppliedPath() throws Exception { + selectedRegionsMemberError(PATH_WITH_SPECIAL_CHARACTERS); + + verify(cluster).getClusterRegion(PATH_WITH_SPECIAL_CHARACTERS); + } + + private String selectedRegionError(String regionFullPath) throws Exception { + when(request.getParameter("pulseData")) + .thenReturn(pulseData("ClusterSelectedRegion", regionFullPath)); + + ObjectNode json = new ClusterSelectedRegionService(repository).execute(request); + + return json.get("selectedRegion").get("errorOnRegion").asText(); + } + + private String selectedRegionsMemberError(String regionFullPath) throws Exception { + when(request.getParameter("pulseData")) + .thenReturn(pulseData("ClusterSelectedRegionsMember", regionFullPath)); + + ObjectNode json = new ClusterSelectedRegionsMemberService(repository).execute(request); + + return json.get("selectedRegionsMembers").get("errorOnRegion").asText(); + } + + /** Builds the {@code pulseData} body the Pulse frontend posts for the region-detail page. */ + private static String pulseData(String service, String regionFullPath) { + ObjectNode parameters = MAPPER.createObjectNode(); + parameters.put("regionFullPath", regionFullPath); + ObjectNode root = MAPPER.createObjectNode(); + root.set(service, parameters); + return root.toString(); + } +} diff --git a/geode-rebalancer/src/test/resources/expected-pom.xml b/geode-rebalancer/src/test/resources/expected-pom.xml index 2d94a9365349..97e1bbedf3ff 100644 --- a/geode-rebalancer/src/test/resources/expected-pom.xml +++ b/geode-rebalancer/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + spring-beans * diff --git a/geode-serialization/src/test/resources/expected-pom.xml b/geode-serialization/src/test/resources/expected-pom.xml index 719336ecdbab..5e7e2721420d 100644 --- a/geode-serialization/src/test/resources/expected-pom.xml +++ b/geode-serialization/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt index 0ce95af717d5..fe2869ac30c4 100644 --- a/geode-server-all/src/integrationTest/resources/dependency_classpath.txt +++ b/geode-server-all/src/integrationTest/resources/dependency_classpath.txt @@ -19,25 +19,24 @@ geode-unsafe-0.0.0.jar geode-deployment-legacy-0.0.0.jar snappy-0.5.jar swagger-annotations-2.2.22.jar -jackson-datatype-jsr310-2.17.0.jar -jackson-annotations-2.17.0.jar -jackson-dataformat-yaml-2.17.0.jar -jackson-core-2.17.0.jar -jackson-datatype-joda-2.17.0.jar -jackson-databind-2.17.0.jar -httpclient5-5.4.4.jar -httpcore5-h2-5.3.4.jar -httpcore5-5.3.4.jar +jackson-datatype-jsr310-2.21.6.jar +jackson-dataformat-yaml-2.21.6.jar +jackson-core-2.21.6.jar +jackson-datatype-joda-2.21.6.jar +jackson-databind-2.21.6.jar +httpclient5-5.6.4.jar +httpcore5-h2-5.4.3.jar +httpcore5-5.4.3.jar HikariCP-4.0.3.jar commons-lang3-3.18.0.jar jaxb-runtime-4.0.2.jar jaxb-core-4.0.2.jar jakarta.xml.bind-api-4.0.2.jar -log4j-slf4j-impl-2.17.2.jar -log4j-core-2.17.2.jar -log4j-jcl-2.17.2.jar -log4j-jul-2.17.2.jar -log4j-api-2.17.2.jar +log4j-slf4j-impl-2.25.5.jar +log4j-core-2.25.5.jar +log4j-jcl-2.25.5.jar +log4j-jul-2.25.5.jar +log4j-api-2.25.5.jar spring-shell-starter-3.3.3.jar rmiio-2.1.2.jar antlr-2.7.7.jar @@ -46,100 +45,104 @@ spring-shell-standard-commands-3.3.3.jar spring-shell-standard-3.3.3.jar spring-shell-core-3.3.3.jar commons-io-2.19.0.jar -micrometer-core-1.14.0.jar +micrometer-core-1.16.7.jar jakarta.resource-api-2.1.0.jar -jetty-ee10-annotations-12.0.27.jar -jetty-ee10-plus-12.0.27.jar +jetty-ee10-annotations-12.0.37.jar +spring-boot-starter-validation-3.3.13.jar +spring-boot-starter-3.3.13.jar +jetty-ee10-plus-12.0.37.jar +jakarta.enterprise.cdi-api-4.0.1.jar +jakarta.interceptor-api-2.1.0.jar +jakarta.annotation-api-2.1.1.jar jakarta.transaction-api-2.0.1.jar -shiro-core-1.13.0.jar +shiro-core-3.0.0.jar jgroups-3.6.20.Final.jar +jackson-annotations-2.21.jar commons-validator-1.7.jar fastutil-8.5.8.jar jna-platform-5.11.0.jar jna-5.11.0.jar jopt-simple-5.0.4.jar classgraph-4.8.147.jar -spring-aop-6.1.14.jar +spring-aop-6.1.21.jar angus-activation-2.0.0.jar jakarta.activation-api-2.1.3.jar -istack-commons-runtime-4.0.1.jar -spring-web-6.1.14.jar +istack-commons-runtime-4.1.1.jar +spring-web-6.1.21.jar spring-shell-table-3.3.3.jar -spring-boot-starter-validation-3.3.5.jar -spring-boot-starter-3.3.5.jar -spring-messaging-6.1.14.jar -spring-boot-autoconfigure-3.3.5.jar -spring-boot-3.3.5.jar -spring-context-6.1.14.jar -spring-beans-6.1.14.jar -spring-expression-6.1.14.jar -spring-core-6.1.14.jar +spring-messaging-6.1.21.jar +spring-boot-autoconfigure-3.3.13.jar +spring-boot-3.3.13.jar +spring-context-6.1.21.jar +spring-beans-6.1.21.jar +spring-expression-6.1.21.jar +spring-core-6.1.21.jar +jline-terminal-jni-3.26.3.jar +jline-terminal-ffm-3.26.3.jar lucene-analysis-phonetic-9.12.3.jar lucene-analysis-common-9.12.3.jar lucene-queryparser-9.12.3.jar lucene-queries-9.12.3.jar lucene-core-9.12.3.jar -shiro-config-ogdl-1.13.0.jar +shiro-config-ogdl-3.0.0.jar commons-beanutils-1.11.0.jar commons-codec-1.15.jar commons-collections-3.2.2.jar commons-digester-2.1.jar commons-logging-1.3.5.jar HdrHistogram-2.2.2.jar -jakarta.enterprise.cdi-api-4.0.1.jar -jakarta.interceptor-api-2.1.0.jar -jakarta.annotation-api-2.1.1.jar -jetty-ee10-webapp-12.0.27.jar -jetty-ee10-servlet-12.0.27.jar +jetty-ee10-webapp-12.0.37.jar +jetty-ee10-servlet-12.0.37.jar jakarta.servlet-api-6.0.0.jar joda-time-2.12.7.jar -jetty-ee-12.0.27.jar -jetty-session-12.0.27.jar -jetty-plus-12.0.27.jar -jetty-security-12.0.27.jar -jetty-server-12.0.27.jar -shiro-cache-1.13.0.jar -shiro-crypto-hash-1.13.0.jar -shiro-crypto-cipher-1.13.0.jar -shiro-config-core-1.13.0.jar -shiro-event-1.13.0.jar -shiro-crypto-core-1.13.0.jar -shiro-lang-1.13.0.jar -jetty-xml-12.0.27.jar -jetty-http-12.0.27.jar -jetty-io-12.0.27.jar -jetty-jndi-12.0.27.jar -jetty-util-12.0.27.jar -spring-boot-starter-logging-3.3.5.jar -logback-classic-1.5.11.jar -jul-to-slf4j-2.0.16.jar -slf4j-api-2.0.17.jar -micrometer-observation-1.14.0.jar -micrometer-commons-1.14.0.jar -LatencyUtils-2.0.3.jar -byte-buddy-1.14.9.jar -spring-jcl-6.1.14.jar -asm-commons-9.8.jar -asm-tree-9.8.jar -asm-9.8.jar -txw2-4.0.2.jar -reactor-core-3.6.10.jar +shiro-hashes-argon2-3.0.0.jar +shiro-hashes-bcrypt-3.0.0.jar +shiro-crypto-hash-3.0.0.jar +bcprov-jdk18on-1.85.jar +jetty-ee-12.0.37.jar +jetty-session-12.0.37.jar +jetty-plus-12.0.37.jar +jetty-security-12.0.37.jar +jetty-server-12.0.37.jar +shiro-cache-3.0.0.jar +shiro-crypto-cipher-3.0.0.jar +shiro-config-core-3.0.0.jar +shiro-event-3.0.0.jar +shiro-crypto-core-3.0.0.jar +shiro-lang-3.0.0.jar +jetty-xml-12.0.37.jar +jetty-http-12.0.37.jar +jetty-io-12.0.37.jar +jetty-jndi-12.0.37.jar +jetty-util-12.0.37.jar +spring-boot-starter-logging-3.3.13.jar +jul-to-slf4j-2.0.17.jar +slf4j-api-2.0.18.jar jline-console-3.26.3.jar jline-builtins-3.26.3.jar jline-reader-3.26.3.jar jline-style-3.26.3.jar jline-terminal-3.26.3.jar +jline-native-3.26.3.jar +micrometer-observation-1.16.7.jar +micrometer-commons-1.16.7.jar +jspecify-1.0.1.jar +LatencyUtils-2.0.3.jar +snakeyaml-2.5.jar +spring-jcl-6.1.21.jar +asm-commons-9.10.1.jar +asm-tree-9.10.1.jar +asm-9.10.1.jar +txw2-4.0.2.jar +reactor-core-3.8.7.jar ST4-4.3.3.jar -snakeyaml-2.2.jar jakarta.enterprise.lang-model-4.0.1.jar reactive-streams-1.0.4.jar -jline-native-3.26.3.jar antlr-runtime-3.5.2.jar -tomcat-embed-el-10.1.31.jar -hibernate-validator-8.0.1.Final.jar +tomcat-embed-el-10.1.42.jar +hibernate-validator-8.0.2.Final.jar jakarta.el-api-5.0.0.jar jakarta.inject-api-2.0.1.jar jakarta.validation-api-3.0.2.jar jboss-logging-3.4.3.Final.jar classmate-1.5.1.jar -logback-core-1.5.11.jar diff --git a/geode-server-all/src/test/resources/expected-pom.xml b/geode-server-all/src/test/resources/expected-pom.xml index 7c572939f7a7..433544b9f5b7 100644 --- a/geode-server-all/src/test/resources/expected-pom.xml +++ b/geode-server-all/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -110,6 +130,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -121,6 +145,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -132,6 +160,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -143,6 +175,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -154,6 +190,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -165,6 +205,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -176,6 +220,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -187,6 +235,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -198,6 +250,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -209,6 +265,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -220,6 +280,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -231,6 +295,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -242,6 +310,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -253,6 +325,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-tcp-server/src/test/resources/expected-pom.xml b/geode-tcp-server/src/test/resources/expected-pom.xml index e8b3e583a3b8..988bd23ac215 100644 --- a/geode-tcp-server/src/test/resources/expected-pom.xml +++ b/geode-tcp-server/src/test/resources/expected-pom.xml @@ -55,6 +55,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -66,6 +70,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -77,6 +85,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -88,6 +100,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + @@ -99,6 +115,10 @@ log4j-to-slf4j org.apache.logging.log4j + + * + ch.qos.logback + diff --git a/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/misc/Unsafe.java b/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/misc/Unsafe.java index 3c5db9e96df1..96e028205521 100644 --- a/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/misc/Unsafe.java +++ b/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/misc/Unsafe.java @@ -26,6 +26,33 @@ public class Unsafe { private final sun.misc.Unsafe unsafe; + + // Cached field offsets for ByteBuffer access + // These are computed once and reused to avoid repeated reflection + private static final long BUFFER_ADDRESS_FIELD_OFFSET; + private static final long BUFFER_CAPACITY_FIELD_OFFSET; + + static { + long addressOffset = -1; + long capacityOffset = -1; + try { + Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + sun.misc.Unsafe unsafeInstance = (sun.misc.Unsafe) unsafeField.get(null); + + // Get field offsets for Buffer fields + Field addressField = java.nio.Buffer.class.getDeclaredField("address"); + addressOffset = unsafeInstance.objectFieldOffset(addressField); + + Field capacityField = java.nio.Buffer.class.getDeclaredField("capacity"); + capacityOffset = unsafeInstance.objectFieldOffset(capacityField); + } catch (Exception e) { + // If initialization fails, offsets remain -1 + } + BUFFER_ADDRESS_FIELD_OFFSET = addressOffset; + BUFFER_CAPACITY_FIELD_OFFSET = capacityOffset; + } + { sun.misc.Unsafe tmp; try { @@ -210,4 +237,59 @@ public boolean compareAndSwapObject(Object o, long offset, Object expected, Obje public void putOrderedObject(Object o, long offset, Object x) { unsafe.putOrderedObject(o, offset, x); } + + /** + * Gets the native memory address from a DirectByteBuffer using field offset. + * This method accesses the 'address' field of java.nio.Buffer directly via Unsafe, + * which does not require --add-opens flags (unlike method reflection with setAccessible()). + * + * @param buffer the DirectByteBuffer to get the address from + * @return the native memory address + */ + public long getBufferAddress(Object buffer) { + if (BUFFER_ADDRESS_FIELD_OFFSET == -1) { + throw new RuntimeException("Buffer address field offset not initialized"); + } + return unsafe.getLong(buffer, BUFFER_ADDRESS_FIELD_OFFSET); + } + + /** + * Sets the native memory address for a ByteBuffer using field offset. + * This allows wrapping an arbitrary memory address as a ByteBuffer. + * + * @param buffer the ByteBuffer to set the address for + * @param address the native memory address + */ + public void setBufferAddress(Object buffer, long address) { + if (BUFFER_ADDRESS_FIELD_OFFSET == -1) { + throw new RuntimeException("Buffer address field offset not initialized"); + } + unsafe.putLong(buffer, BUFFER_ADDRESS_FIELD_OFFSET, address); + } + + /** + * Gets the capacity from a ByteBuffer using field offset. + * + * @param buffer the ByteBuffer to get the capacity from + * @return the buffer capacity + */ + public int getBufferCapacity(Object buffer) { + if (BUFFER_CAPACITY_FIELD_OFFSET == -1) { + throw new RuntimeException("Buffer capacity field offset not initialized"); + } + return unsafe.getInt(buffer, BUFFER_CAPACITY_FIELD_OFFSET); + } + + /** + * Sets the capacity for a ByteBuffer using field offset. + * + * @param buffer the ByteBuffer to set the capacity for + * @param capacity the capacity value + */ + public void setBufferCapacity(Object buffer, int capacity) { + if (BUFFER_CAPACITY_FIELD_OFFSET == -1) { + throw new RuntimeException("Buffer capacity field offset not initialized"); + } + unsafe.putInt(buffer, BUFFER_CAPACITY_FIELD_OFFSET, capacity); + } } diff --git a/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBuffer.java b/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBuffer.java deleted file mode 100644 index dc894cfea212..000000000000 --- a/geode-unsafe/src/main/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBuffer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.apache.geode.unsafe.internal.sun.nio.ch; - -/** - * Provides access to methods on non-SDK class {@link sun.nio.ch.DirectBuffer}. - */ -public interface DirectBuffer { - - /** - * @see sun.nio.ch.DirectBuffer#attachment() - * @param object to get attachment for - * @return returns attachment if object is {@link sun.nio.ch.DirectBuffer} otherwise null. - */ - static Object attachment(final Object object) { - if (object instanceof sun.nio.ch.DirectBuffer) { - return ((sun.nio.ch.DirectBuffer) object).attachment(); - } - - return null; - } - -} diff --git a/geode-unsafe/src/test/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBufferTest.java b/geode-unsafe/src/test/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBufferTest.java deleted file mode 100644 index 6d2f52b1c339..000000000000 --- a/geode-unsafe/src/test/java/org/apache/geode/unsafe/internal/sun/nio/ch/DirectBufferTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more contributor license - * agreements. See the NOTICE file distributed with this work for additional information regarding - * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance with the License. You may obtain a - * copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - - -package org.apache.geode.unsafe.internal.sun.nio.ch; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; - -import java.nio.ByteBuffer; - -import org.junit.jupiter.api.MethodOrderer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.parallel.Execution; - -@Execution(CONCURRENT) -@TestMethodOrder(MethodOrderer.Random.class) -public class DirectBufferTest { - - @Test - public void attachmentIsNullForNonDirectBuffer() { - assertThat(DirectBuffer.attachment(null)).isNull(); - assertThat(DirectBuffer.attachment(new Object())).isNull(); - assertThat(DirectBuffer.attachment(ByteBuffer.allocate(1))).isNull(); - } - - @Test - public void attachmentIsNullForUnslicedDirectBuffer() { - assertThat(DirectBuffer.attachment(ByteBuffer.allocateDirect(1))).isNull(); - } - - @Test - public void attachmentIsRootBufferForDirectBufferSlice() { - final ByteBuffer root = ByteBuffer.allocateDirect(10); - final ByteBuffer slice = root.slice(); - - assertThat(DirectBuffer.attachment(slice)).isSameAs(root); - } - -} diff --git a/geode-unsafe/src/test/resources/expected-pom.xml b/geode-unsafe/src/test/resources/expected-pom.xml index a5533e9a7c87..ef0bed734d20 100644 --- a/geode-unsafe/src/test/resources/expected-pom.xml +++ b/geode-unsafe/src/test/resources/expected-pom.xml @@ -1,5 +1,5 @@ - +