Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,57 @@ public class TestSuiteBootstrap implements LauncherSessionListener {
private static final Integer ELASTIC_BATCH_SIZE = 10;
private static final IndexMappingLanguage ELASTIC_SEARCH_INDEX_MAPPING_LANGUAGE =
IndexMappingLanguage.EN;
private static final String ELASTIC_SEARCH_CLUSTER_ALIAS = "openmetadata";

/**
* Pattern allowed for {@code -DclusterAlias} overrides — must be a valid OpenSearch /
* Elasticsearch index name prefix (lowercase alphanumeric, underscore, or hyphen; must start
* with a letter or digit; max 63 chars).
*
* <p>Declared <em>before</em> {@link #ELASTIC_SEARCH_CLUSTER_ALIAS} on purpose: static fields
* initialize in declaration order, and {@link #resolveClusterAlias()} reads this pattern. If
* this declaration moved below, override validation would NPE on the only path that uses it.
*/
private static final java.util.regex.Pattern CLUSTER_ALIAS_PATTERN =
java.util.regex.Pattern.compile("[a-z0-9][a-z0-9_\\-]{0,62}");

/**
* Cluster alias used as the prefix for all search indices in this test session.
*
* <p>The OpenSearch / Elasticsearch testcontainer is shared across the entire JUnit launcher
* session (single static container, see {@link #SEARCH_CONTAINER}). When tests run in parallel
* (the {@code parallel-tests} profile sets {@code junit.jupiter.execution.parallel.enabled=true}
* and {@code reuseForks=true} keeps everything in one JVM), every test reads and writes against
* the same set of indices. {@link org.openmetadata.it.util.TestNamespace} only isolates entity
* FQNs in the database — it does not isolate documents in the search index.
*
* <p>To prevent cross-test pollution between concurrent CI runs that share the cluster, the alias
* is randomized per session by default so each session writes to its own {@code <alias>_*}
* indices. Set {@code -DclusterAlias=openmetadata} (or any fixed value matching {@link
* #CLUSTER_ALIAS_PATTERN}) to pin the alias for reproducible debugging.
*/
private static final String ELASTIC_SEARCH_CLUSTER_ALIAS = resolveClusterAlias();

Comment on lines +137 to +138
private static String resolveClusterAlias() {
String override = System.getProperty("clusterAlias");
if (override == null || override.isBlank()) {
return "omtest_" + java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
String normalized = override.trim().toLowerCase(java.util.Locale.ROOT);
if (!CLUSTER_ALIAS_PATTERN.matcher(normalized).matches()) {
throw new IllegalArgumentException(
"Invalid -DclusterAlias='"
+ override
+ "'. Must match "
+ CLUSTER_ALIAS_PATTERN.pattern()
+ " (lowercase alphanumeric, underscore, or hyphen; must start with a letter or"
+ " digit; max 63 chars) so it forms a valid OpenSearch/Elasticsearch index prefix.");
}
return normalized;
}
Comment on lines +139 to +155

public static String getClusterAlias() {
return ELASTIC_SEARCH_CLUSTER_ALIAS;
}
Comment on lines +110 to +159

// Default images (can be overridden by system properties)
private static final String DEFAULT_POSTGRES_IMAGE = "postgres:15";
Expand Down Expand Up @@ -164,6 +214,7 @@ public void launcherSessionOpened(LauncherSession session) {
LOG.info("=== TestSuiteBootstrap: Starting test infrastructure ===");
LOG.info("Database type: {}", databaseType);
LOG.info("Search type: {}", searchType);
LOG.info("Search cluster alias: {}", ELASTIC_SEARCH_CLUSTER_ALIAS);
LOG.info("RDF enabled: {}", rdfEnabled);
Comment thread
mohityadav766 marked this conversation as resolved.
LOG.info("Cache provider: {}", cacheProvider);
boolean k8sEnabled = isK8sTestsRequested();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5312,12 +5312,20 @@ void deleteTagAndCheckRelationshipsInSearch(TestNamespace ns) throws Exception {
}

/**
* Search for a specific entity by ID.
* Subclasses should override to use entity-specific search.
* Search for a specific entity by ID. Uses the {@code id.keyword} subfield so the lookup is an
* exact-match term query: the {@code id} field is mapped as {@code text} on every entity index
* and the analyzer tokenises UUIDs on dashes/digits, which makes a plain {@code id:<uuid>}
* query a tokenised match that ranks-and-trims under {@code size=1} and races other docs with
* overlapping hex tokens. Subclasses should override to use entity-specific search.
*/
protected String searchForEntity(String entityId) throws Exception {
OpenMetadataClient client = SdkClients.adminClient();
return client.search().query("id:" + entityId).index(getSearchIndexName()).size(1).execute();
return client
.search()
.query("id.keyword:" + entityId)
.index(getSearchIndexName())
.size(1)
.execute();
}

protected String searchEntities() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ private Map<String, Integer> getAllDataProductsWithAssetsCount() throws Exceptio

private List<EntityReference> getEntityReferencesFromSearchIndex(
UUID entityId, String indexName, String fieldName) throws Exception {
String query = "id:" + entityId.toString();
String query = "id.keyword:" + entityId.toString();
String searchResponse =
SdkClients.adminClient().search().query(query).index(indexName).execute();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ private void verifyDomainInSearch(String expectedFqn, String domainId) {
String searchResponse =
client
.search()
.query("id:" + domainId)
.query("id.keyword:" + domainId)
.index("domain_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -897,7 +897,7 @@ void test_renameDomainDoesNotMatchSimilarNames(TestNamespace ns) throws Exceptio
String searchResponse =
client
.search()
.query("id:" + child.getId())
.query("id.keyword:" + child.getId())
.index("domain_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -1090,7 +1090,7 @@ void test_renameDomainDoesNotAffectSimilarPrefixDomains(TestNamespace ns) throws
String searchResponse =
client
.search()
.query("id:" + child.getId())
.query("id.keyword:" + child.getId())
.index("domain_search_index")
.size(1)
.execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,12 +427,16 @@ private HttpResponse<String> exportGlossaryRaw(
default -> TURTLE_CONTENT_TYPE;
};

// RDF/XML serialization in Jena is significantly slower than Turtle/N-Triples/JSON-LD
// (O(N^2)-ish QName resolution) and contends with Quartz/WorkflowEventConsumer daemon
// threads that @Isolated does not stop. Observed ~69s server time in CI for what is
// typically a sub-second call; 180s gives headroom without masking real hangs.
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + token)
.header("Accept", acceptHeader)
.timeout(Duration.ofSeconds(60))
.timeout(Duration.ofSeconds(180))
.GET()
.build();
Comment on lines +430 to 441

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1085,40 +1085,43 @@ void patch_addDeleteReviewers(TestNamespace ns) {

@Test
void patch_addDeleteReferences(TestNamespace ns) {
OpenMetadataClient client = SdkClients.adminClient();
Glossary glossary = getOrCreateGlossary(ns);

// Create term without references
CreateGlossaryTerm request =
new CreateGlossaryTerm()
.withName(ns.prefix("term_references"))
.withGlossary(glossary.getFullyQualifiedName())
.withDescription("Term for reference patch test");
GlossaryTerm term = createEntity(request);
String termId = term.getId().toString();

// Add reference
// Refresh local state before each patch so the JSON diff does not accidentally include an
// entityStatus transition driven by the async GlossaryTermApprovalWorkflow (which can move
// the server-side status to IN_REVIEW between calls and would otherwise trip the reviewer
// check in EntityRepository.checkUpdatedByReviewer).
org.openmetadata.schema.api.data.TermReference ref1 =
new org.openmetadata.schema.api.data.TermReference()
.withName("reference1")
.withEndpoint(java.net.URI.create("http://reference1.example.com"));
term = getEntity(termId);
term.setReferences(List.of(ref1));
GlossaryTerm updated = patchEntity(term.getId().toString(), term);
GlossaryTerm updated = patchEntity(termId, term);
assertNotNull(updated.getReferences());
assertEquals(1, updated.getReferences().size());

// Add another reference
org.openmetadata.schema.api.data.TermReference ref2 =
new org.openmetadata.schema.api.data.TermReference()
.withName("reference2")
.withEndpoint(java.net.URI.create("http://reference2.example.com"));
updated = getEntity(termId);
updated.setReferences(List.of(ref1, ref2));
GlossaryTerm updated2 = patchEntity(updated.getId().toString(), updated);
GlossaryTerm updated2 = patchEntity(termId, updated);
assertNotNull(updated2.getReferences());
assertEquals(2, updated2.getReferences().size());

// Remove a reference
updated2 = getEntity(termId);
updated2.setReferences(List.of(ref2));
GlossaryTerm updated3 = patchEntity(updated2.getId().toString(), updated2);
GlossaryTerm updated3 = patchEntity(termId, updated2);
assertNotNull(updated3.getReferences());
assertEquals(1, updated3.getReferences().size());
}
Expand Down Expand Up @@ -1400,7 +1403,7 @@ void test_glossaryTermStatusTransitionUpdatesSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + updated.getId())
.query("id.keyword:" + updated.getId())
.index("glossary_term_search_index")
.size(5)
.execute();
Expand Down Expand Up @@ -2265,7 +2268,6 @@ void test_glossaryTermVersionIncrement(TestNamespace ns) {

@Test
void test_glossaryTermReviewersMultipleUpdates(TestNamespace ns) {
OpenMetadataClient client = SdkClients.adminClient();
Glossary glossary = getOrCreateGlossary(ns);

CreateGlossaryTerm request =
Expand All @@ -2274,20 +2276,28 @@ void test_glossaryTermReviewersMultipleUpdates(TestNamespace ns) {
.withGlossary(glossary.getFullyQualifiedName())
.withDescription("Term for multiple reviewer updates");
GlossaryTerm term = createEntity(request);
String termId = term.getId().toString();

// Refresh local state before each patch. The async GlossaryTermApprovalWorkflow promotes
// entityStatus DRAFT -> IN_REVIEW once reviewers exist; sending a stale local copy causes
// the JSON diff to include an entityStatus transition that trips
// EntityRepository.checkUpdatedByReviewer (admin is not in the reviewer list).
term = getEntity(termId);
term.setReviewers(List.of(testUser1().getEntityReference()));
GlossaryTerm updated1 = patchEntity(term.getId().toString(), term);
GlossaryTerm updated1 = patchEntity(termId, term);
assertNotNull(updated1.getReviewers());
assertEquals(1, updated1.getReviewers().size());

updated1 = getEntity(termId);
updated1.setReviewers(
List.of(testUser1().getEntityReference(), testUser2().getEntityReference()));
GlossaryTerm updated2 = patchEntity(updated1.getId().toString(), updated1);
GlossaryTerm updated2 = patchEntity(termId, updated1);
assertNotNull(updated2.getReviewers());
assertTrue(updated2.getReviewers().size() >= 2);

updated2 = getEntity(termId);
updated2.setReviewers(List.of(testUser2().getEntityReference()));
GlossaryTerm updated3 = patchEntity(updated2.getId().toString(), updated2);
GlossaryTerm updated3 = patchEntity(termId, updated2);
assertNotNull(updated3.getReviewers());
assertEquals(1, updated3.getReviewers().size());
}
Expand Down Expand Up @@ -3096,7 +3106,7 @@ void test_glossaryTermSearchIndexUpdatedWhenGlossaryOwnerChanges(TestNamespace n
String response =
client
.search()
.query("id:" + termId)
.query("id.keyword:" + termId)
.index("glossary_term_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -3151,7 +3161,7 @@ void test_glossaryTermSearchIndexUpdatedWhenGlossaryReviewerChanges(TestNamespac
String response =
client
.search()
.query("id:" + termId)
.query("id.keyword:" + termId)
.index("glossary_term_search_index")
.size(1)
.execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
public class IndexTemplateIT {

private static final ObjectMapper MAPPER = new ObjectMapper();
private static final String CLUSTER_ALIAS = "openmetadata";
private static final String CLUSTER_ALIAS = TestSuiteBootstrap.getClusterAlias();

@Test
void testIndexTemplatesExist(TestNamespace ns) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,12 @@ private void restoreSearchAccessControl(OpenMetadataClient adminClient, boolean
private void assertSearchReturnsTable(
OpenMetadataClient client, String tableId, String failureMessage) throws Exception {
String searchResponse =
client.search().query("id:" + tableId).index("table_search_index").size(1).execute();
client
.search()
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
assertTrue(
searchResponse.contains("\"id\":\"" + tableId + "\""),
failureMessage + ". Response: " + searchResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
@TestMethodOrder(OrderAnnotation.class)
public class OrphanedIndexCleanerScopedCleanupIT {

private static final String CLUSTER_ALIAS = "openmetadata";
private static final String CLUSTER_ALIAS = TestSuiteBootstrap.getClusterAlias();
private static final String FOREIGN_PREFIX = "foreigntenant_it_orphans";
private static final String OUR_PREFIX = CLUSTER_ALIAS + "_it_orphans";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ public class SearchIndexFieldLimitIT {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String TABLE_TYPE_NAME = "table";
private static final int NUM_CUSTOM_PROPERTIES = 50;
// Index name with cluster alias prefix (from TestSuiteBootstrap.ELASTIC_SEARCH_CLUSTER_ALIAS)
private static final String TABLE_INDEX = "openmetadata_table_search_index";
// Index name uses the cluster alias resolved by TestSuiteBootstrap (randomized per session by
// default; pin with -DclusterAlias=... for reproducible debugging).
private static final String TABLE_INDEX =
org.openmetadata.it.bootstrap.TestSuiteBootstrap.getClusterAlias() + "_table_search_index";

private static Type STRING_TYPE;
private static Type INTEGER_TYPE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3760,7 +3760,7 @@ void get_entityWithoutDescriptionFromSearch(TestNamespace ns) throws Exception {
String searchResponse =
client
.search()
.query("id:" + tableWithDesc.getId())
.query("id.keyword:" + tableWithDesc.getId())
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -3843,7 +3843,7 @@ void test_searchTableColumns_comprehensive(TestNamespace ns) throws Exception {
String searchResponse =
client
.search()
.query("id:" + table.getId())
.query("id.keyword:" + table.getId())
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -4002,7 +4002,7 @@ void test_multipleDomainInheritance(TestNamespace ns) throws Exception {
String searchResponse =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -4586,11 +4586,13 @@ protected Table getEntityIncludeDeleted(String id) {
// ===================================================================

/**
* Get the full Elasticsearch index name with cluster alias prefix.
* In test environment, cluster alias is "openmetadata" so table index is "openmetadata_table_search_index"
* Get the full Elasticsearch index name with cluster alias prefix. The alias is randomized per
* JUnit session by default in {@link org.openmetadata.it.bootstrap.TestSuiteBootstrap}; it can
* be pinned via {@code -DclusterAlias=...} for reproducible debugging.
*/
private String getTableSearchIndexName() {
return "openmetadata_table_search_index";
return org.openmetadata.it.bootstrap.TestSuiteBootstrap.getClusterAlias()
+ "_table_search_index";
Comment on lines +4594 to +4595
Comment on lines +4594 to +4595
}

// ===================================================================
Expand Down Expand Up @@ -5478,7 +5480,7 @@ void test_ownerPropagationToSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -5553,7 +5555,7 @@ void test_domainPropagationToSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -5625,7 +5627,7 @@ void test_displayNamePropagationToSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand Down Expand Up @@ -5685,7 +5687,7 @@ void test_ownerRemovalPropagationToSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand All @@ -5711,7 +5713,7 @@ void test_ownerRemovalPropagationToSearchIndex(TestNamespace ns) {
String response =
client
.search()
.query("id:" + tableId)
.query("id.keyword:" + tableId)
.index("table_search_index")
.size(1)
.execute();
Expand Down
Loading
Loading