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

io.micronaut.discovery.consul.registration.ConsulAutoRegistration Maven / Gradle / Ivy

There is a newer version: 4.5.0
Show newest version
/*
 * Copyright 2017-2020 original authors
 *
 * 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
 *
 * https://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.micronaut.discovery.consul.registration;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.convert.value.ConvertibleMultiValues;
import io.micronaut.core.convert.value.ConvertibleValues;
import io.micronaut.core.util.StringUtils;
import io.micronaut.discovery.EmbeddedServerInstance;
import io.micronaut.discovery.ServiceInstance;
import io.micronaut.discovery.ServiceInstanceIdGenerator;
import io.micronaut.discovery.client.registration.DiscoveryServiceAutoRegistration;
import io.micronaut.discovery.consul.ConsulConfiguration;
import io.micronaut.discovery.consul.client.v1.Check;
import io.micronaut.discovery.consul.client.v1.ConsulClient;
import io.micronaut.discovery.consul.client.v1.HTTPCheck;
import io.micronaut.discovery.consul.client.v1.NewCheck;
import io.micronaut.discovery.consul.client.v1.NewServiceEntry;
import io.micronaut.discovery.consul.client.v1.TTLCheck;
import io.micronaut.discovery.exceptions.DiscoveryException;
import io.micronaut.discovery.registration.RegistrationException;
import io.micronaut.health.HealthStatus;
import io.micronaut.health.HeartbeatConfiguration;
import io.micronaut.http.HttpStatus;
import io.micronaut.runtime.ApplicationConfiguration;
import io.micronaut.runtime.server.EmbeddedServer;
import jakarta.inject.Singleton;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Auto registration implementation for consul.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
@Singleton
@Requires(beans = {ConsulClient.class, ConsulConfiguration.class})
public class ConsulAutoRegistration extends DiscoveryServiceAutoRegistration {

    private final ConsulClient consulClient;
    private final HeartbeatConfiguration heartbeatConfiguration;
    private final ConsulConfiguration consulConfiguration;
    private final ServiceInstanceIdGenerator idGenerator;
    private final Environment environment;

    /**
     * @param environment            The environment
     * @param consulClient           The Consul client
     * @param heartbeatConfiguration The heartbeat configuration
     * @param consulConfiguration    The Consul configuration
     * @param idGenerator            The id generator
     */
    protected ConsulAutoRegistration(
        Environment environment,
        ConsulClient consulClient,
        HeartbeatConfiguration heartbeatConfiguration,
        ConsulConfiguration consulConfiguration,
        ServiceInstanceIdGenerator idGenerator) {

        super(consulConfiguration.getRegistration());
        this.environment = environment;
        this.consulClient = consulClient;
        this.heartbeatConfiguration = heartbeatConfiguration;
        this.consulConfiguration = consulConfiguration;
        this.idGenerator = idGenerator;
    }

    @Override
    protected void pulsate(ServiceInstance instance, HealthStatus status) {
        ConsulConfiguration.ConsulRegistrationConfiguration registration = consulConfiguration.getRegistration();
        if (registration != null && !registration.getCheck().isHttp() && registration.getCheck().isEnabled() && registered.get()) {

            String checkId = "service:" + idGenerator.generateId(environment, instance);
            if (LOG.isDebugEnabled()) {
                LOG.debug("Reporting status for Check ID [{}]: {}", checkId, status);
            }

            if (status.equals(HealthStatus.UP)) {
                // send a request to /agent/check/pass/:check_id
                Mono passPublisher = Mono.from(consulClient.pass(checkId));
                passPublisher.subscribe(httpStatus -> {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Successfully reported passing state to Consul");
                    }
                }, throwable -> {

                        // check if the service is still registered with Consul
                        Mono.from(consulClient.getServiceIds()).subscribe(serviceIds -> {
                            String serviceId = idGenerator.generateId(environment, instance);
                            if (!serviceIds.contains(serviceId)) {
                                if (LOG.isInfoEnabled()) {
                                    LOG.info("Instance [{}] no longer registered with Consul. Attempting re-registration.", instance.getId());
                                }
                                register(instance);
                            }
                        });

                        if (LOG.isErrorEnabled()) {
                            LOG.error(getErrorMessage(throwable, "Error reporting passing state to Consul: "), throwable);
                        }
                    });
            } else {
                // send a request to /agent/check/fail/:check_id
                Mono failPublisher = Mono.from(consulClient.fail(checkId, status.getDescription().orElse(null)));
                failPublisher.subscribe(httpStatus -> {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Successfully reported failure state to Consul");
                    }
                }, throwable -> {
                    if (LOG.isErrorEnabled()) {
                        LOG.error(getErrorMessage(throwable, "Error reporting failure state to Consul: "), throwable);
                    }
                });
            }
        }
    }

    @Override
    protected void deregister(ServiceInstance instance) {
        ConsulConfiguration.ConsulRegistrationConfiguration registration = consulConfiguration.getRegistration();
        if (registration != null) {
            String applicationName = instance.getId();
            String serviceId = idGenerator.generateId(environment, instance);
            Publisher deregisterPublisher = consulClient.deregister(serviceId);
            final String discoveryService = "Consul";
            performDeregistration(discoveryService, registration, deregisterPublisher, applicationName);
        }
    }

    @Override
    protected void register(ServiceInstance instance) {
        ConsulConfiguration.ConsulRegistrationConfiguration registration = consulConfiguration.getRegistration();
        if (registration != null) {
            String applicationName = instance.getId();
            validateApplicationName(applicationName);
            if (StringUtils.isNotEmpty(applicationName)) {
                NewServiceEntry serviceEntry = new NewServiceEntry(applicationName);
                List tags = new ArrayList<>(registration.getTags());
                Map meta = new HashMap<>(registration.getMeta());

                String address;
                if (registration.isPreferIpAddress()) {
                    address = registration.getIpAddr().orElseGet(() -> {
                        final String host = instance.getHost();
                        try {
                            final InetAddress inetAddress = InetAddress.getByName(host);
                            return inetAddress.getHostAddress();
                        } catch (UnknownHostException e) {
                            throw new RegistrationException("Failed to lookup IP address for host [" + host + "]: " + e.getMessage(), e);
                        }
                    });
                } else {
                    address = instance.getHost();
                }

                serviceEntry.address(address)
                    .port(instance.getPort())
                    .tags(tags)
                    .meta(meta);

                String serviceId = idGenerator.generateId(environment, instance);
                serviceEntry.id(serviceId);

                if (instance instanceof EmbeddedServerInstance embeddedServerInstance) {
                    NewCheck check = null;
                    ApplicationConfiguration applicationConfiguration = embeddedServerInstance.getEmbeddedServer().getApplicationConfiguration();
                    ApplicationConfiguration.InstanceConfiguration instanceConfiguration = applicationConfiguration.getInstance();
                    instanceConfiguration.getGroup().ifPresent(g -> {
                            validateName(g, "Instance Group");
                            tags.add(ServiceInstance.GROUP + "=" + g);
                        }

                    );
                    instanceConfiguration.getZone().ifPresent(z -> {
                            validateName(z, "Instance Zone");
                            tags.add(ServiceInstance.ZONE + "=" + z);
                        }
                    );

                    // include metadata as tags
                    ConvertibleValues metadata = embeddedServerInstance.getMetadata();
                    for (Map.Entry entry : metadata) {
                        tags.add(entry.getKey() + "=" + entry.getValue());
                    }

                    ConsulConfiguration.ConsulRegistrationConfiguration.CheckConfiguration checkConfig = registration.getCheck();
                    if (checkConfig.isEnabled()) {

                        if (heartbeatConfiguration.isEnabled() && !checkConfig.isHttp()) {
                            TTLCheck ttlCheck = new TTLCheck();
                            ttlCheck.ttl(heartbeatConfiguration.getInterval().plus(Duration.ofSeconds(10)));
                            check = ttlCheck;
                        } else {

                            EmbeddedServer embeddedServer = ((EmbeddedServerInstance) instance).getEmbeddedServer();
                            URL serverURL = embeddedServer.getURL();
                            if (registration.isPreferIpAddress() && address != null) {

                                try {
                                    serverURL = new URL(embeddedServer.getURL().getProtocol(), address, embeddedServer.getPort(), embeddedServer.getURL().getPath());
                                } catch (MalformedURLException e) {
                                    if (LOG.isErrorEnabled()) {
                                        LOG.error("invalid url for health check: {}:{}/{}", embeddedServer.getURL().getProtocol() + address, embeddedServer.getPort(), embeddedServer.getURL().getPath());
                                    }
                                    throw new DiscoveryException("Invalid health path configured: " + registration.getHealthPath());
                                }
                            }

                            HTTPCheck httpCheck;
                            try {
                                httpCheck = new HTTPCheck(
                                    new URL(serverURL, registration.getHealthPath().orElse("/health"))
                                );
                            } catch (MalformedURLException e) {
                                throw new DiscoveryException("Invalid health path configured: " + registration.getHealthPath());
                            }

                            httpCheck.interval(checkConfig.getInterval());
                            httpCheck.method(checkConfig.getMethod())
                                .headers(ConvertibleMultiValues.of(checkConfig.getHeaders()));

                            checkConfig.getTlsSkipVerify().ifPresent(httpCheck::setTLSSkipVerify);
                            check = httpCheck;
                        }
                    }

                    if (check != null) {
                        check.status(Check.Status.PASSING);
                        checkConfig.getDeregisterCriticalServiceAfter().ifPresent(check::deregisterCriticalServiceAfter);
                        checkConfig.getNotes().ifPresent(check::notes);
                        checkConfig.getId().ifPresent(check::id);
                        serviceEntry.check(check);
                    }

                }

                customizeServiceEntry(instance, serviceEntry);
                Publisher registerFlowable = consulClient.register(serviceEntry);
                performRegistration("Consul", registration, instance, registerFlowable);
            }
        }
    }

    /**
     * Allows sub classes to override and customize the configuration.
     *
     * @param instance     The instance
     * @param serviceEntry The service entry
     */
    protected void customizeServiceEntry(ServiceInstance instance, NewServiceEntry serviceEntry) {
        // no-op
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy