Skip to content

Commit bbc5960

Browse files
authored
Revert "Revert "feat: health checks""
1 parent 6cdd3c6 commit bbc5960

File tree

8 files changed

+184
-10
lines changed

8 files changed

+184
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package de.sldk.mc;
2+
3+
import de.sldk.mc.health.HealthChecks;
4+
import org.eclipse.jetty.http.HttpStatus;
5+
import org.eclipse.jetty.server.Handler;
6+
import org.eclipse.jetty.server.Request;
7+
import org.eclipse.jetty.server.Response;
8+
import org.eclipse.jetty.util.Callback;
9+
10+
public class HealthController extends Handler.Abstract {
11+
12+
private final HealthChecks checks;
13+
14+
private HealthController(final HealthChecks checks) {
15+
this.checks = checks;
16+
}
17+
18+
public static Handler create(final HealthChecks checks) {
19+
return new HealthController(checks);
20+
}
21+
22+
@Override
23+
public boolean handle(Request request, Response response, Callback callback) throws Exception {
24+
response.setStatus(checks.isHealthy() ? HttpStatus.OK_200 : HttpStatus.SERVICE_UNAVAILABLE_503);
25+
callback.succeeded();
26+
return true;
27+
}
28+
}

src/main/java/de/sldk/mc/MetricsController.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ public class MetricsController extends Handler.Abstract {
1919
private final MetricRegistry metricRegistry = MetricRegistry.getInstance();
2020
private final PrometheusExporter exporter;
2121

22-
public MetricsController(PrometheusExporter exporter) {
22+
private MetricsController(PrometheusExporter exporter) {
2323
this.exporter = exporter;
2424
}
2525

26+
public static Handler create(final PrometheusExporter exporter) {
27+
return new MetricsController(exporter);
28+
}
2629

2730
@Override
2831
public boolean handle(Request request, Response response, Callback callback) throws Exception {

src/main/java/de/sldk/mc/MetricsServer.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package de.sldk.mc;
22

33
import org.eclipse.jetty.http.pathmap.PathSpec;
4+
import de.sldk.mc.health.HealthChecks;
45
import org.eclipse.jetty.server.Server;
56
import org.eclipse.jetty.server.handler.PathMappingsHandler;
67
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
@@ -12,20 +13,23 @@ public class MetricsServer {
1213
private final String host;
1314
private final int port;
1415
private final PrometheusExporter prometheusExporter;
16+
private final HealthChecks healthChecks;
1517

1618
private Server server;
1719

18-
public MetricsServer(String host, int port, PrometheusExporter prometheusExporter) {
20+
public MetricsServer(String host, int port, PrometheusExporter prometheusExporter, HealthChecks healthChecks) {
1921
this.host = host;
2022
this.port = port;
2123
this.prometheusExporter = prometheusExporter;
22-
}
24+
this.healthChecks = healthChecks;
25+
}
2326

2427
public void start() throws Exception {
2528
GzipHandler gzipHandler = new GzipHandler();
2629

2730
var pathMappings = new PathMappingsHandler();
28-
pathMappings.addMapping(PathSpec.from("/metrics"), new MetricsController(prometheusExporter));
31+
pathMappings.addMapping(PathSpec.from("/metrics"), MetricsController.create(prometheusExporter));
32+
pathMappings.addMapping(PathSpec.from("/health"), HealthController.create(healthChecks));
2933

3034
gzipHandler.setHandler(pathMappings);
3135

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package de.sldk.mc.health;
2+
3+
import java.util.Set;
4+
import java.util.concurrent.ConcurrentHashMap;
5+
6+
public final class ConcurrentHealthChecks implements HealthChecks {
7+
8+
private final Set<HealthCheck> checks;
9+
10+
private ConcurrentHealthChecks(final Set<HealthCheck> checks) {
11+
this.checks = checks;
12+
}
13+
14+
public static HealthChecks create() {
15+
return new ConcurrentHealthChecks(ConcurrentHashMap.newKeySet());
16+
}
17+
18+
@Override
19+
public boolean isHealthy() {
20+
for (final HealthCheck check : checks) if (!check.isHealthy()) return false;
21+
return true;
22+
}
23+
24+
@Override
25+
public void add(final HealthCheck check) {
26+
checks.add(check);
27+
}
28+
29+
@Override
30+
public void remove(final HealthCheck check) {
31+
checks.remove(check);
32+
}
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package de.sldk.mc.health;
2+
3+
/**
4+
* Health check.
5+
*/
6+
public interface HealthCheck {
7+
8+
/**
9+
* Checks if the current state is healthy.
10+
*
11+
* @return {@code true} if the state is healthy and {@code false} otherwise
12+
*/
13+
boolean isHealthy();
14+
15+
/**
16+
* Creates a health compound check from the provided ones reporting healthy status if all the checks report it.
17+
*
18+
* @param checks merged health checks
19+
* @return compound health check
20+
*/
21+
static HealthCheck allOf(final HealthCheck... checks) {
22+
return new AllOf(checks);
23+
}
24+
25+
/**
26+
* Creates a compound health check from the provided ones reporting healthy status if any check reports it.
27+
*
28+
* @param checks merged health checks
29+
* @return compound health check
30+
*/
31+
static HealthCheck anyOf(final HealthCheck... checks) {
32+
return new AnyOf(checks);
33+
}
34+
35+
final class AllOf implements HealthCheck {
36+
private final HealthCheck[] checks;
37+
38+
private AllOf(final HealthCheck[] checks) {
39+
this.checks = checks;
40+
}
41+
42+
@Override
43+
public boolean isHealthy() {
44+
for (final HealthCheck check : checks) if (!check.isHealthy()) return false;
45+
46+
return true;
47+
}
48+
}
49+
50+
final class AnyOf implements HealthCheck {
51+
private final HealthCheck[] checks;
52+
53+
private AnyOf(final HealthCheck[] checks) {
54+
this.checks = checks;
55+
}
56+
57+
@Override
58+
public boolean isHealthy() {
59+
for (final HealthCheck check : checks) if (check.isHealthy()) return true;
60+
61+
return false;
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package de.sldk.mc.health;
2+
3+
/**
4+
* Dynamic compound health checks.
5+
*/
6+
public interface HealthChecks extends HealthCheck {
7+
8+
/**
9+
* Adds the provided health check to this one.
10+
*
11+
* @param check added health check
12+
*/
13+
void add(HealthCheck check);
14+
15+
/**
16+
* Removes the provided health check from this one.
17+
*
18+
* @param check removed health check
19+
*/
20+
void remove(HealthCheck check);
21+
}

src/main/kotlin/de/sldk/mc/PrometheusExporter.kt

+11-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
package de.sldk.mc
44

55
import de.sldk.mc.config.PrometheusExporterConfig
6+
import de.sldk.mc.health.ConcurrentHealthChecks
7+
import de.sldk.mc.health.HealthChecks
8+
import org.bukkit.plugin.ServicePriority
69
import org.bukkit.plugin.java.JavaPlugin
710
import java.util.logging.Level
811

12+
913
class PrometheusExporter : JavaPlugin() {
1014
private val config: PrometheusExporterConfig = PrometheusExporterConfig(this)
1115
private var server: MetricsServer? = null
@@ -14,14 +18,18 @@ class PrometheusExporter : JavaPlugin() {
1418
override fun onEnable() {
1519
config.loadDefaultsAndSave()
1620
config.enableConfiguredMetrics()
17-
startMetricsServer()
21+
22+
val healthChecks = ConcurrentHealthChecks.create()
23+
getServer().servicesManager.register(HealthChecks::class.java, healthChecks, this, ServicePriority.Normal)
24+
25+
startMetricsServer(healthChecks)
1826
}
1927

20-
private fun startMetricsServer() {
28+
private fun startMetricsServer(healthChecks: HealthChecks) {
2129
val host = config[PrometheusExporterConfig.HOST]
2230
val port = config[PrometheusExporterConfig.PORT]
2331

24-
server = MetricsServer(host, port, this)
32+
server = MetricsServer(host, port, this, healthChecks)
2533

2634
try {
2735
server?.start()

src/test/java/de/sldk/mc/exporter/PrometheusExporterTest.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package de.sldk.mc.exporter;
22

33

4-
import static org.assertj.core.api.Assertions.assertThat;
5-
64
import de.sldk.mc.MetricsServer;
75
import de.sldk.mc.PrometheusExporter;
6+
import de.sldk.mc.health.ConcurrentHealthChecks;
87
import io.prometheus.client.CollectorRegistry;
98
import io.prometheus.client.Counter;
109
import io.prometheus.client.exporter.common.TextFormat;
@@ -21,6 +20,8 @@
2120
import java.io.IOException;
2221
import java.net.ServerSocket;
2322

23+
import static org.assertj.core.api.Assertions.assertThat;
24+
2425
@ExtendWith(MockitoExtension.class)
2526
public class PrometheusExporterTest {
2627

@@ -34,7 +35,9 @@ public class PrometheusExporterTest {
3435
void setup() throws Exception {
3536
CollectorRegistry.defaultRegistry.clear();
3637
metricsServerPort = getRandomFreePort();
37-
metricsServer = new MetricsServer("localhost", metricsServerPort, exporterMock);
38+
metricsServer = new MetricsServer(
39+
"localhost", metricsServerPort, exporterMock, ConcurrentHealthChecks.create()
40+
);
3841
metricsServer.start();
3942
}
4043

@@ -83,4 +86,14 @@ void metrics_server_should_return_404_on_unknown_paths() {
8386
.statusCode(HttpStatus.NOT_FOUND_404);
8487
}
8588

89+
@Test
90+
void metrics_server_should_return_200_on_health_check() {
91+
String requestPath = URIUtil.newURI("http", "localhost", metricsServerPort, "/health", null);
92+
93+
RestAssured.when()
94+
.get(requestPath)
95+
.then()
96+
.statusCode(HttpStatus.OK_200);
97+
}
98+
8699
}

0 commit comments

Comments
 (0)