diff --git a/java/google/registry/rde/imports/RdeDomainImportAction.java b/java/google/registry/rde/imports/RdeDomainImportAction.java index 3f883f1cc..c53102824 100644 --- a/java/google/registry/rde/imports/RdeDomainImportAction.java +++ b/java/google/registry/rde/imports/RdeDomainImportAction.java @@ -18,6 +18,7 @@ import static google.registry.flows.domain.DomainTransferUtils.createLosingTrans import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData; import static google.registry.flows.domain.DomainTransferUtils.createTransferServerApproveEntities; import static google.registry.mapreduce.MapreduceRunner.PARAM_MAP_SHARDS; +import static google.registry.model.domain.DomainResource.extendRegistrationWithCap; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; import static google.registry.rde.imports.RdeImportUtils.createAutoRenewBillingEventForDomainImport; @@ -44,6 +45,7 @@ import google.registry.model.billing.BillingEvent; import google.registry.model.domain.DomainResource; import google.registry.model.domain.Period; import google.registry.model.domain.Period.Unit; +import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.poll.PollMessage; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferData; @@ -60,6 +62,7 @@ import google.registry.xjc.rdedomain.XjcRdeDomain; import google.registry.xjc.rdedomain.XjcRdeDomainElement; import javax.inject.Inject; import org.joda.money.Money; +import org.joda.time.DateTime; /** * A mapreduce that imports domains from an escrow file. @@ -171,69 +174,118 @@ public class RdeDomainImportAction implements Runnable { getContext().incrementCounter("domain imports attempted"); logger.infofmt("Saving domain %s", xjcDomain.getName()); - ofy().transact(new VoidWork() { - @Override - public void vrun() { - HistoryEntry historyEntry = createHistoryEntryForDomainImport(xjcDomain); - BillingEvent.Recurring autorenewBillingEvent = - createAutoRenewBillingEventForDomainImport(xjcDomain, historyEntry); - PollMessage.Autorenew autorenewPollMessage = - createAutoRenewPollMessageForDomainImport(xjcDomain, historyEntry); - DomainResource domain = XjcToDomainResourceConverter.convertDomain( - xjcDomain, autorenewBillingEvent, autorenewPollMessage); - getDnsQueue().addDomainRefreshTask(domain.getFullyQualifiedDomainName()); - // Keep a list of "extra objects" that need to be saved along with the domain - // and add to it if necessary. - ImmutableSet extraEntitiesToSave = - getImportUtils().createIndexesForEppResource(domain); - // Create speculative server approval entities for pending transfers - if (domain.getTransferData().getTransferStatus() == TransferStatus.PENDING) { - TransferData transferData = domain.getTransferData(); - checkArgumentNotNull(transferData, - "Domain %s is in pending transfer but has no transfer data", - domain.getFullyQualifiedDomainName()); - Money transferCost = getDomainRenewCost( - domain.getFullyQualifiedDomainName(), - transferData.getPendingTransferExpirationTime(), - 1); - // Create speculative entities in anticipation of an automatic server approval. - ImmutableSet serverApproveEntities = - createTransferServerApproveEntities( - transferData.getPendingTransferExpirationTime(), - domain.getRegistrationExpirationTime().plusYears(1), - historyEntry, - domain, - historyEntry.getTrid(), - transferData.getGainingClientId(), - Optional.of(transferCost), - transferData.getTransferRequestTime()); - transferData = - createPendingTransferData( - transferData.asBuilder(), - serverApproveEntities, - Period.create(1, Unit.YEARS)); - // Create a poll message to notify the losing registrar that a transfer was requested. - PollMessage requestPollMessage = createLosingTransferPollMessage(domain.getRepoId(), - transferData, transferData.getPendingTransferExpirationTime(), historyEntry) - .asBuilder().setEventTime(transferData.getTransferRequestTime()).build(); - domain = domain.asBuilder().setTransferData(transferData).build(); - autorenewBillingEvent = autorenewBillingEvent.asBuilder() - .setRecurrenceEndTime(transferData.getPendingTransferExpirationTime()).build(); - autorenewPollMessage = autorenewPollMessage.asBuilder() - .setAutorenewEndTime(transferData.getPendingTransferExpirationTime()).build(); - extraEntitiesToSave = new ImmutableSet.Builder<>() - .add(requestPollMessage) - .addAll(extraEntitiesToSave) - .addAll(serverApproveEntities).build(); - } // End pending transfer check - ofy().save() - .entities(new ImmutableSet.Builder<>() - .add(domain, historyEntry, autorenewBillingEvent, autorenewPollMessage) - .addAll(extraEntitiesToSave) - .build()) - .now(); - } - }); + ofy() + .transact( + new VoidWork() { + @Override + public void vrun() { + HistoryEntry historyEntry = createHistoryEntryForDomainImport(xjcDomain); + BillingEvent.Recurring autorenewBillingEvent = + createAutoRenewBillingEventForDomainImport(xjcDomain, historyEntry); + PollMessage.Autorenew autorenewPollMessage = + createAutoRenewPollMessageForDomainImport(xjcDomain, historyEntry); + DomainResource domain = + XjcToDomainResourceConverter.convertDomain( + xjcDomain, autorenewBillingEvent, autorenewPollMessage); + getDnsQueue().addDomainRefreshTask(domain.getFullyQualifiedDomainName()); + // Keep a list of "extra objects" that need to be saved along with the domain + // and add to it if necessary. + ImmutableSet extraEntitiesToSave = + getImportUtils().createIndexesForEppResource(domain); + // Create speculative server approval entities for pending transfers + if (domain.getTransferData().getTransferStatus() == TransferStatus.PENDING) { + TransferData transferData = domain.getTransferData(); + checkArgumentNotNull( + transferData, + "Domain %s is in pending transfer but has no transfer data", + domain.getFullyQualifiedDomainName()); + Money transferCost = + getDomainRenewCost( + domain.getFullyQualifiedDomainName(), + transferData.getPendingTransferExpirationTime(), + 1); + DateTime automaticTransferTime = + transferData.getPendingTransferExpirationTime(); + // If the transfer will occur within the autorenew grace period, it should + // subsume the autorenew, so we don't add the normal extra year. See the + // original logic in DomainTransferRequestFlow (which is very similar) for + // more information. That said, note that here we stop 1 millisecond before + // the actual transfer time to avoid hitting the transfer-handling part of + // cloneProjectedAtTime(), since unlike in the DomainTransferRequestFlow case, + // this domain already has a pending transfer. + DomainResource domainAtTransferTime = + domain.cloneProjectedAtTime(automaticTransferTime.minusMillis(1)); + boolean inAutorenewGraceAtTransfer = + !domainAtTransferTime + .getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW) + .isEmpty(); + int extraYears = inAutorenewGraceAtTransfer ? 0 : 1; + // Construct the capped new expiration time. + DateTime serverApproveNewExpirationTime = + extendRegistrationWithCap( + automaticTransferTime, + domainAtTransferTime.getRegistrationExpirationTime(), + extraYears); + // Create speculative entities in anticipation of an automatic server + // approval. + ImmutableSet serverApproveEntities = + createTransferServerApproveEntities( + automaticTransferTime, + serverApproveNewExpirationTime, + historyEntry, + domain, + historyEntry.getTrid(), + transferData.getGainingClientId(), + Optional.of(transferCost), + transferData.getTransferRequestTime()); + transferData = + createPendingTransferData( + transferData.asBuilder(), + serverApproveEntities, + Period.create(1, Unit.YEARS)); + // Create a poll message to notify the losing registrar that a transfer was + // requested. + PollMessage requestPollMessage = + createLosingTransferPollMessage( + domain.getRepoId(), + transferData, + serverApproveNewExpirationTime, + historyEntry) + .asBuilder() + .setEventTime(transferData.getTransferRequestTime()) + .build(); + domain = domain.asBuilder().setTransferData(transferData).build(); + autorenewBillingEvent = + autorenewBillingEvent + .asBuilder() + .setRecurrenceEndTime(transferData.getPendingTransferExpirationTime()) + .build(); + autorenewPollMessage = + autorenewPollMessage + .asBuilder() + .setAutorenewEndTime(transferData.getPendingTransferExpirationTime()) + .build(); + extraEntitiesToSave = + new ImmutableSet.Builder<>() + .add(requestPollMessage) + .addAll(extraEntitiesToSave) + .addAll(serverApproveEntities) + .build(); + } // End pending transfer check + ofy() + .save() + .entities( + new ImmutableSet.Builder<>() + .add( + domain, + historyEntry, + autorenewBillingEvent, + autorenewPollMessage) + .addAll(extraEntitiesToSave) + .build()) + .now(); + } + }); // Record the number of domains imported getContext().incrementCounter("domains saved"); logger.infofmt("Domain %s was imported successfully", xjcDomain.getName()); diff --git a/java/google/registry/rde/imports/RdeImportUtils.java b/java/google/registry/rde/imports/RdeImportUtils.java index 015c98c53..8515d1b1a 100644 --- a/java/google/registry/rde/imports/RdeImportUtils.java +++ b/java/google/registry/rde/imports/RdeImportUtils.java @@ -16,12 +16,12 @@ package google.registry.rde.imports; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.PreconditionsUtils.checkArgumentPresent; import static java.nio.charset.StandardCharsets.US_ASCII; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.joda.time.DateTimeZone.UTC; import com.google.appengine.tools.cloudstorage.GcsFilename; import com.google.common.collect.ImmutableSet; @@ -61,7 +61,6 @@ import java.util.UUID; import javax.inject.Inject; import javax.xml.bind.JAXBException; import javax.xml.stream.XMLStreamException; -import org.joda.time.DateTime; /** * Utility functions for escrow file import. @@ -89,14 +88,9 @@ public class RdeImportUtils { public ImmutableSet createIndexesForEppResource(T resource) { @SuppressWarnings("unchecked") - Class resourceClass = (Class) resource.getClass(); - Object existing = ofy.load().key(Key.create(resource)).now(); - if (existing != null) { - // This will roll back the transaction and prevent duplicate history entries from being saved. - throw new ResourceExistsException(); - } ForeignKeyIndex existingForeignKeyIndex = - ForeignKeyIndex.load(resourceClass, resource.getForeignKey(), START_OF_TIME); + ForeignKeyIndex.load( + (Class) resource.getClass(), resource.getForeignKey(), START_OF_TIME); // ForeignKeyIndex should never have existed, since existing resource was not found. checkState( existingForeignKeyIndex == null, @@ -111,20 +105,27 @@ public class RdeImportUtils { /** * Imports a resource from an escrow file. * - *

The host will only be imported if it has not been previously imported. + *

The resource will only be imported if it has not been previously imported. * - *

If the host is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also + *

If the resource is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also * created. */ public void importEppResource(final T resource) { + Object existing = ofy.load().key(Key.create(resource)).now(); + if (existing != null) { + // This will roll back the transaction and prevent duplicate history entries from being saved. + throw new ResourceExistsException(); + } ofy.save().entities(new ImmutableSet.Builder<>() .add(resource) .addAll(createIndexesForEppResource(resource)) .build()); logger.infofmt( "Imported %s resource - ROID=%s, id=%s", - resource.getClass().getCanonicalName(), resource.getRepoId(), resource.getForeignKey()); + resource.getClass().getSimpleName(), + resource.getRepoId(), + resource.getForeignKey()); } /** @@ -221,7 +222,7 @@ public class RdeImportUtils { .setType(HistoryEntry.Type.RDE_IMPORT) .setClientId(domain.getClID()) .setTrid(generateTridForImport()) - .setModificationTime(DateTime.now(UTC)) + .setModificationTime(ofy().getTransactionTime()) .setXmlBytes(getObjectXml(element)) .setBySuperuser(true) .setReason("RDE Import") diff --git a/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java index 4007c62b4..b403bd0f6 100644 --- a/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java +++ b/javatests/google/registry/rde/imports/RdeDomainImportActionTest.java @@ -46,6 +46,7 @@ import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.eppcommon.Trid; import google.registry.model.poll.PollMessage; import google.registry.model.reporting.HistoryEntry; +import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.model.transfer.TransferStatus; import google.registry.request.Response; import google.registry.testing.FakeResponse; @@ -70,6 +71,8 @@ public class RdeDomainImportActionTest extends MapreduceTestCase domains = ofy().load().type(DomainResource.class).list(); + assertThat(domains).hasSize(1); + checkDomain(domains.get(0)); + + // Domain should be assigned to RegistrarX before server approval + DomainResource beforeApproval = + domains.get(0).cloneProjectedAtTime(serverApprovalTime.minus(Seconds.ONE)); + assertThat(beforeApproval.getCurrentSponsorClientId()).isEqualTo("RegistrarX"); + assertThat(loadAutorenewBillingEventForDomain(beforeApproval).getClientId()) + .isEqualTo("RegistrarX"); + assertThat(loadAutorenewPollMessageForDomain(beforeApproval).getClientId()) + .isEqualTo("RegistrarX"); + // Current expiration is 2024-04-03T22:00:00.0Z + assertThat(beforeApproval.getRegistrationExpirationTime()) + .isEqualTo(DateTime.parse("2024-04-03T22:00:00.0Z")); + + // Domain should be assigned to RegistrarY after server approval + DomainResource afterApproval = + domains.get(0).cloneProjectedAtTime(serverApprovalTime); + assertThat(afterApproval.getCurrentSponsorClientId()).isEqualTo("RegistrarY"); + // New expiration should be capped at 10 years from server approval time, which is 2025-02-03, + // instead of 2025-04-03 which would be the current expiration plus a full year. + assertThat(afterApproval.getRegistrationExpirationTime()) + .isEqualTo(DateTime.parse("2025-02-03T22:00:00.0Z")); + + // Same checks for the autorenew billing event and poll message. + checkAutorenewBillingEvent( + afterApproval, "RegistrarY", DateTime.parse("2025-02-03T22:00:00.0Z"), END_OF_TIME); + checkAutorenewPollMessage( + afterApproval, "RegistrarY", DateTime.parse("2025-02-03T22:00:00.0Z"), END_OF_TIME); + + // Check expiration time in losing registrar's poll message responseData. + checkTransferRequestPollMessage(domains.get(0), "RegistrarX", + DateTime.parse("2015-01-29T22:00:00.0Z"), + DateTime.parse("2025-02-03T22:00:00.0Z")); + } + @Test public void testMapreducePendingTransferEvents() throws Exception { pushToGcs(DEPOSIT_1_DOMAIN_PENDING_TRANSFER); @@ -220,11 +265,18 @@ public class RdeDomainImportActionTest extends MapreduceTestCase domains = ofy().load().type(DomainResource.class).list(); assertThat(domains).hasSize(1); checkDomain(domains.get(0)); - checkTransferRequestPollMessage(domains.get(0), "RegistrarX", - DateTime.parse("2015-01-03T22:00:00.0Z")); - checkTransferServerApprovalPollMessage(domains.get(0), "RegistrarX", + checkTransferRequestPollMessage( + domains.get(0), + "RegistrarX", + DateTime.parse("2015-01-03T22:00:00.0Z"), + DateTime.parse("2016-04-03T22:00:00.0Z")); + checkTransferServerApprovalPollMessage( + domains.get(0), + "RegistrarX", DateTime.parse("2015-01-08T22:00:00.0Z")); - checkTransferServerApprovalPollMessage(domains.get(0), "RegistrarY", + checkTransferServerApprovalPollMessage( + domains.get(0), + "RegistrarY", DateTime.parse("2015-01-08T22:00:00.0Z")); // Billing event is set to the end of the transfer grace period, 5 days after server approval checkTransferBillingEvent(domains.get(0), DateTime.parse("2015-01-13T22:00:00.0Z")); @@ -244,11 +296,17 @@ public class RdeDomainImportActionTest extends MapreduceTestCase() { - @SuppressWarnings("unchecked") @Override public DomainResource run() { + HistoryEntry historyEntry = createHistoryEntryForDomainImport(xjcDomain); + BillingEvent.Recurring autorenewBillingEvent = + createAutoRenewBillingEventForDomainImport(xjcDomain, historyEntry); + PollMessage.Autorenew autorenewPollMessage = + createAutoRenewPollMessageForDomainImport(xjcDomain, historyEntry); ofy().save().entities(historyEntry, autorenewBillingEvent, autorenewPollMessage); return XjcToDomainResourceConverter.convertDomain( xjcDomain, autorenewBillingEvent, autorenewPollMessage); diff --git a/javatests/google/registry/rde/imports/testdata/deposit_1_domain_pending_transfer_registration_cap.xml b/javatests/google/registry/rde/imports/testdata/deposit_1_domain_pending_transfer_registration_cap.xml new file mode 100644 index 000000000..6034483e0 --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/deposit_1_domain_pending_transfer_registration_cap.xml @@ -0,0 +1,250 @@ + + + + 2010-10-17T00:00:00Z + + 1.0 + urn:ietf:params:xml:ns:rdeHeader-1.0 + urn:ietf:params:xml:ns:rdeContact-1.0 + urn:ietf:params:xml:ns:rdeHost-1.0 + urn:ietf:params:xml:ns:rdeDomain-1.0 + urn:ietf:params:xml:ns:rdeRegistrar-1.0 + urn:ietf:params:xml:ns:rdeIDN-1.0 + urn:ietf:params:xml:ns:rdeNNDN-1.0 + urn:ietf:params:xml:ns:rdeEppParams-1.0 + + + + + + + test + 2 + + 1 + + 1 + + 1 + + 1 + + 1 + + 1 + + + + + + example1.test + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2024-04-03T22:00:00.0Z + RegistrarY + 2015-01-29T22:00:00.0Z + + pending + RegistrarY + 2015-01-29T22:00:00.0Z + RegistrarX + 2015-02-03T22:00:00.0Z + 2025-02-03T22:00:00.0Z + + + + + + ns1.example.com + Hns1_example_com-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + ns1.example1.test + Hns1_example1_test-TEST + + + 192.0.2.2 + 192.0.2.29 + 1080:0:0:0:8:800:200C:417A + + RegistrarX + RegistrarX + 1999-05-08T12:10:00.0Z + RegistrarX + 2009-10-03T09:34:00.0Z + + + + + sh8013 + Csh8013-TEST + + + + John Doe + Example Inc. + + 123 Example Dr. + Suite 100 + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + RegistrarX + RegistrarX + + 2009-09-13T08:01:00.0Z + RegistrarX + + 2009-11-26T09:10:00.0Z + 2009-12-03T09:05:00.0Z + + + + + + + + + RegistrarX + Registrar X + 123 + ok + + + 123 Example Dr. + + Suite 100 + + Dulles + VA + 20166-6503 + US + + + +1.7035555555 + + +1.7035555556 + + jdoe@example.test + + http://www.example.test + + + whois.example.test + + http://whois.example.test + + + 2005-04-23T11:49:00.0Z + 2009-02-17T17:51:00.0Z + + + + + +http://www.iana.org/domains/idn-tables/tables/br_pt-br_1.0.html + + + http://registro.br/dominio/regras.html + + + + + + xn--exampl-gva.test + pt-BR + example1.test + withheld + 2005-04-23T11:49:00.0Z + + + + + 1.0 + en + + urn:ietf:params:xml:ns:domain-1.0 + + + urn:ietf:params:xml:ns:contact-1.0 + + + urn:ietf:params:xml:ns:host-1.0 + + + urn:ietf:params:xml:ns:rgp-1.0 + + urn:ietf:params:xml:ns:secDNS-1.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/javatests/google/registry/rde/imports/testdata/domain_fragment_pending_transfer_registration_cap.xml b/javatests/google/registry/rde/imports/testdata/domain_fragment_pending_transfer_registration_cap.xml new file mode 100644 index 000000000..071bd41fe --- /dev/null +++ b/javatests/google/registry/rde/imports/testdata/domain_fragment_pending_transfer_registration_cap.xml @@ -0,0 +1,24 @@ + + example1.example + Dexample1-TEST + + jd1234 + sh8013 + sh8013 + RegistrarX + RegistrarX + 1999-04-03T22:00:00.0Z + 2015-04-03T22:00:00.0Z + RegistrarY + 2015-01-03T22:00:00.0Z + + pending + RegistrarY + 2015-01-03T22:00:00.0Z + RegistrarX + 2015-01-08T22:00:00.0Z + 2015-04-03T22:00:00.0Z + + diff --git a/javatests/google/registry/rde/imports/testdata/domain_fragment_transferPeriod.xml b/javatests/google/registry/rde/imports/testdata/domain_fragment_transferPeriod.xml index 41661bb23..314878db3 100644 --- a/javatests/google/registry/rde/imports/testdata/domain_fragment_transferPeriod.xml +++ b/javatests/google/registry/rde/imports/testdata/domain_fragment_transferPeriod.xml @@ -19,6 +19,6 @@ 2014-10-08T16:23:21.897803Z RegistrarY 2014-10-09T08:25:43.305554Z - 2017-08-07T18:05:14.039016Z + 2015-04-03T22:00:00.0Z