diff --git a/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheck.java b/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheck.java new file mode 100644 index 0000000..98e8b79 --- /dev/null +++ b/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheck.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) + * + * 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 + * + * http://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 nl.knaw.dans.lib.util.healthcheck; + +/** + * Interface for checking if all dependencies are ready. + */ +public interface DependenciesReadyCheck { + /** + * Wait until all dependencies are ready. This method should block until all dependencies are ready. + * + * @param checks the names of the checks to wait for; if empty, all checks are waited for + */ + void waitUntilReady(String... checks); +} \ No newline at end of file diff --git a/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheckConfig.java b/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheckConfig.java new file mode 100644 index 0000000..4a0ced4 --- /dev/null +++ b/src/main/java/nl/knaw/dans/lib/util/healthcheck/DependenciesReadyCheckConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) + * + * 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 + * + * http://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 nl.knaw.dans.lib.util.healthcheck; + +import io.dropwizard.util.Duration; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DependenciesReadyCheckConfig { + private List healthChecks; + private Duration pollInterval; +} diff --git a/src/main/java/nl/knaw/dans/lib/util/healthcheck/HealthChecksDependenciesReadyCheck.java b/src/main/java/nl/knaw/dans/lib/util/healthcheck/HealthChecksDependenciesReadyCheck.java new file mode 100644 index 0000000..61f2d2e --- /dev/null +++ b/src/main/java/nl/knaw/dans/lib/util/healthcheck/HealthChecksDependenciesReadyCheck.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2021 DANS - Data Archiving and Networked Services (info@dans.knaw.nl) + * + * 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 + * + * http://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 nl.knaw.dans.lib.util.healthcheck; + +import com.codahale.metrics.health.HealthCheck; +import io.dropwizard.core.setup.Environment; +import io.dropwizard.lifecycle.Managed; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static java.lang.Thread.sleep; + +/** + * Implementation of {@link DependenciesReadyCheck} that waits until all health checks are healthy. + */ +@Slf4j +public class HealthChecksDependenciesReadyCheck implements DependenciesReadyCheck, Managed { + private final List healthChecks = new ArrayList<>(); + private final Environment environment; + private final DependenciesReadyCheckConfig config; + + private long pollInterval; + private boolean running = false; + + public HealthChecksDependenciesReadyCheck(Environment environment, DependenciesReadyCheckConfig config) { + this.environment = environment; + this.config = config; + } + + @Override + public void start() throws Exception { + for (var name : config.getHealthChecks()) { + var healthCheck = environment.healthChecks().getHealthCheck(name); + if (healthCheck == null) { + throw new IllegalArgumentException("Health check with name " + name + " not found"); + } + healthChecks.add(healthCheck); + } + pollInterval = config.getPollInterval().toMilliseconds(); + running = true; + } + + @Override + public void stop() { + running = false; + } + + @Override + public void waitUntilReady(String... checks) { + List checksToWaitFor = getChecksToWaitFor(checks); + while (running && !allHealthy(checksToWaitFor)) { + log.warn("Not all health checks are healthy yet, waiting for {} ms", pollInterval); + try { + sleep(pollInterval); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("Interrupted while waiting for health checks to become healthy, stopping wait"); + break; + } + } + } + + private List getChecksToWaitFor(String... checks) { + if (checks.length == 0) { + return healthChecks; + } + var checkNames = Arrays.asList(checks); + var registeredChecks = environment.healthChecks(); + return checkNames.stream() + .map(name -> { + var healthCheck = registeredChecks.getHealthCheck(name); + if (healthCheck == null) { + throw new IllegalArgumentException("Health check with name " + name + " not found"); + } + return healthCheck; + }) + .collect(Collectors.toList()); + } + + private boolean allHealthy(List checksToWaitFor) { + for (var healthCheck : checksToWaitFor) { + if (!healthCheck.execute().isHealthy()) { + return false; + } + } + return true; + } +}