All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.telicent.smart.cache.cli.probes.HealthProbeServer Maven / Gradle / Ivy

Go to download

Provides a minimalist HTTP server that can be run in the background by CLI applications to provide usable liveness and readiness probes for use by container deployments.

There is a newer version: 0.24.2
Show newest version
/**
 * Copyright (C) Telicent Ltd
 *
 * 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 io.telicent.smart.cache.cli.probes;

import io.telicent.smart.cache.cli.probes.resources.ReadinessResource;
import io.telicent.smart.cache.server.jaxrs.applications.AbstractAppEntrypoint;
import io.telicent.smart.cache.server.jaxrs.applications.CorsConfigurationBuilder;
import io.telicent.smart.cache.server.jaxrs.applications.ServerBuilder;
import io.telicent.smart.cache.server.jaxrs.init.ServerRuntimeInfo;
import io.telicent.smart.cache.server.jaxrs.model.HealthStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.*;
import java.util.function.Supplier;

/**
 * A Health Probe Server
 */
public class HealthProbeServer extends AbstractAppEntrypoint {

    private static final Logger LOGGER = LoggerFactory.getLogger(HealthProbeServer.class);

    private final String displayName;
    private final int port;
    private final Supplier readinessSupplier;
    private final String[] libraries;

    // Executor service that has a single thread AND makes that thread a daemon thread so it doesn't prevent application
    // shutdown
    private final ExecutorService executorService = Executors.newFixedThreadPool(1, r -> {
        Thread t = Executors.defaultThreadFactory().newThread(r);
        t.setDaemon(true);
        return t;
    });
    private Future future;

    /**
     * Creates a new Health Probe Server
     *
     * @param displayName       Display Name
     * @param port              Port to run on
     * @param readinessSupplier Supplier that can determine whether the application this server is hosted in is
     *                          currently healthy
     * @param libraries         Library versions to report in the liveness check
     */
    public HealthProbeServer(String displayName, int port, Supplier readinessSupplier,
                             String... libraries) {
        this.displayName = displayName;
        this.port = port;
        this.readinessSupplier = readinessSupplier;
        this.libraries = libraries;
    }

    @Override
    protected ServerBuilder buildServer() {
        return ServerBuilder.create()
                            .application(HealthProbeApplication.class)
                            .withCors(CorsConfigurationBuilder::withDefaults)
                            .displayName(this.displayName)
                            .port(this.port)
                            .allInterfaces()
                            .withListener(ServerRuntimeInfo.class)
                            .withContextAttribute(ReadinessResource.class.getCanonicalName(), this.readinessSupplier)
                            .withMaxThreads(3)
                            .withVersionInfo("cli-probe-server")
                            .withVersionInfo(libraries);
    }

    /**
     * Runs the server and registers a shutdown hook for cleaning up the server
     */
    public void run() {
        if (this.future != null) {
            throw new IllegalStateException("Health Probe Server is already running");
        }
        LOGGER.info("Starting Health Probe Server on port {}", this.port);

        // Launch this on a background daemon thread
        this.future = this.executorService.submit(() -> {
            try {
                Thread.currentThread().setName("HealthProbeServer");
            } catch (Throwable e) {
                // Ignore if unable to set thread name
            }

            this.run(true);
        });

        // Wire up a shutdown hook
        Runtime.getRuntime().addShutdownHook(new Thread(() -> this.shutdown()));
    }

    /**
     * Shuts down the server
     */
    public void shutdown() {
        if (this.future != null) {
            LOGGER.info("Stopping Health Probe Server on port {}", this.port);
            this.future.cancel(true);
            if (this.server != null) {
                this.server.shutdownNow();
            }
            resolveFuture();
        }
    }

    /**
     * Resolves the future that was created when the server was starting by submitting it to the executor service that
     * provides a thread for it to run on
     */
    private void resolveFuture() {
        if (this.future != null) {
            try {
                this.future.get();
            } catch (Throwable e) {
                // Ignore any errors while shutting down
            } finally {
                this.future = null;
            }
        }
    }

    /**
     * Gets the base URL
     *
     * @return Base URL
     */
    public String getBaseUrl() {
        return String.format("http://localhost:%d", this.port);
    }

    /**
     * Gets the liveness probe URL
     *
     * @return Liveness probe URL
     */
    public String getLivenessUrl() {
        return String.format("%s/%s", this.getBaseUrl(), "version-info");
    }

    /**
     * Gets the readiness probe URL
     *
     * @return Readiness probe URL
     */
    public String getReadinessUrl() {
        return String.format("%s/%s", this.getBaseUrl(), "healthz");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy