This is an automated email from the ASF dual-hosted git repository.
fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-management.git
The following commit(s) were added to refs/heads/main by this push:
new 2caae136 Add startup checks (#293)
2caae136 is described below
commit 2caae136191c6c82ce1bbd555bbf8730bb7648be
Author: Philippus Baalman <[email protected]>
AuthorDate: Wed Sep 25 11:15:44 2024 +0200
Add startup checks (#293)
---
docs/src/main/paradox/healthchecks.md | 6 +--
.../local/src/main/resources/application.conf | 1 +
.../pekko/management/LocalBootstrapTest.scala | 10 ++++
.../startup-checks.backwards.excludes | 20 +++++++
management/src/main/resources/reference.conf | 9 +++-
.../pekko/management/HealthCheckRoutes.scala | 5 ++
.../pekko/management/HealthCheckSettings.scala | 61 ++++++++++++++++++++++
.../pekko/management/ManagementLogMarker.scala | 6 +++
.../management/internal/HealthChecksImpl.scala | 33 +++++++++++-
.../pekko/management/javadsl/HealthChecks.scala | 29 ++++++++++
.../pekko/management/scaladsl/HealthChecks.scala | 27 ++++++++++
.../apache/pekko/management/HealthCheckTest.java | 53 +++++++++++++++++--
.../pekko/management/HealthCheckRoutesSpec.scala | 4 ++
.../pekko/management/HealthCheckSettingsSpec.scala | 53 ++++++++++++++++++-
.../apache/pekko/management/HealthChecksSpec.scala | 56 +++++++++++++++-----
15 files changed, 350 insertions(+), 23 deletions(-)
diff --git a/docs/src/main/paradox/healthchecks.md
b/docs/src/main/paradox/healthchecks.md
index 64010bdf..c4930f1d 100644
--- a/docs/src/main/paradox/healthchecks.md
+++ b/docs/src/main/paradox/healthchecks.md
@@ -58,14 +58,14 @@ Application specific health checks can be added a `name =
<fully qualified class
Health checks can be hosted via the Pekko management HTTP server. The
`pekko.management.HealthCheckRoutes` is enabled
by default as a Pekko management route provider.
-By default all readiness checks are hosted on `/ready` and liveness checks are
hosted on `/alive`. If all of the checks
+By default all startup checks are hosted on `/startup`, readiness checks are
hosted on `/ready` and liveness checks are hosted on `/alive`. If all of the
checks
for an endpoint succeed a `200` is returned, if any fail or return `false` a
`500` is returned. The paths are
-configurable via `pekko.management.health-checks.readiness-path` and
`pekko.management.health-checks.liveness-path` e.g.
+configurable via `pekko.management.health-checks.startup-path`,
`pekko.management.health-checks.readiness-path` and
`pekko.management.health-checks.liveness-path` e.g.
@@snip
[application.conf](/integration-test/local/src/main/resources/application.conf)
{ #health }
The `org.apache.pekko.management.HealthCheckRoutes` can be disabled with the
following configuration but that also
-means that the configured `readiness-checks` and `liveness-checks` will not be
used.
+means that the configured `startup-checks`, `readiness-checks` and
`liveness-checks` will not be used.
```
pekko.management.http.routes {
diff --git a/integration-test/local/src/main/resources/application.conf
b/integration-test/local/src/main/resources/application.conf
index 5d040acd..fbcba00c 100644
--- a/integration-test/local/src/main/resources/application.conf
+++ b/integration-test/local/src/main/resources/application.conf
@@ -48,6 +48,7 @@ pekko.discovery {
#health
pekko.management.health-checks {
+ startup-path = "health/startup"
readiness-path = "health/ready"
liveness-path = "health/alive"
}
diff --git
a/integration-test/local/src/test/scala/org/apache/pekko/management/LocalBootstrapTest.scala
b/integration-test/local/src/test/scala/org/apache/pekko/management/LocalBootstrapTest.scala
index 08fb3e7a..069d01d7 100644
---
a/integration-test/local/src/test/scala/org/apache/pekko/management/LocalBootstrapTest.scala
+++
b/integration-test/local/src/test/scala/org/apache/pekko/management/LocalBootstrapTest.scala
@@ -95,6 +95,8 @@ class LocalBootstrapTest extends AnyWordSpec with
ScalaFutures with Matchers wit
super.afterAll()
}
+ def startupStatusCode(port: Int)(implicit system: ActorSystem): StatusCode =
+ healthCheckStatus(port, "health/startup")
def readyStatusCode(port: Int)(implicit system: ActorSystem): StatusCode =
healthCheckStatus(port, "health/ready")
def aliveStatusCode(port: Int)(implicit system: ActorSystem): StatusCode =
@@ -112,6 +114,14 @@ class LocalBootstrapTest extends AnyWordSpec with
ScalaFutures with Matchers wit
// for http client
implicit val system: ActorSystem = systems(0)
+ "not started up initially" in {
+ eventually {
+ managementPorts.foreach { port =>
+ startupStatusCode(port) shouldEqual StatusCodes.InternalServerError
+ }
+ }
+ }
+
"not be ready initially" in {
eventually {
managementPorts.foreach { port =>
diff --git
a/management/src/main/mima-filters/1.1.x.backwards.excludes/startup-checks.backwards.excludes
b/management/src/main/mima-filters/1.1.x.backwards.excludes/startup-checks.backwards.excludes
new file mode 100644
index 00000000..9bbf27b4
--- /dev/null
+++
b/management/src/main/mima-filters/1.1.x.backwards.excludes/startup-checks.backwards.excludes
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+# Add startup checks
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.management.scaladsl.HealthChecks.startup")
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.management.scaladsl.HealthChecks.startupResult")
diff --git a/management/src/main/resources/reference.conf
b/management/src/main/resources/reference.conf
index 3b8ea102..c5bd579f 100644
--- a/management/src/main/resources/reference.conf
+++ b/management/src/main/resources/reference.conf
@@ -55,7 +55,7 @@ pekko.management {
# The FQCN is the fully qualified class name of the
`ManagementRoutesProvider`.
#
# By default the `org.apache.pekko.management.HealthCheckRoutes` is
enabled, see `health-checks` section of how
- # configure specific readiness and liveness checks.
+ # configure specific startup, readiness and liveness checks.
#
# Route providers included by a library (from reference.conf) can be
excluded by an application
# by using "" or null as the FQCN of the named entry, for example:
@@ -72,8 +72,10 @@ pekko.management {
route-providers-read-only = true
}
- # Health checks for readiness and liveness
+ # Health checks for startup, readiness and liveness
health-checks {
+ # When exposing health checks via Pekko Management, the path to expose
startup checks on
+ startup-path = "startup"
# When exposing health checks via Pekko Management, the path to expose
readiness checks on
readiness-path = "ready"
# When exposing health checks via Pekko Management, the path to expose
readiness checks on
@@ -90,6 +92,9 @@ pekko.management {
#
# Libraries and frameworks that contribute checks are expected to add
their own checks to their reference.conf.
# Applications can add their own checks to application.conf.
+ startup-checks {
+
+ }
readiness-checks {
}
diff --git
a/management/src/main/scala/org/apache/pekko/management/HealthCheckRoutes.scala
b/management/src/main/scala/org/apache/pekko/management/HealthCheckRoutes.scala
index 79735d59..61ec3a70 100644
---
a/management/src/main/scala/org/apache/pekko/management/HealthCheckRoutes.scala
+++
b/management/src/main/scala/org/apache/pekko/management/HealthCheckRoutes.scala
@@ -49,6 +49,11 @@ private[pekko] class HealthCheckRoutes(system:
ExtendedActorSystem) extends Mana
override def routes(mrps: ManagementRouteProviderSettings): Route = {
concat(
+ path(PathMatchers.separateOnSlashes(settings.startupPath)) {
+ get {
+ onComplete(healthChecks.startupResult())(healthCheckResponse)
+ }
+ },
path(PathMatchers.separateOnSlashes(settings.readinessPath)) {
get {
onComplete(healthChecks.readyResult())(healthCheckResponse)
diff --git
a/management/src/main/scala/org/apache/pekko/management/HealthCheckSettings.scala
b/management/src/main/scala/org/apache/pekko/management/HealthCheckSettings.scala
index d5952c5d..55e06bff 100644
---
a/management/src/main/scala/org/apache/pekko/management/HealthCheckSettings.scala
+++
b/management/src/main/scala/org/apache/pekko/management/HealthCheckSettings.scala
@@ -32,6 +32,15 @@ object HealthCheckSettings {
}
new HealthCheckSettings(
+ config
+ .getConfig("startup-checks")
+ .root
+ .unwrapped
+ .asScala
+ .collect {
+ case (name, value) if validFQCN(value) => NamedHealthCheck(name,
value.toString)
+ }
+ .toList,
config
.getConfig("readiness-checks")
.root
@@ -50,6 +59,7 @@ object HealthCheckSettings {
case (name, value) if validFQCN(value) => NamedHealthCheck(name,
value.toString)
}
.toList,
+ config.getString("startup-path"),
config.getString("readiness-path"),
config.getString("liveness-path"),
config.getDuration("check-timeout").asScala)
@@ -63,6 +73,27 @@ object HealthCheckSettings {
/**
* Java API
*/
+ def create(
+ startupChecks: java.util.List[NamedHealthCheck],
+ readinessChecks: java.util.List[NamedHealthCheck],
+ livenessChecks: java.util.List[NamedHealthCheck],
+ startupPath: String,
+ readinessPath: String,
+ livenessPath: String,
+ checkDuration: java.time.Duration) =
+ new HealthCheckSettings(
+ startupChecks.asScala.toList,
+ readinessChecks.asScala.toList,
+ livenessChecks.asScala.toList,
+ startupPath,
+ readinessPath,
+ livenessPath,
+ checkDuration.asScala)
+
+ /**
+ * Java API
+ */
+ @deprecated("Use create that takes `startupChecks` and `startupPath`
parameters instead", "1.1.0")
def create(
readinessChecks: java.util.List[NamedHealthCheck],
livenessChecks: java.util.List[NamedHealthCheck],
@@ -70,27 +101,57 @@ object HealthCheckSettings {
livenessPath: String,
checkDuration: java.time.Duration) =
new HealthCheckSettings(
+ Nil,
readinessChecks.asScala.toList,
livenessChecks.asScala.toList,
+ "",
readinessPath,
livenessPath,
checkDuration.asScala)
}
/**
+ * @param startupChecks List of FQCN of startup checks
* @param readinessChecks List of FQCN of readiness checks
* @param livenessChecks List of FQCN of liveness checks
+ * @param startupPath The path to serve startup on
* @param readinessPath The path to serve readiness on
* @param livenessPath The path to serve liveness on
* @param checkTimeout how long to wait for all health checks to complete
*/
final class HealthCheckSettings(
+ val startupChecks: immutable.Seq[NamedHealthCheck],
val readinessChecks: immutable.Seq[NamedHealthCheck],
val livenessChecks: immutable.Seq[NamedHealthCheck],
+ val startupPath: String,
val readinessPath: String,
val livenessPath: String,
val checkTimeout: FiniteDuration) {
+ @deprecated("Use constructor that takes `startupChecks` and `startupPath`
parameters instead", "1.1.0")
+ def this(
+ readinessChecks: immutable.Seq[NamedHealthCheck],
+ livenessChecks: immutable.Seq[NamedHealthCheck],
+ readinessPath: String,
+ livenessPath: String,
+ checkTimeout: FiniteDuration
+ ) = {
+ this(
+ Nil,
+ readinessChecks,
+ livenessChecks,
+ "",
+ readinessPath,
+ livenessPath,
+ checkTimeout
+ )
+ }
+
+ /**
+ * Java API
+ */
+ def getStartupChecks(): java.util.List[NamedHealthCheck] =
startupChecks.asJava
+
/**
* Java API
*/
diff --git
a/management/src/main/scala/org/apache/pekko/management/ManagementLogMarker.scala
b/management/src/main/scala/org/apache/pekko/management/ManagementLogMarker.scala
index 69f318ff..04786c9c 100644
---
a/management/src/main/scala/org/apache/pekko/management/ManagementLogMarker.scala
+++
b/management/src/main/scala/org/apache/pekko/management/ManagementLogMarker.scala
@@ -40,6 +40,12 @@ object ManagementLogMarker {
def boundHttp(boundAddress: String): LogMarker =
LogMarker("pekkoManagementBound", Map(Properties.HttpAddress ->
boundAddress))
+ /**
+ * Marker "pekkoStartupCheckFailed" of log event when a startup check fails.
+ */
+ val startupCheckFailed: LogMarker =
+ LogMarker("pekkoStartupCheckFailed")
+
/**
* Marker "pekkoReadinessCheckFailed" of log event when a readiness check
fails.
*/
diff --git
a/management/src/main/scala/org/apache/pekko/management/internal/HealthChecksImpl.scala
b/management/src/main/scala/org/apache/pekko/management/internal/HealthChecksImpl.scala
index 43b9480c..c46a8c62 100644
---
a/management/src/main/scala/org/apache/pekko/management/internal/HealthChecksImpl.scala
+++
b/management/src/main/scala/org/apache/pekko/management/internal/HealthChecksImpl.scala
@@ -24,7 +24,8 @@ import pekko.event.Logging
import pekko.management.{ HealthCheckSettings, InvalidHealthCheckException,
ManagementLogMarker, NamedHealthCheck }
import pekko.management.javadsl.{ LivenessCheckSetup => JLivenessCheckSetup }
import pekko.management.javadsl.{ ReadinessCheckSetup => JReadinessCheckSetup }
-import pekko.management.scaladsl.{ HealthChecks, LivenessCheckSetup,
ReadinessCheckSetup }
+import pekko.management.javadsl.{ StartupCheckSetup => JStartupCheckSetup }
+import pekko.management.scaladsl.{ HealthChecks, LivenessCheckSetup,
ReadinessCheckSetup, StartupCheckSetup }
import pekko.util.FutureConverters._
import pekko.util.ccompat.JavaConverters._
@@ -47,6 +48,9 @@ final private[pekko] class HealthChecksImpl(system:
ExtendedActorSystem, setting
private val log = Logging.withMarker(system, classOf[HealthChecksImpl])
+ log.info(
+ "Loading startup checks [{}]",
+ settings.startupChecks.map(a => a.name ->
a.fullyQualifiedClassName).mkString(", "))
log.info(
"Loading readiness checks [{}]",
settings.readinessChecks.map(a => a.name ->
a.fullyQualifiedClassName).mkString(", "))
@@ -54,6 +58,19 @@ final private[pekko] class HealthChecksImpl(system:
ExtendedActorSystem, setting
"Loading liveness checks [{}]",
settings.livenessChecks.map(a => a.name ->
a.fullyQualifiedClassName).mkString(", "))
+ private val startupChecks: immutable.Seq[HealthCheck] = {
+ val fromScaladslSetup = system.settings.setup.get[StartupCheckSetup] match
{
+ case None => Nil
+ case Some(setup) => setup.createHealthChecks(system)
+ }
+ val fromJavadslSetup = system.settings.setup.get[JStartupCheckSetup] match
{
+ case None => Nil
+ case Some(setup) =>
convertSuppliersToScala(setup.createHealthChecks(system))
+ }
+ val fromConfig = load(settings.startupChecks)
+ fromConfig ++ fromScaladslSetup ++ fromJavadslSetup
+ }
+
private val readiness: immutable.Seq[HealthCheck] = {
val fromScaladslSetup = system.settings.setup.get[ReadinessCheckSetup]
match {
case None => Nil
@@ -138,6 +155,20 @@ final private[pekko] class HealthChecksImpl(system:
ExtendedActorSystem, setting
}
}
+ def startupResult(): Future[Either[String, Unit]] = {
+ val result = check(startupChecks)
+ result.onComplete {
+ case Success(Right(())) =>
+ case Success(Left(reason)) =>
+ log.info(ManagementLogMarker.startupCheckFailed, reason)
+ case Failure(e) =>
+ log.warning(ManagementLogMarker.startupCheckFailed, e.getMessage)
+ }
+ result
+ }
+
+ def startup(): Future[Boolean] = startupResult().map(_.isRight)
+
def readyResult(): Future[Either[String, Unit]] = {
val result = check(readiness)
result.onComplete {
diff --git
a/management/src/main/scala/org/apache/pekko/management/javadsl/HealthChecks.scala
b/management/src/main/scala/org/apache/pekko/management/javadsl/HealthChecks.scala
index d3901301..f4c71fe8 100644
---
a/management/src/main/scala/org/apache/pekko/management/javadsl/HealthChecks.scala
+++
b/management/src/main/scala/org/apache/pekko/management/javadsl/HealthChecks.scala
@@ -37,6 +37,18 @@ final class HealthChecks(system: ExtendedActorSystem,
settings: HealthCheckSetti
private val delegate = new HealthChecksImpl(system, settings)
+ /**
+ * Returns CompletionStage(result), containing the system's startup result
+ */
+ def startupResult(): CompletionStage[CheckResult] =
+ delegate.startupResult().map(new CheckResult(_))(system.dispatcher).asJava
+
+ /**
+ * Returns CompletionStage(true) if the system has started
+ */
+ def startup(): CompletionStage[java.lang.Boolean] =
+ startupResult().thenApply(((r: CheckResult) => r.isSuccess).asJava)
+
/**
* Returns CompletionStage(result), containing the system's readiness result
*/
@@ -64,6 +76,23 @@ final class HealthChecks(system: ExtendedActorSystem,
settings: HealthCheckSetti
aliveResult().thenApply(((r: CheckResult) => r.isSuccess).asJava)
}
+object StartupCheckSetup {
+
+ /**
+ * Programmatic definition of startup checks
+ */
+ def create(createHealthChecks: JFunction[ActorSystem,
JList[Supplier[CompletionStage[java.lang.Boolean]]]])
+ : StartupCheckSetup = {
+ new StartupCheckSetup(createHealthChecks)
+ }
+}
+
+/**
+ * Setup for startup checks, constructor is *Internal API*, use factories in
[[StartupCheckSetup]]
+ */
+final class StartupCheckSetup private (
+ val createHealthChecks: JFunction[ActorSystem,
JList[Supplier[CompletionStage[java.lang.Boolean]]]]) extends Setup
+
object ReadinessCheckSetup {
/**
diff --git
a/management/src/main/scala/org/apache/pekko/management/scaladsl/HealthChecks.scala
b/management/src/main/scala/org/apache/pekko/management/scaladsl/HealthChecks.scala
index aad4f34d..c92753e2 100644
---
a/management/src/main/scala/org/apache/pekko/management/scaladsl/HealthChecks.scala
+++
b/management/src/main/scala/org/apache/pekko/management/scaladsl/HealthChecks.scala
@@ -39,6 +39,16 @@ object HealthChecks {
@DoNotInherit
abstract class HealthChecks {
+ /**
+ * Returns Future(true) if the system has started
+ */
+ def startup(): Future[Boolean]
+
+ /**
+ * Returns Future(result) containing the system's startup result
+ */
+ def startupResult(): Future[Either[String, Unit]]
+
/**
* Returns Future(true) if the system is ready to receive user traffic
*/
@@ -62,6 +72,23 @@ abstract class HealthChecks {
def aliveResult(): Future[Either[String, Unit]]
}
+object StartupCheckSetup {
+
+ /**
+ * Programmatic definition of startup checks
+ */
+ def apply(createHealthChecks: ActorSystem =>
immutable.Seq[HealthChecks.HealthCheck]): StartupCheckSetup = {
+ new StartupCheckSetup(createHealthChecks)
+ }
+
+}
+
+/**
+ * Setup for startup checks, constructor is *Internal API*, use factories in
[[StartupCheckSetup]]
+ */
+final class StartupCheckSetup private (
+ val createHealthChecks: ActorSystem =>
immutable.Seq[HealthChecks.HealthCheck]) extends Setup
+
object ReadinessCheckSetup {
/**
diff --git
a/management/src/test/java/org/apache/pekko/management/HealthCheckTest.java
b/management/src/test/java/org/apache/pekko/management/HealthCheckTest.java
index ad05ff34..ca951848 100644
--- a/management/src/test/java/org/apache/pekko/management/HealthCheckTest.java
+++ b/management/src/test/java/org/apache/pekko/management/HealthCheckTest.java
@@ -30,6 +30,7 @@ import org.apache.pekko.actor.setup.ActorSystemSetup;
import org.apache.pekko.management.javadsl.HealthChecks;
import org.apache.pekko.management.javadsl.LivenessCheckSetup;
import org.apache.pekko.management.javadsl.ReadinessCheckSetup;
+import org.apache.pekko.management.javadsl.StartupCheckSetup;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.Assert;
@@ -78,15 +79,47 @@ public class HealthCheckTest extends JUnitSuite {
new HealthChecks(
system,
HealthCheckSettings.create(
- healthChecks, healthChecks, "ready", "alive",
java.time.Duration.ofSeconds(1)));
+ healthChecks,
+ healthChecks,
+ healthChecks,
+ "startup",
+ "ready",
+ "alive",
+ java.time.Duration.ofSeconds(1)));
+ assertEquals(true,
checks.startupResult().toCompletableFuture().get().isSuccess());
assertEquals(true,
checks.aliveResult().toCompletableFuture().get().isSuccess());
assertEquals(true,
checks.readyResult().toCompletableFuture().get().isSuccess());
+ assertEquals(true, checks.startup().toCompletableFuture().get());
assertEquals(true, checks.alive().toCompletableFuture().get());
assertEquals(true, checks.ready().toCompletableFuture().get());
}
@Test
public void notOkayReturnsFalse() throws Exception {
+ List<NamedHealthCheck> healthChecks =
+ Collections.singletonList(
+ new NamedHealthCheck("Ok",
"org.apache.pekko.management.HealthCheckTest$Ok"));
+ HealthChecks checks =
+ new HealthChecks(
+ system,
+ HealthCheckSettings.create(
+ healthChecks,
+ healthChecks,
+ healthChecks,
+ "startup",
+ "ready",
+ "alive",
+ java.time.Duration.ofSeconds(1)));
+ assertEquals(true,
checks.startupResult().toCompletableFuture().get().isSuccess());
+ assertEquals(true,
checks.aliveResult().toCompletableFuture().get().isSuccess());
+ assertEquals(true,
checks.readyResult().toCompletableFuture().get().isSuccess());
+ assertEquals(true, checks.startup().toCompletableFuture().get());
+ assertEquals(true, checks.alive().toCompletableFuture().get());
+ assertEquals(true, checks.ready().toCompletableFuture().get());
+ }
+
+ @Test
+ public void creatableThroughLegacyConstructor() throws Exception {
List<NamedHealthCheck> healthChecks =
Collections.singletonList(
new NamedHealthCheck("Ok",
"org.apache.pekko.management.HealthCheckTest$Ok"));
@@ -95,8 +128,10 @@ public class HealthCheckTest extends JUnitSuite {
system,
HealthCheckSettings.create(
healthChecks, healthChecks, "ready", "alive",
java.time.Duration.ofSeconds(1)));
+ assertEquals(true,
checks.startupResult().toCompletableFuture().get().isSuccess());
assertEquals(true,
checks.aliveResult().toCompletableFuture().get().isSuccess());
assertEquals(true,
checks.readyResult().toCompletableFuture().get().isSuccess());
+ assertEquals(true, checks.startup().toCompletableFuture().get());
assertEquals(true, checks.alive().toCompletableFuture().get());
assertEquals(true, checks.ready().toCompletableFuture().get());
}
@@ -110,7 +145,13 @@ public class HealthCheckTest extends JUnitSuite {
new HealthChecks(
system,
HealthCheckSettings.create(
- healthChecks, healthChecks, "ready", "alive",
java.time.Duration.ofSeconds(1)));
+ healthChecks,
+ healthChecks,
+ healthChecks,
+ "startup",
+ "ready",
+ "alive",
+ java.time.Duration.ofSeconds(1)));
try {
checks.alive().toCompletableFuture().get();
Assert.fail("Expected exception");
@@ -121,6 +162,8 @@ public class HealthCheckTest extends JUnitSuite {
@Test
public void defineViaActorSystemSetup() throws Exception {
+ StartupCheckSetup startupCheckSetup =
+ StartupCheckSetup.create(system -> Collections.singletonList(new
NotOk(system)));
ReadinessCheckSetup readinessSetup =
ReadinessCheckSetup.create(system -> Arrays.asList(new Ok(), new
NotOk(system)));
LivenessCheckSetup livenessSetup =
@@ -128,7 +171,7 @@ public class HealthCheckTest extends JUnitSuite {
// bootstrapSetup is needed for config (otherwise default config)
BootstrapSetup bootstrapSetup =
BootstrapSetup.create(ConfigFactory.parseString("some=thing"));
ActorSystemSetup actorSystemSetup =
- ActorSystemSetup.create(bootstrapSetup, readinessSetup, livenessSetup);
+ ActorSystemSetup.create(bootstrapSetup, startupCheckSetup,
readinessSetup, livenessSetup);
ExtendedActorSystem sys2 =
(ExtendedActorSystem) ActorSystem.create("HealthCheckTest2",
actorSystemSetup);
try {
@@ -138,11 +181,15 @@ public class HealthCheckTest extends JUnitSuite {
HealthCheckSettings.create(
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
+ "startup",
"ready",
"alive",
java.time.Duration.ofSeconds(1)));
+ assertEquals(false,
checks.startupResult().toCompletableFuture().get().isSuccess());
assertEquals(false,
checks.aliveResult().toCompletableFuture().get().isSuccess());
assertEquals(false,
checks.readyResult().toCompletableFuture().get().isSuccess());
+ assertEquals(false, checks.startup().toCompletableFuture().get());
assertEquals(false, checks.alive().toCompletableFuture().get());
assertEquals(false, checks.ready().toCompletableFuture().get());
} finally {
diff --git
a/management/src/test/scala/org/apache/pekko/management/HealthCheckRoutesSpec.scala
b/management/src/test/scala/org/apache/pekko/management/HealthCheckRoutesSpec.scala
index 942b4406..de08ce17 100644
---
a/management/src/test/scala/org/apache/pekko/management/HealthCheckRoutesSpec.scala
+++
b/management/src/test/scala/org/apache/pekko/management/HealthCheckRoutesSpec.scala
@@ -29,10 +29,13 @@ class HealthCheckRoutesSpec extends AnyWordSpec with
Matchers with ScalatestRout
private val eas = system.asInstanceOf[ExtendedActorSystem]
private def testRoute(
+ startupResultValue: Future[Either[String, Unit]] =
Future.successful(Right(())),
readyResultValue: Future[Either[String, Unit]] =
Future.successful(Right(())),
aliveResultValue: Future[Either[String, Unit]] =
Future.successful(Right(()))): Route = {
new HealthCheckRoutes(eas) {
override protected val healthChecks: HealthChecks = new HealthChecks {
+ override def startupResult(): Future[Either[String, Unit]] =
startupResultValue
+ override def startup(): Future[Boolean] =
startupResultValue.map(_.isRight)
override def readyResult(): Future[Either[String, Unit]] =
readyResultValue
override def ready(): Future[Boolean] = readyResultValue.map(_.isRight)
override def aliveResult(): Future[Either[String, Unit]] =
aliveResultValue
@@ -41,6 +44,7 @@ class HealthCheckRoutesSpec extends AnyWordSpec with Matchers
with ScalatestRout
}.routes(ManagementRouteProviderSettings(Uri("http://whocares"), readOnly
= false))
}
+ tests("/startup", result => testRoute(startupResultValue = result))
tests("/ready", result => testRoute(readyResultValue = result))
tests("/alive", result => testRoute(aliveResultValue = result))
diff --git
a/management/src/test/scala/org/apache/pekko/management/HealthCheckSettingsSpec.scala
b/management/src/test/scala/org/apache/pekko/management/HealthCheckSettingsSpec.scala
index 894c8898..27851153 100644
---
a/management/src/test/scala/org/apache/pekko/management/HealthCheckSettingsSpec.scala
+++
b/management/src/test/scala/org/apache/pekko/management/HealthCheckSettingsSpec.scala
@@ -13,32 +13,83 @@
package org.apache.pekko.management
+import scala.annotation.nowarn
+import scala.concurrent.duration.DurationInt
+
import com.typesafe.config.ConfigFactory
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
+@nowarn("msg=deprecated")
class HealthCheckSettingsSpec extends AnyWordSpec with Matchers {
"Health Check Settings" should {
"filter out blank fqcn" in {
HealthCheckSettings(ConfigFactory.parseString("""
+ startup-checks {
+ cluster-membership = ""
+ }
+ readiness-checks {}
+ liveness-checks {}
+ startup-path = ""
+ readiness-path = ""
+ liveness-path = ""
+ check-timeout = 1s
+ """)).startupChecks shouldEqual Nil
+ HealthCheckSettings(ConfigFactory.parseString("""
+ startup-checks {}
readiness-checks {
cluster-membership = ""
}
liveness-checks {}
+ startup-path = ""
readiness-path = ""
liveness-path = ""
check-timeout = 1s
""")).readinessChecks shouldEqual Nil
HealthCheckSettings(ConfigFactory.parseString("""
+ startup-checks {}
liveness-checks {
cluster-membership = ""
}
readiness-checks {}
+ startup-path = ""
readiness-path = ""
liveness-path = ""
check-timeout = 1s
- """)).readinessChecks shouldEqual Nil
+ """)).livenessChecks shouldEqual Nil
+ }
+
+ "be creatable with primary constructor" in {
+ HealthCheckSettings.create(
+ startupChecks = java.util.Collections.emptyList(),
+ readinessChecks = java.util.Collections.emptyList(),
+ livenessChecks = java.util.Collections.emptyList(),
+ startupPath = "",
+ readinessPath = "",
+ livenessPath = "",
+ checkDuration = java.time.Duration.ofSeconds(1L))
+ .startupChecks shouldEqual Nil
+ }
+
+ "be creatable with legacy create method" in {
+ HealthCheckSettings.create(
+ readinessChecks = java.util.Collections.emptyList(),
+ livenessChecks = java.util.Collections.emptyList(),
+ readinessPath = "",
+ livenessPath = "",
+ checkDuration = java.time.Duration.ofSeconds(1L))
+ .startupChecks shouldEqual Nil
+ }
+
+ "be creatable with legacy constructor" in {
+ val healthCheckSettings = new HealthCheckSettings(
+ readinessChecks = Nil,
+ livenessChecks = Nil,
+ readinessPath = "",
+ livenessPath = "",
+ checkTimeout = 1.seconds)
+ healthCheckSettings.livenessChecks shouldEqual Nil
}
}
diff --git
a/management/src/test/scala/org/apache/pekko/management/HealthChecksSpec.scala
b/management/src/test/scala/org/apache/pekko/management/HealthChecksSpec.scala
index b901d40f..40e0d410 100644
---
a/management/src/test/scala/org/apache/pekko/management/HealthChecksSpec.scala
+++
b/management/src/test/scala/org/apache/pekko/management/HealthChecksSpec.scala
@@ -19,7 +19,7 @@ import pekko.actor.setup.ActorSystemSetup
import pekko.actor.{ ActorSystem, BootstrapSetup, ExtendedActorSystem }
import pekko.management.HealthChecksSpec.{ ctxException, failedCause }
import pekko.management.internal.{ CheckFailedException, CheckTimeoutException
}
-import pekko.management.scaladsl.{ HealthChecks, LivenessCheckSetup,
ReadinessCheckSetup }
+import pekko.management.scaladsl.{ HealthChecks, LivenessCheckSetup,
ReadinessCheckSetup, StartupCheckSetup }
import pekko.testkit.TestKit
import org.scalatest.BeforeAndAfterAll
import org.scalatest.concurrent.ScalaFutures
@@ -114,14 +114,17 @@ class HealthChecksSpec
val DoesNotExist = NamedHealthCheck("DoesNotExist",
"org.apache.pekko.management.DoesNotExist")
val CtrExceptionCheck = NamedHealthCheck("CtrExceptionCheck",
"org.apache.pekko.management.CtrException")
- def settings(readiness: im.Seq[NamedHealthCheck], liveness:
im.Seq[NamedHealthCheck]) =
- new HealthCheckSettings(readiness, liveness, "ready", "alive", 500.millis)
+ def settings(startup: im.Seq[NamedHealthCheck], readiness:
im.Seq[NamedHealthCheck],
+ liveness: im.Seq[NamedHealthCheck]) =
+ new HealthCheckSettings(startup, readiness, liveness, "startup", "ready",
"alive", 500.millis)
"HealthCheck" should {
"succeed by default" in {
- val checks = HealthChecks(eas, settings(Nil, Nil))
+ val checks = HealthChecks(eas, settings(Nil, Nil, Nil))
+ checks.startupResult().futureValue shouldEqual Right(())
checks.aliveResult().futureValue shouldEqual Right(())
checks.readyResult().futureValue shouldEqual Right(())
+ checks.startup().futureValue shouldEqual true
checks.alive().futureValue shouldEqual true
checks.ready().futureValue shouldEqual true
}
@@ -129,10 +132,13 @@ class HealthChecksSpec
val checks = HealthChecks(
eas,
settings(
+ im.Seq(OkCheck),
im.Seq(OkCheck),
im.Seq(OkCheck)))
+ checks.startupResult().futureValue shouldEqual Right(())
checks.aliveResult().futureValue shouldEqual Right(())
checks.readyResult().futureValue shouldEqual Right(())
+ checks.startup().futureValue shouldEqual true
checks.alive().futureValue shouldEqual true
checks.ready().futureValue shouldEqual true
}
@@ -140,10 +146,13 @@ class HealthChecksSpec
val checks = HealthChecks(
eas,
settings(
+ im.Seq(NoArgsCtrCheck),
im.Seq(NoArgsCtrCheck),
im.Seq(NoArgsCtrCheck)))
+ checks.startupResult().futureValue shouldEqual Right(())
checks.aliveResult().futureValue shouldEqual Right(())
checks.readyResult().futureValue shouldEqual Right(())
+ checks.startup().futureValue shouldEqual true
checks.alive().futureValue shouldEqual true
checks.ready().futureValue shouldEqual true
}
@@ -151,10 +160,13 @@ class HealthChecksSpec
val checks = HealthChecks(
eas,
settings(
+ im.Seq(FalseCheck),
im.Seq(FalseCheck),
im.Seq(FalseCheck)))
+ checks.startupResult().futureValue.isRight shouldEqual false
checks.readyResult().futureValue.isRight shouldEqual false
checks.aliveResult().futureValue.isRight shouldEqual false
+ checks.startup().futureValue shouldEqual false
checks.ready().futureValue shouldEqual false
checks.alive().futureValue shouldEqual false
}
@@ -162,12 +174,17 @@ class HealthChecksSpec
val checks = HealthChecks(
eas,
settings(
+ im.Seq(ThrowsCheck),
im.Seq(ThrowsCheck),
im.Seq(ThrowsCheck)))
+ checks.startupResult().failed.futureValue shouldEqual
+ CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
checks.readyResult().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
checks.aliveResult().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
+ checks.startup().failed.futureValue shouldEqual
+ CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
checks.ready().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
checks.alive().failed.futureValue shouldEqual
@@ -178,11 +195,15 @@ class HealthChecksSpec
OkCheck,
ThrowsCheck,
FalseCheck)
- val hc = HealthChecks(eas, settings(checks, checks))
+ val hc = HealthChecks(eas, settings(checks, checks, checks))
+ hc.startupResult().failed.futureValue shouldEqual
+ CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
hc.readyResult().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
hc.aliveResult().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
+ hc.startup().failed.futureValue shouldEqual
+ CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
hc.ready().failed.futureValue shouldEqual
CheckFailedException("Check [org.apache.pekko.management.Throws] failed:
null", failedCause)
hc.alive().failed.futureValue shouldEqual
@@ -191,9 +212,11 @@ class HealthChecksSpec
"return failure if check throws" in {
val checks = im.Seq(
NaughtyCheck)
- val hc = HealthChecks(eas, settings(checks, checks))
+ val hc = HealthChecks(eas, settings(checks, checks, checks))
+ hc.startupResult().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
hc.readyResult().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
hc.aliveResult().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
+ hc.startup().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
hc.ready().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
hc.alive().failed.futureValue.getMessage shouldEqual "Check
[org.apache.pekko.management.Naughty] failed: bad"
}
@@ -201,11 +224,15 @@ class HealthChecksSpec
val checks = im.Seq(
SlowCheck,
OkCheck)
- val hc = HealthChecks(eas, settings(checks, checks))
+ val hc = HealthChecks(eas, settings(checks, checks, checks))
+ Await.result(hc.startupResult().failed, 1.second) shouldEqual
CheckTimeoutException(
+ "Check [org.apache.pekko.management.Slow] timed out after 500
milliseconds")
Await.result(hc.readyResult().failed, 1.second) shouldEqual
CheckTimeoutException(
"Check [org.apache.pekko.management.Slow] timed out after 500
milliseconds")
Await.result(hc.aliveResult().failed, 1.second) shouldEqual
CheckTimeoutException(
"Check [org.apache.pekko.management.Slow] timed out after 500
milliseconds")
+ Await.result(hc.startup().failed, 1.second) shouldEqual
CheckTimeoutException(
+ "Check [org.apache.pekko.management.Slow] timed out after 500
milliseconds")
Await.result(hc.ready().failed, 1.second) shouldEqual
CheckTimeoutException(
"Check [org.apache.pekko.management.Slow] timed out after 500
milliseconds")
Await.result(hc.alive().failed, 1.second) shouldEqual
CheckTimeoutException(
@@ -214,43 +241,46 @@ class HealthChecksSpec
"provide useful error if user's ctr is invalid" in {
intercept[InvalidHealthCheckException] {
val checks = im.Seq(InvalidCtrCheck)
- HealthChecks(eas, settings(checks, checks))
+ HealthChecks(eas, settings(checks, checks, checks))
}.getMessage shouldEqual "Health checks:
[NamedHealthCheck(InvalidCtr,org.apache.pekko.management.InvalidCtr)] must have
a no args constructor or a single argument constructor that takes an
ActorSystem"
}
"provide useful error if invalid type" in {
intercept[InvalidHealthCheckException] {
val checks = im.Seq(WrongTypeCheck)
- HealthChecks(eas, settings(checks, checks))
+ HealthChecks(eas, settings(checks, checks, checks))
}.getMessage shouldEqual "Health checks:
[NamedHealthCheck(WrongType,org.apache.pekko.management.WrongType)] must have
type: () => Future[Boolean]"
}
"provide useful error if class not found" in {
intercept[InvalidHealthCheckException] {
val checks =
im.Seq(DoesNotExist, OkCheck)
- HealthChecks(eas, settings(checks, checks))
+ HealthChecks(eas, settings(checks, checks, checks))
}.getMessage shouldEqual "Health check:
[org.apache.pekko.management.DoesNotExist] not found"
}
"provide useful error if class ctr throws" in {
intercept[InvalidHealthCheckException] {
val checks =
im.Seq(OkCheck, CtrExceptionCheck)
- HealthChecks(eas, settings(checks, checks))
+ HealthChecks(eas, settings(checks, checks, checks))
}.getCause shouldEqual ctxException
}
"be possible to define via ActorSystem Setup" in {
+ val startupSetup = StartupCheckSetup(system => List(new Ok(system), new
False(system)))
val readinessSetup = ReadinessCheckSetup(system => List(new Ok(system),
new False(system)))
val livenessSetup = LivenessCheckSetup(system => List(new False(system)))
// bootstrapSetup is needed for config (otherwise default config)
val bootstrapSetup =
BootstrapSetup(ConfigFactory.parseString("some=thing"))
- val actorSystemSetup = ActorSystemSetup(bootstrapSetup, readinessSetup,
livenessSetup)
+ val actorSystemSetup = ActorSystemSetup(bootstrapSetup, startupSetup,
readinessSetup, livenessSetup)
val sys2 = ActorSystem("HealthCheckSpec2",
actorSystemSetup).asInstanceOf[ExtendedActorSystem]
try {
val checks = HealthChecks(
sys2,
- settings(Nil, Nil) // no checks from config
+ settings(Nil, Nil, Nil) // no checks from config
)
+ checks.startupResult().futureValue shouldEqual Left("Check
[org.apache.pekko.management.False] not ok")
checks.aliveResult().futureValue shouldEqual Left("Check
[org.apache.pekko.management.False] not ok")
checks.readyResult().futureValue shouldEqual Left("Check
[org.apache.pekko.management.False] not ok")
+ checks.startup().futureValue shouldEqual false
checks.alive().futureValue shouldEqual false
checks.ready().futureValue shouldEqual false
} finally {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]