Skip to content

Commit 1dadf25

Browse files
authored
BWC tests for Multimodal Search, Hybrid Search and Neural Sparse Search (#533)
* Initial commit of BWC Test Signed-off-by: Varun Jain <varunudr@amazon.com>
1 parent 98e5534 commit 1dadf25

34 files changed

+1259
-300
lines changed

.github/workflows/backwards_compatibility_tests_workflow.yml

+4-5
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ jobs:
3636
- name: Run NeuralSearch Restart-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} on ${{matrix.os}}
3737
run: |
3838
echo "Running restart-upgrade backwards compatibility tests ..."
39-
# Disabling BWC tests due to ongoing build failure. https://github.com/opensearch-project/neural-search/issues/536
40-
# ./gradlew :qa:restart-upgrade:testAgainstNewCluster -D'tests.bwc.version=${{ matrix.bwc_version }}'
39+
./gradlew :qa:restart-upgrade:testAgainstNewCluster -D'tests.bwc.version=${{ matrix.bwc_version }}'
4140
4241
Rolling-Upgrade-BWCTests-NeuralSearch:
4342
strategy:
4443
matrix:
45-
java: [ 11, 17, 21 ]
44+
# Restricting java 21 to 21.0.1 due to ongoing bug in JDK 21.0.2 https://bugs.openjdk.org/browse/JDK-8323659. Once the fix https://github.com/opensearch-project/OpenSearch/pull/11968 get merged this change will be reverted.
45+
java: [ 11, 17, 21.0.1 ]
4646
os: [ubuntu-latest,windows-latest]
4747
bwc_version: [ "2.12.0-SNAPSHOT" ]
4848
opensearch_version: [ "3.0.0-SNAPSHOT" ]
@@ -64,5 +64,4 @@ jobs:
6464
- name: Run NeuralSearch Rolling-Upgrade BWC Tests from BWCVersion-${{ matrix.bwc_version }} to OpenSearch Version-${{ matrix.opensearch_version }} on ${{matrix.os}}
6565
run: |
6666
echo "Running rolling-upgrade backwards compatibility tests ..."
67-
# Disabling BWC tests due to ongoing build failure. https://github.com/opensearch-project/neural-search/issues/536
68-
# ./gradlew :qa:rolling-upgrade:testRollingUpgrade -D'tests.bwc.version=${{ matrix.bwc_version }}'
67+
./gradlew :qa:rolling-upgrade:testRollingUpgrade -D'tests.bwc.version=${{ matrix.bwc_version }}'

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2626
### Infrastructure
2727
- BWC tests for Neural Search ([#515](https://github.com/opensearch-project/neural-search/pull/515))
2828
- Github action to run integ tests in secure opensearch cluster ([#535](https://github.com/opensearch-project/neural-search/pull/535))
29+
- BWC tests for Multimodal search, Hybrid Search and Neural Sparse Search ([#533](https://github.com/opensearch-project/neural-search/pull/533))
2930
### Documentation
3031
### Maintenance
3132
### Refactoring

qa/restart-upgrade/build.gradle

+18
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ task testAgainstOldCluster(type: StandaloneRestIntegTestTask) {
3535
systemProperty 'tests.skip_delete_model_index', 'true'
3636
systemProperty 'tests.plugin_bwc_version', neural_search_bwc_version
3737

38+
//Excluding MultiModalSearchIT, HybridSearchIT, NeuralSparseSearchIT tests from neural search version 2.9 and 2.10 because these features were released in 2.11 version.
39+
if (neural_search_bwc_version.startsWith("2.9") || neural_search_bwc_version.startsWith("2.10")){
40+
filter {
41+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.MultiModalSearchIT.*"
42+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.HybridSearchIT.*"
43+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.NeuralSparseSearchIT.*"
44+
}
45+
}
46+
3847
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}")
3948
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}")
4049
systemProperty 'tests.security.manager', 'false'
@@ -53,6 +62,15 @@ task testAgainstNewCluster(type: StandaloneRestIntegTestTask) {
5362
systemProperty 'tests.is_old_cluster', 'false'
5463
systemProperty 'tests.plugin_bwc_version', neural_search_bwc_version
5564

65+
//Excluding MultiModalSearchIT, HybridSearchIT, NeuralSparseSearchIT tests from neural search version 2.9 and 2.10 because these features were released in 2.11 version.
66+
if (neural_search_bwc_version.startsWith("2.9") || neural_search_bwc_version.startsWith("2.10")){
67+
filter {
68+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.MultiModalSearchIT.*"
69+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.HybridSearchIT.*"
70+
excludeTestsMatching "org.opensearch.neuralsearch.bwc.NeuralSparseSearchIT.*"
71+
}
72+
}
73+
5674
nonInputProperties.systemProperty('tests.rest.cluster', "${-> testClusters."${baseName}".allHttpSocketURI.join(",")}")
5775
nonInputProperties.systemProperty('tests.clustername', "${-> testClusters."${baseName}".getName()}")
5876
systemProperty 'tests.security.manager', 'false'

qa/restart-upgrade/src/test/java/org/opensearch/neuralsearch/bwc/AbstractRestartUpgradeRestTestCase.java

+43-1
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
*/
55
package org.opensearch.neuralsearch.bwc;
66

7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
79
import java.util.Locale;
810
import java.util.Optional;
911
import org.junit.Before;
1012
import org.opensearch.common.settings.Settings;
1113
import org.opensearch.neuralsearch.BaseNeuralSearchIT;
14+
import static org.opensearch.neuralsearch.TestUtils.NEURAL_SEARCH_BWC_PREFIX;
1215
import static org.opensearch.neuralsearch.TestUtils.CLIENT_TIMEOUT_VALUE;
1316
import static org.opensearch.neuralsearch.TestUtils.RESTART_UPGRADE_OLD_CLUSTER;
1417
import static org.opensearch.neuralsearch.TestUtils.BWC_VERSION;
15-
import static org.opensearch.neuralsearch.TestUtils.NEURAL_SEARCH_BWC_PREFIX;
18+
import static org.opensearch.neuralsearch.TestUtils.generateModelId;
1619
import org.opensearch.test.rest.OpenSearchRestTestCase;
1720

1821
public abstract class AbstractRestartUpgradeRestTestCase extends BaseNeuralSearchIT {
@@ -57,4 +60,43 @@ protected static final boolean isRunningAgainstOldCluster() {
5760
protected final Optional<String> getBWCVersion() {
5861
return Optional.ofNullable(System.getProperty(BWC_VERSION, null));
5962
}
63+
64+
protected String uploadTextEmbeddingModel() throws Exception {
65+
String requestBody = Files.readString(Path.of(classLoader.getResource("processor/UploadModelRequestBody.json").toURI()));
66+
return registerModelGroupAndGetModelId(requestBody);
67+
}
68+
69+
protected String registerModelGroupAndGetModelId(final String requestBody) throws Exception {
70+
String modelGroupRegisterRequestBody = Files.readString(
71+
Path.of(classLoader.getResource("processor/CreateModelGroupRequestBody.json").toURI())
72+
);
73+
String modelGroupId = registerModelGroup(String.format(LOCALE, modelGroupRegisterRequestBody, generateModelId()));
74+
return uploadModel(String.format(LOCALE, requestBody, modelGroupId));
75+
}
76+
77+
protected void createPipelineProcessor(final String modelId, final String pipelineName) throws Exception {
78+
String requestBody = Files.readString(Path.of(classLoader.getResource("processor/PipelineConfiguration.json").toURI()));
79+
createPipelineProcessor(requestBody, pipelineName, modelId);
80+
}
81+
82+
protected String uploadSparseEncodingModel() throws Exception {
83+
String requestBody = Files.readString(
84+
Path.of(classLoader.getResource("processor/UploadSparseEncodingModelRequestBody.json").toURI())
85+
);
86+
return registerModelGroupAndGetModelId(requestBody);
87+
}
88+
89+
protected void createPipelineForTextImageProcessor(final String modelId, final String pipelineName) throws Exception {
90+
String requestBody = Files.readString(
91+
Path.of(classLoader.getResource("processor/PipelineForTextImageProcessorConfiguration.json").toURI())
92+
);
93+
createPipelineProcessor(requestBody, pipelineName, modelId);
94+
}
95+
96+
protected void createPipelineForSparseEncodingProcessor(final String modelId, final String pipelineName) throws Exception {
97+
String requestBody = Files.readString(
98+
Path.of(classLoader.getResource("processor/PipelineForSparseEncodingProcessorConfiguration.json").toURI())
99+
);
100+
createPipelineProcessor(requestBody, pipelineName, modelId);
101+
}
60102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package org.opensearch.neuralsearch.bwc;
6+
7+
import java.io.IOException;
8+
import java.nio.file.Files;
9+
import java.nio.file.Path;
10+
import java.util.Arrays;
11+
import java.util.List;
12+
import java.util.Map;
13+
import org.opensearch.index.query.MatchQueryBuilder;
14+
import static org.opensearch.neuralsearch.TestUtils.getModelId;
15+
import static org.opensearch.neuralsearch.TestUtils.NODES_BWC_CLUSTER;
16+
import static org.opensearch.neuralsearch.TestUtils.PARAM_NAME_WEIGHTS;
17+
import static org.opensearch.neuralsearch.TestUtils.TEXT_EMBEDDING_PROCESSOR;
18+
import static org.opensearch.neuralsearch.TestUtils.DEFAULT_NORMALIZATION_METHOD;
19+
import static org.opensearch.neuralsearch.TestUtils.DEFAULT_COMBINATION_METHOD;
20+
import org.opensearch.neuralsearch.query.HybridQueryBuilder;
21+
import org.opensearch.neuralsearch.query.NeuralQueryBuilder;
22+
23+
public class HybridSearchIT extends AbstractRestartUpgradeRestTestCase {
24+
private static final String PIPELINE_NAME = "nlp-hybrid-pipeline";
25+
private static final String PIPELINE1_NAME = "nlp-hybrid-1-pipeline";
26+
private static final String SEARCH_PIPELINE_NAME = "nlp-search-pipeline";
27+
private static final String SEARCH_PIPELINE1_NAME = "nlp-search-1-pipeline";
28+
private static final String TEST_FIELD = "passage_text";
29+
private static final String TEXT_1 = "Hello world";
30+
private static final String TEXT_2 = "Hi planet";
31+
private static final String TEXT_3 = "Hi earth";
32+
private static final String TEXT_4 = "Hi amazon";
33+
private static final String TEXT_5 = "Hi mars";
34+
private static final String TEXT_6 = "Hi opensearch";
35+
private static final String QUERY = "Hi world";
36+
37+
// Test restart-upgrade normalization processor when index with multiple shards
38+
// Create Text Embedding Processor, Ingestion Pipeline, add document and search pipeline with normalization processor
39+
// Validate process , pipeline and document count in restart-upgrade scenario
40+
public void testNormalizationProcessor_whenIndexWithMultipleShards_E2EFlow() throws Exception {
41+
validateNormalizationProcessor("processor/IndexMappingMultipleShard.json", PIPELINE_NAME, SEARCH_PIPELINE_NAME);
42+
}
43+
44+
// Test restart-upgrade normalization processor when index with single shard
45+
// Create Text Embedding Processor, Ingestion Pipeline, add document and search pipeline with normalization processor
46+
// Validate process , pipeline and document count in restart-upgrade scenario
47+
public void testNormalizationProcessor_whenIndexWithSingleShard_E2EFlow() throws Exception {
48+
validateNormalizationProcessor("processor/IndexMappingSingleShard.json", PIPELINE1_NAME, SEARCH_PIPELINE1_NAME);
49+
}
50+
51+
private void validateNormalizationProcessor(final String fileName, final String pipelineName, final String searchPipelineName)
52+
throws Exception {
53+
waitForClusterHealthGreen(NODES_BWC_CLUSTER);
54+
if (isRunningAgainstOldCluster()) {
55+
String modelId = uploadTextEmbeddingModel();
56+
loadModel(modelId);
57+
createPipelineProcessor(modelId, pipelineName);
58+
createIndexWithConfiguration(
59+
getIndexNameForTest(),
60+
Files.readString(Path.of(classLoader.getResource(fileName).toURI())),
61+
pipelineName
62+
);
63+
addDocuments(getIndexNameForTest(), true);
64+
createSearchPipeline(searchPipelineName);
65+
} else {
66+
String modelId = null;
67+
try {
68+
modelId = getModelId(getIngestionPipeline(pipelineName), TEXT_EMBEDDING_PROCESSOR);
69+
loadModel(modelId);
70+
addDocuments(getIndexNameForTest(), false);
71+
validateTestIndex(modelId, getIndexNameForTest(), searchPipelineName);
72+
} finally {
73+
wipeOfTestResources(getIndexNameForTest(), pipelineName, modelId, searchPipelineName);
74+
}
75+
}
76+
}
77+
78+
private void addDocuments(final String indexName, boolean isRunningAgainstOldCluster) throws IOException {
79+
if (isRunningAgainstOldCluster) {
80+
addDocument(indexName, "0", TEST_FIELD, TEXT_1, null, null);
81+
addDocument(indexName, "1", TEST_FIELD, TEXT_2, null, null);
82+
addDocument(indexName, "2", TEST_FIELD, TEXT_3, null, null);
83+
addDocument(indexName, "3", TEST_FIELD, TEXT_4, null, null);
84+
addDocument(indexName, "4", TEST_FIELD, TEXT_5, null, null);
85+
} else {
86+
addDocument(indexName, "5", TEST_FIELD, TEXT_6, null, null);
87+
}
88+
}
89+
90+
private void createSearchPipeline(final String pipelineName) {
91+
createSearchPipeline(
92+
pipelineName,
93+
DEFAULT_NORMALIZATION_METHOD,
94+
DEFAULT_COMBINATION_METHOD,
95+
Map.of(PARAM_NAME_WEIGHTS, Arrays.toString(new float[] { 0.3f, 0.7f }))
96+
);
97+
}
98+
99+
private void validateTestIndex(final String modelId, final String index, final String searchPipeline) throws Exception {
100+
int docCount = getDocCount(index);
101+
assertEquals(6, docCount);
102+
HybridQueryBuilder hybridQueryBuilder = getQueryBuilder(modelId);
103+
Map<String, Object> searchResponseAsMap = search(index, hybridQueryBuilder, null, 1, Map.of("search_pipeline", searchPipeline));
104+
assertNotNull(searchResponseAsMap);
105+
int hits = getHitCount(searchResponseAsMap);
106+
assertEquals(1, hits);
107+
List<Double> scoresList = getNormalizationScoreList(searchResponseAsMap);
108+
for (Double score : scoresList) {
109+
assertTrue(0 <= score && score <= 2);
110+
}
111+
}
112+
113+
private HybridQueryBuilder getQueryBuilder(final String modelId) {
114+
NeuralQueryBuilder neuralQueryBuilder = new NeuralQueryBuilder();
115+
neuralQueryBuilder.fieldName("passage_embedding");
116+
neuralQueryBuilder.modelId(modelId);
117+
neuralQueryBuilder.queryText(QUERY);
118+
neuralQueryBuilder.k(5);
119+
120+
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("text", QUERY);
121+
122+
HybridQueryBuilder hybridQueryBuilder = new HybridQueryBuilder();
123+
hybridQueryBuilder.add(matchQueryBuilder);
124+
hybridQueryBuilder.add(neuralQueryBuilder);
125+
126+
return hybridQueryBuilder;
127+
}
128+
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package org.opensearch.neuralsearch.bwc;
6+
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.util.Map;
10+
import static org.opensearch.neuralsearch.TestUtils.NODES_BWC_CLUSTER;
11+
import static org.opensearch.neuralsearch.TestUtils.TEXT_IMAGE_EMBEDDING_PROCESSOR;
12+
import static org.opensearch.neuralsearch.TestUtils.getModelId;
13+
import org.opensearch.neuralsearch.query.NeuralQueryBuilder;
14+
15+
public class MultiModalSearchIT extends AbstractRestartUpgradeRestTestCase {
16+
private static final String PIPELINE_NAME = "nlp-ingest-pipeline";
17+
private static final String TEST_FIELD = "passage_text";
18+
private static final String TEST_IMAGE_FIELD = "passage_image";
19+
private static final String TEXT = "Hello world";
20+
private static final String TEXT_1 = "Hello world a";
21+
private static final String TEST_IMAGE_TEXT = "/9j/4AAQSkZJRgABAQAASABIAAD";
22+
private static final String TEST_IMAGE_TEXT_1 = "/9j/4AAQSkZJRgbdwoeicfhoid";
23+
24+
// Test restart-upgrade test image embedding processor
25+
// Create Text Image Embedding Processor, Ingestion Pipeline and add document
26+
// Validate process , pipeline and document count in restart-upgrade scenario
27+
public void testTextImageEmbeddingProcessor_E2EFlow() throws Exception {
28+
waitForClusterHealthGreen(NODES_BWC_CLUSTER);
29+
30+
if (isRunningAgainstOldCluster()) {
31+
String modelId = uploadTextEmbeddingModel();
32+
loadModel(modelId);
33+
createPipelineForTextImageProcessor(modelId, PIPELINE_NAME);
34+
createIndexWithConfiguration(
35+
getIndexNameForTest(),
36+
Files.readString(Path.of(classLoader.getResource("processor/IndexMappingMultipleShard.json").toURI())),
37+
PIPELINE_NAME
38+
);
39+
addDocument(getIndexNameForTest(), "0", TEST_FIELD, TEXT, TEST_IMAGE_FIELD, TEST_IMAGE_TEXT);
40+
} else {
41+
String modelId = null;
42+
try {
43+
modelId = getModelId(getIngestionPipeline(PIPELINE_NAME), TEXT_IMAGE_EMBEDDING_PROCESSOR);
44+
loadModel(modelId);
45+
addDocument(getIndexNameForTest(), "1", TEST_FIELD, TEXT_1, TEST_IMAGE_FIELD, TEST_IMAGE_TEXT_1);
46+
validateTestIndex(modelId);
47+
} finally {
48+
wipeOfTestResources(getIndexNameForTest(), PIPELINE_NAME, modelId, null);
49+
}
50+
}
51+
}
52+
53+
private void validateTestIndex(final String modelId) throws Exception {
54+
int docCount = getDocCount(getIndexNameForTest());
55+
assertEquals(2, docCount);
56+
NeuralQueryBuilder neuralQueryBuilder = new NeuralQueryBuilder("passage_embedding", TEXT, TEST_IMAGE_TEXT, modelId, 1, null, null);
57+
Map<String, Object> response = search(getIndexNameForTest(), neuralQueryBuilder, 1);
58+
assertNotNull(response);
59+
}
60+
61+
}

0 commit comments

Comments
 (0)