mirror of
https://github.com/google/nomulus
synced 2025-12-23 06:15:42 +00:00
Modify the way we load resources via foreign keys (#2852)
Previously, we would have separate database calls for mapping from foreign key to repo ID and then from repo ID to object. This PR modifies those calls to load the resource directly (the old system was an artifact of the Datastore key-value storage system). In this PR, we merge the load-resource-by-foreign-key calls into a single database load, as well as adding a separate cache object for (foreign key) -> (resource). Now we cache, and have separate cleaner code paths, for fk -> resource, fk -> repo ID, and repo ID -> resource. Also removes the unused RdeFragmenter class
This commit is contained in:
@@ -28,7 +28,6 @@ import static google.registry.bsa.persistence.Queries.queryMissedRegisteredUnblo
|
||||
import static google.registry.bsa.persistence.Queries.queryUnblockableDomainByLabels;
|
||||
import static google.registry.model.tld.Tld.isEnrolledWithBsa;
|
||||
import static google.registry.model.tld.Tlds.getTldEntitiesOfType;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.util.BatchedStreams.toBatches;
|
||||
@@ -53,7 +52,6 @@ import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.tld.Tld.TldType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.GaeService;
|
||||
import google.registry.request.Response;
|
||||
@@ -185,8 +183,8 @@ public class BsaValidateAction implements Runnable {
|
||||
ImmutableList<UnblockableDomain> batch;
|
||||
do {
|
||||
batch = Queries.batchReadUnblockableDomains(lastRead, transactionBatchSize);
|
||||
ImmutableMap<String, VKey<Domain>> activeDomains =
|
||||
ForeignKeyUtils.load(
|
||||
ImmutableMap<String, Domain> activeDomains =
|
||||
ForeignKeyUtils.loadResources(
|
||||
Domain.class,
|
||||
batch.stream().map(UnblockableDomain::domainName).collect(toImmutableList()),
|
||||
clock.nowUtc());
|
||||
@@ -201,7 +199,7 @@ public class BsaValidateAction implements Runnable {
|
||||
}
|
||||
|
||||
Optional<String> verifyDomainStillUnblockableWithReason(
|
||||
UnblockableDomain domain, ImmutableMap<String, VKey<Domain>> activeDomains) {
|
||||
UnblockableDomain domain, ImmutableMap<String, Domain> activeDomains) {
|
||||
DateTime now = clock.nowUtc();
|
||||
boolean isRegistered = activeDomains.containsKey(domain.domainName());
|
||||
boolean isReserved = isReservedDomain(domain.domainName(), now);
|
||||
@@ -230,10 +228,8 @@ public class BsaValidateAction implements Runnable {
|
||||
domain.reason()));
|
||||
}
|
||||
|
||||
boolean isStalenessAllowed(VKey<Domain> domainVKey) {
|
||||
Domain domain = bsaQuery(() -> replicaTm().loadByKey(domainVKey));
|
||||
var now = clock.nowUtc();
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(now);
|
||||
boolean isStalenessAllowed(Domain domain) {
|
||||
return domain.getCreationTime().plus(maxStaleness).isAfter(clock.nowUtc());
|
||||
}
|
||||
|
||||
/** Returns unique labels across all block lists in the download specified by {@code jobName}. */
|
||||
|
||||
@@ -156,7 +156,7 @@ public final class DomainsRefresher {
|
||||
.collect(toImmutableSet());
|
||||
ImmutableSet<String> currRegistered =
|
||||
ImmutableSet.copyOf(
|
||||
ForeignKeyUtils.load(Domain.class, nameToEntity.keySet(), now).keySet());
|
||||
ForeignKeyUtils.loadKeys(Domain.class, nameToEntity.keySet(), now).keySet());
|
||||
SetView<String> noLongerRegistered = Sets.difference(prevRegistered, currRegistered);
|
||||
SetView<String> newlyRegistered = Sets.difference(currRegistered, prevRegistered);
|
||||
|
||||
|
||||
@@ -145,11 +145,10 @@ public final class LabelDiffUpdates {
|
||||
|
||||
ImmutableSet<String> validDomainNames =
|
||||
labels.stream()
|
||||
.map(label -> validDomainNamesForLabel(label, idnChecker))
|
||||
.flatMap(x -> x)
|
||||
.flatMap(label -> validDomainNamesForLabel(label, idnChecker))
|
||||
.collect(toImmutableSet());
|
||||
ImmutableSet<String> registeredDomainNames =
|
||||
ImmutableSet.copyOf(ForeignKeyUtils.load(Domain.class, validDomainNames, now).keySet());
|
||||
ForeignKeyUtils.loadKeys(Domain.class, validDomainNames, now).keySet();
|
||||
for (String domain : registeredDomainNames) {
|
||||
nonBlockedDomains.add(new UnblockableDomain(domain, Reason.REGISTERED));
|
||||
tm().put(BsaUnblockableDomain.of(domain, BsaUnblockableDomain.Reason.REGISTERED));
|
||||
|
||||
@@ -38,7 +38,6 @@ import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
||||
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
|
||||
import static org.json.simple.JSONValue.toJSONString;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
@@ -185,8 +184,7 @@ public class CheckApiAction implements Runnable {
|
||||
}
|
||||
|
||||
private boolean checkExists(String domainString, DateTime now) {
|
||||
return !ForeignKeyUtils.loadByCache(Domain.class, ImmutableList.of(domainString), now)
|
||||
.isEmpty();
|
||||
return ForeignKeyUtils.loadKeyByCache(Domain.class, domainString, now).isPresent();
|
||||
}
|
||||
|
||||
private Optional<String> checkReserved(InternetDomainName domainName) {
|
||||
|
||||
@@ -71,10 +71,9 @@ public final class ResourceFlowUtils {
|
||||
*/
|
||||
public static <R extends EppResource> void checkLinkedDomains(
|
||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||
if (key == null) {
|
||||
throw new ResourceDoesNotExistException(resourceClass, targetId);
|
||||
}
|
||||
VKey<R> key =
|
||||
ForeignKeyUtils.loadKey(resourceClass, targetId, now)
|
||||
.orElseThrow(() -> new ResourceDoesNotExistException(resourceClass, targetId));
|
||||
if (isLinked(key, now)) {
|
||||
throw new ResourceToDeleteIsReferencedException();
|
||||
}
|
||||
@@ -106,11 +105,10 @@ public final class ResourceFlowUtils {
|
||||
|
||||
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
||||
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
|
||||
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
|
||||
if (key != null) {
|
||||
R resource = tm().loadByKey(key);
|
||||
Optional<R> resource = ForeignKeyUtils.loadResource(clazz, targetId, now);
|
||||
if (resource.isPresent()) {
|
||||
// These are similar exceptions, but we can track them internally as log-based metrics.
|
||||
if (Objects.equals(registrarId, resource.getPersistedCurrentSponsorRegistrarId())) {
|
||||
if (Objects.equals(registrarId, resource.get().getPersistedCurrentSponsorRegistrarId())) {
|
||||
throw new ResourceAlreadyExistsForThisClientException(targetId);
|
||||
} else {
|
||||
throw new ResourceCreateContentionException(targetId);
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.flows.contact;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -26,6 +25,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
@@ -62,7 +62,7 @@ public final class ContactCheckFlow implements TransactionalFlow {
|
||||
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(targetIds, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
checkResourcesExist(Contact.class, targetIds, clock.nowUtc());
|
||||
ForeignKeyUtils.loadKeys(Contact.class, targetIds, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
|
||||
@@ -181,7 +181,7 @@ public final class DomainCheckFlow implements TransactionalFlow {
|
||||
.setAsOfDate(now)
|
||||
.build());
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains =
|
||||
ForeignKeyUtils.load(Domain.class, domainNames, now);
|
||||
ForeignKeyUtils.loadKeys(Domain.class, domainNames, now);
|
||||
// Check block labels only when there are unregistered domains, since "In use" goes before
|
||||
// "Blocked by BSA".
|
||||
ImmutableSet<InternetDomainName> bsaBlockedDomainNames =
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.flows.domain;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyOptionalAuthInfo;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfPresent;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
@@ -37,7 +37,6 @@ import google.registry.flows.custom.DomainInfoFlowCustomLogic;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.AfterValidationParameters;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.BeforeResponseParameters;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.BeforeResponseReturnData;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Info;
|
||||
import google.registry.model.domain.DomainCommand.Info.HostsRequest;
|
||||
@@ -108,9 +107,7 @@ public final class DomainInfoFlow implements MutatingFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = clock.nowUtc();
|
||||
Domain domain =
|
||||
verifyExistence(
|
||||
Domain.class, targetId, ForeignKeyUtils.loadResource(Domain.class, targetId, now));
|
||||
Domain domain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, domain);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setDomain(domain).build());
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.flows.host;
|
||||
|
||||
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -26,6 +25,7 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheck;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheckData;
|
||||
@@ -61,7 +61,8 @@ public final class HostCheckFlow implements TransactionalFlow {
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ImmutableList<String> hostnames = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(hostnames, maxChecks);
|
||||
ImmutableSet<String> existingIds = checkResourcesExist(Host.class, hostnames, clock.nowUtc());
|
||||
ImmutableSet<String> existingIds =
|
||||
ForeignKeyUtils.loadKeys(Host.class, hostnames, clock.nowUtc()).keySet();
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String hostname : hostnames) {
|
||||
boolean unused = !existingIds.contains(hostname);
|
||||
|
||||
@@ -154,7 +154,7 @@ public final class HostUpdateFlow implements MutatingFlow {
|
||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||
verifyUpdateAllowed(
|
||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||
if (isHostRename && ForeignKeyUtils.load(Host.class, newHostName, now) != null) {
|
||||
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
|
||||
@@ -16,19 +16,14 @@ package google.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.isAtOrAfter;
|
||||
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
import static google.registry.util.DateTimeUtils.latestOf;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -41,13 +36,9 @@ import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.TransactionManager;
|
||||
import jakarta.persistence.Query;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
@@ -90,98 +81,6 @@ public final class EppResourceUtils {
|
||||
return (T) resource.cloneProjectedAtTime(now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||
* using a cache, if caching is enabled in config settings.
|
||||
*
|
||||
* <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".
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||
* past.
|
||||
*
|
||||
* <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. RDAP.
|
||||
*
|
||||
* @param foreignKey id to match
|
||||
* @param now the current logical time to project resources at
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyByCacheIfEnabled(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(
|
||||
tm(), clazz, foreignKey, now, RegistryConfig.isEppResourceCachingEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the replica database by foreign
|
||||
* key, using a cache.
|
||||
*
|
||||
* <p>This method ignores the config setting for caching, and is reserved for use cases that can
|
||||
* tolerate slightly stale data.
|
||||
*/
|
||||
public static <T extends EppResource> Optional<T> loadByForeignKeyByCache(
|
||||
Class<T> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(replicaTm(), clazz, foreignKey, now, true);
|
||||
}
|
||||
|
||||
static <T extends EppResource> Optional<T> loadByForeignKeyHelper(
|
||||
TransactionManager txnManager,
|
||||
Class<T> clazz,
|
||||
String foreignKey,
|
||||
DateTime now,
|
||||
boolean useCache) {
|
||||
checkArgument(
|
||||
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
|
||||
"loadByForeignKey may only be called for foreign keyed EPP resources");
|
||||
VKey<T> key =
|
||||
useCache
|
||||
? ForeignKeyUtils.loadByCache(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
|
||||
: ForeignKeyUtils.load(clazz, foreignKey, now);
|
||||
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
|
||||
if (key == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
T resource =
|
||||
useCache
|
||||
? EppResource.loadByCache(key)
|
||||
// This transaction is buried very deeply inside many outer nested calls, hence merits
|
||||
// the use of reTransact() for now pending a substantial refactoring.
|
||||
: txnManager.reTransact(() -> txnManager.loadByKeyIfPresent(key).orElse(null));
|
||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// When setting status values based on a time, choose the greater of "now" and the resource's
|
||||
// UpdateAutoTimestamp. For non-mutating uses (info, RDAP, etc.), this is equivalent to rolling
|
||||
// "now" forward to at least the last update on the resource, so that a read right after a write
|
||||
// doesn't appear stale. For mutating flows, if we had to roll now forward then the flow will
|
||||
// fail when it tries to save anything, since "now" is needed to be > the last update time for
|
||||
// writes.
|
||||
return Optional.of(
|
||||
cloneProjectedAtTime(
|
||||
resource, latestOf(now, resource.getUpdateTimestamp().getTimestamp())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
|
||||
* add code to do the lookup by id directly.
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param uniqueIds a list of ids to match
|
||||
* @param now the logical time of the check
|
||||
*/
|
||||
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
|
||||
Class<T> clazz, Collection<String> uniqueIds, final DateTime now) {
|
||||
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Function that transforms an EppResource to the given DateTime, suitable for use with
|
||||
* Iterables.transform() over a collection of EppResources.
|
||||
@@ -281,21 +180,6 @@ public final class EppResourceUtils {
|
||||
: null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewinds an {@link EppResource} object to a given point in time.
|
||||
*
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise, it returns an
|
||||
* async operation that performs a single fetch operation.
|
||||
*
|
||||
* @return an asynchronous operation returning resource at {@code timestamp} or {@code null} if
|
||||
* resource is deleted or not yet created
|
||||
* @see #loadAtPointInTime(EppResource, DateTime)
|
||||
*/
|
||||
public static <T extends EppResource> Supplier<T> loadAtPointInTimeAsync(
|
||||
final T resource, final DateTime timestamp) {
|
||||
return () -> loadAtPointInTime(resource, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most recent revision of a given EppResource before or at the provided timestamp,
|
||||
* falling back to using the resource as-is if there are no revisions.
|
||||
@@ -370,13 +254,11 @@ public final class EppResourceUtils {
|
||||
/**
|
||||
* Returns whether the given contact or host is linked to (that is, referenced by) a domain.
|
||||
*
|
||||
* <p>This is an eventually consistent query.
|
||||
*
|
||||
* @param key the referent key
|
||||
* @param now the logical time of the check
|
||||
*/
|
||||
public static boolean isLinked(VKey<? extends EppResource> key, DateTime now) {
|
||||
return getLinkedDomainKeys(key, now, 1).size() > 0;
|
||||
return !getLinkedDomainKeys(key, now, 1).isEmpty();
|
||||
}
|
||||
|
||||
private EppResourceUtils() {}
|
||||
|
||||
@@ -18,7 +18,6 @@ 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.model.EppResourceUtils.loadByForeignKeyHelper;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
@@ -42,13 +41,17 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
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.
|
||||
* Util class for mapping a foreign key to a {@link VKey} or {@link EppResource}.
|
||||
*
|
||||
* <p>We return the resource that matches the foreign key at a given time (this may vary over time
|
||||
* e.g. if a domain expires and is re-registered). The instance is never deleted, but it is updated
|
||||
* if a newer entity becomes the active entity.
|
||||
*
|
||||
* <p>One can retrieve either the {@link VKey} (the repo ID) in the situations where that's
|
||||
* sufficient, or the resource itself, either with or without caching.
|
||||
*/
|
||||
public final class ForeignKeyUtils {
|
||||
|
||||
@@ -61,35 +64,17 @@ public final class ForeignKeyUtils {
|
||||
Domain.class, "domainName",
|
||||
Host.class, "hostName");
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
public record MostRecentResource(String repoId, DateTime deletionTime) {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Loads an optional {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
|
||||
* or has been soft-deleted.
|
||||
* <p>Returns empty if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return loadMostRecentResources(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().deletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> VKey.create(clazz, e.getValue().repoId())));
|
||||
public static <E extends EppResource> Optional<VKey<E>> loadKey(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return Optional.ofNullable(loadKeys(clazz, ImmutableList.of(foreignKey), now).get(foreignKey));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,7 +85,38 @@ public final class ForeignKeyUtils {
|
||||
*/
|
||||
public static <E extends EppResource> Optional<E> loadResource(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return loadByForeignKeyHelper(tm(), clazz, foreignKey, now, false);
|
||||
// Note: no need to project to "now" because loadResources already does
|
||||
return Optional.ofNullable(
|
||||
loadResources(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>> loadKeys(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
return loadMostRecentResources(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().deletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> VKey.create(clazz, e.getValue().repoId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a map of {@link String} foreign keys to the {@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.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends EppResource> ImmutableMap<String, E> loadResources(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
return loadMostRecentResourceObjects(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> (E) e.getValue().cloneProjectedAtTime(now)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,22 +152,49 @@ public final class ForeignKeyUtils {
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
row -> (String) row[0],
|
||||
row -> MostRecentResource.create((String) row[1], (DateTime) row[2]))));
|
||||
row -> new MostRecentResource((String) row[1], (DateTime) row[2]))));
|
||||
}
|
||||
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
/** Method to load the most recent {@link EppResource}s for the given foreign keys. */
|
||||
private static <E extends EppResource> ImmutableMap<String, E> loadMostRecentResourceObjects(
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaTm) {
|
||||
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||
JpaTransactionManager tmToUse = useReplicaTm ? replicaTm() : tm();
|
||||
return tmToUse.reTransact(
|
||||
() ->
|
||||
tmToUse
|
||||
.query(
|
||||
("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()),
|
||||
clazz)
|
||||
.setParameter("fks", foreignKeys)
|
||||
.getResultStream()
|
||||
.collect(toImmutableMap(EppResource::getForeignKey, e -> e)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache loader for loading repo IDs and deletion times for the given foreign keys.
|
||||
*
|
||||
* <p>Note: while this is given a {@link VKey}, one cannot use that key to load directly from the
|
||||
* database. That key is basically used to signify a foreign-key + resource-type pairing.
|
||||
*/
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
REPO_ID_CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
@Override
|
||||
public Optional<MostRecentResource> load(VKey<? extends EppResource> key) {
|
||||
return loadAll(ImmutableSet.of(key)).get(key);
|
||||
String foreignKey = (String) key.getKey();
|
||||
return Optional.ofNullable(
|
||||
loadMostRecentResources(key.getKind(), ImmutableList.of(foreignKey), true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<
|
||||
? extends VKey<? extends EppResource>, ? extends Optional<MostRecentResource>>
|
||||
loadAll(Set<? extends VKey<? extends EppResource>> keys) {
|
||||
public Map<VKey<? extends EppResource>, ? extends Optional<MostRecentResource>> loadAll(
|
||||
Set<? extends VKey<? extends EppResource>> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
@@ -161,7 +204,7 @@ public final class ForeignKeyUtils {
|
||||
ImmutableList<String> foreignKeys =
|
||||
keys.stream().map(key -> (String) key.getKey()).collect(toImmutableList());
|
||||
ImmutableMap<String, MostRecentResource> existingKeys =
|
||||
ForeignKeyUtils.loadMostRecentResources(clazz, foreignKeys, true);
|
||||
loadMostRecentResources(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(
|
||||
@@ -171,11 +214,10 @@ public final class ForeignKeyUtils {
|
||||
};
|
||||
|
||||
/**
|
||||
* A limited size, limited time cache for foreign-keyed entities.
|
||||
* A limited size, limited time cache for mapping foreign keys to repo IDs.
|
||||
*
|
||||
* <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>This is only used to cache foreign-keyed entity keys for the purposes of checking whether
|
||||
* they exist (and if so, what entity they point to).
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {@link VKey}s to {@link EppResource}s, whose keys are the
|
||||
@@ -189,22 +231,26 @@ public final class ForeignKeyUtils {
|
||||
* 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."
|
||||
*
|
||||
* <p>If one wishes to load the entity itself via cache, use the {@link
|
||||
* #foreignKeyToResourceCache} instead, as that loads the entity instead. This cache is used for
|
||||
* situations where the repo ID, or the existence of the repo ID, is sufficient.
|
||||
*/
|
||||
@NonFinalForTesting
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
foreignKeyCache = createForeignKeyMapCache(getEppResourceCachingDuration());
|
||||
foreignKeyToRepoIdCache = createForeignKeyToRepoIdCache(getEppResourceCachingDuration());
|
||||
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
createForeignKeyMapCache(Duration expiry) {
|
||||
createForeignKeyToRepoIdCache(Duration expiry) {
|
||||
return CacheUtils.newCacheBuilder(expiry)
|
||||
.maximumSize(getEppResourceMaxCachedEntries())
|
||||
.build(CACHE_LOADER);
|
||||
.build(REPO_ID_CACHE_LOADER);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setCacheForTest(Optional<Duration> expiry) {
|
||||
public static void setRepoIdCacheForTest(Optional<Duration> expiry) {
|
||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||
foreignKeyCache = createForeignKeyMapCache(effectiveExpiry);
|
||||
foreignKeyToRepoIdCache = createForeignKeyToRepoIdCache(effectiveExpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,26 +263,12 @@ public final class ForeignKeyUtils {
|
||||
* <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>> loadByCacheIfEnabled(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadKeysByCacheIfEnabled(
|
||||
Class<E> clazz, Collection<String> foreignKeys, DateTime now) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return load(clazz, foreignKeys, now);
|
||||
return loadKeys(clazz, foreignKeys, now);
|
||||
}
|
||||
return loadByCache(clazz, foreignKeys, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
|
||||
* been soft-deleted.
|
||||
*
|
||||
* <p>This method is reserved for use cases that can tolerate slightly stale data.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadByCache(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return foreignKeyCache
|
||||
return foreignKeyToRepoIdCache
|
||||
.getAll(foreignKeys.stream().map(fk -> VKey.create(clazz, fk)).collect(toImmutableList()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
@@ -247,10 +279,127 @@ public final class ForeignKeyUtils {
|
||||
e -> VKey.create(clazz, e.getValue().get().repoId())));
|
||||
}
|
||||
|
||||
public record MostRecentResource(String repoId, DateTime deletionTime) {
|
||||
/** Loads an optional {@link VKey} to an {@link EppResource} using the cache. */
|
||||
public static <E extends EppResource> Optional<VKey<E>> loadKeyByCache(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return foreignKeyToRepoIdCache
|
||||
.get(VKey.create(clazz, foreignKey))
|
||||
.filter(mrr -> now.isBefore(mrr.deletionTime()))
|
||||
.map(mrr -> VKey.create(clazz, mrr.repoId()));
|
||||
}
|
||||
|
||||
static MostRecentResource create(String repoId, DateTime deletionTime) {
|
||||
return new MostRecentResource(repoId, deletionTime);
|
||||
}
|
||||
/**
|
||||
* Cache loader for loading {@link EppResource}s for the given foreign keys.
|
||||
*
|
||||
* <p>Note: while this is given a {@link VKey}, one cannot use that key to load directly from the
|
||||
* database. That key is basically used to signify a foreign-key + resource-type pairing.
|
||||
*/
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
RESOURCE_CACHE_LOADER =
|
||||
new CacheLoader<>() {
|
||||
@Override
|
||||
public Optional<? extends EppResource> load(VKey<? extends EppResource> key) {
|
||||
String foreignKey = (String) key.getKey();
|
||||
return Optional.ofNullable(
|
||||
loadMostRecentResourceObjects(key.getKind(), ImmutableList.of(foreignKey), true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, Optional<? extends EppResource>> loadAll(
|
||||
Set<? extends VKey<? extends EppResource>> keys) {
|
||||
if (keys.isEmpty()) {
|
||||
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 =
|
||||
keys.stream().map(key -> (String) key.getKey()).collect(toImmutableList());
|
||||
ImmutableMap<String, ? extends EppResource> existingResources =
|
||||
loadMostRecentResourceObjects(clazz, foreignKeys, true);
|
||||
// The above map only contains resources 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(existingResources.get((String) key.getKey())));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An additional limited size, limited time cache for foreign-keyed entities.
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {@link 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."
|
||||
*
|
||||
* <p>This cache bypasses the foreign-key-to-repo-ID lookup and maps directly from the foreign key
|
||||
* to the entity itself (at least, at this point in time).
|
||||
*/
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
foreignKeyToResourceCache = createForeignKeyToResourceCache(getEppResourceCachingDuration());
|
||||
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<? extends EppResource>>
|
||||
createForeignKeyToResourceCache(Duration expiry) {
|
||||
return CacheUtils.newCacheBuilder(expiry)
|
||||
.maximumSize(getEppResourceMaxCachedEntries())
|
||||
.build(RESOURCE_CACHE_LOADER);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setResourceCacheForTest(Optional<Duration> expiry) {
|
||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||
foreignKeyToResourceCache = createForeignKeyToResourceCache(effectiveExpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||
* using a cache, if caching is enabled in config settings.
|
||||
*
|
||||
* <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".
|
||||
*
|
||||
* <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
|
||||
* 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
|
||||
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||
* past.
|
||||
*
|
||||
* <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. RDAP.
|
||||
*/
|
||||
public static <E extends EppResource> Optional<E> loadResourceByCacheIfEnabled(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return RegistryConfig.isEppResourceCachingEnabled()
|
||||
? loadResourceByCache(clazz, foreignKey, now)
|
||||
: loadResource(clazz, foreignKey, now);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from the replica database by foreign
|
||||
* key, using a cache.
|
||||
*
|
||||
* <p>This method ignores the config setting for caching, and is reserved for use cases that can
|
||||
* tolerate slightly stale data.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends EppResource> Optional<E> loadResourceByCache(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return (Optional<E>)
|
||||
foreignKeyToResourceCache
|
||||
.get(VKey.create(clazz, foreignKey))
|
||||
.filter(e -> now.isBefore(e.getDeletionTime()))
|
||||
.map(e -> e.cloneProjectedAtTime(now));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +442,7 @@ public class DomainCommand {
|
||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
ImmutableMap<String, VKey<T>> fks =
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(clazz, foreignKeys, now);
|
||||
if (!fks.keySet().equals(foreignKeys)) {
|
||||
throw new InvalidReferencesException(
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
@@ -23,6 +22,7 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
@@ -68,7 +68,7 @@ public class RdapDomainAction extends RdapActionBase {
|
||||
}
|
||||
// The query string is not used; the RDAP syntax is /rdap/domain/mydomain.com.
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : rdapJsonFormatter.getRequestTime());
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -59,6 +58,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* RDAP action for domain search requests.
|
||||
@@ -183,7 +183,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
private DomainSearchResponse searchByDomainNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
return makeSearchResults(
|
||||
shouldBeVisible(domain) ? ImmutableList.of(domain.get()) : ImmutableList.of());
|
||||
@@ -333,40 +333,35 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
/** Assembles a list of {@link Host} keys by name when the pattern has no wildcard. */
|
||||
private ImmutableList<VKey<Host>> getNameserverRefsByLdhNameWithoutWildcard(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime();
|
||||
// If we need to check the sponsoring registrar, we need to load the resource rather than just
|
||||
// the key.
|
||||
Optional<String> desiredRegistrar = getDesiredRegistrar();
|
||||
String queryString = partialStringQuery.getInitialString();
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
ForeignKeyUtils.loadResourceByCache(Host.class, queryString, timeToQuery);
|
||||
return (host.isEmpty()
|
||||
|| !desiredRegistrar.get().equals(host.get().getPersistedCurrentSponsorRegistrarId()))
|
||||
? ImmutableList.of()
|
||||
: ImmutableList.of(host.get().createVKey());
|
||||
} else {
|
||||
VKey<Host> hostKey =
|
||||
ForeignKeyUtils.load(
|
||||
Host.class,
|
||||
partialStringQuery.getInitialString(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
return (hostKey == null) ? ImmutableList.of() : ImmutableList.of(hostKey);
|
||||
Optional<VKey<Host>> hostKey =
|
||||
ForeignKeyUtils.loadKeyByCache(Host.class, queryString, timeToQuery);
|
||||
return hostKey.map(ImmutableList::of).orElseGet(ImmutableList::of);
|
||||
}
|
||||
}
|
||||
|
||||
/** Assembles a list of {@link Host} keys by name using a superordinate domain suffix. */
|
||||
private ImmutableList<VKey<Host>> getNameserverRefsByLdhNameWithSuffix(
|
||||
final RdapSearchPattern partialStringQuery) {
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
DateTime timeToQuery = shouldIncludeDeleted() ? START_OF_TIME : getRequestTime();
|
||||
// The suffix must be a domain that we manage. That way, we can look up the domain and search
|
||||
// through the subordinate hosts. This is more efficient, and lets us permit wildcard searches
|
||||
// with no initial string.
|
||||
Domain domain =
|
||||
loadByForeignKeyByCache(
|
||||
Domain.class,
|
||||
partialStringQuery.getSuffix(),
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime())
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getSuffix(), timeToQuery)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new UnprocessableEntityException(
|
||||
@@ -379,9 +374,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
// then the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
if (desiredRegistrar.isPresent()) {
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
Optional<Host> host = ForeignKeyUtils.loadResourceByCache(Host.class, fqhn, timeToQuery);
|
||||
if (host.isPresent()
|
||||
&& desiredRegistrar
|
||||
.get()
|
||||
@@ -389,14 +382,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
|
||||
builder.add(host.get().createVKey());
|
||||
}
|
||||
} else {
|
||||
VKey<Host> hostKey =
|
||||
ForeignKeyUtils.load(
|
||||
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
if (hostKey != null) {
|
||||
builder.add(hostKey);
|
||||
} else {
|
||||
logger.atWarning().log("Host key unexpectedly null.");
|
||||
}
|
||||
Optional<VKey<Host>> hostKey =
|
||||
ForeignKeyUtils.loadKeyByCache(Host.class, fqhn, timeToQuery);
|
||||
hostKey.ifPresentOrElse(
|
||||
builder::add, () -> logger.atWarning().log("Host key unexpectedly null."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.rdap.RdapJsonFormatter.OutputDataType;
|
||||
import google.registry.rdap.RdapMetrics.EndpointType;
|
||||
@@ -63,7 +63,7 @@ public class RdapNameserverAction extends RdapActionBase {
|
||||
// If there are no undeleted nameservers with the given name, the foreign key should point to
|
||||
// the most recently deleted one.
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Host.class,
|
||||
pathSearchString,
|
||||
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.rdap;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCache;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaTm;
|
||||
import static google.registry.request.Action.Method.GET;
|
||||
import static google.registry.request.Action.Method.HEAD;
|
||||
@@ -25,6 +24,7 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.primitives.Booleans;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
@@ -159,7 +159,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
.setIncompletenessWarningType(IncompletenessWarningType.COMPLETE);
|
||||
|
||||
Optional<Host> host =
|
||||
loadByForeignKeyByCache(
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Host.class, partialStringQuery.getInitialString(), getRequestTime());
|
||||
|
||||
metricInformationBuilder.setNumHostsRetrieved(host.isPresent() ? 1 : 0);
|
||||
@@ -176,7 +176,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
private NameserverSearchResponse searchByNameUsingSuperordinateDomain(
|
||||
RdapSearchPattern partialStringQuery) {
|
||||
Optional<Domain> domain =
|
||||
loadByForeignKeyByCache(Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
ForeignKeyUtils.loadResourceByCache(
|
||||
Domain.class, partialStringQuery.getSuffix(), getRequestTime());
|
||||
if (domain.isEmpty()) {
|
||||
// Don't allow wildcards with suffixes which are not domains we manage. That would risk a
|
||||
// table scan in many easily foreseeable cases. The user might ask for ns*.zombo.com,
|
||||
@@ -194,7 +195,8 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
|
||||
// We can't just check that the host name starts with the initial query string, because
|
||||
// then the query ns.exam*.example.com would match against nameserver ns.example.com.
|
||||
if (partialStringQuery.matches(fqhn)) {
|
||||
Optional<Host> host = loadByForeignKeyByCache(Host.class, fqhn, getRequestTime());
|
||||
Optional<Host> host =
|
||||
ForeignKeyUtils.loadResourceByCache(Host.class, fqhn, getRequestTime());
|
||||
if (shouldBeVisible(host)) {
|
||||
hostList.add(host.get());
|
||||
if (hostList.size() > rdapResultSetMaxSize) {
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2021 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.rde;
|
||||
|
||||
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.model.rde.RdeMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Loading cache that turns a resource into XML for the various points in time and modes. */
|
||||
public class RdeFragmenter {
|
||||
private final Map<WatermarkModePair, Optional<DepositFragment>> cache = new HashMap<>();
|
||||
private final ImmutableMap<DateTime, Supplier<EppResource>> resourceAtTimes;
|
||||
private final RdeMarshaller marshaller;
|
||||
|
||||
long cacheHits = 0;
|
||||
long resourcesNotFound = 0;
|
||||
long resourcesFound = 0;
|
||||
|
||||
public RdeFragmenter(
|
||||
ImmutableMap<DateTime, Supplier<EppResource>> resourceAtTimes, RdeMarshaller marshaller) {
|
||||
this.resourceAtTimes = resourceAtTimes;
|
||||
this.marshaller = marshaller;
|
||||
}
|
||||
|
||||
public Optional<DepositFragment> marshal(DateTime watermark, RdeMode mode) {
|
||||
Optional<DepositFragment> result = cache.get(WatermarkModePair.create(watermark, mode));
|
||||
if (result != null) {
|
||||
cacheHits++;
|
||||
return result;
|
||||
}
|
||||
EppResource resource = resourceAtTimes.get(watermark).get();
|
||||
if (resource == null) {
|
||||
result = Optional.empty();
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
resourcesNotFound++;
|
||||
return result;
|
||||
}
|
||||
resourcesFound++;
|
||||
if (resource instanceof Domain) {
|
||||
result = Optional.of(marshaller.marshalDomain((Domain) resource, mode));
|
||||
cache.put(WatermarkModePair.create(watermark, mode), result);
|
||||
return result;
|
||||
} else if (resource instanceof Contact) {
|
||||
result = Optional.of(marshaller.marshalContact((Contact) resource));
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
return result;
|
||||
} else if (resource instanceof Host host) {
|
||||
result =
|
||||
Optional.of(
|
||||
host.isSubordinate()
|
||||
? marshaller.marshalSubordinateHost(
|
||||
host,
|
||||
// Note that loadAtPointInTime() does cloneProjectedAtTime(watermark) for
|
||||
// us.
|
||||
Objects.requireNonNull(
|
||||
loadAtPointInTime(
|
||||
tm().loadByKey(host.getSuperordinateDomain()), watermark)))
|
||||
: marshaller.marshalExternalHost(host));
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.FULL), result);
|
||||
cache.put(WatermarkModePair.create(watermark, RdeMode.THIN), result);
|
||||
return result;
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"Resource %s of type %s cannot be converted to XML.",
|
||||
resource, resource.getClass().getSimpleName()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Map key for {@link RdeFragmenter} cache. */
|
||||
record WatermarkModePair(DateTime watermark, RdeMode mode) {
|
||||
|
||||
static WatermarkModePair create(DateTime watermark, RdeMode mode) {
|
||||
return new WatermarkModePair(watermark, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,10 @@ class CommandUtilities {
|
||||
}
|
||||
|
||||
public VKey<? extends EppResource> getKey(String uniqueId, DateTime now) {
|
||||
return ForeignKeyUtils.load(clazz, uniqueId, now);
|
||||
return ForeignKeyUtils.loadKey(clazz, uniqueId, now)
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(String.format("Invalid resource ID %s", uniqueId)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKeyByCacheIfEnabled;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
|
||||
|
||||
@@ -26,6 +25,7 @@ import com.google.common.collect.Sets;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.batch.RelockDomainAction;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.billing.BillingBase.Reason;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -327,7 +327,7 @@ public final class DomainLockUtils {
|
||||
|
||||
private Domain getDomain(String domainName, String registrarId, DateTime now) {
|
||||
Domain domain =
|
||||
loadByForeignKeyByCacheIfEnabled(Domain.class, domainName, now)
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, now)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Domain doesn't exist"));
|
||||
// The user must have specified either the correct registrar ID or the admin registrar ID
|
||||
checkArgument(
|
||||
|
||||
@@ -24,7 +24,7 @@ import com.beust.jcommander.Parameters;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -33,7 +33,6 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Tool to enqueue a poll message for a registrar.
|
||||
@@ -85,11 +84,9 @@ class EnqueuePollMessageCommand extends MutatingCommand {
|
||||
!sendToAll || isNullOrEmpty(clientIds), "Cannot specify both --all and --clients");
|
||||
tm().transact(
|
||||
() -> {
|
||||
Optional<Domain> domainOpt =
|
||||
ForeignKeyUtils.loadResource(Domain.class, domainName, tm().getTransactionTime());
|
||||
checkArgument(
|
||||
domainOpt.isPresent(), "Domain %s doesn't exist or isn't active", domainName);
|
||||
Domain domain = domainOpt.get();
|
||||
Domain domain =
|
||||
ResourceFlowUtils.loadAndVerifyExistence(
|
||||
Domain.class, domainName, tm().getTransactionTime());
|
||||
ImmutableList<String> registrarIds;
|
||||
if (sendToAll) {
|
||||
registrarIds =
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.tools;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -60,7 +59,6 @@ final class GetHistoryEntriesCommand implements Command {
|
||||
type != null && uniqueId != null,
|
||||
"If either of 'type' or 'id' are set then both must be");
|
||||
VKey<? extends EppResource> parentKey = type.getKey(uniqueId, DateTime.now(UTC));
|
||||
checkArgumentNotNull(parentKey, "Invalid resource ID");
|
||||
historyEntries = HistoryEntryDao.loadHistoryObjectsForResource(parentKey, after, before);
|
||||
} else {
|
||||
historyEntries = HistoryEntryDao.loadAllHistoryObjects(after, before);
|
||||
|
||||
@@ -18,19 +18,17 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static google.registry.util.CollectionUtils.findDuplicates;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.tools.soy.DomainRenewSoyInfo;
|
||||
import google.registry.util.Clock;
|
||||
import jakarta.inject.Inject;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
@@ -71,17 +69,15 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern("YYYY-MM-dd");
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
|
||||
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
|
||||
checkArgument(period < 10, "Cannot renew domains for 10 or more years");
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domainName : mainParameters) {
|
||||
Optional<Domain> domainOptional = ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
|
||||
Domain domain = domainOptional.get();
|
||||
|
||||
SoyMapData soyMapData =
|
||||
new SoyMapData(
|
||||
"domainName", domain.getDomainName(),
|
||||
|
||||
@@ -17,9 +17,7 @@ package google.registry.tools;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.Parameters;
|
||||
@@ -30,6 +28,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.template.soy.data.SoyListData;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
@@ -44,7 +43,6 @@ import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
@@ -127,13 +125,13 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
|
||||
@Inject Clock clock;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
superuser = true;
|
||||
DateTime now = clock.nowUtc();
|
||||
Optional<Domain> domainOpt = ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOpt, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = domainOpt.get();
|
||||
Set<String> missingHosts = difference(newHosts, checkResourcesExist(Host.class, newHosts, now));
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
Set<String> missingHosts =
|
||||
difference(newHosts, ForeignKeyUtils.loadKeys(Host.class, newHosts, now).keySet());
|
||||
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
|
||||
checkArgument(
|
||||
locksToPreserve.isEmpty() || undo,
|
||||
|
||||
@@ -87,7 +87,7 @@ class UnrenewDomainCommand extends ConfirmingCommand {
|
||||
new ImmutableMap.Builder<>();
|
||||
|
||||
for (String domainName : mainParameters) {
|
||||
if (ForeignKeyUtils.load(Domain.class, domainName, START_OF_TIME) == null) {
|
||||
if (ForeignKeyUtils.loadKey(Domain.class, domainName, START_OF_TIME).isEmpty()) {
|
||||
domainsNonexistentBuilder.add(domainName);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
|
||||
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
|
||||
import com.beust.jcommander.Parameter;
|
||||
@@ -30,7 +29,7 @@ import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.template.soy.data.SoyMapData;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.GracePeriodBase;
|
||||
@@ -42,7 +41,6 @@ import jakarta.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -146,7 +144,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
boolean forceInPendingDelete;
|
||||
|
||||
@Override
|
||||
protected void initMutatingEppToolCommand() {
|
||||
protected void initMutatingEppToolCommand()
|
||||
throws ResourceFlowUtils.ResourceDoesNotExistException {
|
||||
if (!nameservers.isEmpty()) {
|
||||
checkArgument(
|
||||
addNameservers.isEmpty() && removeNameservers.isEmpty(),
|
||||
@@ -184,9 +183,7 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
ImmutableSet.Builder<String> autorenewGracePeriodWarningDomains = new ImmutableSet.Builder<>();
|
||||
DateTime now = clock.nowUtc();
|
||||
for (String domainName : domains) {
|
||||
Optional<Domain> domainOptional = ForeignKeyUtils.loadResource(Domain.class, domainName, now);
|
||||
checkArgumentPresent(domainOptional, "Domain '%s' does not exist or is deleted", domainName);
|
||||
Domain domain = domainOptional.get();
|
||||
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
|
||||
checkArgument(
|
||||
!domain.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
|
||||
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
|
||||
|
||||
@@ -18,7 +18,7 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
|
||||
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.console.ConsolePermission;
|
||||
import google.registry.model.console.User;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -55,7 +55,7 @@ public class ConsoleDomainGetAction extends ConsoleApiAction {
|
||||
Optional<Domain> possibleDomain =
|
||||
tm().transact(
|
||||
() ->
|
||||
EppResourceUtils.loadByForeignKeyByCacheIfEnabled(
|
||||
ForeignKeyUtils.loadResourceByCacheIfEnabled(
|
||||
Domain.class, paramDomain, tm().getTransactionTime()));
|
||||
if (possibleDomain.isEmpty()) {
|
||||
consoleApiParams.response().setStatus(SC_NOT_FOUND);
|
||||
|
||||
@@ -239,7 +239,7 @@ public class BsaValidateActionTest {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS.minus(Duration.standardSeconds(1)));
|
||||
assertThat(action.isStalenessAllowed(domain.createVKey())).isTrue();
|
||||
assertThat(action.isStalenessAllowed(domain)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -247,7 +247,7 @@ public class BsaValidateActionTest {
|
||||
persistBsaLabel("label");
|
||||
Domain domain = persistActiveDomain("label.app", fakeClock.nowUtc());
|
||||
fakeClock.advanceBy(MAX_STALENESS);
|
||||
assertThat(action.isStalenessAllowed(domain.createVKey())).isFalse();
|
||||
assertThat(action.isStalenessAllowed(domain)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -752,11 +752,7 @@ class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow, Domain
|
||||
persistResource(
|
||||
DatabaseHelper.newDomain("example1.tld")
|
||||
.asBuilder()
|
||||
.setRegistrant(
|
||||
Optional.of(
|
||||
ForeignKeyUtils.loadResource(Contact.class, "sh8013", clock.nowUtc())
|
||||
.get()
|
||||
.createVKey()))
|
||||
.setRegistrant(ForeignKeyUtils.loadKey(Contact.class, "sh8013", clock.nowUtc()))
|
||||
.setNameservers(ImmutableSet.of(host.createVKey()))
|
||||
.setDeletionTime(START_OF_TIME)
|
||||
.build());
|
||||
|
||||
@@ -76,7 +76,6 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.CloudTasksHelper.TaskMatcher;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -185,8 +184,7 @@ class HostUpdateFlowTest extends ResourceFlowTestCase<HostUpdateFlow, Host> {
|
||||
Host renamedHost = doSuccessfulTest();
|
||||
assertThat(renamedHost.isSubordinate()).isTrue();
|
||||
assertHostDnsRequests("ns1.example.tld", "ns2.example.tld");
|
||||
VKey<Host> oldVKeyAfterRename = ForeignKeyUtils.load(Host.class, oldHostName(), clock.nowUtc());
|
||||
assertThat(oldVKeyAfterRename).isNull();
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, oldHostName(), clock.nowUtc())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -48,7 +48,10 @@ class ForeignKeyUtilsTest {
|
||||
|
||||
@RegisterExtension
|
||||
public final TestCacheExtension testCacheExtension =
|
||||
new TestCacheExtension.Builder().withForeignKeyCache(Duration.ofDays(1)).build();
|
||||
new TestCacheExtension.Builder()
|
||||
.withForeignKeyRepoIdCache(Duration.ofDays(1))
|
||||
.withForeignKeyResourceCache(Duration.ofDays(1))
|
||||
.build();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
@@ -56,46 +59,48 @@ class ForeignKeyUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadHost() {
|
||||
void testSuccess_loadHostKey() {
|
||||
Host host = persistActiveHost("ns1.example.com");
|
||||
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.isEqualTo(host.createVKey());
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.hasValue(host.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadDomain() {
|
||||
void testSuccess_loadDomainKey() {
|
||||
Domain domain = persistActiveDomain("example.com");
|
||||
assertThat(ForeignKeyUtils.load(Domain.class, "example.com", fakeClock.nowUtc()))
|
||||
.isEqualTo(domain.createVKey());
|
||||
assertThat(ForeignKeyUtils.loadKey(Domain.class, "example.com", fakeClock.nowUtc()))
|
||||
.hasValue(domain.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadContact() {
|
||||
void testSuccess_loadContactKey() {
|
||||
Contact contact = persistActiveContact("john-doe");
|
||||
assertThat(ForeignKeyUtils.load(Contact.class, "john-doe", fakeClock.nowUtc()))
|
||||
.isEqualTo(contact.createVKey());
|
||||
assertThat(ForeignKeyUtils.loadKey(Contact.class, "john-doe", fakeClock.nowUtc()))
|
||||
.hasValue(contact.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadMostRecentResource() {
|
||||
void testSuccess_loadKeyMostRecentResource() {
|
||||
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());
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.hasValue(newHost.createVKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadNonexistentForeignKey_returnsNull() {
|
||||
assertThat(ForeignKeyUtils.load(Host.class, "ns1.example.com", fakeClock.nowUtc())).isNull();
|
||||
void testSuccess_loadKeyNonexistentForeignKey_returnsNull() {
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadDeletedForeignKey_returnsNull() {
|
||||
void testSuccess_loadKeyDeletedForeignKey_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();
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,16 +108,17 @@ class ForeignKeyUtilsTest {
|
||||
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();
|
||||
assertThat(ForeignKeyUtils.loadKey(Host.class, "ns1.example.com", fakeClock.nowUtc()))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_batchLoad_skipsDeletedAndNonexistent() {
|
||||
void testSuccess_batchLoadKeys_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(
|
||||
ForeignKeyUtils.loadKeys(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
@@ -121,7 +127,7 @@ class ForeignKeyUtilsTest {
|
||||
fakeClock.advanceOneMilli();
|
||||
Host newHost1 = persistActiveHost("ns1.example.com");
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
@@ -129,12 +135,12 @@ class ForeignKeyUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccess_loadHostsCached_cacheIsStale() {
|
||||
void testSuccess_loadHostKeysCached_cacheIsStale() {
|
||||
Host host1 = persistActiveHost("ns1.example.com");
|
||||
Host host2 = persistActiveHost("ns2.example.com");
|
||||
persistResource(host2.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
@@ -144,7 +150,7 @@ class ForeignKeyUtilsTest {
|
||||
persistActiveHost("ns1.example.com");
|
||||
// Even though a new host1 is now live, the cache still returns the VKey to the old one.
|
||||
assertThat(
|
||||
ForeignKeyUtils.loadByCacheIfEnabled(
|
||||
ForeignKeyUtils.loadKeysByCacheIfEnabled(
|
||||
Host.class,
|
||||
ImmutableList.of("ns1.example.com", "ns2.example.com", "ns3.example.com"),
|
||||
fakeClock.nowUtc()))
|
||||
|
||||
@@ -60,8 +60,13 @@ public class TestCacheExtension implements BeforeEachCallback, AfterEachCallback
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withForeignKeyCache(Duration expiry) {
|
||||
cacheHandlers.add(new TestCacheHandler(ForeignKeyUtils::setCacheForTest, expiry));
|
||||
public Builder withForeignKeyRepoIdCache(Duration expiry) {
|
||||
cacheHandlers.add(new TestCacheHandler(ForeignKeyUtils::setRepoIdCacheForTest, expiry));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withForeignKeyResourceCache(Duration expiry) {
|
||||
cacheHandlers.add(new TestCacheHandler(ForeignKeyUtils::setResourceCacheForTest, expiry));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,9 +19,7 @@ import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static com.google.common.net.HttpHeaders.LOCATION;
|
||||
import static com.google.common.net.MediaType.FORM_DATA;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.ForeignKeyUtils.load;
|
||||
import static google.registry.testing.DatabaseHelper.createTld;
|
||||
import static google.registry.testing.DatabaseHelper.loadByKey;
|
||||
import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||
import static google.registry.testing.DatabaseHelper.newDomain;
|
||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||
@@ -40,10 +38,10 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import google.registry.batch.CloudTasksUtils;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.tld.Tld;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
||||
import google.registry.request.RequestParameters;
|
||||
@@ -72,19 +70,19 @@ class NordnUploadActionTest {
|
||||
|
||||
private static final String CLAIMS_CSV =
|
||||
"""
|
||||
1,2010-05-04T10:11:12.000Z,2
|
||||
roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime,application-datetime
|
||||
6-TLD,claims-landrush2.tld,landrush2tcn,88888,2010-05-03T10:11:12.000Z,2010-05-03T08:11:12.000Z
|
||||
8-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-04T10:11:12.000Z,2010-05-04T09:11:12.000Z
|
||||
""";
|
||||
1,2010-05-04T10:11:12.000Z,2
|
||||
roid,domain-name,notice-id,registrar-id,registration-datetime,ack-datetime,application-datetime
|
||||
6-TLD,claims-landrush2.tld,landrush2tcn,88888,2010-05-03T10:11:12.000Z,2010-05-03T08:11:12.000Z
|
||||
8-TLD,claims-landrush1.tld,landrush1tcn,99999,2010-05-04T10:11:12.000Z,2010-05-04T09:11:12.000Z
|
||||
""";
|
||||
|
||||
private static final String SUNRISE_CSV =
|
||||
"""
|
||||
1,2010-05-04T10:11:12.000Z,2
|
||||
roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime
|
||||
2-TLD,sunrise2.tld,new-smdid,88888,2010-05-01T10:11:12.000Z
|
||||
4-TLD,sunrise1.tld,my-smdid,99999,2010-05-02T10:11:12.000Z
|
||||
""";
|
||||
1,2010-05-04T10:11:12.000Z,2
|
||||
roid,domain-name,SMD-id,registrar-id,registration-datetime,application-datetime
|
||||
2-TLD,sunrise2.tld,new-smdid,88888,2010-05-01T10:11:12.000Z
|
||||
4-TLD,sunrise1.tld,my-smdid,99999,2010-05-02T10:11:12.000Z
|
||||
""";
|
||||
|
||||
private static final String LOCATION_URL = "http://trololol";
|
||||
|
||||
@@ -142,12 +140,14 @@ class NordnUploadActionTest {
|
||||
@Test
|
||||
void testSuccess_nothingScheduled() {
|
||||
persistResource(
|
||||
loadByKey(load(Domain.class, "claims-landrush1.tld", clock.nowUtc()))
|
||||
ForeignKeyUtils.loadResource(Domain.class, "claims-landrush1.tld", clock.nowUtc())
|
||||
.get()
|
||||
.asBuilder()
|
||||
.setLordnPhase(LordnPhase.NONE)
|
||||
.build());
|
||||
persistResource(
|
||||
loadByKey(load(Domain.class, "claims-landrush2.tld", clock.nowUtc()))
|
||||
ForeignKeyUtils.loadResource(Domain.class, "claims-landrush2.tld", clock.nowUtc())
|
||||
.get()
|
||||
.asBuilder()
|
||||
.setLordnPhase(LordnPhase.NONE)
|
||||
.build());
|
||||
@@ -233,8 +233,7 @@ class NordnUploadActionTest {
|
||||
}
|
||||
|
||||
private void verifyColumnCleared(String domainName) {
|
||||
VKey<Domain> domainKey = load(Domain.class, domainName, clock.nowUtc());
|
||||
Domain domain = loadByKey(domainKey);
|
||||
Domain domain = ForeignKeyUtils.loadResource(Domain.class, domainName, clock.nowUtc()).get();
|
||||
assertThat(domain.getLordnPhase()).isEqualTo(LordnPhase.NONE);
|
||||
}
|
||||
|
||||
|
||||
@@ -169,13 +169,14 @@ class EnqueuePollMessageCommandTest extends CommandTestCase<EnqueuePollMessageCo
|
||||
|
||||
@Test
|
||||
void testNonexistentDomain() throws Exception {
|
||||
IllegalArgumentException thrown =
|
||||
RuntimeException thrown =
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
RuntimeException.class,
|
||||
() -> runCommandForced("--domain=example2.tld", "--message=This domain needs help"));
|
||||
assertThat(thrown)
|
||||
.hasCauseThat()
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Domain example2.tld doesn't exist or isn't active");
|
||||
.isEqualTo("The domain with given ID (example2.tld) doesn't exist.");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.testing.DatabaseHelper;
|
||||
@@ -174,19 +175,23 @@ public class RenewDomainCommandTest extends EppToolCommandTestCase<RenewDomainCo
|
||||
|
||||
@Test
|
||||
void testFailure_domainDoesntExist() {
|
||||
IllegalArgumentException e =
|
||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("nonexistent.tld"));
|
||||
assertThat(e)
|
||||
assertThat(
|
||||
assertThrows(
|
||||
ResourceFlowUtils.ResourceDoesNotExistException.class,
|
||||
() -> runCommandForced("nonexistent.tld")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("Domain 'nonexistent.tld' does not exist or is deleted");
|
||||
.isEqualTo("The domain with given ID (nonexistent.tld) doesn't exist.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailure_domainIsDeleted() {
|
||||
persistDeletedDomain("deleted.tld", DateTime.parse("2012-10-05T05:05:05Z"));
|
||||
IllegalArgumentException e =
|
||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("deleted.tld"));
|
||||
assertThat(e).hasMessageThat().isEqualTo("Domain 'deleted.tld' does not exist or is deleted");
|
||||
assertThat(
|
||||
assertThrows(
|
||||
ResourceFlowUtils.ResourceDoesNotExistException.class,
|
||||
() -> runCommandForced("deleted.tld")))
|
||||
.hasMessageThat()
|
||||
.isEqualTo("The domain with given ID (deleted.tld) doesn't exist.");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user