Skip to content

Commit

Permalink
Add a way to configure an OpenSearch distribution for the Elasticsear…
Browse files Browse the repository at this point in the history
…ch DevService
  • Loading branch information
marko-bekhta committed Jul 24, 2023
1 parent e96b68a commit 17828c7
Show file tree
Hide file tree
Showing 18 changed files with 488 additions and 33 deletions.
7 changes: 7 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@
<apicurio-common-rest-client.version>0.1.17.Final</apicurio-common-rest-client.version> <!-- must be the version Apicurio Registry uses -->
<testcontainers.version>1.18.3</testcontainers.version> <!-- Make sure to also update docker-java.version to match its needs -->
<docker-java.version>3.3.0</docker-java.version> <!-- must be the version Testcontainers use -->
<!-- Check the compatibility matrix (https://github.com/opensearch-project/opensearch-testcontainers) before upgrading: -->
<opensearch-testcontainers.version>2.0.0</opensearch-testcontainers.version>
<com.dajudge.kindcontainer>1.3.0</com.dajudge.kindcontainer>
<aesh.version>2.7</aesh.version>
<aesh-readline.version>2.4</aesh-readline.version>
Expand Down Expand Up @@ -384,6 +386,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.opensearch</groupId>
<artifactId>opensearch-testcontainers</artifactId>
<version>${opensearch-testcontainers.version}</version>
</dependency>

<!-- OpenTelemetry components, imported as a BOM -->
<dependency>
Expand Down
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/_attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
:gradle-version: ${gradle-wrapper.version}
:elasticsearch-version: ${elasticsearch-server.version}
:elasticsearch-image: ${elasticsearch.image}
:opensearch-image: ${opensearch.image}
:infinispan-version: ${infinispan.version}
:infinispan-protostream-version: ${infinispan.protostream.version}
:logstash-image: ${logstash.image}
Expand Down
16 changes: 12 additions & 4 deletions docs/src/main/asciidoc/elasticsearch-dev-services.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
////
= Dev Services for Elasticsearch

Check warning on line 6 in docs/src/main/asciidoc/elasticsearch-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Dev Services for Elasticsearch'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Dev Services for Elasticsearch'.", "location": {"path": "docs/src/main/asciidoc/elasticsearch-dev-services.adoc", "range": {"start": {"line": 6, "column": 3}}}, "severity": "INFO"}

include::_attributes.adoc[]
:categories: data
:summary: Start Elasticsearch automatically in dev and test modes

If any Elasticsearch-related extension is present (e.g. `quarkus-elasticsearch-rest-client` or `quarkus-hibernate-search-orm-elasticsearch`),
Dev Services for Elasticsearch automatically starts an Elasticsearch server in dev mode and when running tests.
Expand Down Expand Up @@ -48,14 +49,21 @@ Note that the Elasticsearch hosts property is automatically configured with the

== Configuring the image

Dev Services for Elasticsearch only support Elasticsearch based images, OpenSearch is not supported at the moment.
Dev Services for Elasticsearch support distributions based on both Elasticsearch and OpenSearch images.

If you need to use a different image than the default one you can configure it via:
[source, properties]
If you need to use a different Elasticsearch-based image than the default one you can configure it via:

Check warning on line 54 in docs/src/main/asciidoc/elasticsearch-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/elasticsearch-dev-services.adoc", "range": {"start": {"line": 54, "column": 8}}}, "severity": "INFO"}

Check warning on line 54 in docs/src/main/asciidoc/elasticsearch-dev-services.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'through' rather than 'via' unless updating existing content that uses it. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'through' rather than 'via' unless updating existing content that uses it.", "location": {"path": "docs/src/main/asciidoc/elasticsearch-dev-services.adoc", "range": {"start": {"line": 54, "column": 100}}}, "severity": "WARNING"}
[source,properties,subs="attributes"]
----
quarkus.elasticsearch.devservices.image-name={elasticsearch-image}
----

Alternatively, if an OpenSearch-based image should be used, both distribution and image name properties must be specified:
[source,properties,subs="attributes"]
----
quarkus.elasticsearch.devservices.distribution=opensearch
quarkus.elasticsearch.devservices.image-name={opensearch-image}
----

== Current limitations

Currently, only the default backend for Hibernate Search Elasticsearch is supported, because Dev Services for Elasticsearch can only start one Elasticsearch container.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
Expand Down Expand Up @@ -43,7 +44,8 @@ public static String configureSharedNetwork(GenericContainer<?> container, Strin
}

String hostName = (hostNamePrefix + "-" + Base58.randomString(5)).toLowerCase(Locale.ROOT);
container.setNetworkAliases(Collections.singletonList(hostName));
// some containers might try to add their own aliases on start, so we want to keep this list modifiable:
container.setNetworkAliases(new ArrayList<>(List.of(hostName)));

return hostName;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.opensearch</groupId>
<artifactId>opensearch-testcontainers</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.util.function.Supplier;

import org.jboss.logging.Logger;
import org.opensearch.testcontainers.OpensearchContainer;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;

Expand All @@ -31,6 +33,7 @@
import io.quarkus.devservices.common.ConfigureUtil;
import io.quarkus.devservices.common.ContainerAddress;
import io.quarkus.devservices.common.ContainerLocator;
import io.quarkus.elasticsearch.restclient.common.deployment.ElasticsearchDevServicesBuildTimeConfig.Distribution;
import io.quarkus.runtime.configuration.ConfigUtils;

/**
Expand Down Expand Up @@ -171,11 +174,6 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch(
return null;
}

// We only support ELASTIC container for now
if (buildItemConfig.distribution == DevservicesElasticsearchBuildItem.Distribution.OPENSEARCH) {
throw new BuildException("Dev Services for Elasticsearch doesn't support OpenSearch", Collections.emptyList());
}

// Hibernate Search Elasticsearch have a version configuration property, we need to check that it is coherent
// with the image we are about to launch
if (buildItemConfig.version != null) {
Expand All @@ -189,17 +187,25 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch(
}
}

if (buildItemConfig.distribution != null && !buildItemConfig.distribution.equals(config.distribution)) {
throw new BuildException(
"Dev Services for Elasticsearch detected a distribution mismatch, distribution is " + config.distribution
+ " but the configured distribution is " + buildItemConfig.distribution +
". Either configure a different distribution or disable Dev Services for Elasticsearch.",
Collections.emptyList());
}

final Optional<ContainerAddress> maybeContainerAddress = elasticsearchContainerLocator.locateContainer(
config.serviceName,
config.shared,
launchMode.getLaunchMode());

// Starting the server
final Supplier<DevServicesResultBuildItem.RunningDevService> defaultElasticsearchSupplier = () -> {
ElasticsearchContainer container = new ElasticsearchContainer(
DockerImageName.parse(config.imageName)
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"));
ConfigureUtil.configureSharedNetwork(container, "elasticsearch");
GenericContainer<?> container = Distribution.ELASTIC.equals(config.distribution)
? createElasticsearchContainer(config)
: createOpensearchContainer(config);

if (config.serviceName != null) {
container.withLabel(DEV_SERVICE_LABEL, config.serviceName);
}
Expand All @@ -209,15 +215,12 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch(
timeout.ifPresent(container::withStartupTimeout);

container.withEnv(config.containerEnv);
container.addEnv("ES_JAVA_OPTS", config.javaOpts);
// Disable security as else we would need to configure it correctly to avoid tons of WARNING in the log
container.addEnv("xpack.security.enabled", "false");

container.start();
return new DevServicesResultBuildItem.RunningDevService(Feature.ELASTICSEARCH_REST_CLIENT_COMMON.getName(),
container.getContainerId(),
container::close,
buildPropertiesMap(buildItemConfig, container.getHttpHostAddress()));
buildPropertiesMap(buildItemConfig, container.getHost() + ":" + container.getMappedPort(9200)));
};

return maybeContainerAddress
Expand All @@ -229,6 +232,31 @@ private DevServicesResultBuildItem.RunningDevService startElasticsearch(
.orElseGet(defaultElasticsearchSupplier);
}

private GenericContainer<?> createElasticsearchContainer(ElasticsearchDevServicesBuildTimeConfig config) {
ElasticsearchContainer container = new ElasticsearchContainer(
DockerImageName.parse(config.imageName)
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"));
ConfigureUtil.configureSharedNetwork(container, "elasticsearch");

// Disable security as else we would need to configure it correctly to avoid tons of WARNING in the log
// Only needed for Elasticsearch. Opensearch container has security disabled by default
// and is using a different property to enable/disable it.
container.addEnv("xpack.security.enabled", "false");
container.addEnv("ES_JAVA_OPTS", config.javaOpts);
return container;
}

private GenericContainer<?> createOpensearchContainer(ElasticsearchDevServicesBuildTimeConfig config) {
OpensearchContainer container = new OpensearchContainer(
DockerImageName.parse(config.imageName)
.asCompatibleSubstituteFor("opensearchproject/opensearch"));
container.addEnv("OPENSEARCH_JAVA_OPTS", config.javaOpts);
container.addEnv("bootstrap.memory_lock", "true");
container.addEnv("plugins.index_state_management.enabled", "false");
ConfigureUtil.configureSharedNetwork(container, "opensearch");
return container;
}

private Map<String, String> buildPropertiesMap(DevservicesElasticsearchBuildItemsConfiguration buildItemConfig,
String httpHosts) {
Map<String, String> propertiesToSet = new HashMap<>();
Expand All @@ -245,7 +273,7 @@ private String displayProperties(Set<String> hostsConfigProperties) {
private static class DevservicesElasticsearchBuildItemsConfiguration {
private Set<String> hostsConfigProperties;
private String version;
private DevservicesElasticsearchBuildItem.Distribution distribution;
private Distribution distribution;

private DevservicesElasticsearchBuildItemsConfiguration(List<DevservicesElasticsearchBuildItem> buildItems)
throws BuildException {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkus.elasticsearch.restclient.common.deployment;

import static io.quarkus.elasticsearch.restclient.common.deployment.ElasticsearchDevServicesBuildTimeConfig.Distribution;

import io.quarkus.builder.item.MultiBuildItem;

public final class DevservicesElasticsearchBuildItem extends MultiBuildItem {
Expand All @@ -11,7 +13,7 @@ public final class DevservicesElasticsearchBuildItem extends MultiBuildItem {
public DevservicesElasticsearchBuildItem(String hostsConfigProperty) {
this.hostsConfigProperty = hostsConfigProperty;
this.version = null;
this.distribution = Distribution.ELASTIC;
this.distribution = null;
}

public DevservicesElasticsearchBuildItem(String configItemName, String version, Distribution distribution) {
Expand All @@ -32,8 +34,4 @@ public Distribution getDistribution() {
return distribution;
}

public enum Distribution {
ELASTIC,
OPENSEARCH
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,20 @@ public class ElasticsearchDevServicesBuildTimeConfig {
@ConfigItem
public Optional<Integer> port;

/**
* The distribution of search services to use.
* Defaults to the Elasticsearch distribution.
* <p>
* {@link #imageName Image name} <b>must</b> be set when
* the non-default ({@link Distribution#OPENSEARCH Opensearch}) distribution is used.
*/
@ConfigItem(defaultValue = "elastic")
public Distribution distribution;

/**
* The Elasticsearch container image to use.
* Defaults to the elasticsearch image provided by Elastic.
* {@link #distribution Distribution} <b>must</b> be set to {@code opensearch} when an opensearch image name is set.
*/
@ConfigItem(defaultValue = "docker.elastic.co/elasticsearch/elasticsearch:7.17.0")
public String imageName;
Expand Down Expand Up @@ -84,6 +95,7 @@ public boolean equals(Object o) {
return Objects.equals(shared, that.shared)
&& Objects.equals(enabled, that.enabled)
&& Objects.equals(port, that.port)
&& Objects.equals(distribution, that.distribution)
&& Objects.equals(imageName, that.imageName)
&& Objects.equals(javaOpts, that.javaOpts)
&& Objects.equals(serviceName, that.serviceName)
Expand All @@ -92,6 +104,11 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(enabled, port, imageName, javaOpts, shared, serviceName, containerEnv);
return Objects.hash(enabled, port, distribution, imageName, javaOpts, shared, serviceName, containerEnv);
}

public enum Distribution {
ELASTIC,
OPENSEARCH
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.hibernate.search.orm.elasticsearch.deployment;

import static io.quarkus.elasticsearch.restclient.common.deployment.ElasticsearchDevServicesBuildTimeConfig.Distribution;
import static io.quarkus.hibernate.search.orm.elasticsearch.deployment.ClassNames.INDEXED;
import static io.quarkus.hibernate.search.orm.elasticsearch.deployment.ClassNames.PROJECTION_CONSTRUCTOR;
import static io.quarkus.hibernate.search.orm.elasticsearch.deployment.ClassNames.ROOT_MAPPING;
Expand Down Expand Up @@ -402,7 +403,7 @@ DevservicesElasticsearchBuildItem devServices(HibernateSearchElasticsearchBuildT
"hosts");
return new DevservicesElasticsearchBuildItem(hostsPropertyKey,
version.versionString(),
DevservicesElasticsearchBuildItem.Distribution.valueOf(version.distribution().toString().toUpperCase()));
Distribution.valueOf(version.distribution().toString().toUpperCase()));
}

@BuildStep(onlyIfNot = IsNormal.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ By default, the tests of this module are disabled.
To run the tests in a standard JVM with Elasticsearch started in the JVM, you can run the following command:

```
mvn clean install -Dtest-containers
mvn clean install -Dtest-containers -Dstart-containers
```

Additionally, you can generate a native image and run the tests for this native image by adding `-Dnative`:

```
mvn clean install -Dtest-containers -Dnative
mvn clean install -Dtest-containers -Dstart-containers -Dnative
```

4 changes: 2 additions & 2 deletions integration-tests/hibernate-search-orm-opensearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ By default, the tests of this module are disabled.
To run the tests in a standard JVM with OpenSearch started in the JVM, you can run the following command:

```
mvn clean install -Dtest-containers
mvn clean install -Dtest-containers -Dstart-containers
```

Additionally, you can generate a native image and run the tests for this native image by adding `-Dnative`:

```
mvn clean install -Dtest-containers -Dnative
mvn clean install -Dtest-containers -Dstart-containers -Dnative
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.quarkus.it.hibernate.search.orm.opensearch.devservices;

import java.util.List;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.search.mapper.orm.schema.management.SchemaManagementStrategyName;
import org.hibernate.search.mapper.orm.session.SearchSession;

@Path("/test/dev-services")
public class HibernateSearchDevServicesTestResource {

@Inject
SessionFactory sessionFactory;

@Inject
Session session;

@Inject
SearchSession searchSession;

@GET
@Path("/hosts")
@Transactional
@SuppressWarnings("unchecked")
public String hosts() {
return ((List<String>) sessionFactory.getProperties().get("hibernate.search.backend.hosts")).iterator().next();
}

@GET
@Path("/schema-management-strategy")
@Transactional
public String schemaManagementStrategy() {
var strategy = ((SchemaManagementStrategyName) sessionFactory.getProperties()
.get("hibernate.search.schema_management.strategy"));
return strategy == null ? null : strategy.externalRepresentation();
}

@PUT
@Path("/init-data")
@Transactional
public void initData() {
IndexedEntity entity = new IndexedEntity("John Irving");
session.persist(entity);
}

@PUT
@Path("/refresh")
@Produces(MediaType.TEXT_PLAIN)
public String refresh() {
searchSession.workspace().refresh();
return "OK";
}

@GET
@Path("/count")
@Produces(MediaType.TEXT_PLAIN)
public long count() {
return searchSession.search(IndexedEntity.class)
.where(f -> f.matchAll())
.fetchTotalHitCount();
}
}
Loading

0 comments on commit 17828c7

Please sign in to comment.