This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit e0cfb68a2a5a4a676e7402035ce21e8e18b033a3 Author: Jeroen Reijn <j.re...@gmail.com> AuthorDate: Tue Oct 16 22:12:59 2018 +0200 JAMES-2563 Implement ElasticSearch healthCheck --- backends-common/elasticsearch/pom.xml | 4 + .../backends/es/ElasticSearchHealthCheck.java | 90 ++++++++++++++++++++++ .../james/backends/es/DockerElasticSearch.java | 15 +++- .../es/ElasticSearchHealthCheckConnectionTest.java | 44 ++++++++--- .../backends/es/ElasticSearchHealthCheckTest.java | 79 +++++++++++++++++++ .../modules/mailbox/ElasticSearchClientModule.java | 23 ++++++ 6 files changed, 244 insertions(+), 11 deletions(-) diff --git a/backends-common/elasticsearch/pom.xml b/backends-common/elasticsearch/pom.xml index ea60bba..911297c 100644 --- a/backends-common/elasticsearch/pom.xml +++ b/backends-common/elasticsearch/pom.xml @@ -30,6 +30,10 @@ <dependencies> <dependency> <groupId>${james.groupId}</groupId> + <artifactId>james-core</artifactId> + </dependency> + <dependency> + <groupId>${james.groupId}</groupId> <artifactId>james-server-util</artifactId> </dependency> <dependency> diff --git a/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java new file mode 100644 index 0000000..1305420 --- /dev/null +++ b/backends-common/elasticsearch/src/main/java/org/apache/james/backends/es/ElasticSearchHealthCheck.java @@ -0,0 +1,90 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 * + * * + * http://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 org.apache.james.backends.es; + +import java.io.IOException; +import java.util.Set; + +import javax.inject.Inject; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.james.core.healthcheck.ComponentName; +import org.apache.james.core.healthcheck.HealthCheck; +import org.apache.james.core.healthcheck.Result; +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.Requests; +import org.elasticsearch.client.RestHighLevelClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.VisibleForTesting; + + +public class ElasticSearchHealthCheck implements HealthCheck { + private static final Logger LOGGER = LoggerFactory.getLogger(ElasticSearchHealthCheck.class); + private static final ComponentName COMPONENT_NAME = new ComponentName("ElasticSearch Backend"); + + private final Set<IndexName> indexNames; + private final RestHighLevelClient client; + + @Inject + ElasticSearchHealthCheck(RestHighLevelClient client, Set<IndexName> indexNames) { + this.client = client; + this.indexNames = indexNames; + } + + @Override + public ComponentName componentName() { + return COMPONENT_NAME; + } + + @Override + public Result check() { + String[] indices = indexNames.stream() + .map(IndexName::getValue) + .toArray(String[]::new); + ClusterHealthRequest request = Requests.clusterHealthRequest(indices); + + try { + ClusterHealthResponse response = client.cluster() + .health(request, RequestOptions.DEFAULT); + + return toHealthCheckResult(response); + } catch (IOException e) { + LOGGER.error("Error while contacting cluster", e); + return Result.unhealthy(COMPONENT_NAME, "Error while contacting cluster. Check James server logs."); + } + } + + @VisibleForTesting + Result toHealthCheckResult(ClusterHealthResponse response) { + switch (response.getStatus()) { + case GREEN: + case YELLOW: + return Result.healthy(COMPONENT_NAME); + case RED: + return Result.unhealthy(COMPONENT_NAME, response.getClusterName() + " status is RED"); + default: + throw new NotImplementedException("Un-handled ElasticSearch cluster status"); + } + } +} diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java index 9398dae..e3365b2 100644 --- a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java +++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/DockerElasticSearch.java @@ -114,12 +114,23 @@ public class DockerElasticSearch { } } + public ElasticSearchConfiguration configuration(Optional<Duration> requestTimeout) { + return ElasticSearchConfiguration.builder() + .addHost(getHttpHost()) + .requestTimeout(requestTimeout) + .build(); + } + public ElasticSearchConfiguration configuration() { - return ElasticSearchConfiguration.builder().addHost(getHttpHost()).build(); + return configuration(Optional.empty()); } public ClientProvider clientProvider() { - return new ClientProvider(configuration()); + return new ClientProvider(configuration(Optional.empty())); + } + + public ClientProvider clientProvider(Duration requestTimeout) { + return new ClientProvider(configuration(Optional.of(requestTimeout))); } private ElasticSearchAPI esAPI() { diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java similarity index 50% copy from server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java copy to backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java index d6a6c11..448390f 100644 --- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java +++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckConnectionTest.java @@ -16,21 +16,47 @@ * specific language governing permissions and limitations * * under the License. * ****************************************************************/ +package org.apache.james.backends.es; -package org.apache.james.modules.mailbox; +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; -import org.apache.james.backends.es.ClientProvider; import org.elasticsearch.client.RestHighLevelClient; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; + +public class ElasticSearchHealthCheckConnectionTest { + private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5); + + @Rule + public DockerElasticSearchRule elasticSearch = new DockerElasticSearchRule(); + private ElasticSearchHealthCheck elasticSearchHealthCheck; -import com.google.inject.AbstractModule; -import com.google.inject.Scopes; + @Before + public void setUp() { + RestHighLevelClient client = elasticSearch.getDockerElasticSearch().clientProvider(REQUEST_TIMEOUT).get(); -public class ElasticSearchClientModule extends AbstractModule { + elasticSearchHealthCheck = new ElasticSearchHealthCheck(client, ImmutableSet.of()); + } - @Override - protected void configure() { - bind(ClientProvider.class).in(Scopes.SINGLETON); - bind(RestHighLevelClient.class).toProvider(ClientProvider.class); + @Test + public void checkShouldSucceedWhenElasticSearchIsRunning() { + assertThat(elasticSearchHealthCheck.check().isHealthy()).isTrue(); } + @Test + public void checkShouldFailWhenElasticSearchIsPaused() { + + elasticSearch.getDockerElasticSearch().pause(); + + try { + assertThat(elasticSearchHealthCheck.check().isUnHealthy()).isTrue(); + } finally { + elasticSearch.getDockerElasticSearch().unpause(); + } + } } diff --git a/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckTest.java b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckTest.java new file mode 100644 index 0000000..feef5da --- /dev/null +++ b/backends-common/elasticsearch/src/test/java/org/apache/james/backends/es/ElasticSearchHealthCheckTest.java @@ -0,0 +1,79 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you 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 * + * * + * http://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 org.apache.james.backends.es; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlocks; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.routing.RoutingTable; +import org.junit.Before; +import org.junit.Test; +import org.testcontainers.shaded.com.google.common.collect.ImmutableSet; + +public class ElasticSearchHealthCheckTest { + private ElasticSearchHealthCheck healthCheck; + + @Before + public void setup() { + healthCheck = new ElasticSearchHealthCheck(null, ImmutableSet.of()); + } + + @Test + public void checkShouldReturnHealthyWhenElasticSearchClusterHealthStatusIsGreen() { + FakeClusterHealthResponse response = new FakeClusterHealthResponse(ClusterHealthStatus.GREEN); + + assertThat(healthCheck.toHealthCheckResult(response).isHealthy()).isTrue(); + } + + @Test + public void checkShouldReturnUnHealthyWhenElasticSearchClusterHealthStatusIsRed() { + FakeClusterHealthResponse response = new FakeClusterHealthResponse(ClusterHealthStatus.RED); + + assertThat(healthCheck.toHealthCheckResult(response).isUnHealthy()).isTrue(); + } + + @Test + public void checkShouldReturnHealthyWhenElasticSearchClusterHealthStatusIsYellow() { + FakeClusterHealthResponse response = new FakeClusterHealthResponse(ClusterHealthStatus.YELLOW); + + assertThat(healthCheck.toHealthCheckResult(response).isHealthy()).isTrue(); + } + + private static class FakeClusterHealthResponse extends ClusterHealthResponse { + private final ClusterHealthStatus status; + + private FakeClusterHealthResponse(ClusterHealthStatus clusterHealthStatus) { + super("fake-cluster", new String[0], + new ClusterState(new ClusterName("fake-cluster"), 0, null, null, RoutingTable.builder().build(), + DiscoveryNodes.builder().build(), + ClusterBlocks.builder().build(), null, false)); + this.status = clusterHealthStatus; + } + + @Override + public ClusterHealthStatus getStatus() { + return this.status; + } + } +} diff --git a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java index d6a6c11..7aab691 100644 --- a/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java +++ b/server/container/guice/cassandra-guice/src/main/java/org/apache/james/modules/mailbox/ElasticSearchClientModule.java @@ -19,11 +19,22 @@ package org.apache.james.modules.mailbox; +import java.util.Set; + import org.apache.james.backends.es.ClientProvider; +import org.apache.james.backends.es.ElasticSearchHealthCheck; +import org.apache.james.backends.es.IndexName; +import org.apache.james.core.healthcheck.HealthCheck; +import org.apache.james.mailbox.elasticsearch.ElasticSearchMailboxConfiguration; +import org.apache.james.quota.search.elasticsearch.ElasticSearchQuotaConfiguration; import org.elasticsearch.client.RestHighLevelClient; +import com.google.common.collect.ImmutableSet; import com.google.inject.AbstractModule; +import com.google.inject.Provides; import com.google.inject.Scopes; +import com.google.inject.Singleton; +import com.google.inject.multibindings.Multibinder; public class ElasticSearchClientModule extends AbstractModule { @@ -31,6 +42,18 @@ public class ElasticSearchClientModule extends AbstractModule { protected void configure() { bind(ClientProvider.class).in(Scopes.SINGLETON); bind(RestHighLevelClient.class).toProvider(ClientProvider.class); + + Multibinder.newSetBinder(binder(), HealthCheck.class) + .addBinding() + .to(ElasticSearchHealthCheck.class); } + @Provides + @Singleton + Set<IndexName> provideIndexNames(ElasticSearchMailboxConfiguration mailboxConfiguration, + ElasticSearchQuotaConfiguration quotaConfiguration) { + return ImmutableSet.of( + mailboxConfiguration.getIndexMailboxName(), + quotaConfiguration.getIndexQuotaRatioName()); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org