fix(opensearch): configurable shadow write log level + phase-aware index tests (#35302)#35389
Conversation
…e-and-forget tests (#35302) Introduces DOTCMS_SHADOW_WRITE_LOG_LEVEL (default WARN) so operators can raise shadow OS write failures to ERROR during QA or lower them to DEBUG during steady-state migration, without code changes. Adds PhaseRouterTest covering all four migration phases with the mismatched-index-name scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…entletIndexAPIImpl Adds ContentletIndexAPIImplPhaseTest (unit, full mock injection) and ContentletIndexAPIImplMigrationIT (integration, requires two real clusters) covering deactivateIndex, createContentIndex, closeIndex, and activateIndex across all four migration phases. Includes a full-dependency constructor on ContentletIndexAPIImpl for isolated unit testing without APILocator. Registers ContentletIndexAPIImplMigrationIT in OpenSearchUpgradeSuite. Refs: #35302 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ContentIndex addCustomMapping was called unconditionally after a failed index creation, which could apply mappings to a non-existent index and surface an unrelated exception instead of the clean false-return the soft-failure contract promises. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 3m 16s —— View job PR Review
Issues Found1. Shadow failure logging not applied in
// ContentletIndexAPIImpl.java ~line 601
} catch (Exception e) {
Logger.error(this.getClass(), "Error while creating content index " + physicalName, e);
if (ops == primary) {
primaryResult = false;
}
// shadow failures are fire-and-forget in dual-write phases ← comment contradicts the code
}Every other write path in this PR ( 2. Same pattern missed in // ContentletIndexAPIImpl.java ~line 2464
} catch (Exception e) {
Logger.warn(this, "Could not mirror index activation to OS store for index: " + indexName, e);
}This is a shadow write failure and should be 3. final List<T> providers = writeProviders(); // calls MigrationPhase.current() x2
...
final T primary = readProvider(); // calls MigrationPhase.current() againIf 4. The test file covers all four write paths (
These are the most complex paths in 5. The old default was Design concern worth calling outThe test This means the OS pointer store can get a name that does not exist in the OS cluster, and any subsequent Otherwise the core logic is sound: the |
- Logger: add info(Class, String, Throwable) overload so INFO-level log calls can forward the throwable just like DEBUG/WARN/ERROR do - IndexConfigHelper: pass throwable `t` in the INFO branch of logShadowWriteFailure so operators don't silently lose stack traces - ContentletIndexAPIImpl: guard addCustomMapping behind if(contentIndex) in the private createContentIndex(String,int,IndexTag) overload to match the public overload's behaviour; annotate queueApi with @nullable to document the testing-constructor invariant - ContentletIndexAPIImplMigrationIT: trim both operands in esSameAsOs() to avoid false negatives from trailing whitespace; add .onFailure() warnings to tearDown Try.run calls so DB-restore failures are visible Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…aration - ContentletIndexAPIImpl.createContentIndex(String,int): replace the shared `result &=` accumulator with per-provider tracking so a shadow write failure in dual-write phases does not prevent addCustomMapping from running against the successfully-created primary index - PhaseRouter.writeBoolean: initialize primaryResult to false (safe default) instead of true — makes the silent invariant explicit: "assume failure until the primary provider confirms success" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Summary
DOTCMS_SHADOW_WRITE_LOG_LEVEL(defaultWARN) so operators can tune shadow OS write failure verbosity at runtime without code changesPhaseRouterTestcovering all four migration phases with the mismatched-index-name (T0/T1) scenarioContentletIndexAPIImplPhaseTest— fully isolated unit tests fordeactivateIndex,createContentIndex,closeIndex, andactivateIndexusing a full-dependency constructor (noAPILocator)ContentletIndexAPIImplMigrationIT— integration tests requiring two real clusters, covering ghost-index and invalid-name edge cases across phases; registered inOpenSearchUpgradeSuiteaddCustomMappingbehindif (result)increateContentIndex— prevents applying mappings to an index the cluster rejected, keeping the soft-failure contract consistentTest plan
ContentletIndexAPIImplPhaseTest—./mvnw test -pl :dotcms-core -Dtest=ContentletIndexAPIImplPhaseTestPhaseRouterTest—./mvnw test -pl :dotcms-core -Dtest=PhaseRouterTestContentletIndexAPIImplMigrationIT—./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dopensearch.upgrade.test=true -Dit.test=ContentletIndexAPIImplMigrationITOpenSearchUpgradeSuitetestsCloses #35302
🤖 Generated with Claude Code
This PR fixes: #35302