Update Neo4j health check to use the Neo4j Driver
This commit replaces the Neo4j-OGM based health checks with one based on the Neo4j Java driver. A Reactive variant is also added in this commit. See gh-22302pull/22631/head
parent
2756f5911f
commit
c5a7815e42
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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,71 @@
|
||||
/*
|
||||
* 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.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.exceptions.SessionExpiredException;
|
||||
import org.neo4j.driver.reactive.RxResult;
|
||||
import org.neo4j.driver.reactive.RxSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.ReactiveHealthIndicator;
|
||||
|
||||
/**
|
||||
* {@link ReactiveHealthIndicator} that tests the status of a Neo4j by executing a Cypher
|
||||
* statement and extracting server and database information.
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
* @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;
|
||||
|
||||
public Neo4jReactiveHealthIndicator(Driver driver) {
|
||||
this.driver = driver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Mono<Health> doHealthCheck(Health.Builder builder) {
|
||||
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());
|
||||
}
|
||||
|
||||
Mono<ResultSummaryWithEdition> 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));
|
||||
}, RxSession::close);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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"));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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.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.neo4j.driver.SessionConfig;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class Neo4jReactiveHealthIndicatorTest extends Neo4jHealthIndicatorTestBase {
|
||||
|
||||
@Mock
|
||||
private RxSession session;
|
||||
|
||||
@Mock
|
||||
private RxResult statementResult;
|
||||
|
||||
@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);
|
||||
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
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");
|
||||
}).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)) {
|
||||
throw new SessionExpiredException("Session expired");
|
||||
}
|
||||
return statementResult;
|
||||
});
|
||||
when(driver.rxSession(any(SessionConfig.class))).thenReturn(session);
|
||||
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
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");
|
||||
}).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);
|
||||
|
||||
Neo4jReactiveHealthIndicator healthIndicator = new Neo4jReactiveHealthIndicator(driver);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue