Description
Tests cannot currently control which search store (ElasticSearch, OpenSearch, or both) a contentlet is indexed into when created through ContentletDataGen. This blocks straightforward verification of the ES→OS dual-write pipeline from the data-generation layer.
Current state (confirmed in code)
- Indexing seam:
ContentletDataGen.persist() → checkin(contentlet, policy) (dotcms-integration/src/test/java/com/dotcms/datagen/ContentletDataGen.java:227).
ContentletDataGen.setPolicy(IndexPolicy) controls when indexing happens (FORCE / WAIT_FOR / NONE) — but not which store receives the write.
- The migration phase is global only: tests flip
Config.setProperty("FEATURE_FLAG_OPEN_SEARCH_PHASE", "1") by hand.
- Routing lives in
PhaseRouter (ContentletIndexAPIImpl.java:173); phase → write-providers is 0:[ES] 1:[ES,OS] 2:[ES,OS] 3:[OS].
- The existing migration ITs (
ContentletIndexAPIImplMigrationIntegrationTest, ContentletIndexAPIImplPhaseSwitchIntegrationTest) create indices directly via esImpl().createIndex() / osIndexAPI.createIndex() — none drive ContentletDataGen inside a phase context. That is the missing capability.
Proposed approach
Add a phase override to ContentletDataGen, applied per-checkin around the checkin() call in persist(), restoring the prior flag value in a finally:
private MigrationPhase overridePhase = null; // phase-oriented (primary)
public ContentletDataGen migrationPhase(final MigrationPhase phase) {
this.overridePhase = phase;
return this;
}
// store-oriented convenience, implemented on top of the phase override
public enum TargetStore { ES, OS, BOTH }
public ContentletDataGen targetStore(final TargetStore store) {
this.overridePhase = switch (store) {
case ES -> MigrationPhase.PHASE_0_MIGRATION_NOT_STARTED; // ES only
case OS -> MigrationPhase.PHASE_3_OPENSEARCH_ONLY; // OS only
case BOTH -> MigrationPhase.PHASE_1_DUAL_WRITE_ES_READS; // dual write
};
return this;
}
@Override
public Contentlet persist(final Contentlet contentlet) {
final String prior = Config.getStringProperty(MigrationPhase.FLAG_KEY, null); // save prior
try {
if (overridePhase != null) {
Config.setProperty(MigrationPhase.FLAG_KEY, String.valueOf(overridePhase.ordinal()));
}
return checkin(contentlet, null != policy ? policy : IndexPolicy.FORCE);
} finally {
if (overridePhase != null) {
Config.setProperty(MigrationPhase.FLAG_KEY, prior); // restore prior, NOT null
}
}
}
Usage:
new ContentletDataGen(contentType)
.targetStore(TargetStore.BOTH) // or .migrationPhase(PHASE_1_DUAL_WRITE_ES_READS)
.setPolicy(IndexPolicy.WAIT_FOR)
.nextPersisted(); // → written to both ES and OS
Acceptance Criteria
Out of Scope
IndexAPI<F> generic parameterization (tracked separately, must not be bundled here).
- Any change to production routing /
PhaseRouter behavior — this is test-infrastructure only.
Additional Context
- Architecture reference:
docs/backend/OPENSEARCH_MIGRATION.md.
MigrationPhase enum + FLAG_KEY: dotCMS/src/main/java/com/dotcms/content/index/IndexConfigHelper.java.
- All migration tests must be isolated in
OpenSearchUpgradeSuite (project convention).
Priority: Medium
Progress
- Branch:
issue-36266-contentletdatagen-phase-control
- ✅
ContentletDataGen capability implemented (migrationPhase(...) + targetStore(...), per-checkin scope, restore-prior + restore-on-throw, applied to both persist(...) overloads and nextPersistedAndPublish()) — compiles (BUILD SUCCESS).
- ⏳ Store-placement IT in
OpenSearchUpgradeSuite — pending; requires a live two-store (opensearch-upgrade) environment to author/verify and works around the known content-lifecycle bootstrap gap (inferIndexToHit on un-bootstrapped VersionedIndices).
Description
Tests cannot currently control which search store (ElasticSearch, OpenSearch, or both) a contentlet is indexed into when created through
ContentletDataGen. This blocks straightforward verification of the ES→OS dual-write pipeline from the data-generation layer.Current state (confirmed in code)
ContentletDataGen.persist()→checkin(contentlet, policy)(dotcms-integration/src/test/java/com/dotcms/datagen/ContentletDataGen.java:227).ContentletDataGen.setPolicy(IndexPolicy)controls when indexing happens (FORCE/WAIT_FOR/NONE) — but not which store receives the write.Config.setProperty("FEATURE_FLAG_OPEN_SEARCH_PHASE", "1")by hand.PhaseRouter(ContentletIndexAPIImpl.java:173); phase → write-providers is0:[ES] 1:[ES,OS] 2:[ES,OS] 3:[OS].ContentletIndexAPIImplMigrationIntegrationTest,ContentletIndexAPIImplPhaseSwitchIntegrationTest) create indices directly viaesImpl().createIndex()/osIndexAPI.createIndex()— none driveContentletDataGeninside a phase context. That is the missing capability.Proposed approach
Add a phase override to
ContentletDataGen, applied per-checkin around thecheckin()call inpersist(), restoring the prior flag value in afinally:Usage:
Acceptance Criteria
ContentletDataGenexposes a phase-oriented builder method (migrationPhase(MigrationPhase)) that overrides the active migration phase only for the duration of the persist/checkin.ContentletDataGenexposes a store-oriented convenience method (targetStore(ES | OS | BOTH)) implemented on top of the phase override (ES→Phase 0, OS→Phase 3, BOTH→dual-write Phase 1).checkin()call inpersist()and restored afterward — sequentialnextPersisted()calls on different gens do not leak phase state.checkin()throws (restore lives in afinally).setPolicy(...)(e.g..migrationPhase(...).setPolicy(WAIT_FOR)), andWAIT_FORstill allows immediate assertion of index contents.nextPersistedAndPublish, etc.) honor the same phase override.dotcms-integration, ends in the literalITsuffix, and is registered inOpenSearchUpgradeSuite. It asserts:BOTH(dual-write) → contentlet present in both ES and OS indices.OS(Phase 3) → present in OS, absent from ES.ES(Phase 0) → present in ES, absent from OS.Out of Scope
IndexAPI<F>generic parameterization (tracked separately, must not be bundled here).PhaseRouterbehavior — this is test-infrastructure only.Additional Context
docs/backend/OPENSEARCH_MIGRATION.md.MigrationPhaseenum +FLAG_KEY:dotCMS/src/main/java/com/dotcms/content/index/IndexConfigHelper.java.OpenSearchUpgradeSuite(project convention).Priority: Medium
Progress
issue-36266-contentletdatagen-phase-controlContentletDataGencapability implemented (migrationPhase(...)+targetStore(...), per-checkin scope, restore-prior + restore-on-throw, applied to bothpersist(...)overloads andnextPersistedAndPublish()) — compiles (BUILD SUCCESS).OpenSearchUpgradeSuite— pending; requires a live two-store (opensearch-upgrade) environment to author/verify and works around the known content-lifecycle bootstrap gap (inferIndexToHiton un-bootstrappedVersionedIndices).