[Backport 2.19] Add data stream backing-index modify API and attach_to_data_stream restore option (manual) - #22849
Conversation
…#22487) Introduce POST /_data_stream/_modify, a metadata-only API to add or remove backing indices of a data stream, applied atomically in a single cluster-state update without touching shards. Multiple add/remove actions may be batched in one request. The write (highest-generation) backing index cannot be removed. Resolves opensearch-project#8271. Signed-off-by: Ankit Jain <jainankitk@apache.org>
…pensearch-project#22539) When set, a restored ".ds-<stream>-NNNNNN" index is attached to a pre-existing data stream of the same name in the same cluster-state update, advancing the generation as needed; the default preserves restoring it as a standalone index. The attached index must map the stream's timestamp field as a date. Signed-off-by: Ankit Jain <jainankitk@apache.org>
The two cherry-picked commits target main (3.x). Five call sites differ on the
2.19 line and are adapted here:
- ClusterManagerTask does not exist on 2.19. The throttling key is a String
constant in ClusterManagerTaskKeys with
registerClusterManagerTask(String, boolean), so MODIFY_DATA_STREAM_KEY
("modify-data-stream") is added there and MetadataDataStreamsService
registers against it. Same wire string and throttling behaviour as main,
matching the convention MetadataCreateDataStreamService already uses.
- DataStreamAction was a Java record; server compiles with -source 11 on this
line, so it becomes a final class with an explicit canonical constructor
(keeping the requireNonNull checks), the same accessor names, and
hand-written equals/hashCode/toString.
- org.opensearch.action.support.clustermanager.Acknowledged{Request,Response}
-> org.opensearch.action.support.master.* (matches CreateDataStreamAction).
- org.opensearch.transport.client.node.NodeClient
-> org.opensearch.client.node.NodeClient (matches RestCreateDataStreamAction).
- ModifyDataStreamsClusterStateUpdateRequest.clusterManagerNodeTimeout(TimeValue)
-> masterNodeTimeout(TimeValue), which is the setter name on this line.
No production code or tests were dropped.
Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: dongkyun-yoo <dongkyun.yoo@linecorp.com>
…tant opensearch-project#22539 guards the RestoreSnapshotRequest.attachToDataStream wire field on Version.V_3_8_0, which does not exist on this line. The guard must key on a version that no already-released node reports, otherwise a patched node writes the extra boolean to a stock peer of the same version, the peer never consumes it, and the transport stream desynchronizes. That failure mode was reproduced on a two-node mixed cluster: with the guard on Version.CURRENT (== V_2_19_6), a restore coordinated by a patched node against a stock 2.19.6 cluster-manager fails with illegal_state_exception: Message not fully read (request) for requestId [..], action [cluster:admin/snapshot/restore], available [0]; resetting and nothing is restored. It is worse than a broken feature: the boolean is written whenever the peer version passes the guard, regardless of whether the caller requested the attach, so every cluster:admin/snapshot/restore from a patched non-cluster-manager node fails -- a restore-API outage, not a degraded edge case. Adding V_2_19_7 and moving CURRENT to it makes the mixed-version case degrade safely: verified on the same two-node cluster, a patched 2.19.7 coordinator talking to a stock 2.19.6 cluster-manager mounts the index and simply skips the attach, with no serialization exception on either node, while an all-2.19.7 cluster performs the attach as intended. The version bump touches three declarations that must move together, or the build fails the assertCurrentVersionMatchesParsed check in BwcVersions: Version.java, buildSrc/version.properties, and gradle/libs.versions.toml (this line keeps the authoritative dependency version in the Gradle version catalog). Reviewers: this commit is deliberately separate from the backport itself. If the 2.19.7 bump is owned by the release process rather than a feature backport, drop this commit and re-target the two guards at whatever the next unreleased 2.19 constant becomes -- but they must not be left on Version.CURRENT. Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: dongkyun-yoo <dongkyun.yoo@linecorp.com>
References the original main-line PRs. Add this PR's own link alongside them once the number is assigned. Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: dongkyun-yoo <dongkyun.yoo@linecorp.com>
PR Reviewer Guide 🔍(Review updated until commit fa3f261)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to fa3f261 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit 698afcd
Suggestions up to commit 698afcd
Suggestions up to commit 5f3ea85
Suggestions up to commit ae988e9
|
|
❌ Gradle check result for ae988e9: 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? |
`gradle check` failed on `spotlessJavaCheck`. The 2.19 import renames in the adaptation commit changed the sort order of three files: - RestModifyDataStreamsAction: org.opensearch.client.node.NodeClient now sorts before org.opensearch.core.xcontent.XContentParser - ModifyDataStreamsAction: org.opensearch.action.support.clustermanager.* now sorts before org.opensearch.action.support.master.* - MetadataDataStreamsService: a leftover double blank line Produced by `./gradlew spotlessApply`; no logic changes. `./gradlew precommit` now passes end to end (spotlessJavaCheck, licenseHeaders, forbiddenApis*, thirdPartyAudit, jarHell, loggerUsageCheck, validatePom). Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: dongkyun-yoo <dongkyun.yoo@linecorp.com>
|
Persistent review updated to latest commit 5f3ea85 |
|
❌ Gradle check result for 5f3ea85: 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 698afcd |
|
❌ Gradle check result for 698afcd: 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 698afcd |
|
❕ Gradle check result for 698afcd: 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. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 2.19 #22849 +/- ##
============================================
+ Coverage 71.92% 72.29% +0.36%
+ Complexity 66009 64698 -1311
============================================
Files 5342 5107 -235
Lines 307392 300124 -7268
Branches 44862 44119 -743
============================================
- Hits 221105 216963 -4142
+ Misses 67823 65037 -2786
+ Partials 18464 18124 -340 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Codecov reported 68.55% patch coverage on this PR, with 89 missing lines and 20 partials. The gap was almost entirely a test-type mismatch rather than untested code: gradle/code-coverage.gradle sets `testType = TestSuiteType.UNIT_TEST`, so ModifyDataStreamsIT and DataStreamRestoreAutoAttachIT — which do exercise these paths — contribute nothing to the coverage report. These are unit tests covering the same behaviour. New: - DataStreamActionTests (37) — the Type enum including fromValue's rejection path, the constructor's requireNonNull branches, both factories, the StreamInput/writeTo round trip, fromXContent/toXContent for both action types plus malformed input, and equals/hashCode/toString. This class carries the most hand-written code on this line: on main it is a Java record, and 2.19's server compiles with -source 11, so it was converted to a final class with an explicit canonical constructor and hand-written equals/hashCode/toString. - RestModifyDataStreamsActionTests (15) — getName, routes, body parsing for both action types, several actions in one body, and the malformed-body paths. - ModifyDataStreamsTransportActionTests (8) — the transport action, cluster block check and response reading. - RestoreSnapshotRequestBuilderTests (1) — the setAttachToDataStream setter. Extended: - MetadataDataStreamsServiceTests (18 -> 38) — every validateTimestampFieldMapping rejection branch, the last-backing-index and write-index guards, the unhide skip conditions, multi-action composition across two streams, and the cluster-manager task submission path. - ModifyDataStreamsRequestTests (3 -> 11), RestoreServiceTests (9 -> 11), DataStreamTests, RestoreSnapshotRequestTests — the remaining branches. 143 tests across the nine classes, 0 failures. Coverage read from the JaCoCo report rather than estimated: RestModifyDataStreamsAction, DataStreamAction, RestoreService.attachRestoredBackingIndices and DataStream.parseDataStreamName reach 100% line and branch. Two branches are deliberately left uncovered because they are unreachable without changing production code, and are documented here rather than worked around: - MetadataDataStreamsService's `default:` arm on the action-type switch. DataStreamAction.Type declares exactly two constants and both have explicit cases, so the generated switch map cannot index past them; a null type cannot reach it either (switch on a null enum throws, and the constructor rejects null). DataStreamAction is final and this repository's MockMaker is subclass-based, so a fake type cannot be substituted. - The `actions == null` side of ModifyDataStreamsAction.Request's validation. The field is private final and neither assignment can yield null. Both become live only if a third Type constant is added. No production code is modified. Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: dongkyun-yoo <dongkyun.yoo@linecorp.com>
|
Persistent review updated to latest commit fa3f261 |
Description
Manual backport of two main-line commits to
2.19:POST /_data_stream/_modifywithadd_backing_index/remove_backing_index(metadata-only, applied atomically in a single cluster-state update)attach_to_data_streamoption on the snapshot restore API, which attaches a restored.ds-<stream>-NNNNNNindex to a pre-existing data stream of the same name in the same cluster-state updateBackported manually rather than by the bot because the two commits do not compile on this
line unchanged. Both cherry-picks apply to
2.19with zero conflicts; the compilefailures only surface afterwards, and are addressed in a separate commit so the backport
content stays reviewable against the originals.
Why this matters on 2.19
Deleting a data stream backing index silently detaches it, and until #22487/#22539 there was no way to put it back —
_data_stream/_modifyreturns 405 andattach_to_data_streamis an unknown parameter. Any workflow that takes a backing index out of the cluster and restores it (searchable-snapshot mounts, cold-to-warm moves, restoring a RED backing index from a snapshot) leaves the data behind as an index that is no longer reachable by the data stream name. #8271 describes the same problem from the cold-to-warm restore side.2.19 API adaptations (commit 3)
ClusterManagerTaskenum +registerClusterManagerTask(ClusterManagerTask, boolean)ClusterManagerTaskKeysString constants +registerClusterManagerTask(String, boolean). AddsMODIFY_DATA_STREAM_KEY = "modify-data-stream", same wire string and throttling behaviour, matchingMetadataCreateDataStreamServiceDataStreamActionas a Javarecordservercompiles with-source 11; converted to a final class with an explicit canonical constructor (keeping therequireNonNullchecks), identical accessor names, hand-writtenequals/hashCode/toStringorg.opensearch.action.support.clustermanager.Acknowledged{Request,Response}org.opensearch.action.support.master.*(matchesCreateDataStreamAction)org.opensearch.transport.client.node.NodeClientorg.opensearch.client.node.NodeClient(matchesRestCreateDataStreamAction)clusterManagerNodeTimeout(TimeValue)masterNodeTimeout(TimeValue)No production code and no tests were dropped. Features adjacent to the backported hunks that do not exist on this line (
AliasWriteIndexPolicyinRestoreSnapshotRequest,ViewServiceinNode,wlm_stats_listinRestHighLevelClientTests) were excluded rather than pulled in.The version constant — please read (commit 4)
#22539 guards the
RestoreSnapshotRequest.attachToDataStreamwire field onVersion.V_3_8_0, which does not exist here. The guard has to key on a version that no already-released node reports. Keying it onVersion.CURRENT(==V_2_19_6) is not a degraded workaround — it is an outage:Testing
Run on this branch, JDK 21, macOS arm64:
:server:test— 15423 tests, 0 failures, 0 errors, 67 skipped across 1841 suites(
BUILD SUCCESSFUL; counts read from the JUnit XML, not the console)VersionTests27/27 (the suite that polices the bump),RestoreSnapshotRequestTests,RestoreServiceTests,MetadataDataStreamsServiceTests,DataStreamTests,ModifyDataStreamsRequestTests:server:internalClusterTest—ModifyDataStreamsIT7/7 andDataStreamRestoreAutoAttachIT2/2, i.e. the suites the two PRs bring with themWorth flagging for reviewers:
RestoreSnapshotRequestTestscannot catch a wrong versionguard. It extends
AbstractWireSerializingTestCase, andAbstractWireTestCasehardcodes the round-trip atVersion.CURRENT, so both guards are trivially symmetric there. Reverting the guard to the known-badVersion.CURRENTform still yieldstests=3 failures=0. Mixed-version safety on this change can only be established at the node level.