-
Notifications
You must be signed in to change notification settings - Fork 0
1713: add multi-cluster IT testing class for Cross-Cluster support #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -285,6 +285,9 @@ integTest { | |||||||
| exclude 'org/opensearch/sql/doctest/**/*IT.class' | ||||||||
| exclude 'org/opensearch/sql/correctness/**' | ||||||||
|
|
||||||||
| // Skip to run these IT tests on a different cluster | ||||||||
| exclude 'org/opensearch/sql/multiClusterSearch/**' | ||||||||
|
|
||||||||
| // Explain IT is dependent on internal implementation of old engine so it's not necessary | ||||||||
| // to run these with new engine and not necessary to make this consistent with old engine. | ||||||||
| exclude 'org/opensearch/sql/legacy/ExplainIT.class' | ||||||||
|
|
@@ -301,7 +304,7 @@ integTest { | |||||||
| exclude 'org/opensearch/sql/jdbc/**' | ||||||||
|
|
||||||||
| // Exclude this IT until running IT with security plugin enabled is ready | ||||||||
| exclude 'org/opensearch/sql/ppl/CrossClusterSearchIT.class' | ||||||||
| // exclude 'org/opensearch/sql/ppl/CrossClusterSearchIT.class' | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ☝️ |
||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||
|
|
@@ -481,6 +484,76 @@ task bwcTestSuite(type: StandaloneRestIntegTestTask) { | |||||||
| dependsOn tasks.named("${baseName}#fullRestartClusterTask") | ||||||||
| } | ||||||||
|
|
||||||||
| testClusters { | ||||||||
| multiClusterSearch { | ||||||||
| testDistribution = "ARCHIVE" | ||||||||
| numberOfNodes = 3 | ||||||||
| plugin ":opensearch-sql-plugin" | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| testClusters { | ||||||||
|
Comment on lines
+493
to
+495
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
| multiClusterSearchRemote { | ||||||||
| testDistribution = "ARCHIVE" | ||||||||
| plugin ":opensearch-sql-plugin" | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| task multiClusterSearch(type: RestIntegTestTask) { | ||||||||
|
|
||||||||
| useCluster testClusters.multiClusterSearch | ||||||||
| useCluster testClusters.multiClusterSearchRemote | ||||||||
|
|
||||||||
| testLogging { | ||||||||
| events "passed", "skipped", "failed" | ||||||||
| } | ||||||||
|
|
||||||||
| // Set properties for connection to clusters and between clusters | ||||||||
| doFirst { | ||||||||
| getClusters().forEach { cluster -> | ||||||||
| String allTransportSocketURI = cluster.nodes.stream().flatMap { node -> | ||||||||
| node.getAllTransportPortURI().stream() | ||||||||
| }.collect(Collectors.joining(",")) | ||||||||
| String allHttpSocketURI = cluster.nodes.stream().flatMap { node -> | ||||||||
| node.getAllHttpSocketURI().stream() | ||||||||
| }.collect(Collectors.joining(",")) | ||||||||
|
|
||||||||
| systemProperty "tests.rest.${cluster.name}.http_hosts", "${-> allHttpSocketURI}" | ||||||||
| systemProperty "tests.rest.${cluster.name}.transport_hosts", "${-> allTransportSocketURI}" | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| dependsOn ':opensearch-sql-plugin:bundlePlugin' | ||||||||
|
|
||||||||
| systemProperty 'tests.security.manager', 'false' | ||||||||
| systemProperty('project.root', project.projectDir.absolutePath) | ||||||||
|
|
||||||||
| systemProperty "https", System.getProperty("https") | ||||||||
| systemProperty "user", System.getProperty("user") | ||||||||
| systemProperty "password", System.getProperty("password") | ||||||||
|
Comment on lines
+531
to
+533
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is no-op, can be removed |
||||||||
|
|
||||||||
| // Set default query size limit | ||||||||
| systemProperty 'defaultQuerySizeLimit', '10000' | ||||||||
|
|
||||||||
| // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for | ||||||||
| // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time. | ||||||||
| doFirst { | ||||||||
| if (System.getProperty("debug-jvm") != null) { | ||||||||
| setDebug(true); | ||||||||
| } | ||||||||
| systemProperty 'cluster.debug', getDebug() | ||||||||
| } | ||||||||
|
|
||||||||
|
|
||||||||
| if (System.getProperty("test.debug") != null) { | ||||||||
| jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5006' | ||||||||
| } | ||||||||
|
|
||||||||
| filter { | ||||||||
| includeTestsMatching "org.opensearch.sql.multiClusterSearch.*IT" | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| def opensearch_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile | ||||||||
| opensearch_tmp_dir.mkdirs() | ||||||||
|
|
||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -105,10 +105,10 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE | |
| } | ||
|
|
||
| // Modified from initClient in OpenSearchRestTestCase | ||
| public void initRemoteClient() throws IOException { | ||
| public void initRemoteClient(String clusterName) throws IOException { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this not reduce potential future reuse of this function?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The cluster name is defined in the gradle task. Sean hardcoded the name in a static variable, so this only used to work with the remoteCluster, and now it works with whatever the gradle task has named the cluster. |
||
| if (remoteClient == null) { | ||
| assert remoteAdminClient == null; | ||
| String cluster = getTestRestCluster(REMOTE_CLUSTER); | ||
| String cluster = getTestRestCluster(clusterName); | ||
| String[] stringUrls = cluster.split(","); | ||
| List<HttpHost> hosts = new ArrayList<>(stringUrls.length); | ||
| for (String stringUrl : stringUrls) { | ||
|
|
@@ -252,14 +252,14 @@ protected static void configureHttpsClient(RestClientBuilder builder, Settings s | |
| * Initialize rest client to remote cluster, | ||
| * and create a connection to it from the coordinating cluster. | ||
| */ | ||
| public void configureMultiClusters() throws IOException { | ||
| initRemoteClient(); | ||
| public void configureMultiClusters(String clusterName) throws IOException { | ||
| initRemoteClient(clusterName); | ||
|
|
||
| Request connectionRequest = new Request("PUT", "_cluster/settings"); | ||
| String connectionSetting = "{\"persistent\": {\"cluster\": {\"remote\": {\"" | ||
| + REMOTE_CLUSTER | ||
| + clusterName | ||
| + "\": {\"seeds\": [\"" | ||
| + getTestTransportCluster(REMOTE_CLUSTER).split(",")[0] | ||
| + getTestTransportCluster(clusterName).split(",")[0] | ||
| + "\"]}}}}}"; | ||
| connectionRequest.setJsonEntity(connectionSetting); | ||
| adminClient().performRequest(connectionRequest); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| /* | ||
| * Copyright OpenSearch Contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
|
|
||
| package org.opensearch.sql.multiClusterSearch; | ||
|
|
||
| import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_ACCOUNT; | ||
| import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_BANK; | ||
| import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_DOG; | ||
| import static org.opensearch.sql.util.MatcherUtils.columnName; | ||
| import static org.opensearch.sql.util.MatcherUtils.rows; | ||
| import static org.opensearch.sql.util.MatcherUtils.verifyColumn; | ||
| import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; | ||
|
|
||
| import java.io.IOException; | ||
| import org.json.JSONObject; | ||
| import org.junit.Rule; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.junit.rules.ExpectedException; | ||
| import org.opensearch.client.ResponseException; | ||
| import org.opensearch.sql.ppl.PPLIntegTestCase; | ||
|
|
||
| public class CrossClusterSearchIT extends PPLIntegTestCase { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably add a comment describing difference with |
||
|
|
||
| @Rule | ||
| public ExpectedException exceptionRule = ExpectedException.none(); | ||
|
|
||
| public static final String REMOTE_CLUSTER = "multiClusterSearchRemote"; | ||
|
|
||
| private final static String TEST_INDEX_BANK_REMOTE = REMOTE_CLUSTER + ":" + TEST_INDEX_BANK; | ||
| private final static String TEST_INDEX_DOG_REMOTE = REMOTE_CLUSTER + ":" + TEST_INDEX_DOG; | ||
| private final static String TEST_INDEX_DOG_MATCH_ALL_REMOTE = MATCH_ALL_REMOTE_CLUSTER + ":" + TEST_INDEX_DOG; | ||
| private final static String TEST_INDEX_ACCOUNT_REMOTE = REMOTE_CLUSTER + ":" + TEST_INDEX_ACCOUNT; | ||
|
|
||
| @Override | ||
| public void init() throws IOException { | ||
| configureMultiClusters(REMOTE_CLUSTER); | ||
| loadIndex(Index.BANK); | ||
| loadIndex(Index.BANK, remoteClient()); | ||
| loadIndex(Index.DOG); | ||
| loadIndex(Index.DOG, remoteClient()); | ||
| loadIndex(Index.ACCOUNT, remoteClient()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCrossClusterSearchAllFields() throws IOException { | ||
| JSONObject result = executeQuery(String.format("search source=%s", TEST_INDEX_DOG_REMOTE)); | ||
| verifyColumn(result, columnName("dog_name"), columnName("holdersName"), columnName("age")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testMatchAllCrossClusterSearchAllFields() throws IOException { | ||
| JSONObject result = executeQuery(String.format("search source=%s", TEST_INDEX_DOG_MATCH_ALL_REMOTE)); | ||
| verifyColumn(result, columnName("dog_name"), columnName("holdersName"), columnName("age")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCrossClusterSearchWithoutLocalFieldMappingShouldFail() throws IOException { | ||
| exceptionRule.expect(ResponseException.class); | ||
| exceptionRule.expectMessage("400 Bad Request"); | ||
| exceptionRule.expectMessage("IndexNotFoundException"); | ||
|
|
||
| executeQuery(String.format("search source=%s", TEST_INDEX_ACCOUNT_REMOTE)); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCrossClusterSearchCommandWithLogicalExpression() throws IOException { | ||
| JSONObject result = executeQuery(String.format( | ||
| "search source=%s firstname='Hattie' | fields firstname", TEST_INDEX_BANK_REMOTE)); | ||
| verifyDataRows(result, rows("Hattie")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCrossClusterSearchMultiClusters() throws IOException { | ||
| JSONObject result = executeQuery(String.format( | ||
| "search source=%s,%s firstname='Hattie' | fields firstname", TEST_INDEX_BANK_REMOTE, TEST_INDEX_BANK)); | ||
| verifyDataRows(result, | ||
| rows("Hattie"), | ||
| rows("Hattie")); | ||
| } | ||
|
|
||
| @Test | ||
| public void testCrossClusterDescribeAllFields() throws IOException { | ||
| JSONObject result = executeQuery(String.format("describe %s", TEST_INDEX_DOG_REMOTE)); | ||
| verifyColumn( | ||
| result, | ||
| columnName("TABLE_CAT"), | ||
| columnName("TABLE_SCHEM"), | ||
| columnName("TABLE_NAME"), | ||
| columnName("COLUMN_NAME"), | ||
| columnName("DATA_TYPE"), | ||
| columnName("TYPE_NAME"), | ||
| columnName("COLUMN_SIZE"), | ||
| columnName("BUFFER_LENGTH"), | ||
| columnName("DECIMAL_DIGITS"), | ||
| columnName("NUM_PREC_RADIX"), | ||
| columnName("NULLABLE"), | ||
| columnName("REMARKS"), | ||
| columnName("COLUMN_DEF"), | ||
| columnName("SQL_DATA_TYPE"), | ||
| columnName("SQL_DATETIME_SUB"), | ||
| columnName("CHAR_OCTET_LENGTH"), | ||
| columnName("ORDINAL_POSITION"), | ||
| columnName("IS_NULLABLE"), | ||
| columnName("SCOPE_CATALOG"), | ||
| columnName("SCOPE_SCHEMA"), | ||
| columnName("SCOPE_TABLE"), | ||
| columnName("SOURCE_DATA_TYPE"), | ||
| columnName("IS_AUTOINCREMENT"), | ||
| columnName("IS_GENERATEDCOLUMN") | ||
| ); | ||
| } | ||
|
|
||
| @Test | ||
| public void testMatchAllCrossClusterDescribeAllFields() throws IOException { | ||
| JSONObject result = executeQuery(String.format("describe %s", TEST_INDEX_DOG_MATCH_ALL_REMOTE)); | ||
| verifyColumn( | ||
| result, | ||
| columnName("TABLE_CAT"), | ||
| columnName("TABLE_SCHEM"), | ||
| columnName("TABLE_NAME"), | ||
| columnName("COLUMN_NAME"), | ||
| columnName("DATA_TYPE"), | ||
| columnName("TYPE_NAME"), | ||
| columnName("COLUMN_SIZE"), | ||
| columnName("BUFFER_LENGTH"), | ||
| columnName("DECIMAL_DIGITS"), | ||
| columnName("NUM_PREC_RADIX"), | ||
| columnName("NULLABLE"), | ||
| columnName("REMARKS"), | ||
| columnName("COLUMN_DEF"), | ||
| columnName("SQL_DATA_TYPE"), | ||
| columnName("SQL_DATETIME_SUB"), | ||
| columnName("CHAR_OCTET_LENGTH"), | ||
| columnName("ORDINAL_POSITION"), | ||
| columnName("IS_NULLABLE"), | ||
| columnName("SCOPE_CATALOG"), | ||
| columnName("SCOPE_SCHEMA"), | ||
| columnName("SCOPE_TABLE"), | ||
| columnName("SOURCE_DATA_TYPE"), | ||
| columnName("IS_AUTOINCREMENT"), | ||
| columnName("IS_GENERATEDCOLUMN") | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is for every push, can we run it after doctests? For development, doctests are more relevant to us than testing with security enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this was akin to Integration Testing, just against a different cluster. The tests run are integration tests.
I would categorize doctests are more API testing which seems more highlevel... but is just another form of Integration Test.
So... 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right it's more so when we push and we want to know if all integ test and doctest pass we would have to wait x time for mutlicluster IT to finish (spin up, run tests, and clean up) before getting our results. While the ITs added here are really only relevant for release testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This CI runs on demand only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to run these tests in every push/PR, add it to
sql-test-and-build-workflow.yml