Skip to content

Instantly share code, notes, and snippets.

@srbs
Created December 15, 2021 01:26
Show Gist options
  • Save srbs/fc2daa1491d80b2dd30b5ec824f3c010 to your computer and use it in GitHub Desktop.
Save srbs/fc2daa1491d80b2dd30b5ec824f3c010 to your computer and use it in GitHub Desktop.
spring-boot-admin auto-deregister scheduled task when a replacement is online
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
// inspired by: https://github.com/codecentric/spring-boot-admin/issues/535#issuecomment-493010425
// behaves similar to InfoUpdate, but _only_ deregisters when a replacement is online
public class DeregisterOffline {
private static final Logger log = LoggerFactory.getLogger(DeregisterOffline.class);
private final InstanceRepository repository;
public DeregisterOffline(InstanceRepository repository) {
this.repository = repository;
}
public Mono<Void> deregisterCheck(InstanceId id) {
return repository.computeIfPresent(id, (key, instance) -> this.doDeregisterCheck(instance)).then();
}
private Mono<Instance> doDeregisterCheck(Instance instance) {
// only check those that are offline
if (!instance.getStatusInfo().isOffline()) {
return Mono.empty();
}
// look for instances with the same name
return repository.findByName(instance.getRegistration().getName())
// are any online?
.any(it -> !it.getStatusInfo().isOffline())
// if so, deregister given offline instance
.map(result -> result ? this.deregister(instance) : instance);
}
private Instance deregister(Instance instance) {
log.info("Deregistered {}", instance);
return instance.deregister();
}
}
import java.time.Duration;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import org.reactivestreams.Publisher;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// inspired by: https://github.com/codecentric/spring-boot-admin/issues/535#issuecomment-493010425
@Configuration
public class DeregisterOfflineConfiguration {
@Bean(initMethod = "start", destroyMethod = "stop")
@ConditionalOnMissingBean
public DeregisterOfflineTrigger deregisterOfflineTrigger(
DeregisterOffline deregisterOffline,
Publisher<InstanceEvent> events
) {
DeregisterOfflineTrigger trigger = new DeregisterOfflineTrigger(deregisterOffline, events);
trigger.setInterval(Duration.ofMinutes(1));
trigger.setLifetime(Duration.ofMinutes(1));
return trigger;
}
@Bean
@ConditionalOnMissingBean
public DeregisterOffline deregisterOffline(InstanceRepository instanceRepository) {
return new DeregisterOffline(instanceRepository);
}
}
import java.time.Duration;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegisteredEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceRegistrationUpdatedEvent;
import de.codecentric.boot.admin.server.domain.values.InstanceId;
import de.codecentric.boot.admin.server.services.AbstractEventHandler;
import de.codecentric.boot.admin.server.services.IntervalCheck;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import reactor.core.scheduler.Schedulers;
// inspired by: https://github.com/codecentric/spring-boot-admin/issues/535#issuecomment-493010425
// almost identical to InfoUpdateTrigger
public class DeregisterOfflineTrigger extends AbstractEventHandler<InstanceEvent> {
private static final Logger log = LoggerFactory.getLogger(DeregisterOfflineTrigger.class);
private final DeregisterOffline deregisterOffline;
private final IntervalCheck intervalCheck;
public DeregisterOfflineTrigger(
final DeregisterOffline deregisterOffline,
final Publisher<InstanceEvent> publisher
) {
super(publisher, InstanceEvent.class);
this.deregisterOffline = deregisterOffline;
this.intervalCheck = new IntervalCheck("deregister", this::deregisterCheck, Duration.ofMinutes(1), Duration.ofMinutes(1));
}
@Override
protected Publisher<Void> handle(Flux<InstanceEvent> publisher) {
Scheduler scheduler = Schedulers.newSingle("offline-deregister");
return publisher.subscribeOn(scheduler)
.filter(event -> event instanceof InstanceRegisteredEvent ||
event instanceof InstanceRegistrationUpdatedEvent)
.flatMap(event -> this.deregisterCheck(event.getInstance()))
.doFinally(s -> scheduler.dispose());
}
private Mono<Void> deregisterCheck(InstanceId instanceId) {
return deregisterOffline.deregisterCheck(instanceId)
.onErrorResume(e -> {
log.warn("Unexpected error during deregister check for {}", instanceId, e);
return Mono.empty();
})
.doFinally(s -> this.intervalCheck.markAsChecked(instanceId));
}
@Override
public void start() {
super.start();
this.intervalCheck.start();
}
@Override
public void stop() {
super.stop();
this.intervalCheck.stop();
}
public void setInterval(Duration updateInterval) {
this.intervalCheck.setInterval(updateInterval);
}
public void setLifetime(Duration infoLifetime) {
this.intervalCheck.setMinRetention(infoLifetime);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment