diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml index 9783a666d28..394fa29dac8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml @@ -64,6 +64,11 @@ hazelcast-spring true + + com.hazelcast + hazelcast-client + test + com.sun.mail jakarta.mail diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfiguration.java new file mode 100644 index 00000000000..097b7f3bdfa --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfiguration.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2019 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. + * 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 org.springframework.boot.actuate.autoconfigure.hazelcast; + +import java.util.Map; + +import com.hazelcast.core.HazelcastInstance; + +import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthIndicatorConfiguration; +import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for + * {@link HazelcastHealthIndicator}. + * + * @author Dmytro Nosan + * @since 2.2.0 + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(HazelcastInstance.class) +@ConditionalOnBean(HazelcastInstance.class) +@ConditionalOnEnabledHealthIndicator("hazelcast") +@AutoConfigureBefore(HealthIndicatorAutoConfiguration.class) +@AutoConfigureAfter(HazelcastAutoConfiguration.class) +public class HazelcastHealthIndicatorAutoConfiguration + extends CompositeHealthIndicatorConfiguration { + + @Bean + @ConditionalOnMissingBean(name = "hazelcastHealthIndicator") + public HealthIndicator hazelcastHealthIndicator(Map hazelcastInstances) { + return createHealthIndicator(hazelcastInstances); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/package-info.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/package-info.java new file mode 100644 index 00000000000..58d63614e04 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/hazelcast/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2019 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. + * 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. + */ + +/** + * Auto-configuration for Hazelcast's actuator. + */ +package org.springframework.boot.actuate.autoconfigure.hazelcast; diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java new file mode 100644 index 00000000000..8056ceef9b5 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationIntegrationTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2019 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. + * 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 org.springframework.boot.actuate.autoconfigure.hazelcast; + +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.config.Config; +import com.hazelcast.core.Hazelcast; +import com.hazelcast.core.HazelcastInstance; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link HazelcastHealthIndicatorAutoConfiguration}. + * + * @author Dmytro Nosan + */ +class HazelcastHealthIndicatorAutoConfigurationIntegrationTests { + + private final HazelcastInstance hazelcastServer = Hazelcast.newHazelcastInstance(new Config()); + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner().withBean(ClientConfig.class) + .withConfiguration(AutoConfigurations.of(HazelcastHealthIndicatorAutoConfiguration.class, + HazelcastAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + + @AfterEach + void shutdown() { + this.hazelcastServer.shutdown(); + } + + @Test + void hazelcastUp() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(HazelcastInstance.class).hasSingleBean(HazelcastHealthIndicator.class); + HazelcastInstance hazelcast = context.getBean(HazelcastInstance.class); + Health health = context.getBean(HazelcastHealthIndicator.class).health(); + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", hazelcast.getName()) + .containsEntry("uuid", hazelcast.getLocalEndpoint().getUuid()); + }); + } + + @Test + void hazelcastDown() { + this.contextRunner.run((context) -> { + shutdown(); + assertThat(context).hasSingleBean(HazelcastHealthIndicator.class); + Health health = context.getBean(HazelcastHealthIndicator.class).health(); + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + }); + } + +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java new file mode 100644 index 00000000000..a980c6db548 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/hazelcast/HazelcastHealthIndicatorAutoConfigurationTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2019 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. + * 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 org.springframework.boot.actuate.autoconfigure.hazelcast; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration; +import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator; +import org.springframework.boot.actuate.health.ApplicationHealthIndicator; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HazelcastHealthIndicatorAutoConfiguration}. + * + * @author Dmytro Nosan + */ +class HazelcastHealthIndicatorAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(HazelcastAutoConfiguration.class, + HazelcastHealthIndicatorAutoConfiguration.class, HealthIndicatorAutoConfiguration.class)); + + @Test + void runShouldCreateIndicator() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(HazelcastHealthIndicator.class) + .doesNotHaveBean(ApplicationHealthIndicator.class)); + } + + @Test + void runWhenDisabledShouldNotCreateIndicator() { + this.contextRunner.withPropertyValues("management.health.hazelcast.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(HazelcastHealthIndicator.class) + .doesNotHaveBean(HazelcastHealthIndicator.class) + .hasSingleBean(ApplicationHealthIndicator.class)); + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicator.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicator.java new file mode 100644 index 00000000000..42d531b5d92 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2012-2019 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. + * 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 org.springframework.boot.actuate.hazelcast; + +import java.util.LinkedHashMap; +import java.util.Map; + +import com.hazelcast.core.Endpoint; +import com.hazelcast.core.HazelcastInstance; +import com.hazelcast.transaction.TransactionalTask; + +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; +import org.springframework.util.Assert; + +/** + * {@link HealthIndicator} for a Hazelcast. + * + * @author Dmytro Nosan + * @since 2.2.0 + */ +public class HazelcastHealthIndicator extends AbstractHealthIndicator { + + private static final TransactionalTask TASK = (context) -> null; + + private final HazelcastInstance hazelcast; + + public HazelcastHealthIndicator(HazelcastInstance hazelcast) { + super("Hazelcast health check failed"); + Assert.notNull(hazelcast, "HazelcastInstance must not be null"); + this.hazelcast = hazelcast; + } + + @Override + protected void doHealthCheck(Health.Builder builder) { + this.hazelcast.executeTransaction(TASK); + builder.up().withDetails(getDetails()); + } + + private Map getDetails() { + Map details = new LinkedHashMap<>(); + Endpoint endpoint = this.hazelcast.getLocalEndpoint(); + details.put("name", this.hazelcast.getName()); + details.put("uuid", endpoint.getUuid()); + return details; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/package-info.java new file mode 100644 index 00000000000..44e785578dc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/hazelcast/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2019 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. + * 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. + */ + +/** + * Actuator support for Hazelcast. + */ +package org.springframework.boot.actuate.hazelcast; diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java new file mode 100644 index 00000000000..a8dd1d2841a --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/hazelcast/HazelcastHealthIndicatorTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2019 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. + * 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 org.springframework.boot.actuate.hazelcast; + +import com.hazelcast.core.Endpoint; +import com.hazelcast.core.HazelcastException; +import com.hazelcast.core.HazelcastInstance; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.when; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link HazelcastHealthIndicator}. + * + * @author Dmytro Nosan + */ +class HazelcastHealthIndicatorTests { + + private final HazelcastInstance hazelcast = mock(HazelcastInstance.class); + + private final HazelcastHealthIndicator healthIndicator = new HazelcastHealthIndicator(this.hazelcast); + + @Test + void hazelcastUp() { + Endpoint endpoint = mock(Endpoint.class); + when(this.hazelcast.getName()).thenReturn("hz0-instance"); + when(this.hazelcast.getLocalEndpoint()).thenReturn(endpoint); + when(endpoint.getUuid()).thenReturn("7581bb2f-879f-413f-b574-0071d7519eb0"); + + Health health = this.healthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.UP); + assertThat(health.getDetails()).containsOnlyKeys("name", "uuid").containsEntry("name", "hz0-instance") + .containsEntry("uuid", "7581bb2f-879f-413f-b574-0071d7519eb0"); + } + + @Test + void hazelcastDown() { + when(this.hazelcast.executeTransaction(any())).thenThrow(new HazelcastException()); + + Health health = this.healthIndicator.health(); + + assertThat(health.getStatus()).isEqualTo(Status.DOWN); + } + +}