1
0
mirror of https://github.com/google/nomulus synced 2026-07-08 17:16:54 +00:00

Compare commits

...

12 Commits

Author SHA1 Message Date
Weimin Yu a557b3f376 Disable the cron job for ResaveAllEppResourcesPipelineAction (#1799)
See b/249863289 for more information.
2022-09-30 12:05:29 -04:00
sarahcaseybot f4a49864b5 Add a get_package_promotion Command (#1793)
* Add a get_package_promotion Command

* add changes to loadByTokenString

* Fix test
2022-09-29 15:02:16 -04:00
gbrodman acdecca181 Don't create unnecessary synthetic History objects (#1796) 2022-09-26 13:41:57 -04:00
gbrodman 5264ab3fc3 Create pipeline to save synthetic DomainHistory objects (#1795)
This runs over all domains that weren't deleted as of September 5. This
will fix most of b/248112997, which is itself caused by b/245940594 --
creating synthetic history objects means that the RDE pipeline will look
at those instead of the potentially-no-longer-valid data in the old
history objects.
2022-09-22 14:58:50 -04:00
sarahcaseybot a9d59e4d6e Fix id generation in PackagePromotion (#1788)
* Fix id generation in PackagePromotion

* Fix update command tests
2022-09-21 15:19:49 -04:00
sarahcaseybot 1d3738da27 Add mutating commands for PackagePromotion (#1769)
* Add mutating commands for PackagePromotion

* Add checkAllocationToken methods

* Remove abstract methods

* Add better comments

* Small fixes

* Remove unneccesary init method

* Only assert in transaction in helper method
2022-09-21 12:38:09 -04:00
Lai Jiang 82a3a49268 Rename various fields and classes after migration (#1784)
Also fixed a bug introduced in #1785 where identity checked were performed instead of equality. This resulted in two sets containing the same elements not being regarded as equal and subsequent DNS updated being unnecessarily enqueued.
2022-09-21 11:49:22 -04:00
Pavlo Tkach 5bbad483e4 Fail genenerate invoices job when billing events not finished expanding (#1791) 2022-09-21 09:20:05 -04:00
Pavlo Tkach f6e9dae58d Add REMOVEPACKAGE token functionality to domain transfer flow (#1792) 2022-09-19 15:11:36 -04:00
Lai Jiang c4c1c72306 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.
2022-09-19 14:41:19 -04:00
Pavlo Tkach 775f672f2a Do not enqueue DNS updates when flow doesn't affect nameservers (#1785) 2022-09-16 16:59:04 -04:00
gbrodman 372c854268 Create a scrap command to cancel OneTime billing events by ID (#1790)
This allows us to correct situations where we have erroneously charged
registrars for an action, without explicitly issuing a refund.
2022-09-16 16:17:31 -04:00
154 changed files with 2591 additions and 1172 deletions
+4
View File
@@ -707,6 +707,10 @@ createToolTask(
createToolTask(
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
createToolTask(
'createSyntheticDomainHistories',
'google.registry.tools.javascrap.CreateSyntheticDomainHistoriesPipeline')
project.tasks.create('generateSqlSchema', JavaExec) {
classpath = sourceSets.nonprod.runtimeClasspath
main = 'google.registry.tools.DevTool'
@@ -91,7 +91,7 @@ public class DeleteProberDataAction implements Runnable {
// Note: creationTime must be compared to a Java object (CreateAutoTimestamp) but deletionTime can
// be compared directly to the SQL timestamp (it's a DateTime)
private static final String DOMAIN_QUERY_STRING =
"FROM Domain d WHERE d.tld IN :tlds AND d.fullyQualifiedDomainName NOT LIKE 'nic.%' AND"
"FROM Domain d WHERE d.tld IN :tlds AND d.domainName NOT LIKE 'nic.%' AND"
+ " (d.subordinateHosts IS EMPTY OR d.subordinateHosts IS NULL) AND d.creationTime <"
+ " :creationTimeCutoff AND ((d.creationTime <= :nowAutoTimestamp AND d.deletionTime >"
+ " current_timestamp()) OR d.deletionTime < :nowMinusSoftDeleteDelay) ORDER BY d.repoId";
@@ -220,7 +220,7 @@ public class DeleteProberDataAction implements Runnable {
private void hardDeleteDomainsAndHosts(
ImmutableList<String> domainRepoIds, ImmutableList<String> hostNames) {
jpaTm()
.query("DELETE FROM Host WHERE fullyQualifiedHostName IN :hostNames")
.query("DELETE FROM Host WHERE hostName IN :hostNames")
.setParameter("hostNames", hostNames)
.executeUpdate();
jpaTm()
@@ -301,7 +301,7 @@ public class RdePipeline implements Serializable {
.apply(
"Read all production Registrars",
RegistryJpaIO.read(
"SELECT clientIdentifier FROM Registrar WHERE type NOT IN (:types)",
"SELECT registrarId FROM Registrar WHERE type NOT IN (:types)",
ImmutableMap.of("types", IGNORED_REGISTRAR_TYPES),
String.class,
id -> VKey.createSql(Registrar.class, id)))
@@ -75,8 +75,8 @@ public class SafeBrowsingTransforms {
private final String apiKey;
/**
* Maps a domain name's {@code fullyQualifiedDomainName} to its corresponding {@link
* DomainNameInfo} to facilitate batching SafeBrowsing API requests.
* Maps a domain name's {@code domainName} to its corresponding {@link DomainNameInfo} to
* facilitate batching SafeBrowsing API requests.
*/
private final Map<String, DomainNameInfo> domainNameInfoBuffer =
new LinkedHashMap<>(BATCH_SIZE);
@@ -186,8 +186,8 @@ public class SafeBrowsingTransforms {
private JSONObject createRequestBody() throws JSONException {
// Accumulate all domain names to evaluate.
JSONArray threatArray = new JSONArray();
for (String fullyQualifiedDomainName : domainNameInfoBuffer.keySet()) {
threatArray.put(new JSONObject().put("url", fullyQualifiedDomainName));
for (String domainName : domainNameInfoBuffer.keySet()) {
threatArray.put(new JSONObject().put("url", domainName));
}
// Construct the JSON request body
return new JSONObject()
@@ -113,7 +113,7 @@ public class Spec11Pipeline implements Serializable {
Read<Object[], KV<String, String>> read =
RegistryJpaIO.read(
"select d.repoId, r.emailAddress from Domain d join Registrar r on"
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = 'REAL' and"
+ " d.currentSponsorClientId = r.registrarId where r.type = 'REAL' and"
+ " d.deletionTime > now()",
false,
Spec11Pipeline::parseRow);
@@ -25,23 +25,23 @@ import org.json.JSONObject;
public abstract class ThreatMatch implements Serializable {
private static final String THREAT_TYPE_FIELD = "threatType";
private static final String DOMAIN_NAME_FIELD = "fullyQualifiedDomainName";
private static final String DOMAIN_NAME_FIELD = "domainName";
/** Returns what kind of threat it is (malware, phishing etc.) */
public abstract String threatType();
/** Returns the fully qualified domain name [SLD].[TLD] of the matched threat. */
public abstract String fullyQualifiedDomainName();
public abstract String domainName();
@VisibleForTesting
static ThreatMatch create(String threatType, String fullyQualifiedDomainName) {
return new AutoValue_ThreatMatch(threatType, fullyQualifiedDomainName);
static ThreatMatch create(String threatType, String domainName) {
return new AutoValue_ThreatMatch(threatType, domainName);
}
/** Returns a {@link JSONObject} representing a subset of this object's data. */
JSONObject toJSON() throws JSONException {
return new JSONObject()
.put(THREAT_TYPE_FIELD, threatType())
.put(DOMAIN_NAME_FIELD, fullyQualifiedDomainName());
.put(DOMAIN_NAME_FIELD, domainName());
}
/** Parses a {@link JSONObject} and returns an equivalent {@link ThreatMatch}. */
@@ -37,7 +37,7 @@ import google.registry.dns.writer.BaseDnsWriter;
import google.registry.dns.writer.DnsWriter;
import google.registry.dns.writer.DnsWriterZone;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
import google.registry.model.tld.Registries;
import google.registry.util.Clock;
@@ -134,10 +134,10 @@ public class CloudDnsWriter extends BaseDnsWriter {
ImmutableSet.Builder<ResourceRecordSet> domainRecords = new ImmutableSet.Builder<>();
// Construct DS records (if any).
Set<DelegationSignerData> dsData = domain.get().getDsData();
Set<DomainDsData> dsData = domain.get().getDsData();
if (!dsData.isEmpty()) {
HashSet<String> dsRrData = new HashSet<>();
for (DelegationSignerData ds : dsData) {
for (DomainDsData ds : dsData) {
dsRrData.add(ds.toRrData());
}
@@ -28,7 +28,7 @@ import google.registry.config.RegistryConfig.Config;
import google.registry.dns.writer.BaseDnsWriter;
import google.registry.dns.writer.DnsWriterZone;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
import google.registry.model.tld.Registries;
import google.registry.util.Clock;
@@ -185,7 +185,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
private RRset makeDelegationSignerSet(Domain domain) {
RRset signerSet = new RRset();
for (DelegationSignerData signerData : domain.getDsData()) {
for (DomainDsData signerData : domain.getDsData()) {
DSRecord dsRecord =
new DSRecord(
toAbsoluteName(domain.getDomainName()),
@@ -102,6 +102,7 @@
<target>backend</target>
</cron>
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
@@ -110,6 +111,7 @@
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
-->
<cron>
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
@@ -267,7 +269,7 @@
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
each month. -->
<schedule>1 of month 17:00</schedule>
<schedule>1 of month 19:00</schedule>
<target>backend</target>
</cron>
@@ -86,6 +86,7 @@
<target>backend</target>
</cron>
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
<description>
@@ -94,6 +95,7 @@
<schedule>1st monday of month 09:00</schedule>
<target>backend</target>
</cron>
-->
<cron>
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
@@ -91,10 +91,10 @@ public class ExportDomainListsAction implements Runnable {
// field that compares with the substituted value.
jpaTm()
.query(
"SELECT fullyQualifiedDomainName FROM Domain "
"SELECT domainName FROM Domain "
+ "WHERE tld = :tld "
+ "AND deletionTime > :now "
+ "ORDER by fullyQualifiedDomainName ASC",
+ "ORDER by domainName ASC",
String.class)
.setParameter("tld", tld)
.setParameter("now", clock.nowUtc())
@@ -114,7 +114,7 @@ class SyncRegistrarsSheet {
// and you'll need to remove deleted columns probably like a week after
// deployment.
//
builder.put("clientIdentifier", convert(registrar.getRegistrarId()));
builder.put("registrarId", convert(registrar.getRegistrarId()));
builder.put("registrarName", convert(registrar.getRegistrarName()));
builder.put("state", convert(registrar.getState()));
builder.put("ianaIdentifier", convert(registrar.getIanaIdentifier()));
@@ -44,8 +44,8 @@ import dagger.Module;
import dagger.Provides;
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.tld.Registry;
import google.registry.model.tld.label.ReservationType;
import google.registry.monitoring.whitebox.CheckApiMetric;
@@ -156,7 +156,7 @@ public class CheckApiAction implements Runnable {
}
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) {
@@ -17,7 +17,6 @@ package google.registry.flows;
import static com.google.common.collect.Sets.intersection;
import static google.registry.model.EppResourceUtils.isLinked;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
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.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
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.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
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
* 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(
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
EppException failfastException =
tm().transact(
() -> {
final ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
if (fki == null) {
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
if (key == null) {
return new ResourceDoesNotExistException(resourceClass, targetId);
}
return isLinked(fki.getResourceKey(), now)
? new ResourceToDeleteIsReferencedException()
: null;
return isLinked(key, now) ? new ResourceToDeleteIsReferencedException() : null;
});
if (failfastException != null) {
throw failfastException;
@@ -118,7 +112,7 @@ 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 = loadAndGetKey(clazz, targetId, now);
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
if (key != null) {
R resource = tm().loadByKey(key);
// 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.AllocationTokenFlowUtils;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Domain;
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.EppResponse;
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
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.
.setAsOfDate(now)
.build());
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains =
ForeignKeyIndex.load(Domain.class, domainNames, now);
ImmutableMap<String, VKey<Domain>> existingDomains =
ForeignKeyUtils.load(Domain.class, domainNames, now);
Optional<AllocationTokenExtension> allocationTokenExtension =
eppInput.getSingleExtension(AllocationTokenExtension.class);
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
@@ -227,7 +227,7 @@ public final class DomainCheckFlow implements Flow {
private Optional<String> getMessageForCheck(
InternetDomainName domainName,
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains,
ImmutableMap<String, VKey<Domain>> existingDomains,
ImmutableMap<InternetDomainName, String> tokenCheckResults,
ImmutableMap<String, TldState> tldStates,
Optional<AllocationToken> allocationToken) {
@@ -251,7 +251,7 @@ public final class DomainCheckFlow implements Flow {
/** Handle the fee check extension. */
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
ImmutableMap<String, InternetDomainName> domainNames,
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains,
ImmutableMap<String, VKey<Domain>> existingDomains,
ImmutableSet<String> availableDomains,
DateTime now,
Optional<AllocationToken> allocationToken)
@@ -297,14 +297,14 @@ public final class DomainCheckFlow implements Flow {
* 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
* 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
* entire entity.
*/
private ImmutableMap<String, Domain> loadDomainsForRestoreChecks(
FeeCheckCommandExtension<?, ?> feeCheck,
ImmutableMap<String, InternetDomainName> domainNames,
ImmutableMap<String, ForeignKeyIndex<Domain>> existingDomains) {
ImmutableMap<String, VKey<Domain>> existingDomains) {
ImmutableList<String> restoreCheckDomains;
if (feeCheck instanceof FeeCheckCommandExtensionV06) {
// 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 =
restoreCheckDomains.stream()
.filter(existingDomains::containsKey)
.collect(toImmutableMap(d -> d, d -> existingDomains.get(d).getResourceKey()));
.collect(toImmutableMap(d -> d, existingDomains::get));
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
return ImmutableMap.copyOf(
@@ -248,7 +248,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
validateRegistrationPeriod(years);
verifyResourceDoesNotExist(Domain.class, targetId, now, registrarId);
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
InternetDomainName domainName = validateDomainName(command.getFullyQualifiedDomainName());
InternetDomainName domainName = validateDomainName(command.getDomainName());
String domainLabel = domainName.parts().get(0);
Registry registry = Registry.get(domainName.parent().toString());
validateCreateCommandContactsAndNameservers(command, registry, domainName);
@@ -639,10 +639,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
}
private static PollMessage.OneTime createNameCollisionOneTimePollMessage(
String fullyQualifiedDomainName,
HistoryEntry historyEntry,
String registrarId,
DateTime now) {
String domainName, HistoryEntry historyEntry, String registrarId, DateTime now) {
return new PollMessage.OneTime.Builder()
.setRegistrarId(registrarId)
.setEventTime(now)
@@ -650,7 +647,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
.setResponseData(
ImmutableList.of(
DomainPendingActionNotificationResponse.create(
fullyQualifiedDomainName, true, historyEntry.getTrid(), now)))
domainName, true, historyEntry.getTrid(), now)))
.setHistoryEntry(historyEntry)
.build();
}
@@ -105,7 +105,7 @@ import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
import google.registry.model.domain.launch.LaunchPhase;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.SecDnsCreateExtension;
import google.registry.model.domain.secdns.SecDnsInfoExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
@@ -316,14 +316,14 @@ public class DomainFlowUtils {
}
/** Check that the DS data that will be set on a domain is valid. */
static void validateDsData(Set<DelegationSignerData> dsData) throws EppException {
static void validateDsData(Set<DomainDsData> dsData) throws EppException {
if (dsData != null) {
if (dsData.size() > MAX_DS_RECORDS_PER_DOMAIN) {
throw new TooManyDsRecordsException(
String.format(
"A maximum of %s DS records are allowed per domain.", MAX_DS_RECORDS_PER_DOMAIN));
}
ImmutableList<DelegationSignerData> invalidAlgorithms =
ImmutableList<DomainDsData> invalidAlgorithms =
dsData.stream()
.filter(ds -> !validateAlgorithm(ds.getAlgorithm()))
.collect(toImmutableList());
@@ -333,7 +333,7 @@ public class DomainFlowUtils {
"Domain contains DS record(s) with an invalid algorithm wire value: %s",
invalidAlgorithms));
}
ImmutableList<DelegationSignerData> invalidDigestTypes =
ImmutableList<DomainDsData> invalidDigestTypes =
dsData.stream()
.filter(ds -> !DigestType.fromWireValue(ds.getDigestType()).isPresent())
.collect(toImmutableList());
@@ -343,7 +343,7 @@ public class DomainFlowUtils {
"Domain contains DS record(s) with an invalid digest type: %s",
invalidDigestTypes));
}
ImmutableList<DelegationSignerData> digestsWithInvalidDigestLength =
ImmutableList<DomainDsData> digestsWithInvalidDigestLength =
dsData.stream()
.filter(
ds ->
@@ -920,16 +920,15 @@ public class DomainFlowUtils {
* and we are going to ignore it; clients who don't care about secDNS can just ignore it.
*/
static void addSecDnsExtensionIfPresent(
ImmutableList.Builder<ResponseExtension> extensions,
ImmutableSet<DelegationSignerData> dsData) {
ImmutableList.Builder<ResponseExtension> extensions, ImmutableSet<DomainDsData> dsData) {
if (!dsData.isEmpty()) {
extensions.add(SecDnsInfoExtension.create(dsData));
}
}
/** Update {@link DelegationSignerData} based on an update extension command. */
static ImmutableSet<DelegationSignerData> updateDsData(
ImmutableSet<DelegationSignerData> oldDsData, SecDnsUpdateExtension secDnsUpdate)
/** Update {@link DomainDsData} based on an update extension command. */
static ImmutableSet<DomainDsData> updateDsData(
ImmutableSet<DomainDsData> oldDsData, SecDnsUpdateExtension secDnsUpdate)
throws EppException {
// We don't support 'urgent' because we do everything as fast as we can anyways.
if (Boolean.TRUE.equals(secDnsUpdate.getUrgent())) { // We allow both false and null.
@@ -948,8 +947,8 @@ public class DomainFlowUtils {
if (remove != null && Boolean.FALSE.equals(remove.getAll())) {
throw new SecDnsAllUsageException(); // Explicit all=false is meaningless.
}
Set<DelegationSignerData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
Set<DelegationSignerData> toRemove =
Set<DomainDsData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
Set<DomainDsData> toRemove =
(remove == null)
? ImmutableSet.of()
: (remove.getAll() == null) ? remove.getDsData() : oldDsData;
@@ -1001,9 +1000,9 @@ public class DomainFlowUtils {
validateRegistrantAllowedOnTld(tld, command.getRegistrantContactId());
validateNoDuplicateContacts(command.getContacts());
validateRequiredContactsPresent(command.getRegistrant(), command.getContacts());
ImmutableSet<String> fullyQualifiedHostNames = command.getNameserverFullyQualifiedHostNames();
validateNameserversCountForTld(tld, domainName, fullyQualifiedHostNames.size());
validateNameserversAllowedOnTld(tld, fullyQualifiedHostNames);
ImmutableSet<String> hostNames = command.getNameserverHostNames();
validateNameserversCountForTld(tld, domainName, hostNames.size());
validateNameserversAllowedOnTld(tld, hostNames);
}
/** Validate the secDNS extension, if present. */
@@ -1542,11 +1541,11 @@ public class DomainFlowUtils {
/** Nameservers are not allow-listed for this TLD. */
public static class NameserversNotAllowedForTldException
extends StatusProhibitsOperationException {
public NameserversNotAllowedForTldException(Set<String> fullyQualifiedHostNames) {
public NameserversNotAllowedForTldException(Set<String> hostNames) {
super(
String.format(
"Nameservers '%s' are not allow-listed for this TLD",
Joiner.on(',').join(fullyQualifiedHostNames)));
Joiner.on(',').join(hostNames)));
}
}
@@ -109,7 +109,7 @@ public final class DomainInfoFlow implements Flow {
// This is a policy decision that is left up to us by the rfcs.
DomainInfoData.Builder infoBuilder =
DomainInfoData.newBuilder()
.setFullyQualifiedDomainName(domain.getDomainName())
.setDomainName(domain.getDomainName())
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorRegistrarId())
.setRegistrant(
@@ -32,6 +32,8 @@ import static google.registry.flows.domain.DomainTransferUtils.createLosingTrans
import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData;
import static google.registry.flows.domain.DomainTransferUtils.createTransferResponse;
import static google.registry.flows.domain.DomainTransferUtils.createTransferServerApproveEntities;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -63,6 +65,7 @@ import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
@@ -132,6 +135,8 @@ import org.joda.time.DateTime;
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
* @error {@link
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
*/
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
public final class DomainTransferRequestFlow implements TransactionalFlow {
@@ -169,19 +174,24 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
extensionManager.validate();
DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
Period period =
superuserExtension.isPresent()
? superuserExtension.get().getRenewalPeriod()
: ((Transfer) resourceCommand).getPeriod();
verifyTransferAllowed(existingDomain, period, now, superuserExtension);
verifyTransferAllowed(existingDomain, period, now, superuserExtension, allocationToken);
// If client passed an applicable static token this updates the domain
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
String tld = existingDomain.getTld();
Registry registry = Registry.get(tld);
// An optional extension from the client specifying what they think the transfer should cost.
@@ -293,9 +303,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
Domain existingDomain,
Period period,
DateTime now,
Optional<DomainTransferRequestSuperuserExtension> superuserExtension)
Optional<DomainTransferRequestSuperuserExtension> superuserExtension,
Optional<AllocationToken> allocationToken)
throws EppException {
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
if (!isSuperuser) {
verifyAuthInfoPresentForResourceTransfer(authInfo);
verifyAuthInfo(authInfo.get(), existingDomain);
@@ -218,7 +218,7 @@ public final class DomainTransferUtils {
TransferData transferData,
@Nullable DateTime extendedRegistrationExpirationTime) {
return new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(targetId)
.setDomainName(targetId)
.setGainingRegistrarId(transferData.getGainingRegistrarId())
.setLosingRegistrarId(transferData.getLosingRegistrarId())
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
@@ -74,7 +74,7 @@ import google.registry.model.domain.DomainCommand.Update.Change;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
import google.registry.model.eppcommon.AuthInfo;
@@ -87,6 +87,7 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tld.Registry;
import java.util.Objects;
import java.util.Optional;
import javax.inject.Inject;
import org.joda.time.DateTime;
@@ -181,7 +182,10 @@ public final class DomainUpdateFlow implements TransactionalFlow {
DomainHistory domainHistory =
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
validateNewState(newDomain);
dnsQueue.addDomainRefreshTask(targetId);
if (!Objects.equals(newDomain.getDsData(), existingDomain.getDsData())
|| !Objects.equals(newDomain.getNsHosts(), existingDomain.getNsHosts())) {
dnsQueue.addDomainRefreshTask(targetId);
}
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
entitiesToSave.add(newDomain, domainHistory);
Optional<BillingEvent.OneTime> statusUpdateBillingEvent =
@@ -229,8 +233,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
validateContactsHaveTypes(add.getContacts());
validateContactsHaveTypes(remove.getContacts());
validateRegistrantAllowedOnTld(tld, command.getInnerChange().getRegistrantContactId());
validateNameserversAllowedOnTld(
tld, add.getNameserverFullyQualifiedHostNames());
validateNameserversAllowedOnTld(tld, add.getNameserverHostNames());
}
private Domain performUpdate(Update command, Domain domain, DateTime now) throws EppException {
@@ -260,7 +263,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
secDnsUpdate.isPresent()
? updateDsData(
domain.getDsData().stream()
.map(DelegationSignerData::cloneWithoutDomainRepoId)
.map(DomainDsData::cloneWithoutDomainRepoId)
.collect(toImmutableSet()),
secDnsUpdate.get())
: domain.getDsData())
@@ -268,12 +271,18 @@ public final class DomainUpdateFlow implements TransactionalFlow {
.setLastEppUpdateRegistrarId(registrarId)
.addStatusValues(add.getStatusValues())
.removeStatusValues(remove.getStatusValues())
.addNameservers(add.getNameservers().stream().collect(toImmutableSet()))
.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()))
.removeContacts(remove.getContacts())
.addContacts(add.getContacts())
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
if (!add.getNameservers().isEmpty()) {
domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
}
if (!remove.getNameservers().isEmpty()) {
domainBuilder.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()));
}
Optional<DomainUpdateSuperuserExtension> superuserExt =
eppInput.getSingleExtension(DomainUpdateSuperuserExtension.class);
if (superuserExt.isPresent()) {
@@ -175,11 +175,7 @@ public class AllocationTokenFlowUtils {
return Optional.empty();
}
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
validateToken(
InternetDomainName.from(command.getFullyQualifiedDomainName()),
tokenEntity,
registrarId,
now);
validateToken(InternetDomainName.from(command.getDomainName()), tokenEntity, registrarId, now);
return Optional.of(
tokenCustomLogic.validateToken(command, tokenEntity, registry, registrarId, now));
}
@@ -293,11 +289,11 @@ public class AllocationTokenFlowUtils {
}
}
/** The __REMOVEPACKAGE__ token is missing on a renew package domain command */
/** The __REMOVEPACKAGE__ token is missing on a package domain command */
public static class MissingRemovePackageTokenOnPackageDomainException
extends AssociationProhibitsOperationException {
MissingRemovePackageTokenOnPackageDomainException() {
super("Domains that are inside packages cannot be explicitly renewed");
super("Domains that are inside packages cannot be explicitly renewed or transferred");
}
}
@@ -134,7 +134,7 @@ public final class HostCreateFlow implements TransactionalFlow {
superordinateDomain
.get()
.asBuilder()
.addSubordinateHost(command.getFullyQualifiedHostName())
.addSubordinateHost(command.getHostName())
.build());
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so
// they are only written as NS records from the referencing domain.
@@ -93,7 +93,7 @@ public final class HostInfoFlow implements Flow {
return responseBuilder
.setResData(
hostInfoDataBuilder
.setFullyQualifiedHostName(host.getHostName())
.setHostName(host.getHostName())
.setRepoId(host.getRepoId())
.setStatusValues(statusValues.build())
.setInetAddresses(host.getInetAddresses())
@@ -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.verifySuperordinateDomainNotInPendingDelete;
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.persistence.transaction.TransactionManagerFactory.tm;
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.exceptions.ResourceHasClientUpdateProhibitedException;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.Domain;
import google.registry.model.domain.metadata.MetadataExtension;
@@ -129,7 +129,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
extensionManager.validate();
Update command = (Update) resourceCommand;
Change change = command.getInnerChange();
String suppliedNewHostName = change.getFullyQualifiedHostName();
String suppliedNewHostName = change.getHostName();
DateTime now = tm().getTransactionTime();
validateHostName(targetId);
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
@@ -147,7 +147,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
verifyUpdateAllowed(
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);
}
AddRemove add = command.getInnerAdd();
@@ -260,7 +260,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
dnsQueue.addHostRefreshTask(existingHost.getHostName());
}
// In case of a rename, there are many updates we need to queue up.
if (((Update) resourceCommand).getInnerChange().getFullyQualifiedHostName() != null) {
if (((Update) resourceCommand).getInnerChange().getHostName() != null) {
// If the renamed host is also subordinate, then we must enqueue an update to write the new
// glue.
if (newHost.isSubordinate()) {
@@ -140,10 +140,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
DateTime lastEppUpdateTime;
/** Status values associated with this resource. */
@Ignore
@Column(name = "statuses")
// TODO(b/177567432): rename to "statuses" once we're off datastore.
Set<StatusValue> status;
@Ignore Set<StatusValue> statuses;
public String getRepoId() {
return repoId;
@@ -189,7 +186,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
}
public final ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(status);
return nullToEmptyImmutableCopy(statuses);
}
public DateTime getDeletionTime() {
@@ -307,7 +304,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
statusValue,
resourceClass.getSimpleName());
}
getInstance().status = statusValues;
getInstance().statuses = statusValues;
return thisCastToDerived();
}
@@ -36,7 +36,6 @@ import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntryDao;
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
* cache.
* Loads the last created version of an {@link EppResource} from the database by foreign key,
* using a cache.
*
* <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 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
* timestamp on the resource's entity group (which is essentially free since all writes to the
* entity group must be serialized anyways) 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.
* 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. WHOIS.
@@ -150,19 +148,18 @@ public final class EppResourceUtils {
checkArgument(
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
"loadByForeignKey may only be called for foreign keyed EPP resources");
ForeignKeyIndex<T> fki =
VKey<T> key =
useCache
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
.getOrDefault(foreignKey, null)
: ForeignKeyIndex.load(clazz, foreignKey, now);
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
? ForeignKeyUtils.loadCached(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.loadCached(fki.getResourceKey())
: tm().transact(() -> tm().loadByKeyIfPresent(fki.getResourceKey()).orElse(null));
? EppResource.loadCached(key)
: tm().transact(() -> tm().loadByKeyIfPresent(key).orElse(null));
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
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
* 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(
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
return ForeignKeyIndex.load(clazz, uniqueIds, now).keySet();
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
}
/**
@@ -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, "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);
}
/**
* 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);
}
}
}
@@ -68,7 +68,7 @@ public class OteStats {
((DomainCommand.Create)
((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
.getResourceCommand())
.getFullyQualifiedDomainName()
.getDomainName()
.startsWith(ACE_PREFIX);
private static final Predicate<EppInput> IS_SUBORDINATE =
@@ -64,7 +64,7 @@ public final class ResourceTransferUtils {
DomainTransferData domainTransferData = (DomainTransferData) transferData;
builder =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(eppResource.getForeignKey())
.setDomainName(eppResource.getForeignKey())
.setExtendedRegistrationExpirationTime(
ADD_EXDATE_STATUSES.contains(domainTransferData.getTransferStatus())
? domainTransferData.getTransferredRegistrationExpirationTime()
@@ -24,7 +24,7 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.DomainDsDataHistory;
import google.registry.model.host.Host;
import google.registry.model.reporting.DomainTransactionRecord;
@@ -67,12 +67,12 @@ public class BulkQueryEntities {
public static Domain assembleDomain(
DomainLite domainLite,
ImmutableSet<GracePeriod> gracePeriods,
ImmutableSet<DelegationSignerData> delegationSignerData,
ImmutableSet<DomainDsData> domainDsData,
ImmutableSet<VKey<Host>> nsHosts) {
Domain.Builder builder = new Domain.Builder();
builder.copyFrom(domainLite);
builder.setGracePeriods(gracePeriods);
builder.setDsData(delegationSignerData);
builder.setDsData(domainDsData);
builder.setNameservers(nsHosts);
// Restore the original update timestamp (this gets cleared when we set nameservers or DS data).
builder.setUpdateTimestamp(domainLite.getUpdateTimestamp());
@@ -99,9 +99,7 @@ public class BulkQueryEntities {
.map(GracePeriod::createFromHistory)
.collect(toImmutableSet()))
.setDsData(
dsDataHistories.stream()
.map(DelegationSignerData::create)
.collect(toImmutableSet()))
dsDataHistories.stream().map(DomainDsData::create).collect(toImmutableSet()))
// Restore the original update timestamp (this gets cleared when we set nameservers or
// DS data).
.setUpdateTimestamp(domainHistoryLite.domainBase.getUpdateTimestamp())
@@ -14,7 +14,6 @@
package google.registry.model.contact;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
@@ -46,13 +45,13 @@ import org.joda.time.DateTime;
@Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithStringVKey
@WithStringVKey(compositeKey = true)
@Access(AccessType.FIELD)
public class Contact extends ContactBase implements ForeignKeyedEppResource {
@Override
public VKey<Contact> createVKey() {
return VKey.create(Contact.class, getRepoId(), Key.create(this));
return VKey.createSql(Contact.class, getRepoId());
}
@Override
@@ -19,7 +19,7 @@ import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
@@ -66,7 +66,7 @@ import org.joda.time.DateTime;
@Index(columnList = "transfer_billing_event_id"),
@Index(columnList = "transfer_billing_recurrence_id")
})
@WithStringVKey
@WithStringVKey(compositeKey = true)
@ExternalMessagingName("domain")
@Access(AccessType.FIELD)
public class Domain extends DomainBase implements ForeignKeyedEppResource {
@@ -116,15 +116,14 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
}
/**
* Returns the set of {@link DelegationSignerData} associated with the domain.
* Returns the set of {@link DomainDsData} associated with the domain.
*
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
* private. The caller can use the public {@link #getDsData()} to get the DS data.
*
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
* Hibernate would try to set the foreign key to null(through an UPDATE TABLE sql) instead of
* deleting the whole entry from the table when the {@link DelegationSignerData} is removed from
* the set.
* deleting the whole entry from the table when the {@link DomainDsData} is removed from the set.
*/
@Access(AccessType.PROPERTY)
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@@ -134,7 +133,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
insertable = false,
updatable = false)
@SuppressWarnings("UnusedMethod")
private Set<DelegationSignerData> getInternalDelegationSignerData() {
private Set<DomainDsData> getInternalDelegationSignerData() {
return dsData;
}
@@ -148,7 +147,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
@Override
public VKey<Domain> createVKey() {
return VKey.create(Domain.class, getRepoId(), Key.create(this));
return VKey.createSql(Domain.class, getRepoId());
}
@Override
@@ -53,7 +53,7 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.contact.Contact;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
@@ -112,17 +112,14 @@ public class DomainBase extends EppResource
* Fully qualified domain name (puny-coded), which serves as the foreign key for this domain.
*
* <p>This is only unique in the sense that for any given lifetime specified as the time range
* from (creationTime, deletionTime) there can only be one domain in Datastore with this name.
* from (creationTime, deletionTime) there can only be one domain in the database with this name.
* However, there can be many domains with the same name and non-overlapping lifetimes.
*
* @invariant fullyQualifiedDomainName == fullyQualifiedDomainName.toLowerCase(Locale.ENGLISH)
* @invariant domainName == domainName.toLowerCase(Locale.ENGLISH)
*/
// TODO(b/177567432): Rename this to domainName when we are off Datastore
@Column(name = "domainName")
@Index
String fullyQualifiedDomainName;
@Index String domainName;
/** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */
/** The top level domain this is under, dernormalized from {@link #domainName}. */
@Index String tld;
/** References to hosts that are the nameservers for the domain. */
@@ -145,7 +142,7 @@ public class DomainBase extends EppResource
DomainAuthInfo authInfo;
/** Data used to construct DS records for this domain. */
@Ignore @Transient Set<DelegationSignerData> dsData;
@Ignore @Transient Set<DomainDsData> dsData;
/**
* The claims notice supplied when this domain was created, if there was one.
@@ -340,14 +337,14 @@ public class DomainBase extends EppResource
@Override
public String getForeignKey() {
return fullyQualifiedDomainName;
return domainName;
}
public String getDomainName() {
return fullyQualifiedDomainName;
return domainName;
}
public ImmutableSet<DelegationSignerData> getDsData() {
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
@@ -392,9 +389,9 @@ public class DomainBase extends EppResource
// Hibernate needs this in order to populate dsData but no one else should ever use it
@SuppressWarnings("UnusedMethod")
private void setInternalDelegationSignerData(Set<DelegationSignerData> dsData) {
private void setInternalDelegationSignerData(Set<DomainDsData> dsData) {
if (this.dsData instanceof PersistentSet) {
Set<DelegationSignerData> nonNullDsData = nullToEmpty(dsData);
Set<DomainDsData> nonNullDsData = nullToEmpty(dsData);
this.dsData.retainAll(nonNullDsData);
this.dsData.addAll(nonNullDsData);
} else {
@@ -728,9 +725,9 @@ public class DomainBase extends EppResource
// If there is no autorenew end time, set it to END_OF_TIME.
instance.autorenewEndTime = firstNonNull(getInstance().autorenewEndTime, END_OF_TIME);
checkArgumentNotNull(emptyToNull(instance.fullyQualifiedDomainName), "Missing domainName");
checkArgumentNotNull(emptyToNull(instance.domainName), "Missing domainName");
checkArgumentNotNull(instance.getRegistrant(), "Missing registrant");
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
instance.tld = getTldFromDomainName(instance.domainName);
T newDomain = super.build();
// Hibernate throws exception if gracePeriods or dsData is null because we enabled all
@@ -751,11 +748,11 @@ public class DomainBase extends EppResource
domainName.equals(canonicalizeHostname(domainName)),
"Domain name %s not in puny-coded, lower-case form",
domainName);
getInstance().fullyQualifiedDomainName = domainName;
getInstance().domainName = domainName;
return thisCastToDerived();
}
public B setDsData(ImmutableSet<DelegationSignerData> dsData) {
public B setDsData(ImmutableSet<DomainDsData> dsData) {
getInstance().dsData = dsData;
getInstance().resetUpdateTimestamp();
return thisCastToDerived();
@@ -17,7 +17,6 @@ package google.registry.model.domain;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
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 google.registry.util.CollectionUtils.difference;
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.ImmutableSet;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.Contact;
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.SingleResourceCommand;
import google.registry.model.host.Host;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.persistence.VKey;
import java.util.Set;
import javax.annotation.Nullable;
@@ -106,9 +105,9 @@ public class DomainCommand {
@XmlRootElement
@XmlType(
propOrder = {
"fullyQualifiedDomainName",
"domainName",
"period",
"nameserverFullyQualifiedHostNames",
"nameserverHostNames",
"registrantContactId",
"foreignKeyedDesignatedContacts",
"authInfo"
@@ -118,12 +117,12 @@ public class DomainCommand {
/** Fully qualified domain name, which serves as a unique identifier for this domain. */
@XmlElement(name = "name")
String fullyQualifiedDomainName;
String domainName;
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
Set<String> nameserverFullyQualifiedHostNames;
Set<String> nameserverHostNames;
/** Resolved keys to hosts that are the nameservers for the domain. */
@XmlTransient Set<VKey<Host>> nameservers;
@@ -145,15 +144,15 @@ public class DomainCommand {
@Override
public String getTargetId() {
return fullyQualifiedDomainName;
return domainName;
}
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
public String getDomainName() {
return domainName;
}
public ImmutableSet<String> getNameserverFullyQualifiedHostNames() {
return nullToEmptyImmutableCopy(nameserverFullyQualifiedHostNames);
public ImmutableSet<String> getNameserverHostNames() {
return nullToEmptyImmutableCopy(nameserverHostNames);
}
public ImmutableSet<VKey<Host>> getNameservers() {
@@ -173,7 +172,7 @@ public class DomainCommand {
@Override
public Create cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
Create clone = clone(this);
clone.nameservers = linkHosts(clone.nameserverFullyQualifiedHostNames, now);
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
if (registrantContactId == null) {
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
} else {
@@ -206,7 +205,7 @@ public class DomainCommand {
/** The name of the domain to look up, and an attribute specifying the host lookup type. */
@XmlElement(name = "name")
NameWithHosts fullyQualifiedDomainName;
NameWithHosts domainName;
DomainAuthInfo authInfo;
@@ -245,12 +244,12 @@ public class DomainCommand {
/** Get the enum that specifies the requested hosts (applies only to info flows). */
public HostsRequest getHostsRequest() {
// Null "hosts" is implicitly ALL.
return MoreObjects.firstNonNull(fullyQualifiedDomainName.hosts, HostsRequest.ALL);
return MoreObjects.firstNonNull(domainName.hosts, HostsRequest.ALL);
}
@Override
public String getTargetId() {
return fullyQualifiedDomainName.name;
return domainName.name;
}
@Override
@@ -338,15 +337,12 @@ public class DomainCommand {
}
/** The inner change type on a domain update command. */
@XmlType(propOrder = {
"nameserverFullyQualifiedHostNames",
"foreignKeyedDesignatedContacts",
"statusValues"})
@XmlType(propOrder = {"nameserverHostNames", "foreignKeyedDesignatedContacts", "statusValues"})
public static class AddRemove extends ResourceUpdate.AddRemove {
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
Set<String> nameserverFullyQualifiedHostNames;
Set<String> nameserverHostNames;
/** Resolved keys to hosts that are the nameservers for the domain. */
@XmlTransient Set<VKey<Host>> nameservers;
@@ -359,8 +355,8 @@ public class DomainCommand {
@XmlTransient
Set<DesignatedContact> contacts;
public ImmutableSet<String> getNameserverFullyQualifiedHostNames() {
return nullSafeImmutableCopy(nameserverFullyQualifiedHostNames);
public ImmutableSet<String> getNameserverHostNames() {
return nullSafeImmutableCopy(nameserverHostNames);
}
public ImmutableSet<VKey<Host>> getNameservers() {
@@ -374,7 +370,7 @@ public class DomainCommand {
/** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
private AddRemove cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
AddRemove clone = clone(this);
clone.nameservers = linkHosts(clone.nameserverFullyQualifiedHostNames, now);
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
return clone;
}
@@ -413,13 +409,12 @@ public class DomainCommand {
}
}
private static Set<VKey<Host>> linkHosts(Set<String> fullyQualifiedHostNames, DateTime now)
private static Set<VKey<Host>> linkHosts(Set<String> hostNames, DateTime now)
throws InvalidReferencesException {
if (fullyQualifiedHostNames == null) {
if (hostNames == null) {
return null;
}
return ImmutableSet.copyOf(
loadByForeignKeysCached(fullyQualifiedHostNames, Host.class, now).values());
return ImmutableSet.copyOf(loadByForeignKeysCached(hostNames, Host.class, now).values());
}
private static Set<DesignatedContact> linkContacts(
@@ -445,13 +440,12 @@ public class DomainCommand {
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
throws InvalidReferencesException {
ImmutableMap<String, ForeignKeyIndex<T>> fkis =
ForeignKeyIndex.loadCached(clazz, foreignKeys, now);
if (!fkis.keySet().equals(foreignKeys)) {
ImmutableMap<String, VKey<T>> fks = ForeignKeyUtils.loadCached(clazz, foreignKeys, now);
if (!fks.keySet().equals(foreignKeys)) {
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. */
@@ -25,7 +25,7 @@ import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.DomainDsDataHistory;
import google.registry.model.host.Host;
import google.registry.model.reporting.DomainTransactionRecord;
@@ -280,7 +280,7 @@ public class DomainHistory extends HistoryEntry {
.map(GracePeriod::createFromHistory)
.collect(toImmutableSet());
domainBase.dsData =
dsDataHistories.stream().map(DelegationSignerData::create).collect(toImmutableSet());
dsDataHistories.stream().map(DomainDsData::create).collect(toImmutableSet());
// Normally Hibernate would see that the domain fields are all null and would fill
// domainBase with a null object. Unfortunately, the updateTimestamp is never null in SQL.
if (domainBase.getDomainName() == null) {
@@ -30,28 +30,30 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned for an EPP info flow on a domain. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"fullyQualifiedDomainName",
"repoId",
"statusValues",
"registrant",
"contacts",
"nameservers",
"subordinateHosts",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"registrationExpirationTime",
"lastTransferTime",
"authInfo"})
@XmlType(
propOrder = {
"domainName",
"repoId",
"statusValues",
"registrant",
"contacts",
"nameservers",
"subordinateHosts",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"registrationExpirationTime",
"lastTransferTime",
"authInfo"
})
@AutoValue
@CopyAnnotations
public abstract class DomainInfoData implements ResponseData {
@XmlElement(name = "name")
abstract String getFullyQualifiedDomainName();
abstract String getDomainName();
@XmlElement(name = "roid")
abstract String getRepoId();
@@ -110,7 +112,8 @@ public abstract class DomainInfoData implements ResponseData {
/** Builder for {@link DomainInfoData}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setFullyQualifiedDomainName(String fullyQualifiedDomainName);
public abstract Builder setDomainName(String domainName);
public abstract Builder setRepoId(String repoId);
public abstract Builder setStatusValues(@Nullable ImmutableSet<StatusValue> statusValues);
public abstract Builder setRegistrant(String registrant);
@@ -17,7 +17,7 @@ package google.registry.model.domain.secdns;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.secdns.DelegationSignerData.DomainDsDataId;
import google.registry.model.domain.secdns.DomainDsData.DomainDsDataId;
import java.io.Serializable;
import javax.persistence.Access;
import javax.persistence.AccessType;
@@ -34,15 +34,14 @@ import javax.xml.bind.annotation.XmlType;
*
* @see <a href="http://tools.ietf.org/html/rfc5910">RFC 5910</a>
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
* <p>TODO(b/177567432): Rename this class to DomainDsData.
*/
@XmlType(name = "dsData")
@Entity
@IdClass(DomainDsDataId.class)
@Table(indexes = @Index(columnList = "domainRepoId"))
public class DelegationSignerData extends DomainDsDataBase {
@Table(name = "DelegationSignerData", indexes = @Index(columnList = "domainRepoId"))
public class DomainDsData extends DomainDsDataBase {
private DelegationSignerData() {}
private DomainDsData() {}
@Override
@Id
@@ -79,21 +78,21 @@ public class DelegationSignerData extends DomainDsDataBase {
return super.getDigest();
}
public DelegationSignerData cloneWithDomainRepoId(String domainRepoId) {
DelegationSignerData clone = clone(this);
public DomainDsData cloneWithDomainRepoId(String domainRepoId) {
DomainDsData clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
return clone;
}
public DelegationSignerData cloneWithoutDomainRepoId() {
DelegationSignerData clone = clone(this);
public DomainDsData cloneWithoutDomainRepoId() {
DomainDsData clone = clone(this);
clone.domainRepoId = null;
return clone;
}
public static DelegationSignerData create(
public static DomainDsData create(
int keyTag, int algorithm, int digestType, byte[] digest, String domainRepoId) {
DelegationSignerData instance = new DelegationSignerData();
DomainDsData instance = new DomainDsData();
instance.keyTag = keyTag;
instance.algorithm = algorithm;
instance.digestType = digestType;
@@ -102,17 +101,15 @@ public class DelegationSignerData extends DomainDsDataBase {
return instance;
}
public static DelegationSignerData create(
int keyTag, int algorithm, int digestType, byte[] digest) {
public static DomainDsData create(int keyTag, int algorithm, int digestType, byte[] digest) {
return create(keyTag, algorithm, digestType, digest, null);
}
public static DelegationSignerData create(
int keyTag, int algorithm, int digestType, String digestAsHex) {
public static DomainDsData create(int keyTag, int algorithm, int digestType, String digestAsHex) {
return create(keyTag, algorithm, digestType, DatatypeConverter.parseHexBinary(digestAsHex));
}
public static DelegationSignerData create(DomainDsDataHistory history) {
public static DomainDsData create(DomainDsDataHistory history) {
return create(
history.keyTag,
history.algorithm,
@@ -121,7 +118,7 @@ public class DelegationSignerData extends DomainDsDataBase {
history.domainRepoId);
}
/** Class to represent the composite primary key of {@link DelegationSignerData} entity. */
/** Class to represent the composite primary key of {@link DomainDsData} entity. */
static class DomainDsDataId extends ImmutableObject implements Serializable {
String domainRepoId;
@@ -26,7 +26,7 @@ import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for {@link DelegationSignerData} and {@link DomainDsDataHistory}. */
/** Base class for {@link DomainDsData} and {@link DomainDsDataHistory}. */
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@@ -25,7 +25,7 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/** Entity class to represent a historic {@link DelegationSignerData}. */
/** Entity class to represent a historic {@link DomainDsData}. */
@Entity
public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSerializable {
@@ -39,10 +39,9 @@ public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSeria
/**
* Creates a {@link DomainDsDataHistory} instance from given {@link #domainHistoryRevisionId} and
* {@link DelegationSignerData} instance.
* {@link DomainDsData} instance.
*/
public static DomainDsDataHistory createFrom(
long domainHistoryRevisionId, DelegationSignerData dsData) {
public static DomainDsDataHistory createFrom(long domainHistoryRevisionId, DomainDsData dsData) {
DomainDsDataHistory instance = new DomainDsDataHistory();
instance.domainHistoryRevisionId = domainHistoryRevisionId;
instance.domainRepoId = dsData.domainRepoId;
@@ -36,13 +36,13 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
Long maxSigLife;
/** Signatures for this domain. */
Set<DelegationSignerData> dsData;
Set<DomainDsData> dsData;
public Long getMaxSigLife() {
return maxSigLife;
}
public ImmutableSet<DelegationSignerData> getDsData() {
public ImmutableSet<DomainDsData> getDsData() {
return nullSafeImmutableCopy(dsData);
}
}
@@ -24,9 +24,9 @@ import javax.xml.bind.annotation.XmlRootElement;
public class SecDnsInfoExtension extends ImmutableObject implements ResponseExtension {
/** Signatures for this domain. */
ImmutableSet<DelegationSignerData> dsData;
ImmutableSet<DomainDsData> dsData;
public static SecDnsInfoExtension create(ImmutableSet<DelegationSignerData> dsData) {
public static SecDnsInfoExtension create(ImmutableSet<DomainDsData> dsData) {
SecDnsInfoExtension instance = new SecDnsInfoExtension();
instance.dsData = dsData;
return instance;
@@ -70,9 +70,9 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
@XmlTransient
abstract static class AddRemoveBase extends ImmutableObject {
/** Delegations to add or remove. */
Set<DelegationSignerData> dsData;
Set<DomainDsData> dsData;
public ImmutableSet<DelegationSignerData> getDsData() {
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
}
@@ -15,6 +15,7 @@
package google.registry.model.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -28,6 +29,8 @@ import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
@@ -40,7 +43,9 @@ import org.joda.time.DateTime;
public class PackagePromotion extends ImmutableObject implements Buildable {
/** An autogenerated identifier for the package promotion. */
@Id long packagePromotionId;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long packagePromotionId;
/** The allocation token string for the package. */
@Column(nullable = false)
@@ -94,6 +99,21 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return Optional.ofNullable(lastNotificationSent);
}
/** Loads and returns a PackagePromotion entity by its token string directly from Cloud SQL. */
public static Optional<PackagePromotion> loadByTokenString(String tokenString) {
jpaTm().assertInTransaction();
return jpaTm()
.query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class)
.setParameter("token", VKey.createSql(AllocationToken.class, tokenString))
.getResultStream()
.findFirst();
}
@Override
public VKey<PackagePromotion> createVKey() {
return VKey.createSql(PackagePromotion.class, packagePromotionId);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -143,7 +163,7 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return this;
}
public Builder setNextBillingDate(@Nullable DateTime nextBillingDate) {
public Builder setNextBillingDate(DateTime nextBillingDate) {
checkArgumentNotNull(nextBillingDate, "Next billing date must not be null");
getInstance().nextBillingDate = nextBillingDate;
return this;
@@ -14,7 +14,6 @@
package google.registry.model.host;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.annotations.ExternalMessagingName;
@@ -52,7 +51,7 @@ import javax.persistence.AccessType;
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
})
@ExternalMessagingName("host")
@WithStringVKey
@WithStringVKey(compositeKey = true)
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public class Host extends HostBase implements ForeignKeyedEppResource {
@@ -65,7 +64,7 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
@Override
public VKey<Host> createVKey() {
return VKey.create(Host.class, getRepoId(), Key.create(this));
return VKey.createSql(Host.class, getRepoId());
}
@Override
@@ -35,7 +35,6 @@ import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
@@ -64,10 +63,7 @@ public class HostBase extends EppResource {
* from (creationTime, deletionTime) there can only be one host in Datastore with this name.
* However, there can be many hosts with the same name and non-overlapping lifetimes.
*/
// TODO(b/177567432): Rename this to hostName when we are off Datastore
@Index
@Column(name = "hostName")
String fullyQualifiedHostName;
@Index String hostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index Set<InetAddress> inetAddresses;
@@ -95,7 +91,7 @@ public class HostBase extends EppResource {
DateTime lastSuperordinateChange;
public String getHostName() {
return fullyQualifiedHostName;
return hostName;
}
public VKey<Domain> getSuperordinateDomain() {
@@ -120,7 +116,7 @@ public class HostBase extends EppResource {
@Override
public String getForeignKey() {
return fullyQualifiedHostName;
return hostName;
}
@Override
@@ -197,7 +193,7 @@ public class HostBase extends EppResource {
hostName.equals(canonicalizeHostname(hostName)),
"Host name %s not in puny-coded, lower-case form",
hostName);
getInstance().fullyQualifiedHostName = hostName;
getInstance().hostName = hostName;
return thisCastToDerived();
}
@@ -36,7 +36,7 @@ public class HostCommand {
@XmlTransient
abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
implements ResourceCreateOrChange<Host.Builder> {
public String getFullyQualifiedHostName() {
public String getHostName() {
return getTargetId();
}
}
@@ -28,23 +28,25 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned for an EPP info flow on a host. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"fullyQualifiedHostName",
"repoId",
"statusValues",
"inetAddresses",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime" })
@XmlType(
propOrder = {
"hostName",
"repoId",
"statusValues",
"inetAddresses",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime"
})
@AutoValue
@CopyAnnotations
public abstract class HostInfoData implements ResponseData {
@XmlElement(name = "name")
abstract String getFullyQualifiedHostName();
abstract String getHostName();
@XmlElement(name = "roid")
abstract String getRepoId();
@@ -79,7 +81,8 @@ public abstract class HostInfoData implements ResponseData {
/** Builder for {@link HostInfoData}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setFullyQualifiedHostName(String fullyQualifiedHostName);
public abstract Builder setHostName(String hostName);
public abstract Builder setRepoId(String repoId);
public abstract Builder setStatusValues(ImmutableSet<StatusValue> statusValues);
public abstract Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses);
@@ -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;
}
}
@@ -91,10 +91,10 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
public static DomainPendingActionNotificationResponse create(
String fullyQualifiedDomainName, boolean actionResult, Trid trid, DateTime processedDate) {
String domainName, boolean actionResult, Trid trid, DateTime processedDate) {
return init(
new DomainPendingActionNotificationResponse(),
fullyQualifiedDomainName,
domainName,
actionResult,
trid,
processedDate);
@@ -140,13 +140,9 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
public static HostPendingActionNotificationResponse create(
String fullyQualifiedHostName, boolean actionResult, Trid trid, DateTime processedDate) {
String hostName, boolean actionResult, Trid trid, DateTime processedDate) {
return init(
new HostPendingActionNotificationResponse(),
fullyQualifiedHostName,
actionResult,
trid,
processedDate);
new HostPendingActionNotificationResponse(), hostName, actionResult, trid, processedDate);
}
}
}
@@ -386,7 +386,7 @@ public abstract class PollMessage extends ImmutableObject
TransferResponse transferResponse;
@Column(name = "transfer_response_domain_name")
String fullyQualifiedDomainName;
String domainName;
@Column(name = "transfer_response_domain_expiration_time")
DateTime extendedRegistrationExpirationTime;
@@ -427,7 +427,7 @@ public abstract class PollMessage extends ImmutableObject
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate);
} else if (fullyQualifiedDomainName != null) {
} else if (domainName != null) {
pendingActionNotificationResponse =
DomainPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
@@ -457,10 +457,10 @@ public abstract class PollMessage extends ImmutableObject
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.build();
} else if (fullyQualifiedDomainName != null) {
} else if (domainName != null) {
transferResponse =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(fullyQualifiedDomainName)
.setDomainName(domainName)
.setGainingRegistrarId(transferResponse.getGainingRegistrarId())
.setLosingRegistrarId(transferResponse.getLosingRegistrarId())
.setTransferStatus(transferResponse.getTransferStatus())
@@ -486,7 +486,7 @@ public abstract class PollMessage extends ImmutableObject
OneTime instance = getInstance();
// Note: In its current form, the code will basically just ignore everything but the first
// PendingActionNotificationResponse and TransferResponse in responseData, and will override
// any identifier fields (e.g. contactId, fullyQualifiedDomainName) obtained from the
// any identifier fields (e.g. contactId, domainName) obtained from the
// PendingActionNotificationResponse if a TransferResponse is found with different values
// for those fields. It is not clear what the constraints should be on this data or
// whether we should enforce them here, though historically we have not, so the current
@@ -507,8 +507,7 @@ public abstract class PollMessage extends ImmutableObject
instance.contactId = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
instanceof DomainPendingActionNotificationResponse) {
instance.fullyQualifiedDomainName =
instance.pendingActionNotificationResponse.nameOrId.value;
instance.domainName = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
instanceof HostPendingActionNotificationResponse) {
instance.hostId = instance.pendingActionNotificationResponse.nameOrId.value;
@@ -527,7 +526,7 @@ public abstract class PollMessage extends ImmutableObject
instance.contactId = ((ContactTransferResponse) instance.transferResponse).getContactId();
} else if (instance.transferResponse instanceof DomainTransferResponse) {
DomainTransferResponse response = (DomainTransferResponse) instance.transferResponse;
instance.fullyQualifiedDomainName = response.getFullyQualifiedDomainName();
instance.domainName = response.getDomainName();
instance.extendedRegistrationExpirationTime =
response.getExtendedRegistrationExpirationTime();
}
@@ -28,10 +28,10 @@ public interface PremiumPricingEngine {
/**
* Returns the prices for the given fully qualified domain name at the given time.
*
* <p>Note that the fullyQualifiedDomainName must only contain a single part left of the TLD, i.e.
* subdomains are not allowed, but multi-part TLDs are.
* <p>Note that the domainName must only contain a single part left of the TLD, i.e. subdomains
* are not allowed, but multi-part TLDs are.
*/
DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime);
DomainPrices getDomainPrices(String domainName, DateTime priceTime);
/**
* A class containing information on premium prices for a specific domain name.
@@ -34,9 +34,9 @@ public final class StaticPremiumListPricingEngine implements PremiumPricingEngin
@Inject StaticPremiumListPricingEngine() {}
@Override
public DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime) {
String tld = getTldFromDomainName(fullyQualifiedDomainName);
String label = InternetDomainName.from(fullyQualifiedDomainName).parts().get(0);
public DomainPrices getDomainPrices(String domainName, DateTime priceTime) {
String tld = getTldFromDomainName(domainName);
String label = InternetDomainName.from(domainName).parts().get(0);
Registry registry = Registry.get(checkNotNull(tld, "tld"));
Optional<Money> premiumPrice =
registry.getPremiumListName().flatMap(pl -> PremiumListDao.getPremiumPrice(pl, label));
@@ -207,11 +207,10 @@ public class Registrar extends ImmutableObject
* Unique registrar client id. Must conform to "clIDType" as defined in RFC5730.
*
* @see <a href="http://tools.ietf.org/html/rfc5730#section-4.2">Shared Structure Schema</a>
* <p>TODO(b/177567432): Rename this field to registrarId.
*/
@Id
@Column(name = "registrarId", nullable = false)
String clientIdentifier;
@Column(nullable = false)
String registrarId;
/**
* Registrar name. This is a distinct from the client identifier since there are no restrictions
@@ -270,9 +269,7 @@ public class Registrar extends ImmutableObject
String failoverClientCertificateHash;
/** An allow list of netmasks (in CIDR notation) which the client is allowed to connect from. */
// TODO(b/177567432): Rename to ipAddressAllowList once Cloud SQL migration is complete.
@Column(name = "ip_address_allow_list")
List<CidrAddressBlock> ipAddressWhitelist;
List<CidrAddressBlock> ipAddressAllowList;
/** A hashed password for EPP access. The hash is a base64 encoded SHA256 string. */
String passwordHash;
@@ -408,7 +405,7 @@ public class Registrar extends ImmutableObject
boolean registryLockAllowed = false;
public String getRegistrarId() {
return clientIdentifier;
return registrarId;
}
public DateTime getCreationTime() {
@@ -498,7 +495,7 @@ public class Registrar extends ImmutableObject
}
public ImmutableList<CidrAddressBlock> getIpAddressAllowList() {
return nullToEmptyImmutableCopy(ipAddressWhitelist);
return nullToEmptyImmutableCopy(ipAddressAllowList);
}
public RegistrarAddress getLocalizedAddress() {
@@ -587,7 +584,7 @@ public class Registrar extends ImmutableObject
() ->
jpaTm()
.query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", clientIdentifier)
.setParameter("registrarId", registrarId)
.getResultStream()
.collect(toImmutableList()));
}
@@ -595,7 +592,7 @@ public class Registrar extends ImmutableObject
@Override
public Map<String, Object> toJsonMap() {
return new JsonMapBuilder()
.put("clientIdentifier", clientIdentifier)
.put("registrarId", registrarId)
.put("ianaIdentifier", ianaIdentifier)
.putString("creationTime", creationTime.getTimestamp())
.putString("lastUpdateTime", lastUpdateTime.getTimestamp())
@@ -655,7 +652,7 @@ public class Registrar extends ImmutableObject
/** Creates a {@link VKey} for this instance. */
@Override
public VKey<Registrar> createVKey() {
return createVKey(clientIdentifier);
return createVKey(registrarId);
}
/** Creates a {@link VKey} for the given {@code registrarId}. */
@@ -678,7 +675,7 @@ public class Registrar extends ImmutableObject
checkArgument(
Range.closed(3, 16).contains(registrarId.length()),
"Registrar ID must be 3-16 characters long.");
getInstance().clientIdentifier = registrarId;
getInstance().registrarId = registrarId;
return this;
}
@@ -796,7 +793,7 @@ public class Registrar extends ImmutableObject
}
public Builder setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
getInstance().ipAddressWhitelist = ImmutableList.copyOf(ipAddressAllowList);
getInstance().ipAddressAllowList = ImmutableList.copyOf(ipAddressAllowList);
return this;
}
@@ -72,11 +72,9 @@ public class ClaimsList extends ImmutableObject {
* <p>Note that the value of this field is parsed from the claims list file(See this <a
* href="https://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1">RFC</>), it is
* the DNL List creation datetime from the rfc.
*
* <p>TODO(b/177567432): Rename this field to tmdbGenerationTime.
*/
@Column(name = "tmdb_generation_time", nullable = false)
DateTime creationTime;
@Column(nullable = false)
DateTime tmdbGenerationTime;
/**
* A map from labels to claims keys.
@@ -143,7 +141,7 @@ public class ClaimsList extends ImmutableObject {
* creation datetime</a>
*/
public DateTime getTmdbGenerationTime() {
return creationTime;
return tmdbGenerationTime;
}
/** Returns the creation time of this claims list. */
@@ -225,7 +223,7 @@ public class ClaimsList extends ImmutableObject {
public static ClaimsList create(
DateTime tmdbGenerationTime, ImmutableMap<String, String> labelsToKeys) {
ClaimsList instance = new ClaimsList();
instance.creationTime = checkNotNull(tmdbGenerationTime);
instance.tmdbGenerationTime = checkNotNull(tmdbGenerationTime);
instance.labelsToKeys = checkNotNull(labelsToKeys);
return instance;
}
@@ -33,22 +33,24 @@ public class TransferResponse extends BaseTransferObject implements ResponseData
/** An adapter to output the XML in response to a transfer command on a domain. */
@XmlRootElement(name = "trnData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(propOrder = {
"fullyQualifiedDomainName",
"transferStatus",
"gainingClientId",
"transferRequestTime",
"losingClientId",
"pendingTransferExpirationTime",
"extendedRegistrationExpirationTime"},
namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
propOrder = {
"domainName",
"transferStatus",
"gainingClientId",
"transferRequestTime",
"losingClientId",
"pendingTransferExpirationTime",
"extendedRegistrationExpirationTime"
},
namespace = "urn:ietf:params:xml:ns:domain-1.0")
public static class DomainTransferResponse extends TransferResponse {
@XmlElement(name = "name")
String fullyQualifiedDomainName;
String domainName;
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
public String getDomainName() {
return domainName;
}
/**
@@ -65,8 +67,8 @@ public class TransferResponse extends BaseTransferObject implements ResponseData
/** Builder for {@link DomainTransferResponse}. */
public static class Builder
extends BaseTransferObject.Builder<DomainTransferResponse, Builder> {
public Builder setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
public Builder setDomainName(String domainName) {
getInstance().domainName = domainName;
return this;
}
@@ -61,7 +61,7 @@ public interface JpaTransactionManager extends TransactionManager {
*/
<T> TypedQuery<T> query(String sqlString, Class<T> resultClass);
/** Creates a JPA SQU query for the given criteria query. */
/** Creates a JPA SQL query for the given criteria query. */
<T> TypedQuery<T> criteriaQuery(CriteriaQuery<T> criteriaQuery);
/**
@@ -16,7 +16,6 @@ package google.registry.rdap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
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.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
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.primitives.Booleans;
import com.googlecode.objectify.cmd.Query;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -199,13 +199,12 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
final RdapSearchPattern partialStringQuery) {
// We can't query for undeleted domains as part of the query itself; that would require an
// inequality query on deletion time, and we are already using inequality queries on
// fullyQualifiedDomainName. So we instead pick an arbitrary limit of
// RESULT_SET_SIZE_SCALING_FACTOR times the result set size limit, fetch up to that many, and
// weed out all deleted domains. If there still isn't a full result set's worth of domains, we
// give up and return just the ones we found. Don't use queryItems, because it checks that the
// initial string is at least a certain length, which we don't need in this case. Query the
// domains directly, rather than the foreign keys, because then we have an index on TLD if we
// need it.
// domainName. So we instead pick an arbitrary limit of RESULT_SET_SIZE_SCALING_FACTOR times the
// result set size limit, fetch up to that many, and weed out all deleted domains. If there
// still isn't a full result set's worth of domains, we give up and return just the ones we
// found. Don't use queryItems, because it checks that the initial string is at least a certain
// length, which we don't need in this case. Query the domains directly, rather than the foreign
// keys, because then we have an index on TLD if we need it.
int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
RdapResultSet<Domain> resultSet;
if (tm().isOfy()) {
@@ -213,10 +212,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
auditedOfy()
.load()
.type(Domain.class)
.filter("fullyQualifiedDomainName <", partialStringQuery.getNextInitialString())
.filter("fullyQualifiedDomainName >=", partialStringQuery.getInitialString());
.filter("domainName <", partialStringQuery.getNextInitialString())
.filter("domainName >=", partialStringQuery.getInitialString());
if (cursorString.isPresent()) {
query = query.filter("fullyQualifiedDomainName >", cursorString.get());
query = query.filter("domainName >", cursorString.get());
}
if (partialStringQuery.getSuffix() != null) {
query = query.filter("tld", partialStringQuery.getSuffix());
@@ -234,16 +233,14 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
CriteriaQueryBuilder<Domain> queryBuilder =
CriteriaQueryBuilder.create(replicaJpaTm(), Domain.class)
.where(
"fullyQualifiedDomainName",
"domainName",
criteriaBuilder::like,
String.format("%s%%", partialStringQuery.getInitialString()))
.orderByAsc("fullyQualifiedDomainName");
.orderByAsc("domainName");
if (cursorString.isPresent()) {
queryBuilder =
queryBuilder.where(
"fullyQualifiedDomainName",
criteriaBuilder::greaterThan,
cursorString.get());
"domainName", criteriaBuilder::greaterThan, cursorString.get());
}
if (partialStringQuery.getSuffix() != null) {
queryBuilder =
@@ -258,18 +255,18 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
/** Searches for domains by domain name with a TLD suffix. */
private DomainSearchResponse searchByDomainNameByTld(String tld) {
// Even though we are not searching on fullyQualifiedDomainName, we want the results to come
// back ordered by name, so we are still in the same boat as
// searchByDomainNameWithInitialString, unable to perform an inequality query on deletion time.
// Don't use queryItems, because it doesn't handle pending deletes.
// Even though we are not searching on domainName, we want the results to come back ordered by
// name, so we are still in the same boat as searchByDomainNameWithInitialString, unable to
// perform an inequality query on deletion time. Don't use queryItems, because it doesn't handle
// pending deletes.
int querySizeLimit = RESULT_SET_SIZE_SCALING_FACTOR * rdapResultSetMaxSize;
RdapResultSet<Domain> resultSet;
if (tm().isOfy()) {
Query<Domain> query = auditedOfy().load().type(Domain.class).filter("tld", tld);
if (cursorString.isPresent()) {
query = query.filter("fullyQualifiedDomainName >", cursorString.get());
query = query.filter("domainName >", cursorString.get());
}
query = query.order("fullyQualifiedDomainName").limit(querySizeLimit);
query = query.order("domainName").limit(querySizeLimit);
resultSet = getMatchingResources(query, true, querySizeLimit);
} else {
resultSet =
@@ -281,10 +278,10 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
Domain.class,
"tld",
tld,
Optional.of("fullyQualifiedDomainName"),
Optional.of("domainName"),
cursorString,
DeletedItemHandling.INCLUDE)
.orderByAsc("fullyQualifiedDomainName");
.orderByAsc("domainName");
return getMatchingResourcesSql(builder, true, querySizeLimit);
});
}
@@ -331,9 +328,9 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
return getNameserverRefsByLdhNameWithSuffix(partialStringQuery);
}
// 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
// deletion time, so we can check the deletion status in the query itself. The initial string
// must be present, to avoid querying every host in the system. This restriction is enforced by
// the foreign keys, because then we have an index on fully qualified host name and deletion
// time, so we can check the deletion status in the query itself. The initial string must be
// present, to avoid querying every host in the system. This restriction is enforced by
// {@link queryItems}.
//
// Only return the first maxNameserversInFirstStage nameservers. This could result in an
@@ -344,7 +341,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
Query<Host> query =
queryItems(
Host.class,
"fullyQualifiedHostName",
"hostName",
partialStringQuery,
Optional.empty(),
DeletedItemHandling.EXCLUDE,
@@ -362,7 +359,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
CriteriaQueryBuilder<Host> builder =
queryItemsSql(
Host.class,
"fullyQualifiedHostName",
"hostName",
partialStringQuery,
Optional.empty(),
DeletedItemHandling.EXCLUDE);
@@ -400,7 +397,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
: ImmutableList.of(host.get().createVKey());
} else {
VKey<Host> hostKey =
loadAndGetKey(
ForeignKeyUtils.load(
Host.class,
partialStringQuery.getInitialString(),
shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
@@ -442,7 +439,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
}
} else {
VKey<Host> hostKey =
loadAndGetKey(
ForeignKeyUtils.load(
Host.class, fqhn, shouldIncludeDeleted() ? START_OF_TIME : getRequestTime());
if (hostKey != null) {
builder.add(hostKey);
@@ -558,7 +555,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
// If we are not performing an inequality query, we can filter on the cursor in the query.
// Otherwise, we will need to filter the results afterward.
} else if (cursorString.isPresent()) {
query = query.filter("fullyQualifiedDomainName >", cursorString.get());
query = query.filter("domainName >", cursorString.get());
}
Stream<Domain> stream = Streams.stream(query).filter(this::isAuthorized);
if (cursorString.isPresent()) {
@@ -574,7 +571,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
CriteriaQueryBuilder<Domain> queryBuilder =
CriteriaQueryBuilder.create(replicaJpaTm(), Domain.class)
.whereFieldContains("nsHosts", hostKey)
.orderByAsc("fullyQualifiedDomainName");
.orderByAsc("domainName");
CriteriaBuilder criteriaBuilder =
replicaJpaTm().getEntityManager().getCriteriaBuilder();
if (!shouldIncludeDeleted()) {
@@ -585,9 +582,7 @@ public class RdapDomainSearchAction extends RdapSearchActionBase {
if (cursorString.isPresent()) {
queryBuilder =
queryBuilder.where(
"fullyQualifiedDomainName",
criteriaBuilder::greaterThan,
cursorString.get());
"domainName", criteriaBuilder::greaterThan, cursorString.get());
}
replicaJpaTm()
.criteriaQuery(queryBuilder.build())
@@ -223,7 +223,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
Query<Host> query =
queryItems(
Host.class,
"fullyQualifiedHostName",
"hostName",
partialStringQuery,
cursorString,
getDeletedItemHandling(),
@@ -237,7 +237,7 @@ public class RdapNameserverSearchAction extends RdapSearchActionBase {
CriteriaQueryBuilder<Host> queryBuilder =
queryItemsSql(
Host.class,
"fullyQualifiedHostName",
"hostName",
partialStringQuery,
cursorString,
getDeletedItemHandling());
@@ -25,7 +25,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.rdap.AbstractJsonableObject.RestrictJsonNames;
import google.registry.rdap.RdapDataStructures.Event;
import google.registry.rdap.RdapDataStructures.EventWithoutActor;
@@ -444,7 +444,7 @@ final class RdapObjectClasses {
@JsonableElement
abstract int digestType();
static DsData create(DelegationSignerData dsData) {
static DsData create(DomainDsData dsData) {
return new AutoValue_RdapObjectClasses_SecureDns_DsData(
dsData.getKeyTag(),
dsData.getAlgorithm(),
@@ -490,7 +490,7 @@ final class RdapObjectClasses {
abstract ImmutableList.Builder<DsData> dsDataBuilder();
Builder addDsData(DelegationSignerData dsData) {
Builder addDsData(DomainDsData dsData) {
dsDataBuilder().add(DsData.create(dsData));
return this;
}
@@ -25,7 +25,7 @@ import google.registry.model.contact.Contact;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.rde.RdeMode;
import google.registry.model.transfer.DomainTransferData;
@@ -197,7 +197,7 @@ final class DomainToXjcConverter {
// completely useless.
if (!model.getDsData().isEmpty()) {
XjcSecdnsDsOrKeyType secdns = new XjcSecdnsDsOrKeyType();
for (DelegationSignerData ds : model.getDsData()) {
for (DomainDsData ds : model.getDsData()) {
secdns.getDsDatas().add(convertDelegationSignerData(ds));
}
bean.setSecDNS(secdns);
@@ -284,8 +284,8 @@ final class DomainToXjcConverter {
return bean;
}
/** Converts {@link DelegationSignerData} to {@link XjcSecdnsDsDataType}. */
private static XjcSecdnsDsDataType convertDelegationSignerData(DelegationSignerData model) {
/** Converts {@link DomainDsData} to {@link XjcSecdnsDsDataType}. */
private static XjcSecdnsDsDataType convertDelegationSignerData(DomainDsData model) {
XjcSecdnsDsDataType bean = new XjcSecdnsDsDataType();
bean.setKeyTag(model.getKeyTag());
bean.setAlg((short) model.getAlgorithm());
@@ -15,7 +15,10 @@
package google.registry.reporting.billing;
import static google.registry.beam.BeamUtils.createJobName;
import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static javax.servlet.http.HttpServletResponse.SC_OK;
@@ -29,6 +32,7 @@ import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryEnvironment;
import google.registry.model.common.Cursor;
import google.registry.persistence.PersistenceModule;
import google.registry.reporting.ReportingModule;
import google.registry.request.Action;
@@ -40,6 +44,7 @@ import google.registry.util.Clock;
import google.registry.util.CloudTasksUtils;
import java.io.IOException;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.YearMonth;
@@ -108,6 +113,19 @@ public class GenerateInvoicesAction implements Runnable {
response.setContentType(MediaType.PLAIN_TEXT_UTF_8);
logger.atInfo().log("Launching invoicing pipeline for %s.", yearMonth);
try {
DateTime currentCursorTime =
tm().transact(
() ->
tm().loadByKeyIfPresent(Cursor.createGlobalVKey(RECURRING_BILLING))
.orElse(Cursor.createGlobal(RECURRING_BILLING, START_OF_TIME))
.getCursorTime());
if (yearMonth.getMonthOfYear() >= currentCursorTime.getMonthOfYear()) {
throw new IllegalStateException(
"Latest billing events expansion cycle hasn't finished yet, terminating invoicing"
+ " pipeline");
}
LaunchFlexTemplateParameter parameter =
new LaunchFlexTemplateParameter()
.setJobName(createJobName("invoicing", clock))
@@ -150,7 +168,7 @@ public class GenerateInvoicesAction implements Runnable {
}
response.setStatus(SC_OK);
response.setPayload(String.format("Launched invoicing pipeline: %s", jobId));
} catch (IOException e) {
} catch (IOException | IllegalStateException e) {
logger.atWarning().withCause(e).log("Template Launch failed.");
emailUtils.sendAlertEmail(String.format("Pipeline Launch failed due to %s", e.getMessage()));
response.setStatus(SC_INTERNAL_SERVER_ERROR);
@@ -15,7 +15,7 @@
-- Determine the number of domains each registrar sponsors per tld.
-- This is just the number of fullyQualifiedDomainNames under each
-- This is just the number of domainNames under each
-- tld-registrar pair.
SELECT
@@ -137,10 +137,7 @@ public class Spec11EmailUtils {
threatMatch ->
tm()
.createQueryComposer(Domain.class)
.where(
"fullyQualifiedDomainName",
Comparator.EQ,
threatMatch.fullyQualifiedDomainName())
.where("domainName", Comparator.EQ, threatMatch.domainName())
.stream()
.anyMatch(Domain::shouldPublishToDns))
.collect(toImmutableList());
@@ -176,7 +173,7 @@ public class Spec11EmailUtils {
.map(
threatMatch ->
ImmutableMap.of(
"fullyQualifiedDomainName", threatMatch.fullyQualifiedDomainName(),
"domainName", threatMatch.domainName(),
"threatType", threatMatch.threatType()))
.collect(toImmutableList());
@@ -316,7 +316,7 @@ public class AuthenticatedRegistrarAccessor {
jpaTm()
.query(
"SELECT r FROM Registrar r INNER JOIN RegistrarPoc rp ON "
+ "r.clientIdentifier = rp.registrarId WHERE rp.gaeUserId = "
+ "r.registrarId = rp.registrarId WHERE rp.gaeUserId = "
+ ":gaeUserId AND r.state != :state",
Registrar.class)
.setParameter("gaeUserId", user.getUserId())
@@ -19,10 +19,10 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import google.registry.model.EppResource;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.host.Host;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.persistence.VKey;
import org.joda.time.DateTime;
@@ -42,7 +42,7 @@ class CommandUtilities {
}
public VKey<? extends EppResource> getKey(String uniqueId, DateTime now) {
return ForeignKeyIndex.loadAndGetKey(clazz, uniqueId, now);
return ForeignKeyUtils.load(clazz, uniqueId, now);
}
}
@@ -0,0 +1,120 @@
// 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.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.domain.token.PackagePromotion;
import google.registry.persistence.VKey;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
/** Shared base class for commands to create or update a PackagePromotion object. */
abstract class CreateOrUpdatePackagePromotionCommand extends MutatingCommand {
@Parameter(description = "Allocation token String of the package token", required = true)
List<String> mainParameters;
@Nullable
@Parameter(
names = {"-d", "--max_domains"},
description = "Maximum concurrent active domains allowed in the package")
Integer maxDomains;
@Nullable
@Parameter(
names = {"-c", "--max_creates"},
description = "Maximum domain creations allowed in the package each year")
Integer maxCreates;
@Nullable
@Parameter(
names = {"-p", "--price"},
description = "Annual price of the package")
Money price;
@Nullable
@Parameter(
names = "--next_billing_date",
description = "The next date that the package should be billed for its annual fee")
Date nextBillingDate;
/** Returns the existing PackagePromotion or null if it does not exist. */
@Nullable
abstract PackagePromotion getOldPackagePromotion(String token);
/** Returns the allocation token object. */
AllocationToken getAndCheckAllocationToken(String token) {
Optional<AllocationToken> allocationToken =
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(AllocationToken.class, token)));
checkArgument(
allocationToken.isPresent(),
"An allocation token with the token String %s does not exist. The package token must be"
+ " created first before it can be used to create a PackagePromotion",
token);
checkArgument(
allocationToken.get().getTokenType().equals(TokenType.PACKAGE),
"The allocation token must be of the PACKAGE token type");
return allocationToken.get();
}
/** Does not clear the lastNotificationSent field. Subclasses can override this. */
boolean clearLastNotificationSent() {
return false;
}
@Override
protected final void init() throws Exception {
for (String token : mainParameters) {
jpaTm()
.transact(
() -> {
PackagePromotion oldPackage = getOldPackagePromotion(token);
checkArgument(
oldPackage != null || price != null,
"PackagePrice is required when creating a new package");
AllocationToken allocationToken = getAndCheckAllocationToken(token);
PackagePromotion.Builder builder =
(oldPackage == null)
? new PackagePromotion.Builder().setToken(allocationToken)
: oldPackage.asBuilder();
Optional.ofNullable(maxDomains).ifPresent(builder::setMaxDomains);
Optional.ofNullable(maxCreates).ifPresent(builder::setMaxCreates);
Optional.ofNullable(price).ifPresent(builder::setPackagePrice);
Optional.ofNullable(nextBillingDate)
.ifPresent(
nextBillingDate ->
builder.setNextBillingDate(new DateTime(nextBillingDate)));
if (clearLastNotificationSent()) {
builder.setLastNotificationSent(null);
}
PackagePromotion newPackage = builder.build();
stageEntityChange(oldPackage, newPackage);
});
}
}
}
@@ -0,0 +1,39 @@
// 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.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import org.jetbrains.annotations.Nullable;
/** Command to create a PackagePromotion */
@Parameters(
separators = " =",
commandDescription =
"Create new package promotion object(s) for registrars to register multiple names under"
+ " one contractual annual package price using a package allocation token")
public final class CreatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
@Nullable
@Override
PackagePromotion getOldPackagePromotion(String tokenString) {
checkArgument(
!PackagePromotion.loadByTokenString(tokenString).isPresent(),
"PackagePromotion with token %s already exists",
tokenString);
return null;
}
}
@@ -0,0 +1,47 @@
// 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.tools;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import java.util.List;
/** Command to show a {@link PackagePromotion} object. */
@Parameters(separators = " =", commandDescription = "Show package promotion object(s)")
public class GetPackagePromotionCommand extends GetEppResourceCommand {
@Parameter(description = "Package token(s)", required = true)
private List<String> mainParameters;
@Override
void runAndPrint() {
for (String token : mainParameters) {
jpaTm()
.transact(
() -> {
PackagePromotion packagePromotion =
checkArgumentPresent(
PackagePromotion.loadByTokenString(token),
"PackagePromotion with package token %s does not exist",
token);
System.out.println(packagePromotion);
});
}
}
}
@@ -16,6 +16,7 @@ package google.registry.tools;
import com.google.common.collect.ImmutableMap;
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
import google.registry.tools.javascrap.CreateCancellationsForOneTimesCommand;
/** Container class to create and run remote commands against a Datastore instance. */
public final class RegistryTool {
@@ -36,10 +37,12 @@ public final class RegistryTool {
.put("convert_idn", ConvertIdnCommand.class)
.put("count_domains", CountDomainsCommand.class)
.put("create_anchor_tenant", CreateAnchorTenantCommand.class)
.put("create_cancellations_for_one_times", CreateCancellationsForOneTimesCommand.class)
.put("create_cdns_tld", CreateCdnsTld.class)
.put("create_contact", CreateContactCommand.class)
.put("create_domain", CreateDomainCommand.class)
.put("create_host", CreateHostCommand.class)
.put("create_package_promotion", CreatePackagePromotionCommand.class)
.put("create_premium_list", CreatePremiumListCommand.class)
.put("create_registrar", CreateRegistrarCommand.class)
.put("create_registrar_groups", CreateRegistrarGroupsCommand.class)
@@ -68,6 +71,7 @@ public final class RegistryTool {
.put("get_history_entries", GetHistoryEntriesCommand.class)
.put("get_host", GetHostCommand.class)
.put("get_keyring_secret", GetKeyringSecretCommand.class)
.put("get_package_promotion", GetPackagePromotionCommand.class)
.put("get_premium_list", GetPremiumListCommand.class)
.put("get_registrar", GetRegistrarCommand.class)
.put("get_reserved_list", GetReservedListCommand.class)
@@ -104,6 +108,7 @@ public final class RegistryTool {
.put("update_cursors", UpdateCursorsCommand.class)
.put("update_domain", UpdateDomainCommand.class)
.put("update_keyring_secret", UpdateKeyringSecretCommand.class)
.put("update_package_promotion", UpdatePackagePromotionCommand.class)
.put("update_premium_list", UpdatePremiumListCommand.class)
.put("update_registrar", UpdateRegistrarCommand.class)
.put("update_reserved_list", UpdateReservedListCommand.class)
@@ -42,6 +42,7 @@ import google.registry.request.Modules.UrlFetchServiceModule;
import google.registry.request.Modules.UserServiceModule;
import google.registry.tools.AuthModule.LocalCredentialModule;
import google.registry.tools.javascrap.CompareEscrowDepositsCommand;
import google.registry.tools.javascrap.CreateCancellationsForOneTimesCommand;
import google.registry.util.UtilsModule;
import google.registry.whois.NonCachingWhoisModule;
import javax.annotation.Nullable;
@@ -95,6 +96,8 @@ interface RegistryToolComponent {
void inject(CreateAnchorTenantCommand command);
void inject(CreateCancellationsForOneTimesCommand command);
void inject(CreateCdnsTld command);
void inject(CreateContactCommand command);
@@ -33,7 +33,7 @@ import com.google.common.collect.ImmutableSortedSet;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.tools.soy.DomainRenewSoyInfo;
@@ -232,7 +232,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
private ImmutableList<ImmutableMap<String, Object>> getExistingDsData(Domain domain) {
ImmutableList.Builder<ImmutableMap<String, Object>> dsDataJsons = new ImmutableList.Builder();
HexBinaryAdapter hexBinaryAdapter = new HexBinaryAdapter();
for (DelegationSignerData dsData : domain.getDsData()) {
for (DomainDsData dsData : domain.getDsData()) {
dsDataJsons.add(
ImmutableMap.of(
"keyTag", dsData.getKeyTag(),
@@ -31,6 +31,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
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.Unit;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry.Type;
import google.registry.util.Clock;
@@ -88,7 +88,7 @@ class UnrenewDomainCommand extends ConfirmingCommand implements CommandWithRemot
new ImmutableMap.Builder<>();
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);
continue;
}
@@ -0,0 +1,45 @@
// 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.tools;
import static com.google.common.base.Preconditions.checkArgument;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import google.registry.model.domain.token.PackagePromotion;
import java.util.Optional;
/** Command to update a PackagePromotion */
@Parameters(separators = " =", commandDescription = "Update package promotion object(s)")
public final class UpdatePackagePromotionCommand extends CreateOrUpdatePackagePromotionCommand {
@Parameter(
names = "--clear_last_notification_sent",
description =
"Clear the date last max-domain non-compliance notification was sent? (This should be"
+ " cleared whenever a tier is upgraded)")
boolean clearLastNotificationSent;
@Override
PackagePromotion getOldPackagePromotion(String token) {
Optional<PackagePromotion> oldPackage = PackagePromotion.loadByTokenString(token);
checkArgument(oldPackage.isPresent(), "PackagePromotion with token %s does not exist", token);
return oldPackage.get();
}
@Override
boolean clearLastNotificationSent() {
return clearLastNotificationSent;
}
}
@@ -0,0 +1,126 @@
// 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.tools.javascrap;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableSet;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Cancellation;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.QueryComposer.Comparator;
import google.registry.tools.CommandWithRemoteApi;
import google.registry.tools.ConfirmingCommand;
import google.registry.tools.params.LongParameter;
import java.util.List;
/**
* Command to create {@link Cancellation}s for specified {@link OneTime} billing events.
*
* <p>This can be used to fix situations where we've inadvertently billed registrars. It's generally
* easier and better to issue cancellations within the Nomulus system than to attempt to issue
* refunds after the fact.
*/
@Parameters(separators = " =", commandDescription = "Manually create Cancellations for OneTimes.")
public class CreateCancellationsForOneTimesCommand extends ConfirmingCommand
implements CommandWithRemoteApi {
@Parameter(
description = "Space-delimited billing event ID(s) to cancel",
required = true,
validateWith = LongParameter.class)
private List<Long> mainParameters;
private ImmutableSet<OneTime> oneTimesToCancel;
@Override
protected void init() {
ImmutableSet.Builder<Long> missingIdsBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<Long> alreadyCancelledIdsBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<OneTime> oneTimesBuilder = new ImmutableSet.Builder<>();
tm().transact(
() -> {
for (Long billingEventId : ImmutableSet.copyOf(mainParameters)) {
VKey<OneTime> key = VKey.createSql(OneTime.class, billingEventId);
if (tm().exists(key)) {
OneTime oneTime = tm().loadByKey(key);
if (alreadyCancelled(oneTime)) {
alreadyCancelledIdsBuilder.add(billingEventId);
} else {
oneTimesBuilder.add(oneTime);
}
} else {
missingIdsBuilder.add(billingEventId);
}
}
});
oneTimesToCancel = oneTimesBuilder.build();
System.out.printf("Found %d OneTime(s) to cancel\n", oneTimesToCancel.size());
ImmutableSet<Long> missingIds = missingIdsBuilder.build();
if (!missingIds.isEmpty()) {
System.out.printf("Missing OneTime(s) for IDs %s\n", missingIds);
}
ImmutableSet<Long> alreadyCancelledIds = alreadyCancelledIdsBuilder.build();
if (!alreadyCancelledIds.isEmpty()) {
System.out.printf(
"The following OneTime IDs were already cancelled: %s\n", alreadyCancelledIds);
}
}
@Override
protected String prompt() {
return String.format("Create cancellations for %d OneTime(s)?", oneTimesToCancel.size());
}
@Override
protected String execute() throws Exception {
int cancelledOneTimes = 0;
for (OneTime oneTime : oneTimesToCancel) {
cancelledOneTimes +=
tm().transact(
() -> {
if (alreadyCancelled(oneTime)) {
System.out.printf(
"OneTime %d already cancelled, this is unexpected.\n", oneTime.getId());
return 0;
}
tm().put(
new Cancellation.Builder()
.setOneTimeEventKey(oneTime.createVKey())
.setBillingTime(oneTime.getBillingTime())
.setDomainHistoryId(oneTime.getDomainHistoryId())
.setRegistrarId(oneTime.getRegistrarId())
.setEventTime(oneTime.getEventTime())
.setReason(BillingEvent.Reason.ERROR)
.setTargetId(oneTime.getTargetId())
.build());
System.out.printf(
"Added Cancellation for OneTime with ID %d\n", oneTime.getId());
return 1;
});
}
return String.format("Created %d Cancellation event(s)", cancelledOneTimes);
}
private boolean alreadyCancelled(OneTime oneTime) {
return tm().createQueryComposer(Cancellation.class)
.where("refOneTime", Comparator.EQ, oneTime.getId())
.first()
.isPresent();
}
}
@@ -0,0 +1,137 @@
// 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.tools.javascrap;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableMap;
import dagger.Component;
import google.registry.beam.common.RegistryJpaIO;
import google.registry.beam.common.RegistryPipelineOptions;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.model.domain.Domain;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
import google.registry.persistence.VKey;
import java.io.Serializable;
import javax.inject.Singleton;
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
import org.apache.beam.sdk.transforms.DoFn;
import org.apache.beam.sdk.transforms.ParDo;
import org.joda.time.DateTime;
/**
* Pipeline that creates a synthetic history for every non-deleted {@link Domain} in SQL.
*
* <p>This is created to fix the issue identified in b/248112997. After b/245940594, there were some
* domains where the most recent history object did not represent the state of the domain as it
* exists in the world. Because RDE loads only from DomainHistory objects, this means that RDE was
* producing wrong data. This pipeline mitigates that issue by creating synthetic history events for
* every domain that was not deleted as of the start of the pipeline -- then, we can guarantee that
* this new history object represents the state of the domain as far as we know.
*
* <p>To run the pipeline (replace the environment as appropriate):
*
* <p><code>
* $ ./nom_build :core:createSyntheticDomainHistories --args="--region=us-central1
* --runner=DataflowRunner
* --registryEnvironment=CRASH
* --project={project-id}
* --workerMachineType=n2-standard-4"
* </code>
*/
public class CreateSyntheticDomainHistoriesPipeline implements Serializable {
private static final String HISTORY_REASON =
"Create synthetic domain histories to fix RDE for b/248112997";
private static final DateTime BAD_PIPELINE_START_TIME =
DateTime.parse("2022-09-05T09:00:00.000Z");
private static final DateTime BAD_PIPELINE_END_TIME = DateTime.parse("2022-09-10T12:00:00.000Z");
static void setup(Pipeline pipeline, String registryAdminRegistrarId) {
pipeline
.apply(
"Read all domain repo IDs",
RegistryJpaIO.read(
"SELECT d.repoId FROM Domain d WHERE deletionTime > :badPipelineStartTime AND NOT"
+ " EXISTS (SELECT 1 FROM DomainHistory dh WHERE dh.domainRepoId = d.repoId"
+ " AND dh.modificationTime > :badPipelineEndTime)",
ImmutableMap.of(
"badPipelineStartTime",
BAD_PIPELINE_START_TIME,
"badPipelineEndTime",
BAD_PIPELINE_END_TIME),
String.class,
repoId -> VKey.createSql(Domain.class, repoId)))
.apply(
"Save a synthetic DomainHistory for each domain",
ParDo.of(new DomainHistoryCreator(registryAdminRegistrarId)));
}
private static class DomainHistoryCreator extends DoFn<VKey<Domain>, Void> {
private final String registryAdminRegistrarId;
private DomainHistoryCreator(String registryAdminRegistrarId) {
this.registryAdminRegistrarId = registryAdminRegistrarId;
}
@ProcessElement
public void processElement(
@Element VKey<Domain> key, PipelineOptions options, OutputReceiver<Void> outputReceiver) {
jpaTm()
.transact(
() -> {
Domain domain = jpaTm().loadByKey(key);
jpaTm()
.put(
HistoryEntry.createBuilderForResource(domain)
.setRegistrarId(registryAdminRegistrarId)
.setBySuperuser(true)
.setRequestedByRegistrar(false)
.setModificationTime(jpaTm().getTransactionTime())
.setReason(HISTORY_REASON)
.setType(HistoryEntry.Type.SYNTHETIC)
.build());
outputReceiver.output(null);
});
}
}
public static void main(String[] args) {
RegistryPipelineOptions options =
PipelineOptionsFactory.fromArgs(args).withValidation().as(RegistryPipelineOptions.class);
RegistryPipelineOptions.validateRegistryPipelineOptions(options);
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
String registryAdminRegistrarId =
DaggerCreateSyntheticDomainHistoriesPipeline_ConfigComponent.create()
.getRegistryAdminRegistrarId();
Pipeline pipeline = Pipeline.create(options);
setup(pipeline, registryAdminRegistrarId);
pipeline.run();
}
@Singleton
@Component(modules = ConfigModule.class)
interface ConfigComponent {
@Config("registryAdminClientId")
String getRegistryAdminRegistrarId();
}
}
@@ -30,7 +30,7 @@ import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig.Config;
import google.registry.gcs.GcsUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.host.Host;
import google.registry.request.Action;
import google.registry.request.HttpException.BadRequestException;
@@ -241,7 +241,7 @@ public class GenerateZoneFilesAction implements Runnable, JsonActionRunner.JsonA
// Load the nameservers at the export time in case they've been renamed or deleted.
loadAtPointInTime(nameserver, exportTime).getHostName()));
}
for (DelegationSignerData dsData : domain.getDsData()) {
for (DomainDsData dsData : domain.getDsData()) {
result.append(
String.format(
DS_FORMAT,
@@ -69,7 +69,7 @@ public final class ListDomainsAction extends ListObjectsAction<Domain> {
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("fullyQualifiedDomainName");
return ImmutableSet.of("domainName");
}
@Override
@@ -44,7 +44,7 @@ public final class ListHostsAction extends ListObjectsAction<Host> {
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("fullyQualifiedHostName");
return ImmutableSet.of("hostName");
}
@Override
@@ -39,7 +39,7 @@ public final class ListRegistrarsAction extends ListObjectsAction<Registrar> {
@Override
public ImmutableSet<String> getPrimaryKeyFields() {
return ImmutableSet.of("clientIdentifier");
return ImmutableSet.of("registrarId");
}
@Override
@@ -51,7 +51,7 @@ public final class ListRegistrarsAction extends ListObjectsAction<Registrar> {
public ImmutableBiMap<String, String> getFieldAliases() {
return ImmutableBiMap.of(
"billingId", "billingIdentifier",
"clientId", "clientIdentifier",
"clientId", "registrarId",
"premiumNames", "blockPremiumNames");
}
@@ -82,7 +82,7 @@ public class RefreshDnsForAllDomainsAction implements Runnable {
() ->
jpaTm()
.query(
"SELECT fullyQualifiedDomainName FROM Domain "
"SELECT domainName FROM Domain "
+ "WHERE tld IN (:tlds) "
+ "AND deletionTime > :now",
String.class)
@@ -132,7 +132,7 @@ registry.json.Response.prototype.results;
/**
* @typedef {{
* allowedTlds: !Array<string>,
* clientIdentifier: string,
* registrarId: string,
* clientCertificate: string?,
* clientCertificateHash: string?,
* failoverClientCertificate: string?,
@@ -50,7 +50,7 @@
<class>google.registry.model.domain.DomainHistory</class>
<class>google.registry.model.domain.GracePeriod</class>
<class>google.registry.model.domain.GracePeriod$GracePeriodHistory</class>
<class>google.registry.model.domain.secdns.DelegationSignerData</class>
<class>google.registry.model.domain.secdns.DomainDsData</class>
<class>google.registry.model.domain.secdns.DomainDsDataHistory</class>
<class>google.registry.model.domain.token.AllocationToken</class>
<class>google.registry.model.domain.token.PackagePromotion</class>
@@ -26,7 +26,7 @@
-- query's performance and consider switching to using a native query.
SELECT b, r FROM BillingEvent b
JOIN Registrar r ON b.clientId = r.clientIdentifier
JOIN Registrar r ON b.clientId = r.registrarId
JOIN Domain d ON b.domainRepoId = d.repoId
JOIN Tld t ON t.tldStr = d.tld
LEFT JOIN BillingCancellation c ON b.id = c.refOneTime
@@ -19,14 +19,14 @@
-- email address.
SELECT
domain.fullyQualifiedDomainName AS domainName,
domain.domainName AS domainName,
domain.__key__.name AS domainRepoId,
registrar.clientId AS registrarId,
COALESCE(registrar.emailAddress, '') AS registrarEmailAddress
FROM ( (
SELECT
__key__,
fullyQualifiedDomainName,
domainName,
currentSponsorClientId,
creationTime
FROM
@@ -122,7 +122,7 @@
</tr>
{for $threat in $threats}
<tr>
<td>{$threat['fullyQualifiedDomainName']}</td>
<td>{$threat['domainName']}</td>
<td>{$threat['threatType']}</td>
</tr>
{/for}
@@ -18,7 +18,7 @@
/** Registrar whois settings page for view and edit. */
{template .settings}
{@param clientIdentifier: string}
{@param registrarId: string}
{@param? ianaIdentifier: int}
{@param? icannReferralEmail: string}
{@param readonly: bool}
@@ -40,8 +40,8 @@
<table>
{call registry.soy.forms.inputFieldRowWithValue}
{param label: 'Name' /}
{param name: 'clientIdentifier' /}
{param value: $clientIdentifier /}
{param name: 'registrarId' /}
{param value: $registrarId /}
{param readonly: true /}
{/call}
{call registry.soy.forms.inputFieldRowWithValue}
@@ -98,7 +98,7 @@ public class ResaveEntityActionTest {
clock.advanceOneMilli();
assertThat(domain.getCurrentSponsorRegistrarId()).isEqualTo("TheRegistrar");
runAction(
domain.createVKey().getOfyKey().getString(),
domain.createVKey().stringify(),
DateTime.parse("2016-02-06T10:00:01Z"),
ImmutableSortedSet.of());
Domain resavedDomain = loadByEntity(domain);
@@ -128,7 +128,7 @@ public class ResaveEntityActionTest {
assertThat(domain.getGracePeriods()).isNotEmpty();
runAction(
domain.createVKey().getOfyKey().getString(),
domain.createVKey().stringify(),
requestedTime,
ImmutableSortedSet.of(requestedTime.plusDays(5)));
Domain resavedDomain = loadByEntity(domain);
@@ -33,7 +33,7 @@ import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.registrar.Registrar;
@@ -110,7 +110,7 @@ public class RegistryJpaReadTest {
Read<Object[], String> read =
RegistryJpaIO.read(
"select d, r.emailAddress from Domain d join Registrar r on"
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = :type"
+ " d.currentSponsorClientId = r.registrarId where r.type = :type"
+ " and d.deletionTime > now()",
ImmutableMap.of("type", Registrar.Type.REAL),
false,
@@ -152,7 +152,7 @@ public class RegistryJpaReadTest {
Read<Domain, String> read =
RegistryJpaIO.read(
"select d from Domain d join Registrar r on"
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = :type"
+ " d.currentSponsorClientId = r.registrarId where r.type = :type"
+ " and d.deletionTime > now()",
ImmutableMap.of("type", Registrar.Type.REAL),
Domain.class,
@@ -200,7 +200,7 @@ public class RegistryJpaReadTest {
.setPersistedCurrentSponsorRegistrarId(registrar.getRegistrarId())
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password")))
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2})))
.setDsData(ImmutableSet.of(DomainDsData.create(1, 2, 3, new byte[] {0, 1, 2})))
.setLaunchNotice(
LaunchNotice.create("tcnid", "validatorId", START_OF_TIME, START_OF_TIME))
.setSmdId("smdid")
@@ -355,7 +355,7 @@ class InvoicingPipelineTest {
.isEqualTo(
'\n'
+ "SELECT b, r FROM BillingEvent b\n"
+ "JOIN Registrar r ON b.clientId = r.clientIdentifier\n"
+ "JOIN Registrar r ON b.clientId = r.registrarId\n"
+ "JOIN Domain d ON b.domainRepoId = d.repoId\n"
+ "JOIN Tld t ON t.tldStr = d.tld\n"
+ "LEFT JOIN BillingCancellation c ON b.id = c.refOneTime\n"
@@ -104,7 +104,7 @@ public class PublishDnsUpdatesActionTest {
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain1);
persistActiveSubordinateHost("ns2.example.xn--q9jyb4c", domain1);
Domain domain2 = persistActiveDomain("example2.xn--q9jyb4c");
persistActiveSubordinateHost("ns1.example.xn--q9jyb4c", domain2);
persistActiveSubordinateHost("ns1.example2.xn--q9jyb4c", domain2);
clock.advanceOneMilli();
}
@@ -39,7 +39,7 @@ import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.RateLimiter;
import google.registry.dns.writer.clouddns.CloudDnsWriter.ZoneStateException;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
@@ -267,7 +267,7 @@ public class CloudDnsWriterTest {
for (int i = 0; i < dsRecords; i++) {
dsRecordData.add(
DelegationSignerData.create(i, 3, 1, base16().decode("1234567890ABCDEF")).toRrData());
DomainDsData.create(i, 3, 1, base16().decode("1234567890ABCDEF")).toRrData());
}
recordSetBuilder.add(
new ResourceRecordSet()
@@ -284,10 +284,10 @@ public class CloudDnsWriterTest {
/** Returns a domain to be persisted in Datastore. */
private static Domain fakeDomain(
String domainName, ImmutableSet<Host> nameservers, int numDsRecords) {
ImmutableSet.Builder<DelegationSignerData> dsDataBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<DomainDsData> dsDataBuilder = new ImmutableSet.Builder<>();
for (int i = 0; i < numDsRecords; i++) {
dsDataBuilder.add(DelegationSignerData.create(i, 3, 1, base16().decode("1234567890ABCDEF")));
dsDataBuilder.add(DomainDsData.create(i, 3, 1, base16().decode("1234567890ABCDEF")));
}
ImmutableSet.Builder<VKey<Host>> hostRefBuilder = new ImmutableSet.Builder<>();
@@ -36,7 +36,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import google.registry.model.domain.Domain;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host;
import google.registry.testing.AppEngineExtension;
@@ -177,8 +177,7 @@ public class DnsUpdateWriterTest {
.asBuilder()
.setNameservers(ImmutableSet.of(persistActiveHost("ns1.example.tld").createVKey()))
.setDsData(
ImmutableSet.of(
DelegationSignerData.create(1, 3, 1, base16().decode("0123456789ABCDEF"))))
ImmutableSet.of(DomainDsData.create(1, 3, 1, base16().decode("0123456789ABCDEF"))))
.build();
persistResource(domain);
@@ -193,7 +193,7 @@ public class SyncRegistrarsSheetTest {
assertThat(rows).hasSize(2);
ImmutableMap<String, String> row = rows.get(0);
assertThat(row).containsEntry("clientIdentifier", "aaaregistrar");
assertThat(row).containsEntry("registrarId", "aaaregistrar");
assertThat(row).containsEntry("registrarName", "AAA Registrar Inc.");
assertThat(row).containsEntry("state", "SUSPENDED");
assertThat(row).containsEntry("ianaIdentifier", "8");
@@ -288,7 +288,7 @@ public class SyncRegistrarsSheetTest {
assertThat(row).containsEntry("billingAccountMap", "{JPY=JPY7890, USD=USD1234}");
row = rows.get(1);
assertThat(row).containsEntry("clientIdentifier", "anotherregistrar");
assertThat(row).containsEntry("registrarId", "anotherregistrar");
assertThat(row).containsEntry("registrarName", "Another Registrar LLC");
assertThat(row).containsEntry("state", "ACTIVE");
assertThat(row).containsEntry("ianaIdentifier", "1");
@@ -337,7 +337,7 @@ public class SyncRegistrarsSheetTest {
verify(sheetSynchronizer).synchronize(eq("foobar"), rowsCaptor.capture());
ImmutableMap<String, String> row = getOnlyElement(getOnlyElement(rowsCaptor.getAllValues()));
assertThat(row).containsEntry("clientIdentifier", "SomeRegistrar");
assertThat(row).containsEntry("registrarId", "SomeRegistrar");
assertThat(row).containsEntry("registrarName", "Some Registrar");
assertThat(row).containsEntry("state", "ACTIVE");
assertThat(row).containsEntry("ianaIdentifier", "8");
@@ -52,7 +52,6 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
}
private void doSuccessfulTest() throws Exception {
persistActiveContact(getUniqueIdFromCommand());
clock.advanceOneMilli();
assertTransactionalFlow(true);
runFlowAssertResponse(loadFile("generic_success_response.xml"));
@@ -83,6 +82,7 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
@Test
void testSuccess() throws Exception {
persistActiveContact(getUniqueIdFromCommand());
doSuccessfulTest();
}
@@ -403,6 +403,7 @@ class ContactUpdateFlowTest extends ResourceFlowTestCase<ContactUpdateFlow, Cont
@Test
void testSuccess_nonAsciiInLocAddress() throws Exception {
setEppInput("contact_update_hebrew_loc.xml");
persistActiveContact(getUniqueIdFromCommand());
doSuccessfulTest();
}
@@ -161,7 +161,7 @@ import google.registry.model.domain.fee.BaseFee.FeeType;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
@@ -860,7 +860,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertAboutDomains()
.that(domain)
.hasExactlyDsData(
DelegationSignerData.create(
DomainDsData.create(
12345, 3, 1, base16().decode("A94A8FE5CCB19BA61C4C0873D391E987982FBBD3"))
.cloneWithDomainRepoId(domain.getRepoId()));
}
@@ -3159,15 +3159,14 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
@Test
void testFailure_packageToken_registrationTooLong() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
persistContactsAndHosts();
setEppInput(
"domain_create_allocationtoken.xml",
@@ -64,7 +64,7 @@ import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.GracePeriod;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
@@ -335,8 +335,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
.asBuilder()
.setDsData(
ImmutableSet.of(
DelegationSignerData.create(
12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
DomainDsData.create(12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
.setNameservers(ImmutableSet.of(host1.createVKey(), host3.createVKey()))
.build());
doSuccessfulTest("domain_info_response_dsdata.xml", false);
@@ -541,8 +540,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Domain> {
null))
.setDsData(
ImmutableSet.of(
DelegationSignerData.create(
12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
DomainDsData.create(12345, 3, 1, base16().decode("49FD46E6C4B45C55D4AC"))))
.build());
doSuccessfulTest("domain_info_response_dsdata_addperiod.xml", false);
}
@@ -442,7 +442,6 @@ class DomainTransferApproveFlowTest
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
setupDomainWithPendingTransfer("example", "tld");
domain = loadByEntity(domain);
persistResource(
loadByKey(domain.getAutorenewBillingEvent())
@@ -489,7 +488,6 @@ class DomainTransferApproveFlowTest
.setLabelsToPrices(ImmutableMap.of("example", new BigDecimal("67.89")))
.build());
persistResource(Registry.get("tld").asBuilder().setPremiumList(pl).build());
setupDomainWithPendingTransfer("example", "tld");
domain = loadByEntity(domain);
persistResource(
loadByKey(domain.getAutorenewBillingEvent())
@@ -170,7 +170,7 @@ class DomainTransferCancelFlowTest
.setResponseData(
ImmutableList.of(
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(getUniqueIdFromCommand())
.setDomainName(getUniqueIdFromCommand())
.setTransferStatus(TransferStatus.CLIENT_CANCELLED)
.setTransferRequestTime(TRANSFER_REQUEST_TIME)
.setGainingRegistrarId("NewRegistrar")
@@ -21,6 +21,9 @@ import static com.google.common.truth.Truth8.assertThat;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_REQUESTED_TIME;
import static google.registry.batch.AsyncTaskEnqueuer.PARAM_RESOURCE_KEY;
import static google.registry.batch.AsyncTaskEnqueuer.QUEUE_ASYNC_ACTIONS;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.domain.token.AllocationToken.TokenType.PACKAGE;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
@@ -61,6 +64,7 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.truth.Truth8;
import com.googlecode.objectify.Key;
import google.registry.batch.ResaveEntityAction;
import google.registry.flows.EppException;
@@ -85,6 +89,7 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException;
import google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException;
import google.registry.flows.exceptions.AlreadyPendingTransferException;
import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
import google.registry.flows.exceptions.MissingTransferRequestAuthInfoException;
@@ -1700,13 +1705,15 @@ class DomainTransferRequestFlowTest
.setDomainName("example.tld")
.build());
doSuccessfulTest(
"domain_transfer_request_allocation_token.xml", "domain_transfer_request_response.xml");
"domain_transfer_request_allocation_token.xml",
"domain_transfer_request_response.xml",
ImmutableMap.of("TOKEN", "abc123"));
}
@Test
void testFailure_invalidAllocationToken() throws Exception {
setupDomain("example", "tld");
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(InvalidAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1720,7 +1727,7 @@ class DomainTransferRequestFlowTest
.setTokenType(SINGLE_USE)
.setDomainName("otherdomain.tld")
.build());
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown =
assertThrows(AllocationTokenNotValidForDomainException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1741,7 +1748,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(60), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(AllocationTokenNotInPromotionException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1762,7 +1769,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown =
assertThrows(AllocationTokenNotValidForRegistrarException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1784,7 +1791,7 @@ class DomainTransferRequestFlowTest
.put(clock.nowUtc().plusDays(1), TokenStatus.ENDED)
.build())
.build());
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown = assertThrows(AllocationTokenNotValidForTldException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@@ -1800,9 +1807,89 @@ class DomainTransferRequestFlowTest
.setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.build());
setEppInput("domain_transfer_request_allocation_token.xml");
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
EppException thrown =
assertThrows(AlreadyRedeemedAllocationTokenException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
void testFailsPackageDomainInvalidAllocationToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("example", "tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
setupDomain("example", "tld");
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
setEppInput("domain_transfer_request_allocation_token.xml", ImmutableMap.of("TOKEN", "abc123"));
assertThrows(MissingRemovePackageTokenOnPackageDomainException.class, this::runFlow);
}
@Test
void testFailsToTransferPackageDomainNoRemovePackageToken() throws Exception {
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("example", "tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
setupDomain("example", "tld");
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
setEppInput("domain_transfer_request.xml");
assertThrows(MissingRemovePackageTokenOnPackageDomainException.class, this::runFlow);
}
@Test
void testSuccesfullyAppliesRemovePackageToken() throws Exception {
setupDomain("example", "tld");
AllocationToken token =
persistResource(
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(PACKAGE)
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setRenewalPriceBehavior(SPECIFIED)
.build());
domain = loadByEntity(domain);
persistResource(
loadByKey(domain.getAutorenewBillingEvent())
.asBuilder()
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
.setRenewalPrice(Money.of(USD, new BigDecimal("10.00")))
.build());
persistResource(
reloadResourceByForeignKey()
.asBuilder()
.setCurrentPackageToken(token.createVKey())
.build());
doSuccessfulTest(
"domain_transfer_request_allocation_token.xml",
"domain_transfer_request_response.xml",
ImmutableMap.of("TOKEN", "__REMOVEPACKAGE__"));
Domain domain = reloadResourceByForeignKey();
Truth8.assertThat(domain.getCurrentPackageToken()).isEmpty();
RenewalPriceBehavior priceBehavior =
loadByKey(domain.getAutorenewBillingEvent()).getRenewalPriceBehavior();
assertThat(priceBehavior).isEqualTo(DEFAULT);
}
}

Some files were not shown because too many files have changed in this diff Show More