Skip to content

Commit 6a4f7c1

Browse files
authored
Health check fixed (helidon-io#1809)
* Health check fixed for native image. * Also fixed for hotspot (used Set instead of List) Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent 56697d1 commit 6a4f7c1

11 files changed

Lines changed: 103 additions & 29 deletions

File tree

health/health-checks/src/main/java/io/helidon/health/checks/DeadlockHealthCheck.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import javax.enterprise.context.ApplicationScoped;
2424
import javax.inject.Inject;
2525

26+
import io.helidon.common.NativeImageHelper;
2627
import io.helidon.health.common.BuiltInHealthCheck;
2728

2829
import org.eclipse.microprofile.health.HealthCheck;
@@ -40,16 +41,20 @@
4041
@BuiltInHealthCheck
4142
public final class DeadlockHealthCheck implements HealthCheck {
4243
private static final Logger LOGGER = Logger.getLogger(DeadlockHealthCheck.class.getName());
44+
private static final String NAME = "deadlock";
4345

4446
/**
4547
* Used for detecting deadlocks. Injected in the constructor.
4648
*/
4749
private final ThreadMXBean threadBean;
50+
private final boolean disabled;
4851

4952
@Inject
5053
// this will be ignored if not within CDI
5154
DeadlockHealthCheck(ThreadMXBean threadBean) {
5255
this.threadBean = threadBean;
56+
// in Graal native image, we cannot use this check, as it would always fail
57+
this.disabled = NativeImageHelper.isNativeImage();
5358
}
5459

5560
/**
@@ -65,6 +70,16 @@ public static DeadlockHealthCheck create(ThreadMXBean threadBean) {
6570

6671
@Override
6772
public HealthCheckResponse call() {
73+
if (disabled) {
74+
LOGGER.log(Level.FINEST, "Running in graal native image, this health-check always returns up.");
75+
return HealthCheckResponse.builder()
76+
.name(NAME)
77+
.withData("enabled", "false")
78+
.withData("description", "in native image")
79+
.up()
80+
.build();
81+
}
82+
6883
boolean noDeadLock = false;
6984
try {
7085
// Thanks to https://stackoverflow.com/questions/1102359/programmatic-deadlock-detection-in-java#1102410
@@ -73,7 +88,7 @@ public HealthCheckResponse call() {
7388
// ThreadBean does not work - probably in native image
7489
LOGGER.log(Level.FINEST, "Failed to find deadlocks in ThreadMXBean, ignoring this healthcheck", e);
7590
}
76-
return HealthCheckResponse.named("deadlock")
91+
return HealthCheckResponse.named(NAME)
7792
.state(noDeadLock)
7893
.build();
7994
}

health/health-checks/src/main/java/io/helidon/health/checks/HealthChecks.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.lang.management.ManagementFactory;
1919

2020
import io.helidon.common.HelidonFeatures;
21+
import io.helidon.common.NativeImageHelper;
2122

2223
import org.eclipse.microprofile.health.HealthCheck;
2324

@@ -27,8 +28,6 @@
2728
* @see #healthChecks()
2829
*/
2930
public final class HealthChecks {
30-
private static final boolean IS_GRAAL_VM = Boolean.getBoolean("com.oracle.graalvm.isaot");
31-
3231
static {
3332
HelidonFeatures.register("Health", "Built-ins");
3433
}
@@ -75,7 +74,7 @@ public static HeapMemoryHealthCheck heapMemoryCheck() {
7574
* @see io.helidon.health.HealthSupport.Builder#addLiveness(org.eclipse.microprofile.health.HealthCheck...)
7675
*/
7776
public static HealthCheck[] healthChecks() {
78-
if (IS_GRAAL_VM) {
77+
if (NativeImageHelper.isNativeImage()) {
7978
return new HealthCheck[] {
8079
//diskSpaceCheck(), // - bug
8180
heapMemoryCheck()

health/health/src/main/java/io/helidon/health/HealthSupport.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Collections;
2121
import java.util.Comparator;
2222
import java.util.HashSet;
23-
import java.util.LinkedHashSet;
2423
import java.util.LinkedList;
2524
import java.util.List;
2625
import java.util.Map;
@@ -260,9 +259,9 @@ public static HealthSupport create(Config config) {
260259
* Fluent API builder for {@link io.helidon.health.HealthSupport}.
261260
*/
262261
public static final class Builder implements io.helidon.common.Builder<HealthSupport> {
263-
private final Set<HealthCheck> allChecks = new LinkedHashSet<>();
264-
private final Set<HealthCheck> livenessChecks = new LinkedHashSet<>();
265-
private final Set<HealthCheck> readinessChecks = new LinkedHashSet<>();
262+
private final List<HealthCheck> allChecks = new LinkedList<>();
263+
private final List<HealthCheck> livenessChecks = new LinkedList<>();
264+
private final List<HealthCheck> readinessChecks = new LinkedList<>();
266265

267266
private final Set<Class<?>> excludedClasses = new HashSet<>();
268267
private final Set<String> includedHealthChecks = new HashSet<>();

microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCdiExtension.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
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.
@@ -35,6 +35,7 @@
3535
import io.helidon.common.HelidonFlavor;
3636
import io.helidon.common.serviceloader.HelidonServiceLoader;
3737
import io.helidon.config.Config;
38+
import io.helidon.config.mp.MpConfig;
3839
import io.helidon.health.HealthSupport;
3940
import io.helidon.health.common.BuiltInHealthCheck;
4041
import io.helidon.microprofile.server.RoutingBuilders;
@@ -89,22 +90,22 @@ void registerProducers(@Observes BeforeBeanDiscovery bbd) {
8990
}
9091

9192
void registerHealth(@Observes @Initialized(ApplicationScoped.class) Object adv, BeanManager bm) {
92-
Config config = ((Config) ConfigProvider.getConfig()).get("health");
93+
org.eclipse.microprofile.config.Config config = ConfigProvider.getConfig();
94+
Config helidonConfig = MpConfig.toHelidonConfig(config).get("health");
9395

94-
if (!config.get("enabled").asBoolean().orElse(true)) {
96+
if (!config.getOptionalValue("health.enabled", Boolean.class).orElse(true)) {
9597
LOGGER.finest("Health support is disabled in configuration");
9698
return;
9799
}
98100

99101
HealthSupport.Builder builder = HealthSupport.builder()
100-
.config(config);
102+
.config(helidonConfig);
101103

102104
CDI<Object> cdi = CDI.current();
103105

104106
// Collect built-in checks if disabled, otherwise set list to empty for filtering
105-
Optional<Boolean> disableDefaults = config.get("mp.health.disable-default-procedures")
106-
.asBoolean()
107-
.asOptional();
107+
Optional<Boolean> disableDefaults = config.getOptionalValue("mp.health.disable-default-procedures",
108+
Boolean.class);
108109

109110
List<HealthCheck> builtInHealthChecks = disableDefaults.map(
110111
b -> b ? cdi.select(HealthCheck.class, BUILT_IN_HEALTH_CHECK_LITERAL)
@@ -134,7 +135,7 @@ void registerHealth(@Observes @Initialized(ApplicationScoped.class) Object adv,
134135
healthCheckProvider.readinessChecks().forEach(builder::addReadiness);
135136
});
136137

137-
RoutingBuilders.create(config)
138+
RoutingBuilders.create(helidonConfig)
138139
.routingBuilder()
139140
.register(builder.build());
140141
}

microprofile/health/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
requires java.json;
3434
requires microprofile.config.api;
3535
requires microprofile.health.api;
36+
requires io.helidon.config.mp;
3637

3738
exports io.helidon.microprofile.health;
3839

tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/BeanClass.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ public class BeanClass {
2727
@Produces
2828
@RequestScoped
2929
public BeanType produceBeanType(@ConfigProperty(name = "app.message") String message) {
30-
System.out.println("Producing in request scoped.");
3130
return new BeanType(message);
3231
}
3332

tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/LivenessHealthcheck.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
/*
2-
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
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+
* http://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.
315
*/
416
package io.helidon.tests.integration.nativeimage.mp1;
517

@@ -23,7 +35,7 @@ public class LivenessHealthcheck implements HealthCheck {
2335

2436
@Override
2537
public HealthCheckResponse call() {
26-
return HealthCheckResponse.named("message")
38+
return HealthCheckResponse.named("mp1-live")
2739
.state(true)
2840
.withData("app.message", message)
2941
.build();

tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/Mp1Main.java

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import java.time.Instant;
2424
import java.util.ArrayList;
2525
import java.util.Base64;
26+
import java.util.HashMap;
2627
import java.util.LinkedList;
2728
import java.util.List;
29+
import java.util.Map;
2830
import java.util.Set;
2931
import java.util.concurrent.ExecutionException;
3032
import java.util.concurrent.atomic.AtomicInteger;
@@ -115,11 +117,11 @@ public static void main(final String[] args) {
115117
.forEach(names::add);
116118
names.sort(String::compareTo);
117119

118-
System.out.println("All configuration options:");
119-
names.forEach(it -> {
120-
config.getOptionalValue(it, String.class)
121-
.ifPresent(value -> System.out.println(it + "=" + value));
122-
});
120+
// System.out.println("All configuration options:");
121+
// names.forEach(it -> {
122+
// config.getOptionalValue(it, String.class)
123+
// .ifPresent(value -> System.out.println(it + "=" + value));
124+
// });
123125

124126
server.stop();
125127

@@ -427,8 +429,13 @@ private static void validateHealth(Errors.Collector collector, WebTarget target)
427429
.get(JsonObject.class);
428430

429431
JsonArray checks = health.getJsonArray("checks");
430-
if (checks.size() < 1) {
431-
collector.fatal("There should be at least one readiness healtcheck provided by this app");
432+
if (checks.size() != 1) {
433+
collector.fatal("There should be a readiness health check provided by this app");
434+
} else {
435+
JsonObject check = checks.getJsonObject(0);
436+
if (!"mp1-ready".equals(check.getString("name"))) {
437+
collector.fatal("The readiness health check should be named \"mp1-ready\", but is " + check.getString("name"));
438+
}
432439
}
433440

434441
try {
@@ -439,13 +446,48 @@ private static void validateHealth(Errors.Collector collector, WebTarget target)
439446
checks = health.getJsonArray("checks");
440447
if (checks.size() < 1) {
441448
collector.fatal("There should be at least one liveness healtcheck provided by this app");
449+
} else {
450+
Map<String, JsonObject> checkMap = new HashMap<>();
451+
checks.forEach(it -> {
452+
JsonObject healthCheck = (JsonObject) it;
453+
checkMap.put(healthCheck.getString("name"), healthCheck);
454+
});
455+
// now the check map contains all healthchecks we have
456+
// validate our own
457+
JsonObject healthCheck = healthExistsAndUp(collector, checkMap, "mp1-live");
458+
if (null != healthCheck) {
459+
String message = healthCheck.getJsonObject("data").getString("app.message");
460+
if (!"Properties message".equals(message)) {
461+
collector.fatal("Message health check should return injected app.message from properties, but got "
462+
+ message);
463+
}
464+
}
465+
466+
healthExistsAndUp(collector, checkMap, "deadlock");
467+
healthExistsAndUp(collector, checkMap, "diskSpace");
468+
healthExistsAndUp(collector, checkMap, "heapMemory");
442469
}
443470
} catch (ServiceUnavailableException e) {
444471
collector.fatal(e, "Failed to invoke health endpoint. Exception: " + e.getClass().getName()
445472
+ ", message: " + e.getMessage() + ", " + e.getResponse().readEntity(String.class));
446473
}
447474
}
448475

476+
private static JsonObject healthExistsAndUp(Errors.Collector collector,
477+
Map<String, JsonObject> checkMap,
478+
String name) {
479+
JsonObject healthCheck = checkMap.get(name);
480+
if (null == healthCheck) {
481+
collector.fatal("\"" + name + "\" health check is not available");
482+
} else {
483+
String status = healthCheck.getString("state");
484+
if (!"UP".equals(status)) {
485+
collector.fatal("Health check \"" + name + "\" should be up, but is " + status);
486+
}
487+
}
488+
return healthCheck;
489+
}
490+
449491
private static void validateMetrics(Errors.Collector collector, WebTarget target) {
450492
JsonObject vendor = target.path("/metrics/vendor")
451493
.request(MediaType.APPLICATION_JSON)

tests/integration/native-image/mp-1/src/main/java/io/helidon/tests/integration/nativeimage/mp1/ReadinessHealthcheck.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class ReadinessHealthcheck implements HealthCheck {
3535

3636
@Override
3737
public HealthCheckResponse call() {
38-
return HealthCheckResponse.named("message")
38+
return HealthCheckResponse.named("mp1-ready")
3939
.state(true)
4040
.withData("app.message", message)
4141
.build();

tests/integration/native-image/mp-1/src/main/java/module-info.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
requires microprofile.metrics.api;
3333
requires java.json.bind;
3434
requires microprofile.config.api;
35+
// this is required, as otherwise the beans from this module
36+
// never reach health check CDI extension
37+
requires io.helidon.health.checks;
3538

3639
exports io.helidon.tests.integration.nativeimage.mp1;
3740

0 commit comments

Comments
 (0)