diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.elasticsearch-base.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.elasticsearch-base.gradle index 7e9c9078..29ad2bbf 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.elasticsearch-base.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.elasticsearch-base.gradle @@ -2,10 +2,3 @@ repositories { mavenCentral() maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } } - -configurations.all { - resolutionStrategy.dependencySubstitution { - substitute(module("org.codehaus.groovy:groovy")) - .using(module("org.apache.groovy:groovy:4.0.10")) - } -} diff --git a/elasticsearch-bom/build.gradle.kts b/elasticsearch-bom/build.gradle.kts index 1f39ff0d..87a65b87 100644 --- a/elasticsearch-bom/build.gradle.kts +++ b/elasticsearch-bom/build.gradle.kts @@ -1,3 +1,9 @@ plugins { id("io.micronaut.build.internal.bom") } +micronautBuild { + micronautBuild { + // required because elasticsearch-rest-high-level-client was removed + tasks.named("checkVersionCatalogCompatibility") { onlyIf { false } } + } +} diff --git a/elasticsearch/build.gradle.kts b/elasticsearch/build.gradle.kts index c899b62a..78635a90 100644 --- a/elasticsearch/build.gradle.kts +++ b/elasticsearch/build.gradle.kts @@ -7,9 +7,7 @@ dependencies { compileOnly(libs.graal.svm) implementation(mn.micronaut.management) - api(libs.managed.elasticsearch.java) - api(libs.managed.elasticsearch.rest.high.level.client) api(mn.micronaut.http) implementation(mn.micronaut.jackson.databind) @@ -27,3 +25,8 @@ tasks { } } +micronautBuild { + binaryCompatibility { + enabled.set(false) + } +} diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultElasticsearchClientFactory.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultElasticsearchClientFactory.java index c9e99503..a4e6859d 100755 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultElasticsearchClientFactory.java +++ b/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultElasticsearchClientFactory.java @@ -25,7 +25,6 @@ import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; import co.elastic.clients.elasticsearch.ElasticsearchClient; @@ -44,17 +43,6 @@ @Factory public class DefaultElasticsearchClientFactory { - /** - * Create the {@link RestHighLevelClient} bean for the given configuration. - * - * @param elasticsearchConfiguration The {@link DefaultElasticsearchConfigurationProperties} object - * @return A {@link RestHighLevelClient} bean - */ - @Bean(preDestroy = "close") - RestHighLevelClient restHighLevelClient(DefaultElasticsearchConfigurationProperties elasticsearchConfiguration) { - return new RestHighLevelClient(restClientBuilder(elasticsearchConfiguration)); - } - /** * @param elasticsearchConfiguration The {@link DefaultElasticsearchConfigurationProperties} object * @return The Elasticsearch Rest Client diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultHttpAsyncClientBuilderFactory.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultHttpAsyncClientBuilderFactory.java index 84d362f3..bdfe202e 100644 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultHttpAsyncClientBuilderFactory.java +++ b/elasticsearch/src/main/java/io/micronaut/elasticsearch/DefaultHttpAsyncClientBuilderFactory.java @@ -19,8 +19,7 @@ import io.micronaut.context.annotation.Factory; import io.micronaut.context.annotation.Requires; import org.apache.http.impl.nio.client.HttpAsyncClientBuilder; -import org.elasticsearch.client.RestHighLevelClient; - +import org.elasticsearch.client.RestClient; import jakarta.inject.Singleton; /** @@ -29,7 +28,7 @@ * @author Puneet Behl * @since 1.0.0 */ -@Requires(classes = {RestHighLevelClient.class}) +@Requires(classes = {RestClient.class}) @Factory public class DefaultHttpAsyncClientBuilderFactory { diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/conditon/RequiresElasticsearch.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/conditon/RequiresElasticsearch.java index d615b2af..adc6e2b4 100644 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/conditon/RequiresElasticsearch.java +++ b/elasticsearch/src/main/java/io/micronaut/elasticsearch/conditon/RequiresElasticsearch.java @@ -17,7 +17,7 @@ import io.micronaut.elasticsearch.ElasticsearchSettings; import io.micronaut.context.annotation.Requires; -import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.RestClient; import java.lang.annotation.*; @@ -31,6 +31,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PACKAGE, ElementType.TYPE}) @Requires(property = ElasticsearchSettings.PREFIX) -@Requires(classes = {RestHighLevelClient.class}) +@Requires(classes = {RestClient.class}) public @interface RequiresElasticsearch { } diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/graalvm/RestClientSubstitutions.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/graalvm/RestClientSubstitutions.java index d4bedbe3..ce5f60f7 100644 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/graalvm/RestClientSubstitutions.java +++ b/elasticsearch/src/main/java/io/micronaut/elasticsearch/graalvm/RestClientSubstitutions.java @@ -47,7 +47,7 @@ * * We substitute it with an implementation which does not use serialization. * - * Forked from Quarkus: https://github.com/quarkusio/quarkus/blob/c9cba824e8812fa3f15474b8382ac5d90f7238aa/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/runtime/graal/Substitute_RestClient.java + * Forked from Quarkus: https://github.com/quarkusio/quarkus/blob/main/extensions/elasticsearch-rest-client-common/runtime/src/main/java/io/quarkus/elasticsearch/restclient/common/runtime/graal/Substitute_RestClient.java * * @author Iván López * @since 2.0.0 @@ -109,7 +109,7 @@ public NoSerializationBasicAuthCache() { this(null); } - protected HttpHost getKey(final HttpHost host) { + private HttpHost getKey(final HttpHost host) { if (host.getPort() <= 0) { final int port; try { diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicator.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicator.java index c30ff3a3..67b20b7f 100644 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicator.java +++ b/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicator.java @@ -23,11 +23,12 @@ import org.reactivestreams.Publisher; import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient; +import co.elastic.clients.elasticsearch.cluster.HealthResponse; import jakarta.inject.Singleton; -import java.io.IOException; import static io.micronaut.health.HealthStatus.DOWN; import static io.micronaut.health.HealthStatus.UP; +import java.util.Locale; /** * A {@link HealthIndicator} for Elasticsearch that uses an automatically-configured high-level REST client, injected as a dependency, to communicate @@ -39,7 +40,6 @@ */ @Requires(beans = HealthEndpoint.class) @Requires(property = HealthEndpoint.PREFIX + ".elasticsearch.enabled", notEquals = "false") -@Requires(property = HealthEndpoint.PREFIX + ".elasticsearch.rest.high.level.enabled", notEquals = "true") // we don't want to clash with the deprecated check @Singleton public class ElasticsearchClientHealthIndicator implements HealthIndicator { @@ -65,24 +65,41 @@ public ElasticsearchClientHealthIndicator(ElasticsearchAsyncClient client) { */ @Override public Publisher getResult() { - return (subscriber -> { - final HealthResult.Builder resultBuilder = HealthResult.builder(NAME); - try { - client.cluster().health().handle((health, exception) -> { - if (exception != null) { - subscriber.onNext(resultBuilder.status(DOWN).exception(exception).build()); - subscriber.onComplete(); - } else { - HealthStatus status = health.status() == co.elastic.clients.elasticsearch._types.HealthStatus.Red ? DOWN : UP; - subscriber.onNext(resultBuilder.status(status).details(health).build()); - subscriber.onComplete(); - } - return health; - }); - } catch (IOException e) { - subscriber.onNext(resultBuilder.status(DOWN).exception(e).build()); - subscriber.onComplete(); - } - }); + return (subscriber -> client.cluster().health() + .handle((health, exception) -> { + final HealthResult.Builder resultBuilder = HealthResult.builder(NAME); + if (exception != null) { + subscriber.onNext(resultBuilder.status(DOWN).exception(exception).build()); + subscriber.onComplete(); + } else { + HealthStatus status = health.status() == co.elastic.clients.elasticsearch._types.HealthStatus.Red ? DOWN : UP; + subscriber.onNext(resultBuilder.status(status).details(healthResultDetails(health)).build()); + subscriber.onComplete(); + } + return health; + })); + } + + private String healthResultDetails(HealthResponse response) { + return "{" + + "\"cluster_name\":\"" + response.clusterName() + "\"," + + "\"status\":\"" + response.status().name().toLowerCase(Locale.ENGLISH) + "\"," + + "\"timed_out\":" + response.timedOut() + "," + + "\"number_of_nodes\":" + response.numberOfNodes() + "," + + "\"number_of_data_nodes\":" + response.numberOfDataNodes() + "," + + "\"number_of_pending_tasks\":" + response.numberOfPendingTasks() + "," + + "\"number_of_in_flight_fetch\":" + response.numberOfInFlightFetch() + "," + + "\"task_max_waiting_in_queue\":\"" + response.taskMaxWaitingInQueueMillis() + "\"," + + "\"task_max_waiting_in_queue_millis\":" + response.taskMaxWaitingInQueueMillis() + "," + + "\"active_shards_percent_as_number\":\"" + response.activeShardsPercentAsNumber() + "\"," + + "\"active_primary_shards\":" + response.activePrimaryShards() + "," + + "\"active_shards\":" + response.activeShards() + "," + + "\"relocating_shards\":" + response.relocatingShards() + "," + + "\"initializing_shards\":" + response.initializingShards() + "," + + "\"unassigned_shards\":" + response.unassignedShards() + "," + + "\"delayed_unassigned_shards\":" + response.delayedUnassignedShards() + + "}"; } } + + diff --git a/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicator.java b/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicator.java deleted file mode 100644 index ad293e69..00000000 --- a/elasticsearch/src/main/java/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicator.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2017-2020 original authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.micronaut.elasticsearch.health; - -import io.micronaut.context.annotation.Requires; -import io.micronaut.health.HealthStatus; -import io.micronaut.management.endpoint.health.HealthEndpoint; -import io.micronaut.management.health.indicator.HealthIndicator; -import io.micronaut.management.health.indicator.HealthResult; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; -import org.elasticsearch.client.RequestOptions; -import org.elasticsearch.client.RestHighLevelClient; -import org.elasticsearch.common.Strings; -import org.elasticsearch.xcontent.ToXContent; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.XContentFactory; -import org.reactivestreams.Publisher; - -import jakarta.inject.Singleton; -import java.io.IOException; - -import static io.micronaut.health.HealthStatus.DOWN; -import static io.micronaut.health.HealthStatus.UP; -import static java.util.Collections.emptyMap; -import static org.elasticsearch.cluster.health.ClusterHealthStatus.GREEN; -import static org.elasticsearch.cluster.health.ClusterHealthStatus.YELLOW; - -/** - * A {@link HealthIndicator} for Elasticsearch that uses an automatically-configured high-level REST client, injected as a dependency, to communicate - * with Elasticsearch. - * - * @author Puneet Behl - * @author Robyn Dalgleish - * @since 1.0.0 - */ -@Requires(beans = HealthEndpoint.class) -@Requires(property = HealthEndpoint.PREFIX + ".elasticsearch.rest.high.level.enabled", notEquals = "false") -@Singleton -public class ElasticsearchHealthIndicator implements HealthIndicator { - - private static final String NAME = "elasticsearch"; - - private final RestHighLevelClient esClient; - - /** - * Constructor. - * - * @param esClient The Elasticsearch high level REST client. - */ - public ElasticsearchHealthIndicator(RestHighLevelClient esClient) { - this.esClient = esClient; - } - - /** - * Tries to call the cluster info API on Elasticsearch to obtain information about the cluster. If the call succeeds, the Elasticsearch cluster - * health status (GREEN / YELLOW / RED) will be included in the health indicator details. - * - * @return A positive health result UP if the cluster can be communicated with and is in either GREEN or YELLOW status. A negative health result - * DOWN if the cluster cannot be communicated with or is in RED status. - */ - @Override - public Publisher getResult() { - - return (subscriber -> esClient.cluster().healthAsync(new ClusterHealthRequest(), RequestOptions.DEFAULT, new ActionListener() { - - private final HealthResult.Builder resultBuilder = HealthResult.builder(NAME); - - @Override - public void onResponse(ClusterHealthResponse response) { - - HealthResult result; - - try { - result = resultBuilder - .status(healthResultStatus(response)) - .details(healthResultDetails(response)) - .build(); - } catch (IOException e) { - result = resultBuilder.status(DOWN).exception(e).build(); - } - - subscriber.onNext(result); - subscriber.onComplete(); - } - - @Override - public void onFailure(Exception e) { - subscriber.onNext(resultBuilder.status(DOWN).exception(e).build()); - subscriber.onComplete(); - } - })); - } - - private String healthResultDetails(ClusterHealthResponse response) throws IOException { - XContentBuilder xContentBuilder = XContentFactory.jsonBuilder(); - response.toXContent(xContentBuilder, new ToXContent.MapParams(emptyMap())); - return Strings.toString(xContentBuilder); - } - - private HealthStatus healthResultStatus(ClusterHealthResponse response) { - return response.getStatus() == GREEN || response.getStatus() == YELLOW ? UP : DOWN; - } -} diff --git a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/DefaultElasticsearchConfigurationPropertiesSpec.groovy b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/DefaultElasticsearchConfigurationPropertiesSpec.groovy index d70b66df..15325c9d 100644 --- a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/DefaultElasticsearchConfigurationPropertiesSpec.groovy +++ b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/DefaultElasticsearchConfigurationPropertiesSpec.groovy @@ -16,6 +16,7 @@ package io.micronaut.elasticsearch +import co.elastic.clients.elasticsearch.ElasticsearchClient import io.micronaut.context.ApplicationContext import io.micronaut.context.annotation.Factory import io.micronaut.context.annotation.Replaces @@ -27,7 +28,6 @@ import org.apache.http.impl.client.BasicCredentialsProvider import org.apache.http.impl.nio.client.HttpAsyncClientBuilder import org.apache.http.impl.nio.reactor.IOReactorConfig import org.elasticsearch.client.NodeSelector -import org.elasticsearch.client.RestHighLevelClient import spock.lang.Specification import jakarta.inject.Singleton @@ -38,8 +38,7 @@ import jakarta.inject.Singleton */ class DefaultElasticsearchConfigurationPropertiesSpec extends Specification { - void "Test Elasticsearch high level rest client configrations"() { - + void "Test Elasticsearch rest client configrations"() { when: ApplicationContext applicationContext = ApplicationContext.run( @@ -108,7 +107,7 @@ class DefaultElasticsearchConfigurationPropertiesSpec extends Specification { expect: applicationContext.containsBean(DefaultElasticsearchConfigurationProperties) - applicationContext.containsBean(RestHighLevelClient) + applicationContext.containsBean(ElasticsearchClient) applicationContext.getBean(DefaultElasticsearchConfigurationProperties).httpHosts.size() == 2 } diff --git a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchAuthorizationSpec.groovy b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchAuthorizationSpec.groovy index c2f4f12d..5d3caab2 100644 --- a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchAuthorizationSpec.groovy +++ b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchAuthorizationSpec.groovy @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package io.micronaut.elasticsearch +import co.elastic.clients.elasticsearch.ElasticsearchClient +import co.elastic.clients.elasticsearch.core.InfoResponse import io.micronaut.context.ApplicationContext -import org.elasticsearch.client.RequestOptions -import org.elasticsearch.client.RestHighLevelClient -import org.elasticsearch.client.core.MainResponse +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy import org.testcontainers.elasticsearch.ElasticsearchContainer -import org.testcontainers.utility.DockerImageName import spock.lang.Requires import spock.lang.Specification @@ -31,30 +29,40 @@ class ElasticsearchAuthorizationSpec extends Specification { static final String ELASTICSEARCH_VERSION = System.getProperty("elasticsearch.version") static final String ELASTICSEARCH_USERNAME = "elastic" static final String ELASTICSEARCH_PASSWORD = "changeme" + static final ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:$ELASTICSEARCH_VERSION") + .withExposedPorts(9200) + .withEnv("xpack.security.enabled", "false") + .withPassword(ELASTICSEARCH_PASSWORD) + .waitingFor(new LogMessageWaitStrategy().withRegEx(".*\"message\":\"started\".*")) + + void setupSpec() { + container.start() + } + + void cleanupSpec() { + container.stop() + } void "Test Elasticsearch authorization"() { given: - ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName.parse("elasticsearch:$ELASTICSEARCH_VERSION").asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch")) - container.withPassword(ELASTICSEARCH_PASSWORD) - container.start() String token = new String(Base64.getEncoder().encode((ELASTICSEARCH_USERNAME + ':' + ELASTICSEARCH_PASSWORD).getBytes())) ApplicationContext applicationContext = ApplicationContext.run( - 'elasticsearch.httpHosts': 'http://' + container.getHttpHostAddress(), - 'elasticsearch.default-headers': "Authorization:Basic ${token}" + 'elasticsearch.httpHosts': "http://${container.httpHostAddress}", + 'elasticsearch.default-headers': "Authorization:Basic $token" ) expect: - applicationContext.containsBean(RestHighLevelClient) - applicationContext.getBean(RestHighLevelClient).ping(RequestOptions.DEFAULT) - MainResponse response = applicationContext.getBean(RestHighLevelClient).info(RequestOptions.DEFAULT) - System.out.println(String.format("cluster: %s, node: %s, version: %s %s", response.getClusterName(), response.getNodeName(), response.getVersion().getNumber(), response.getVersion().getBuildDate())) + applicationContext.containsBean(ElasticsearchClient) + applicationContext.getBean(ElasticsearchClient).ping() + InfoResponse response = applicationContext.getBean(ElasticsearchClient).info() + System.out.println(String.format("cluster: %s, node: %s, version: %s %s", response.clusterName(), response.name(), response.version().number(), response.version().buildDate())) cleanup: applicationContext.close() - container.stop() + } } diff --git a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchMappingSpec.groovy b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchMappingSpec.groovy index 42477601..840f7866 100755 --- a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchMappingSpec.groovy +++ b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/ElasticsearchMappingSpec.groovy @@ -16,23 +16,13 @@ package io.micronaut.elasticsearch +import co.elastic.clients.elasticsearch.ElasticsearchClient +import co.elastic.clients.elasticsearch._types.Result +import co.elastic.clients.elasticsearch.core.* +import co.elastic.clients.elasticsearch.indices.ExistsRequest import io.micronaut.context.ApplicationContext -import org.elasticsearch.Version -import org.elasticsearch.action.DocWriteResponse -import org.elasticsearch.action.delete.DeleteRequest -import org.elasticsearch.action.delete.DeleteResponse -import org.elasticsearch.action.get.GetRequest -import org.elasticsearch.action.get.GetResponse -import org.elasticsearch.action.index.IndexRequest -import org.elasticsearch.action.index.IndexResponse -import org.elasticsearch.client.RequestOptions -import org.elasticsearch.client.RestHighLevelClient -import org.elasticsearch.client.core.MainResponse -import org.elasticsearch.client.indices.GetIndexRequest -import org.elasticsearch.search.fetch.subphase.FetchSourceContext -import org.elasticsearch.xcontent.XContentType +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy import org.testcontainers.elasticsearch.ElasticsearchContainer -import org.testcontainers.utility.DockerImageName import spock.lang.Requires import spock.lang.Specification @@ -45,134 +35,118 @@ import spock.lang.Specification class ElasticsearchMappingSpec extends Specification { final static String ELASTICSEARCH_VERSION = System.getProperty("elasticsearch.version") + static final ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:$ELASTICSEARCH_VERSION") + .withExposedPorts(9200) + .withEnv("xpack.security.enabled", "false") + .waitingFor(new LogMessageWaitStrategy().withRegEx(".*\"message\":\"started\".*")) - void "Test Elasticsearch connection"() { - given: - ElasticsearchContainer container = new ElasticsearchContainer( - DockerImageName.parse("elasticsearch:$ELASTICSEARCH_VERSION") - .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch") - ) + void setupSpec() { container.start() + } + + void cleanupSpec() { + container.stop() + } - ApplicationContext applicationContext = ApplicationContext.run('elasticsearch.httpHosts': 'http://' + container.getHttpHostAddress()) + + void "Test Elasticsearch connection"() { + given: + ApplicationContext applicationContext = ApplicationContext.run('elasticsearch.httpHosts': 'http://' + container.httpHostAddress) expect: - applicationContext.containsBean(RestHighLevelClient) - applicationContext.getBean(RestHighLevelClient).ping(RequestOptions.DEFAULT) - MainResponse response = applicationContext.getBean(RestHighLevelClient).info(RequestOptions.DEFAULT) - System.out.println(String.format("cluser: %s, node: %s, version: %s", response.getClusterName(), response.getNodeName(), response.getVersion())) + applicationContext.containsBean(ElasticsearchClient) + applicationContext.getBean(ElasticsearchClient).ping() + InfoResponse response = applicationContext.getBean(ElasticsearchClient).info() + System.out.println(String.format("cluser: %s, node: %s, version: %s %s", response.clusterName(), response.name(), response.version().number(), response.version().buildDate())) cleanup: applicationContext.close() - container.stop() } - void "Test Elasticsearch(7.x) Mapping API"() { - + void "Test Elasticsearch(8.x) Mapping API"() { given: - ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:$ELASTICSEARCH_VERSION") - container.start() - ApplicationContext applicationContext = ApplicationContext.run('elasticsearch.httpHosts': 'http://' + container.getHttpHostAddress()) - RestHighLevelClient client = applicationContext.getBean(RestHighLevelClient) + ElasticsearchClient client = applicationContext.getBean(ElasticsearchClient) expect: "Make sure the version of ES is up to date because these tests may cause unexpected results" - client.info(RequestOptions.DEFAULT).getVersion().getNumber().equals(Version.fromString(ELASTICSEARCH_VERSION).toString()) + ELASTICSEARCH_VERSION == client.info().version().number() when: - GetIndexRequest getIndexRequest = new GetIndexRequest("posts") + ExistsRequest existsRequest = new ExistsRequest.Builder().index("posts").build() then: "index does not exists" - !client.indices().exists(getIndexRequest, RequestOptions.DEFAULT) + !client.indices().exists(existsRequest).value() when: "create index request" - IndexRequest request = new IndexRequest( - "posts", - "doc", - "1") - String jsonString = "{" + - "\"user\":\"kimchy\"," + - "\"postDate\":\"2013-01-30\"," + - "\"message\":\"trying out Elasticsearch\"" + - "}" - - request.source(jsonString, XContentType.JSON) - IndexResponse response = client.index(request, RequestOptions.DEFAULT) + IndexRequest.Builder requestBuilder = new IndexRequest.Builder<>() + .index("posts") + .id("1") + + Map document = new HashMap<>() + document.put("user", "kimchy") + document.put("postDate", "2013-01-30") + document.put("message", "trying out Elasticsearch") + requestBuilder.document(document) + + IndexResponse response = client.index(requestBuilder.build()) then: "verify version and result" - response.getIndex() == "posts" - response.getVersion() == 1 - response.getResult() == DocWriteResponse.Result.CREATED + response.index() == "posts" + response.version() == 1 + response.result() == Result.Created when: "update index request" - request = new IndexRequest( - "posts", - "doc", - "1") - jsonString = "{" + - "\"user\":\"kimchy1\"," + - "\"postDate\":\"2018-10-30\"," + - "\"message\":\"Trying out Elasticsearch6\"" + - "}" - - request.source(jsonString, XContentType.JSON) - response = client.index(request, RequestOptions.DEFAULT) + requestBuilder = new IndexRequest.Builder<>() + .index("posts") + .id("1") - then: "verify version and result" - response.getIndex() == "posts" - response.getVersion() == 2 - response.getResult() == DocWriteResponse.Result.UPDATED + document = new HashMap<>() + document.put("user", "kimchy1") + document.put("postDate", "2018-10-30") + document.put("message", "Trying out Elasticsearch6") + requestBuilder.document(document) - when: "get request" - GetRequest getRequest = new GetRequest( - "posts", - "doc", - "1"). - fetchSourceContext(FetchSourceContext.DO_NOT_FETCH_SOURCE) + response = client.index(requestBuilder.build()) - String[] includes = ["message", "*Date"] - String[] excludes = [] - FetchSourceContext fetchSourceContext = - new FetchSourceContext(true, includes, excludes) - getRequest.fetchSourceContext(fetchSourceContext) + then: "verify version and result" + response.index() == "posts" + response.version() == 2 + response.result() == Result.Updated + when: "get request" + GetRequest getRequest = new GetRequest.Builder() + .index("posts") + .id("1") + .sourceIncludes("message", "*Date") + .build() - GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT) + GetResponse getResponse = client.get(getRequest, Map.class) then: "verify source" - getResponse.getType() == "doc" - getResponse.getIndex() == "posts" - getResponse.isExists() - getResponse.getVersion() == 2 - getResponse.getSourceAsMap() == [postDate:"2018-10-30", message:"Trying out Elasticsearch6"] - + getResponse.index() == "posts" + getResponse.version() == 2 + getResponse.source() == [postDate: "2018-10-30", message: "Trying out Elasticsearch6"] when: "exits request" - getRequest = new GetRequest( - "posts", - "doc", - "1") - getRequest.fetchSourceContext(new FetchSourceContext(false)) - getRequest.storedFields("_none_") + co.elastic.clients.elasticsearch.core.ExistsRequest existsRequest2 = new co.elastic.clients.elasticsearch.core.ExistsRequest.Builder() + .index("posts") + .id("1") + .build() then: - client.exists(getRequest, RequestOptions.DEFAULT) - + client.exists(existsRequest2) when: "delete request" - DeleteRequest deleteRequest = new DeleteRequest( - "posts", - "doc", - "1") - - DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT) + DeleteRequest deleteRequest = new DeleteRequest.Builder() + .index("posts") + .id("1") + .build() + DeleteResponse deleteResponse = client.delete(deleteRequest) then: - deleteResponse.getIndex() == "posts" + deleteResponse.index() == "posts" cleanup: applicationContext.close() - container.stop() } - } diff --git a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicatorSpec.groovy b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicatorSpec.groovy similarity index 66% rename from elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicatorSpec.groovy rename to elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicatorSpec.groovy index 0858dda1..d4929611 100644 --- a/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchHealthIndicatorSpec.groovy +++ b/elasticsearch/src/test/groovy/io/micronaut/elasticsearch/health/ElasticsearchClientHealthIndicatorSpec.groovy @@ -22,45 +22,40 @@ import io.micronaut.context.exceptions.NoSuchBeanException import io.micronaut.elasticsearch.DefaultElasticsearchConfigurationProperties import io.micronaut.health.HealthStatus import io.micronaut.management.health.indicator.HealthResult -import org.apache.http.auth.AuthScope -import org.apache.http.auth.UsernamePasswordCredentials -import org.apache.http.client.CredentialsProvider -import org.apache.http.impl.client.BasicCredentialsProvider +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy import org.testcontainers.elasticsearch.ElasticsearchContainer -import org.testcontainers.utility.DockerImageName +import reactor.core.publisher.Flux import spock.lang.Requires import spock.lang.Specification -import reactor.core.publisher.Flux - /** * @author Puneet Behl * @since 1.0.0 */ @Requires({ sys['elasticsearch.version'] }) -class ElasticsearchHealthIndicatorSpec extends Specification { +class ElasticsearchClientHealthIndicatorSpec extends Specification { final static String ELASTICSEARCH_VERSION = System.getProperty("elasticsearch.version") void "test elasticsearch health indicator"() { given: - ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName.parse("elasticsearch:$ELASTICSEARCH_VERSION").asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch")) + ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:$ELASTICSEARCH_VERSION") + .withExposedPorts(9200) + .withEnv("xpack.security.enabled", "false") + .waitingFor(new LogMessageWaitStrategy().withRegEx(".*\"message\":\"started\".*")) container.start() - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider() - credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme")) - - ApplicationContext applicationContext = ApplicationContext.run('elasticsearch.httpHosts': "http://${container.getHttpHostAddress()}") + ApplicationContext applicationContext = ApplicationContext.run('elasticsearch.httpHosts': "http://$container.httpHostAddress") expect: applicationContext.containsBean(DefaultElasticsearchConfigurationProperties) when: - ElasticsearchHealthIndicator indicator = applicationContext.getBean(ElasticsearchHealthIndicator) + ElasticsearchClientHealthIndicator indicator = applicationContext.getBean(ElasticsearchClientHealthIndicator) HealthResult result = Flux.from(indicator.getResult()).blockFirst() then: result.status == HealthStatus.UP - new JsonSlurper().parseText((String) result.details).status == "green" + new JsonSlurper().parseText((String) result.details).status == co.elastic.clients.elasticsearch._types.HealthStatus.Green.name().toLowerCase(Locale.ENGLISH) when: container.stop() @@ -69,20 +64,19 @@ class ElasticsearchHealthIndicatorSpec extends Specification { then: result.status == HealthStatus.DOWN - cleanup: applicationContext?.stop() + container.stop() } - void "test that ElasticsearchHealthIndicator is not created when the endpoints.health.elasticsearch.rest.high.level.enabled is set to false "() { + void "test that ElasticsearchClientHealthIndicator is not created when the endpoints.health.elasticsearch.rest.high.level.enabled is set to false "() { ApplicationContext applicationContext = ApplicationContext.run( 'elasticsearch.httpHosts': "http://localhost:9200", - 'endpoints.health.elasticsearch.rest.high.level.enabled': "false" - + 'endpoints.health.elasticsearch.enabled': "false" ) when: - applicationContext.getBean(ElasticsearchHealthIndicator) + applicationContext.getBean(ElasticsearchClientHealthIndicator) then: thrown(NoSuchBeanException) diff --git a/elasticsearch/src/test/resources/logback.xml b/elasticsearch/src/test/resources/logback.xml new file mode 100644 index 00000000..3d2478ba --- /dev/null +++ b/elasticsearch/src/test/resources/logback.xml @@ -0,0 +1,15 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level [%thread] %logger{25} [%file:%line] - %msg%n + UTF-8 + + + + + + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 59e64c48..f23a93fe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ micronaut = "4.0.0-M1" micronaut-security = "4.0.0-M1" micronaut-test = "4.0.0-M1" -managed-elasticsearch = "7.16.3" +managed-elasticsearch = "8.2.0" graal-svm = "22.3.1" groovy = "4.0.11" @@ -16,7 +16,6 @@ testcontainers = "1.18.0" micronaut-security = { module = "io.micronaut.security:micronaut-security-bom", version.ref = "micronaut-security" } managed-elasticsearch-java = { module = "co.elastic.clients:elasticsearch-java", version.ref = "managed-elasticsearch" } -managed-elasticsearch-rest-high-level-client = { module = "org.elasticsearch.client:elasticsearch-rest-high-level-client", version.ref = "managed-elasticsearch" } graal-svm = { module = "org.graalvm.nativeimage:svm", version.ref = "graal-svm" } diff --git a/src/main/docs/guide/configuration.adoc b/src/main/docs/guide/configuration.adoc index a3c0dde4..e3ae4ba9 100644 --- a/src/main/docs/guide/configuration.adoc +++ b/src/main/docs/guide/configuration.adoc @@ -13,7 +13,7 @@ To configure the https://www.elastic.co/guide/en/elasticsearch/client/java-api-c [source,groovy] .build.gradle ---- -implementation "io.micronaut.elasticsearch:micronaut-elasticsearch" +implementation("io.micronaut.elasticsearch:micronaut-elasticsearch") ---- You should then configure the `httpHosts` of the Elasticsearch server you wish to communicate with in `application.yml` as: @@ -27,7 +27,7 @@ elasticsearch: See the API for api:configuration.elasticsearch.DefaultElasticsearchConfigurationProperties[] for more information on the available configuration options. -Once you have the above configuration in place then you can inject the `co.elastic.clients.elasticsearch.ElasticsearchClient`, the `co.elastic.clients.elasticsearch.ElasticsearchAsyncClient`, the `org.elasticsearch.client.RestClient` or the deprecated `org.elasticsearch.client.RestHighLevelClient` bean. The following is the simplest way to get Elasticsearch information using the ElasticsearchClient: +Once you have the above configuration in place then you can inject the `co.elastic.clients.elasticsearch.ElasticsearchClient`, the `co.elastic.clients.elasticsearch.ElasticsearchAsyncClient` or the `org.elasticsearch.client.RestClient` bean. The following is the simplest way to get Elasticsearch information using the ElasticsearchClient: [source,groovy] ---- diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/configuration/elasticsearch/ElasticsearchSpec.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/configuration/elasticsearch/ElasticsearchSpec.groovy index 5c60dadd..dfe2ffc0 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/configuration/elasticsearch/ElasticsearchSpec.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/configuration/elasticsearch/ElasticsearchSpec.groovy @@ -16,34 +16,28 @@ package io.micronaut.docs.configuration.elasticsearch -import io.micronaut.elasticsearch.DefaultElasticsearchConfigurationProperties +import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient +import co.elastic.clients.elasticsearch.ElasticsearchClient +import co.elastic.clients.elasticsearch.core.InfoResponse import io.micronaut.context.ApplicationContext import io.micronaut.context.annotation.Factory - -//tag::httpClientFactoryImports[] import io.micronaut.context.annotation.Replaces -import org.apache.http.auth.AuthScope +import io.micronaut.elasticsearch.DefaultElasticsearchConfigurationProperties +//tag::httpClientFactoryImports[] +import jakarta.inject.Singleton +import org.apache.http.auth.AuthScope //end::httpClientFactoryImports[] -import co.elastic.clients.elasticsearch.ElasticsearchAsyncClient -import co.elastic.clients.elasticsearch.ElasticsearchClient -import co.elastic.clients.elasticsearch.core.InfoResponse + import org.apache.http.auth.UsernamePasswordCredentials import org.apache.http.client.CredentialsProvider import org.apache.http.impl.client.BasicCredentialsProvider import org.apache.http.impl.nio.client.HttpAsyncClientBuilder -import org.elasticsearch.Version -import org.elasticsearch.client.RequestOptions -import org.elasticsearch.client.RestHighLevelClient -import org.elasticsearch.client.core.MainResponse +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy import org.testcontainers.elasticsearch.ElasticsearchContainer -import org.testcontainers.utility.DockerImageName import spock.lang.Requires import spock.lang.Shared import spock.lang.Specification - -import jakarta.inject.Singleton - //tag::singletonImports[] //end::singletonImports[] /** @@ -57,43 +51,23 @@ class ElasticsearchSpec extends Specification { final static String ELASTICSEARCH_VERSION = System.getProperty("elasticsearch.version") @Shared - ElasticsearchContainer elasticsearch = new ElasticsearchContainer( - DockerImageName.parse("elasticsearch:$ELASTICSEARCH_VERSION") - .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch") - ) + static final ElasticsearchContainer container = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:$ELASTICSEARCH_VERSION") + .withExposedPorts(9200) + .withEnv("xpack.security.enabled", "false") + .waitingFor(new LogMessageWaitStrategy().withRegEx(".*\"message\":\"started\".*")) // end::es-testcontainer[] + void setupSpec() { + container.start() + } - //tag::es-stats[] - void "Test simple info for Elasticsearch stats using the High Level REST Client"() { - given: - //tag::es-conf[] - elasticsearch.start() - ApplicationContext applicationContext = ApplicationContext.run("elasticsearch.httpHosts": "http://${elasticsearch.getHttpHostAddress()}", "test") - //end::es-conf - String stats - - when: - //tag::es-bean[] - RestHighLevelClient client = applicationContext.getBean(RestHighLevelClient) - //end::es-bean[] - MainResponse response = - client.info(RequestOptions.DEFAULT) // <1> - - then: - "docker-cluster" == response.getClusterName() - Version.fromString(ELASTICSEARCH_VERSION).toString() == response.getVersion().getNumber() - - cleanup: - applicationContext.close() - elasticsearch.stop() + void cleanupSpec() { + container.stop() } - //end::es-dbstats[] void "Test simple info for Elasticsearch stats using the ElasticsearchClient"() { given: - elasticsearch.start() - ApplicationContext applicationContext = ApplicationContext.run("elasticsearch.httpHosts": "http://${elasticsearch.getHttpHostAddress()}", "test") + ApplicationContext applicationContext = ApplicationContext.run("elasticsearch.httpHosts": "http://$container.httpHostAddress", "test") String stats when: @@ -105,17 +79,15 @@ class ElasticsearchSpec extends Specification { then: "docker-cluster" == response.clusterName() - Version.fromString(ELASTICSEARCH_VERSION).toString() == response.version().number() + ELASTICSEARCH_VERSION == response.version().number() cleanup: applicationContext.close() - elasticsearch.stop() } void "Test simple info for Elasticsearch stats using the ElasticsearchAsyncClient"() { given: - elasticsearch.start() - ApplicationContext applicationContext = ApplicationContext.run("elasticsearch.httpHosts": "http://${elasticsearch.getHttpHostAddress()}", "test") + ApplicationContext applicationContext = ApplicationContext.run("elasticsearch.httpHosts": "http://$container.httpHostAddress", "test") String stats when: @@ -125,11 +97,10 @@ class ElasticsearchSpec extends Specification { then: "docker-cluster" == response.clusterName() - Version.fromString(ELASTICSEARCH_VERSION).toString() == response.version().number() + ELASTICSEARCH_VERSION == response.version().number() cleanup: applicationContext.close() - elasticsearch.stop() } void "Test overiding HttpAsyncClientBuilder bean"() {