fix(index): deactivateIndex must not orphan the OS store when the last index is removed (#36501)#36502
Conversation
…t index is removed (#36501) When the deactivated slot was the last populated one in the OpenSearch VersionedIndices store, the rebuilt record is empty and saveIndices rejects it by contract ("At least one index must be specified"). Before this change that meant, under a non-zero migration phase: - Phase 3 (OS primary, no try/catch): the exception propagated and deactivateIndex failed hard. - Phases 1/2 (best-effort OS mirror): the exception was swallowed with a warning, leaving a stale/dangling store row that initOSCatchup would treat as authoritative on the next restart (same failure mode as issue #35640). Extract the OS-mirror rebuild into a shared helper (deduplicating the phase-3 and phase-1/2 blocks) that removes the version row instead of saving an empty record — parity with clearOsStorePointer's #35640 guard. Adds DeactivateIndexEmptyStoreIT (registered in OpenSearchUpgradeSuite) covering deactivate-last-index under phase 3 (no throw, store cleared) and phase 1 (no stale row). Found while enabling phased integration runs (#36320). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 1m 27s —— View job Rollback-Safety Analysis
Verdict: No unsafe category matched. Summary of what changed:
Checked against every category in the reference doc:
This PR is safe to roll back. |
|
Tick the box to add this pull request to the merge queue (same as
|
Proposed Changes
Fixes the first product finding tracked in #36501, surfaced while enabling phased integration runs (#36320).
ContentletIndexAPIImpl.deactivateIndex(String)rebuilds the OpenSearchVersionedIndicesstore without the deactivated slot. When that slot was the last populated one, the rebuilt record is empty andVersionedIndicesAPI.saveIndicesrejects it by contract ("At least one index must be specified"). Under a non-zero migration phase this caused:deactivateIndexfailed hard 🔴The stale row is exactly what
clearOsStorePointerwas written to prevent for #35640: on the next restartinitOSCatchupwould treat it as authoritative and recreate the deleted index empty.Fix
mirrorDeactivateToOsStore(indexName), deduplicating the near-identical phase-3 and phase-1/2 blocks.clearOsStorePointer: if the rebuild is empty, remove the version row (removeVersion(OPENSEARCH_3X)) instead of saving an empty record. This gives the OS versioned store the same "empty = OK" semantics the legacy ES path already has.Test
DeactivateIndexEmptyStoreIT(registered inOpenSearchUpgradeSuite) — deactivates the last index under phase 3 (asserts no throw + store cleared) and phase 1 (asserts no stale row). Verified locally:Tests run: 2, Failures: 0.Checklist
opensearch-upgradeprofile).clearOsStorePointer[QA-G6] OpenSearch Migration — Index Delete: ES vs OS Behavior (TC-016–TC-020) #35640 guard.Related: #36501 (tracking), #36320 (phase-testing tooling), #35640 (original store-pointer guard).
This PR fixes: #36501