Fix Mongo health indicators when using the strict V1 API

Closes gh-41101
This commit is contained in:
Andy Wilkinson 2024-06-14 17:05:09 +01:00
parent 60f8939e26
commit 31f967723d
8 changed files with 177 additions and 22 deletions

View File

@ -99,6 +99,7 @@ dependencies {
testImplementation("org.springframework:spring-test")
testImplementation("com.squareup.okhttp3:mockwebserver")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:mongodb")
testImplementation("org.testcontainers:neo4j")
testImplementation("org.testcontainers:testcontainers")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2024 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.
@ -43,7 +43,7 @@ public class MongoHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
Document result = this.mongoTemplate.executeCommand("{ isMaster: 1 }");
Document result = this.mongoTemplate.executeCommand("{ hello: 1 }");
builder.up().withDetail("maxWireVersion", result.getInteger("maxWireVersion"));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2024 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.
@ -43,7 +43,7 @@ public class MongoReactiveHealthIndicator extends AbstractReactiveHealthIndicato
@Override
protected Mono<Health> doHealthCheck(Health.Builder builder) {
Mono<Document> buildInfo = this.reactiveMongoTemplate.executeCommand("{ isMaster: 1 }");
Mono<Document> buildInfo = this.reactiveMongoTemplate.executeCommand("{ hello: 1 }");
return buildInfo.map((document) -> up(builder, document));
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2012-2024 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.mongo;
import java.time.Duration;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoClientSettings.Builder;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.actuate.data.mongo.MongoHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.data.mongodb.core.MongoTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link MongoHealthIndicator}.
*
* @author Andy Wilkinson
*/
@Testcontainers(disabledWithoutDocker = true)
class MongoHealthIndicatorIntegrationTests {
@Container
static MongoDBContainer mongo = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(3)
.withStartupTimeout(Duration.ofMinutes(2));
@Test
void standardApi() {
Health health = mongoHealth();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void strictV1Api() {
Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build());
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
private Health mongoHealth() {
return mongoHealth(null);
}
private Health mongoHealth(ServerApi serverApi) {
Builder settingsBuilder = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(mongo.getConnectionString()));
if (serverApi != null) {
settingsBuilder.serverApi(serverApi);
}
MongoClientSettings settings = settingsBuilder.build();
MongoClient mongoClient = MongoClients.create(settings);
MongoHealthIndicator healthIndicator = new MongoHealthIndicator(new MongoTemplate(mongoClient, "db"));
return healthIndicator.getHealth(true);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -42,24 +42,24 @@ class MongoHealthIndicatorTests {
Document commandResult = mock(Document.class);
given(commandResult.getInteger("maxWireVersion")).willReturn(10);
MongoTemplate mongoTemplate = mock(MongoTemplate.class);
given(mongoTemplate.executeCommand("{ isMaster: 1 }")).willReturn(commandResult);
given(mongoTemplate.executeCommand("{ hello: 1 }")).willReturn(commandResult);
MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
assertThat(health.getDetails()).containsEntry("maxWireVersion", 10);
then(commandResult).should().getInteger("maxWireVersion");
then(mongoTemplate).should().executeCommand("{ isMaster: 1 }");
then(mongoTemplate).should().executeCommand("{ hello: 1 }");
}
@Test
void mongoIsDown() {
MongoTemplate mongoTemplate = mock(MongoTemplate.class);
given(mongoTemplate.executeCommand("{ isMaster: 1 }")).willThrow(new MongoException("Connection failed"));
given(mongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed"));
MongoHealthIndicator healthIndicator = new MongoHealthIndicator(mongoTemplate);
Health health = healthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat((String) health.getDetails().get("error")).contains("Connection failed");
then(mongoTemplate).should().executeCommand("{ isMaster: 1 }");
then(mongoTemplate).should().executeCommand("{ hello: 1 }");
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright 2012-2024 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.mongo;
import java.time.Duration;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoClientSettings.Builder;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.actuate.data.mongo.MongoReactiveHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status;
import org.springframework.boot.testsupport.testcontainers.DockerImageNames;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link MongoReactiveHealthIndicator}.
*
* @author Andy Wilkinson
*/
@Testcontainers(disabledWithoutDocker = true)
class MongoReactiveHealthIndicatorIntegrationTests {
@Container
static MongoDBContainer mongo = new MongoDBContainer(DockerImageNames.mongo()).withStartupAttempts(3)
.withStartupTimeout(Duration.ofMinutes(2));
@Test
void standardApi() {
Health health = mongoHealth();
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
@Test
void strictV1Api() {
Health health = mongoHealth(ServerApi.builder().strict(true).version(ServerApiVersion.V1).build());
assertThat(health.getStatus()).isEqualTo(Status.UP);
}
private Health mongoHealth() {
return mongoHealth(null);
}
private Health mongoHealth(ServerApi serverApi) {
Builder settingsBuilder = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(mongo.getConnectionString()));
if (serverApi != null) {
settingsBuilder.serverApi(serverApi);
}
MongoClientSettings settings = settingsBuilder.build();
MongoClient mongoClient = MongoClients.create(settings);
MongoReactiveHealthIndicator healthIndicator = new MongoReactiveHealthIndicator(
new ReactiveMongoTemplate(mongoClient, "db"));
return healthIndicator.getHealth(true).block(Duration.ofSeconds(30));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -45,7 +45,7 @@ class MongoReactiveHealthIndicatorTests {
Document buildInfo = mock(Document.class);
given(buildInfo.getInteger("maxWireVersion")).willReturn(10);
ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class);
given(reactiveMongoTemplate.executeCommand("{ isMaster: 1 }")).willReturn(Mono.just(buildInfo));
given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willReturn(Mono.just(buildInfo));
MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator(
reactiveMongoTemplate);
Mono<Health> health = mongoReactiveHealthIndicator.health();
@ -59,8 +59,7 @@ class MongoReactiveHealthIndicatorTests {
@Test
void testMongoIsDown() {
ReactiveMongoTemplate reactiveMongoTemplate = mock(ReactiveMongoTemplate.class);
given(reactiveMongoTemplate.executeCommand("{ isMaster: 1 }"))
.willThrow(new MongoException("Connection failed"));
given(reactiveMongoTemplate.executeCommand("{ hello: 1 }")).willThrow(new MongoException("Connection failed"));
MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator(
reactiveMongoTemplate);
Mono<Health> health = mongoReactiveHealthIndicator.health();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2024 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.
@ -80,14 +80,6 @@ class SampleSessionMongoApplicationTests {
assertThat(sessions).hasSize(1);
}
@Test
void health() {
ResponseEntity<String> entity = this.restTemplate
.getForEntity("http://localhost:" + this.port + "/actuator/health", String.class);
assertThat(entity.getBody()).contains("\"status\":\"UP\"");
assertThat(entity.getBody()).contains("maxWireVersion");
}
private String performLogin() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));