///////////////////////////////////////////////////////////////////////////////

    Copyright (c) 2019, 2020 Oracle and/or its affiliates.

    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.

///////////////////////////////////////////////////////////////////////////////

= Helidon SE Health Check Guide
:description: Helidon health-checks
:keywords: helidon, health-check, health, check

This guide describes how to create a sample Helidon SE project
that can be used to run some basic examples using both built-in and custom health-checks.

== What you need

[width=50%,role="flex, sm7"]
|===
|About 15 minutes
|<<about/03_prerequisites.adoc,Helidon Prerequisites>>
|===

=== Create a sample SE project

Generate the project sources using the Helidon SE Maven archetype.
The result is a simple project that can be used for the examples in this guide.

[source,bash,subs="attributes+"]
.Run the Maven archetype:
----
mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion={helidon-version} \
    -DgroupId=io.helidon.examples \
    -DartifactId=helidon-quickstart-se \
    -Dpackage=io.helidon.examples.quickstart.se
----

=== Using the built-in health-checks

Helidon has a set of built-in health-checks that can be optionally enabled to report various
 health-check statuses that are commonly used:

* deadlock detection
* available disk space
* available heap memory

The following example will demonstrate how to use the built-in health-checks.  These examples are all executed
from the root directory of your project (helidon-quickstart-se).

[source,xml]
.Notice that the built-in health-check dependency is already in the project's pom.xml file:
----
<dependency>
    <groupId>io.helidon.health</groupId>
    <artifactId>helidon-health-checks</artifactId>
</dependency>
----

[source,java]
.Modify `Main.java`, import `HealthCheckReponse`, and replace the `createRouting` method:
----

import org.eclipse.microprofile.health.HealthCheckResponse;  // <1>

...

private static Routing createRouting(Config config) {

    HealthSupport health = HealthSupport.builder()
      .addLiveness(HealthChecks.healthChecks())  // <2>
      .build();

    return Routing.builder()
      .register(JsonSupport.create()) // <3>
      .register(health)  // <4>
      .build();
}
----
<1> Import `HealthCheckResponse`.
<2> Add built-in health-checks (requires the `helidon-health-checks`
 dependency).
<3> Register the `JSON-P` support in the WebServer routing.
<4> Register the created health support with web server routing (adds the
`/health` endpoint).


[source,bash]
.Build the application, skipping unit tests, then run it:
----
mvn package -DskipTests=true
java -jar target/helidon-quickstart-se.jar
----

[source,bash]
.Verify the health endpoint in a new terminal window:
----
curl http://localhost:8080/health
----

[source,json]
.JSON response:
----
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "deadlock",
      "state": "UP",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "319.58 GB",
        "freeBytes": 343144304640,
        "percentFree": "68.63%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "196.84 MB",
        "freeBytes": 206404016,
        "max": "3.56 GB",
        "maxBytes": 3817865216,
        "percentFree": "98.66%",
        "total": "245.50 MB",
        "totalBytes": 257425408
      }
    }
  ]
}
----

NOTE: In MicroProfile Health 2.0 `outcome` and `state` were replaced by `status` in the JSON response wire format.
Helidon currently provides both fields for backwards compatibility, but use of `outcome` and `state` is deprecated
and will be removed in a future release. You should rely on `status` instead.

=== Custom liveness health-checks

You can create application specific custom health-checks and integrate them with Helidon
using the `HealthSupport` class, which is a  WebServer service that contains
a collection of registered `HealthCheck` instances. When queried, it invokes the registered
health-check and returns a response with a status code representing the overall
state of the application.

[source,xml]
.Notice the custom health-checks dependency is already in the project's pom.xml file:
----
<dependency>
    <groupId>io.helidon.health</groupId>
    <artifactId>helidon-health</artifactId>
</dependency>
----

[source,java]
.Replace the `HealthSupport` builder in the `Main.createRouting` method:
----
HealthSupport health = HealthSupport.builder()
  .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build()) // <1>
  .build();

----
<1> Add a custom liveness health-check. This example returns `UP` and current time.

[source,bash]
.Build and run the application, then verify the custom health endpoint:
----
curl http://localhost:8080/health
----

[source,json]
.JSON response:
----
{
    "outcome": "UP",
    "checks": [
        {
            "name": "LivenessCheck",
            "state": "UP",
            "data": {
                "time": 1546958376613
            }
        }
    ]
}
----

=== Custom readiness health-check

You can add a readiness check to indicate that the application is ready to be used.  In this
example, the server will wait five seconds before it becomes ready.

[source,java]
.Add a `readyTime` variable to the `Main` class, then set it five seconds after the application starts:
----
import java.util.concurrent.atomic.AtomicLong; // <1>

public final class Main {

  private static AtomicLong readyTime = new AtomicLong(0); // <2>
  ...

    static WebServer startServer() throws IOException {
    ...
      server.start() ...

        // Server threads are not daemon. No need to block. Just react.
      try {
        Thread.sleep(5000); // <3>
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }

      readyTime.set(System.currentTimeMillis()); // <4>
      return server;
----
<1> Import AtomicLong.
<2> Declare the `readyTime` variable.
<3> Sleep five seconds.
<4> Set the `readyTime` to the time when the server became ready.


[source,java]
.Add a readiness check to the `HealhSupport` builder in the `Main.createRouting` method:
----
HealthSupport health = HealthSupport.builder()
  .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
  .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build()) // <1>
  .build();
----
<1> Add the readiness check.

[source,bash]
.Build and run the application.  Issue the curl command with -v within five seconds and you see the application is not ready:
----
curl -v  http://localhost:8080/health/ready
----

[source,json]
.HTTP response:
----
...
< HTTP/1.1 503 Service Unavailable // <1>
...
{
  "outcome": "DOWN",
  "status": "DOWN",
  "checks": [
    {
      "name": "ReadinessCheck",
      "state": "DOWN",
      "status": "DOWN",
      "data": {
        "time,": 0
      }
    }
  ]
}
----
<1> The HTTP status is `503` since the application is not ready.

[source,bash]
.After five seconds you will see the application is ready:
----
curl -v http://localhost:8080/health/ready
----

[source,json]
.JSON response:
----
...
< HTTP/1.1 200 OK // <1>
...
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566243562097
      }
    }
  ]
}
----
<1> The HTTP status is `200` indicating that the application is ready.

When using the health-check URLs, you can get the following health-check data

* liveness only - http://localhost:8080/health/live
* readiness only -  http://localhost:8080/health/ready
* both -  http://localhost:8080/health


[source,bash]
.Get both liveness and readiness data from a single query:
----
curl http://localhost:8080/health
----

[source,json]
.JSON response:
----
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time": 1566244094548
      }
    },
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566244093012
      }
    }
  ]
}
----

=== Combine built-in and custom health-checks

You can combine built-in and custom health-checks using the same HealthSupport builder.

[source,java]
.Register a custom health-check in the `Main.createRouting` method:
----
HealthSupport health = HealthSupport.builder()
    .addLiveness(HealthChecks.healthChecks())  // <1>
    .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build())
    .build();
----
<1> Add the built-in health-checks back to `HealthSupport` builder.

[source,bash]
.Build and run the application, then verify the health endpoint.  You will see both the built-in and custom health-check data:
----
curl http://localhost:8080/health
----

[source,json]
.JSON response:
----
{
  "outcome": "UP",
  "status": "UP",
  "checks": [
    {
      "name": "LivenessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time": 1566245527673
      }
    },
    {
      "name": "ReadinessCheck",
      "state": "UP",
      "status": "UP",
      "data": {
        "time,": 1566245527620
      }
    },
    {
      "name": "deadlock",
      "state": "UP",
      "status": "UP"
    },
    {
      "name": "diskSpace",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "326.17 GB",
        "freeBytes": 350224424960,
        "percentFree": "70.05%",
        "total": "465.63 GB",
        "totalBytes": 499963174912
      }
    },
    {
      "name": "heapMemory",
      "state": "UP",
      "status": "UP",
      "data": {
        "free": "247.76 MB",
        "freeBytes": 259791680,
        "max": "4.00 GB",
        "maxBytes": 4294967296,
        "percentFree": "99.80%",
        "total": "256.00 MB",
        "totalBytes": 268435456
      }
    }
  ]
}
----


=== Custom health-check URL path

You can use a custom URL path for heath checks by setting the `WebContext`.  In the following example, only
the liveness URL is changed, but you can do the same for the readiness and default
health-checks.

[source,java]
.Register a custom URL path with the custom health-check in the `Main.createRouting` method:
----
HealthSupport health = HealthSupport.builder()
    .webContext("/probe/live")// <1>
    .addLiveness(() -> HealthCheckResponse.named("livenessProbe")
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .build();

----
<1> Change the liveness URL path using a `WebContext`.

[source,bash]
.Build and run the application, then verify that the liveness endpoint is using the `/probe/live`:
----
curl http://localhost:8080/probe/live
----

[source,json]
.JSON response:
----
{
  "outcome": "UP",
  "checks": [
    {
      "name": "livenessProbe",
      "state": "UP",
      "data": {
        "time": 1546958376613
      }
    }
  ]
}
----

=== Using Liveness and Readiness health-checks with Kubernetes

The following example shows how to integrate the Helidon health API in an application that implements
health endpoints for the Kubernetes liveness and readiness probes.

[source,java]
.Change the `HealthSupport` builder in the `Main.createRouting` method to use the built-in liveness checks, a custom liveness check, and a readiness check:
----
HealthSupport health = HealthSupport.builder()
    .addLiveness(HealthChecks.healthChecks()) // <1>
    .addLiveness(() -> HealthCheckResponse.named("LivenessCheck")  // <2>
      .up()
      .withData("time", System.currentTimeMillis())
      .build())
    .addReadiness(() -> HealthCheckResponse.named("ReadinessCheck")  // <3>
      .state (readyTime.get() != 0 )
      .withData( "time", readyTime.get())
      .build())
    .build();
----
<1> Add built-in health-checks.
<2> Add a custom liveness check.
<2> Add a custom readiness check.


[source,bash]
.Build and run the application, then verify the liveness and readiness endpoints:
----
curl http://localhost:8080/health/live
curl http://localhost:8080/health/ready
----


[source,bash]
.Stop the application and build the docker image:
----
docker build -t helidon-quickstart-se .
----

[source,yaml]
.Create the Kubernetes YAML specification, named `health.yaml`, with the following content:
----
kind: Service
apiVersion: v1
metadata:
  name: helidon-health // <1>
  labels:
    app: helidon-health
spec:
  type: NodePort
  selector:
    app: helidon-health
  ports:
    - port: 8080
      targetPort: 8080
      name: http
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: helidon-health // <2>
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: helidon-health
        version: v1
    spec:
      containers:
        - name: helidon-health
          image: helidon-quickstart-se
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /health/live // <3>
              port: 8080
            initialDelaySeconds: 5 // <4>
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/ready // <5>
              port: 8080
            initialDelaySeconds: 5 // <6>
            periodSeconds: 2
            timeoutSeconds: 3
---
----
<1> A service of type `NodePort` that serves the default routes on port `8080`.
<2> A deployment with one replica of a pod.
<3> The HTTP endpoint for the liveness probe.
<4> The liveness probe configuration.
<5> The HTTP endpoint for the readiness probe.
<6> The readiness probe configuration.


[source,bash]
.Create and deploy the application into Kubernetes:
----
kubectl apply -f ./health.yaml
----

[source,bash]
.Get the service information:
----
kubectl get service/helidon-health
----

[source,bash]
----
NAME             TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
helidon-health   NodePort   10.107.226.62   <none>        8080:30116/TCP   4s // <1>
----
<1> A service of type `NodePort` that serves the default routes on port `30116`.

[source,bash]
.Verify the health endpoints using port '30116', your port may be different:
----
curl http://localhost:30116/health
----

[source,bash]
.Delete the application, cleaning up Kubernetes resources:
----
kubectl delete -f ./health.yaml
----

=== Summary
This guide demonstrated how to use health-check in a Helidon SE application as follows:

* Access the default health-check
* Create and use custom readiness and liveness checks
* Customize the health-check root path
* Integrate Helidon health-check with Kubernetes

Please refer to the following reference for additional information:

* Helidon Javadoc at https://helidon.io/docs/latest/apidocs/index.html?overview-summary.html

