Move backoff logic to Updater

This commit is contained in:
Ryan Harg 2024-11-27 10:39:33 +01:00
parent da402b0da9
commit 4b0f3cefb8
5 changed files with 58 additions and 27 deletions

View file

@ -23,10 +23,10 @@ The configuration can contain a list of update targets:
ddnssclient can also be configured using some properties or environment variables ddnssclient can also be configured using some properties or environment variables
| Property | Environment variable | Value | Default | | Property | Environment variable | Value | Default |
| -------- |-----------------------|-----------------------------------|---------| |-----------------------------|------------------------------|-----------------------------------|---------|
| ddnsclient.interval | DDNSCLIENT_INTERVAL | e.g. "3600s", "60m", "1h" | "5m" | | ddnsclient.interval | DDNSCLIENT_INTERVAL | e.g. "3600s", "60m", "1h" | "5m" |
| ddnsclient.ip-provider | DDNSCLIENT_IP_PROVIDER | an ip lookup provider (see below) | null | | ddnsclient.ip-provider | DDNSCLIENT_IP_PROVIDER | an ip lookup provider (see below) | null |
| ddnsclient.dyndns.backup-duration | DDNSCLIENT_DYNDNS_BACKUP_DURATION | e.g. "60s", "1m" | "60s" | | ddnsclient.backoff-duration | DDNS_CLIENT_BACKOFF_DURATION | e.g. "60s", "1m" | "60s" |
## IP lookup ## IP lookup

View file

@ -9,8 +9,14 @@ import de.rpr.ddnsclient.model.DyndnsAuth;
import de.rpr.ddnsclient.model.IPs; import de.rpr.ddnsclient.model.IPs;
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject; import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@ApplicationScoped @ApplicationScoped
public class Updater { public class Updater {
@ -19,15 +25,23 @@ public class Updater {
private final DynDnsRouter dynDnsRouter; private final DynDnsRouter dynDnsRouter;
private final IpProvider ipProvider; private final IpProvider ipProvider;
private final DnsResolver dnsResolver; private final DnsResolver dnsResolver;
private final Duration backoff;
private final Config config; private final Config config;
private final Map<String, LocalDateTime> updateMap = new HashMap<>();
@Inject @Inject
public Updater(DynDnsRouter dynDnsRouter, IpProvider ipProvider, DnsResolver dnsResolver, Config config) { public Updater(DynDnsRouter dynDnsRouter,
IpProvider ipProvider,
DnsResolver dnsResolver,
Config config,
@ConfigProperty(name = "ddnsclient.backoff-duration:300s") Duration backoff) {
this.dynDnsRouter = dynDnsRouter; this.dynDnsRouter = dynDnsRouter;
this.ipProvider = ipProvider; this.ipProvider = ipProvider;
this.dnsResolver = dnsResolver; this.dnsResolver = dnsResolver;
this.config = config; this.config = config;
this.backoff = backoff;
} }
void run() { void run() {
@ -42,6 +56,12 @@ public class Updater {
config.forEach(cfg -> { config.forEach(cfg -> {
if (updateMap.containsKey(cfg.hostname())
&& Duration.between(updateMap.get(cfg.hostname()), LocalDateTime.now()).toSeconds() < backoff.toSeconds()) {
log.debug("Back-off period, skipping update.");
return;
}
log.infof("Handling %s.", cfg.hostname()); log.infof("Handling %s.", cfg.hostname());
IPs registeredIps = dnsResolver.resolve(cfg.hostname()); IPs registeredIps = dnsResolver.resolve(cfg.hostname());
@ -51,6 +71,7 @@ public class Updater {
log.tracef("IPs changed, updating..."); log.tracef("IPs changed, updating...");
DynDns dynDns = dynDnsRouter.get(cfg.provider()); DynDns dynDns = dynDnsRouter.get(cfg.provider());
dynDns.update(cfg.hostname(), publicIps, new DyndnsAuth(null, null, cfg.token())); dynDns.update(cfg.hostname(), publicIps, new DyndnsAuth(null, null, cfg.token()));
updateMap.put(cfg.hostname(), LocalDateTime.now());
} else { } else {
log.infof("Hostname is up-to-date.", cfg.hostname()); log.infof("Hostname is up-to-date.", cfg.hostname());
} }

View file

@ -22,11 +22,6 @@ public class Ddnss implements DynDns {
private static final Logger log = Logger.getLogger(Ddnss.class); private static final Logger log = Logger.getLogger(Ddnss.class);
@ConfigProperty(name = "ddnsclient.dyndns.backoff-duration")
Duration backoff;
private final Map<String, LocalDateTime> updateMap = new HashMap<>();
@Override @Override
public String name() { public String name() {
return "ddnss"; return "ddnss";
@ -34,12 +29,6 @@ public class Ddnss implements DynDns {
public void update(String hostname, IPs currentIps, DyndnsAuth auth) { public void update(String hostname, IPs currentIps, DyndnsAuth auth) {
if (updateMap.containsKey(hostname)
&& Duration.between(updateMap.get(hostname), LocalDateTime.now()).toSeconds() < backoff.toSeconds()) {
log.debug("Back-off period, skipping update.");
return;
}
String updateUrl = MessageFormat.format("https://ddnss.de/upd.php?key={0}&host={1}&ip={2}&ip6={3}", String updateUrl = MessageFormat.format("https://ddnss.de/upd.php?key={0}&host={1}&ip={2}&ip6={3}",
auth.token(), hostname, currentIps.v4(), currentIps.v6()); auth.token(), hostname, currentIps.v4(), currentIps.v6());
@ -55,7 +44,6 @@ public class Ddnss implements DynDns {
if (response.statusCode() != 200) { if (response.statusCode() != 200) {
log.errorf("Couldn't update hostname %s." + hostname); log.errorf("Couldn't update hostname %s." + hostname);
} }
updateMap.put(hostname, LocalDateTime.now());
log.info("Hostname updated."); log.info("Hostname updated.");
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View file

@ -2,7 +2,3 @@ quarkus.naming.enable-jndi=true
quarkus.log.level=INFO quarkus.log.level=INFO
quarkus.log.category."de.rpr.ddnsclient".min-level=TRACE quarkus.log.category."de.rpr.ddnsclient".min-level=TRACE
quarkus.log.category."de.rpr.ddnsclient".level=INFO quarkus.log.category."de.rpr.ddnsclient".level=INFO
ddnsclient.interval=15s
#ddnsclient.ip-provider=
ddnsclient.dyndns.backoff-duration=60s

View file

@ -15,6 +15,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import java.time.Duration;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -34,10 +35,12 @@ class UpdaterTest {
add(new Value("ddnss", "example.org", null, null, "token")); add(new Value("ddnss", "example.org", null, null, "token"));
}}; }};
Duration backoff = Duration.ofMinutes(5);
@Test @Test
void should_throw_exception_if_config_is_empty() { void should_throw_exception_if_config_is_empty() {
Config config = new Config(); Config config = new Config();
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, config); Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, config, backoff);
Assertions.assertThrows(IllegalStateException.class, updater::run); Assertions.assertThrows(IllegalStateException.class, updater::run);
} }
@ -53,7 +56,7 @@ class UpdaterTest {
add(new Value("ddnss", "host2.example.org", null, null, "token")); add(new Value("ddnss", "host2.example.org", null, null, "token"));
}}; }};
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, multipleConfigEntries); Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, multipleConfigEntries, backoff);
updater.run(); updater.run();
ArgumentCaptor<String> hostnameCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> hostnameCaptor = ArgumentCaptor.forClass(String.class);
@ -71,7 +74,7 @@ class UpdaterTest {
when(ipProvider.getPublicIps()).thenReturn(ips); when(ipProvider.getPublicIps()).thenReturn(ips);
when(dnsResolver.resolve("example.org")).thenReturn(ips); when(dnsResolver.resolve("example.org")).thenReturn(ips);
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig); Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff);
updater.run(); updater.run();
verify(dynDnsRouter, never()).get(any()); verify(dynDnsRouter, never()).get(any());
@ -89,7 +92,7 @@ class UpdaterTest {
when(ipProvider.getPublicIps()).thenReturn(publicIps); when(ipProvider.getPublicIps()).thenReturn(publicIps);
when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6")); when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig); Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff);
updater.run(); updater.run();
verify(ddnss).update( verify(ddnss).update(
@ -99,4 +102,27 @@ class UpdaterTest {
); );
} }
@Test
void should_not_try_to_update_hostname_within_configured_backoff_duration() {
DynDns ddnss = mock(Ddnss.class);
when(dynDnsRouter.get("ddnss")).thenReturn(ddnss);
IPs publicIps = new IPs("ipv4", "ipv6");
when(ipProvider.getPublicIps()).thenReturn(publicIps);
when(dnsResolver.resolve("example.org")).thenReturn(new IPs("registered_ipv4", "registered_ipv6"));
Updater updater = new Updater(dynDnsRouter, ipProvider, dnsResolver, ddnssConfig, backoff);
updater.run();
updater.run();
verify(ddnss, times(1)).update(
"example.org",
publicIps,
new DyndnsAuth(null, null, "token")
);
}
} }