io.micronaut.discovery.consul.registration.ConsulAutoRegistration Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of micronaut-discovery-client Show documentation
Show all versions of micronaut-discovery-client Show documentation
Adds Service Discovery Features for Eureka and Consul
The 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.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
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.ConsulCheck;
import io.micronaut.discovery.consul.client.v1.ConsulCheckStatus;
import io.micronaut.discovery.consul.client.v1.ConsulClient;
import io.micronaut.discovery.consul.client.v1.ConsulNewServiceEntry;
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.*;
/**
* Auto registration implementation for consul.
*
* @author Graeme Rocher
* @since 1.0
*/
@Singleton
@Requires(beans = {ConsulClient.class, ConsulConfiguration.class})
public class ConsulAutoRegistration extends DiscoveryServiceAutoRegistration {
private static final String DEFAULT_CHECK_STATUS = ConsulCheckStatus.PASSING.toString();
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)) {
String address = address(instance, registration);
Map meta = new HashMap<>(registration.getMeta());
String serviceId = idGenerator.generateId(environment, instance);
ConsulNewServiceEntry serviceEntry = new ConsulNewServiceEntry(
applicationName,
address,
instance.getPort(),
tags(instance, registration),
serviceId,
meta,
createChecks(instance, registration, address));
Publisher registerFlowable = consulClient.register(serviceEntry);
performRegistration("Consul", registration, instance, registerFlowable);
}
}
}
@NonNull
private List tags(@NonNull ServiceInstance instance,
@NonNull ConsulConfiguration.ConsulRegistrationConfiguration registration) {
List tags = new ArrayList<>(registration.getTags());
if (instance instanceof EmbeddedServerInstance embeddedServerInstance) {
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());
}
}
return tags;
}
@NonNull
private List createChecks(@NonNull ServiceInstance instance,
@NonNull ConsulConfiguration.ConsulRegistrationConfiguration registration,
String address) {
ConsulConfiguration.ConsulRegistrationConfiguration.CheckConfiguration checkConfig = registration.getCheck();
if (checkConfig.isEnabled()) {
return Collections.singletonList(createCheck(checkConfig, instance, registration, address));
}
return Collections.emptyList();
}
private String address(@NonNull ServiceInstance instance,
@NonNull ConsulConfiguration.ConsulRegistrationConfiguration registration) {
String address = null;
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);
}
});
}
if (StringUtils.isEmpty(address)) {
address = instance.getHost();
}
return address;
}
private ConsulCheck createCheck(@NonNull ConsulConfiguration.ConsulRegistrationConfiguration.CheckConfiguration checkConfig,
@NonNull ServiceInstance instance,
@NonNull ConsulConfiguration.ConsulRegistrationConfiguration registration,
@Nullable String address) {
ConsulCheck check = new ConsulCheck();
check.setDeregisterCriticalServiceAfter(deregisterCriticalServiceAfterCheck(checkConfig));
checkConfig.getId().ifPresent(check::setId);
check.setStatus(DEFAULT_CHECK_STATUS);
checkConfig.getNotes().ifPresent(check::setNotes);
if (heartbeatConfiguration.isEnabled() && !checkConfig.isHttp()) {
check.setTtl(heartbeatConfiguration.getInterval().plus(Duration.ofSeconds(10)).toSeconds() + "s");
} else {
check.setInterval(checkInternal(checkConfig));
httpCheckUrl(instance, registration, address).ifPresent(check::setHttp);
check.setMethod(checkConfig.getMethod());
checkConfig.getTlsSkipVerify().ifPresent(check::setTlsSkipVerify);
check.setHeader(checkConfig.getHeaders());
}
return check;
}
@Nullable
private String deregisterCriticalServiceAfterCheck(@NonNull ConsulConfiguration.ConsulRegistrationConfiguration.CheckConfiguration checkConfig) {
return checkConfig.getDeregisterCriticalServiceAfter().map(d -> d.toMinutes() + "m").orElse(null);
}
@Nullable
private String checkInternal(@NonNull ConsulConfiguration.ConsulRegistrationConfiguration.CheckConfiguration checkConfig) {
return checkConfig.getInterval().toSeconds() + "s";
}
private Optional httpCheckUrl(@NonNull ServiceInstance instance,
@NonNull ConsulConfiguration.ConsulRegistrationConfiguration registration,
@Nullable String address) {
if (instance instanceof EmbeddedServerInstance embeddedServerInstance) {
EmbeddedServer embeddedServer = embeddedServerInstance.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());
}
}
try {
return Optional.of(new URL(serverURL, registration.getHealthPath().orElse("/health")));
} catch (MalformedURLException e) {
throw new DiscoveryException("Invalid health path configured: " + registration.getHealthPath());
}
}
return Optional.empty();
}
/**
* Allows sub classes to override and customize the configuration.
*
* @param instance The instance
* @param serviceEntry The service entry
* @deprecated no longer used
*/
@Deprecated(forRemoval = true, since = "4.1.0")
protected void customizeServiceEntry(ServiceInstance instance, ConsulNewServiceEntry serviceEntry) {
// no-op
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy