mirror of
https://github.com/google/nomulus.git
synced 2025-07-27 13:06:27 +02:00
Refactor ForeignKeyIndex into ForeignKeyUtils (#1783)
The old class is modeled after datastore with some logic jammed in for it to work with SQL as well. As of #1777, the ofy related logic is deleted, however the general structure of the class remained datastore oriented. This PR refactors the existing class into a ForeignKeyUtils helper class that does away wit the index subclasses and provides static helper methods to do the same, in a SQL-idiomatic fashion. Some minor changes are made to the EPP resource classes to make it possible to create them in a SQL only environment in tests.
This commit is contained in:
parent
775f672f2a
commit
c4c1c72306
31 changed files with 487 additions and 573 deletions
|
@ -44,8 +44,8 @@ import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
|
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
|
||||||
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
|
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.tld.Registry;
|
import google.registry.model.tld.Registry;
|
||||||
import google.registry.model.tld.label.ReservationType;
|
import google.registry.model.tld.label.ReservationType;
|
||||||
import google.registry.monitoring.whitebox.CheckApiMetric;
|
import google.registry.monitoring.whitebox.CheckApiMetric;
|
||||||
|
@ -156,7 +156,7 @@ public class CheckApiAction implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkExists(String domainString, DateTime now) {
|
private boolean checkExists(String domainString, DateTime now) {
|
||||||
return !ForeignKeyIndex.loadCached(Domain.class, ImmutableList.of(domainString), now).isEmpty();
|
return !ForeignKeyUtils.loadCached(Domain.class, ImmutableList.of(domainString), now).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<String> checkReserved(InternetDomainName domainName) {
|
private Optional<String> checkReserved(InternetDomainName domainName) {
|
||||||
|
|
|
@ -17,7 +17,6 @@ package google.registry.flows;
|
||||||
import static com.google.common.collect.Sets.intersection;
|
import static com.google.common.collect.Sets.intersection;
|
||||||
import static google.registry.model.EppResourceUtils.isLinked;
|
import static google.registry.model.EppResourceUtils.isLinked;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -38,6 +37,7 @@ import google.registry.flows.exceptions.TooManyResourceChecksException;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.contact.Contact;
|
import google.registry.model.contact.Contact;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
|
@ -45,7 +45,6 @@ import google.registry.model.domain.Period;
|
||||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||||
import google.registry.model.eppcommon.AuthInfo;
|
import google.registry.model.eppcommon.AuthInfo;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.transfer.TransferStatus;
|
import google.registry.model.transfer.TransferStatus;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -70,22 +69,17 @@ public final class ResourceFlowUtils {
|
||||||
/**
|
/**
|
||||||
* Check whether if there are domains linked to the resource to be deleted. Throws an exception if
|
* Check whether if there are domains linked to the resource to be deleted. Throws an exception if
|
||||||
* so.
|
* so.
|
||||||
*
|
|
||||||
* <p>Note that in datastore this is a smoke test as the query for linked domains is eventually
|
|
||||||
* consistent, so we only check a few domains to fail fast.
|
|
||||||
*/
|
*/
|
||||||
public static <R extends EppResource> void checkLinkedDomains(
|
public static <R extends EppResource> void checkLinkedDomains(
|
||||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||||
EppException failfastException =
|
EppException failfastException =
|
||||||
tm().transact(
|
tm().transact(
|
||||||
() -> {
|
() -> {
|
||||||
final ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
|
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||||
if (fki == null) {
|
if (key == null) {
|
||||||
return new ResourceDoesNotExistException(resourceClass, targetId);
|
return new ResourceDoesNotExistException(resourceClass, targetId);
|
||||||
}
|
}
|
||||||
return isLinked(fki.getResourceKey(), now)
|
return isLinked(key, now) ? new ResourceToDeleteIsReferencedException() : null;
|
||||||
? new ResourceToDeleteIsReferencedException()
|
|
||||||
: null;
|
|
||||||
});
|
});
|
||||||
if (failfastException != null) {
|
if (failfastException != null) {
|
||||||
throw failfastException;
|
throw failfastException;
|
||||||
|
@ -118,7 +112,7 @@ public final class ResourceFlowUtils {
|
||||||
|
|
||||||
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
||||||
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
|
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
|
||||||
VKey<R> key = loadAndGetKey(clazz, targetId, now);
|
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
R resource = tm().loadByKey(key);
|
R resource = tm().loadByKey(key);
|
||||||
// These are similar exceptions, but we can track them internally as log-based metrics.
|
// These are similar exceptions, but we can track them internally as log-based metrics.
|
||||||
|
|
|
@ -53,6 +53,7 @@ import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseRet
|
||||||
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
|
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
|
||||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.billing.BillingEvent;
|
import google.registry.model.billing.BillingEvent;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.domain.DomainCommand.Check;
|
import google.registry.model.domain.DomainCommand.Check;
|
||||||
|
@ -70,7 +71,6 @@ import google.registry.model.eppoutput.CheckData.DomainCheck;
|
||||||
import google.registry.model.eppoutput.CheckData.DomainCheckData;
|
import google.registry.model.eppoutput.CheckData.DomainCheckData;
|
||||||
import google.registry.model.eppoutput.EppResponse;
|
import google.registry.model.eppoutput.EppResponse;
|
||||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||||
import google.registry.model.tld.Registry;
|
import google.registry.model.tld.Registry;
|
||||||
import google.registry.model.tld.Registry.TldState;
|
import google.registry.model.tld.Registry.TldState;
|
||||||
|
@ -169,8 +169,8 @@ public final class DomainCheckFlow implements Flow {
|
||||||
// TODO: Use as of date from fee extension v0.12 instead of now, if specified.
|
// TODO: Use as of date from fee extension v0.12 instead of now, if specified.
|
||||||
.setAsOfDate(now)
|
.setAsOfDate(now)
|
||||||
.build());
|
.build());
|
||||||
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains =
|
ImmutableMap<String, VKey<Domain>> existingDomains =
|
||||||
ForeignKeyIndex.load(Domain.class, domainNames, now);
|
ForeignKeyUtils.load(Domain.class, domainNames, now);
|
||||||
Optional<AllocationTokenExtension> allocationTokenExtension =
|
Optional<AllocationTokenExtension> allocationTokenExtension =
|
||||||
eppInput.getSingleExtension(AllocationTokenExtension.class);
|
eppInput.getSingleExtension(AllocationTokenExtension.class);
|
||||||
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
|
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
|
||||||
|
@ -227,7 +227,7 @@ public final class DomainCheckFlow implements Flow {
|
||||||
|
|
||||||
private Optional<String> getMessageForCheck(
|
private Optional<String> getMessageForCheck(
|
||||||
InternetDomainName domainName,
|
InternetDomainName domainName,
|
||||||
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains,
|
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||||
ImmutableMap<InternetDomainName, String> tokenCheckResults,
|
ImmutableMap<InternetDomainName, String> tokenCheckResults,
|
||||||
ImmutableMap<String, TldState> tldStates,
|
ImmutableMap<String, TldState> tldStates,
|
||||||
Optional<AllocationToken> allocationToken) {
|
Optional<AllocationToken> allocationToken) {
|
||||||
|
@ -251,7 +251,7 @@ public final class DomainCheckFlow implements Flow {
|
||||||
/** Handle the fee check extension. */
|
/** Handle the fee check extension. */
|
||||||
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
|
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
|
||||||
ImmutableMap<String, InternetDomainName> domainNames,
|
ImmutableMap<String, InternetDomainName> domainNames,
|
||||||
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains,
|
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||||
ImmutableSet<String> availableDomains,
|
ImmutableSet<String> availableDomains,
|
||||||
DateTime now,
|
DateTime now,
|
||||||
Optional<AllocationToken> allocationToken)
|
Optional<AllocationToken> allocationToken)
|
||||||
|
@ -297,14 +297,14 @@ public final class DomainCheckFlow implements Flow {
|
||||||
* renewal is part of the cost of a restore.
|
* renewal is part of the cost of a restore.
|
||||||
*
|
*
|
||||||
* <p>This may be resource-intensive for large checks of many restore fees, but those are
|
* <p>This may be resource-intensive for large checks of many restore fees, but those are
|
||||||
* comparatively rare, and we are at least using an in-memory cache. Also this will get a lot
|
* comparatively rare, and we are at least using an in-memory cache. Also, this will get a lot
|
||||||
* nicer in Cloud SQL when we can SELECT just the fields we want rather than having to load the
|
* nicer in Cloud SQL when we can SELECT just the fields we want rather than having to load the
|
||||||
* entire entity.
|
* entire entity.
|
||||||
*/
|
*/
|
||||||
private ImmutableMap<String, Domain> loadDomainsForRestoreChecks(
|
private ImmutableMap<String, Domain> loadDomainsForRestoreChecks(
|
||||||
FeeCheckCommandExtension<?, ?> feeCheck,
|
FeeCheckCommandExtension<?, ?> feeCheck,
|
||||||
ImmutableMap<String, InternetDomainName> domainNames,
|
ImmutableMap<String, InternetDomainName> domainNames,
|
||||||
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains) {
|
ImmutableMap<String, VKey<Domain>> existingDomains) {
|
||||||
ImmutableList<String> restoreCheckDomains;
|
ImmutableList<String> restoreCheckDomains;
|
||||||
if (feeCheck instanceof FeeCheckCommandExtensionV06) {
|
if (feeCheck instanceof FeeCheckCommandExtensionV06) {
|
||||||
// The V06 fee extension supports specifying the command fees to check on a per-domain basis.
|
// The V06 fee extension supports specifying the command fees to check on a per-domain basis.
|
||||||
|
@ -329,7 +329,7 @@ public final class DomainCheckFlow implements Flow {
|
||||||
ImmutableMap<String, VKey<Domain>> existingDomainsToLoad =
|
ImmutableMap<String, VKey<Domain>> existingDomainsToLoad =
|
||||||
restoreCheckDomains.stream()
|
restoreCheckDomains.stream()
|
||||||
.filter(existingDomains::containsKey)
|
.filter(existingDomains::containsKey)
|
||||||
.collect(toImmutableMap(d -> d, d -> existingDomains.get(d).getResourceKey()));
|
.collect(toImmutableMap(d -> d, existingDomains::get));
|
||||||
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
|
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
|
||||||
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
|
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
|
||||||
return ImmutableMap.copyOf(
|
return ImmutableMap.copyOf(
|
||||||
|
|
|
@ -26,7 +26,6 @@ import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain
|
||||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
|
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
|
||||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
||||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
|
||||||
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
|
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||||
|
@ -46,6 +45,7 @@ import google.registry.flows.TransactionalFlow;
|
||||||
import google.registry.flows.annotations.ReportingSpec;
|
import google.registry.flows.annotations.ReportingSpec;
|
||||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.domain.metadata.MetadataExtension;
|
import google.registry.model.domain.metadata.MetadataExtension;
|
||||||
|
@ -147,7 +147,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||||
verifyUpdateAllowed(
|
verifyUpdateAllowed(
|
||||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||||
if (isHostRename && loadAndGetKey(Host.class, newHostName, now) != null) {
|
if (isHostRename && ForeignKeyUtils.load(Host.class, newHostName, now) != null) {
|
||||||
throw new HostAlreadyExistsException(newHostName);
|
throw new HostAlreadyExistsException(newHostName);
|
||||||
}
|
}
|
||||||
AddRemove add = command.getInnerAdd();
|
AddRemove add = command.getInnerAdd();
|
||||||
|
|
|
@ -36,7 +36,6 @@ import google.registry.model.contact.Contact;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.model.reporting.HistoryEntryDao;
|
import google.registry.model.reporting.HistoryEntryDao;
|
||||||
import google.registry.model.tld.Registry;
|
import google.registry.model.tld.Registry;
|
||||||
|
@ -117,20 +116,19 @@ public final class EppResourceUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the last created version of an {@link EppResource} from Datastore by foreign key, using a
|
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||||
* cache.
|
* using a cache.
|
||||||
*
|
*
|
||||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||||
* created resource was deleted before time "now".
|
* created resource was deleted before time "now".
|
||||||
*
|
*
|
||||||
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
||||||
* it may have various expirable conditions and status values that might implicitly change its
|
* it may have various expirable conditions and status values that might implicitly change its
|
||||||
* state as time progresses even if it has not been updated in Datastore. Rather, the resource
|
* state as time progresses even if it has not been updated in the database. Rather, the resource
|
||||||
* must be combined with a timestamp to view its current state. We use a global last updated
|
* must be combined with a timestamp to view its current state. We use a global last updated
|
||||||
* timestamp on the resource's entity group (which is essentially free since all writes to the
|
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||||
* entity group must be serialized anyways) to guarantee monotonically increasing write times, and
|
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||||
* forward our projected time to the greater of this timestamp or "now". This guarantees that
|
* past.
|
||||||
* we're not projecting into the past.
|
|
||||||
*
|
*
|
||||||
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
||||||
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
|
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
|
||||||
|
@ -150,19 +148,18 @@ public final class EppResourceUtils {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
|
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
|
||||||
"loadByForeignKey may only be called for foreign keyed EPP resources");
|
"loadByForeignKey may only be called for foreign keyed EPP resources");
|
||||||
ForeignKeyIndex<T> fki =
|
VKey<T> key =
|
||||||
useCache
|
useCache
|
||||||
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
|
? ForeignKeyUtils.loadCached(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
|
||||||
.getOrDefault(foreignKey, null)
|
: ForeignKeyUtils.load(clazz, foreignKey, now);
|
||||||
: ForeignKeyIndex.load(clazz, foreignKey, now);
|
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
|
||||||
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
|
if (key == null) {
|
||||||
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
T resource =
|
T resource =
|
||||||
useCache
|
useCache
|
||||||
? EppResource.loadCached(fki.getResourceKey())
|
? EppResource.loadCached(key)
|
||||||
: tm().transact(() -> tm().loadByKeyIfPresent(fki.getResourceKey()).orElse(null));
|
: tm().transact(() -> tm().loadByKeyIfPresent(key).orElse(null));
|
||||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
@ -178,7 +175,7 @@ public final class EppResourceUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks multiple {@link EppResource} objects from Datastore by unique ids.
|
* Checks multiple {@link EppResource} objects from the database by unique ids.
|
||||||
*
|
*
|
||||||
* <p>There are currently no resources that support checks and do not use foreign keys. If we need
|
* <p>There are currently no resources that support checks and do not use foreign keys. If we need
|
||||||
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
|
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
|
||||||
|
@ -190,7 +187,7 @@ public final class EppResourceUtils {
|
||||||
*/
|
*/
|
||||||
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
|
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
|
||||||
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
|
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
|
||||||
return ForeignKeyIndex.load(clazz, uniqueIds, now).keySet();
|
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
236
core/src/main/java/google/registry/model/ForeignKeyUtils.java
Normal file
236
core/src/main/java/google/registry/model/ForeignKeyUtils.java
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// http://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 google.registry.model;
|
||||||
|
|
||||||
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
|
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||||
|
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||||
|
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||||
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Streams;
|
||||||
|
import google.registry.config.RegistryConfig;
|
||||||
|
import google.registry.model.contact.Contact;
|
||||||
|
import google.registry.model.domain.Domain;
|
||||||
|
import google.registry.model.host.Host;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
|
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||||
|
import google.registry.util.NonFinalForTesting;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util class to map a foreign key to the {@link VKey} to the active instance of {@link EppResource}
|
||||||
|
* whose unique repoId matches the foreign key string at a given time. The instance is never
|
||||||
|
* deleted, but it is updated if a newer entity becomes the active entity.
|
||||||
|
*/
|
||||||
|
public final class ForeignKeyUtils {
|
||||||
|
|
||||||
|
private ForeignKeyUtils() {}
|
||||||
|
|
||||||
|
private static final ImmutableMap<Class<? extends EppResource>, String>
|
||||||
|
RESOURCE_TYPE_TO_FK_PROPERTY =
|
||||||
|
ImmutableMap.of(
|
||||||
|
Contact.class, "contactId",
|
||||||
|
Domain.class, "fullyQualifiedDomainName",
|
||||||
|
Host.class, "fullyQualifiedHostName");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||||
|
*
|
||||||
|
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||||
|
* created resource was deleted before time "now".
|
||||||
|
*
|
||||||
|
* @param clazz the resource type to load
|
||||||
|
* @param foreignKey foreign key to match
|
||||||
|
* @param now the current logical time to use when checking for soft deletion of the foreign key
|
||||||
|
* index
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static <E extends EppResource> VKey<E> load(
|
||||||
|
Class<E> clazz, String foreignKey, DateTime now) {
|
||||||
|
return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a map of {@link String} foreign keys to {@link VKey}s to {@link EppResource} that are
|
||||||
|
* active at or after the specified moment in time.
|
||||||
|
*
|
||||||
|
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
|
||||||
|
* or has been soft deleted.
|
||||||
|
*/
|
||||||
|
public static <E extends EppResource> ImmutableMap<String, VKey<E>> load(
|
||||||
|
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||||
|
return load(clazz, foreignKeys, false).entrySet().stream()
|
||||||
|
.filter(e -> now.isBefore(e.getValue().deletionTime()))
|
||||||
|
.collect(toImmutableMap(Entry::getKey, e -> VKey.createSql(clazz, e.getValue().repoId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to load {@link VKey}s to all the most recent {@link EppResource}s for the given
|
||||||
|
* foreign keys, regardless of whether or not they have been soft-deleted.
|
||||||
|
*
|
||||||
|
* <p>Used by both the cached (w/o deletion check) and the non-cached (with deletion check) calls.
|
||||||
|
*
|
||||||
|
* <p>Note that in production, the {@code deletionTime} for entities with the same foreign key
|
||||||
|
* should monotonically increase as one cannot create a domain/host/contact with the same foreign
|
||||||
|
* key without soft deleting the existing resource first. However, in test, there's no such
|
||||||
|
* guarantee and one must make sure that no two resources with the same foreign key exist with the
|
||||||
|
* same max {@code deleteTime}, usually {@code END_OF_TIME}, lest this method throws an error due
|
||||||
|
* to duplicate keys.
|
||||||
|
*/
|
||||||
|
private static <E extends EppResource> ImmutableMap<String, MostRecentResource> load(
|
||||||
|
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
|
||||||
|
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||||
|
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
||||||
|
return jpaTmToUse.transact(
|
||||||
|
() ->
|
||||||
|
jpaTmToUse
|
||||||
|
.query(
|
||||||
|
("SELECT %fkProperty%, repoId, deletionTime FROM %entity% WHERE (%fkProperty%,"
|
||||||
|
+ " deletionTime) IN (SELECT %fkProperty%, MAX(deletionTime) FROM"
|
||||||
|
+ " %entity% WHERE %fkProperty% IN (:fks) GROUP BY %fkProperty%)")
|
||||||
|
.replace("%fkProperty%", fkProperty)
|
||||||
|
.replace("%entity%", clazz.getSimpleName()),
|
||||||
|
Object[].class)
|
||||||
|
.setParameter("fks", foreignKeys)
|
||||||
|
.getResultStream()
|
||||||
|
.collect(
|
||||||
|
toImmutableMap(
|
||||||
|
row -> (String) row[0],
|
||||||
|
row -> MostRecentResource.create((String) row[1], (DateTime) row[2]))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||||
|
CACHE_LOADER =
|
||||||
|
new CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<MostRecentResource> load(VKey<? extends EppResource> key) {
|
||||||
|
return loadAll(ImmutableList.of(key)).get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<VKey<? extends EppResource>, Optional<MostRecentResource>> loadAll(
|
||||||
|
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||||
|
if (!keys.iterator().hasNext()) {
|
||||||
|
return ImmutableMap.of();
|
||||||
|
}
|
||||||
|
// It is safe to use the resource type of first element because when this function is
|
||||||
|
// called, it is always passed with a list of VKeys with the same type.
|
||||||
|
Class<? extends EppResource> clazz = keys.iterator().next().getKind();
|
||||||
|
ImmutableList<String> foreignKeys =
|
||||||
|
Streams.stream(keys)
|
||||||
|
.map(key -> (String) key.getSqlKey())
|
||||||
|
.collect(toImmutableList());
|
||||||
|
ImmutableMap<String, MostRecentResource> existingKeys =
|
||||||
|
ForeignKeyUtils.load(clazz, foreignKeys, true);
|
||||||
|
// The above map only contains keys that exist in the database, so we re-add the
|
||||||
|
// missing ones with Optional.empty() values for caching.
|
||||||
|
return Maps.asMap(
|
||||||
|
ImmutableSet.copyOf(keys),
|
||||||
|
key -> Optional.ofNullable(existingKeys.get((String) key.getSqlKey())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A limited size, limited time cache for foreign-keyed entities.
|
||||||
|
*
|
||||||
|
* <p>This is only used to cache foreign-keyed entities for the purposes of checking whether they
|
||||||
|
* exist (and if so, what entity they point to) during a few domain flows. Any other operations on
|
||||||
|
* foreign keys should not use this cache.
|
||||||
|
*
|
||||||
|
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||||
|
* EppResource>}, but they are not legal {VKey}s to {@link EppResource}s, whose keys are the SQL
|
||||||
|
* primary keys, i.e. the {@code repoId}s. Instead, their keys are the foreign keys used to query
|
||||||
|
* the database. We use {@link VKey} here because it is a convenient composite class that contains
|
||||||
|
* both the resource type and the foreign key, which are needed to for the query and caching.
|
||||||
|
*
|
||||||
|
* <p>Also note that the value type of this cache is {@link Optional} because the foreign keys in
|
||||||
|
* question are coming from external commands, and thus don't necessarily represent entities in
|
||||||
|
* our system that actually exist. So we cache the fact that they *don't* exist by using
|
||||||
|
* Optional.empty(), then several layers up the EPP command will fail with an error message like
|
||||||
|
* "The contact with given IDs (blah) don't exist."
|
||||||
|
*/
|
||||||
|
@NonFinalForTesting
|
||||||
|
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||||
|
foreignKeyCache = createForeignKeyMapCache(getEppResourceCachingDuration());
|
||||||
|
|
||||||
|
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||||
|
createForeignKeyMapCache(Duration expiry) {
|
||||||
|
return CacheUtils.newCacheBuilder(expiry)
|
||||||
|
.maximumSize(getEppResourceMaxCachedEntries())
|
||||||
|
.build(CACHE_LOADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public static void setCacheForTest(Optional<Duration> expiry) {
|
||||||
|
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||||
|
foreignKeyCache = createForeignKeyMapCache(effectiveExpiry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a list of {@link VKey} to {@link EppResource} instances by class and foreign key strings
|
||||||
|
* that are active at or after the specified moment in time, using the cache if enabled.
|
||||||
|
*
|
||||||
|
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
|
||||||
|
* been soft deleted.
|
||||||
|
*
|
||||||
|
* <p>Don't use the cached version of this method unless you really need it for performance
|
||||||
|
* reasons, and are OK with the trade-offs in loss of transactional consistency.
|
||||||
|
*/
|
||||||
|
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadCached(
|
||||||
|
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||||
|
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||||
|
return load(clazz, foreignKeys, now);
|
||||||
|
}
|
||||||
|
return foreignKeyCache
|
||||||
|
.getAll(
|
||||||
|
foreignKeys.stream().map(fk -> VKey.createSql(clazz, fk)).collect(toImmutableList()))
|
||||||
|
.entrySet()
|
||||||
|
.stream()
|
||||||
|
.filter(e -> e.getValue().isPresent() && now.isBefore(e.getValue().get().deletionTime()))
|
||||||
|
.collect(
|
||||||
|
toImmutableMap(
|
||||||
|
e -> (String) e.getKey().getSqlKey(),
|
||||||
|
e -> VKey.createSql(clazz, e.getValue().get().repoId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AutoValue
|
||||||
|
abstract static class MostRecentResource {
|
||||||
|
|
||||||
|
abstract String repoId();
|
||||||
|
|
||||||
|
abstract DateTime deletionTime();
|
||||||
|
|
||||||
|
static MostRecentResource create(String repoId, DateTime deletionTime) {
|
||||||
|
return new AutoValue_ForeignKeyUtils_MostRecentResource(repoId, deletionTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package google.registry.model.contact;
|
package google.registry.model.contact;
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.annotations.ExternalMessagingName;
|
import google.registry.model.annotations.ExternalMessagingName;
|
||||||
import google.registry.model.annotations.ReportedOn;
|
import google.registry.model.annotations.ReportedOn;
|
||||||
|
@ -46,13 +45,13 @@ import org.joda.time.DateTime;
|
||||||
@Index(columnList = "searchName")
|
@Index(columnList = "searchName")
|
||||||
})
|
})
|
||||||
@ExternalMessagingName("contact")
|
@ExternalMessagingName("contact")
|
||||||
@WithStringVKey
|
@WithStringVKey(compositeKey = true)
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VKey<Contact> createVKey() {
|
public VKey<Contact> createVKey() {
|
||||||
return VKey.create(Contact.class, getRepoId(), Key.create(this));
|
return VKey.createSql(Contact.class, getRepoId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -66,7 +66,7 @@ import org.joda.time.DateTime;
|
||||||
@Index(columnList = "transfer_billing_event_id"),
|
@Index(columnList = "transfer_billing_event_id"),
|
||||||
@Index(columnList = "transfer_billing_recurrence_id")
|
@Index(columnList = "transfer_billing_recurrence_id")
|
||||||
})
|
})
|
||||||
@WithStringVKey
|
@WithStringVKey(compositeKey = true)
|
||||||
@ExternalMessagingName("domain")
|
@ExternalMessagingName("domain")
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||||
|
@ -148,7 +148,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VKey<Domain> createVKey() {
|
public VKey<Domain> createVKey() {
|
||||||
return VKey.create(Domain.class, getRepoId(), Key.create(this));
|
return VKey.createSql(Domain.class, getRepoId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -17,7 +17,6 @@ package google.registry.model.domain;
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||||
import static com.google.common.collect.Maps.transformValues;
|
|
||||||
import static com.google.common.collect.Sets.difference;
|
import static com.google.common.collect.Sets.difference;
|
||||||
import static google.registry.util.CollectionUtils.difference;
|
import static google.registry.util.CollectionUtils.difference;
|
||||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||||
|
@ -31,6 +30,7 @@ import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
import google.registry.model.contact.Contact;
|
import google.registry.model.contact.Contact;
|
||||||
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
|
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
|
||||||
|
@ -39,7 +39,6 @@ import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
|
||||||
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
|
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
|
||||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -445,13 +444,12 @@ public class DomainCommand {
|
||||||
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
|
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
|
||||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||||
throws InvalidReferencesException {
|
throws InvalidReferencesException {
|
||||||
ImmutableMap<String, ForeignKeyIndex<T>> fkis =
|
ImmutableMap<String, VKey<T>> fks = ForeignKeyUtils.loadCached(clazz, foreignKeys, now);
|
||||||
ForeignKeyIndex.loadCached(clazz, foreignKeys, now);
|
if (!fks.keySet().equals(foreignKeys)) {
|
||||||
if (!fkis.keySet().equals(foreignKeys)) {
|
|
||||||
throw new InvalidReferencesException(
|
throw new InvalidReferencesException(
|
||||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fkis.keySet())));
|
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||||
}
|
}
|
||||||
return ImmutableMap.copyOf(transformValues(fkis, ForeignKeyIndex::getResourceKey));
|
return fks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Exception to throw when referenced objects don't exist. */
|
/** Exception to throw when referenced objects don't exist. */
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
package google.registry.model.host;
|
package google.registry.model.host;
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
import com.googlecode.objectify.annotation.Entity;
|
||||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.annotations.ExternalMessagingName;
|
import google.registry.model.annotations.ExternalMessagingName;
|
||||||
|
@ -52,7 +51,7 @@ import javax.persistence.AccessType;
|
||||||
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
|
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
|
||||||
})
|
})
|
||||||
@ExternalMessagingName("host")
|
@ExternalMessagingName("host")
|
||||||
@WithStringVKey
|
@WithStringVKey(compositeKey = true)
|
||||||
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
||||||
public class Host extends HostBase implements ForeignKeyedEppResource {
|
public class Host extends HostBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
|
@ -65,7 +64,7 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VKey<Host> createVKey() {
|
public VKey<Host> createVKey() {
|
||||||
return VKey.create(Host.class, getRepoId(), Key.create(this));
|
return VKey.createSql(Host.class, getRepoId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,311 +0,0 @@
|
||||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// http://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 google.registry.model.index;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
|
||||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
|
||||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
|
||||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
|
||||||
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
|
|
||||||
import static google.registry.util.TypeUtils.instantiate;
|
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.ImmutableBiMap;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import com.google.common.collect.Multimaps;
|
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import google.registry.config.RegistryConfig;
|
|
||||||
import google.registry.model.BackupGroupRoot;
|
|
||||||
import google.registry.model.CacheUtils;
|
|
||||||
import google.registry.model.EppResource;
|
|
||||||
import google.registry.model.contact.Contact;
|
|
||||||
import google.registry.model.domain.Domain;
|
|
||||||
import google.registry.model.host.Host;
|
|
||||||
import google.registry.persistence.VKey;
|
|
||||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
|
||||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
|
||||||
import google.registry.util.NonFinalForTesting;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to map a foreign key to the active instance of {@link EppResource} whose unique id matches
|
|
||||||
* the foreign key string. The instance is never deleted, but it is updated if a newer entity
|
|
||||||
* becomes the active entity.
|
|
||||||
*/
|
|
||||||
public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroupRoot {
|
|
||||||
|
|
||||||
/** The {@link ForeignKeyIndex} type for {@link Contact} entities. */
|
|
||||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<Contact> {}
|
|
||||||
|
|
||||||
/** The {@link ForeignKeyIndex} type for {@link Domain} entities. */
|
|
||||||
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<Domain> {}
|
|
||||||
|
|
||||||
/** The {@link ForeignKeyIndex} type for {@link Host} entities. */
|
|
||||||
public static class ForeignKeyHostIndex extends ForeignKeyIndex<Host> {}
|
|
||||||
|
|
||||||
private static final ImmutableBiMap<
|
|
||||||
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
|
|
||||||
RESOURCE_CLASS_TO_FKI_CLASS =
|
|
||||||
ImmutableBiMap.of(
|
|
||||||
Contact.class, ForeignKeyContactIndex.class,
|
|
||||||
Domain.class, ForeignKeyDomainIndex.class,
|
|
||||||
Host.class, ForeignKeyHostIndex.class);
|
|
||||||
|
|
||||||
private static final ImmutableMap<Class<? extends EppResource>, String>
|
|
||||||
RESOURCE_CLASS_TO_FKI_PROPERTY =
|
|
||||||
ImmutableMap.of(
|
|
||||||
Contact.class, "contactId",
|
|
||||||
Domain.class, "fullyQualifiedDomainName",
|
|
||||||
Host.class, "fullyQualifiedHostName");
|
|
||||||
|
|
||||||
String foreignKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The deletion time of this {@link ForeignKeyIndex}.
|
|
||||||
*
|
|
||||||
* <p>This will generally be equal to the deletion time of {@link #reference}. However, in the
|
|
||||||
* case of a {@link Host} that was renamed, this field will hold the time of the rename.
|
|
||||||
*/
|
|
||||||
DateTime deletionTime;
|
|
||||||
|
|
||||||
/** The referenced resource. */
|
|
||||||
VKey<E> reference;
|
|
||||||
|
|
||||||
public String getForeignKey() {
|
|
||||||
return foreignKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime getDeletionTime() {
|
|
||||||
return deletionTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VKey<E> getResourceKey() {
|
|
||||||
return reference;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T extends EppResource> Class<ForeignKeyIndex<T>> mapToFkiClass(
|
|
||||||
Class<T> resourceClass) {
|
|
||||||
return (Class<ForeignKeyIndex<T>>) RESOURCE_CLASS_TO_FKI_CLASS.get(resourceClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a {@link ForeignKeyIndex} instance for a resource, expiring at a specified time. */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <E extends EppResource> ForeignKeyIndex<E> create(
|
|
||||||
E resource, DateTime deletionTime) {
|
|
||||||
Class<E> resourceClass = (Class<E>) resource.getClass();
|
|
||||||
ForeignKeyIndex<E> instance = instantiate(mapToFkiClass(resourceClass));
|
|
||||||
instance.reference = (VKey<E>) resource.createVKey();
|
|
||||||
instance.foreignKey = resource.getForeignKey();
|
|
||||||
instance.deletionTime = deletionTime;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
|
|
||||||
*
|
|
||||||
* <p>Returns null if no foreign key index with this foreign key was ever created, or if the most
|
|
||||||
* recently created foreign key index was deleted before time "now". This method does not actually
|
|
||||||
* check that the referenced resource actually exists. However, for normal epp resources, it is
|
|
||||||
* safe to assume that the referenced resource exists if the foreign key index does.
|
|
||||||
*
|
|
||||||
* @param clazz the resource type to load
|
|
||||||
* @param foreignKey id to match
|
|
||||||
* @param now the current logical time to use when checking for soft deletion of the foreign key
|
|
||||||
* index
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static <E extends EppResource> VKey<E> loadAndGetKey(
|
|
||||||
Class<E> clazz, String foreignKey, DateTime now) {
|
|
||||||
ForeignKeyIndex<E> index = load(clazz, foreignKey, now);
|
|
||||||
return index == null ? null : index.getResourceKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a {@link ForeignKeyIndex} by class and id string that is active at or after the specified
|
|
||||||
* moment in time.
|
|
||||||
*
|
|
||||||
* <p>This will return null if the {@link ForeignKeyIndex} doesn't exist or has been soft deleted.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static <E extends EppResource> ForeignKeyIndex<E> load(
|
|
||||||
Class<E> clazz, String foreignKey, DateTime now) {
|
|
||||||
return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a map of {@link ForeignKeyIndex} instances by class and id strings that are active at or
|
|
||||||
* after the specified moment in time.
|
|
||||||
*
|
|
||||||
* <p>The returned map will omit any keys for which the {@link ForeignKeyIndex} doesn't exist or
|
|
||||||
* has been soft deleted.
|
|
||||||
*/
|
|
||||||
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
|
|
||||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
|
||||||
return loadIndexesFromStore(clazz, foreignKeys, false).entrySet().stream()
|
|
||||||
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
|
|
||||||
.collect(entriesToImmutableMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper method to load all of the most recent {@link ForeignKeyIndex}es for the given foreign
|
|
||||||
* keys, regardless of whether or not they have been soft-deleted.
|
|
||||||
*
|
|
||||||
* <p>Used by both the cached (w/o deletion check) and the non-cached (with deletion check) calls.
|
|
||||||
*/
|
|
||||||
private static <E extends EppResource>
|
|
||||||
ImmutableMap<String, ForeignKeyIndex<E>> loadIndexesFromStore(
|
|
||||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
|
|
||||||
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
|
|
||||||
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
|
||||||
ImmutableList<ForeignKeyIndex<E>> indexes =
|
|
||||||
jpaTmToUse.transact(
|
|
||||||
() ->
|
|
||||||
jpaTmToUse
|
|
||||||
.criteriaQuery(
|
|
||||||
CriteriaQueryBuilder.create(clazz)
|
|
||||||
.whereFieldIsIn(property, foreignKeys)
|
|
||||||
.build())
|
|
||||||
.getResultStream()
|
|
||||||
.map(e -> create(e, e.getDeletionTime()))
|
|
||||||
.collect(toImmutableList()));
|
|
||||||
// We need to find and return the entities with the maximum deletionTime for each foreign key.
|
|
||||||
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
|
|
||||||
.map(
|
|
||||||
entry ->
|
|
||||||
Maps.immutableEntry(
|
|
||||||
entry.getKey(),
|
|
||||||
entry.getValue().stream()
|
|
||||||
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
|
|
||||||
.get()))
|
|
||||||
.collect(entriesToImmutableMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
static final CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
|
|
||||||
new CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<ForeignKeyIndex<?>> load(VKey<ForeignKeyIndex<?>> key) {
|
|
||||||
String foreignKey = key.getSqlKey().toString();
|
|
||||||
return Optional.ofNullable(
|
|
||||||
loadIndexesFromStore(
|
|
||||||
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(key.getKind()),
|
|
||||||
ImmutableSet.of(foreignKey),
|
|
||||||
true)
|
|
||||||
.get(foreignKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> loadAll(
|
|
||||||
Iterable<? extends VKey<ForeignKeyIndex<?>>> keys) {
|
|
||||||
if (!keys.iterator().hasNext()) {
|
|
||||||
return ImmutableMap.of();
|
|
||||||
}
|
|
||||||
Class<? extends EppResource> resourceClass =
|
|
||||||
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(keys.iterator().next().getKind());
|
|
||||||
ImmutableSet<String> foreignKeys =
|
|
||||||
Streams.stream(keys).map(v -> v.getSqlKey().toString()).collect(toImmutableSet());
|
|
||||||
ImmutableSet<VKey<ForeignKeyIndex<?>>> typedKeys = ImmutableSet.copyOf(keys);
|
|
||||||
ImmutableMap<String, ? extends ForeignKeyIndex<? extends EppResource>> existingFkis =
|
|
||||||
loadIndexesFromStore(resourceClass, foreignKeys, true);
|
|
||||||
// ofy omits keys that don't have values in Datastore, so re-add them in
|
|
||||||
// here with Optional.empty() values.
|
|
||||||
return Maps.asMap(
|
|
||||||
typedKeys,
|
|
||||||
(VKey<ForeignKeyIndex<?>> key) ->
|
|
||||||
Optional.ofNullable(existingFkis.getOrDefault(key.getSqlKey().toString(), null)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A limited size, limited time cache for foreign key entities.
|
|
||||||
*
|
|
||||||
* <p>This is only used to cache foreign key entities for the purposes of checking whether they
|
|
||||||
* exist (and if so, what entity they point to) during a few domain flows. Any other operations on
|
|
||||||
* foreign keys should not use this cache.
|
|
||||||
*
|
|
||||||
* <p>Note that the value type of this cache is Optional because the foreign keys in question are
|
|
||||||
* coming from external commands, and thus don't necessarily represent entities in our system that
|
|
||||||
* actually exist. So we cache the fact that they *don't* exist by using Optional.empty(), and
|
|
||||||
* then several layers up the EPP command will fail with an error message like "The contact with
|
|
||||||
* given IDs (blah) don't exist."
|
|
||||||
*/
|
|
||||||
@NonFinalForTesting
|
|
||||||
private static LoadingCache<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>
|
|
||||||
cacheForeignKeyIndexes = createForeignKeyIndexesCache(getEppResourceCachingDuration());
|
|
||||||
|
|
||||||
private static LoadingCache<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>
|
|
||||||
createForeignKeyIndexesCache(Duration expiry) {
|
|
||||||
return CacheUtils.newCacheBuilder(expiry)
|
|
||||||
.maximumSize(getEppResourceMaxCachedEntries())
|
|
||||||
.build(CACHE_LOADER);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public static void setCacheForTest(Optional<Duration> expiry) {
|
|
||||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
|
||||||
cacheForeignKeyIndexes = createForeignKeyIndexesCache(effectiveExpiry);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a list of {@link ForeignKeyIndex} instances by class and id strings that are active at or
|
|
||||||
* after the specified moment in time, using the cache if enabled.
|
|
||||||
*
|
|
||||||
* <p>The returned map will omit any keys for which the {@link ForeignKeyIndex} doesn't exist or
|
|
||||||
* has been soft deleted.
|
|
||||||
*
|
|
||||||
* <p>Don't use the cached version of this method unless you really need it for performance
|
|
||||||
* reasons, and are OK with the trade-offs in loss of transactional consistency.
|
|
||||||
*/
|
|
||||||
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> loadCached(
|
|
||||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
|
||||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
|
||||||
return load(clazz, foreignKeys, now);
|
|
||||||
}
|
|
||||||
Class<? extends ForeignKeyIndex<?>> fkiClass = mapToFkiClass(clazz);
|
|
||||||
// Safe to cast VKey<FKI<E>> to VKey<FKI<?>>
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ImmutableList<VKey<ForeignKeyIndex<?>>> fkiVKeys =
|
|
||||||
foreignKeys.stream()
|
|
||||||
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.createSql(fkiClass, fk))
|
|
||||||
.collect(toImmutableList());
|
|
||||||
// This cast is safe because when we loaded ForeignKeyIndexes above we used type clazz, which
|
|
||||||
// is scoped to E.
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ImmutableMap<String, ForeignKeyIndex<E>> fkisFromCache =
|
|
||||||
cacheForeignKeyIndexes.getAll(fkiVKeys).entrySet().stream()
|
|
||||||
.filter(entry -> entry.getValue().isPresent())
|
|
||||||
.filter(entry -> now.isBefore(entry.getValue().get().getDeletionTime()))
|
|
||||||
.collect(
|
|
||||||
toImmutableMap(
|
|
||||||
entry -> entry.getKey().getSqlKey().toString(),
|
|
||||||
entry -> (ForeignKeyIndex<E>) entry.getValue().get()));
|
|
||||||
return fkisFromCache;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,6 @@ package google.registry.rdap;
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
import static google.registry.model.EppResourceUtils.loadByForeignKeyCached;
|
||||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
|
||||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
@ -36,6 +35,7 @@ import com.google.common.flogger.FluentLogger;
|
||||||
import com.google.common.net.InetAddresses;
|
import com.google.common.net.InetAddresses;
|
||||||
import com.google.common.primitives.Booleans;
|
import com.google.common.primitives.Booleans;
|
||||||
import com.googlecode.objectify.cmd.Query;
|
import com.googlecode.objectify.cmd.Query;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
|
@ -331,9 +331,9 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||||
return getNameserverRefsByLdhNameWithSuffix(partialStringQuery);
|
return getNameserverRefsByLdhNameWithSuffix(partialStringQuery);
|
||||||
}
|
}
|
||||||
// If there's no suffix, query the host resources. Query the resources themselves, rather than
|
// If there's no suffix, query the host resources. Query the resources themselves, rather than
|
||||||
// the foreign key indexes, because then we have an index on fully qualified host name and
|
// the foreign keys, because then we have an index on fully qualified host name and deletion
|
||||||
// deletion time, so we can check the deletion status in the query itself. The initial string
|
// time, so we can check the deletion status in the query itself. The initial string must be
|
||||||
// must be present, to avoid querying every host in the system. This restriction is enforced by
|
// present, to avoid querying every host in the system. This restriction is enforced by
|
||||||
// {@link queryItems}.
|
// {@link queryItems}.
|
||||||
//
|
//
|
||||||
// Only return the first maxNameserversInFirstStage nameservers. This could result in an
|
// Only return the first maxNameserversInFirstStage nameservers. This could result in an
|
||||||
|
@ -400,7 +400,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||||
: ImmutableList.of(host.get().createVKey());
|
: ImmutableList.of(host.get().createVKey());
|
||||||
} else {
|
} else {
|
||||||
VKey<Host> hostKey =
|
VKey<Host> hostKey =
|
||||||
loadAndGetKey(
|
ForeignKeyUtils.load(
|
||||||
Host.class,
|
Host.class,
|
||||||
partialStringQuery.getInitialString(),
|
partialStringQuery.getInitialString(),
|
||||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||||
|
@ -442,7 +442,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
VKey<Host> hostKey =
|
VKey<Host> hostKey =
|
||||||
loadAndGetKey(
|
ForeignKeyUtils.load(
|
||||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||||
if (hostKey != null) {
|
if (hostKey != null) {
|
||||||
builder.add(hostKey);
|
builder.add(hostKey);
|
||||||
|
|
|
@ -19,10 +19,10 @@ import static com.google.common.base.Preconditions.checkState;
|
||||||
import com.google.common.base.Ascii;
|
import com.google.common.base.Ascii;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.contact.Contact;
|
import google.registry.model.contact.Contact;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ class CommandUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
public VKey<? extends EppResource> getKey(String uniqueId, DateTime now) {
|
public VKey<? extends EppResource> getKey(String uniqueId, DateTime now) {
|
||||||
return ForeignKeyIndex.loadAndGetKey(clazz, uniqueId, now);
|
return ForeignKeyUtils.load(clazz, uniqueId, now);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableMultimap;
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.billing.BillingEvent;
|
import google.registry.model.billing.BillingEvent;
|
||||||
import google.registry.model.billing.BillingEvent.Recurring;
|
import google.registry.model.billing.BillingEvent.Recurring;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
|
@ -38,7 +39,6 @@ import google.registry.model.domain.DomainHistory;
|
||||||
import google.registry.model.domain.Period;
|
import google.registry.model.domain.Period;
|
||||||
import google.registry.model.domain.Period.Unit;
|
import google.registry.model.domain.Period.Unit;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.poll.PollMessage;
|
import google.registry.model.poll.PollMessage;
|
||||||
import google.registry.model.reporting.HistoryEntry.Type;
|
import google.registry.model.reporting.HistoryEntry.Type;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
|
@ -88,7 +88,7 @@ class UnrenewDomainCommand extends ConfirmingCommand implements CommandWithRemot
|
||||||
new ImmutableMap.Builder<>();
|
new ImmutableMap.Builder<>();
|
||||||
|
|
||||||
for (String domainName : mainParameters) {
|
for (String domainName : mainParameters) {
|
||||||
if (ForeignKeyIndex.load(Domain.class, domainName, START_OF_TIME) == null) {
|
if (ForeignKeyUtils.load(Domain.class, domainName, START_OF_TIME) == null) {
|
||||||
domainsNonexistentBuilder.add(domainName);
|
domainsNonexistentBuilder.add(domainName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ public class ResaveEntityActionTest {
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertThat(domain.getCurrentSponsorRegistrarId()).isEqualTo("TheRegistrar");
|
assertThat(domain.getCurrentSponsorRegistrarId()).isEqualTo("TheRegistrar");
|
||||||
runAction(
|
runAction(
|
||||||
domain.createVKey().getOfyKey().getString(),
|
domain.createVKey().stringify(),
|
||||||
DateTime.parse("2016-02-06T10:00:01Z"),
|
DateTime.parse("2016-02-06T10:00:01Z"),
|
||||||
ImmutableSortedSet.of());
|
ImmutableSortedSet.of());
|
||||||
Domain resavedDomain = loadByEntity(domain);
|
Domain resavedDomain = loadByEntity(domain);
|
||||||
|
@ -128,7 +128,7 @@ public class ResaveEntityActionTest {
|
||||||
|
|
||||||
assertThat(domain.getGracePeriods()).isNotEmpty();
|
assertThat(domain.getGracePeriods()).isNotEmpty();
|
||||||
runAction(
|
runAction(
|
||||||
domain.createVKey().getOfyKey().getString(),
|
domain.createVKey().stringify(),
|
||||||
requestedTime,
|
requestedTime,
|
||||||
ImmutableSortedSet.of(requestedTime.plusDays(5)));
|
ImmutableSortedSet.of(requestedTime.plusDays(5)));
|
||||||
Domain resavedDomain = loadByEntity(domain);
|
Domain resavedDomain = loadByEntity(domain);
|
||||||
|
|
|
@ -104,7 +104,7 @@ public class PublishDnsUpdatesActionTest {
|
||||||
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain1);
|
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain1);
|
||||||
persistActiveSubordinateHost("ns2.example.xn--q9jyb4c", domain1);
|
persistActiveSubordinateHost("ns2.example.xn--q9jyb4c", domain1);
|
||||||
Domain domain2 = persistActiveDomain("example2.xn--q9jyb4c");
|
Domain domain2 = persistActiveDomain("example2.xn--q9jyb4c");
|
||||||
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain2);
|
persistActiveSubordinateHost("ns1.example2.xn--q9jyb4c", domain2);
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,6 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doSuccessfulTest() throws Exception {
|
private void doSuccessfulTest() throws Exception {
|
||||||
persistActiveContact(getUniqueIdFromCommand());
|
|
||||||
clock.advanceOneMilli();
|
clock.advanceOneMilli();
|
||||||
assertTransactionalFlow(true);
|
assertTransactionalFlow(true);
|
||||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||||
|
@ -83,6 +82,7 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccess() throws Exception {
|
void testSuccess() throws Exception {
|
||||||
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
doSuccessfulTest();
|
doSuccessfulTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,6 +403,7 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
|
||||||
@Test
|
@Test
|
||||||
void testSuccess_nonAsciiInLocAddress() throws Exception {
|
void testSuccess_nonAsciiInLocAddress() throws Exception {
|
||||||
setEppInput("contact_update_hebrew_loc.xml");
|
setEppInput("contact_update_hebrew_loc.xml");
|
||||||
|
persistActiveContact(getUniqueIdFromCommand());
|
||||||
doSuccessfulTest();
|
doSuccessfulTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -442,7 +442,6 @@ class DomainTransferApproveFlowTest
|
||||||
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
|
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
|
||||||
.build());
|
.build());
|
||||||
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
|
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
|
||||||
setupDomainWithPendingTransfer("example", "tld");
|
|
||||||
domain = loadByEntity(domain);
|
domain = loadByEntity(domain);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadByKey(domain.getAutorenewBillingEvent())
|
loadByKey(domain.getAutorenewBillingEvent())
|
||||||
|
@ -489,7 +488,6 @@ class DomainTransferApproveFlowTest
|
||||||
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
|
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
|
||||||
.build());
|
.build());
|
||||||
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
|
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
|
||||||
setupDomainWithPendingTransfer("example", "tld");
|
|
||||||
domain = loadByEntity(domain);
|
domain = loadByEntity(domain);
|
||||||
persistResource(
|
persistResource(
|
||||||
loadByKey(domain.getAutorenewBillingEvent())
|
loadByKey(domain.getAutorenewBillingEvent())
|
||||||
|
|
|
@ -1475,9 +1475,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||||
void testFailure_addPendingDeleteContact() throws Exception {
|
void testFailure_addPendingDeleteContact() throws Exception {
|
||||||
persistReferencedEntities();
|
persistReferencedEntities();
|
||||||
persistDomain();
|
persistDomain();
|
||||||
persistActiveHost("ns1.example.foo");
|
|
||||||
persistActiveHost("ns2.example.foo");
|
|
||||||
persistActiveContact("sh8013");
|
|
||||||
persistResource(
|
persistResource(
|
||||||
loadByForeignKey(Contact.class, "mak21", clock.nowUtc())
|
loadByForeignKey(Contact.class, "mak21", clock.nowUtc())
|
||||||
.get()
|
.get()
|
||||||
|
@ -1494,9 +1491,6 @@ class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow, Domain
|
||||||
void testFailure_addPendingDeleteHost() throws Exception {
|
void testFailure_addPendingDeleteHost() throws Exception {
|
||||||
persistReferencedEntities();
|
persistReferencedEntities();
|
||||||
persistDomain();
|
persistDomain();
|
||||||
persistActiveHost("ns1.example.foo");
|
|
||||||
persistActiveContact("mak21");
|
|
||||||
persistActiveContact("sh8013");
|
|
||||||
persistResource(
|
persistResource(
|
||||||
loadByForeignKey(Host.class, "ns2.example.foo", clock.nowUtc())
|
loadByForeignKey(Host.class, "ns2.example.foo", clock.nowUtc())
|
||||||
.get()
|
.get()
|
||||||
|
|
|
@ -69,14 +69,15 @@ import google.registry.flows.host.HostUpdateFlow.CannotRemoveSubordinateHostLast
|
||||||
import google.registry.flows.host.HostUpdateFlow.CannotRenameExternalHostException;
|
import google.registry.flows.host.HostUpdateFlow.CannotRenameExternalHostException;
|
||||||
import google.registry.flows.host.HostUpdateFlow.HostAlreadyExistsException;
|
import google.registry.flows.host.HostUpdateFlow.HostAlreadyExistsException;
|
||||||
import google.registry.flows.host.HostUpdateFlow.RenameHostToExternalRemoveIpException;
|
import google.registry.flows.host.HostUpdateFlow.RenameHostToExternalRemoveIpException;
|
||||||
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.model.tld.Registry;
|
import google.registry.model.tld.Registry;
|
||||||
import google.registry.model.transfer.DomainTransferData;
|
import google.registry.model.transfer.DomainTransferData;
|
||||||
import google.registry.model.transfer.TransferStatus;
|
import google.registry.model.transfer.TransferStatus;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.testing.DatabaseHelper;
|
import google.registry.testing.DatabaseHelper;
|
||||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -184,9 +185,8 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||||
Host renamedHost = doSuccessfulTest();
|
Host renamedHost = doSuccessfulTest();
|
||||||
assertThat(renamedHost.isSubordinate()).isTrue();
|
assertThat(renamedHost.isSubordinate()).isTrue();
|
||||||
assertDnsTasksEnqueued("ns1.example.tld", "ns2.example.tld");
|
assertDnsTasksEnqueued("ns1.example.tld", "ns2.example.tld");
|
||||||
ForeignKeyIndex<Host> oldFkiAfterRename =
|
VKey<Host> oldVKeyAfterRename = ForeignKeyUtils.load(Host.class, oldHostName(), clock.nowUtc());
|
||||||
ForeignKeyIndex.load(Host.class, oldHostName(), clock.nowUtc());
|
assertThat(oldVKeyAfterRename).isNull();
|
||||||
assertThat(oldFkiAfterRename).isNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -982,13 +982,13 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||||
@Test
|
@Test
|
||||||
void testFailure_addRemoveSameStatusValues() throws Exception {
|
void testFailure_addRemoveSameStatusValues() throws Exception {
|
||||||
createTld("tld");
|
createTld("tld");
|
||||||
persistActiveDomain("example.tld");
|
Domain domain = persistActiveDomain("example.tld");
|
||||||
setEppHostUpdateInput(
|
setEppHostUpdateInput(
|
||||||
"ns1.example.tld",
|
"ns1.example.tld",
|
||||||
"ns2.example.tld",
|
"ns2.example.tld",
|
||||||
"<host:status s=\"clientUpdateProhibited\"/>",
|
"<host:status s=\"clientUpdateProhibited\"/>",
|
||||||
"<host:status s=\"clientUpdateProhibited\"/>");
|
"<host:status s=\"clientUpdateProhibited\"/>");
|
||||||
persistActiveSubordinateHost(oldHostName(), persistActiveDomain("example.tld"));
|
persistActiveSubordinateHost(oldHostName(), domain);
|
||||||
EppException thrown = assertThrows(AddRemoveSameValueException.class, this::runFlow);
|
EppException thrown = assertThrows(AddRemoveSameValueException.class, this::runFlow);
|
||||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// http://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 google.registry.model;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
|
import static google.registry.testing.DatabaseHelper.persistActiveContact;
|
||||||
|
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||||
|
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
||||||
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import google.registry.model.contact.Contact;
|
||||||
|
import google.registry.model.domain.Domain;
|
||||||
|
import google.registry.model.host.Host;
|
||||||
|
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||||
|
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||||
|
import google.registry.testing.FakeClock;
|
||||||
|
import google.registry.testing.TestCacheExtension;
|
||||||
|
import java.time.Duration;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ForeignKeyUtils}. */
|
||||||
|
class ForeignKeyUtilsTest {
|
||||||
|
|
||||||
|
private final FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
public final JpaIntegrationTestExtension jpaIntegrationTestExtension =
|
||||||
|
new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension();
|
||||||
|
|
||||||
|
@RegisterExtension
|
||||||
|
public final TestCacheExtension testCacheExtension =
|
||||||
|
new TestCacheExtension.Builder().withForeignKeyCache(Duration.ofDays(1)).build();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
createTld("com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadHost() {
|
||||||
|
Host host = persistActiveHost("ns1.example.com");
|
||||||
|
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||||
|
.isEqualTo(host.createVKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadDomain() {
|
||||||
|
Domain domain = persistActiveDomain("example.com");
|
||||||
|
assertThat(ForeignKeyUtils.load(Domain.class, "example.com", fakeClock.nowUtc()))
|
||||||
|
.isEqualTo(domain.createVKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadContact() {
|
||||||
|
Contact contact = persistActiveContact("john-doe");
|
||||||
|
assertThat(ForeignKeyUtils.load(Contact.class, "john-doe", fakeClock.nowUtc()))
|
||||||
|
.isEqualTo(contact.createVKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadMostRecentResource() {
|
||||||
|
Host host = persistActiveHost("ns1.example.com");
|
||||||
|
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||||
|
fakeClock.advanceOneMilli();
|
||||||
|
Host newHost = persistActiveHost("ns1.example.com");
|
||||||
|
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||||
|
.isEqualTo(newHost.createVKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadNonexistentForeignKey_returnsNull() {
|
||||||
|
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadDeletedForeignKey_returnsNull() {
|
||||||
|
Host host = persistActiveHost("ns1.example.com");
|
||||||
|
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||||
|
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_mostRecentKeySoftDeleted_returnsNull() {
|
||||||
|
Host host1 = persistActiveHost("ns1.example.com");
|
||||||
|
fakeClock.advanceOneMilli();
|
||||||
|
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
|
||||||
|
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_batchLoad_skipsDeletedAndNonexistent() {
|
||||||
|
Host host1 = persistActiveHost("ns1.example.com");
|
||||||
|
Host host2 = persistActiveHost("ns2.example.com");
|
||||||
|
persistResource(host2.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||||
|
assertThat(
|
||||||
|
ForeignKeyUtils.load(
|
||||||
|
Host.class,
|
||||||
|
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||||
|
fakeClock.nowUtc()))
|
||||||
|
.containsExactlyEntriesIn(ImmutableMap.of("ns1.example.com", host1.createVKey()));
|
||||||
|
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
|
||||||
|
fakeClock.advanceOneMilli();
|
||||||
|
Host newHost1 = persistActiveHost("ns1.example.com");
|
||||||
|
assertThat(
|
||||||
|
ForeignKeyUtils.loadCached(
|
||||||
|
Host.class,
|
||||||
|
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||||
|
fakeClock.nowUtc()))
|
||||||
|
.containsExactlyEntriesIn(ImmutableMap.of("ns1.example.com", newHost1.createVKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSuccess_loadHostsCached_cacheIsStale() {
|
||||||
|
Host host1 = persistActiveHost("ns1.example.com");
|
||||||
|
Host host2 = persistActiveHost("ns2.example.com");
|
||||||
|
persistResource(host2.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||||
|
assertThat(
|
||||||
|
ForeignKeyUtils.loadCached(
|
||||||
|
Host.class,
|
||||||
|
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||||
|
fakeClock.nowUtc()))
|
||||||
|
.containsExactlyEntriesIn(ImmutableMap.of("ns1.example.com", host1.createVKey()));
|
||||||
|
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
|
||||||
|
fakeClock.advanceOneMilli();
|
||||||
|
persistActiveHost("ns1.example.com");
|
||||||
|
// Even though a new host1 is now live, the cache still returns the VKey to the old one.
|
||||||
|
assertThat(
|
||||||
|
ForeignKeyUtils.loadCached(
|
||||||
|
Host.class,
|
||||||
|
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||||
|
fakeClock.nowUtc()))
|
||||||
|
.containsExactlyEntriesIn(ImmutableMap.of("ns1.example.com", host1.createVKey()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -168,9 +168,8 @@ public class DomainTest {
|
||||||
domain =
|
domain =
|
||||||
persistResource(
|
persistResource(
|
||||||
cloneAndSetAutoTimestamps(
|
cloneAndSetAutoTimestamps(
|
||||||
new Domain.Builder()
|
domain
|
||||||
.setDomainName("example.com")
|
.asBuilder()
|
||||||
.setRepoId("4-COM")
|
|
||||||
.setCreationRegistrarId("TheRegistrar")
|
.setCreationRegistrarId("TheRegistrar")
|
||||||
.setLastEppUpdateTime(fakeClock.nowUtc())
|
.setLastEppUpdateTime(fakeClock.nowUtc())
|
||||||
.setLastEppUpdateRegistrarId("NewRegistrar")
|
.setLastEppUpdateRegistrarId("NewRegistrar")
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
|
||||||
//
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
// http://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 google.registry.model.index;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
|
||||||
import static google.registry.testing.DatabaseHelper.persistActiveHost;
|
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.model.EntityTestCase;
|
|
||||||
import google.registry.model.host.Host;
|
|
||||||
import google.registry.testing.TestCacheExtension;
|
|
||||||
import java.time.Duration;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Unit tests for {@link ForeignKeyIndex}. */
|
|
||||||
class ForeignKeyIndexTest extends EntityTestCase {
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
public final TestCacheExtension testCacheExtension =
|
|
||||||
new TestCacheExtension.Builder().withForeignIndexKeyCache(Duration.ofDays(1)).build();
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
createTld("com");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testLoadForNonexistentForeignKey_returnsNull() {
|
|
||||||
assertThat(ForeignKeyIndex.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testLoadForDeletedForeignKey_returnsNull() {
|
|
||||||
Host host = persistActiveHost("ns1.example.com");
|
|
||||||
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
|
||||||
assertThat(ForeignKeyIndex.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testLoad_newerKeyHasBeenSoftDeleted() {
|
|
||||||
Host host1 = persistActiveHost("ns1.example.com");
|
|
||||||
fakeClock.advanceOneMilli();
|
|
||||||
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
|
|
||||||
assertThat(ForeignKeyIndex.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testBatchLoad_skipsDeletedAndNonexistent() {
|
|
||||||
persistActiveHost("ns1.example.com");
|
|
||||||
Host host = persistActiveHost("ns2.example.com");
|
|
||||||
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
|
||||||
assertThat(
|
|
||||||
ForeignKeyIndex.load(
|
|
||||||
Host.class,
|
|
||||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
|
||||||
fakeClock.nowUtc())
|
|
||||||
.keySet())
|
|
||||||
.containsExactly("ns1.example.com");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDeadCodeThatDeletedScrapCommandsReference() {
|
|
||||||
persistActiveHost("omg");
|
|
||||||
assertThat(ForeignKeyIndex.load(Host.class, "omg", fakeClock.nowUtc()).getForeignKey())
|
|
||||||
.isEqualTo("omg");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,13 +15,13 @@
|
||||||
package google.registry.testing;
|
package google.registry.testing;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import google.registry.model.EppResource;
|
import google.registry.model.EppResource;
|
||||||
import google.registry.model.index.ForeignKeyIndex;
|
import google.registry.model.ForeignKeyUtils;
|
||||||
import google.registry.model.tld.label.PremiumListDao;
|
import google.registry.model.tld.label.PremiumListDao;
|
||||||
import google.registry.model.tmch.ClaimsListDao;
|
import google.registry.model.tmch.ClaimsListDao;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Map;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
|
@ -53,37 +53,30 @@ public class TestCacheExtension implements BeforeEachCallback, AfterEachCallback
|
||||||
|
|
||||||
/** Builder for {@link TestCacheExtension}. */
|
/** Builder for {@link TestCacheExtension}. */
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
private final Map<String, TestCacheHandler> cacheHandlerMap = Maps.newHashMap();
|
private final List<TestCacheHandler> cacheHandlers = new ArrayList<>();
|
||||||
|
|
||||||
public Builder withEppResourceCache(Duration expiry) {
|
public Builder withEppResourceCache(Duration expiry) {
|
||||||
cacheHandlerMap.put(
|
cacheHandlers.add(new TestCacheHandler(EppResource::setCacheForTest, expiry));
|
||||||
"EppResource.cacheEppResources",
|
|
||||||
new TestCacheHandler(EppResource::setCacheForTest, expiry));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withForeignIndexKeyCache(Duration expiry) {
|
public Builder withForeignKeyCache(Duration expiry) {
|
||||||
cacheHandlerMap.put(
|
cacheHandlers.add(new TestCacheHandler(ForeignKeyUtils::setCacheForTest, expiry));
|
||||||
"ForeignKeyIndex.cacheForeignKeyIndexes",
|
|
||||||
new TestCacheHandler(ForeignKeyIndex::setCacheForTest, expiry));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withPremiumListsCache(Duration expiry) {
|
public Builder withPremiumListsCache(Duration expiry) {
|
||||||
cacheHandlerMap.put(
|
cacheHandlers.add(new TestCacheHandler(PremiumListDao::setPremiumListCacheForTest, expiry));
|
||||||
"PremiumListSqlDao.premiumListCache",
|
|
||||||
new TestCacheHandler(PremiumListDao::setPremiumListCacheForTest, expiry));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder withClaimsListCache(Duration expiry) {
|
public Builder withClaimsListCache(Duration expiry) {
|
||||||
cacheHandlerMap.put(
|
cacheHandlers.add(new TestCacheHandler(ClaimsListDao::setCacheForTest, expiry));
|
||||||
"ClaimsListDao.CACHE", new TestCacheHandler(ClaimsListDao::setCacheForTest, expiry));
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestCacheExtension build() {
|
public TestCacheExtension build() {
|
||||||
return new TestCacheExtension(ImmutableList.copyOf(cacheHandlerMap.values()));
|
return new TestCacheExtension(ImmutableList.copyOf(cacheHandlers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,7 @@ class GetContactCommandTest extends CommandTestCase<GetContactCommand> {
|
||||||
persistActiveContact("sh8013");
|
persistActiveContact("sh8013");
|
||||||
runCommand("sh8013");
|
runCommand("sh8013");
|
||||||
assertInStdout("contactId=sh8013");
|
assertInStdout("contactId=sh8013");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Contact" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Contact"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chMLEgdDb250YWN0IgYyLVJPSUQM");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -54,11 +50,7 @@ class GetContactCommandTest extends CommandTestCase<GetContactCommand> {
|
||||||
persistActiveContact("sh8013");
|
persistActiveContact("sh8013");
|
||||||
runCommand("sh8013", "--expand");
|
runCommand("sh8013", "--expand");
|
||||||
assertInStdout("contactId=sh8013");
|
assertInStdout("contactId=sh8013");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Contact" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Contact"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chMLEgdDb250YWN0IgYyLVJPSUQM");
|
|
||||||
assertNotInStdout("LiveRef");
|
assertNotInStdout("LiveRef");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,16 +61,8 @@ class GetContactCommandTest extends CommandTestCase<GetContactCommand> {
|
||||||
runCommand("sh8013", "jd1234");
|
runCommand("sh8013", "jd1234");
|
||||||
assertInStdout("contactId=sh8013");
|
assertInStdout("contactId=sh8013");
|
||||||
assertInStdout("contactId=jd1234");
|
assertInStdout("contactId=jd1234");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Contact" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
assertInStdout("Websafe key: " + "kind:Contact" + "@sql:rO0ABXQABjMtUk9JRA");
|
||||||
+ "kind:Contact"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chMLEgdDb250YWN0IgYyLVJPSUQM");
|
|
||||||
assertInStdout(
|
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Contact"
|
|
||||||
+ "@sql:rO0ABXQABjMtUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chMLEgdDb250YWN0IgYzLVJPSUQM");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -39,11 +39,7 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
|
||||||
runCommand("example.tld");
|
runCommand("example.tld");
|
||||||
assertInStdout("fullyQualifiedDomainName=example.tld");
|
assertInStdout("fullyQualifiedDomainName=example.tld");
|
||||||
assertInStdout("Contact=VKey<Contact>(sql:3-ROID");
|
assertInStdout("Contact=VKey<Contact>(sql:3-ROID");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Domain" + "@sql:rO0ABXQABTItVExE");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Domain"
|
|
||||||
+ "@sql:rO0ABXQABTItVExE"
|
|
||||||
+ "@ofy:agR0ZXN0chELEgZEb21haW4iBTItVExEDA");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -52,11 +48,7 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
|
||||||
runCommand("example.tld", "--expand");
|
runCommand("example.tld", "--expand");
|
||||||
assertInStdout("fullyQualifiedDomainName=example.tld");
|
assertInStdout("fullyQualifiedDomainName=example.tld");
|
||||||
assertInStdout("sqlKey=3-ROID");
|
assertInStdout("sqlKey=3-ROID");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Domain" + "@sql:rO0ABXQABTItVExE");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Domain"
|
|
||||||
+ "@sql:rO0ABXQABTItVExE"
|
|
||||||
+ "@ofy:agR0ZXN0chELEgZEb21haW4iBTItVExEDA");
|
|
||||||
assertNotInStdout("LiveRef");
|
assertNotInStdout("LiveRef");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,16 +68,8 @@ class GetDomainCommandTest extends CommandTestCase<GetDomainCommand> {
|
||||||
runCommand("example.tld", "example2.tld");
|
runCommand("example.tld", "example2.tld");
|
||||||
assertInStdout("fullyQualifiedDomainName=example.tld");
|
assertInStdout("fullyQualifiedDomainName=example.tld");
|
||||||
assertInStdout("fullyQualifiedDomainName=example2.tld");
|
assertInStdout("fullyQualifiedDomainName=example2.tld");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Domain" + "@sql:rO0ABXQABTItVExE");
|
||||||
"Websafe key: "
|
assertInStdout("Websafe key: " + "kind:Domain" + "@sql:rO0ABXQABTQtVExE");
|
||||||
+ "kind:Domain"
|
|
||||||
+ "@sql:rO0ABXQABTItVExE"
|
|
||||||
+ "@ofy:agR0ZXN0chELEgZEb21haW4iBTItVExEDA");
|
|
||||||
assertInStdout(
|
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Domain"
|
|
||||||
+ "@sql:rO0ABXQABTQtVExE"
|
|
||||||
+ "@ofy:agR0ZXN0chELEgZEb21haW4iBTQtVExEDA");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -42,11 +42,7 @@ class GetHostCommandTest extends CommandTestCase<GetHostCommand> {
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
runCommand("ns1.example.tld");
|
runCommand("ns1.example.tld");
|
||||||
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Host" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Host"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chALEgRIb3N0IgYyLVJPSUQM");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -54,11 +50,7 @@ class GetHostCommandTest extends CommandTestCase<GetHostCommand> {
|
||||||
persistActiveHost("ns1.example.tld");
|
persistActiveHost("ns1.example.tld");
|
||||||
runCommand("ns1.example.tld", "--expand");
|
runCommand("ns1.example.tld", "--expand");
|
||||||
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Host" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Host"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chALEgRIb3N0IgYyLVJPSUQM");
|
|
||||||
assertNotInStdout("LiveRef");
|
assertNotInStdout("LiveRef");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,16 +61,8 @@ class GetHostCommandTest extends CommandTestCase<GetHostCommand> {
|
||||||
runCommand("ns1.example.tld", "ns2.example.tld");
|
runCommand("ns1.example.tld", "ns2.example.tld");
|
||||||
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
assertInStdout("fullyQualifiedHostName=ns1.example.tld");
|
||||||
assertInStdout("fullyQualifiedHostName=ns2.example.tld");
|
assertInStdout("fullyQualifiedHostName=ns2.example.tld");
|
||||||
assertInStdout(
|
assertInStdout("Websafe key: " + "kind:Host" + "@sql:rO0ABXQABjItUk9JRA");
|
||||||
"Websafe key: "
|
assertInStdout("Websafe key: " + "kind:Host" + "@sql:rO0ABXQABjMtUk9JRA");
|
||||||
+ "kind:Host"
|
|
||||||
+ "@sql:rO0ABXQABjItUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chALEgRIb3N0IgYyLVJPSUQM");
|
|
||||||
assertInStdout(
|
|
||||||
"Websafe key: "
|
|
||||||
+ "kind:Host"
|
|
||||||
+ "@sql:rO0ABXQABjMtUk9JRA"
|
|
||||||
+ "@ofy:agR0ZXN0chALEgRIb3N0IgYzLVJPSUQM");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -160,11 +160,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setNameservers(ImmutableSet.of(host1.createVKey()))
|
.setNameservers(ImmutableSet.of(host1.createVKey()))
|
||||||
.build());
|
.build());
|
||||||
persistResource(
|
persistResource(domain.asBuilder().setNameservers(ImmutableSet.of(host2.createVKey())).build());
|
||||||
DatabaseHelper.newDomain("example.tld")
|
|
||||||
.asBuilder()
|
|
||||||
.setNameservers(ImmutableSet.of(host2.createVKey()))
|
|
||||||
.build());
|
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--client=NewRegistrar", nsParam, "example.abc", "example.tld");
|
"--client=NewRegistrar", nsParam, "example.abc", "example.tld");
|
||||||
eppVerifier
|
eppVerifier
|
||||||
|
@ -228,8 +224,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
Host host1 = persistActiveHost("ns1.zdns.google");
|
Host host1 = persistActiveHost("ns1.zdns.google");
|
||||||
Host host2 = persistActiveHost("ns2.zdns.google");
|
Host host2 = persistActiveHost("ns2.zdns.google");
|
||||||
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host1.createVKey(), host2.createVKey());
|
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host1.createVKey(), host2.createVKey());
|
||||||
persistResource(
|
persistResource(domain.asBuilder().setNameservers(nameservers).build());
|
||||||
DatabaseHelper.newDomain("example.tld").asBuilder().setNameservers(nameservers).build());
|
|
||||||
runCommandForced(
|
runCommandForced(
|
||||||
"--client=NewRegistrar", "--nameservers=ns2.zdns.google,ns3.zdns.google", "example.tld");
|
"--client=NewRegistrar", "--nameservers=ns2.zdns.google,ns3.zdns.google", "example.tld");
|
||||||
eppVerifier.verifySent("domain_update_set_nameservers.xml");
|
eppVerifier.verifySent("domain_update_set_nameservers.xml");
|
||||||
|
@ -243,7 +238,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
VKey<Contact> techContactKey = techContact.createVKey();
|
VKey<Contact> techContactKey = techContact.createVKey();
|
||||||
|
|
||||||
persistResource(
|
persistResource(
|
||||||
DatabaseHelper.newDomain("example.tld")
|
domain
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setContacts(
|
.setContacts(
|
||||||
ImmutableSet.of(
|
ImmutableSet.of(
|
||||||
|
@ -261,7 +256,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
Host host = persistActiveHost("ns1.zdns.google");
|
Host host = persistActiveHost("ns1.zdns.google");
|
||||||
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
||||||
persistResource(
|
persistResource(
|
||||||
DatabaseHelper.newDomain("example.tld")
|
domain
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setStatusValues(
|
.setStatusValues(
|
||||||
ImmutableSet.of(
|
ImmutableSet.of(
|
||||||
|
@ -370,7 +365,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
VKey<Contact> techContactKey = techContact.createVKey();
|
VKey<Contact> techContactKey = techContact.createVKey();
|
||||||
|
|
||||||
persistResource(
|
persistResource(
|
||||||
DatabaseHelper.newDomain("example.tld")
|
domain
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setContacts(
|
.setContacts(
|
||||||
ImmutableSet.of(
|
ImmutableSet.of(
|
||||||
|
@ -394,7 +389,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
Host host = persistActiveHost("ns1.zdns.google");
|
Host host = persistActiveHost("ns1.zdns.google");
|
||||||
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
||||||
persistResource(
|
persistResource(
|
||||||
DatabaseHelper.newDomain("example.tld")
|
domain
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setStatusValues(ImmutableSet.of(SERVER_UPDATE_PROHIBITED))
|
.setStatusValues(ImmutableSet.of(SERVER_UPDATE_PROHIBITED))
|
||||||
.setNameservers(nameservers)
|
.setNameservers(nameservers)
|
||||||
|
@ -419,7 +414,7 @@ class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomainCommand
|
||||||
Host host = persistActiveHost("ns1.zdns.google");
|
Host host = persistActiveHost("ns1.zdns.google");
|
||||||
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
ImmutableSet<VKey<Host>> nameservers = ImmutableSet.of(host.createVKey());
|
||||||
persistResource(
|
persistResource(
|
||||||
DatabaseHelper.newDomain("example.tld")
|
domain
|
||||||
.asBuilder()
|
.asBuilder()
|
||||||
.setStatusValues(ImmutableSet.of(PENDING_DELETE))
|
.setStatusValues(ImmutableSet.of(PENDING_DELETE))
|
||||||
.setNameservers(nameservers)
|
.setNameservers(nameservers)
|
||||||
|
|
|
@ -398,7 +398,7 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
|
||||||
server.runInAppEngineEnvironment(
|
server.runInAppEngineEnvironment(
|
||||||
() -> {
|
() -> {
|
||||||
createTld("tld");
|
createTld("tld");
|
||||||
persistResource(DatabaseHelper.newDomain("example.tld"));
|
persistResource(DatabaseHelper.newDomain("example-lock.tld"));
|
||||||
saveRegistryLock(
|
saveRegistryLock(
|
||||||
new RegistryLock.Builder()
|
new RegistryLock.Builder()
|
||||||
.setRegistrarPocId("johndoe@theregistrar.com")
|
.setRegistrarPocId("johndoe@theregistrar.com")
|
||||||
|
@ -406,7 +406,7 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
|
||||||
.setRegistrarId("TheRegistrar")
|
.setRegistrarId("TheRegistrar")
|
||||||
.setVerificationCode("f1be78a2-2d61-458c-80f0-9dd8f2f8625f")
|
.setVerificationCode("f1be78a2-2d61-458c-80f0-9dd8f2f8625f")
|
||||||
.isSuperuser(false)
|
.isSuperuser(false)
|
||||||
.setDomainName("example.tld")
|
.setDomainName("example-lock.tld")
|
||||||
.build());
|
.build());
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -81,7 +81,7 @@ public class WhoisActionTest {
|
||||||
public final TestCacheExtension testCacheExtension =
|
public final TestCacheExtension testCacheExtension =
|
||||||
new TestCacheExtension.Builder()
|
new TestCacheExtension.Builder()
|
||||||
.withEppResourceCache(Duration.ofDays(1))
|
.withEppResourceCache(Duration.ofDays(1))
|
||||||
.withForeignIndexKeyCache(Duration.ofDays(1))
|
.withForeignKeyCache(Duration.ofDays(1))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final FakeResponse response = new FakeResponse();
|
private final FakeResponse response = new FakeResponse();
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Loading…
Add table
Add a link
Reference in a new issue