GH-886: feat(opensearch): Add path prefix configuration support
Fixes: #886 Add ability to configure path prefix for OpenSearch API endpoints via properties. This allows connecting to OpenSearch instances running behind a reverse proxy with a non-root path, similar to Spring Elasticsearch's path-prefix property. - Add pathPrefix property to OpenSearchVectorStoreProperties - Apply pathPrefix to OpenSearchClient when configured - Add documentation and unit tests Signed-off-by: Soby Chacko <soby.chacko@broadcom.com>
This commit is contained in:
committed by
Ilayaperumal Gopinathan
parent
0bbccf8ca0
commit
8b8fb4f02d
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -122,6 +122,10 @@ public class OpenSearchVectorStoreAutoConfiguration {
|
||||
httpClientBuilder.setDefaultRequestConfig(createRequestConfig(properties));
|
||||
return httpClientBuilder;
|
||||
});
|
||||
String pathPrefix = properties.getPathPrefix();
|
||||
if (StringUtils.hasText(pathPrefix)) {
|
||||
transportBuilder.setPathPrefix(pathPrefix);
|
||||
}
|
||||
|
||||
return new OpenSearchClient(transportBuilder.build());
|
||||
}
|
||||
|
||||
@@ -55,6 +55,13 @@ public class OpenSearchVectorStoreProperties extends CommonVectorStoreProperties
|
||||
*/
|
||||
private Duration readTimeout;
|
||||
|
||||
/**
|
||||
* Path prefix for OpenSearch API endpoints. Used when OpenSearch is behind a reverse
|
||||
* proxy with a non-root path. For example, if your OpenSearch instance is accessible
|
||||
* at https://example.com/opensearch/, set this to "/opensearch".
|
||||
*/
|
||||
private String pathPrefix;
|
||||
|
||||
private Aws aws = new Aws();
|
||||
|
||||
public List<String> getUris() {
|
||||
@@ -121,6 +128,14 @@ public class OpenSearchVectorStoreProperties extends CommonVectorStoreProperties
|
||||
this.readTimeout = readTimeout;
|
||||
}
|
||||
|
||||
public String getPathPrefix() {
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
public void setPathPrefix(String pathPrefix) {
|
||||
this.pathPrefix = pathPrefix;
|
||||
}
|
||||
|
||||
public Aws getAws() {
|
||||
return this.aws;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,6 +25,7 @@ import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.opensearch.client.opensearch.OpenSearchClient;
|
||||
import org.opensearch.client.transport.Transport;
|
||||
import org.opensearch.testcontainers.OpensearchContainer;
|
||||
import org.testcontainers.junit.jupiter.Container;
|
||||
import org.testcontainers.junit.jupiter.Testcontainers;
|
||||
@@ -50,6 +51,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
@@ -89,7 +91,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
new Document("3", getText("classpath:/test/data/great.depression.txt"), Map.of("meta2", "meta2")));
|
||||
|
||||
@Test
|
||||
public void addAndSearchTest() {
|
||||
void addAndSearchTest() {
|
||||
|
||||
this.contextRunner.run(context -> {
|
||||
OpenSearchVectorStore vectorStore = context.getBean(OpenSearchVectorStore.class);
|
||||
@@ -148,7 +150,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationDisabledWhenTypeIsNone() {
|
||||
void autoConfigurationDisabledWhenTypeIsNone() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.vectorstore.type=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenSearchVectorStoreProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenSearchVectorStore.class)).isEmpty();
|
||||
@@ -157,7 +159,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationEnabledByDefault() {
|
||||
void autoConfigurationEnabledByDefault() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenSearchVectorStoreProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(VectorStore.class)).isNotEmpty();
|
||||
@@ -166,7 +168,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationEnabledWhenTypeIsOpensearch() {
|
||||
void autoConfigurationEnabledWhenTypeIsOpensearch() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.vectorstore.type=opensearch").run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenSearchVectorStoreProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(VectorStore.class)).isNotEmpty();
|
||||
@@ -175,7 +177,7 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfigurationWithSslBundles() {
|
||||
void autoConfigurationWithSslBundles() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(SslAutoConfiguration.class)).run(context -> {
|
||||
assertThat(context.getBeansOfType(SslBundles.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenSearchClient.class)).isNotEmpty();
|
||||
@@ -185,6 +187,27 @@ class OpenSearchVectorStoreAutoConfigurationIT {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPathPrefixIsConfigured() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(OpenSearchVectorStoreProperties.CONFIG_PREFIX + ".pathPrefix=/custom-path",
|
||||
"spring.ai.vectorstore.opensearch.initialize-schema=false" // Prevent
|
||||
// schema
|
||||
// initialization
|
||||
)
|
||||
.run(context -> {
|
||||
// Verify the property is correctly set in the properties bean
|
||||
OpenSearchVectorStoreProperties properties = context.getBean(OpenSearchVectorStoreProperties.class);
|
||||
assertThat(properties.getPathPrefix()).isEqualTo("/custom-path");
|
||||
|
||||
// Verify the OpenSearchClient was configured with the correct pathPrefix
|
||||
OpenSearchClient client = context.getBean(OpenSearchClient.class);
|
||||
Transport transport = (Transport) ReflectionTestUtils.getField(client, "transport");
|
||||
String configuredPathPrefix = (String) ReflectionTestUtils.getField(transport, "pathPrefix");
|
||||
assertThat(configuredPathPrefix).isEqualTo("/custom-path");
|
||||
});
|
||||
}
|
||||
|
||||
private String getText(String uri) {
|
||||
var resource = new DefaultResourceLoader().getResource(uri);
|
||||
try {
|
||||
|
||||
@@ -105,6 +105,7 @@ spring:
|
||||
similarity-function: cosinesimil
|
||||
read-timeout: <time to wait for response>
|
||||
connect-timeout: <time to wait until connection established>
|
||||
path-prefix: <custom path prefix>
|
||||
ssl-bundle: <name of SSL bundle>
|
||||
aws: # Only for Amazon OpenSearch Service
|
||||
host: <aws opensearch host>
|
||||
@@ -128,6 +129,7 @@ Properties starting with `spring.ai.vectorstore.opensearch.*` are used to config
|
||||
|`spring.ai.vectorstore.opensearch.similarity-function`| The similarity function to use | `cosinesimil`
|
||||
|`spring.ai.vectorstore.opensearch.read-timeout`| Time to wait for response from the opposite endpoint. 0 - infinity. | -
|
||||
|`spring.ai.vectorstore.opensearch.connect-timeout`| Time to wait until connection established. 0 - infinity. | -
|
||||
|`spring.ai.vectorstore.opensearch.path-prefix`| Path prefix for OpenSearch API endpoints. Useful when OpenSearch is behind a reverse proxy with a non-root path. | -
|
||||
|`spring.ai.vectorstore.opensearch.ssl-bundle`| Name of the SSL Bundle to use in case of SSL connection | -
|
||||
|`spring.ai.vectorstore.opensearch.aws.host`| Hostname of the OpenSearch instance | -
|
||||
|`spring.ai.vectorstore.opensearch.aws.service-name`| AWS service name | -
|
||||
@@ -147,6 +149,11 @@ You can control whether the AWS-specific OpenSearch auto-configuration is enable
|
||||
This fallback logic ensures that users have explicit control over the type of OpenSearch integration, preventing accidental activation of AWS-specific logic when not desired.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The `path-prefix` property allows you to specify a custom path prefix when OpenSearch is running behind a reverse proxy that uses a non-root path.
|
||||
For example, if your OpenSearch instance is accessible at `https://example.com/opensearch/` instead of `https://example.com/`, you would set `path-prefix: /opensearch`.
|
||||
====
|
||||
|
||||
The following similarity functions are available:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user