mirror of
https://github.com/google/nomulus
synced 2026-07-11 18:42:25 +00:00
Project domains and hosts on cache retrieval (#3143)
It's not perfect because we're running outside of a transaction, but we should make a best-effort attempt to project domains/hosts to the current time.
This commit is contained in:
@@ -38,6 +38,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
@@ -106,12 +107,16 @@ public final class CacheModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
public static HostCache provideHostCache(
|
||||
Optional<SimplifiedJedisClient> jedisClient, CacheMetrics cacheMetrics) {
|
||||
Optional<SimplifiedJedisClient> jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
if (jedisClient.isEmpty()) {
|
||||
return repoId ->
|
||||
Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)));
|
||||
return repoId -> {
|
||||
Instant now = clock.now();
|
||||
return Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)))
|
||||
.filter(host -> now.isBefore(host.getDeletionTime()))
|
||||
.map(host -> (Host) host.cloneProjectedAtTime(now));
|
||||
};
|
||||
}
|
||||
return new MultilayerHostCache(jedisClient.get(), cacheMetrics);
|
||||
return new MultilayerHostCache(jedisClient.get(), clock, cacheMetrics);
|
||||
}
|
||||
|
||||
private static SSLSocketFactory createValkeySslSocketFactory(String valkeyCertificateAuthority) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.util.Clock;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -30,12 +29,9 @@ import java.util.Optional;
|
||||
public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
|
||||
implements DomainCache {
|
||||
|
||||
private final Clock clock;
|
||||
|
||||
public MultilayerDomainCache(
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, cacheMetrics);
|
||||
this.clock = clock;
|
||||
super(jedisClient, clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,15 +42,10 @@ public class MultilayerDomainCache extends MultilayerEppResourceCache<Domain>
|
||||
@Override
|
||||
protected Optional<Domain> loadFromDatabase(String domainName) {
|
||||
// Don't use the cache (avoid caching the same domain twice). Do use the replica SQL instance.
|
||||
Optional<Domain> possibleDomain =
|
||||
Optional.ofNullable(
|
||||
ForeignKeyUtils.loadMostRecentResourceObjects(
|
||||
Domain.class, ImmutableList.of(domainName), true)
|
||||
.get(domainName));
|
||||
Instant now = clock.now();
|
||||
return possibleDomain
|
||||
.filter(domain -> now.isBefore(domain.getDeletionTime()))
|
||||
.map(domain -> domain.cloneProjectedAtTime(now));
|
||||
return Optional.ofNullable(
|
||||
ForeignKeyUtils.loadMostRecentResourceObjects(
|
||||
Domain.class, ImmutableList.of(domainName), true)
|
||||
.get(domainName));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,7 +18,9 @@ import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.util.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -36,11 +38,13 @@ public abstract class MultilayerEppResourceCache<V extends EppResource> {
|
||||
.build();
|
||||
|
||||
private final SimplifiedJedisClient jedisClient;
|
||||
private final Clock clock;
|
||||
private final CacheMetrics cacheMetrics;
|
||||
|
||||
protected MultilayerEppResourceCache(
|
||||
SimplifiedJedisClient jedisClient, CacheMetrics cacheMetrics) {
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
this.jedisClient = jedisClient;
|
||||
this.clock = clock;
|
||||
this.cacheMetrics = cacheMetrics;
|
||||
}
|
||||
|
||||
@@ -50,7 +54,16 @@ public abstract class MultilayerEppResourceCache<V extends EppResource> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Optional<V> loadFromCaches(Class<V> clazz, String key) {
|
||||
Instant now = clock.now();
|
||||
return (Optional<V>)
|
||||
loadFromCachesInternal(clazz, key)
|
||||
.filter(v -> now.isBefore(v.getDeletionTime()))
|
||||
.map(v -> v.cloneProjectedAtTime(now));
|
||||
}
|
||||
|
||||
private Optional<V> loadFromCachesInternal(Class<V> clazz, String key) {
|
||||
// hopefully the resource is in the local cache
|
||||
Optional<V> possibleValue = Optional.ofNullable(localCache.getIfPresent(key));
|
||||
if (possibleValue.isPresent()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Clock;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -27,8 +28,9 @@ import java.util.Optional;
|
||||
*/
|
||||
public class MultilayerHostCache extends MultilayerEppResourceCache<Host> implements HostCache {
|
||||
|
||||
public MultilayerHostCache(SimplifiedJedisClient jedisClient, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, cacheMetrics);
|
||||
public MultilayerHostCache(
|
||||
SimplifiedJedisClient jedisClient, Clock clock, CacheMetrics cacheMetrics) {
|
||||
super(jedisClient, clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,11 +24,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -100,4 +103,39 @@ public class MultilayerDomainCacheTest {
|
||||
verify(cacheMetrics).recordLookup("Domain", CacheMetrics.CacheHitType.MISS_NONEXISTENT);
|
||||
verifyNoMoreInteractions(cacheMetrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_filtersOutDeletedDomain() {
|
||||
Domain domain =
|
||||
persistActiveDomain("example.tld")
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.now().plus(Duration.ofDays(1)))
|
||||
.build();
|
||||
when(jedisClient.get(Domain.class, "example.tld")).thenReturn(Optional.of(domain));
|
||||
assertThat(cache.loadByDomainName("example.tld")).hasValue(domain);
|
||||
|
||||
clock.advanceBy(Duration.ofDays(2));
|
||||
assertThat(cache.loadByDomainName("example.tld")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_projectsToCurrentTime() {
|
||||
Domain domain =
|
||||
persistActiveDomain("example.tld")
|
||||
.asBuilder()
|
||||
.addGracePeriod(
|
||||
GracePeriod.create(
|
||||
GracePeriodStatus.ADD,
|
||||
"example.tld",
|
||||
clock.now().plus(Duration.ofDays(5)),
|
||||
"TheRegistrar",
|
||||
null))
|
||||
.build();
|
||||
when(jedisClient.get(Domain.class, "example.tld")).thenReturn(Optional.of(domain));
|
||||
assertThat(cache.loadByDomainName("example.tld").get().getGracePeriods())
|
||||
.containsExactlyElementsIn(domain.getGracePeriods());
|
||||
|
||||
clock.advanceBy(Duration.ofDays(10));
|
||||
assertThat(cache.loadByDomainName("example.tld").get().getGracePeriods()).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import google.registry.testing.FakeClock;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -38,12 +40,13 @@ public class MultilayerHostCacheTest {
|
||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
||||
|
||||
private final SimplifiedJedisClient jedisClient = mock(SimplifiedJedisClient.class);
|
||||
private final FakeClock clock = new FakeClock();
|
||||
private final CacheMetrics cacheMetrics = mock(CacheMetrics.class);
|
||||
private MultilayerHostCache cache;
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
cache = new MultilayerHostCache(jedisClient, cacheMetrics);
|
||||
cache = new MultilayerHostCache(jedisClient, clock, cacheMetrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -80,4 +83,18 @@ public class MultilayerHostCacheTest {
|
||||
verify(cacheMetrics).recordLookup("Host", CacheMetrics.CacheHitType.MISS_NONEXISTENT);
|
||||
verifyNoMoreInteractions(cacheMetrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad_filtersOutDeletedHost() {
|
||||
Host host =
|
||||
persistActiveHost("ns1.example.tld")
|
||||
.asBuilder()
|
||||
.setDeletionTime(clock.now().plus(Duration.ofDays(1)))
|
||||
.build();
|
||||
when(jedisClient.get(Host.class, host.getRepoId())).thenReturn(Optional.of(host));
|
||||
assertThat(cache.loadByRepoId(host.getRepoId())).hasValue(host);
|
||||
|
||||
clock.advanceBy(Duration.ofDays(2));
|
||||
assertThat(cache.loadByRepoId(host.getRepoId())).isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,10 @@ class RdapTestHelper {
|
||||
"We reserve the right to modify this agreement at any time.");
|
||||
rdapJsonFormatter.rdapTosStaticUrl = "https://www.example.tld/about/rdap/tos.html";
|
||||
rdapJsonFormatter.hostCache =
|
||||
(repoId) -> Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)));
|
||||
(repoId) ->
|
||||
Optional.ofNullable(EppResource.loadByCache(VKey.create(Host.class, repoId)))
|
||||
.filter(host -> clock.now().isBefore(host.getDeletionTime()))
|
||||
.map(host -> (Host) host.cloneProjectedAtTime(clock.now()));
|
||||
return rdapJsonFormatter;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user