diff --git a/.github/workflows/sql-test-and-build-workflow.yml b/.github/workflows/sql-test-and-build-workflow.yml index ffb5a838fb..920e593825 100644 --- a/.github/workflows/sql-test-and-build-workflow.yml +++ b/.github/workflows/sql-test-and-build-workflow.yml @@ -21,6 +21,9 @@ jobs: - name: Build with Gradle run: ./gradlew build assemble -Dopensearch.version=${{ env.OPENSEARCH_VERSION }} + - name: Run backward compatibility tests + run: ./bwctest.sh + - name: Create Artifact Path run: | mkdir -p opensearch-sql-builds diff --git a/.gitignore b/.gitignore index 415d7d43a3..7eacb4168b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ .project # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -/bin/ +bin/ /target/ log/ elasticsearch-sql.iml diff --git a/bwctest.sh b/bwctest.sh new file mode 100755 index 0000000000..07365399b7 --- /dev/null +++ b/bwctest.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +function usage() { + echo "" + echo "This script is used to run Backwards Compatibility tests" + echo "--------------------------------------------------------------------------" + echo "Usage: $0 [args]" + echo "" + echo "Required arguments:" + echo "None" + echo "" + echo -e "-h\tPrint this message." + echo "--------------------------------------------------------------------------" +} + +while getopts ":h" arg; do + case $arg in + h) + usage + exit 1 + ;; + ?) + echo "Invalid option: -${OPTARG}" + exit 1 + ;; + esac +done + +# Place SQL artifact for the current version for bwc +function setup_bwc_artifact() { + # This gets opensearch version from build.gradle (e.g. 1.2.0-SNAPSHOT), + # then converts to plugin version by appending ".0" (e.g. 1.2.0.0-SNAPSHOT), + # assuming one line in build.gradle is 'opensearch_version= System.getProperty("opensearch.version", "")'. + plugin_version=$(grep 'opensearch_version = System.getProperty' build.gradle | \ + grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+[^"]*' | sed -e 's/\([0-9]\)\([^0-9]*\)$/\1.0\2/') + plugin_artifact="./plugin/build/distributions/opensearch-sql-$plugin_version.zip" + bwc_artifact_dir="./integ-test/src/test/resources/bwc/$plugin_version" + + if [ -z "${plugin_version// }" ]; then + echo "Error: failed to retrieve plugin version from build.gradle." >&2 + exit 1 + fi + + # copy current artifact to bwc artifact directory if it's not there + if [ ! -f "$bwc_artifact_dir/opensearch-sql-$plugin_version.zip" ]; then + if [ ! -f "$plugin_artifact" ]; then + ./gradlew assemble + fi + mkdir -p "$bwc_artifact_dir" + cp "$plugin_artifact" "$bwc_artifact_dir" + fi +} + +setup_bwc_artifact +./gradlew bwcTestSuite -Dtests.security.manager=false + diff --git a/integ-test/build.gradle b/integ-test/build.gradle index bd17655936..8d37e48836 100644 --- a/integ-test/build.gradle +++ b/integ-test/build.gradle @@ -24,6 +24,9 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.opensearch.gradle.test.RestIntegTestTask +import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask + +import java.util.concurrent.Callable apply plugin: 'opensearch.build' apply plugin: 'opensearch.rest-test' @@ -88,6 +91,9 @@ compileTestJava { testClusters.all { testDistribution = 'archive' +} + +testClusters.integTest { plugin ":plugin" } @@ -113,6 +119,12 @@ integTest { jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005' } + if (System.getProperty("tests.rest.bwcsuite") == null) { + filter { + excludeTestsMatching "org.opensearch.sql.bwc.*IT" + } + } + exclude 'org/opensearch/sql/doctest/**/*IT.class' exclude 'org/opensearch/sql/correctness/**' @@ -138,7 +150,7 @@ task docTest(type: RestIntegTestTask) { // 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 { systemProperty 'cluster.debug', getDebug()} + doFirst { systemProperty 'cluster.debug', getDebug() } if (System.getProperty("test.debug") != null) { jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005' @@ -159,7 +171,7 @@ task comparisonTest(type: RestIntegTestTask) { // 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 { systemProperty 'cluster.debug', getDebug()} + doFirst { systemProperty 'cluster.debug', getDebug() } if (System.getProperty("test.debug") != null) { jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005' @@ -180,7 +192,7 @@ task comparisonTest(type: RestIntegTestTask) { systemProperty "queries", System.getProperty("queries") } -task compileJdbc(type:Exec) { +task compileJdbc(type: Exec) { workingDir '../sql-jdbc/' if (DefaultNativePlatform.getCurrentOperatingSystem().isWindows()) { @@ -192,6 +204,146 @@ task compileJdbc(type:Exec) { } } +String bwcVersion = "1.13.2.0"; +String baseName = "sqlBwcCluster" +String bwcFilePath = "src/test/resources/bwc/" + +2.times { i -> + testClusters { + "${baseName}$i" { + testDistribution = "ARCHIVE" + versions = ["7.10.2", "1.2.1-SNAPSHOT"] + numberOfNodes = 3 + plugin(provider(new Callable() { + @Override + RegularFile call() throws Exception { + return new RegularFile() { + @Override + File getAsFile() { + return fileTree(bwcFilePath + bwcVersion).getSingleFile() + } + } + } + })) + setting 'path.repo', "${buildDir}/cluster/shared/repo/${baseName}" + setting 'http.content_type.required', 'true' + } + } +} + +List> plugins = [ + provider(new Callable() { + @Override + RegularFile call() throws Exception { + return new RegularFile() { + @Override + File getAsFile() { + return fileTree(bwcFilePath + project.version).getSingleFile() + } + } + } + }) +] + +// Creates 2 test clusters with 3 nodes of the old version. +2.times { i -> + task "${baseName}#oldVersionClusterTask$i"(type: StandaloneRestIntegTestTask) { + useCluster testClusters."${baseName}$i" + filter { + includeTestsMatching "org.opensearch.sql.bwc.*IT" + } + systemProperty 'tests.rest.bwcsuite', 'old_cluster' + systemProperty 'tests.rest.bwcsuite_round', 'old' + systemProperty 'tests.plugin_bwc_version', bwcVersion + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}$i".allHttpSocketURI.join(",")}") + nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}$i".getName()}") + } +} + +// Upgrade one node of the old cluster to new OpenSearch version with upgraded plugin version. +// This results in a mixed cluster with 2 nodes on the old version and 1 upgraded node. +// This is also used as a one third upgraded cluster for a rolling upgrade. +task "${baseName}#mixedClusterTask"(type: StandaloneRestIntegTestTask) { + useCluster testClusters."${baseName}0" + dependsOn "${baseName}#oldVersionClusterTask0" + doFirst { + testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) + } + filter { + includeTestsMatching "org.opensearch.sql.bwc.*IT" + } + systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' + systemProperty 'tests.rest.bwcsuite_round', 'first' + systemProperty 'tests.plugin_bwc_version', bwcVersion + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}") + nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}") +} + +// Upgrade the second node to new OpenSearch version with upgraded plugin version after the first node is upgraded. +// This results in a mixed cluster with 1 node on the old version and 2 upgraded nodes. +// This is used for rolling upgrade. +task "${baseName}#twoThirdsUpgradedClusterTask"(type: StandaloneRestIntegTestTask) { + dependsOn "${baseName}#mixedClusterTask" + useCluster testClusters."${baseName}0" + doFirst { + testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) + } + filter { + includeTestsMatching "org.opensearch.sql.bwc.*IT" + } + systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' + systemProperty 'tests.rest.bwcsuite_round', 'second' + systemProperty 'tests.plugin_bwc_version', bwcVersion + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}") + nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}") +} + +// Upgrade the third node to new OpenSearch version with upgraded plugin version after the second node is upgraded. +// This results in a fully upgraded cluster. +// This is used for rolling upgrade. +task "${baseName}#rollingUpgradeClusterTask"(type: StandaloneRestIntegTestTask) { + dependsOn "${baseName}#twoThirdsUpgradedClusterTask" + useCluster testClusters."${baseName}0" + doFirst { + testClusters."${baseName}0".upgradeNodeAndPluginToNextVersion(plugins) + } + filter { + includeTestsMatching "org.opensearch.sql.bwc.*IT" + } + mustRunAfter "${baseName}#mixedClusterTask" + systemProperty 'tests.rest.bwcsuite', 'mixed_cluster' + systemProperty 'tests.rest.bwcsuite_round', 'third' + systemProperty 'tests.plugin_bwc_version', bwcVersion + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}0".allHttpSocketURI.join(",")}") + nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}0".getName()}") +} + +// Upgrade all the nodes of the old cluster to new OpenSearch version with upgraded plugin version +// at the same time resulting in a fully upgraded cluster. +task "${baseName}#fullRestartClusterTask"(type: StandaloneRestIntegTestTask) { + dependsOn "${baseName}#oldVersionClusterTask1" + useCluster testClusters."${baseName}1" + doFirst { + testClusters."${baseName}1".upgradeAllNodesAndPluginsToNextVersion(plugins) + } + filter { + includeTestsMatching "org.opensearch.sql.bwc.*IT" + } + systemProperty 'tests.rest.bwcsuite', 'upgraded_cluster' + systemProperty 'tests.plugin_bwc_version', bwcVersion + nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}1".allHttpSocketURI.join(",")}") + nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}1".getName()}") +} + +// A bwc test suite which runs all the bwc tasks combined +task bwcTestSuite(type: StandaloneRestIntegTestTask) { + exclude '**/*Test*' + exclude '**/*IT*' + dependsOn tasks.named("${baseName}#mixedClusterTask") + dependsOn tasks.named("${baseName}#rollingUpgradeClusterTask") + dependsOn tasks.named("${baseName}#fullRestartClusterTask") +} + def opensearch_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile opensearch_tmp_dir.mkdirs() diff --git a/integ-test/lombok.config b/integ-test/lombok.config index 6aa51d71ec..189c0bef98 100644 --- a/integ-test/lombok.config +++ b/integ-test/lombok.config @@ -1,2 +1,3 @@ # This file is generated by the 'io.freefair.lombok' Gradle plugin config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/integ-test/src/test/java/org/opensearch/sql/bwc/SQLBackwardsCompatibilityIT.java b/integ-test/src/test/java/org/opensearch/sql/bwc/SQLBackwardsCompatibilityIT.java new file mode 100644 index 0000000000..079980248f --- /dev/null +++ b/integ-test/src/test/java/org/opensearch/sql/bwc/SQLBackwardsCompatibilityIT.java @@ -0,0 +1,214 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + + +package org.opensearch.sql.bwc; + + +import org.json.JSONObject; +import org.junit.Assert; +import org.opensearch.client.Request; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.Response; +import org.opensearch.common.settings.Settings; +import org.opensearch.sql.legacy.SQLIntegTestCase; +import org.opensearch.sql.legacy.TestsConstants; +import org.opensearch.test.rest.OpenSearchRestTestCase; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.opensearch.sql.legacy.TestUtils.createIndexByRestClient; +import static org.opensearch.sql.legacy.TestUtils.isIndexExist; +import static org.opensearch.sql.legacy.TestUtils.loadDataByRestClient; +import static org.opensearch.sql.legacy.plugin.RestSqlAction.LEGACY_QUERY_API_ENDPOINT; +import static org.opensearch.sql.legacy.plugin.RestSqlAction.QUERY_API_ENDPOINT; +import static org.opensearch.sql.plugin.rest.RestQuerySettingsAction.LEGACY_SQL_SETTINGS_API_ENDPOINT; +import static org.opensearch.sql.util.MatcherUtils.rows; +import static org.opensearch.sql.util.MatcherUtils.schema; +import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; +import static org.opensearch.sql.util.MatcherUtils.verifySchema; +import static org.opensearch.sql.util.TestUtils.getResponseBody; + +public class SQLBackwardsCompatibilityIT extends SQLIntegTestCase { + + private static final ClusterType CLUSTER_TYPE = ClusterType.parse(System.getProperty("tests.rest.bwcsuite")); + private static final String CLUSTER_NAME = System.getProperty("tests.clustername"); + + @Override + protected final boolean preserveIndicesUponCompletion() { + return true; + } + + @Override + protected final boolean preserveReposUponCompletion() { + return true; + } + + @Override + protected boolean preserveTemplatesUponCompletion() { + return true; + } + + @Override + protected final Settings restClientSettings() { + return Settings + .builder() + .put(super.restClientSettings()) + // increase the timeout here to 90 seconds to handle long waits for a green + // cluster health. the waits for green need to be longer than a minute to + // account for delayed shards + .put(OpenSearchRestTestCase.CLIENT_SOCKET_TIMEOUT, "90s") + .build(); + } + + private enum ClusterType { + OLD, + MIXED, + UPGRADED; + + public static ClusterType parse(String value) { + switch (value) { + case "old_cluster": + return OLD; + case "mixed_cluster": + return MIXED; + case "upgraded_cluster": + return UPGRADED; + default: + throw new AssertionError("unknown cluster type: " + value); + } + } + } + + @SuppressWarnings("unchecked") + public void testBackwardsCompatibility() throws Exception { + String uri = getUri(); + Map> responseMap = (Map>) getAsMap(uri).get("nodes"); + for (Map response : responseMap.values()) { + List> plugins = (List>) response.get("plugins"); + Set pluginNames = plugins.stream().map(map -> map.get("name")).collect(Collectors.toSet()); + switch (CLUSTER_TYPE) { + case OLD: + Assert.assertTrue(pluginNames.contains("opendistro-sql")); + updateLegacySQLSettings(); + loadIndex(Index.ACCOUNT); + verifySQLQueries(LEGACY_QUERY_API_ENDPOINT); + break; + case MIXED: + Assert.assertTrue(pluginNames.contains("opensearch-sql")); + verifySQLSettings(); + verifySQLQueries(LEGACY_QUERY_API_ENDPOINT); + break; + case UPGRADED: + Assert.assertTrue(pluginNames.contains("opensearch-sql")); + verifySQLSettings(); + verifySQLQueries(QUERY_API_ENDPOINT); + break; + } + break; + } + } + + private String getUri() { + switch (CLUSTER_TYPE) { + case OLD: + return "_nodes/" + CLUSTER_NAME + "-0/plugins"; + case MIXED: + String round = System.getProperty("tests.rest.bwcsuite_round"); + if (round.equals("second")) { + return "_nodes/" + CLUSTER_NAME + "-1/plugins"; + } else if (round.equals("third")) { + return "_nodes/" + CLUSTER_NAME + "-2/plugins"; + } else { + return "_nodes/" + CLUSTER_NAME + "-0/plugins"; + } + case UPGRADED: + return "_nodes/plugins"; + default: + throw new AssertionError("unknown cluster type: " + CLUSTER_TYPE); + } + } + + private void updateLegacySQLSettings() throws IOException { + Request request = new Request("PUT", LEGACY_SQL_SETTINGS_API_ENDPOINT); + request.setJsonEntity(String.format(Locale.ROOT, "{\n" + + " \"persistent\" : {\n" + + " \"%s\" : \"%s\"\n" + + " }\n" + + "}", "opendistro.sql.cursor.keep_alive", "7m")); + + RequestOptions.Builder restOptionsBuilder = RequestOptions.DEFAULT.toBuilder(); + restOptionsBuilder.addHeader("Content-Type", "application/json"); + request.setOptions(restOptionsBuilder); + + Response response = client().performRequest(request); + JSONObject jsonObject = new JSONObject(getResponseBody(response)); + Assert.assertTrue((boolean) jsonObject.get("acknowledged")); + } + + private void verifySQLSettings() throws IOException { + Request request = new Request("GET", "_cluster/settings?flat_settings"); + + RequestOptions.Builder restOptionsBuilder = RequestOptions.DEFAULT.toBuilder(); + restOptionsBuilder.addHeader("Content-Type", "application/json"); + request.setOptions(restOptionsBuilder); + + Response response = client().performRequest(request); + JSONObject jsonObject = new JSONObject(getResponseBody(response)); + Assert.assertEquals("{\"transient\":{},\"persistent\":{\"opendistro.sql.cursor.keep_alive\":\"7m\"}}", jsonObject.toString()); + } + + private void verifySQLQueries(String endpoint) throws IOException { + JSONObject filterResponse = executeSQLQuery(endpoint, "SELECT COUNT(*) FILTER(WHERE age > 35) FROM " + TestsConstants.TEST_INDEX_ACCOUNT); + verifySchema(filterResponse, schema("COUNT(*) FILTER(WHERE age > 35)", null, "integer")); + verifyDataRows(filterResponse, rows(238)); + + JSONObject aggResponse = executeSQLQuery(endpoint, "SELECT COUNT(DISTINCT age) FROM " + TestsConstants.TEST_INDEX_ACCOUNT); + verifySchema(aggResponse, schema("COUNT(DISTINCT age)", null, "integer")); + verifyDataRows(aggResponse, rows(21)); + + JSONObject groupByResponse = executeSQLQuery(endpoint, "select a.gender from " + TestsConstants.TEST_INDEX_ACCOUNT + " a group by a.gender having count(*) > 0"); + verifySchema(groupByResponse, schema("gender", null, "text")); + Assert.assertEquals("[[\"F\"],[\"M\"]]", groupByResponse.getJSONArray("datarows").toString()); + } + + private JSONObject executeSQLQuery(String endpoint, String query) throws IOException { + Request request = new Request("POST", endpoint); + request.setJsonEntity(String.format(Locale.ROOT, "{" + + " \"query\" : \"%s\"" + + "}", query)); + + RequestOptions.Builder restOptionsBuilder = RequestOptions.DEFAULT.toBuilder(); + restOptionsBuilder.addHeader("Content-Type", "application/json"); + request.setOptions(restOptionsBuilder); + + Response response = client().performRequest(request); + return new JSONObject(getResponseBody(response)); + } + + @Override + public boolean shouldResetQuerySizeLimit() { + return false; + } + + @Override + protected synchronized void loadIndex(Index index) throws IOException { + String indexName = index.getName(); + String mapping = index.getMapping(); + // current directory becomes 'integ-test/build/testrun/sqlBwcCluster#' during bwc + String dataSet = "../../../" + index.getDataSet(); + + if (!isIndexExist(client(), indexName)) { + createIndexByRestClient(client(), indexName, mapping); + loadDataByRestClient(client(), indexName, dataSet); + } + } + +} diff --git a/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java b/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java index d4cd4955c4..0c479cbd6e 100644 --- a/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java +++ b/integ-test/src/test/java/org/opensearch/sql/legacy/SQLIntegTestCase.java @@ -6,6 +6,31 @@ package org.opensearch.sql.legacy; +import com.google.common.base.Strings; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.opensearch.client.Request; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.Response; +import org.opensearch.sql.common.setting.Settings; + +import javax.management.MBeanServerInvocationHandler; +import javax.management.ObjectName; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Locale; + import static org.opensearch.sql.legacy.TestUtils.createIndexByRestClient; import static org.opensearch.sql.legacy.TestUtils.getAccountIndexMapping; import static org.opensearch.sql.legacy.TestUtils.getBankIndexMapping; @@ -37,30 +62,6 @@ import static org.opensearch.sql.legacy.plugin.RestSqlAction.EXPLAIN_API_ENDPOINT; import static org.opensearch.sql.legacy.plugin.RestSqlAction.QUERY_API_ENDPOINT; -import com.google.common.base.Strings; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Locale; -import javax.management.MBeanServerInvocationHandler; -import javax.management.ObjectName; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; -import org.json.JSONArray; -import org.json.JSONObject; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.Before; -import org.opensearch.client.Request; -import org.opensearch.client.RequestOptions; -import org.opensearch.client.Response; -import org.opensearch.sql.common.setting.Settings; - /** * OpenSearch Rest integration test base for SQL testing */ @@ -71,13 +72,19 @@ public abstract class SQLIntegTestCase extends OpenSearchSQLRestTestCase { public static final Integer DEFAULT_QUERY_SIZE_LIMIT = Integer.parseInt(System.getProperty("defaultQuerySizeLimit", "200")); + public boolean shouldResetQuerySizeLimit() { + return true; + } + @Before public void setUpIndices() throws Exception { if (client() == null) { initClient(); } - resetQuerySizeLimit(); + if (shouldResetQuerySizeLimit()) { + resetQuerySizeLimit(); + } init(); } @@ -132,8 +139,10 @@ public static void dumpCoverage() { */ @AfterClass public static void cleanUpIndices() throws IOException { - wipeAllOpenSearchIndices(); - wipeAllClusterSettings(); + if (System.getProperty("tests.rest.bwcsuite") == null) { + wipeAllOpenSearchIndices(); + wipeAllClusterSettings(); + } } protected void setQuerySizeLimit(Integer limit) throws IOException { diff --git a/integ-test/src/test/resources/bwc/1.13.2.0/opendistro-sql-1.13.2.0.zip b/integ-test/src/test/resources/bwc/1.13.2.0/opendistro-sql-1.13.2.0.zip new file mode 100644 index 0000000000..9a44ed9777 Binary files /dev/null and b/integ-test/src/test/resources/bwc/1.13.2.0/opendistro-sql-1.13.2.0.zip differ