Skip to content

Commit c5a7815

Browse files
meistermeiersnicoll
authored andcommitted
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-22302
1 parent 2756f59 commit c5a7815

File tree

16 files changed

+1185
-119
lines changed

16 files changed

+1185
-119
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.neo4j;
18+
19+
import io.micrometer.core.instrument.MeterRegistry;
20+
21+
import java.util.Collections;
22+
import java.util.Map;
23+
24+
import org.apache.commons.logging.Log;
25+
import org.apache.commons.logging.LogFactory;
26+
import org.neo4j.driver.Driver;
27+
import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
30+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
32+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
34+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
35+
import org.springframework.boot.autoconfigure.neo4j.Neo4jDriverAutoConfiguration;
36+
import org.springframework.context.annotation.Bean;
37+
import org.springframework.context.annotation.Configuration;
38+
39+
/**
40+
* {@link EnableAutoConfiguration Auto-configuration} for metrics on all available
41+
* {@link Driver drivers}.
42+
* <p>
43+
* The reason we are doing this dance with the manual binding is the fact that this
44+
* autoconfiguration should work with more than one instance of the driver. If a user has
45+
* multiple instances configured, than each instance should be bound via the binder to
46+
* registry. Without that requirement, we could just add a {@link Bean @Bean} of type
47+
* {@link Neo4jDriverMetrics} to the context and be done.
48+
*
49+
* @author Michael J. Simons
50+
* @since 2.4.0
51+
*/
52+
@Configuration(proxyBeanMethods = false)
53+
@AutoConfigureAfter({ MetricsAutoConfiguration.class, Neo4jDriverAutoConfiguration.class,
54+
SimpleMetricsExportAutoConfiguration.class })
55+
@ConditionalOnClass({ Driver.class, MeterRegistry.class })
56+
@ConditionalOnBean({ Driver.class, MeterRegistry.class })
57+
public class Neo4jDriverMetricsAutoConfiguration {
58+
59+
private static final Log logger = LogFactory.getLog(Neo4jDriverMetricsAutoConfiguration.class);
60+
61+
@Autowired
62+
public void bindDataSourcesToRegistry(Map<String, Driver> drivers, MeterRegistry registry) {
63+
64+
drivers.forEach((name, driver) -> {
65+
if (!Neo4jDriverMetrics.metricsAreEnabled(driver)) {
66+
return;
67+
}
68+
driver.verifyConnectivityAsync()
69+
.thenRunAsync(() -> new Neo4jDriverMetrics(name, driver, Collections.emptyList()).bindTo(registry))
70+
.exceptionally(e -> {
71+
logger.warn("Could not verify connection for " + driver + " and thus not bind to metrics: "
72+
+ e.getMessage());
73+
return null;
74+
});
75+
});
76+
}
77+
78+
}
Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,42 +16,77 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.neo4j;
1818

19-
import java.util.Map;
20-
21-
import org.neo4j.ogm.session.SessionFactory;
22-
2319
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
20+
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
2421
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
22+
import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration;
23+
import org.springframework.boot.actuate.health.Health;
2524
import org.springframework.boot.actuate.health.HealthContributor;
25+
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
2626
import org.springframework.boot.actuate.neo4j.Neo4jHealthIndicator;
27+
import org.springframework.boot.actuate.neo4j.Neo4jReactiveHealthIndicator;
2728
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
29+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
2830
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2931
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3234
import org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration;
35+
import org.springframework.boot.autoconfigure.neo4j.Neo4jDriverAutoConfiguration;
3336
import org.springframework.context.annotation.Bean;
3437
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.core.annotation.Order;
39+
import reactor.core.publisher.Flux;
40+
41+
import java.util.Map;
42+
43+
import org.neo4j.driver.Driver;
3544

3645
/**
3746
* {@link EnableAutoConfiguration Auto-configuration} for {@link Neo4jHealthIndicator}.
47+
* The auto-configuration here is responsible for both imperative and reactive health
48+
* checks. The reactive health check has precedence over the imperative one.
3849
*
3950
* @author Eric Spiegelberg
4051
* @author Stephane Nicoll
52+
* @author Michael J. Simons
4153
* @since 2.0.0
4254
*/
4355
@Configuration(proxyBeanMethods = false)
44-
@ConditionalOnClass(SessionFactory.class)
45-
@ConditionalOnBean(SessionFactory.class)
56+
@ConditionalOnClass({ Driver.class, Health.class })
57+
@ConditionalOnBean(Driver.class)
4658
@ConditionalOnEnabledHealthIndicator("neo4j")
47-
@AutoConfigureAfter(Neo4jDataAutoConfiguration.class)
48-
public class Neo4jHealthContributorAutoConfiguration
49-
extends CompositeHealthContributorConfiguration<Neo4jHealthIndicator, SessionFactory> {
50-
51-
@Bean
52-
@ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
53-
public HealthContributor neo4jHealthContributor(Map<String, SessionFactory> sessionFactories) {
54-
return createContributor(sessionFactories);
59+
@AutoConfigureBefore(HealthContributorAutoConfiguration.class)
60+
@AutoConfigureAfter({ Neo4jDriverAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
61+
public class Neo4jHealthContributorAutoConfiguration {
62+
63+
@Configuration(proxyBeanMethods = false)
64+
@Order(-20)
65+
static class Neo4jHealthIndicatorConfiguration
66+
extends CompositeHealthContributorConfiguration<Neo4jHealthIndicator, Driver> {
67+
68+
@Bean
69+
// If Neo4jReactiveHealthIndicatorConfiguration kicked in, don't add the
70+
// imperative version as well
71+
@ConditionalOnMissingBean(name = "neo4jHealthContributor")
72+
public HealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
73+
return createContributor(drivers);
74+
}
75+
76+
}
77+
78+
@Configuration(proxyBeanMethods = false)
79+
@ConditionalOnClass({ Flux.class })
80+
@Order(-30)
81+
static class Neo4jReactiveHealthIndicatorConfiguration
82+
extends CompositeReactiveHealthContributorConfiguration<Neo4jReactiveHealthIndicator, Driver> {
83+
84+
@Bean
85+
@ConditionalOnMissingBean(name = "neo4jHealthContributor")
86+
public ReactiveHealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
87+
return createComposite(drivers);
88+
}
89+
5590
}
5691

5792
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/neo4j/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.neo4j;
18+
19+
import io.micrometer.core.instrument.MeterRegistry;
20+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
21+
import org.junit.jupiter.api.Nested;
22+
import org.junit.jupiter.api.Test;
23+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
24+
import org.springframework.boot.actuate.neo4j.Neo4jDriverMetrics;
25+
import org.springframework.boot.autoconfigure.AutoConfigurations;
26+
import org.springframework.boot.test.context.FilteredClassLoader;
27+
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
28+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
29+
import org.springframework.boot.test.context.runner.ContextConsumer;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
33+
import org.neo4j.driver.Driver;
34+
35+
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.mockito.Mockito.spy;
37+
import static org.mockito.Mockito.verify;
38+
import static org.mockito.Mockito.verifyNoMoreInteractions;
39+
import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithMetrics;
40+
import static org.springframework.boot.actuate.autoconfigure.neo4j.Neo4jDriverMocks.mockDriverWithoutMetrics;
41+
42+
/**
43+
* @author Michael J. Simons
44+
*/
45+
class Neo4jDriverMetricsAutoConfigurationTest {
46+
47+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration(
48+
AutoConfigurations.of(MetricsAutoConfiguration.class, Neo4jDriverMetricsAutoConfiguration.class));
49+
50+
private final ContextConsumer<AssertableApplicationContext> assertNoInteractionsWithRegistry = ctx -> {
51+
52+
if (ctx.getBeansOfType(MeterRegistry.class).isEmpty()) {
53+
return;
54+
}
55+
56+
MeterRegistry mockedRegistry = ctx.getBean(MeterRegistry.class);
57+
verify(mockedRegistry).config();
58+
verifyNoMoreInteractions(mockedRegistry);
59+
};
60+
61+
@Nested
62+
class NoMatches {
63+
64+
@Test
65+
void shouldRequireAllNeededClasses() {
66+
contextRunner.withUserConfiguration(WithMeterRegistry.class)
67+
.withClassLoader(new FilteredClassLoader(Driver.class)).run(assertNoInteractionsWithRegistry);
68+
69+
contextRunner.withUserConfiguration(WithDriverWithMetrics.class)
70+
.withClassLoader(new FilteredClassLoader(MeterRegistry.class))
71+
.run(assertNoInteractionsWithRegistry);
72+
}
73+
74+
@Test
75+
void shouldRequireAllNeededBeans() {
76+
contextRunner.withUserConfiguration(WithDriverWithMetrics.class).run(assertNoInteractionsWithRegistry);
77+
78+
contextRunner.withUserConfiguration(WithMeterRegistry.class).run(assertNoInteractionsWithRegistry);
79+
}
80+
81+
@Test
82+
void shouldRequireDriverWithMetrics() {
83+
contextRunner.withUserConfiguration(WithDriverWithoutMetrics.class, WithMeterRegistry.class)
84+
.run(assertNoInteractionsWithRegistry);
85+
86+
}
87+
88+
}
89+
90+
@Nested
91+
class Matches {
92+
93+
@Test
94+
void shouldRequireDriverWithMetrics() {
95+
contextRunner.withUserConfiguration(WithDriverWithMetrics.class, WithMeterRegistry.class).run(ctx -> {
96+
97+
// Wait a bit to let the completable future of the test that mocks
98+
// connectiviy complete.
99+
Thread.sleep(500L);
100+
101+
MeterRegistry meterRegistry = ctx.getBean(MeterRegistry.class);
102+
assertThat(meterRegistry.getMeters()).extracting(m -> m.getId().getName())
103+
.filteredOn(s -> s.startsWith(Neo4jDriverMetrics.PREFIX)).isNotEmpty();
104+
});
105+
}
106+
107+
}
108+
109+
@Configuration(proxyBeanMethods = false)
110+
static class WithDriverWithMetrics {
111+
112+
@Bean
113+
Driver driver() {
114+
return mockDriverWithMetrics();
115+
}
116+
117+
}
118+
119+
@Configuration(proxyBeanMethods = false)
120+
static class WithDriverWithoutMetrics {
121+
122+
@Bean
123+
Driver driver() {
124+
return mockDriverWithoutMetrics();
125+
}
126+
127+
}
128+
129+
@Configuration(proxyBeanMethods = false)
130+
static class WithMeterRegistry {
131+
132+
@Bean
133+
MeterRegistry meterRegistry() {
134+
135+
MeterRegistry meterRegistry = spy(SimpleMeterRegistry.class);
136+
return meterRegistry;
137+
}
138+
139+
}
140+
141+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.neo4j;
18+
19+
import java.util.Collections;
20+
import java.util.concurrent.CompletableFuture;
21+
22+
import org.neo4j.driver.ConnectionPoolMetrics;
23+
import org.neo4j.driver.Driver;
24+
import org.neo4j.driver.Metrics;
25+
import org.neo4j.driver.exceptions.ClientException;
26+
27+
import static org.mockito.Mockito.mock;
28+
import static org.mockito.Mockito.when;
29+
30+
/**
31+
* Some predefined mocks, only to be used internally for tests.
32+
*
33+
* @author Michael J. Simons
34+
*/
35+
final class Neo4jDriverMocks {
36+
37+
public static Driver mockDriverWithMetrics() {
38+
ConnectionPoolMetrics p1 = mock(ConnectionPoolMetrics.class);
39+
when(p1.id()).thenReturn("p1");
40+
41+
Metrics metrics = mock(Metrics.class);
42+
when(metrics.connectionPoolMetrics()).thenReturn(Collections.singletonList(p1));
43+
44+
Driver driver = mock(Driver.class);
45+
when(driver.metrics()).thenReturn(metrics);
46+
47+
when(driver.verifyConnectivityAsync()).thenReturn(CompletableFuture.completedFuture(null));
48+
49+
return driver;
50+
}
51+
52+
public static Driver mockDriverWithoutMetrics() {
53+
54+
Driver driver = mock(Driver.class);
55+
when(driver.metrics()).thenThrow(ClientException.class);
56+
return driver;
57+
}
58+
59+
private Neo4jDriverMocks() {
60+
}
61+
62+
}

0 commit comments

Comments
 (0)