Skip to content

Commit ad2af4f

Browse files
authored
Add semantic enrichment support for OpenSearch sink index creation (opensearch-project#6771)
* Add semantic enrichment support for OpenSearch sink index creation Signed-off-by: Kondaka <krishkdk@amazon.com> * Fixed license headers Signed-off-by: Kondaka <krishkdk@amazon.com> * Addressed review comments. Modified to use AWS SDK instead of http endpoint for creating semantic enrichment enabled indexes Signed-off-by: Kondaka <krishkdk@amazon.com> * Addressed review comments Signed-off-by: Kondaka <krishkdk@amazon.com> * Fixed failing end2end tests by creating a SemanticFieldMapping as a separate class Signed-off-by: Kondaka <krishkdk@amazon.com> * Fixed failing license checks. Addressed comments Signed-off-by: Kondaka <krishkdk@amazon.com> * Fixed failing license checks. Addressed comments Signed-off-by: Kondaka <krishkdk@amazon.com> --------- Signed-off-by: Kondaka <krishkdk@amazon.com>
1 parent e33821d commit ad2af4f

17 files changed

Lines changed: 1183 additions & 6 deletions

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ subprojects {
285285

286286
configure(subprojects.findAll {it.name != 'data-prepper-api'}) {
287287
dependencies {
288-
implementation platform('software.amazon.awssdk:bom:2.30.23')
288+
implementation platform('software.amazon.awssdk:bom:2.39.0')
289289
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
290290
}
291291
}
@@ -385,4 +385,4 @@ coreProjects.each { coreProject ->
385385
def releaseTask = tasks.named('release').get()
386386
releaseTask.dependsOn assembleTasks
387387
releaseTask.dependsOn publishTasks
388-
}
388+
}

data-prepper-plugins/opensearch/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ dependencies {
3535
implementation 'io.micrometer:micrometer-core'
3636
implementation 'software.amazon.awssdk:s3'
3737
implementation 'software.amazon.awssdk:opensearchserverless'
38+
implementation 'software.amazon.awssdk:opensearch'
3839
implementation libs.commons.lang3
3940
implementation libs.caffeine
4041
implementation 'software.amazon.awssdk:apache-client'

data-prepper-plugins/opensearch/src/main/java/org/opensearch/dataprepper/plugins/sink/opensearch/ConnectionConfiguration.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/*
22
* Copyright OpenSearch Contributors
33
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
48
*/
59

610
package org.opensearch.dataprepper.plugins.sink.opensearch;
@@ -120,7 +124,7 @@ public class ConnectionConfiguration {
120124
private final byte[] clientCertContent;
121125
private final byte[] clientKeyContent;
122126

123-
List<String> getHosts() {
127+
public List<String> getHosts() {
124128
return hosts;
125129
}
126130

@@ -131,12 +135,12 @@ String getUsername() {
131135
String getApitoken() {
132136
return apitoken;
133137
}
134-
138+
135139
String getPassword() {
136140
return password;
137141
}
138142

139-
boolean isAwsSigv4() {
143+
public boolean isAwsSigv4() {
140144
return awsSigv4;
141145
}
142146

data-prepper-plugins/opensearch/src/main/java/org/opensearch/dataprepper/plugins/sink/opensearch/OpenSearchSink.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexManagerFactory;
3636
import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexTemplateAPIWrapper;
3737
import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexTemplateAPIWrapperFactory;
38+
import org.opensearch.dataprepper.plugins.sink.opensearch.index.SemanticEnrichmentIndexManager;
3839
import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexType;
3940
import org.opensearch.dataprepper.plugins.sink.opensearch.index.TemplateStrategy;
4041
import org.opensearch.dataprepper.plugins.source.opensearch.configuration.ServerlessOptions;
@@ -156,6 +157,13 @@ private void doInitializeInternal() throws IOException {
156157

157158
maybeUpdateServerlessNetworkPolicy();
158159

160+
// Create index with semantic enrichment via AWS control plane (AOSS or managed domain) if configured.
161+
new SemanticEnrichmentIndexManager(awsCredentialsSupplier).maybeCreateIndex(
162+
connectionConfiguration,
163+
openSearchSinkConfig.getIndexConfiguration().getSemanticEnrichmentConfig(),
164+
openSearchSinkConfig.getIndexConfiguration().getSemanticEnrichmentResourceName(),
165+
configuredIndexAlias);
166+
159167
indexManager.setupIndex();
160168

161169
ingester.initialize();

data-prepper-plugins/opensearch/src/main/java/org/opensearch/dataprepper/plugins/sink/opensearch/configuration/AwsAuthenticationConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
/*
22
* Copyright OpenSearch Contributors
33
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
48
*/
59

610
package org.opensearch.dataprepper.plugins.sink.opensearch.configuration;
711

812
import com.fasterxml.jackson.annotation.JsonProperty;
913
import jakarta.validation.constraints.Size;
14+
import org.opensearch.dataprepper.plugins.sink.opensearch.index.SemanticEnrichmentConfig;
1015
import software.amazon.awssdk.regions.Region;
1116

1217
import java.util.Map;
@@ -34,9 +39,15 @@ public class AwsAuthenticationConfiguration {
3439
@JsonProperty("serverless")
3540
private boolean serverless = false;
3641

42+
@JsonProperty("resource_name")
43+
private String resourceName;
44+
3745
@JsonProperty("serverless_options")
3846
private ServerlessOptions serverlessOptions;
3947

48+
@JsonProperty("semantic_enrichment")
49+
private SemanticEnrichmentConfig semanticEnrichmentConfig;
50+
4051
public String getAwsStsRoleArn() {
4152
return awsStsRoleArn;
4253
}
@@ -57,8 +68,16 @@ public boolean isServerlessCollection() {
5768
return serverless;
5869
}
5970

71+
public String getResourceName() {
72+
return resourceName;
73+
}
74+
6075
public ServerlessOptions getServerlessOptions() {
6176
return serverlessOptions;
6277
}
78+
79+
public SemanticEnrichmentConfig getSemanticEnrichmentConfig() {
80+
return semanticEnrichmentConfig;
81+
}
6382
}
6483

data-prepper-plugins/opensearch/src/main/java/org/opensearch/dataprepper/plugins/sink/opensearch/index/IndexConfiguration.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
/*
22
* Copyright OpenSearch Contributors
33
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
48
*/
59

610
package org.opensearch.dataprepper.plugins.sink.opensearch.index;
@@ -103,8 +107,9 @@ public class IndexConfiguration {
103107
private final String versionExpression;
104108
private final VersionType versionType;
105109
private final boolean normalizeIndex;
106-
107110
private final ScriptConfiguration scriptConfiguration;
111+
private final SemanticEnrichmentConfig semanticEnrichmentConfig;
112+
private final String semanticEnrichmentResourceName;
108113

109114
private final String queryWhen;
110115

@@ -134,6 +139,8 @@ private IndexConfiguration(final Builder builder) {
134139
this.versionType = builder.versionType;
135140
this.normalizeIndex = builder.normalizeIndex;
136141
this.queryOnBulkFailures = builder.queryOnIndexingFailure;
142+
this.semanticEnrichmentConfig = builder.semanticEnrichmentConfig;
143+
this.semanticEnrichmentResourceName = builder.semanticEnrichmentResourceName;
137144

138145
determineTemplateType(builder);
139146

@@ -179,6 +186,7 @@ private IndexConfiguration(final Builder builder) {
179186
this.actions = builder.actions;
180187
this.scriptConfiguration = builder.scriptConfiguration;
181188
this.documentRootKey = builder.documentRootKey;
189+
182190
this.queryWhen = builder.queryWhen;
183191
this.queryTerm = builder.queryTerm;
184192
this.queryActionOnFound = builder.actionOnFound;
@@ -282,6 +290,12 @@ public static IndexConfiguration readIndexConfig(final OpenSearchSinkConfig open
282290
.withDocumentRootKey(openSearchSinkConfig.getDocumentRootKey())
283291
.withDistributionVersion(openSearchSinkConfig.getDistributionVersion());
284292

293+
final AwsAuthenticationConfiguration awsConfig = openSearchSinkConfig.getAwsAuthenticationOptions();
294+
if (awsConfig != null && awsConfig.getSemanticEnrichmentConfig() != null) {
295+
builder = builder.withSemanticEnrichmentConfig(awsConfig.getSemanticEnrichmentConfig());
296+
builder = builder.withSemanticEnrichmentResourceName(awsConfig.getResourceName());
297+
}
298+
285299

286300
final String versionExpression = openSearchSinkConfig.getVersionExpression();
287301
builder = builder.withVersionExpression(versionExpression);
@@ -329,6 +343,13 @@ public static IndexConfiguration readIndexConfig(final OpenSearchSinkConfig open
329343
return builder.build();
330344
}
331345

346+
public SemanticEnrichmentConfig getSemanticEnrichmentConfig() {
347+
return semanticEnrichmentConfig;
348+
}
349+
350+
public String getSemanticEnrichmentResourceName() {
351+
return semanticEnrichmentResourceName;
352+
}
332353

333354
public IndexType getIndexType() {
334355
return indexType;
@@ -541,7 +562,9 @@ public static class Builder {
541562
private Duration queryDuration;
542563
private boolean queryOnIndexingFailure;
543564

565+
private SemanticEnrichmentConfig semanticEnrichmentConfig;
544566
private Integer queryAsyncLimit;
567+
private String semanticEnrichmentResourceName;
545568

546569
public Builder withIndexAlias(final String indexAlias) {
547570
checkArgument(indexAlias != null, "indexAlias cannot be null.");
@@ -782,6 +805,16 @@ public Builder withQueryAsyncLimit(final Integer queryAsyncLimit) {
782805
return this;
783806
}
784807

808+
public Builder withSemanticEnrichmentConfig(final SemanticEnrichmentConfig semanticEnrichmentConfig) {
809+
this.semanticEnrichmentConfig = semanticEnrichmentConfig;
810+
return this;
811+
}
812+
813+
public Builder withSemanticEnrichmentResourceName(final String resourceName) {
814+
this.semanticEnrichmentResourceName = resourceName;
815+
return this;
816+
}
817+
785818
public IndexConfiguration build() {
786819
return new IndexConfiguration(this);
787820
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.plugins.sink.opensearch.index;
11+
12+
import java.net.URI;
13+
import java.util.List;
14+
15+
public class OpenSearchEndpointIdentifier {
16+
17+
private OpenSearchEndpointIdentifier() {
18+
}
19+
20+
public static String extractCollectionId(final List<String> hosts) {
21+
final String hostname = getHostname(hosts);
22+
return hostname.split("\\.")[0];
23+
}
24+
25+
public static String extractDomainName(final List<String> hosts) {
26+
final String hostname = getHostname(hosts);
27+
final String prefix = hostname.split("\\.")[0];
28+
final String withoutSearchPrefix = prefix.replaceFirst("^(search-|vpc-)", "");
29+
final int lastHyphen = withoutSearchPrefix.lastIndexOf('-');
30+
if (lastHyphen <= 0) {
31+
throw new IllegalArgumentException(
32+
"Unable to extract domain name from host: " + hostname +
33+
". Please set the 'domain_name' option in semantic_enrichment config.");
34+
}
35+
return withoutSearchPrefix.substring(0, lastHyphen);
36+
}
37+
38+
static String getHostname(final List<String> hosts) {
39+
if (hosts == null || hosts.isEmpty()) {
40+
throw new IllegalArgumentException("Hosts list is empty, cannot extract endpoint identifier");
41+
}
42+
final String hostname = URI.create(hosts.get(0)).getHost();
43+
if (hostname == null) {
44+
throw new IllegalArgumentException("Unable to parse hostname from: " + hosts.get(0));
45+
}
46+
return hostname;
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
10+
package org.opensearch.dataprepper.plugins.sink.opensearch.index;
11+
12+
import com.fasterxml.jackson.annotation.JsonProperty;
13+
14+
import java.util.List;
15+
16+
public class SemanticEnrichmentConfig {
17+
18+
@JsonProperty("fields")
19+
private List<SemanticFieldMapping> fields;
20+
21+
public List<SemanticFieldMapping> getFields() {
22+
return fields;
23+
}
24+
}

0 commit comments

Comments
 (0)