feat(datastream): add Modify Data Streams API to add/remove backing indices#22316
feat(datastream): add Modify Data Streams API to add/remove backing indices#22316madhankb wants to merge 7 commits into
Conversation
PR Reviewer Guide 🔍(Review updated until commit da4fbd6)Here are some key observations to aid the review process:
|
|
Persistent review updated to latest commit 58474de |
PR Code Suggestions ✨Latest suggestions up to da4fbd6 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit f38773a
Suggestions up to commit bd3f8dd
Suggestions up to commit 58474de
|
|
❌ Gradle check result for 58474de: 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? |
|
Background: |
Introduces the _data_stream/_modify API allowing backing indices to be added to or removed from one or more data streams in a single atomic cluster-state update. - DataStreamAction: add/remove operation model with XContent parsing - ModifyDataStreamsAction: ActionType, Request, and transport action - RestModifyDataStreamsAction: POST /_data_stream/_modify endpoint - DataStream.addBackingIndex / removeBackingIndex with validation (cannot remove the write index; index must belong to the stream) - DataStreamFieldMapper: allow enabling _data_stream_timestamp on mapping merge but never disabling it once enabled - Client wiring (IndicesAdminClient, AbstractClient) + async variant - MODIFY_DATA_STREAM cluster-manager task registration - Unit tests, internal cluster IT, and rest-api-spec + YAML tests Signed-off-by: Madhan <mkbn@amazon.com>
…oss-stream guard Signed-off-by: Madhan <mkbn@amazon.com>
Signed-off-by: Madhan <mkbn@amazon.com>
|
Persistent review updated to latest commit bd3f8dd |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #22316 +/- ##
============================================
+ Coverage 73.38% 73.40% +0.01%
- Complexity 76071 76093 +22
============================================
Files 6076 6076
Lines 345461 345488 +27
Branches 49723 49728 +5
============================================
+ Hits 253527 253597 +70
+ Misses 71731 71694 -37
+ Partials 20203 20197 -6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
RestHighLevelClientTests.testApiNamingConventions asserts that every API in the rest-api-spec is either implemented in the High-Level REST Client or listed in an allowlist. The new indices.modify_data_stream spec is not implemented in the HLRC (which is deprecated and no longer extended for new APIs), so it must be listed in notRequiredApi - consistent with how other recent server-only APIs (e.g. wlm_stats_list) are handled. Fixes the gradle-check failure in :client:rest-high-level:test. Signed-off-by: Madhan <mkbn@amazon.com>
…; use value equality in mapper Signed-off-by: Madhan <mkbn@amazon.com>
…meta-field merge Covers the previously-untested added lines reported by codecov/patch: - DataStream.addBackingIndex (inserts at head, generation unchanged) - DataStream.removeBackingIndex validation (index not in stream; write index) - DataStreamFieldMapper merge validator (enable on merge allowed; disable once enabled rejected) Signed-off-by: Madhan <mkbn@amazon.com>
|
Persistent review updated to latest commit f38773a |
The IndicesAdminClient.modifyDataStream overloads, AbstractClient implementations, and the modifyDataStreamAsync CompletionStage default were only exercised by the internal cluster IT, whose coverage is not included in the unit-test jacoco report that gradle-check uploads to codecov. That left these newly added lines uncovered in the patch report, dropping codecov/patch below its target. Add a fast :server:test using NoOpClient to drive all three client entrypoints through the real AbstractClient wiring, covering the previously missed lines. Signed-off-by: Madhan <mkbn@amazon.com>
|
Persistent review updated to latest commit da4fbd6 |
Intent
Add a new Modify Data Streams API to OpenSearch: a POST /_data_stream/_modify endpoint that adds or removes backing indices across one or more data streams in a single atomic cluster-state update. New DataStreamAction models an add/remove-backing-index op (PublicApi since 3.8.0). ModifyDataStreamsAction provides the ActionType, Request, and TransportClusterManagerNodeAction applying changes via a ClusterStateUpdateTask; MODIFY_DATA_STREAM cluster-manager task registered. DataStream gains addBackingIndex (inserts at head so write index preserved) and removeBackingIndex throws when index is absent or is the write index. DataStreamFieldMapper merge validator allows enabling _data_stream_timestamp on a mapping merge but never disabling it once enabled. Client wired via IndicesAdminClient/AbstractClient with a CompletionStage async variant. Includes unit tests, an internal cluster IT, and rest-api-spec JSON + YAML tests.
This branch already incorporates: (1) four review fixes the user approved earlier (null-safe data stream lookup avoiding NPE; MapperService closed in finally to avoid analyzer leak; per-action data_stream/index marked required; addBackingIndex rejects an index already belonging to a DIFFERENT data stream); and (2) a fix for the gradle-check failure - RestHighLevelClientTests.testApiNamingConventions failed because the new indices.modify_data_stream rest-api-spec was neither implemented in the High-Level REST Client (deprecated, not extended for new APIs) nor allowlisted. The fix adds indices.modify_data_stream to the notRequiredApi allowlist, consistent with other server-only APIs like wlm_stats_list. Verified locally: :client:rest-high-level:test --tests RestHighLevelClientTests.testApiNamingConventions now passes. This is the only known CI failure (the sibling check-result job just mirrors gradle-check's result). Excluded local artifacts: .osd-toolchain/ and modify-data-stream-test-guide.html.
What Changed
POST /_data_stream/_modifyendpoint that adds or removes backing indices across one or more data streams in a single atomic cluster-state update, via a newDataStreamActionop model,ModifyDataStreamsAction(ActionType, Request, andTransportClusterManagerNodeActionapplying changes through aClusterStateUpdateTask),RestModifyDataStreamsAction, and a registeredMODIFY_DATA_STREAMcluster-manager task.DataStreamwithaddBackingIndex(inserts at the head so the write index is preserved, and rejects an index already belonging to a different stream or whose name conflicts with the stream generation) andremoveBackingIndex(rejects an absent index or the write index); relaxes theDataStreamFieldMappermerge validator to allow enabling_data_stream_timestampon a mapping merge but never disabling it once enabled.IndicesAdminClient/AbstractClient(with aCompletionStageasync variant), adds theindices.modify_data_streamrest-api-spec JSON (allowlisted inRestHighLevelClientTests) plus YAML REST tests, and adds unit tests, request tests, and an internal cluster IT.Risk Assessment
✅ Low: The two substantive issues from the prior round are correctly fixed with dedicated tests, the residual cross-stream generation-conflict case is provably unreachable, and the only remaining note is an intentional, upstream-consistent behavior change.
Testing
Exercised the new POST /_data_stream/_modify API end-to-end against a real OpenSearch node built from the target commit, capturing a live curl/HTTP transcript that shows add, remove, atomic add+remove, standalone-index adoption, auto hide/un-hide, generation preservation, and every error guard returning HTTP 400 with the correct message (including the generation-conflict and cross-stream review fixes). All supporting automated layers also pass: 27 datastream unit tests, the 3-scenario internal cluster IT, the 3-scenario rest-api-spec YAML test against a real cluster, and the HLRC naming-conventions test that was the previously-known gradle-check failure. No code or test changes were needed; the working tree is clean and the test node was shut down.
Evidence: Live HTTP transcript of POST /_data_stream/_modify against a real OpenSearch node
Evidence: Core add/remove behavior shown live over HTTP
Evidence: Error/validation guards returning HTTP 400 (incl. review fixes)
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
server/src/main/java/org/opensearch/action/admin/indices/datastream/ModifyDataStreamsAction.java:355- Adding a standalone index whose name matches the backing-index pattern with a counter greater than the data stream's generation (e.g. add.ds-foo-000099to data streamfooat generation 2) causesMetadata.Builder.build()->validateDataStreamsto throwIllegalStateException. Thrown from insideClusterStateUpdateTask.execute(), this surfaces to the client as HTTP 500 rather than a 400 bad_request, unlike the other add-index validation failures which throwIllegalArgumentException. Consider validating the candidate index name against the stream generation inaddBackingIndexand throwingIllegalArgumentException.server/src/main/java/org/opensearch/action/admin/indices/datastream/ModifyDataStreamsAction.java:267- Thetimeout(ack) parameter is parsed in RestModifyDataStreamsAction and advertised in the rest-api-spec JSON, but the transport action uses a plainClusterStateUpdateTaskand unconditionally returnsnew AcknowledgedResponse(true)inclusterStateProcessedwithout waiting for node acknowledgements withintimeout. The acknowledgement is therefore alwaystrueandtimeouthas no effect (weaker semantics thanAckedClusterStateUpdateTaskused by sibling metadata-write APIs). This mirrors upstream behavior, so likely intentional — flagging that the documentedtimeoutparam is a no-op.server/src/main/java/org/opensearch/index/mapper/DataStreamFieldMapper.java:54- The merge validator(previous, current) -> previous == current || (previous == false && current)compares two boxedBooleanvalues with==(reference equality) in theprevious == currentterm. It happens to work because ofBooleanautobox caching and the second clause handles the false->true case, and the disable-after-enable logic is correct, but reference comparison of boxed types is a fragile pattern. Use value comparison (e.g.previous.equals(current)orObjects.equals).🔧 Fix: Reject generation-conflicting backing index name; use value equality in mapper
1 info still open:
server/src/main/java/org/opensearch/index/mapper/DataStreamFieldMapper.java:55- The new merge validator(previous, current) -> Objects.equals(previous, current) || (previous == false && current)changes the global merge behavior of the_data_stream_timestampmeta field for ALL indices, not just those adopted via the new modify API. Previously (default validator with updateable=false)enabledcould not change on any mapping merge; now a MAPPING_UPDATE can flip it false->true on any index. This is required for the adopt-an-index path and matches upstream Elasticsearch behavior, but it also means a user could enable_data_stream_timestampon a regular (non-data-stream) index via PUT _mapping, which would then require every document to carry a single-valued timestamp field. Flagging as an intentional, upstream-consistent widening of merge semantics for awareness; no action needed.✅ **Test** - passed
✅ No issues found.
./gradlew :server:test --tests org.opensearch.action.admin.indices.datastream.*— 27 unit tests pass (ModifyDataStreamsActionTests=16, ModifyDataStreamsRequestTests=5, DataStreamActionTests=6)./gradlew :rest-api-spec:yamlRestTest --tests org.opensearch.test.rest.ClientYamlTestSuiteIT -Dtests.method="*modify_data_stream*"— 3 YAML REST scenarios pass against a real cluster (remove+add, reject write-index removal, reject empty actions)./gradlew :server:internalClusterTest --tests org.opensearch.action.admin.indices.datastream.ModifyDataStreamsIT— 3 IT scenarios pass (testRemoveThenAddBackingIndex, testAddStandaloneIndexAsBackingIndex, testRemoveWriteIndexFails)./gradlew :client:rest-high-level:test --tests org.opensearch.client.RestHighLevelClientTests.testApiNamingConventions— passes (prior gradle-check failure now green)Manual live HTTP transcript via./gradlew :run+ curl against localhost:9200: PUT data-stream template, create data stream, 2x rollover, then POST /_data_stream/_modify for remove / add / adopt-standalone / atomic add+remove and 5 rejected error cases (write-index, empty-actions, cross-stream, aliases, generation-conflict)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.