Keep test cluster logs out of Gradle cache - #22789
Conversation
PR Reviewer Guide 🔍(Review updated until commit c4cda68)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 93c9547 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 9e1423e
Suggestions up to commit 9c1a53f
Suggestions up to commit a121842
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #22789 +/- ##
============================================
- Coverage 71.66% 71.62% -0.05%
+ Complexity 77365 77348 -17
============================================
Files 6170 6170
Lines 359698 359710 +12
Branches 52458 52460 +2
============================================
- Hits 257782 257642 -140
- Misses 81509 81628 +119
- Partials 20407 20440 +33 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| expansions.put(heapDumpOrigin, "-XX:HeapDumpPath=" + relativeLogPath); | ||
| expansions.put("logs/gc.log", relativeLogPath.resolve("gc.log").toString()); | ||
| expansions.put("-XX:ErrorFile=logs/hs_err_pid%p.log", "-XX:ErrorFile=" + relativeLogPath.resolve("hs_err_pid%p.log")); | ||
| Path absoluteLogPath = logPath.toAbsolutePath(); |
There was a problem hiding this comment.
@NextbrickInc the OpenSearch (by default) have restrictions on file system access, this is one of the reasons why workingDir is being used here, logs are always relative to it (as well as conf, repo, ...), the security agent we run with will enforce that. I am wondering if it is worth exploring canUseSharedDistribution() conditions instead as per issue problem statement? Thank you.
There was a problem hiding this comment.
Thanks @reta - agreed that the JVM output paths must remain relative to workingDir. I reverted the
absolute-path approach and moved the fix to the distribution-selection decision.
The existing canUseSharedDistribution() predicate determines whether the distribution needs customization
(non-Windows, no extra jars, no modules, and no plugins), but it does not establish that the resolved
distribution is safe to use as a runtime directory. In the default unmodified-cluster case, the method returns
true and getDistroDir() points into Gradle's artifact transform cache - the case described in #20226.
The proposed change makes canUseSharedDistribution() return false. setupNodeDistribution() therefore
hard-links the distribution into the node workingDir, with a copy fallback, and getDistroDir() resolves to that
node-local distribution. JVM log, heap-dump, and fatal-error paths remain relative to workingDir, preserving
the security-agent boundary while preventing runtime writes under Gradle's immutable transform
workspace.
I also replaced the implementation-only unit coverage with TestClustersPlugin functional coverage. The tests
run two builds against one TestKit cache and verify an unchanged path/SHA-256 snapshot of the transformed
distribution. A three-node fixture verifies isolated node-local distros, relative JVM paths, and
start/stop/restart behavior. Locally, the final :build-tools:check completed all 67 integration tests with zero
failures, errors, or skips on Gradle 9.4.1 and Temurin JDK 21.
The tradeoff is that this disables shared-distribution reuse for all test clusters. My reasoning is that the
transform cache must be treated as strictly read-only, and the current startup path cannot guarantee that
invariant. The method remains the policy boundary so shared use can be re-enabled if runtime writes are
fully isolated in the future.
Pls find pdf below:
OpenSearch_PR_22789_Bug_Fix_Technical_Brief Shrey Narayan NextBricks .pdf
|
Persistent review updated to latest commit 4feee83 |
4feee83 to
a121842
Compare
|
Persistent review updated to latest commit a121842 |
|
❌ Gradle check result for a121842: null Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 9c1a53f |
|
❌ Gradle check result for 9c1a53f: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 9e1423e |
Signed-off-by: Shrey Narayan <employeenextbrick@gmail.com>
9e1423e to
93c9547
Compare
|
Persistent review updated to latest commit 93c9547 |
|
❕ Gradle check result for 93c9547: UNSTABLE Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure. |
Signed-off-by: Shrey Narayan <employeenextbrick@gmail.com>
|
Persistent review updated to latest commit f66e91f |
| // using original location can be too long due to MAX_PATH restrictions on windows CI | ||
| // TODO revisit when moving to shorter paths on CI by using Teamcity | ||
| return OS.current() != OS.WINDOWS && extraJarFiles.size() == 0 && modules.size() == 0 && plugins.size() == 0; | ||
| // A shared distribution lives in Gradle's immutable artifact transform cache. Copy it into workingDir so |
There was a problem hiding this comment.
I reproduced #20226 end to end, and the answer to the scoping question turned out to be the opposite of what we'd assumed: the cluster that corrupted the workspace was an INTEG_TEST cluster, not ARCHIVE.
Reproduction — cross-cluster-replication @ main, ./gradlew integTest --no-daemon, Gradle 9.4.1, OpenSearch 3.9.0-SNAPSHOT. After the first build:
~/.gradle/caches/9.4.1/transforms/fa34e8b315277f51e6deb23ecde213c8/
transformed/opensearch-3.9.0-SNAPSHOT.zip/opensearch-3.9.0-SNAPSHOT/logs/gc.log
transformed/opensearch-3.9.0-SNAPSHOT.zip/opensearch-3.9.0-SNAPSHOT/logs/gc.log.00
The second build then fails exactly as the issue reports:
The contents of the immutable workspace
'.../transforms/fa34e8b315277f51e6deb23ecde213c8' have been modified.
outputDirectory:
- logs (Directory, ca2c9eac4ec3987d9f770207005e0a55)
- gc.log (RegularFile, a215e10a6bcb2b1a4a95a95a629c10df)
- gc.log.00 (RegularFile, fb07ccfe57e8380a144cca09ebe334b2)
Which cluster did it. From the node command lines of that same build — integTest-0:
-Dopensearch.path.home=~/.gradle/caches/9.4.1/transforms/fa34e8b3.../transformed/
opensearch-3.9.0-SNAPSHOT.zip/opensearch-3.9.0-SNAPSHOT
-Dopensearch.distribution.type=zip -Dopensearch.bundled_jdk=false
-Xlog:gc*,...:file=logs/gc.log
versus leaderCluster-0:
-Dopensearch.path.home=~/ccr-repro/build/testclusters/leaderCluster-0/distro/3.9.0-ARCHIVE
-Dopensearch.distribution.type=tar
| cluster | distribution | ran from | polluted |
|---|---|---|---|
integTest-0 |
INTEG_TEST | transform workspace | yes |
leaderCluster-0/1 |
ARCHIVE | node-local copy | no |
followCluster-0/1 |
ARCHIVE | node-local copy | no |
The ARCHIVE clusters escaped only because CCR installs plugins into them, so plugins.size() != 0 forces a copy — incidental, not protective. An ARCHIVE-scoped guard would have left this exact failure in place, and it also means #20241's "use INTEG_TEST" points into the bug rather than around it.
Mechanism — and this is a little different from what the issue assumes. It isn't the node process; as you noted earlier, its working directory is the node's own workingDir and its logs stay relative to that. It's the bin scripts: runOpenSearchBinScriptWithInput() sets spec.workingDir(getDistroDir()) and passes getOpenSearchEnvironment(), so opensearch-keystore and opensearch-plugin execute inside the distribution while inheriting the node's JVM options. -Xlog:gc*:file=logs/gc.log is relative, so it resolves into the workspace. The inheritance is directly observable: in one run opensearch-plugin failed with Port already in use: 7777, the node's JMX agent port.
Verification of the fix. Rebuilt build-tools from this branch, published to mavenLocal, cleared the polluted workspace and re-ran the same CCR integTest. integTest-0 now runs from its own distro/3.9.0-INTEG_TEST copy, no node has path.home inside a transform workspace, and the transform cache holds zero gc.log files — the GC log lands in build/testclusters/integTest-0/logs/ where it belongs.
A correction on my own PR. The functional test I added doesn't demonstrate any of this, and can't: the fixture's bin/opensearch is exec echo "opensearch script executed!" and never starts a JVM, so no GC log is written in either code state and both cache assertions pass on main too. Restoring the previous gate and running my own test gives CACHE-CHANGED: false with no log keys; the only assertion that changed between states was assertCustomDistro, which asserts the implementation rather than the property. That is also why this class of bug isn't reachable from the buildSrc harness at all — the fake opensearch-keystore never starts a JVM either. I've removed those assertions in e0b7696 and let the reproduction carry the evidence; the change is now +6/−67, with the production behaviour unchanged and the comment explaining the workspace-immutability reason.
If the reproduction settles the question, please review and approve so this can be merged. If you'd still prefer something narrower now that the mechanism is pinned down, the remaining option I can see is running the bin scripts with their working directory outside the distribution rather than disabling the shared distribution — happy to do that instead if you'd rather keep the optimisation. @cwperks, tagging you as requested.
There was a problem hiding this comment.
|
Persistent review updated to latest commit e0b7696 |
|
❌ Gradle check result for e0b7696: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
@reta @dbwiddis @cwperks — some history, since this has had several attempts. This is the permanent form of what #20227 already shipped. That PR set Mechanism. The write doesn't come from the node process — its working directory is the node's own That's why the fix belongs at distribution selection rather than at the node: the node was never the writer. I tried #20229's working-directory approach first and reverted it — @reta's inline point about the security agent enforcing On scoping to ARCHIVE. Reproduced against
Cluster | Distribution | Ran from | Polluted
-- | -- | -- | --
integTest-0 | INTEG_TEST | transform workspace | yes
leaderCluster-0/-1 | ARCHIVE | node-local copy | no
followCluster-0/-1 | ARCHIVE | node-local copy | no
~/.gradle/caches/9.4.1/transforms/fa34e8b315277f51e6deb23ecde213c8/ transformed/opensearch-3.9.0-SNAPSHOT.zip/opensearch-3.9.0-SNAPSHOT/logs/gc.log The ARCHIVE clusters escaped only because CCR installs plugins into them, making Why no test fails without this change. The buildSrc functional harness can't reach the defect: its fake On the automated dead-code note — I kept the predicate as a named method because the comment on it is the only record of the immutable-workspace constraint. Happy to inline it.
Please approve and merge. Thanks alot. OpenSearch_PR22789_Reproduction_Report_NextBricks_Shrey_Narayan.pdf |
Reproducing GITHUB#20226 against cross-cluster-replication shows the write does not come from the node process, whose working directory is the node's own workingDir. It comes from the bin scripts: runOpenSearchBinScriptWithInput sets the working directory to the distribution and passes the node environment, so opensearch-keystore and opensearch-plugin run inside the distribution while inheriting the node's JVM options. A relative option such as -Xlog:gc*:file=logs/gc.log then resolves into Gradle's artifact transform workspace, which Gradle 8.6+ validates as immutable. Record that in the comment on canUseSharedDistribution(); the behaviour is unchanged. Also remove the functional test added earlier. It cannot fail: the fixture's bin/opensearch is `exec echo "opensearch script executed!"` and never starts a JVM, so no GC log is written in either code state and both cache assertions pass on main as well. The only assertion that changed between states was assertCustomDistro, which asserts the implementation rather than the property. The reproduction recorded on the pull request carries the evidence instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Shrey Narayan <employeenextbrick@gmail.com>
e0b7696 to
69b70c4
Compare
|
Persistent review updated to latest commit 69b70c4 |
|
❌ Gradle check result for 69b70c4: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit c4cda68 |
|
❌ Gradle check result for c4cda68: null Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
Description
Resolve JVM output paths against each test node's absolute log directory so shared Gradle distributions remain immutable. This keeps heap dumps, GC logs, and fatal error logs out of Gradle's transform cache.
Add regression coverage for all three JVM output paths.
Related Issues
Resolves #20226
Testing
Contributor
Shrey Narayan, NextBrick
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.