Polish "Update Neo4j health check to use the Neo4j Driver"
See gh-22302
This commit is contained in:
@@ -86,6 +86,7 @@ dependencies {
|
||||
optional("org.liquibase:liquibase-core")
|
||||
optional("org.mongodb:mongodb-driver-reactivestreams")
|
||||
optional("org.mongodb:mongodb-driver-sync")
|
||||
optional("org.neo4j.driver:neo4j-java-driver")
|
||||
optional("org.springframework:spring-jdbc")
|
||||
optional("org.springframework:spring-jms")
|
||||
optional("org.springframework:spring-messaging")
|
||||
@@ -96,7 +97,6 @@ dependencies {
|
||||
optional("org.springframework.data:spring-data-couchbase")
|
||||
optional("org.springframework.data:spring-data-ldap")
|
||||
optional("org.springframework.data:spring-data-mongodb")
|
||||
optional("org.springframework.data:spring-data-neo4j")
|
||||
optional("org.springframework.data:spring-data-redis")
|
||||
optional("org.springframework.data:spring-data-elasticsearch")
|
||||
optional("org.springframework.data:spring-data-solr")
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
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.neo4j.Neo4jDriverAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
|
||||
* {@link Driver drivers}.
|
||||
* <p>
|
||||
* The reason we are doing this dance with the manual binding is the fact that this
|
||||
* autoconfiguration should work with more than one instance of the driver. If a user has
|
||||
* multiple instances configured, than each instance should be bound via the binder to
|
||||
* registry. Without that requirement, we could just add a {@link Bean @Bean} of type
|
||||
* {@link Neo4jDriverMetrics} to the context and be done.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @since 2.4.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureAfter({ MetricsAutoConfiguration.class, Neo4jDriverAutoConfiguration.class,
|
||||
SimpleMetricsExportAutoConfiguration.class })
|
||||
@ConditionalOnClass({ Driver.class, MeterRegistry.class })
|
||||
@ConditionalOnBean({ Driver.class, MeterRegistry.class })
|
||||
public class Neo4jDriverMetricsAutoConfiguration {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(Neo4jDriverMetricsAutoConfiguration.class);
|
||||
|
||||
@Autowired
|
||||
public void bindDataSourcesToRegistry(Map<String, Driver> drivers, MeterRegistry registry) {
|
||||
|
||||
drivers.forEach((name, driver) -> {
|
||||
if (!Neo4jDriverMetrics.metricsAreEnabled(driver)) {
|
||||
return;
|
||||
}
|
||||
driver.verifyConnectivityAsync()
|
||||
.thenRunAsync(() -> new Neo4jDriverMetrics(name, driver, Collections.emptyList()).bindTo(registry))
|
||||
.exceptionally(e -> {
|
||||
logger.warn("Could not verify connection for " + driver + " and thus not bind to metrics: "
|
||||
+ e.getMessage());
|
||||
return null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,36 +16,24 @@
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.neo4j;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
|
||||
import org.neo4j.driver.Driver;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthContributorConfigurations.Neo4jConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jHealthContributorConfigurations.Neo4jReactiveConfiguration;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
|
||||
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.data.neo4j.Neo4jDataAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jDriverAutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link Neo4jHealthIndicator}.
|
||||
* The auto-configuration here is responsible for both imperative and reactive health
|
||||
* checks. The reactive health check has precedence over the imperative one.
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for
|
||||
* {@link Neo4jReactiveHealthIndicator} and {@link Neo4jHealthIndicator}.
|
||||
*
|
||||
* @author Eric Spiegelberg
|
||||
* @author Stephane Nicoll
|
||||
@@ -53,40 +41,11 @@ import org.neo4j.driver.Driver;
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Driver.class, Health.class })
|
||||
@ConditionalOnClass(Driver.class)
|
||||
@ConditionalOnBean(Driver.class)
|
||||
@ConditionalOnEnabledHealthIndicator("neo4j")
|
||||
@AutoConfigureBefore(HealthContributorAutoConfiguration.class)
|
||||
@AutoConfigureAfter({ Neo4jDriverAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
|
||||
@AutoConfigureAfter(Neo4jAutoConfiguration.class)
|
||||
@Import({ Neo4jReactiveConfiguration.class, Neo4jConfiguration.class })
|
||||
public class Neo4jHealthContributorAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Order(-20)
|
||||
static class Neo4jHealthIndicatorConfiguration
|
||||
extends CompositeHealthContributorConfiguration<Neo4jHealthIndicator, Driver> {
|
||||
|
||||
@Bean
|
||||
// If Neo4jReactiveHealthIndicatorConfiguration kicked in, don't add the
|
||||
// imperative version as well
|
||||
@ConditionalOnMissingBean(name = "neo4jHealthContributor")
|
||||
public HealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
|
||||
return createContributor(drivers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Flux.class })
|
||||
@Order(-30)
|
||||
static class Neo4jReactiveHealthIndicatorConfiguration
|
||||
extends CompositeReactiveHealthContributorConfiguration<Neo4jReactiveHealthIndicator, Driver> {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "neo4jHealthContributor")
|
||||
public ReactiveHealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
|
||||
return createComposite(drivers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
|
||||
import org.springframework.boot.actuate.health.HealthContributor;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Health contributor options for Neo4j.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class Neo4jHealthContributorConfigurations {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class Neo4jConfiguration extends CompositeHealthContributorConfiguration<Neo4jHealthIndicator, Driver> {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
|
||||
HealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
|
||||
return createContributor(drivers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Flux.class)
|
||||
static class Neo4jReactiveConfiguration
|
||||
extends CompositeReactiveHealthContributorConfiguration<Neo4jReactiveHealthIndicator, Driver> {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
|
||||
ReactiveHealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
|
||||
return createContributor(drivers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoCo
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.test.util.ApplicationContextTestUtils;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
@@ -69,7 +70,7 @@ class SpringApplicationHierarchyTests {
|
||||
@EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class,
|
||||
ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class,
|
||||
CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
|
||||
Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class,
|
||||
RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class })
|
||||
static class Parent {
|
||||
@@ -80,7 +81,7 @@ class SpringApplicationHierarchyTests {
|
||||
@EnableAutoConfiguration(exclude = { ElasticsearchDataAutoConfiguration.class,
|
||||
ElasticsearchRepositoriesAutoConfiguration.class, CassandraAutoConfiguration.class,
|
||||
CassandraDataAutoConfiguration.class, MongoDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
|
||||
MongoReactiveDataAutoConfiguration.class, Neo4jAutoConfiguration.class, Neo4jDataAutoConfiguration.class,
|
||||
Neo4jRepositoriesAutoConfiguration.class, RedisAutoConfiguration.class,
|
||||
RedisRepositoriesAutoConfiguration.class, FlywayAutoConfiguration.class, MetricsAutoConfiguration.class })
|
||||
static class Child {
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithMetrics;
|
||||
import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
class Neo4jDriverMetricsAutoConfigurationTest {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
|
||||
AutoConfigurations.of(MetricsAutoConfiguration.class, Neo4jDriverMetricsAutoConfiguration.class));
|
||||
|
||||
private final ContextConsumer<AssertableApplicationContext> assertNoInteractionsWithRegistry = ctx -> {
|
||||
|
||||
if (ctx.getBeansOfType(MeterRegistry.class).isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MeterRegistry mockedRegistry = ctx.getBean(MeterRegistry.class);
|
||||
verify(mockedRegistry).config();
|
||||
verifyNoMoreInteractions(mockedRegistry);
|
||||
};
|
||||
|
||||
@Nested
|
||||
class NoMatches {
|
||||
|
||||
@Test
|
||||
void shouldRequireAllNeededClasses() {
|
||||
contextRunner.withUserConfiguration(WithMeterRegistry.class)
|
||||
.withClassLoader(new FilteredClassLoader(Driver.class)).run(assertNoInteractionsWithRegistry);
|
||||
|
||||
contextRunner.withUserConfiguration(WithDriverWithMetrics.class)
|
||||
.withClassLoader(new FilteredClassLoader(MeterRegistry.class))
|
||||
.run(assertNoInteractionsWithRegistry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireAllNeededBeans() {
|
||||
contextRunner.withUserConfiguration(WithDriverWithMetrics.class).run(assertNoInteractionsWithRegistry);
|
||||
|
||||
contextRunner.withUserConfiguration(WithMeterRegistry.class).run(assertNoInteractionsWithRegistry);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireDriverWithMetrics() {
|
||||
contextRunner.withUserConfiguration(WithDriverWithoutMetrics.class, WithMeterRegistry.class)
|
||||
.run(assertNoInteractionsWithRegistry);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Matches {
|
||||
|
||||
@Test
|
||||
void shouldRequireDriverWithMetrics() {
|
||||
contextRunner.withUserConfiguration(WithDriverWithMetrics.class, WithMeterRegistry.class).run(ctx -> {
|
||||
|
||||
// Wait a bit to let the completable future of the test that mocks
|
||||
// connectiviy complete.
|
||||
Thread.sleep(500L);
|
||||
|
||||
MeterRegistry meterRegistry = ctx.getBean(MeterRegistry.class);
|
||||
assertThat(meterRegistry.getMeters()).extracting(m -> m.getId().getName())
|
||||
.filteredOn(s -> s.startsWith(Neo4jDriverMetrics.PREFIX)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WithDriverWithMetrics {
|
||||
|
||||
@Bean
|
||||
Driver driver() {
|
||||
return mockDriverWithMetrics();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WithDriverWithoutMetrics {
|
||||
|
||||
@Bean
|
||||
Driver driver() {
|
||||
return mockDriverWithoutMetrics();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WithMeterRegistry {
|
||||
|
||||
@Bean
|
||||
MeterRegistry meterRegistry() {
|
||||
|
||||
MeterRegistry meterRegistry = spy(SimpleMeterRegistry.class);
|
||||
return meterRegistry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.neo4j.driver.ConnectionPoolMetrics;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Metrics;
|
||||
import org.neo4j.driver.exceptions.ClientException;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Some predefined mocks, only to be used internally for tests.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
final class Neo4jDriverMocks {
|
||||
|
||||
public static Driver mockDriverWithMetrics() {
|
||||
ConnectionPoolMetrics p1 = mock(ConnectionPoolMetrics.class);
|
||||
when(p1.id()).thenReturn("p1");
|
||||
|
||||
Metrics metrics = mock(Metrics.class);
|
||||
when(metrics.connectionPoolMetrics()).thenReturn(Collections.singletonList(p1));
|
||||
|
||||
Driver driver = mock(Driver.class);
|
||||
when(driver.metrics()).thenReturn(metrics);
|
||||
|
||||
when(driver.verifyConnectivityAsync()).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
public static Driver mockDriverWithoutMetrics() {
|
||||
|
||||
Driver driver = mock(Driver.class);
|
||||
when(driver.metrics()).thenThrow(ClientException.class);
|
||||
return driver;
|
||||
}
|
||||
|
||||
private Neo4jDriverMocks() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 "Neo4j,"
|
||||
* Neo4j Sweden AB [https://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
* Copyright 2012-2020 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
|
||||
* 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,
|
||||
@@ -18,14 +15,14 @@
|
||||
*/
|
||||
package org.springframework.boot.actuate.autoconfigure.neo4j;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
|
||||
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.CompositeReactiveHealthContributor;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.PingHealthIndicator;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
|
||||
import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
@@ -33,14 +30,10 @@ import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
|
||||
|
||||
/**
|
||||
* Tests for {@link Neo4jHealthContributorAutoConfiguration}.
|
||||
@@ -55,106 +48,61 @@ class Neo4jHealthContributorAutoConfigurationTests {
|
||||
.withConfiguration(AutoConfigurations.of(HealthContributorAutoConfiguration.class,
|
||||
Neo4jHealthContributorAutoConfiguration.class));
|
||||
|
||||
@Nested
|
||||
class NoMatches {
|
||||
|
||||
@Test
|
||||
void shouldRespectManagementDecisions() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class)
|
||||
.withPropertyValues("management.health.neo4j.enabled=false")
|
||||
.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class)
|
||||
.hasSingleBean(PingHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireHealthClass() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Health.class))
|
||||
.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireDriverClass() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Driver.class))
|
||||
.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireDriverBean() {
|
||||
contextRunner.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireHealthIndicatorClasses() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class)
|
||||
.withClassLoader(new FilteredClassLoader(Health.class, Flux.class))
|
||||
.run(ctx -> assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
|
||||
contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Flux.class))
|
||||
.run(ctx -> assertThat(ctx).hasSingleBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultIndicatorCanBeReplaced() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class, WithCustomIndicator.class).run((context) -> {
|
||||
assertThat(context).hasBean("neo4jHealthIndicator");
|
||||
Health health = context.getBean("neo4jHealthIndicator", HealthIndicator.class).health();
|
||||
assertThat(health.getDetails()).containsOnly(entry("test", true));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Matches {
|
||||
|
||||
@Test
|
||||
void shouldCreateHealthIndicator() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class).withClassLoader(new FilteredClassLoader(Flux.class))
|
||||
.run(ctx -> assertThat(ctx).hasSingleBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void runShouldCreateHealthIndicator() {
|
||||
this.contextRunner.withUserConfiguration(Neo4jConfiguration.class).run((context) -> assertThat(context)
|
||||
.hasSingleBean(Neo4jReactiveHealthIndicator.class).doesNotHaveBean(Neo4jHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reactiveHealthCheckShouldHavePrecedence() {
|
||||
contextRunner.withUserConfiguration(WithDriver.class).run(ctx -> {
|
||||
assertThat(ctx).doesNotHaveBean(Neo4jHealthIndicator.class).hasBean("neo4jHealthContributor");
|
||||
void runWithoutReactorShouldCreateHealthIndicator() {
|
||||
this.contextRunner.withUserConfiguration(Neo4jConfiguration.class)
|
||||
.withClassLoader(new FilteredClassLoader(Flux.class)).run((context) -> assertThat(context)
|
||||
.hasSingleBean(Neo4jHealthIndicator.class).doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
assertThat(ctx).getBean("neo4jHealthContributor").isInstanceOf(CompositeReactiveHealthContributor.class)
|
||||
.satisfies(o -> {
|
||||
CompositeReactiveHealthContributor chc = (CompositeReactiveHealthContributor) o;
|
||||
assertThat(chc.getContributor("driver")).isInstanceOf(Neo4jReactiveHealthIndicator.class);
|
||||
});
|
||||
}
|
||||
@Test
|
||||
void runWhenDisabledShouldNotCreateIndicator() {
|
||||
this.contextRunner.withUserConfiguration(Neo4jConfiguration.class)
|
||||
.withPropertyValues("management.health.neo4j.enabled=false")
|
||||
.run((context) -> assertThat(context).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
);
|
||||
@Test
|
||||
void defaultIndicatorCanBeReplaced() {
|
||||
this.contextRunner.withUserConfiguration(Neo4jConfiguration.class, CustomIndicatorConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasBean("neo4jHealthIndicator");
|
||||
Health health = context.getBean("neo4jHealthIndicator", HealthIndicator.class).health();
|
||||
assertThat(health.getDetails()).containsOnly(entry("test", true));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRequireDriverBean() {
|
||||
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(Neo4jHealthIndicator.class)
|
||||
.doesNotHaveBean(Neo4jReactiveHealthIndicator.class));
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WithDriver {
|
||||
static class Neo4jConfiguration {
|
||||
|
||||
@Bean
|
||||
Driver driver() {
|
||||
return mockDriverWithoutMetrics();
|
||||
return mock(Driver.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class WithCustomIndicator {
|
||||
static class CustomIndicatorConfiguration {
|
||||
|
||||
@Bean
|
||||
HealthIndicator neo4jHealthIndicator() {
|
||||
return new AbstractHealthIndicator() {
|
||||
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
protected void doHealthCheck(Health.Builder builder) {
|
||||
builder.up().withDetail("test", true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ dependencies {
|
||||
optional("org.liquibase:liquibase-core")
|
||||
optional("org.mongodb:mongodb-driver-reactivestreams")
|
||||
optional("org.mongodb:mongodb-driver-sync")
|
||||
optional("org.neo4j.driver:neo4j-java-driver")
|
||||
optional("org.springframework:spring-jdbc")
|
||||
optional("org.springframework:spring-messaging")
|
||||
optional("org.springframework:spring-webflux")
|
||||
@@ -57,7 +58,6 @@ dependencies {
|
||||
optional("org.springframework.data:spring-data-elasticsearch")
|
||||
optional("org.springframework.data:spring-data-ldap")
|
||||
optional("org.springframework.data:spring-data-mongodb")
|
||||
optional("org.springframework.data:spring-data-neo4j")
|
||||
optional("org.springframework.data:spring-data-redis")
|
||||
optional("org.springframework.data:spring-data-rest-webmvc")
|
||||
optional("org.springframework.data:spring-data-solr")
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.micrometer.core.instrument.FunctionCounter;
|
||||
import io.micrometer.core.instrument.Gauge;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import org.neo4j.driver.ConnectionPoolMetrics;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Metrics;
|
||||
import org.neo4j.driver.exceptions.ClientException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This is a {@link MeterBinder} that binds all available Neo4j driver metrics to
|
||||
* Micrometer.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public final class Neo4jDriverMetrics implements MeterBinder {
|
||||
|
||||
/**
|
||||
* Prefixed used for driver metrics.
|
||||
*/
|
||||
public static final String PREFIX = "neo4j.driver.connections";
|
||||
|
||||
private static final String BASE_UNIT_CONNECTIONS = "connections";
|
||||
|
||||
private final Driver driver;
|
||||
|
||||
private final Iterable<Tag> tags;
|
||||
|
||||
public Neo4jDriverMetrics(String name, Driver driver, Iterable<Tag> tags) {
|
||||
|
||||
Assert.notNull(name, "Bean name must not be null");
|
||||
Assert.notNull(driver, "Driver must not be null");
|
||||
Assert.notNull(tags, "Tags must not be null (but may be empty)");
|
||||
this.driver = driver;
|
||||
this.tags = Tags.concat(tags, "name", name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(MeterRegistry meterRegistry) {
|
||||
|
||||
Metrics metrics = this.driver.metrics();
|
||||
metrics.connectionPoolMetrics().forEach(this.getPoolMetricsBinder(meterRegistry));
|
||||
}
|
||||
|
||||
Consumer<ConnectionPoolMetrics> getPoolMetricsBinder(MeterRegistry meterRegistry) {
|
||||
return (poolMetrics) -> {
|
||||
Iterable<Tag> poolTags = Tags.concat(this.tags, "poolId", poolMetrics.id());
|
||||
|
||||
FunctionCounter.builder(PREFIX + ".acquired", poolMetrics, ConnectionPoolMetrics::acquired).tags(poolTags)
|
||||
.baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that have been acquired.")
|
||||
.register(meterRegistry);
|
||||
|
||||
FunctionCounter.builder(PREFIX + ".closed", poolMetrics, ConnectionPoolMetrics::closed).tags(poolTags)
|
||||
.baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections have been closed.")
|
||||
.register(meterRegistry);
|
||||
|
||||
FunctionCounter.builder(PREFIX + ".created", poolMetrics, ConnectionPoolMetrics::created).tags(poolTags)
|
||||
.baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections have ever been created.")
|
||||
.register(meterRegistry);
|
||||
|
||||
FunctionCounter.builder(PREFIX + ".failedToCreate", poolMetrics, ConnectionPoolMetrics::failedToCreate)
|
||||
.tags(poolTags).baseUnit(BASE_UNIT_CONNECTIONS)
|
||||
.description("The amount of connections have been failed to create.").register(meterRegistry);
|
||||
|
||||
Gauge.builder(PREFIX + ".idle", poolMetrics, ConnectionPoolMetrics::idle).tags(poolTags)
|
||||
.baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that are currently idle.")
|
||||
.register(meterRegistry);
|
||||
|
||||
Gauge.builder(PREFIX + ".inUse", poolMetrics, ConnectionPoolMetrics::inUse).tags(poolTags)
|
||||
.baseUnit(BASE_UNIT_CONNECTIONS).description("The amount of connections that are currently in-use.")
|
||||
.register(meterRegistry);
|
||||
|
||||
FunctionCounter
|
||||
.builder(PREFIX + ".timedOutToAcquire", poolMetrics, ConnectionPoolMetrics::timedOutToAcquire)
|
||||
.tags(poolTags).baseUnit(BASE_UNIT_CONNECTIONS)
|
||||
.description(
|
||||
"The amount of failures to acquire a connection from a pool within maximum connection acquisition timeout.")
|
||||
.register(meterRegistry);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to check whether driver metrics are enabled without throwing an
|
||||
* exception.
|
||||
* @param driver the bean to check whether it has metrics enabled or not
|
||||
* @return true, if the given bean exposes metrics
|
||||
*/
|
||||
public static boolean metricsAreEnabled(Driver driver) {
|
||||
|
||||
try {
|
||||
driver.metrics();
|
||||
return true;
|
||||
}
|
||||
catch (ClientException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import org.neo4j.driver.summary.DatabaseInfo;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.summary.ServerInfo;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health.Builder;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Handle health check details for a Neo4j server.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class Neo4jHealthDetailsHandler {
|
||||
|
||||
/**
|
||||
* Add health details for the specified {@link ResultSummary} and {@code edition}.
|
||||
* @param builder the {@link Builder} to use
|
||||
* @param edition the edition of the server
|
||||
* @param resultSummary server information
|
||||
*/
|
||||
void addHealthDetails(Builder builder, String edition, ResultSummary resultSummary) {
|
||||
ServerInfo serverInfo = resultSummary.server();
|
||||
builder.up().withDetail("server", serverInfo.version() + "@" + serverInfo.address()).withDetail("edition",
|
||||
edition);
|
||||
DatabaseInfo databaseInfo = resultSummary.database();
|
||||
if (StringUtils.hasText(databaseInfo.name())) {
|
||||
builder.withDetail("database", databaseInfo.name());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,14 +24,11 @@ import org.neo4j.driver.Result;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
import org.neo4j.driver.exceptions.SessionExpiredException;
|
||||
import org.neo4j.driver.summary.DatabaseInfo;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.summary.ServerInfo;
|
||||
|
||||
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.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link HealthIndicator} that tests the status of a Neo4j by executing a Cypher
|
||||
@@ -51,11 +48,6 @@ public class Neo4jHealthIndicator extends AbstractHealthIndicator {
|
||||
*/
|
||||
static final String CYPHER = "CALL dbms.components() YIELD name, edition WHERE name = 'Neo4j Kernel' RETURN edition";
|
||||
|
||||
/**
|
||||
* Message indicating that the health check failed.
|
||||
*/
|
||||
static final String MESSAGE_HEALTH_CHECK_FAILED = "Neo4j health check failed";
|
||||
|
||||
/**
|
||||
* Message logged before retrying a health check.
|
||||
*/
|
||||
@@ -67,65 +59,41 @@ public class Neo4jHealthIndicator extends AbstractHealthIndicator {
|
||||
static final SessionConfig DEFAULT_SESSION_CONFIG = SessionConfig.builder().withDefaultAccessMode(AccessMode.WRITE)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* The driver for this health indicator instance.
|
||||
*/
|
||||
private final Driver driver;
|
||||
|
||||
private final Neo4jHealthDetailsHandler healthDetailsHandler;
|
||||
|
||||
public Neo4jHealthIndicator(Driver driver) {
|
||||
super(MESSAGE_HEALTH_CHECK_FAILED);
|
||||
super("Neo4j health check failed");
|
||||
this.driver = driver;
|
||||
this.healthDetailsHandler = new Neo4jHealthDetailsHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) {
|
||||
|
||||
try {
|
||||
ResultSummaryWithEdition resultSummaryWithEdition;
|
||||
// Retry one time when the session has been expired
|
||||
try {
|
||||
resultSummaryWithEdition = runHealthCheckQuery();
|
||||
runHealthCheckQuery(builder);
|
||||
}
|
||||
catch (SessionExpiredException sessionExpiredException) {
|
||||
catch (SessionExpiredException ex) {
|
||||
// Retry one time when the session has been expired
|
||||
logger.warn(MESSAGE_SESSION_EXPIRED);
|
||||
resultSummaryWithEdition = runHealthCheckQuery();
|
||||
runHealthCheckQuery(builder);
|
||||
}
|
||||
buildStatusUp(resultSummaryWithEdition, builder);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
builder.down().withException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given {@link ResultSummary} to the {@link Health.Builder builder}
|
||||
* without actually calling {@code build}.
|
||||
* @param resultSummaryWithEdition the result summary returned by the server
|
||||
* @param builder the health builder to be modified
|
||||
* @return the modified health builder
|
||||
*/
|
||||
static Health.Builder buildStatusUp(ResultSummaryWithEdition resultSummaryWithEdition, Health.Builder builder) {
|
||||
ServerInfo serverInfo = resultSummaryWithEdition.resultSummary.server();
|
||||
DatabaseInfo databaseInfo = resultSummaryWithEdition.resultSummary.database();
|
||||
|
||||
builder.up().withDetail("server", serverInfo.version() + "@" + serverInfo.address()).withDetail("edition",
|
||||
resultSummaryWithEdition.edition);
|
||||
|
||||
if (StringUtils.hasText(databaseInfo.name())) {
|
||||
builder.withDetail("database", databaseInfo.name());
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
ResultSummaryWithEdition runHealthCheckQuery() {
|
||||
private void runHealthCheckQuery(Health.Builder builder) {
|
||||
// We use WRITE here to make sure UP is returned for a server that supports
|
||||
// all possible workloads
|
||||
try (Session session = this.driver.session(DEFAULT_SESSION_CONFIG)) {
|
||||
Result result = session.run(CYPHER);
|
||||
String edition = result.single().get("edition").asString();
|
||||
ResultSummary resultSummary = result.consume();
|
||||
return new ResultSummaryWithEdition(resultSummary, edition);
|
||||
this.healthDetailsHandler.addHealthDetails(builder, edition, resultSummary);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@ import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.exceptions.SessionExpiredException;
|
||||
import org.neo4j.driver.reactive.RxResult;
|
||||
import org.neo4j.driver.reactive.RxSession;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
|
||||
@@ -34,19 +36,20 @@ import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
* statement and extracting server and database information.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public final class Neo4jReactiveHealthIndicator extends AbstractReactiveHealthIndicator {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(Neo4jReactiveHealthIndicator.class);
|
||||
|
||||
/**
|
||||
* The driver for this health indicator instance.
|
||||
*/
|
||||
private final Driver driver;
|
||||
|
||||
private final Neo4jHealthDetailsHandler healthDetailsHandler;
|
||||
|
||||
public Neo4jReactiveHealthIndicator(Driver driver) {
|
||||
this.driver = driver;
|
||||
this.healthDetailsHandler = new Neo4jHealthDetailsHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -54,17 +57,19 @@ public final class Neo4jReactiveHealthIndicator extends AbstractReactiveHealthIn
|
||||
return runHealthCheckQuery()
|
||||
.doOnError(SessionExpiredException.class,
|
||||
(e) -> logger.warn(Neo4jHealthIndicator.MESSAGE_SESSION_EXPIRED))
|
||||
.retryWhen(Retry.max(1).filter(SessionExpiredException.class::isInstance))
|
||||
.map((r) -> Neo4jHealthIndicator.buildStatusUp(r, builder).build());
|
||||
.retryWhen(Retry.max(1).filter(SessionExpiredException.class::isInstance)).map((result) -> {
|
||||
this.healthDetailsHandler.addHealthDetails(builder, result.getT1(), result.getT2());
|
||||
return builder.build();
|
||||
});
|
||||
}
|
||||
|
||||
Mono<ResultSummaryWithEdition> runHealthCheckQuery() {
|
||||
Mono<Tuple2<String, ResultSummary>> runHealthCheckQuery() {
|
||||
// We use WRITE here to make sure UP is returned for a server that supports
|
||||
// all possible workloads
|
||||
return Mono.using(() -> this.driver.rxSession(Neo4jHealthIndicator.DEFAULT_SESSION_CONFIG), (session) -> {
|
||||
RxResult result = session.run(Neo4jHealthIndicator.CYPHER);
|
||||
return Mono.from(result.records()).map((record) -> record.get("edition").asString())
|
||||
.zipWhen((edition) -> Mono.from(result.consume()), (e, r) -> new ResultSummaryWithEdition(r, e));
|
||||
.zipWhen((edition) -> Mono.from(result.consume()));
|
||||
}, RxSession::close);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
|
||||
/**
|
||||
* A holder for a {@link ResultSummary result summary} and database edition.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
final class ResultSummaryWithEdition {
|
||||
|
||||
final ResultSummary resultSummary;
|
||||
|
||||
final String edition;
|
||||
|
||||
ResultSummaryWithEdition(ResultSummary resultSummary, String edition) {
|
||||
this.resultSummary = resultSummary;
|
||||
this.edition = edition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics.PREFIX;
|
||||
import static org.springframework.boot.actuate.neo4j.Neo4jDriverMocks.mockDriverWithMetrics;
|
||||
import static org.springframework.boot.actuate.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
class Neo4jDriverMetricsTests {
|
||||
|
||||
@Test
|
||||
void shouldDetectEnabledMetrics() {
|
||||
|
||||
Driver driver = mockDriverWithMetrics();
|
||||
assertThat(Neo4jDriverMetrics.metricsAreEnabled(driver)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDetectDisabledMetrics() {
|
||||
|
||||
Driver driver = mockDriverWithoutMetrics();
|
||||
assertThat(Neo4jDriverMetrics.metricsAreEnabled(driver)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterCorrectMeters() {
|
||||
|
||||
SimpleMeterRegistry registry = new SimpleMeterRegistry();
|
||||
Neo4jDriverMetrics metrics = new Neo4jDriverMetrics("driver", mockDriverWithMetrics(), Collections.emptyList());
|
||||
metrics.bindTo(registry);
|
||||
|
||||
assertThat(registry.get(PREFIX + ".acquired").functionCounter()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".closed").functionCounter()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".created").functionCounter()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".failedToCreate").functionCounter()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".idle").gauge()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".inUse").gauge()).isNotNull();
|
||||
assertThat(registry.get(PREFIX + ".timedOutToAcquire").functionCounter()).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.neo4j.driver.ConnectionPoolMetrics;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Metrics;
|
||||
import org.neo4j.driver.exceptions.ClientException;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Some predefined mocks, only to be used internally for tests.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
final class Neo4jDriverMocks {
|
||||
|
||||
public static Driver mockDriverWithMetrics() {
|
||||
ConnectionPoolMetrics p1 = mock(ConnectionPoolMetrics.class);
|
||||
when(p1.id()).thenReturn("p1");
|
||||
|
||||
Metrics metrics = mock(Metrics.class);
|
||||
when(metrics.connectionPoolMetrics()).thenReturn(Collections.singletonList(p1));
|
||||
|
||||
Driver driver = mock(Driver.class);
|
||||
when(driver.metrics()).thenReturn(metrics);
|
||||
|
||||
when(driver.verifyConnectivityAsync()).thenReturn(CompletableFuture.completedFuture(null));
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
public static Driver mockDriverWithoutMetrics() {
|
||||
|
||||
Driver driver = mock(Driver.class);
|
||||
when(driver.metrics()).thenThrow(ClientException.class);
|
||||
return driver;
|
||||
}
|
||||
|
||||
private Neo4jDriverMocks() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 "Neo4j,"
|
||||
* Neo4j Sweden AB [https://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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.neo4j;
|
||||
|
||||
import org.mockito.Mock;
|
||||
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.summary.DatabaseInfo;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.summary.ServerInfo;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Contains some shared mocks for the health indicator tests.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
abstract class Neo4jHealthIndicatorTestBase {
|
||||
|
||||
@Mock
|
||||
protected Driver driver;
|
||||
|
||||
@Mock
|
||||
protected ResultSummary resultSummary;
|
||||
|
||||
@Mock
|
||||
protected ServerInfo serverInfo;
|
||||
|
||||
@Mock
|
||||
protected DatabaseInfo databaseInfo;
|
||||
|
||||
@Mock
|
||||
protected Record record;
|
||||
|
||||
protected void prepareSharedMocks() {
|
||||
|
||||
when(serverInfo.version()).thenReturn("4711");
|
||||
when(serverInfo.address()).thenReturn("Zu Hause");
|
||||
when(databaseInfo.name()).thenReturn("n/a");
|
||||
when(resultSummary.server()).thenReturn(serverInfo);
|
||||
when(resultSummary.database()).thenReturn(databaseInfo);
|
||||
when(record.get("edition")).thenReturn(Values.value("ultimate collectors edition"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,29 +16,29 @@
|
||||
|
||||
package org.springframework.boot.actuate.neo4j;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.Result;
|
||||
import org.neo4j.driver.Session;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.exceptions.ServiceUnavailableException;
|
||||
import org.neo4j.driver.exceptions.SessionExpiredException;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
|
||||
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.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link Neo4jHealthIndicator}.
|
||||
@@ -47,126 +47,87 @@ import static org.mockito.Mockito.when;
|
||||
* @author Stephane Nicoll
|
||||
* @author Michael Simons
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class Neo4jHealthIndicatorTests extends Neo4jHealthIndicatorTestBase {
|
||||
|
||||
@Mock
|
||||
private Session session;
|
||||
|
||||
@Mock
|
||||
private Result statementResult;
|
||||
|
||||
@Test
|
||||
void shouldWorkWithoutDatabaseName() {
|
||||
when(this.serverInfo.version()).thenReturn("4711");
|
||||
when(this.serverInfo.address()).thenReturn("Zu Hause");
|
||||
when(this.resultSummary.server()).thenReturn(this.serverInfo);
|
||||
when(this.resultSummary.database()).thenReturn(this.databaseInfo);
|
||||
|
||||
when(this.databaseInfo.name()).thenReturn(null);
|
||||
|
||||
when(record.get("edition")).thenReturn(Values.value("some edition"));
|
||||
when(this.statementResult.single()).thenReturn(this.record);
|
||||
when(this.statementResult.consume()).thenReturn(this.resultSummary);
|
||||
when(this.session.run(anyString())).thenReturn(this.statementResult);
|
||||
|
||||
when(this.driver.session(any(SessionConfig.class))).thenReturn(this.session);
|
||||
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(this.driver);
|
||||
Health health = healthIndicator.health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
assertThat(health.getDetails()).doesNotContainKey("database");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "some edition");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWorkWithEmptyDatabaseName() {
|
||||
when(this.serverInfo.version()).thenReturn("4711");
|
||||
when(this.serverInfo.address()).thenReturn("Zu Hause");
|
||||
when(this.resultSummary.server()).thenReturn(this.serverInfo);
|
||||
when(this.resultSummary.database()).thenReturn(this.databaseInfo);
|
||||
|
||||
when(this.databaseInfo.name()).thenReturn("");
|
||||
|
||||
when(record.get("edition")).thenReturn(Values.value("some edition"));
|
||||
when(this.statementResult.single()).thenReturn(this.record);
|
||||
when(this.statementResult.consume()).thenReturn(this.resultSummary);
|
||||
when(this.session.run(anyString())).thenReturn(this.statementResult);
|
||||
|
||||
when(driver.session(any(SessionConfig.class))).thenReturn(this.session);
|
||||
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(this.driver);
|
||||
Health health = healthIndicator.health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
assertThat(health.getDetails()).doesNotContainKey("database");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "some edition");
|
||||
}
|
||||
class Neo4jHealthIndicatorTests {
|
||||
|
||||
@Test
|
||||
void neo4jIsUp() {
|
||||
|
||||
prepareSharedMocks();
|
||||
|
||||
when(this.statementResult.single()).thenReturn(this.record);
|
||||
when(this.statementResult.consume()).thenReturn(this.resultSummary);
|
||||
when(this.session.run(anyString())).thenReturn(this.statementResult);
|
||||
|
||||
when(this.driver.session(any(SessionConfig.class))).thenReturn(this.session);
|
||||
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(this.driver);
|
||||
Health health = healthIndicator.health();
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", "test");
|
||||
Driver driver = mockDriver(resultSummary, "ultimate collectors edition");
|
||||
Health health = new Neo4jHealthIndicator(driver).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
assertThat(health.getDetails()).containsEntry("database", "n/a");
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
assertThat(health.getDetails()).containsEntry("database", "test");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "ultimate collectors edition");
|
||||
|
||||
verify(session).close();
|
||||
verifyNoMoreInteractions(this.driver, this.session, this.statementResult, this.resultSummary, this.serverInfo,
|
||||
this.databaseInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void neo4jSessionIsExpiredOnce() {
|
||||
void neo4jIsUpWithoutDatabaseName() {
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", null);
|
||||
Driver driver = mockDriver(resultSummary, "some edition");
|
||||
Health health = new Neo4jHealthIndicator(driver).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
assertThat(health.getDetails()).doesNotContainKey("database");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "some edition");
|
||||
}
|
||||
|
||||
AtomicInteger cnt = new AtomicInteger(0);
|
||||
@Test
|
||||
void neo4jIsUpWithEmptyDatabaseName() {
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", "");
|
||||
Driver driver = mockDriver(resultSummary, "some edition");
|
||||
Health health = new Neo4jHealthIndicator(driver).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
assertThat(health.getDetails()).doesNotContainKey("database");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "some edition");
|
||||
}
|
||||
|
||||
prepareSharedMocks();
|
||||
when(this.statementResult.single()).thenReturn(this.record);
|
||||
when(this.statementResult.consume()).thenReturn(this.resultSummary);
|
||||
when(this.session.run(anyString())).thenAnswer(invocation -> {
|
||||
if (cnt.compareAndSet(0, 1)) {
|
||||
@Test
|
||||
void neo4jIsUpWithOneSessionExpiredException() {
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", "");
|
||||
Session session = mock(Session.class);
|
||||
Result statementResult = mockStatementResult(resultSummary, "some edition");
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
given(session.run(anyString())).will((invocation) -> {
|
||||
if (count.compareAndSet(0, 1)) {
|
||||
throw new SessionExpiredException("Session expired");
|
||||
}
|
||||
return Neo4jHealthIndicatorTests.this.statementResult;
|
||||
return statementResult;
|
||||
});
|
||||
when(driver.session(any(SessionConfig.class))).thenReturn(this.session);
|
||||
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(this.driver);
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.session(any(SessionConfig.class))).willReturn(session);
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(driver);
|
||||
Health health = healthIndicator.health();
|
||||
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
|
||||
verify(this.session, times(2)).close();
|
||||
verifyNoMoreInteractions(this.driver, this.session, this.statementResult, this.resultSummary, this.serverInfo,
|
||||
this.databaseInfo);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
verify(session, times(2)).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void neo4jSessionIsDown() {
|
||||
|
||||
when(driver.session(any(SessionConfig.class))).thenThrow(ServiceUnavailableException.class);
|
||||
|
||||
Neo4jHealthIndicator healthIndicator = new Neo4jHealthIndicator(driver);
|
||||
Health health = healthIndicator.health();
|
||||
|
||||
void neo4jIsDown() {
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.session(any(SessionConfig.class))).willThrow(ServiceUnavailableException.class);
|
||||
Health health = new Neo4jHealthIndicator(driver).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
|
||||
assertThat(health.getDetails()).containsKeys("error");
|
||||
}
|
||||
|
||||
verifyNoMoreInteractions(this.driver, this.session, this.statementResult, this.resultSummary, this.serverInfo,
|
||||
this.databaseInfo);
|
||||
private Result mockStatementResult(ResultSummary resultSummary, String edition) {
|
||||
Record record = mock(Record.class);
|
||||
given(record.get("edition")).willReturn(Values.value(edition));
|
||||
Result statementResult = mock(Result.class);
|
||||
given(statementResult.single()).willReturn(record);
|
||||
given(statementResult.consume()).willReturn(resultSummary);
|
||||
return statementResult;
|
||||
}
|
||||
|
||||
private Driver mockDriver(ResultSummary resultSummary, String edition) {
|
||||
Result statementResult = mockStatementResult(resultSummary, edition);
|
||||
Session session = mock(Session.class);
|
||||
given(session.run(anyString())).willReturn(statementResult);
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.session(any(SessionConfig.class))).willReturn(session);
|
||||
return driver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,102 +16,101 @@
|
||||
|
||||
package org.springframework.boot.actuate.neo4j;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Record;
|
||||
import org.neo4j.driver.SessionConfig;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.driver.exceptions.ServiceUnavailableException;
|
||||
import org.neo4j.driver.exceptions.SessionExpiredException;
|
||||
import org.neo4j.driver.reactive.RxResult;
|
||||
import org.neo4j.driver.reactive.RxSession;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
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.ArgumentMatchers.anyString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link Neo4jReactiveHealthIndicator}.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class Neo4jReactiveHealthIndicatorTest extends Neo4jHealthIndicatorTestBase {
|
||||
|
||||
@Mock
|
||||
private RxSession session;
|
||||
|
||||
@Mock
|
||||
private RxResult statementResult;
|
||||
class Neo4jReactiveHealthIndicatorTest {
|
||||
|
||||
@Test
|
||||
void neo4jIsUp() {
|
||||
|
||||
prepareSharedMocks();
|
||||
when(statementResult.records()).thenReturn(Mono.just(record));
|
||||
when(statementResult.consume()).thenReturn(Mono.just(resultSummary));
|
||||
when(session.run(anyString())).thenReturn(statementResult);
|
||||
|
||||
when(driver.rxSession(any(SessionConfig.class))).thenReturn(session);
|
||||
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", "test");
|
||||
Driver driver = mockDriver(resultSummary, "ultimate collectors edition");
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith(health -> {
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith((health) -> {
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "ultimate collectors edition");
|
||||
}).verifyComplete();
|
||||
|
||||
verify(session).close();
|
||||
verifyNoMoreInteractions(driver, session, statementResult, resultSummary, serverInfo, databaseInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void neo4jSessionIsExpiredOnce() {
|
||||
|
||||
AtomicInteger cnt = new AtomicInteger(0);
|
||||
|
||||
prepareSharedMocks();
|
||||
when(statementResult.records()).thenReturn(Mono.just(record));
|
||||
when(statementResult.consume()).thenReturn(Mono.just(resultSummary));
|
||||
when(session.run(anyString())).thenAnswer(invocation -> {
|
||||
if (cnt.compareAndSet(0, 1)) {
|
||||
void neo4jIsUpWithOneSessionExpiredException() {
|
||||
ResultSummary resultSummary = ResultSummaryMock.createResultSummary("4711", "My Home", "");
|
||||
RxSession session = mock(RxSession.class);
|
||||
RxResult statementResult = mockStatementResult(resultSummary, "some edition");
|
||||
AtomicInteger count = new AtomicInteger(0);
|
||||
given(session.run(anyString())).will((invocation) -> {
|
||||
if (count.compareAndSet(0, 1)) {
|
||||
throw new SessionExpiredException("Session expired");
|
||||
}
|
||||
return statementResult;
|
||||
});
|
||||
when(driver.rxSession(any(SessionConfig.class))).thenReturn(session);
|
||||
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.rxSession(any(SessionConfig.class))).willReturn(session);
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith(health -> {
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith((health) -> {
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@Zu Hause");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "ultimate collectors edition");
|
||||
assertThat(health.getDetails()).containsEntry("server", "4711@My Home");
|
||||
assertThat(health.getDetails()).containsEntry("edition", "some edition");
|
||||
}).verifyComplete();
|
||||
|
||||
verify(session, times(2)).close();
|
||||
verifyNoMoreInteractions(driver, session, statementResult, resultSummary, serverInfo, databaseInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void neo4jSessionIsDown() {
|
||||
|
||||
when(driver.rxSession(any(SessionConfig.class))).thenThrow(ServiceUnavailableException.class);
|
||||
|
||||
void neo4jIsDown() {
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.rxSession(any(SessionConfig.class))).willThrow(ServiceUnavailableException.class);
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith(health -> {
|
||||
healthIndicator.health().as(StepVerifier::create).consumeNextWith((health) -> {
|
||||
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
|
||||
assertThat(health.getDetails()).containsKeys("error");
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
verifyNoMoreInteractions(driver, session, statementResult, resultSummary, serverInfo, databaseInfo);
|
||||
private RxResult mockStatementResult(ResultSummary resultSummary, String edition) {
|
||||
Record record = mock(Record.class);
|
||||
given(record.get("edition")).willReturn(Values.value(edition));
|
||||
RxResult statementResult = mock(RxResult.class);
|
||||
given(statementResult.records()).willReturn(Mono.just(record));
|
||||
given(statementResult.consume()).willReturn(Mono.just(resultSummary));
|
||||
return statementResult;
|
||||
}
|
||||
|
||||
private Driver mockDriver(ResultSummary resultSummary, String edition) {
|
||||
RxResult statementResult = mockStatementResult(resultSummary, edition);
|
||||
RxSession session = mock(RxSession.class);
|
||||
given(session.run(anyString())).willReturn(statementResult);
|
||||
Driver driver = mock(Driver.class);
|
||||
given(driver.rxSession(any(SessionConfig.class))).willReturn(session);
|
||||
return driver;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.neo4j;
|
||||
|
||||
import org.neo4j.driver.summary.DatabaseInfo;
|
||||
import org.neo4j.driver.summary.ResultSummary;
|
||||
import org.neo4j.driver.summary.ServerInfo;
|
||||
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Test utility to mock {@link ResultSummary}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
final class ResultSummaryMock {
|
||||
|
||||
private ResultSummaryMock() {
|
||||
}
|
||||
|
||||
static ResultSummary createResultSummary(String serverVersion, String serverAddress, String databaseName) {
|
||||
ServerInfo serverInfo = mock(ServerInfo.class);
|
||||
given(serverInfo.version()).willReturn(serverVersion);
|
||||
given(serverInfo.address()).willReturn(serverAddress);
|
||||
DatabaseInfo databaseInfo = mock(DatabaseInfo.class);
|
||||
given(databaseInfo.name()).willReturn(databaseName);
|
||||
ResultSummary resultSummary = mock(ResultSummary.class);
|
||||
given(resultSummary.server()).willReturn(serverInfo);
|
||||
given(resultSummary.database()).willReturn(databaseInfo);
|
||||
return resultSummary;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -856,6 +856,9 @@ The following `ReactiveHealthIndicators` are auto-configured by Spring Boot when
|
||||
| {spring-boot-actuator-module-code}/mongo/MongoReactiveHealthIndicator.java[`MongoReactiveHealthIndicator`]
|
||||
| Checks that a Mongo database is up.
|
||||
|
||||
| {spring-boot-actuator-module-code}/neo4j/Neo4jReactiveHealthIndicator.java[`Neo4jReactiveHealthIndicator`]
|
||||
| Checks that a Neo4j database is up.
|
||||
|
||||
| {spring-boot-actuator-module-code}/redis/RedisReactiveHealthIndicator.java[`RedisReactiveHealthIndicator`]
|
||||
| Checks that a Redis server is up.
|
||||
|===
|
||||
|
||||
Reference in New Issue
Block a user