1
0
mirror of https://github.com/google/nomulus synced 2026-05-24 08:41:48 +00:00

Compare commits

...

17 Commits

Author SHA1 Message Date
Weimin Yu
54f1357d83 Fix show-sql which stopped working (#596)
* Fix show-sql which stopped working

Made show-sql property configurable in JpaUnitTestRules.

Added a few comments on foreign key constraint behavior.
2020-05-21 12:20:56 -04:00
Lai Jiang
c73d154084 Do not enqueue update snapshot task if import fails (#578)
If the import from Datastore to BigQuery fails, there is no point
enqueuing a job to update the snapshot view.

Also when there's an error updating the snapshot view, log it at severe
level. The HTTP exception thrown is logged at info and triggers a retry
implicitly. I'm not sure if we want this behavior though. Do we want to
retry upon snapshot updating failures? Unless the failurs are transient,
retrying doesn't help. In our case the failure (End of time out of range
in Standard SQL) is not transient.
2020-05-21 11:40:45 -04:00
gbrodman
259d2e2cdc Run "npm audit fix" to fix a vulnerability (#592) 2020-05-20 15:12:27 -04:00
Shicong Huang
0f174d9ce0 Add all existing entities to VKeyTranslatorFactory (#595)
EntityClasses.ALL_CLASSES has all of our registered entities so
we can use it to initialize VKeyTranslatorFactory.classRegistry
to avoid adding them one by one.

Also, this PR changed to use Key.getKind() to get the kind of
the entity to solve the problem that when the entity class
is an inner class, its kind should still be the class name
instead of OuterClass$InnerClass.
2020-05-20 14:24:45 -04:00
Weimin Yu
ca2edb6a17 Close input channel in LevelDbLogReader (#594)
* Close input channel in LevelDbLogReader

Input channel should be closed when all data has been read.
2020-05-20 12:54:13 -04:00
Weimin Yu
3947ac6ef7 Read LevelDb incrementally (#593)
* Read LevelDb incrementally

Made LevelDbLogReader an iterator over a LevelDb data stream,
Reducing memory footprint which is important when used in a
Dataflow pipeline.
2020-05-20 10:26:34 -04:00
Michael Muller
579a3d0ac1 Make VKey persist to datastore as a key (#591)
* Make VKey persist to datastore as a key

Convert nsHosts entirely to VKey as a proof-of-concept.

Tested as follows:
    1) Deployed to crash, verified that nameservers were visible for several
       domains (indicating that we are able to load a set of Keys as VKeys)
    2) Updated the set of nameservers for a domain (removing some initial
       hosts) and verified that the changes went through.
    3) Deployed the old version to crash, verified that I was able to retrieve
       the newly saved VKeys as Keys.
    4) Modified the hosts for the same domain (adding back one of the hosts)
       and verified that the change took effect.
    5) Redeployed this change to crash, again updated the nameservers to add
       another host.
    6) Again restored the old version, verified that the new hosts were
       visible.

* Changes in response to review

* Convert to a single VKeyTranslatorFactory instance

* Moved vkey field rename to V25
2020-05-19 14:10:28 -04:00
Lai Jiang
5fe929b027 Log InternalServerErrorException at SEVERE (#585)
Normal HttpException logs at INFO because they usual do not indicate
anything out of the ordinary and is meant to convey to the client that
there is some expected error. However InternalServerErrorException is
something that we do care about being alerted for so we log it at SEVERE.
2020-05-18 22:55:13 -04:00
Lai Jiang
fb335b7d89 Upgrade to Gradle 6.4.1 (#590) 2020-05-18 16:47:02 -04:00
Shicong Huang
a0f4013d53 Add JUnit5 extension to run test twice against different databases (#588)
* Add JUnit5 extension to run test against different databases

* Fix typos

* Add some explanation
2020-05-18 11:06:21 -04:00
Lai Jiang
5e596bb389 Upgrade to Gradle 6.4 (#589) 2020-05-14 14:57:24 -04:00
Lai Jiang
f62fd82803 Log information about SSL connection from the client (#586) 2020-05-14 09:38:33 -04:00
sarahcaseybot
b7353ef338 Add TimedTransitionProperty Converters (#561)
* Add TimedTldStateTransitionMapConverter

* Move timedTransitions to a base class and add BillingCostTransitionConverter

* Add test of TimedTransitionPropertyConverterBase

* clean up tests

* Switch tests to JUnit 5

* Make JpaUnitTestRule an extension
2020-05-12 11:46:19 -04:00
Michael Muller
832e1ce047 Implement all DatastoreTransactionManager methods (#581)
* Implement all DatastoreTransactionManager methods

In the course of this:

- Make assertDelete() specific to JpaTransactionManager, remove the return
  value from delete()
- Converter "in transaction" assertion to IllegalStateException, which is less
  JPA specific.

* Upgraded DatastoreTransactionManagerTest to junit5
2020-05-11 17:17:57 -04:00
0xflotus
8087f5bbca (docs): fixed small errors (#572)
* Update first-steps-tutorial.md

* Update proxy-setup.md (#1)

* Update registrar-faq.md (#2)

* Update first-steps-tutorial.md
2020-05-11 10:01:47 -04:00
Ben McIlwain
7f3dbfb62f Reflect refunded billing events on deletion in expiration time (#579)
* Reflect refunded billing events on deletion in expiration time

This doesn't make any change at the time of the domain deletion itself, but it
will matter if the domain is then undeleted, because we need to know what
expiration date to restore, and if there were any renew or autorenew charges
that were refunded by the deletion because they were in a grace period, they
shouldn't be coming back during the restore.

* Add tests for new expiration date behavior

* Add handling of add/renew grace period overlap
2020-05-08 21:51:20 -04:00
Michael Muller
04f429c4d6 Convert DomainBase's contacts to VKeys (#574)
* Convert DomainBase's contacts to VKeys

Convert usage of DomainBase contacts from Key to VKey.  This is the same
change as done for nameserver hosts, as it affects all external interfaces.
As with nameserver hosts, we preserve the existing representation so as not to
afffect the datastore representation.
2020-05-07 11:19:15 -04:00
83 changed files with 2234 additions and 517 deletions

View File

@@ -880,6 +880,9 @@ task standardTest(type: FilteringTest) {
// forkEvery 1
// Sets the maximum number of test executors that may exist at the same time.
// Also, Gradle executes tests in 1 thread and some of our test infrastructures
// depend on that, e.g. DualDatabaseTestInvocationContextProvider injects
// different implementation of TransactionManager into TransactionManagerFactory.
maxParallelForks 5
systemProperty 'test.projectRoot', rootProject.projectRootDir

View File

@@ -283,7 +283,9 @@ public class DeleteContactsAndHostsAction implements Runnable {
/** Determine whether the target resource is a linked resource on the domain. */
private boolean isLinked(DomainBase domain, Key<? extends EppResource> resourceKey) {
if (resourceKey.getKind().equals(KIND_CONTACT)) {
return domain.getReferencedContacts().contains(resourceKey);
return domain
.getReferencedContacts()
.contains(VKey.createOfy(ContactResource.class, (Key<ContactResource>) resourceKey));
} else if (resourceKey.getKind().equals(KIND_HOST)) {
return domain
.getNameservers()

View File

@@ -73,8 +73,10 @@ public class BigqueryPollJobAction implements Runnable {
@Override
public void run() {
checkJobOutcome(); // Throws a NotModifiedException if the job hasn't completed.
if (payload == null || payload.length == 0) {
boolean jobOutcome =
checkJobOutcome(); // Throws a NotModifiedException if the job hasn't completed.
// If the job failed, do not enqueue the next step.
if (!jobOutcome || payload == null || payload.length == 0) {
return;
}
// If there is a payload, it's a chained task, so enqueue it.

View File

@@ -48,6 +48,7 @@ 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;
import java.util.Objects;
import java.util.Optional;
@@ -96,8 +97,9 @@ public final class ResourceFlowUtils {
queryForLinkedDomains(fki.getResourceKey(), now)
.limit(FAILFAST_CHECK_COUNT)
.keys();
VKey<R> resourceVKey = VKey.createOfy(resourceClass, fki.getResourceKey());
Predicate<DomainBase> predicate =
domain -> getPotentialReferences.apply(domain).contains(fki.getResourceKey());
domain -> getPotentialReferences.apply(domain).contains(resourceVKey);
return ofy().load().keys(keys).values().stream().anyMatch(predicate)
? new ResourceToDeleteIsReferencedException()
: null;
@@ -184,9 +186,8 @@ public final class ResourceFlowUtils {
}
// The roid should match one of the contacts.
Optional<Key<ContactResource>> foundContact =
domain
.getReferencedContacts()
.stream()
domain.getReferencedContacts().stream()
.map(VKey::getOfyKey)
.filter(key -> key.getName().equals(authRepoId))
.findFirst();
if (!foundContact.isPresent()) {

View File

@@ -77,6 +77,7 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.DomainCommand.Create;
@@ -352,7 +353,7 @@ public class DomainCreateFlow implements TransactionalFlow {
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
.setSmdId(signedMarkId)
.setDsData(secDnsCreate.isPresent() ? secDnsCreate.get().getDsData() : null)
.setRegistrant(command.getRegistrant())
.setRegistrant(VKey.createOfy(ContactResource.class, command.getRegistrant()))
.setAuthInfo(command.getAuthInfo())
.setFullyQualifiedDomainName(targetId)
.setNameservers(

View File

@@ -212,6 +212,28 @@ public final class DomainDeleteFlow implements TransactionalFlow {
builder.setDeletePollMessage(Key.create(deletePollMessage));
}
// Cancel any grace periods that were still active, and set the expiration time accordingly.
DateTime newExpirationTime = existingDomain.getRegistrationExpirationTime();
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
// No cancellation is written if the grace period was not for a billable event.
if (gracePeriod.hasBillingEvent()) {
entitiesToSave.add(
BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
if (gracePeriod.getOneTimeBillingEvent() != null) {
// Take the amount of amount of registration time being refunded off the expiration time.
// This can be either add grace periods or renew grace periods.
BillingEvent.OneTime oneTime =
ofy().load().key(gracePeriod.getOneTimeBillingEvent()).now();
newExpirationTime = newExpirationTime.minusYears(oneTime.getPeriodYears());
} else if (gracePeriod.getRecurringBillingEvent() != null) {
// Take 1 year off the registration if in the autorenew grace period (no need to load the
// recurring billing event; all autorenews are for 1 year).
newExpirationTime = newExpirationTime.minusYears(1);
}
}
}
builder.setRegistrationExpirationTime(newExpirationTime);
DomainBase newDomain = builder.build();
updateForeignKeyIndexDeletionTime(newDomain);
handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry);
@@ -221,14 +243,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
// event and poll message will already have been deleted in
// ResourceDeleteFlow since it's listed in serverApproveEntities.
dnsQueue.addDomainRefreshTask(existingDomain.getFullyQualifiedDomainName());
// Cancel any grace periods that were still active.
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
// No cancellation is written if the grace period was not for a billable event.
if (gracePeriod.hasBillingEvent()) {
entitiesToSave.add(
BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
}
}
entitiesToSave.add(newDomain, historyEntry);
EntityChanges entityChanges = flowCustomLogic.beforeSave(
BeforeSaveParameters.newBuilder()

View File

@@ -35,6 +35,7 @@ import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRIS
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_SPECIFIC_USE;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
@@ -120,6 +121,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tmch.ClaimsListShard;
import google.registry.persistence.VKey;
import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.util.Idn;
import java.math.BigDecimal;
@@ -309,7 +311,10 @@ public class DomainFlowUtils {
Set<Key<HostResource>> nameservers)
throws EppException {
ImmutableList.Builder<Key<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
contacts.stream()
.map(DesignatedContact::getContactKey)
.map(VKey::getOfyKey)
.forEach(keysToLoad::add);
Optional.ofNullable(registrant).ifPresent(keysToLoad::add);
keysToLoad.addAll(nameservers);
verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values());
@@ -355,7 +360,7 @@ public class DomainFlowUtils {
contacts.stream()
.collect(
toImmutableSetMultimap(
DesignatedContact::getType, DesignatedContact::getContactKey));
DesignatedContact::getType, contact -> contact.getContactKey().getOfyKey()));
// If any contact type has multiple contacts:
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
@@ -978,7 +983,7 @@ public class DomainFlowUtils {
for (DesignatedContact contact : contacts) {
builder.add(
ForeignKeyedDesignatedContact.create(
contact.getType(), ofy().load().key(contact.getContactKey()).now().getContactId()));
contact.getType(), tm().load(contact.getContactKey()).getContactId()));
}
return builder.build();
}

View File

@@ -21,7 +21,6 @@ import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfP
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
@@ -102,16 +101,16 @@ public final class DomainInfoFlow implements Flow {
flowCustomLogic.afterValidation(
AfterValidationParameters.newBuilder().setDomain(domain).build());
// Prefetch all referenced resources. Calling values() blocks until loading is done.
// We do nameservers separately since they've been converted to VKey.
tm().load(domain.getNameservers());
ofy().load().values(domain.getReferencedContacts()).values();
tm().load(domain.getReferencedContacts());
// Registrars can only see a few fields on unauthorized domains.
// This is a policy decision that is left up to us by the rfcs.
DomainInfoData.Builder infoBuilder = DomainInfoData.newBuilder()
.setFullyQualifiedDomainName(domain.getFullyQualifiedDomainName())
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorClientId())
.setRegistrant(ofy().load().key(domain.getRegistrant()).now().getContactId());
DomainInfoData.Builder infoBuilder =
DomainInfoData.newBuilder()
.setFullyQualifiedDomainName(domain.getFullyQualifiedDomainName())
.setRepoId(domain.getRepoId())
.setCurrentSponsorClientId(domain.getCurrentSponsorClientId())
.setRegistrant(tm().load(domain.getRegistrant()).getContactId());
// If authInfo is non-null, then the caller is authorized to see the full information since we
// will have already verified the authInfo is valid.
if (clientId.equals(domain.getCurrentSponsorClientId()) || authInfo.isPresent()) {

View File

@@ -60,6 +60,7 @@ import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Update;
import google.registry.model.domain.DomainCommand.Update.AddRemove;
@@ -256,7 +257,12 @@ public final class DomainUpdateFlow implements TransactionalFlow {
.collect(toImmutableSet()))
.addContacts(add.getContacts())
.removeContacts(remove.getContacts())
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
.setRegistrant(
firstNonNull(
change.getRegistrant() != null
? VKey.createOfy(ContactResource.class, change.getRegistrant())
: null,
domain.getRegistrant()))
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
return domainBuilder.build();
}
@@ -269,7 +275,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
private void validateNewState(DomainBase newDomain) throws EppException {
validateNoDuplicateContacts(newDomain.getContacts());
validateRequiredContactsPresent(newDomain.getRegistrant(), newDomain.getContacts());
validateRequiredContactsPresent(newDomain.getRegistrant().getOfyKey(), newDomain.getContacts());
validateDsData(newDomain.getDsData());
validateNameserversCountForTld(
newDomain.getTld(),

View File

@@ -14,7 +14,6 @@
package google.registry.flows.host;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.failfastForAsyncDelete;
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
@@ -82,17 +81,6 @@ public final class HostDeleteFlow implements TransactionalFlow {
@Inject EppResponse.Builder responseBuilder;
@Inject HostDeleteFlow() {}
/**
* Hack to convert DomainBase's nameserver VKey's to Ofy Key's.
*
* <p>We currently need this because {@code failfastForAsyncDelete()} checks to see if a name is
* in the ofy keys and is used for both nameservers and contacts. When we convert contacts to
* VKey's, we can remove this and do the conversion in {@code failfastForAsyncDelete()}.
*/
private static ImmutableSet<Key<HostResource>> getNameserverOfyKeys(DomainBase domain) {
return domain.getNameservers().stream().map(key -> key.getOfyKey()).collect(toImmutableSet());
}
@Override
public final EppResponse run() throws EppException {
extensionManager.register(MetadataExtension.class);
@@ -100,7 +88,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
validateClientIsLoggedIn(clientId);
DateTime now = tm().getTransactionTime();
validateHostName(targetId);
failfastForAsyncDelete(targetId, now, HostResource.class, HostDeleteFlow::getNameserverOfyKeys);
failfastForAsyncDelete(targetId, now, HostResource.class, DomainBase::getNameservers);
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
if (!isSuperuser) {

View File

@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index;
@@ -30,6 +31,8 @@ import google.registry.model.annotations.ExternalMessagingName;
import google.registry.model.annotations.ReportedOn;
import google.registry.model.contact.PostalInfo.Type;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import java.util.Objects;
import java.util.Optional;
@@ -60,6 +63,7 @@ import org.joda.time.DateTime;
@javax.persistence.Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithStringVKey
public class ContactResource extends EppResource
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
@@ -193,6 +197,10 @@ public class ContactResource extends EppResource
})
Disclose disclose;
public VKey<ContactResource> createVKey() {
return VKey.createOfy(ContactResource.class, Key.create(this));
}
public String getContactId() {
return contactId;
}

View File

@@ -18,9 +18,11 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.Index;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.ContactResource;
import google.registry.persistence.VKey;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlEnumValue;
@@ -36,6 +38,9 @@ import javax.xml.bind.annotation.XmlEnumValue;
* situation with hosts where client-side renames would make that data stale. However, we sometimes
* rename contacts internally ourselves, and it's easier to use the same model for both cases.
*
* <p>This entity type is not persisted in Cloud SQL. The different roles are represented as
* separate fields in the Domain table.
*
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
* - Contact and Client Identifiers</a>
*/
@@ -58,22 +63,28 @@ public class DesignatedContact extends ImmutableObject {
REGISTRANT
}
public static DesignatedContact create(Type type, Key<ContactResource> contact) {
public static DesignatedContact create(Type type, VKey<ContactResource> contact) {
DesignatedContact instance = new DesignatedContact();
instance.type = type;
instance.contact = checkArgumentNotNull(contact, "Must specify contact key");
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
instance.contact = contact.maybeGetOfyKey().orElse(null);
return instance;
}
Type type;
@Index Key<ContactResource> contact;
@Ignore VKey<ContactResource> contactVKey;
public Type getType() {
return type;
}
public Key<ContactResource> getContactKey() {
return contact;
public VKey<ContactResource> getContactKey() {
return contactVKey;
}
public DesignatedContact reconstitute() {
return create(type, VKey.createOfy(ContactResource.class, contact));
}
}

View File

@@ -80,6 +80,7 @@ import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.JoinTable;
import javax.persistence.PostLoad;
import javax.persistence.Transient;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -135,18 +136,11 @@ public class DomainBase extends EppResource
@Index
String tld;
/**
* References to hosts that are the nameservers for the domain.
*
* <p>This is a legacy field: we have to preserve it because it is still persisted and indexed in
* the datastore, but all external references go through nsHostVKeys.
*/
@Index @ElementCollection @Transient Set<Key<HostResource>> nsHosts;
@Ignore
/** References to hosts that are the nameservers for the domain. */
@Index
@ElementCollection
@JoinTable(name = "DomainHost")
Set<VKey<HostResource>> nsHostVKeys;
Set<VKey<HostResource>> nsHosts;
/**
* The union of the contacts visible via {@link #getContacts} and {@link #getRegistrant}.
@@ -155,6 +149,17 @@ public class DomainBase extends EppResource
*/
@Transient Set<DesignatedContact> allContacts;
/**
* Contacts as they are stored in cloud SQL.
*
* <p>This information is duplicated in allContacts, and must be kept in sync with it.
*/
@Ignore VKey<ContactResource> adminContact;
@Ignore VKey<ContactResource> billingContact;
@Ignore VKey<ContactResource> techContact;
@Ignore VKey<ContactResource> registrantContact;
/** Authorization info (aka transfer secret) of the domain. */
@Embedded
@AttributeOverrides({
@@ -257,10 +262,33 @@ public class DomainBase extends EppResource
@OnLoad
void load() {
nsHostVKeys =
nullToEmptyImmutableCopy(nsHosts).stream()
.map(hostKey -> VKey.createOfy(HostResource.class, hostKey))
.collect(toImmutableSet());
// Reconstitute all of the contacts so that they have VKeys.
allContacts =
allContacts.stream().map(contact -> contact.reconstitute()).collect(toImmutableSet());
setContactFields(allContacts, true);
}
@PostLoad
void postLoad() {
// Reconstitute the contact list.
ImmutableSet.Builder<DesignatedContact> contactsBuilder =
new ImmutableSet.Builder<DesignatedContact>();
if (registrantContact != null) {
contactsBuilder.add(
DesignatedContact.create(DesignatedContact.Type.REGISTRANT, registrantContact));
}
if (billingContact != null) {
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.BILLING, billingContact));
}
if (techContact != null) {
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.TECH, techContact));
}
if (adminContact != null) {
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.ADMIN, adminContact));
}
allContacts = contactsBuilder.build();
}
public ImmutableSet<String> getSubordinateHosts() {
@@ -323,9 +351,7 @@ public class DomainBase extends EppResource
}
public ImmutableSet<VKey<HostResource>> getNameservers() {
// Since nsHostVKeys gets initialized both from setNameservers() and the OnLoad method, this
// should always be valid.
return nullToEmptyImmutableCopy(nsHostVKeys);
return nullToEmptyImmutableCopy(nsHosts);
}
public final String getCurrentSponsorClientId() {
@@ -515,13 +541,20 @@ public class DomainBase extends EppResource
}
/** A key to the registrant who registered this domain. */
public Key<ContactResource> getRegistrant() {
return nullToEmpty(allContacts)
.stream()
.filter(IS_REGISTRANT)
.findFirst()
.get()
.getContactKey();
public VKey<ContactResource> getRegistrant() {
return registrantContact;
}
public VKey<ContactResource> getAdminContact() {
return adminContact;
}
public VKey<ContactResource> getBillingContact() {
return billingContact;
}
public VKey<ContactResource> getTechContact() {
return techContact;
}
/** Associated contacts for the domain (other than registrant). */
@@ -537,7 +570,7 @@ public class DomainBase extends EppResource
}
/** Returns all referenced contacts from this domain or application. */
public ImmutableSet<Key<ContactResource>> getReferencedContacts() {
public ImmutableSet<VKey<ContactResource>> getReferencedContacts() {
return nullToEmptyImmutableCopy(allContacts)
.stream()
.map(DesignatedContact::getContactKey)
@@ -549,6 +582,37 @@ public class DomainBase extends EppResource
return tld;
}
/**
* Sets the individual contact fields from {@code contacts}.
*
* <p>The registrant field is only set if {@code includeRegistrant} is true, as this field needs
* to be set in some circumstances but not in others.
*/
private void setContactFields(Set<DesignatedContact> contacts, boolean includeRegistrant) {
// Set the individual contact fields.
for (DesignatedContact contact : contacts) {
switch (contact.getType()) {
case BILLING:
billingContact = contact.getContactKey();
break;
case TECH:
techContact = contact.getContactKey();
break;
case ADMIN:
adminContact = contact.getContactKey();
break;
case REGISTRANT:
if (includeRegistrant) {
registrantContact = contact.getContactKey();
}
break;
default:
throw new IllegalArgumentException("Unknown contact resource type: " + contact.getType());
}
}
}
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
private static final Predicate<DesignatedContact> IS_REGISTRANT =
(DesignatedContact contact) -> DesignatedContact.Type.REGISTRANT.equals(contact.type);
@@ -567,14 +631,6 @@ public class DomainBase extends EppResource
Builder(DomainBase instance) {
super(instance);
// Convert nsHosts to nsHostVKeys.
if (instance.nsHosts != null) {
instance.nsHostVKeys =
instance.nsHosts.stream()
.map(key -> VKey.createOfy(HostResource.class, key))
.collect(toImmutableSet());
}
}
@Override
@@ -593,7 +649,11 @@ public class DomainBase extends EppResource
checkArgumentNotNull(
emptyToNull(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
checkArgument(instance.allContacts.stream().anyMatch(IS_REGISTRANT), "Missing registrant");
if (instance.getRegistrant() == null
&& instance.allContacts.stream().anyMatch(IS_REGISTRANT)) {
throw new IllegalArgumentException("registrant is null but is in allContacts");
}
checkArgumentNotNull(instance.getRegistrant(), "Missing registrant");
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
return super.build();
}
@@ -611,11 +671,14 @@ public class DomainBase extends EppResource
return thisCastToDerived();
}
public Builder setRegistrant(Key<ContactResource> registrant) {
public Builder setRegistrant(VKey<ContactResource> registrant) {
// Replace the registrant contact inside allContacts.
getInstance().allContacts = union(
getInstance().getContacts(),
DesignatedContact.create(Type.REGISTRANT, checkArgumentNotNull(registrant)));
// Set the registrant field specifically.
getInstance().registrantContact = registrant;
return thisCastToDerived();
}
@@ -625,27 +688,12 @@ public class DomainBase extends EppResource
}
public Builder setNameservers(VKey<HostResource> nameserver) {
Optional<Key<HostResource>> nsKey = nameserver.maybeGetOfyKey();
if (nsKey.isPresent()) {
getInstance().nsHosts = ImmutableSet.of(nsKey.get());
} else {
getInstance().nsHosts = null;
}
getInstance().nsHostVKeys = ImmutableSet.of(nameserver);
getInstance().nsHosts = ImmutableSet.of(nameserver);
return thisCastToDerived();
}
public Builder setNameservers(ImmutableSet<VKey<HostResource>> nameservers) {
// If we have all of the ofy keys, we can set nsHosts. Otherwise, make it null.
if (nameservers != null
&& nameservers.stream().allMatch(key -> key.maybeGetOfyKey().isPresent())) {
getInstance().nsHosts =
nameservers.stream().map(key -> key.getOfyKey()).collect(toImmutableSet());
} else {
getInstance().nsHosts = null;
}
getInstance().nsHostVKeys = forceEmptyToNull(nameservers);
getInstance().nsHosts = forceEmptyToNull(nameservers);
return thisCastToDerived();
}
@@ -673,12 +721,16 @@ public class DomainBase extends EppResource
public Builder setContacts(ImmutableSet<DesignatedContact> contacts) {
checkArgument(contacts.stream().noneMatch(IS_REGISTRANT), "Registrant cannot be a contact");
// Replace the non-registrant contacts inside allContacts.
getInstance().allContacts =
Streams.concat(
nullToEmpty(getInstance().allContacts).stream().filter(IS_REGISTRANT),
contacts.stream())
.collect(toImmutableSet());
// Set the individual fields.
getInstance().setContactFields(contacts, false);
return thisCastToDerived();
}

View File

@@ -41,6 +41,7 @@ import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.HostResource;
import google.registry.model.index.ForeignKeyIndex;
import google.registry.persistence.VKey;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
@@ -188,7 +189,7 @@ public class DomainCommand {
now);
for (DesignatedContact contact : contacts) {
if (DesignatedContact.Type.REGISTRANT.equals(contact.getType())) {
clone.registrant = contact.getContactKey();
clone.registrant = contact.getContactKey().getOfyKey();
clone.contacts = forceEmptyToNull(difference(contacts, contact));
break;
}
@@ -439,8 +440,10 @@ public class DomainCommand {
loadByForeignKeysCached(foreignKeys.build(), ContactResource.class, now);
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
for (ForeignKeyedDesignatedContact contact : contacts) {
linkedContacts.add(DesignatedContact.create(
contact.type, loadedContacts.get(contact.contactId)));
linkedContacts.add(
DesignatedContact.create(
contact.type,
VKey.createOfy(ContactResource.class, loadedContacts.get(contact.contactId))));
}
return linkedContacts.build();
}

View File

@@ -15,6 +15,7 @@
package google.registry.model.ofy;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
@@ -94,42 +95,45 @@ public class DatastoreTransactionManager implements TransactionManager {
@Override
public void saveNew(Object entity) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
checkArgumentNotNull(entity, "entity must be specified");
getOfy().save().entity(entity);
}
@Override
public void saveAllNew(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
getOfy().save().entities(entities);
}
@Override
public void saveNewOrUpdate(Object entity) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
checkArgumentNotNull(entity, "entity must be specified");
getOfy().save().entity(entity);
}
@Override
public void saveNewOrUpdateAll(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
getOfy().save().entities(entities);
}
@Override
public void update(Object entity) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
checkArgumentNotNull(entity, "entity must be specified");
getOfy().save().entity(entity);
}
@Override
public void updateAll(ImmutableCollection<?> entities) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
getOfy().save().entities(entities);
}
@Override
public boolean checkExists(Object entity) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
return getOfy().load().key(Key.create(entity)).now() != null;
}
@Override
public <T> boolean checkExists(VKey<T> key) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
return getOfy().load().key(key.getOfyKey()).now() != null;
}
// TODO: add tests for these methods. They currently have some degree of test coverage because
@@ -138,7 +142,7 @@ public class DatastoreTransactionManager implements TransactionManager {
// interface tests that are applied to both the datastore and SQL implementations.
@Override
public <T> Optional<T> maybeLoad(VKey<T> key) {
return Optional.of(getOfy().load().key(key.getOfyKey()).now());
return Optional.ofNullable(getOfy().load().key(key.getOfyKey()).now());
}
@Override
@@ -161,16 +165,12 @@ public class DatastoreTransactionManager implements TransactionManager {
@Override
public <T> ImmutableList<T> loadAll(Class<T> clazz) {
// We can do a ofy().load().type(clazz), but this doesn't work in a transaction.
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
}
@Override
public <T> int delete(VKey<T> key) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
}
@Override
public <T> void assertDelete(VKey<T> key) {
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
public <T> void delete(VKey<T> key) {
getOfy().delete().key(key.getOfyKey()).now();
}
}

View File

@@ -36,7 +36,6 @@ import com.googlecode.objectify.impl.translate.opt.joda.MoneyStringTranslatorFac
import google.registry.config.RegistryEnvironment;
import google.registry.model.EntityClasses;
import google.registry.model.ImmutableObject;
import google.registry.model.host.HostResource;
import google.registry.model.translators.BloomFilterOfStringTranslatorFactory;
import google.registry.model.translators.CidrAddressBlockTranslatorFactory;
import google.registry.model.translators.CommitLogRevisionsTranslatorFactory;
@@ -130,7 +129,7 @@ public class ObjectifyService {
new InetAddressTranslatorFactory(),
new MoneyStringTranslatorFactory(),
new ReadableInstantUtcTranslatorFactory(),
new VKeyTranslatorFactory<HostResource>(HostResource.class),
new VKeyTranslatorFactory(),
new UpdateAutoTimestampTranslatorFactory())) {
factory().getTranslators().add(translatorFactory);
}

View File

@@ -14,13 +14,14 @@
package google.registry.model.translators;
import static java.nio.charset.StandardCharsets.UTF_8;
import static com.google.common.base.Functions.identity;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.model.EntityClasses.ALL_CLASSES;
import com.googlecode.objectify.Key;
import com.google.appengine.api.datastore.Key;
import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.persistence.VKey;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* Translator factory for VKey.
@@ -28,57 +29,39 @@ import java.net.URLEncoder;
* <p>These get translated to a string containing the URL safe encoding of the objectify key
* followed by a (url-unsafe) ampersand delimiter and the SQL key.
*/
public class VKeyTranslatorFactory<T> extends AbstractSimpleTranslatorFactory<VKey, String> {
private final Class<T> refClass;
public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey, Key> {
public VKeyTranslatorFactory(Class<T> refClass) {
// Class registry allowing us to restore the original class object from the unqualified class
// name, which is all the datastore key gives us.
// Note that entities annotated with @EntitySubclass are removed because they share the same
// kind of the key with their parent class.
private static final ImmutableMap<String, Class> CLASS_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(toImmutableMap(com.googlecode.objectify.Key::getKind, identity()));
;
public VKeyTranslatorFactory() {
super(VKey.class);
this.refClass = refClass;
}
@Override
public SimpleTranslator<VKey, String> createTranslator() {
return new SimpleTranslator<VKey, String>() {
public SimpleTranslator<VKey, Key> createTranslator() {
return new SimpleTranslator<VKey, Key>() {
@Override
public VKey loadValue(String datastoreValue) {
int pos = datastoreValue.indexOf('&');
Key ofyKey = null;
String sqlKey = null;
if (pos > 0) {
// We have an objectify key.
ofyKey = Key.create(datastoreValue.substring(0, pos));
}
if (pos < datastoreValue.length() - 1) {
// We have an SQL key.
sqlKey = decode(datastoreValue.substring(pos + 1));
}
return VKey.create(refClass, sqlKey, ofyKey);
public VKey loadValue(Key datastoreValue) {
// TODO(mmuller): we need to call a method on refClass to also reconstitute the SQL key.
return datastoreValue == null
? null
: VKey.createOfy(
CLASS_REGISTRY.get(datastoreValue.getKind()),
com.googlecode.objectify.Key.create(datastoreValue));
}
@Override
public String saveValue(VKey key) {
return ((key.getOfyKey() == null) ? "" : key.getOfyKey().getString())
+ "&"
+ ((key.getSqlKey() == null) ? "" : encode(key.getSqlKey().toString()));
public Key saveValue(VKey key) {
return key == null ? null : key.getOfyKey().getRaw();
}
};
}
private static String encode(String val) {
try {
return URLEncoder.encode(val, UTF_8.toString());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private static String decode(String encoded) {
try {
return URLDecoder.decode(encoded, UTF_8.toString());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2020 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.persistence.converter;
import avro.shaded.com.google.common.collect.Maps;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.BillingCostTransition;
import java.util.Map;
import javax.persistence.Converter;
import org.joda.money.Money;
import org.joda.time.DateTime;
/**
* JPA converter for storing/retrieving {@link TimedTransitionProperty <Money, BillingCostTransition
* >} objects.
*/
@Converter(autoApply = true)
public class BillingCostTransitionConverter
extends TimedTransitionPropertyConverterBase<Money, BillingCostTransition> {
@Override
Map.Entry<String, String> convertToDatabaseMapEntry(
Map.Entry<DateTime, BillingCostTransition> entry) {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().toString());
}
@Override
Map.Entry<DateTime, Money> convertToEntityMapEntry(Map.Entry<String, String> entry) {
return Maps.immutableEntry(DateTime.parse(entry.getKey()), Money.parse(entry.getValue()));
}
@Override
Class<BillingCostTransition> getTimedTransitionSubclass() {
return BillingCostTransition.class;
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.persistence.converter.StringMapDescriptor.StringMap;
import java.util.Map;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
import org.joda.time.DateTime;
/**
* Base JPA converter for {@link TimedTransitionProperty} objects that are stored in a column with
* data type of hstore in the database.
*/
public abstract class TimedTransitionPropertyConverterBase<K, V extends TimedTransition<K>>
implements AttributeConverter<TimedTransitionProperty<K, V>, StringMap> {
abstract Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<DateTime, V> entry);
abstract Map.Entry<DateTime, K> convertToEntityMapEntry(Map.Entry<String, String> entry);
abstract Class<V> getTimedTransitionSubclass();
@Override
public StringMap convertToDatabaseColumn(@Nullable TimedTransitionProperty<K, V> attribute) {
return attribute == null
? null
: StringMap.create(
attribute.entrySet().stream()
.map(this::convertToDatabaseMapEntry)
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
}
@Override
public TimedTransitionProperty<K, V> convertToEntityAttribute(@Nullable StringMap dbData) {
if (dbData == null) {
return null;
}
Map<DateTime, K> map =
dbData.getMap().entrySet().stream()
.map(this::convertToEntityMapEntry)
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
return TimedTransitionProperty.fromValueMap(
ImmutableSortedMap.copyOf(map), getTimedTransitionSubclass());
}
}

View File

@@ -0,0 +1,48 @@
// Copyright 2020 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.persistence.converter;
import com.google.common.collect.Maps;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldStateTransition;
import java.util.Map;
import javax.persistence.Converter;
import org.joda.time.DateTime;
/**
* JPA converter for storing/retrieving {@link TimedTransitionProperty<TldState,
* TldStateTransition>} objects.
*/
@Converter(autoApply = true)
public class TldStateTransitionConverter
extends TimedTransitionPropertyConverterBase<TldState, TldStateTransition> {
@Override
Map.Entry<String, String> convertToDatabaseMapEntry(
Map.Entry<DateTime, TldStateTransition> entry) {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name());
}
@Override
Map.Entry<DateTime, TldState> convertToEntityMapEntry(Map.Entry<String, String> entry) {
return Maps.immutableEntry(DateTime.parse(entry.getKey()), TldState.valueOf(entry.getValue()));
}
@Override
Class<TldStateTransition> getTimedTransitionSubclass() {
return TldStateTransition.class;
}
}

View File

@@ -14,6 +14,7 @@
package google.registry.persistence.transaction;
import google.registry.persistence.VKey;
import javax.persistence.EntityManager;
/** Sub-interface of {@link TransactionManager} which defines JPA related methods. */
@@ -21,4 +22,7 @@ public interface JpaTransactionManager extends TransactionManager {
/** Returns the {@link EntityManager} for the current request. */
EntityManager getEntityManager();
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
public abstract <T> void assertDelete(VKey<T> key);
}

View File

@@ -77,7 +77,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void assertInTransaction() {
if (!inTransaction()) {
throw new PersistenceException("Not in a transaction");
throw new IllegalStateException("Not in a transaction");
}
}
@@ -278,8 +278,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
.getResultList());
}
@Override
public <T> int delete(VKey<T> key) {
private <T> int internalDelete(VKey<T> key) {
checkArgumentNotNull(key, "key must be specified");
assertInTransaction();
EntityType<?> entityType = getEntityType(key.getKind());
@@ -291,9 +290,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
return query.executeUpdate();
}
@Override
public <T> void delete(VKey<T> key) {
internalDelete(key);
}
@Override
public <T> void assertDelete(VKey<T> key) {
if (delete(key) != 1) {
if (internalDelete(key) != 1) {
throw new IllegalArgumentException(
String.format("Error deleting the entity of the key: %s", key.getSqlKey()));
}

View File

@@ -123,9 +123,6 @@ public interface TransactionManager {
/** Loads all entities of the given type, returns empty if there is no such entity. */
<T> ImmutableList<T> loadAll(Class<T> clazz);
/** Deletes the entity by its id, returns the number of deleted entity. */
<T> int delete(VKey<T> key);
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
<T> void assertDelete(VKey<T> key);
/** Deletes the entity by its id. */
<T> void delete(VKey<T> key);
}

View File

@@ -16,6 +16,7 @@ package google.registry.persistence.transaction;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.persistence.DaggerPersistenceComponent;
@@ -26,7 +27,9 @@ import java.util.function.Supplier;
// TODO: Rename this to PersistenceFactory and move to persistence package.
public class TransactionManagerFactory {
private static final TransactionManager TM = createTransactionManager();
private static final DatastoreTransactionManager ofyTm = createTransactionManager();
@NonFinalForTesting private static TransactionManager tm = ofyTm;
/** Supplier for jpaTm so that it is initialized only once, upon first usage. */
@NonFinalForTesting
@@ -45,10 +48,7 @@ public class TransactionManagerFactory {
}
}
private static TransactionManager createTransactionManager() {
// TODO: Determine how to provision TransactionManager after the dual-write. During the
// dual-write transitional phase, we need the TransactionManager for both Datastore and Cloud
// SQL, and this method returns the one for Datastore.
private static DatastoreTransactionManager createTransactionManager() {
return new DatastoreTransactionManager(null);
}
@@ -67,7 +67,7 @@ public class TransactionManagerFactory {
/** Returns {@link TransactionManager} instance. */
public static TransactionManager tm() {
return TM;
return tm;
}
/** Returns {@link JpaTransactionManager} instance. */
@@ -75,8 +75,20 @@ public class TransactionManagerFactory {
return jpaTm.get();
}
/** Returns {@link DatastoreTransactionManager} instance. */
@VisibleForTesting
public static DatastoreTransactionManager ofyTm() {
return ofyTm;
}
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
public static void setJpaTm(JpaTransactionManager newJpaTm) {
jpaTm = Suppliers.ofInstance(newJpaTm);
}
/** Sets the return of {@link #tm()} to the given instance of {@link TransactionManager}. */
@VisibleForTesting
public static void setTm(TransactionManager newTm) {
tm = newTm;
}
}

View File

@@ -52,6 +52,7 @@ import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.rdap.RdapDataStructures.Event;
import google.registry.rdap.RdapDataStructures.EventAction;
import google.registry.rdap.RdapDataStructures.Link;
@@ -346,7 +347,12 @@ public class RdapJsonFormatter {
ImmutableSet.copyOf(tm().load(domainBase.getNameservers()));
// Load the registrant and other contacts and add them to the data.
Map<Key<ContactResource>, ContactResource> loadedContacts =
ofy().load().keys(domainBase.getReferencedContacts());
ofy()
.load()
.keys(
domainBase.getReferencedContacts().stream()
.map(VKey::getOfyKey)
.collect(toImmutableSet()));
// RDAP Response Profile 2.7.3, A domain MUST have the REGISTRANT, ADMIN, TECH roles and MAY
// have others. We also add the BILLING.
//
@@ -361,7 +367,7 @@ public class RdapJsonFormatter {
.sorted(DESIGNATED_CONTACT_ORDERING)
.collect(
toImmutableSetMultimap(
DesignatedContact::getContactKey, DesignatedContact::getType));
contact -> contact.getContactKey().getOfyKey(), DesignatedContact::getType));
for (Key<ContactResource> contactKey : contactsToRoles.keySet()) {
Set<RdapEntity.Role> roles =

View File

@@ -15,13 +15,12 @@
package google.registry.rde;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.Ascii;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.domain.DomainBase;
@@ -30,6 +29,7 @@ import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.rde.RdeMode;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.util.Idn;
import google.registry.xjc.domain.XjcDomainContactAttrType;
import google.registry.xjc.domain.XjcDomainContactType;
@@ -167,11 +167,11 @@ final class DomainBaseToXjcConverter {
// o An OPTIONAL <registrant> element that contain the identifier for
// the human or organizational social information object associated
// as the holder of the domain name object.
Key<ContactResource> registrant = model.getRegistrant();
VKey<ContactResource> registrant = model.getRegistrant();
if (registrant == null) {
logger.atWarning().log("Domain %s has no registrant contact.", domainName);
} else {
ContactResource registrantContact = ofy().load().key(registrant).now();
ContactResource registrantContact = tm().load(registrant);
checkState(
registrantContact != null,
"Registrant contact %s on domain %s does not exist",
@@ -304,7 +304,7 @@ final class DomainBaseToXjcConverter {
"Contact key for type %s is null on domain %s",
model.getType(),
domainName);
ContactResource contact = ofy().load().key(model.getContactKey()).now();
ContactResource contact = tm().load(model.getContactKey());
checkState(
contact != null,
"Contact %s on domain %s does not exist",

View File

@@ -18,6 +18,7 @@ import static com.google.common.html.HtmlEscapers.htmlEscaper;
import com.google.common.flogger.FluentLogger;
import java.io.IOException;
import java.util.logging.Level;
import javax.servlet.http.HttpServletResponse;
/** Base for exceptions that cause an HTTP error response. */
@@ -28,11 +29,18 @@ public abstract class HttpException extends RuntimeException {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Level logLevel;
private final int responseCode;
protected HttpException(int responseCode, String message, Throwable cause) {
protected HttpException(int responseCode, String message, Throwable cause, Level logLevel) {
super(message, cause);
this.responseCode = responseCode;
this.logLevel = logLevel;
}
protected HttpException(int responseCode, String message, Throwable cause) {
this(responseCode, message, cause, Level.INFO);
}
public final int getResponseCode() {
@@ -57,7 +65,7 @@ public abstract class HttpException extends RuntimeException {
*/
public final void send(HttpServletResponse rsp) throws IOException {
rsp.sendError(getResponseCode(), htmlEscaper().escape(getMessage()));
logger.atInfo().withCause(getCause()).log("%s", this);
logger.at(logLevel).withCause(getCause()).log("%s", this);
}
/**
@@ -196,7 +204,7 @@ public abstract class HttpException extends RuntimeException {
/** Exception that causes a 500 response. */
public static final class InternalServerErrorException extends HttpException {
public InternalServerErrorException(String message) {
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null);
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null, Level.SEVERE);
}
public InternalServerErrorException(String message, Throwable cause) {

View File

@@ -40,12 +40,10 @@ class CompareDbBackups {
}
ImmutableSet<ComparableEntity> entities1 =
new RecordAccumulator()
.readDirectory(new File(args[0]), DATA_FILE_MATCHER)
RecordAccumulator.readDirectory(new File(args[0]), DATA_FILE_MATCHER)
.getComparableEntitySet();
ImmutableSet<ComparableEntity> entities2 =
new RecordAccumulator()
.readDirectory(new File(args[1]), DATA_FILE_MATCHER)
RecordAccumulator.readDirectory(new File(args[1]), DATA_FILE_MATCHER)
.getComparableEntitySet();
// Calculate the entities added and removed.

View File

@@ -14,17 +14,27 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Optional;
/**
* Reads records from a set of LevelDB files and builds a gigantic ImmutableList from them.
* Iterator that incrementally parses binary data in LevelDb format into records.
*
* <p>The input source is automatically closed when all data have been read.
*
* <p>See <a
* href="https://github.com/google/leveldb/blob/master/doc/log_format.md">log_format.md</a> for the
@@ -32,19 +42,74 @@ import java.nio.file.Path;
*
* <p>There are several other implementations of this, none of which appeared suitable for our use
* case: <a href="https://github.com/google/leveldb">The original C++ implementation</a>. <a
* href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/files/RecordWriteChannel">
* com.google.appengine.api.files.RecordWriteChannel</a> - Exactly what we need but deprecated. The
* href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/files/RecordReadChannel">
* com.google.appengine.api.files.RecordReadChannel</a> - Exactly what we need but deprecated. The
* referenced replacement: <a
* href="https://github.com/GoogleCloudPlatform/appengine-gcs-client.git">The App Engine GCS
* Client</a> - Does not appear to have any support for working with LevelDB.
*/
public final class LevelDbLogReader {
public final class LevelDbLogReader implements Iterator<byte[]> {
@VisibleForTesting static final int BLOCK_SIZE = 32 * 1024;
@VisibleForTesting static final int HEADER_SIZE = 7;
private final ByteArrayOutputStream recordContents = new ByteArrayOutputStream();
private final ImmutableList.Builder<byte[]> recordListBuilder = new ImmutableList.Builder<>();
private final LinkedList<byte[]> recordList = Lists.newLinkedList();
private final ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK_SIZE);
private final ReadableByteChannel channel;
LevelDbLogReader(ReadableByteChannel channel) {
this.channel = channel;
}
@Override
public boolean hasNext() {
while (recordList.isEmpty()) {
try {
Optional<byte[]> block = readFromChannel();
if (!block.isPresent()) {
return false;
}
if (block.get().length != BLOCK_SIZE) {
throw new IllegalStateException("Data size is not multiple of " + BLOCK_SIZE);
}
processBlock(block.get());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return true;
}
@Override
public byte[] next() {
checkState(hasNext(), "The next() method called on empty iterator.");
return recordList.removeFirst();
}
/**
* Returns the next {@link #BLOCK_SIZE} bytes from the input channel, or {@link
* Optional#empty()} if there is no more data.
*/
// TODO(weiminyu): use ByteBuffer directly.
private Optional<byte[]> readFromChannel() throws IOException {
while (channel.isOpen()) {
int bytesRead = channel.read(byteBuffer);
if (!byteBuffer.hasRemaining() || bytesRead < 0) {
byteBuffer.flip();
if (!byteBuffer.hasRemaining()) {
channel.close();
return Optional.empty();
}
byte[] result = new byte[byteBuffer.remaining()];
byteBuffer.get(result);
byteBuffer.clear();
return Optional.of(result);
}
}
return Optional.empty();
}
/** Read a complete block, which must be exactly 32 KB. */
private void processBlock(byte[] block) {
@@ -63,7 +128,7 @@ public final class LevelDbLogReader {
// If this is the last (or only) chunk in the record, store the full contents into the List.
if (recordHeader.type == ChunkType.FULL || recordHeader.type == ChunkType.LAST) {
recordListBuilder.add(recordContents.toByteArray());
recordList.add(recordContents.toByteArray());
recordContents.reset();
}
@@ -96,40 +161,24 @@ public final class LevelDbLogReader {
return new RecordHeader(checksum, size, ChunkType.fromCode(type));
}
/** Reads all records in the Reader into the record set. */
public void readFrom(InputStream source) throws IOException {
byte[] block = new byte[BLOCK_SIZE];
// read until we have no more.
while (true) {
int amountRead = source.read(block, 0, BLOCK_SIZE);
if (amountRead <= 0) {
break;
}
assert amountRead == BLOCK_SIZE;
processBlock(block);
}
/** Returns a {@link LevelDbLogReader} over a {@link ReadableByteChannel}. */
public static LevelDbLogReader from(ReadableByteChannel channel) {
return new LevelDbLogReader(channel);
}
/** Reads all records from the file specified by "path" into the record set. */
public void readFrom(Path path) throws IOException {
readFrom(Files.newInputStream(path));
/** Returns a {@link LevelDbLogReader} over an {@link InputStream}. */
public static LevelDbLogReader from(InputStream source) {
return new LevelDbLogReader(Channels.newChannel(source));
}
/** Reads all records from the specified file into the record set. */
public void readFrom(String filename) throws IOException {
readFrom(FileSystems.getDefault().getPath(filename));
/** Returns a {@link LevelDbLogReader} over a file specified by {@link Path}. */
public static LevelDbLogReader from(Path path) throws IOException {
return from(Files.newInputStream(path));
}
/**
* Gets the list of records constructed so far.
*
* <p>Note that this does not invalidate the internal state of the object: we return a copy and
* this can be called multiple times.
*/
ImmutableList<byte[]> getRecords() {
return recordListBuilder.build();
/** Returns a {@link LevelDbLogReader} over a file specified by {@code filename}. */
public static LevelDbLogReader from(String filename) throws IOException {
return from(FileSystems.getDefault().getPath(filename));
}
/** Aggregates the fields in a record header. */

View File

@@ -15,38 +15,43 @@
package google.registry.tools;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.function.Predicate;
/** Accumulates Entity records from level db files under a directory hierarchy. */
class RecordAccumulator {
private final LevelDbLogReader reader = new LevelDbLogReader();
private final ImmutableList<byte[]> records;
RecordAccumulator(ImmutableList<byte[]> records) {
this.records = records;
}
/** Recursively reads all records in the directory. */
public final RecordAccumulator readDirectory(File dir, Predicate<File> fileMatcher) {
public static RecordAccumulator readDirectory(File dir, Predicate<File> fileMatcher) {
ImmutableList.Builder<byte[]> builder = new ImmutableList.Builder<>();
for (File child : dir.listFiles()) {
if (child.isDirectory()) {
readDirectory(child, fileMatcher);
builder.addAll(readDirectory(child, fileMatcher).records);
} else if (fileMatcher.test(child)) {
try {
reader.readFrom(new FileInputStream(child));
builder.addAll(LevelDbLogReader.from(child.getPath()));
} catch (IOException e) {
throw new RuntimeException("IOException reading from file: " + child, e);
}
}
}
return this;
return new RecordAccumulator(builder.build());
}
/** Creates an entity set from the current set of raw records. */
ImmutableSet<ComparableEntity> getComparableEntitySet() {
ImmutableSet.Builder<ComparableEntity> builder = new ImmutableSet.Builder<>();
for (byte[] rawRecord : reader.getRecords()) {
for (byte[] rawRecord : records) {
// Parse the entity proto and create an Entity object from it.
EntityProto proto = new EntityProto();
proto.parseFrom(rawRecord);

View File

@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import static org.joda.time.DateTimeZone.UTC;
@@ -291,11 +291,9 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
ImmutableSet<String> getContactsOfType(
DomainBase domainBase, final DesignatedContact.Type contactType) {
return domainBase
.getContacts()
.stream()
return domainBase.getContacts().stream()
.filter(contact -> contact.getType().equals(contactType))
.map(contact -> ofy().load().key(contact.getContactKey()).now().getContactId())
.map(contact -> tm().load(contact.getContactKey()).getContactId())
.collect(toImmutableSet());
}
}

View File

@@ -22,7 +22,6 @@ import static google.registry.xml.UtcDateTimeAdapter.getFormattedString;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.ContactResource;
@@ -35,6 +34,7 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.translators.EnumToAttributeAdapter.EppEnum;
import google.registry.persistence.VKey;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -128,7 +128,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
}
/** Returns the contact of the given type. */
private Optional<Key<ContactResource>> getContactReference(Type type) {
private Optional<VKey<ContactResource>> getContactReference(Type type) {
Optional<DesignatedContact> contactOfType =
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
return contactOfType.map(DesignatedContact::getContactKey);
@@ -149,14 +149,14 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
/** Emit the contact entry of the given type. */
DomainEmitter emitContact(
String contactType, Optional<Key<ContactResource>> contact, boolean preferUnicode) {
String contactType, Optional<VKey<ContactResource>> contact, boolean preferUnicode) {
if (!contact.isPresent()) {
return this;
}
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
// someone's attention.
ContactResource contactResource = EppResource.loadCached(contact.get());
ContactResource contactResource = EppResource.loadCached(contact.get().getOfyKey());
if (contactResource == null) {
logger.atSevere().log(
"(BUG) Broken reference found from domain %s to contact %s",

View File

@@ -35,6 +35,7 @@
<class>google.registry.model.domain.GracePeriod</class>
<!-- Customized type converters -->
<class>google.registry.persistence.converter.BillingCostTransitionConverter</class>
<class>google.registry.persistence.converter.BloomFilterConverter</class>
<class>google.registry.persistence.converter.CidrAddressBlockListConverter</class>
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
@@ -47,11 +48,13 @@
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
<class>google.registry.persistence.converter.StringListConverter</class>
<class>google.registry.persistence.converter.StringSetConverter</class>
<class>google.registry.persistence.converter.TldStateTransitionConverter</class>
<class>google.registry.persistence.converter.UpdateAutoTimestampConverter</class>
<class>google.registry.persistence.converter.ZonedDateTimeConverter</class>
<!-- Generated converters for VKey -->
<class>google.registry.model.host.VKeyConverter_HostResource</class>
<class>google.registry.model.contact.VKeyConverter_ContactResource</class>
<!-- TODO(weiminyu): check out application-layer validation. -->
<validation-mode>NONE</validation-mode>

View File

@@ -190,7 +190,9 @@ public class DeleteContactsAndHostsActionTest
.hasDeletionTime(END_OF_TIME);
DomainBase domainReloaded =
loadByForeignKey(DomainBase.class, "example.tld", clock.nowUtc()).get();
assertThat(domainReloaded.getReferencedContacts()).contains(Key.create(contactUpdated));
// We have to check for the objectify key here, specifically, as the SQL side of the key does
// not get persisted.
assertThat(domainReloaded.getReferencedContacts()).contains(contactUpdated.createVKey());
HistoryEntry historyEntry =
getOnlyHistoryEntryOfType(contactUpdated, HistoryEntry.Type.CONTACT_DELETE_FAILURE);
assertPollMessageFor(

View File

@@ -17,6 +17,7 @@ package google.registry.export;
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import static google.registry.testing.TestLogHandlerUtils.assertLogMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -174,6 +175,7 @@ public class BigqueryPollJobActionTest {
action.run();
assertLogMessage(
logHandler, SEVERE, String.format("Bigquery job failed - %s:%s", PROJECT_ID, JOB_ID));
assertNoTasksEnqueued(CHAINED_QUEUE_NAME);
}
@Test

View File

@@ -18,6 +18,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_AND_CLOSE;
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
import static google.registry.model.registry.Registry.TldState.PREDELEGATION;
import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRISE;
@@ -26,6 +27,7 @@ import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.createTlds;
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.DomainBaseSubject.assertAboutDomains;
import static google.registry.testing.EppMetricSubject.assertThat;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
@@ -84,19 +86,311 @@ public class EppLifecycleDomainTest extends EppTestCase {
"domain_create_response.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00.0Z",
"EXDATE", "2002-06-01T00:02:00.0Z"));
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-07T00:02:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2000-06-06T00:02:00Z"));
// Delete domain example.tld after its add grace period has expired.
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-07-01T00:02:00Z")
.hasResponse("generic_success_action_pending_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-07-03T00:02:00Z")
.hasResponse(
"domain_info_response_redemptionperiod_wildcard.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date doesn't change because the deletion didn't cancel any charges.
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2000-07-01T00:02:00Z"));
// Restore the domain.
assertThatCommand("domain_update_restore_request.xml")
.atTime("2000-07-01T00:03:00Z")
.hasResponse("generic_success_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-07-02T00:03:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// TODO(mcilwain): The exp. date should be restored back to 2002-06-01T00:02:00Z,
// but this is old behavior of being 1 year after the moment of the restore.
"EXDATE", "2001-07-01T00:03:00Z",
"UPDATE", "2000-07-01T00:03:00Z"));
assertThatLogoutSucceeds();
}
@Test
public void testDomainDeleteRestore_duringAutorenewGracePeriod() throws Exception {
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
// Create domain example.tld
assertThatCommand(
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-01T00:02:00Z")
.hasResponse(
"domain_create_response.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-07T00:02:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2000-06-06T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2002-06-07T00:02:00Z")
.hasResponse(
"domain_info_response_graceperiod.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date has advanced 1 year because of autorenew.
"EXDATE", "2003-06-01T00:02:00Z",
// This is the time of the autorenew.
"UPDATE", "2002-06-01T00:02:00Z",
"GRACEPERIOD", "autoRenewPeriod"));
// Delete domain example.tld during its autorenew grace period.
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2002-07-01T00:02:00Z")
.hasResponse("generic_success_action_pending_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2002-07-03T00:02:00Z")
.hasResponse(
"domain_info_response_redemptionperiod_wildcard.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date reverts back to what it was originally because the deletion
// canceled out the autorenew.
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2002-07-01T00:02:00Z"));
// Restore the domain.
assertThatCommand("domain_update_restore_request.xml")
.atTime("2002-07-05T00:03:00Z")
.hasResponse("generic_success_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2002-07-07T00:03:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// TODO(mcilwain): The exp. date should be 2003-06-01T00:02:00Z, the same as its
// value prior to the deletion, because the year that was taken off when the
// autorenew was canceled will be re-added in renewal during the restore.
// For now though, the current behavior is 1 year after restore.
"EXDATE", "2003-07-05T00:03:00Z",
"UPDATE", "2002-07-05T00:03:00Z"));
assertThatLogoutSucceeds();
}
@Test
public void testDomainDeleteRestore_duringRenewalGracePeriod() throws Exception {
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
// Create domain example.tld
assertThatCommand(
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-01T00:02:00Z")
.hasResponse(
"domain_create_response.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-07T00:02:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2000-06-06T00:02:00Z"));
assertThatCommand(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXPDATE", "2002-06-01", "YEARS", "3"))
.atTime("2000-06-08T00:00:00Z")
.hasResponse(
"domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-10T00:02:00Z")
.hasResponse(
"domain_info_response_graceperiod.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date is 5 years in total after the create.
"EXDATE", "2005-06-01T00:02:00Z",
// This is the time of the renew.
"UPDATE", "2000-06-08T00:00:00Z",
"GRACEPERIOD", "renewPeriod"));
// Delete domain example.tld during its renew grace period.
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-12T00:00:00Z")
.hasResponse("generic_success_action_pending_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-13T00:00:00Z")
.hasResponse(
"domain_info_response_redemptionperiod_wildcard.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date reverts back to what it was originally because the deletion
// canceled out the 3-year renewal.
"EXDATE", "2002-06-01T00:02:00Z",
"UPDATE", "2000-06-12T00:00:00Z"));
// Restore the domain.
assertThatCommand("domain_update_restore_request.xml")
.atTime("2000-06-20T00:00:00Z")
.hasResponse("generic_success_response.xml");
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-21T00:00:00Z")
.hasResponse(
"domain_info_response_inactive.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// TODO(mcilwain): The exp. date should be 2002-06-01T00:02:00Z, which is the
// current registration expiration time on the (deleted) domain, but for now is
// 1 year after restore.
"EXDATE", "2001-06-20T00:00:00Z",
"UPDATE", "2000-06-20T00:00:00Z"));
assertThatLogoutSucceeds();
}
@Test
public void testDomainDelete_duringAddAndRenewalGracePeriod_deletesImmediately()
throws Exception {
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
DateTime createTime = DateTime.parse("2000-06-01T00:02:00Z");
// Create domain example.tld
assertThatCommand(
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime(createTime)
.hasResponse(
"domain_create_response.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-02T00:02:00Z")
.hasResponse(
"domain_info_response_addperiod_wildcard.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
"EXDATE", "2002-06-01T00:02:00Z"));
DateTime renewTime = DateTime.parse("2000-06-03T00:00:00Z");
assertThatCommand(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXPDATE", "2002-06-01", "YEARS", "3"))
.atTime(renewTime)
.hasResponse(
"domain_renew_response.xml",
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-06-01T00:02:00Z"));
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-03T03:00:00Z")
.hasResponse(
"domain_info_response_graceperiod_add_and_renew.xml",
ImmutableMap.of(
"DOMAIN", "example.tld",
"CRDATE", "2000-06-01T00:02:00Z",
// The exp. date is 5 years in total after the create.
"EXDATE", "2005-06-01T00:02:00Z",
// This is the time of the renew.
"UPDATE", "2000-06-03T00:00:00Z"));
DomainBase domain =
loadByForeignKey(DomainBase.class, "example.tld", DateTime.parse("2000-06-03T04:00:00Z"))
.get();
DateTime deleteTime = DateTime.parse("2000-06-04T00:00:00Z");
// Delete domain example.tld during both grace periods.
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-04T00:00:00Z")
.hasResponse("generic_success_response.xml");
// Verify that it is immediately non-existent.
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
.atTime("2000-06-04T00:01:00Z")
.hasResponse(
"response_error.xml",
ImmutableMap.of(
"CODE", "2303", "MSG", "The domain with given ID (example.tld) doesn't exist."));
// The expected one-time billing event, that should have an associated Cancellation.
OneTime oneTimeCreateBillingEvent = makeOneTimeCreateBillingEvent(domain, createTime);
OneTime oneTimeRenewBillingEvent = makeOneTimeRenewBillingEvent(domain, renewTime);
// Verify that the OneTime billing event associated with the domain creation is canceled.
assertBillingEventsForResource(
domain,
// There should be one-time billing events for the create and the renew.
oneTimeCreateBillingEvent,
oneTimeRenewBillingEvent,
// There should be two ended recurring billing events, one each from the create and renew.
// (The former was ended by the renew and the latter was ended by the delete.)
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), renewTime),
makeRecurringRenewBillingEvent(domain, createTime.plusYears(5), deleteTime),
// There should be Cancellations offsetting both of the one-times.
makeCancellationBillingEventForCreate(
domain, oneTimeCreateBillingEvent, createTime, deleteTime),
makeCancellationBillingEventForRenew(
domain, oneTimeRenewBillingEvent, renewTime, deleteTime));
// Verify that the registration expiration time was set back to the creation time, because the
// entire cost of registration was refunded. We have to do this through the DB instead of EPP
// because domains deleted during the add grace period vanish immediately as far as the world
// outside our system is concerned.
DomainBase deletedDomain = ofy().load().entity(domain).now();
assertAboutDomains().that(deletedDomain).hasRegistrationExpirationTime(createTime);
assertThatLogoutSucceeds();
}
@@ -143,9 +437,16 @@ public class EppLifecycleDomainTest extends EppTestCase {
oneTimeCreateBillingEvent,
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), deleteTime),
// Check for the existence of a cancellation for the given one-time billing event.
makeCancellationBillingEventFor(
makeCancellationBillingEventForCreate(
domain, oneTimeCreateBillingEvent, createTime, deleteTime));
// Verify that the registration expiration time was set back to the creation time, because the
// entire cost of registration was refunded. We have to do this through the DB instead of EPP
// because domains deleted during the add grace period vanish immediately as far as the world
// outside our system is concerned.
DomainBase deletedDomain = ofy().load().entity(domain).now();
assertAboutDomains().that(deletedDomain).hasRegistrationExpirationTime(createTime);
assertThatLogoutSucceeds();
}
@@ -268,7 +569,7 @@ public class EppLifecycleDomainTest extends EppTestCase {
expectedCreateEapBillingEvent,
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), deleteTime),
// ... and verify that the create one-time billing event was canceled ...
makeCancellationBillingEventFor(
makeCancellationBillingEventForCreate(
domain, expectedOneTimeCreateBillingEvent, createTime, deleteTime));
// ... but there was NOT a Cancellation for the EAP fee, as this would fail if additional
// billing events were present.

View File

@@ -284,20 +284,42 @@ public class EppTestCase extends ShardableTestCase {
.setCost(Money.parse("USD 26.00"))
.setPeriodYears(2)
.setEventTime(createTime)
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength()))
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_CREATE))
.build();
}
/** Makes a one-time billing event corresponding to the given domain's renewal. */
protected static BillingEvent.OneTime makeOneTimeRenewBillingEvent(
DomainBase domain, DateTime renewTime) {
return new BillingEvent.OneTime.Builder()
.setReason(Reason.RENEW)
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setCost(Money.parse("USD 33.00"))
.setPeriodYears(3)
.setEventTime(renewTime)
.setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_RENEW))
.build();
}
/** Makes a recurring billing event corresponding to the given domain's creation. */
protected static BillingEvent.Recurring makeRecurringCreateBillingEvent(
DomainBase domain, DateTime eventTime, DateTime endTime) {
return makeRecurringCreateBillingEvent(
return makeRecurringBillingEvent(
domain, getOnlyHistoryEntryOfType(domain, Type.DOMAIN_CREATE), eventTime, endTime);
}
/** Makes a recurring billing event corresponding to the given domain's renewal. */
protected static BillingEvent.Recurring makeRecurringRenewBillingEvent(
DomainBase domain, DateTime eventTime, DateTime endTime) {
return makeRecurringBillingEvent(
domain, getOnlyHistoryEntryOfType(domain, Type.DOMAIN_RENEW), eventTime, endTime);
}
/** Makes a recurring billing event corresponding to the given history entry. */
protected static BillingEvent.Recurring makeRecurringCreateBillingEvent(
protected static BillingEvent.Recurring makeRecurringBillingEvent(
DomainBase domain, HistoryEntry historyEntry, DateTime eventTime, DateTime endTime) {
return new BillingEvent.Recurring.Builder()
.setReason(Reason.RENEW)
@@ -311,22 +333,33 @@ public class EppTestCase extends ShardableTestCase {
}
/** Makes a cancellation billing event cancelling out the given domain create billing event. */
protected static BillingEvent.Cancellation makeCancellationBillingEventFor(
DomainBase domain,
OneTime billingEventToCancel,
DateTime createTime,
DateTime deleteTime) {
protected static BillingEvent.Cancellation makeCancellationBillingEventForCreate(
DomainBase domain, OneTime billingEventToCancel, DateTime createTime, DateTime deleteTime) {
return new BillingEvent.Cancellation.Builder()
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(deleteTime)
.setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel))
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength()))
.setReason(Reason.CREATE)
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
.build();
}
/** Makes a cancellation billing event cancelling out the given domain renew billing event. */
protected static BillingEvent.Cancellation makeCancellationBillingEventForRenew(
DomainBase domain, OneTime billingEventToCancel, DateTime renewTime, DateTime deleteTime) {
return new BillingEvent.Cancellation.Builder()
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(deleteTime)
.setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel))
.setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
.setReason(Reason.RENEW)
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
.build();
}
/**
* Finds the Key to the actual one-time create billing event associated with a domain's creation.
*

View File

@@ -151,7 +151,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
newDomainBase(getUniqueIdFromCommand())
.asBuilder()
.setCreationTimeForTest(TIME_BEFORE_FLOW)
.setRegistrant(Key.create(contact))
.setRegistrant(contact.createVKey())
.setRegistrationExpirationTime(expirationTime)
.build();
earlierHistoryEntry =
@@ -391,7 +391,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
GracePeriod.create(GracePeriodStatus.TRANSFER, TIME_BEFORE_FLOW.plusDays(1), "foo", null));
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
DateTime originalExpirationTime = domain.getRegistrationExpirationTime();
DateTime expectedExpirationTime = domain.getRegistrationExpirationTime().minusYears(2);
clock.advanceOneMilli();
runFlowAssertResponse(loadFile(responseFilename, substitutions));
DomainBase resource = reloadResourceByForeignKey();
@@ -413,7 +413,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
// deletion time, that means once it passes the domain will experience a "phantom autorenew"
// where the expirationTime advances and the grace period appears, but since the delete flow
// closed the autorenew recurrences immediately, there are no other autorenew effects.
assertAboutDomains().that(resource).hasRegistrationExpirationTime(originalExpirationTime);
assertAboutDomains().that(resource).hasRegistrationExpirationTime(expectedExpirationTime);
// All existing grace periods that were for billable actions should cause cancellations.
assertAutorenewClosedAndCancellationCreatedFor(
renewBillingEvent, getOnlyHistoryEntryOfType(resource, DOMAIN_DELETE));
@@ -700,7 +700,9 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
newDomainBase("example1.tld")
.asBuilder()
.setRegistrant(
Key.create(loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get()))
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc())
.get()
.createVKey())
.setNameservers(ImmutableSet.of(host.createKey()))
.setDeletionTime(START_OF_TIME)
.build());

View File

@@ -112,11 +112,11 @@ public class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Dom
.setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z"))
.setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z"))
.setRegistrationExpirationTime(DateTime.parse("2005-04-03T22:00:00.0Z"))
.setRegistrant(Key.create(registrant))
.setRegistrant(registrant.createVKey())
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, Key.create(contact)),
DesignatedContact.create(Type.TECH, Key.create(contact))))
DesignatedContact.create(Type.ADMIN, contact.createVKey()),
DesignatedContact.create(Type.TECH, contact.createVKey())))
.setNameservers(
inactive ? null : ImmutableSet.of(host1.createKey(), host2.createKey()))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR")))

View File

@@ -19,8 +19,8 @@ import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registry.Registry.TldState.QUIET_PERIOD;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
import static google.registry.testing.DatastoreHelper.createTld;
@@ -135,10 +135,10 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.TECH, Key.create(mak21Contact)),
DesignatedContact.create(Type.ADMIN, Key.create(mak21Contact)),
DesignatedContact.create(Type.BILLING, Key.create(mak21Contact))))
.setRegistrant(Key.create(mak21Contact))
DesignatedContact.create(Type.TECH, mak21Contact.createVKey()),
DesignatedContact.create(Type.ADMIN, mak21Contact.createVKey()),
DesignatedContact.create(Type.BILLING, mak21Contact.createVKey())))
.setRegistrant(mak21Contact.createVKey())
.setNameservers(ImmutableSet.of(host.createKey()))
.build());
historyEntryDomainCreate =
@@ -160,8 +160,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact)),
DesignatedContact.create(Type.ADMIN, Key.create(unusedContact))))
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey()),
DesignatedContact.create(Type.ADMIN, unusedContact.createVKey())))
.setNameservers(ImmutableSet.of(host.createKey()))
.build());
historyEntryDomainCreate =
@@ -298,7 +298,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
contactsBuilder.add(
DesignatedContact.create(
DesignatedContact.Type.values()[i % 4],
Key.create(persistActiveContact(String.format("max_test_%d", i)))));
persistActiveContact(String.format("max_test_%d", i)).createVKey()));
}
ImmutableList<DesignatedContact> contacts = contactsBuilder.build();
persistResource(
@@ -319,8 +319,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
assertThat(domain.getNameservers()).hasSize(13);
// getContacts does not return contacts of type REGISTRANT, so check these separately.
assertThat(domain.getContacts()).hasSize(3);
assertThat(ofy().load().key(domain.getRegistrant()).now().getContactId())
.isEqualTo("max_test_7");
assertThat(tm().load(domain.getRegistrant()).getContactId()).isEqualTo("max_test_7");
assertNoBillingEvents();
assertDnsTasksEnqueued("example.tld");
}
@@ -403,7 +402,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistResource(
newDomainBase(getUniqueIdFromCommand())
.asBuilder()
.setRegistrant(Key.create(sh8013))
.setRegistrant(sh8013.createVKey())
.build());
clock.advanceOneMilli();
runFlowAssertResponse(loadFile("generic_success_response.xml"));
@@ -415,7 +414,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
persistReferencedEntities();
ContactResource sh8013 =
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get();
Key<ContactResource> sh8013Key = Key.create(sh8013);
VKey<ContactResource> sh8013Key = sh8013.createVKey();
persistResource(
newDomainBase(getUniqueIdFromCommand())
.asBuilder()
@@ -866,8 +865,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.setContacts(
DesignatedContact.create(
Type.TECH,
Key.create(
loadByForeignKey(ContactResource.class, "foo", clock.nowUtc()).get())))
loadByForeignKey(ContactResource.class, "foo", clock.nowUtc())
.get()
.createVKey()))
.build());
EppException thrown = assertThrows(DuplicateContactForRoleException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1077,8 +1077,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.setContacts(
DesignatedContact.create(
Type.TECH,
Key.create(
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get())))
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc())
.get()
.createVKey()))
.build());
EppException thrown = assertThrows(AddRemoveSameValueException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1093,8 +1094,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, Key.create(sh8013Contact)),
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact))))
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
.build());
EppException thrown = assertThrows(MissingAdminContactException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1109,8 +1110,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, Key.create(sh8013Contact)),
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact))))
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
.build());
EppException thrown = assertThrows(MissingTechnicalContactException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -1223,7 +1224,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("ns1.example.foo"))
.build());
runFlow();
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
.isEqualTo("sh8013");
}
@@ -1237,10 +1238,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.getContacts()
.forEach(
contact -> {
assertThat(ofy().load().key(contact.getContactKey()).now().getContactId())
.isEqualTo("mak21");
assertThat(tm().load(contact.getContactKey()).getContactId()).isEqualTo("mak21");
});
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
.isEqualTo("mak21");
runFlow();
@@ -1249,10 +1249,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
.getContacts()
.forEach(
contact -> {
assertThat(ofy().load().key(contact.getContactKey()).now().getContactId())
.isEqualTo("sh8013");
assertThat(tm().load(contact.getContactKey()).getContactId()).isEqualTo("sh8013");
});
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
.isEqualTo("sh8013");
}

View File

@@ -28,6 +28,7 @@ import com.googlecode.objectify.annotation.Parent;
import com.googlecode.objectify.annotation.Serialize;
import com.googlecode.objectify.cmd.Query;
import google.registry.model.ofy.Ofy;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
@@ -164,7 +165,8 @@ public abstract class EntityTestCase {
: (Class<?>) inner;
}
// Descend into persisted ImmutableObject classes, but not anything else.
if (ImmutableObject.class.isAssignableFrom(fieldClass)) {
if (ImmutableObject.class.isAssignableFrom(fieldClass)
&& !VKey.class.isAssignableFrom(fieldClass)) {
getAllPotentiallyIndexedFieldPaths(fieldClass).stream()
.map(subfield -> field.getName() + "." + subfield)
.distinct()

View File

@@ -22,7 +22,6 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
@@ -30,6 +29,7 @@ import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
@@ -56,15 +56,20 @@ public class DomainBaseSqlTest {
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
DomainBase domain;
Key<ContactResource> contactKey;
Key<ContactResource> contact2Key;
VKey<ContactResource> contactKey;
VKey<ContactResource> contact2Key;
VKey<HostResource> host1VKey;
HostResource host;
ContactResource contact;
ContactResource contact2;
@BeforeEach
public void setUp() {
contactKey = Key.create(ContactResource.class, "contact_id1");
contact2Key = Key.create(ContactResource.class, "contact_id2");
saveRegistrar("registrar1");
saveRegistrar("registrar2");
saveRegistrar("registrar3");
contactKey = VKey.createSql(ContactResource.class, "contact_id1");
contact2Key = VKey.createSql(ContactResource.class, "contact_id2");
host1VKey = VKey.createSql(HostResource.class, "host1");
@@ -104,23 +109,28 @@ public class DomainBaseSqlTest {
.setCreationClientId("registrar1")
.setPersistedCurrentSponsorClientId("registrar2")
.build();
contact = makeContact("contact_id1");
contact2 = makeContact("contact_id2");
}
@Test
public void testDomainBasePersistence() {
saveRegistrar("registrar1");
saveRegistrar("registrar2");
saveRegistrar("registrar3");
jpaTm()
.transact(
() -> {
// Persist the domain.
EntityManager em = jpaTm().getEntityManager();
em.persist(domain);
// Persist the contacts. Note that these need to be persisted before the domain
// otherwise we get a foreign key constraint error. If we ever decide to defer the
// relevant foreign key checks to commit time, then the order would not matter.
jpaTm().saveNew(contact);
jpaTm().saveNew(contact2);
// Persist the host.
em.persist(host);
// Persist the domain.
jpaTm().saveNew(domain);
// Persist the host. This does _not_ need to be persisted before the domain,
// because only the row in the join table (DomainHost) is subject to foreign key
// constraints, and Hibernate knows to insert it after domain and host.
jpaTm().saveNew(host);
});
jpaTm()
@@ -130,13 +140,11 @@ public class DomainBaseSqlTest {
EntityManager em = jpaTm().getEntityManager();
DomainBase result = em.find(DomainBase.class, "4-COM");
// Fix contacts, grace period and DS data, since we can't persist them yet.
// Fix grace period and DS data, since we can't persist them yet.
result =
result
.asBuilder()
.setRegistrant(contactKey)
.setContacts(
ImmutableSet.of(DesignatedContact.create(Type.ADMIN, contact2Key)))
.setDsData(
ImmutableSet.of(
DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2})))
@@ -151,16 +159,40 @@ public class DomainBaseSqlTest {
}
@Test
public void testForeignKeyConstraints() {
public void testHostForeignKeyConstraints() {
assertThrowForeignKeyViolation(
() -> {
jpaTm()
.transact(
() -> {
// Persist the domain without the associated host object.
EntityManager em = jpaTm().getEntityManager();
em.persist(domain);
jpaTm().saveNew(contact);
jpaTm().saveNew(contact2);
jpaTm().saveNew(domain);
});
});
}
@Test
public void testContactForeignKeyConstraints() {
assertThrowForeignKeyViolation(
() -> {
jpaTm()
.transact(
() -> {
// Persist the domain without the associated contact objects.
jpaTm().saveNew(domain);
jpaTm().saveNew(host);
});
});
}
public static ContactResource makeContact(String repoId) {
return new ContactResource.Builder()
.setRepoId(repoId)
.setCreationClientId("registrar1")
.setTransferData(new TransferData.Builder().build())
.setPersistedCurrentSponsorClientId("registrar1")
.build();
}
}

View File

@@ -80,20 +80,20 @@ public class DomainBaseTest extends EntityTestCase {
.setRepoId("1-COM")
.build())
.createKey();
Key<ContactResource> contact1Key =
Key.create(
persistResource(
VKey<ContactResource> contact1Key =
persistResource(
new ContactResource.Builder()
.setContactId("contact_id1")
.setRepoId("2-COM")
.build()));
Key<ContactResource> contact2Key =
Key.create(
persistResource(
.build())
.createVKey();
VKey<ContactResource> contact2Key =
persistResource(
new ContactResource.Builder()
.setContactId("contact_id2")
.setRepoId("3-COM")
.build()));
.build())
.createVKey();
Key<HistoryEntry> historyEntryKey =
Key.create(persistResource(new HistoryEntry.Builder().setParent(domainKey).build()));
oneTimeBillKey = Key.create(historyEntryKey, BillingEvent.OneTime.class, 1);
@@ -771,60 +771,4 @@ public class DomainBaseTest extends EntityTestCase {
assertThat(getOnlyElement(clone.getGracePeriods()).getType())
.isEqualTo(GracePeriodStatus.TRANSFER);
}
private static ImmutableSet<Key<HostResource>> getOfyNameservers(DomainBase domain) {
return domain.getNameservers().stream().map(key -> key.getOfyKey()).collect(toImmutableSet());
}
@Test
public void testNameservers_nsHostsOfyKeys() {
assertThat(domain.nsHosts).isEqualTo(getOfyNameservers(domain));
// Test the setNameserver that functions on a function.
VKey<HostResource> host1Key =
persistResource(
new HostResource.Builder()
.setFullyQualifiedHostName("ns2.example.com")
.setSuperordinateDomain(domainKey)
.setRepoId("2-COM")
.build())
.createKey();
DomainBase dom = new DomainBase.Builder(domain).setNameservers(host1Key).build();
assertThat(dom.getNameservers()).isEqualTo(ImmutableSet.of(host1Key));
assertThat(getOfyNameservers(dom)).isEqualTo(ImmutableSet.of(host1Key.getOfyKey()));
// Test that setting to a single host of null throws an NPE.
assertThrows(
NullPointerException.class,
() -> new DomainBase.Builder(domain).setNameservers((VKey<HostResource>) null));
// Test that setting to a set of values works.
VKey<HostResource> host2Key =
persistResource(
new HostResource.Builder()
.setFullyQualifiedHostName("ns3.example.com")
.setSuperordinateDomain(domainKey)
.setRepoId("3-COM")
.build())
.createKey();
dom =
new DomainBase.Builder(domain).setNameservers(ImmutableSet.of(host1Key, host2Key)).build();
assertThat(dom.getNameservers()).isEqualTo(ImmutableSet.of(host1Key, host2Key));
assertThat(getOfyNameservers(dom))
.isEqualTo(ImmutableSet.of(host1Key.getOfyKey(), host2Key.getOfyKey()));
// Set of values, passing null.
dom =
new DomainBase.Builder(domain)
.setNameservers((ImmutableSet<VKey<HostResource>>) null)
.build();
assertThat(dom.nsHostVKeys).isNull();
assertThat(dom.nsHosts).isNull();
// Empty set of values gets translated to null.
dom = new DomainBase.Builder(domain).setNameservers(ImmutableSet.of()).build();
assertThat(dom.nsHostVKeys).isNull();
assertThat(dom.nsHosts).isNull();
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.ImmutableObject;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.BillingCostTransition;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link BillingCostTransitionConverter}. */
public class BillingCostTransitionConverterTest {
@RegisterExtension
public final JpaUnitTestRule jpa =
new JpaTestRules.Builder()
.withInitScript("sql/flyway/V14__load_extension_for_hstore.sql")
.withEntityClass(TestEntity.class)
.buildUnitTestRule();
private static final ImmutableSortedMap<DateTime, Money> values =
ImmutableSortedMap.of(
START_OF_TIME,
Money.of(USD, 8),
DateTime.parse("2001-01-01T00:00:00.0Z"),
Money.of(USD, 0));
@Test
void roundTripConversion_returnsSameTimedTransitionProperty() {
TimedTransitionProperty<Money, BillingCostTransition> timedTransitionProperty =
TimedTransitionProperty.fromValueMap(values, BillingCostTransition.class);
TestEntity testEntity = new TestEntity(timedTransitionProperty);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.timedTransitionProperty).containsExactlyEntriesIn(timedTransitionProperty);
}
@Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject {
@Id String name = "id";
TimedTransitionProperty<Money, BillingCostTransition> timedTransitionProperty;
private TestEntity() {}
private TestEntity(
TimedTransitionProperty<Money, BillingCostTransition> timedTransitionProperty) {
this.timedTransitionProperty = timedTransitionProperty;
}
}
}

View File

@@ -0,0 +1,181 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Maps;
import google.registry.model.ImmutableObject;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import java.util.Map;
import javax.persistence.Converter;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NoResultException;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link TimedTransitionPropertyConverterBase}. */
public class TimedTransitionPropertyConverterBaseTest {
@RegisterExtension
public final JpaUnitTestRule jpa =
new JpaTestRules.Builder()
.withInitScript("sql/flyway/V14__load_extension_for_hstore.sql")
.withEntityClass(TestTimedTransitionPropertyConverter.class, TestEntity.class)
.buildUnitTestRule();
private static final DateTime DATE_1 = DateTime.parse("2001-01-01T00:00:00.000Z");
private static final DateTime DATE_2 = DateTime.parse("2002-01-01T00:00:00.000Z");
private static final ImmutableSortedMap<DateTime, String> VALUES =
ImmutableSortedMap.of(
START_OF_TIME, "val1",
DATE_1, "val2",
DATE_2, "val3");
private static final TimedTransitionProperty<String, TestTransition> TIMED_TRANSITION_PROPERTY =
TimedTransitionProperty.fromValueMap(VALUES, TestTransition.class);
@Test
void roundTripConversion_returnsSameTimedTransitionProperty() {
TestEntity testEntity = new TestEntity(TIMED_TRANSITION_PROPERTY);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property).containsExactlyEntriesIn(TIMED_TRANSITION_PROPERTY);
}
@Test
void testUpdateColumn_succeeds() {
TestEntity testEntity = new TestEntity(TIMED_TRANSITION_PROPERTY);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property).containsExactlyEntriesIn(TIMED_TRANSITION_PROPERTY);
ImmutableSortedMap<DateTime, String> newValues = ImmutableSortedMap.of(START_OF_TIME, "val4");
persisted.property = TimedTransitionProperty.fromValueMap(newValues, TestTransition.class);
jpaTm().transact(() -> jpaTm().getEntityManager().merge(persisted));
TestEntity updated =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(updated.property.toValueMap()).isEqualTo(newValues);
}
@Test
void testNullValue_writesAndReadsNullSuccessfully() {
TestEntity testEntity = new TestEntity(null);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.property).isNull();
}
@Test
void testNativeQuery_succeeds() {
executeNativeQuery(
"INSERT INTO \"TestEntity\" (name, property) VALUES ('id',"
+ " 'val1=>1970-01-01T00:00:00.000Z, val2=>2001-01-01T00:00:00.000Z')");
assertThat(
getSingleResultFromNativeQuery(
"SELECT property -> 'val1' FROM \"TestEntity\" WHERE name = 'id'"))
.isEqualTo(START_OF_TIME.toString());
assertThat(
getSingleResultFromNativeQuery(
"SELECT property -> 'val2' FROM \"TestEntity\" WHERE name = 'id'"))
.isEqualTo(DATE_1.toString());
executeNativeQuery(
"UPDATE \"TestEntity\" SET property = 'val3=>2002-01-01T00:00:00.000Z' WHERE name = 'id'");
assertThat(
getSingleResultFromNativeQuery(
"SELECT property -> 'val3' FROM \"TestEntity\" WHERE name = 'id'"))
.isEqualTo(DATE_2.toString());
executeNativeQuery("DELETE FROM \"TestEntity\" WHERE name = 'id'");
assertThrows(
NoResultException.class,
() ->
getSingleResultFromNativeQuery(
"SELECT property -> 'val3' FROM \"TestEntity\" WHERE name = 'id'"));
}
private static Object getSingleResultFromNativeQuery(String sql) {
return jpaTm()
.transact(() -> jpaTm().getEntityManager().createNativeQuery(sql).getSingleResult());
}
private static void executeNativeQuery(String sql) {
jpaTm().transact(() -> jpaTm().getEntityManager().createNativeQuery(sql).executeUpdate());
}
public static class TestTransition extends TimedTransition<String> {
private String transition;
@Override
public String getValue() {
return transition;
}
@Override
protected void setValue(String transition) {
this.transition = transition;
}
}
@Converter(autoApply = true)
private static class TestTimedTransitionPropertyConverter
extends TimedTransitionPropertyConverterBase<String, TestTransition> {
@Override
Map.Entry<DateTime, String> convertToEntityMapEntry(Map.Entry<String, String> entry) {
return Maps.immutableEntry(DateTime.parse(entry.getKey()), entry.getValue());
}
@Override
Class<TestTransition> getTimedTransitionSubclass() {
return TestTransition.class;
}
@Override
Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<DateTime, TestTransition> entry) {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue());
}
}
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
private static class TestEntity extends ImmutableObject {
@Id String name = "id";
TimedTransitionProperty<String, TestTransition> property;
private TestEntity() {}
private TestEntity(TimedTransitionProperty<String, TestTransition> timedTransitionProperty) {
this.property = timedTransitionProperty;
}
}
}

View File

@@ -0,0 +1,80 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.ImmutableObject;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldStateTransition;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link TldStateTransitionConverter}. */
public class TldStateTransitionConverterTest {
@RegisterExtension
public final JpaUnitTestRule jpa =
new JpaTestRules.Builder()
.withInitScript("sql/flyway/V14__load_extension_for_hstore.sql")
.withEntityClass(TestEntity.class)
.buildUnitTestRule();
private static final ImmutableSortedMap<DateTime, TldState> values =
ImmutableSortedMap.of(
START_OF_TIME,
TldState.PREDELEGATION,
DateTime.parse("2001-01-01T00:00:00.0Z"),
TldState.QUIET_PERIOD,
DateTime.parse("2002-01-01T00:00:00.0Z"),
TldState.PDT,
DateTime.parse("2003-01-01T00:00:00.0Z"),
TldState.GENERAL_AVAILABILITY);
@Test
void roundTripConversion_returnsSameTimedTransitionProperty() {
TimedTransitionProperty<TldState, TldStateTransition> timedTransitionProperty =
TimedTransitionProperty.fromValueMap(values, TldStateTransition.class);
TestEntity testEntity = new TestEntity(timedTransitionProperty);
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
TestEntity persisted =
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
assertThat(persisted.timedTransitionProperty).containsExactlyEntriesIn(timedTransitionProperty);
}
@Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject {
@Id String name = "id";
TimedTransitionProperty<TldState, TldStateTransition> timedTransitionProperty;
private TestEntity() {}
private TestEntity(
TimedTransitionProperty<TldState, TldStateTransition> timedTransitionProperty) {
this.timedTransitionProperty = timedTransitionProperty;
}
}
}

View File

@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
@@ -60,10 +61,11 @@ public class JpaTestRules {
/**
* Junit rule for unit tests with JPA framework, when the underlying database is populated by the
* optional init script (which must not be the Nomulus Cloud SQL schema).
* optional init script (which must not be the Nomulus Cloud SQL schema). This rule can also be
* used as am extension for JUnit5 tests.
*/
public static class JpaUnitTestRule extends JpaTransactionManagerRule {
public static class JpaUnitTestRule extends JpaTransactionManagerRule
implements BeforeEachCallback, AfterEachCallback {
private JpaUnitTestRule(
Clock clock,
Optional<String> initScriptPath,
@@ -71,6 +73,16 @@ public class JpaTestRules {
ImmutableMap<String, String> userProperties) {
super(clock, initScriptPath, extraEntityClasses, userProperties);
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
this.before();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
this.after();
}
}
/**
@@ -169,6 +181,17 @@ public class JpaTestRules {
return this;
}
/**
* Enables logging of SQL statements.
*
* <p>SQL logging is very noisy and disabled by default. This method maybe useful when
* troubleshooting a specific test.
*/
public Builder withSqlLogging() {
withProperty(Environment.SHOW_SQL, "true");
return this;
}
/** Builds a {@link JpaIntegrationTestRule} instance. */
public JpaIntegrationTestRule buildIntegrationTestRule() {
return new JpaIntegrationTestRule(
@@ -195,7 +218,9 @@ public class JpaTestRules {
return new JpaIntegrationWithCoverageExtension(buildIntegrationTestRule());
}
/** Builds a {@link JpaUnitTestRule} instance. */
/**
* Builds a {@link JpaUnitTestRule} instance that can also be used as an extension for JUnit5.
*/
public JpaUnitTestRule buildUnitTestRule() {
checkState(
!Objects.equals(GOLDEN_SCHEMA_SQL_PATH, initScript),

View File

@@ -31,14 +31,19 @@ import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link JpaTransactionManagerImpl}. */
/**
* Unit tests for SQL only APIs defined in {@link JpaTransactionManagerImpl}. Note that the tests
* for common APIs in {@link TransactionManager} are added in {@link TransactionManagerTest}.
*
* <p>TODO(shicong): Remove duplicate tests that covered by TransactionManagerTest by refactoring
* the test schema.
*/
@RunWith(JUnit4.class)
public class JpaTransactionManagerImplTest {
@@ -63,29 +68,6 @@ public class JpaTransactionManagerImplTest {
.withEntityClass(TestEntity.class, TestCompoundIdEntity.class)
.buildUnitTestRule();
@Test
public void inTransaction_returnsCorrespondingResult() {
assertThat(jpaTm().inTransaction()).isFalse();
jpaTm().transact(() -> assertThat(jpaTm().inTransaction()).isTrue());
assertThat(jpaTm().inTransaction()).isFalse();
}
@Test
public void assertInTransaction_throwsExceptionWhenNotInTransaction() {
assertThrows(PersistenceException.class, () -> jpaTm().assertInTransaction());
jpaTm().transact(() -> jpaTm().assertInTransaction());
assertThrows(PersistenceException.class, () -> jpaTm().assertInTransaction());
}
@Test
public void getTransactionTime_throwsExceptionWhenNotInTransaction() {
FakeClock txnClock = fakeClock;
txnClock.advanceOneMilli();
assertThrows(PersistenceException.class, () -> jpaTm().getTransactionTime());
jpaTm().transact(() -> assertThat(jpaTm().getTransactionTime()).isEqualTo(txnClock.nowUtc()));
assertThrows(PersistenceException.class, () -> jpaTm().getTransactionTime());
}
@Test
public void transact_succeeds() {
assertPersonEmpty();
@@ -333,14 +315,14 @@ public class JpaTransactionManagerImplTest {
public void delete_succeeds() {
jpaTm().transact(() -> jpaTm().saveNew(theEntity));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isTrue();
assertThat(jpaTm().transact(() -> jpaTm().delete(theEntityKey))).isEqualTo(1);
jpaTm().transact(() -> jpaTm().delete(theEntityKey));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
}
@Test
public void delete_returnsZeroWhenNoEntity() {
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
assertThat(jpaTm().transact(() -> jpaTm().delete(theEntityKey))).isEqualTo(0);
jpaTm().transact(() -> jpaTm().delete(theEntityKey));
assertThat(jpaTm().transact(() -> jpaTm().checkExists(theEntity))).isFalse();
}

View File

@@ -41,6 +41,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
@@ -146,13 +148,15 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
ImmutableMap.Builder builder = properties.builder();
builder.putAll(userProperties);
// Forbid Hibernate push to stay consistent with flyway-based schema management.
builder.put(Environment.HBM2DDL_AUTO, "none");
builder.put(Environment.SHOW_SQL, "true");
properties = builder.build();
Map<String, String> mergedProperties = Maps.newHashMap();
mergedProperties.putAll(properties);
mergedProperties.putAll(userProperties);
properties = ImmutableMap.copyOf(mergedProperties);
}
// Forbid Hibernate push to stay consistent with flyway-based schema management.
checkState(
Objects.equals(properties.get(Environment.HBM2DDL_AUTO), "none"),
"The HBM2DDL_AUTO property must be 'none'.");
assertReasonableNumDbConnections();
emf =
createEntityManagerFactory(

View File

@@ -0,0 +1,273 @@
// Copyright 2019 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.persistence.transaction;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.ImmutableObject;
import google.registry.model.ofy.DatastoreTransactionManager;
import google.registry.model.ofy.Ofy;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineRule;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
import java.util.NoSuchElementException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
* Unit tests for common APIs in {@link DatastoreTransactionManager} and {@link
* JpaTransactionManagerImpl}.
*/
@DualDatabaseTest
public class TransactionManagerTest {
private final FakeClock fakeClock = new FakeClock();
private final TestEntity theEntity = new TestEntity("theEntity", "foo");
private final ImmutableList<TestEntity> moreEntities =
ImmutableList.of(
new TestEntity("entity1", "foo"),
new TestEntity("entity2", "bar"),
new TestEntity("entity3", "qux"));
@RegisterExtension public InjectRule inject = new InjectRule();
@RegisterExtension
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withClock(fakeClock)
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestEntity.class)
.withJpaUnitTestEntities(TestEntity.class)
.build();
public TransactionManagerTest() {}
@BeforeEach
public void setUp() {
inject.setStaticField(Ofy.class, "clock", fakeClock);
}
@TestTemplate
public void inTransaction_returnsCorrespondingResult() {
assertThat(tm().inTransaction()).isFalse();
tm().transact(() -> assertThat(tm().inTransaction()).isTrue());
assertThat(tm().inTransaction()).isFalse();
}
@TestTemplate
public void assertInTransaction_throwsExceptionWhenNotInTransaction() {
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
tm().transact(() -> tm().assertInTransaction());
assertThrows(IllegalStateException.class, () -> tm().assertInTransaction());
}
@TestTemplate
public void getTransactionTime_throwsExceptionWhenNotInTransaction() {
FakeClock txnClock = fakeClock;
txnClock.advanceOneMilli();
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
tm().transact(() -> assertThat(tm().getTransactionTime()).isEqualTo(txnClock.nowUtc()));
assertThrows(IllegalStateException.class, () -> tm().getTransactionTime());
}
@TestTemplate
public void transact_hasNoEffectWithPartialSuccess() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
assertThrows(
RuntimeException.class,
() ->
tm()
.transact(
() -> {
tm().saveNew(theEntity);
throw new RuntimeException();
}));
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
}
@TestTemplate
public void transact_reusesExistingTransaction() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().transact(() -> tm().saveNew(theEntity)));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
}
@TestTemplate
public void saveNew_succeeds() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
}
@TestTemplate
public void saveAllNew_succeeds() {
moreEntities.forEach(
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveAllNew(moreEntities));
fakeClock.advanceOneMilli();
moreEntities.forEach(
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
}
@TestTemplate
public void saveNewOrUpdate_persistsNewEntity() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNewOrUpdate(theEntity));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().load(theEntity.key()))).isEqualTo(theEntity);
}
@TestTemplate
public void saveNewOrUpdate_updatesExistingEntity() {
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
TestEntity persisted = tm().transact(() -> tm().load(theEntity.key()));
assertThat(persisted.data).isEqualTo("foo");
theEntity.data = "bar";
tm().transact(() -> tm().saveNewOrUpdate(theEntity));
fakeClock.advanceOneMilli();
persisted = tm().transact(() -> tm().load(theEntity.key()));
fakeClock.advanceOneMilli();
assertThat(persisted.data).isEqualTo("bar");
}
@TestTemplate
public void saveNewOrUpdateAll_succeeds() {
moreEntities.forEach(
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isFalse());
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNewOrUpdateAll(moreEntities));
fakeClock.advanceOneMilli();
moreEntities.forEach(
entity -> assertThat(tm().transact(() -> tm().checkExists(entity))).isTrue());
}
@TestTemplate
public void update_succeeds() {
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
TestEntity persisted =
tm().transact(
() ->
tm().load(
VKey.create(TestEntity.class, theEntity.name, Key.create(theEntity))));
fakeClock.advanceOneMilli();
assertThat(persisted.data).isEqualTo("foo");
theEntity.data = "bar";
tm().transact(() -> tm().update(theEntity));
fakeClock.advanceOneMilli();
persisted = tm().transact(() -> tm().load(theEntity.key()));
assertThat(persisted.data).isEqualTo("bar");
}
@TestTemplate
public void load_succeeds() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
TestEntity persisted = tm().transact(() -> tm().load(theEntity.key()));
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@TestTemplate
public void load_throwsOnMissingElement() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
assertThrows(
NoSuchElementException.class, () -> tm().transact(() -> tm().load(theEntity.key())));
}
@TestTemplate
public void maybeLoad_succeeds() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
TestEntity persisted = tm().transact(() -> tm().maybeLoad(theEntity.key()).get());
assertThat(persisted.name).isEqualTo("theEntity");
assertThat(persisted.data).isEqualTo("foo");
}
@TestTemplate
public void maybeLoad_nonExistentObject() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().maybeLoad(theEntity.key())).isPresent()).isFalse();
}
@TestTemplate
public void delete_succeeds() {
fakeClock.advanceOneMilli();
tm().transact(() -> tm().saveNew(theEntity));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isTrue();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().delete(theEntity.key()));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
}
@TestTemplate
public void delete_returnsZeroWhenNoEntity() {
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
fakeClock.advanceOneMilli();
tm().transact(() -> tm().delete(theEntity.key()));
fakeClock.advanceOneMilli();
assertThat(tm().transact(() -> tm().checkExists(theEntity))).isFalse();
}
@Entity(name = "TestEntity")
@javax.persistence.Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject {
@Id @javax.persistence.Id private String name;
private String data;
private TestEntity() {}
private TestEntity(String name, String data) {
this.name = name;
this.data = data;
}
public VKey<TestEntity> key() {
return VKey.create(TestEntity.class, name, Key.create(this));
}
}
}

View File

@@ -240,22 +240,22 @@ public class DomainBaseToXjcConverterTest {
ImmutableSet.of(
DesignatedContact.create(
DesignatedContact.Type.ADMIN,
Key.create(
makeContactResource(
makeContactResource(
clock,
"10-Q9JYB4C",
"5372808-IRL",
"be that word our sign in parting",
"BOFH@cat.みんな"))),
"BOFH@cat.みんな")
.createVKey()),
DesignatedContact.create(
DesignatedContact.Type.TECH,
Key.create(
makeContactResource(
makeContactResource(
clock,
"11-Q9JYB4C",
"5372808-TRL",
"bird or fiend!? i shrieked upstarting",
"bog@cat.みんな")))))
"bog@cat.みんな")
.createVKey())))
.setCreationClientId("LawyerCat")
.setCreationTimeForTest(DateTime.parse("1900-01-01T00:00:00Z"))
.setPersistedCurrentSponsorClientId("GetTheeBack")
@@ -273,9 +273,9 @@ public class DomainBaseToXjcConverterTest {
makeHostResource(clock, "4-Q9JYB4C", "ns2.cat.みんな", "bad:f00d:cafe::15:beef")
.createKey()))
.setRegistrant(
Key.create(
makeContactResource(
clock, "12-Q9JYB4C", "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな")))
makeContactResource(
clock, "12-Q9JYB4C", "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな")
.createVKey())
.setRegistrationExpirationTime(DateTime.parse("1930-01-01T00:00:00Z"))
.setGracePeriods(
ImmutableSet.of(

View File

@@ -63,9 +63,8 @@ final class RdeFixtures {
.setFullyQualifiedDomainName("example." + tld)
.setRepoId(generateNewDomainRoid(tld))
.setRegistrant(
Key.create(
makeContactResource(
clock, "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな")))
makeContactResource(clock, "5372808-ERL", "(◕‿◕) nevermore", "prophet@evil.みんな")
.createVKey())
.build();
HistoryEntry historyEntry =
persistResource(new HistoryEntry.Builder().setParent(domain).build());
@@ -90,20 +89,20 @@ final class RdeFixtures {
ImmutableSet.of(
DesignatedContact.create(
DesignatedContact.Type.ADMIN,
Key.create(
makeContactResource(
makeContactResource(
clock,
"5372808-IRL",
"be that word our sign in parting",
"BOFH@cat.みんな"))),
"BOFH@cat.みんな")
.createVKey()),
DesignatedContact.create(
DesignatedContact.Type.TECH,
Key.create(
makeContactResource(
makeContactResource(
clock,
"5372808-TRL",
"bird or fiend!? i shrieked upstarting",
"bog@cat.みんな")))))
"bog@cat.みんな")
.createVKey())))
.setCreationClientId("TheRegistrar")
.setPersistedCurrentSponsorClientId("TheRegistrar")
.setCreationTimeForTest(clock.nowUtc())

View File

@@ -27,7 +27,6 @@ import static google.registry.testing.DatastoreHelper.persistResource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.OteStatsTestHelper;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactResource;
@@ -127,9 +126,9 @@ public enum Fixture {
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(ADMIN, Key.create(robert)),
DesignatedContact.create(BILLING, Key.create(google)),
DesignatedContact.create(TECH, Key.create(justine))))
DesignatedContact.create(ADMIN, robert.createVKey()),
DesignatedContact.create(BILLING, google.createVKey()),
DesignatedContact.create(TECH, justine.createVKey())))
.setNameservers(
ImmutableSet.of(
persistActiveHost("ns1.love.xn--q9jyb4c").createKey(),
@@ -141,9 +140,9 @@ public enum Fixture {
.asBuilder()
.setContacts(
ImmutableSet.of(
DesignatedContact.create(ADMIN, Key.create(robert)),
DesignatedContact.create(BILLING, Key.create(google)),
DesignatedContact.create(TECH, Key.create(justine))))
DesignatedContact.create(ADMIN, robert.createVKey()),
DesignatedContact.create(BILLING, google.createVKey()),
DesignatedContact.create(TECH, justine.createVKey())))
.setNameservers(
ImmutableSet.of(
persistActiveHost("ns1.linode.com").createKey(),

View File

@@ -17,6 +17,8 @@ package google.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.injectTmForDualDatabaseTest;
import static google.registry.testing.DualDatabaseTestInvocationContextProvider.restoreTmAfterDualDatabaseTest;
import static google.registry.util.ResourceUtils.readResourceUtf8;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.json.XML.toJSONObject;
@@ -45,6 +47,7 @@ import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import google.registry.util.Clock;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -118,8 +121,11 @@ public final class AppEngineRule extends ExternalResource
*/
JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null;
JpaUnitTestRule jpaUnitTestRule;
private boolean withDatastoreAndCloudSql;
private boolean enableJpaEntityCoverageCheck;
private boolean withJpaUnitTest;
private boolean withLocalModules;
private boolean withTaskQueue;
private boolean withUserService;
@@ -131,12 +137,14 @@ public final class AppEngineRule extends ExternalResource
// Test Objectify entity classes to be used with this AppEngineRule instance.
private ImmutableList<Class<?>> ofyTestEntities;
private ImmutableList<Class<?>> jpaTestEntities;
/** Builder for {@link AppEngineRule}. */
public static class Builder {
private AppEngineRule rule = new AppEngineRule();
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder();
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder<>();
private ImmutableList.Builder<Class<?>> jpaTestEntities = new ImmutableList.Builder<>();
/** Turn on the Datastore service and the Cloud SQL service. */
public Builder withDatastoreAndCloudSql() {
@@ -205,11 +213,24 @@ public final class AppEngineRule extends ExternalResource
return this;
}
public Builder withJpaUnitTestEntities(Class<?>... entities) {
jpaTestEntities.add(entities);
rule.withJpaUnitTest = true;
return this;
}
public AppEngineRule build() {
checkState(
!rule.enableJpaEntityCoverageCheck || rule.withDatastoreAndCloudSql,
"withJpaEntityCoverageCheck enabled without Cloud SQL");
checkState(
!rule.withJpaUnitTest || rule.withDatastoreAndCloudSql,
"withJpaUnitTestEntities enabled without Cloud SQL");
checkState(
!rule.withJpaUnitTest || !rule.enableJpaEntityCoverageCheck,
"withJpaUnitTestEntities cannot be set when enableJpaEntityCoverageCheck");
rule.ofyTestEntities = this.ofyTestEntities.build();
rule.jpaTestEntities = this.jpaTestEntities.build();
return rule;
}
}
@@ -328,11 +349,18 @@ public final class AppEngineRule extends ExternalResource
if (enableJpaEntityCoverageCheck) {
jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension();
jpaIntegrationWithCoverageExtension.beforeEach(context);
} else if (withJpaUnitTest) {
jpaUnitTestRule =
builder
.withEntityClass(jpaTestEntities.toArray(new Class[jpaTestEntities.size()]))
.buildUnitTestRule();
jpaUnitTestRule.before();
} else {
jpaIntegrationTestRule = builder.buildIntegrationTestRule();
jpaIntegrationTestRule.before();
}
}
injectTmForDualDatabaseTest(context);
}
/** Called after each test method. JUnit 5 only. */
@@ -341,11 +369,14 @@ public final class AppEngineRule extends ExternalResource
if (withDatastoreAndCloudSql) {
if (enableJpaEntityCoverageCheck) {
jpaIntegrationWithCoverageExtension.afterEach(context);
} else if (withJpaUnitTest) {
jpaUnitTestRule.after();
} else {
jpaIntegrationTestRule.after();
}
}
after();
restoreTmAfterDualDatabaseTest(context);
}
/**
@@ -560,4 +591,8 @@ public final class AppEngineRule extends ExternalResource
makeRegistrarContact2(),
makeRegistrarContact3()));
}
boolean isWithDatastoreAndCloudSql() {
return withDatastoreAndCloudSql;
}
}

View File

@@ -93,6 +93,7 @@ import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import java.util.Arrays;
import java.util.List;
@@ -144,7 +145,7 @@ public class DatastoreHelper {
public static DomainBase newDomainBase(
String domainName, String repoId, ContactResource contact) {
Key<ContactResource> contactKey = Key.create(contact);
VKey<ContactResource> contactKey = contact.createVKey();
return new DomainBase.Builder()
.setRepoId(repoId)
.setFullyQualifiedDomainName(domainName)
@@ -487,11 +488,11 @@ public class DatastoreHelper {
.setCreationClientId("TheRegistrar")
.setCreationTimeForTest(creationTime)
.setRegistrationExpirationTime(expirationTime)
.setRegistrant(Key.create(contact))
.setRegistrant(contact.createVKey())
.setContacts(
ImmutableSet.of(
DesignatedContact.create(Type.ADMIN, Key.create(contact)),
DesignatedContact.create(Type.TECH, Key.create(contact))))
DesignatedContact.create(Type.ADMIN, contact.createVKey()),
DesignatedContact.create(Type.TECH, contact.createVKey())))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("fooBAR")))
.addGracePeriod(
GracePeriod.create(GracePeriodStatus.ADD, now.plusDays(10), "foo", null))

View File

@@ -0,0 +1,28 @@
// Copyright 2020 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.testing;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
/** Annotation to add {@link DualDatabaseTestInvocationContextProvider} for the annotated test. */
@Target({TYPE})
@Retention(RUNTIME)
@ExtendWith(DualDatabaseTestInvocationContextProvider.class)
public @interface DualDatabaseTest {}

View File

@@ -0,0 +1,125 @@
// Copyright 2020 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.testing;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
/**
* Implementation of {@link TestTemplateInvocationContextProvider} to execute tests against
* different database. The test annotated with {@link TestTemplate} will be executed twice against
* Datastore and PostgresQL respectively.
*/
class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocationContextProvider {
private static final Namespace NAMESPACE =
Namespace.create(DualDatabaseTestInvocationContextProvider.class);
private static final String INJECTED_TM_SUPPLIER_KEY = "injected_tm_supplier_key";
private static final String ORIGINAL_TM_KEY = "original_tm_key";
@Override
public boolean supportsTestTemplate(ExtensionContext context) {
return true;
}
@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
ExtensionContext context) {
return Stream.of(
createInvocationContext("Test Datastore", TransactionManagerFactory::ofyTm),
createInvocationContext("Test PostgreSQL", TransactionManagerFactory::jpaTm));
}
private TestTemplateInvocationContext createInvocationContext(
String name, Supplier<? extends TransactionManager> tmSupplier) {
return new TestTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return name;
}
@Override
public List<Extension> getAdditionalExtensions() {
return ImmutableList.of(new DatabaseSwitchInvocationContext(tmSupplier));
}
};
}
private static class DatabaseSwitchInvocationContext implements TestInstancePostProcessor {
private Supplier<? extends TransactionManager> tmSupplier;
private DatabaseSwitchInvocationContext(Supplier<? extends TransactionManager> tmSupplier) {
this.tmSupplier = tmSupplier;
}
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context)
throws Exception {
List<Field> appEngineRuleFields =
Stream.of(testInstance.getClass().getFields())
.filter(field -> field.getType().isAssignableFrom(AppEngineRule.class))
.collect(toImmutableList());
if (appEngineRuleFields.size() != 1) {
throw new IllegalStateException(
"@DualDatabaseTest test must have only 1 AppEngineRule field");
}
appEngineRuleFields.get(0).setAccessible(true);
AppEngineRule appEngineRule = (AppEngineRule) appEngineRuleFields.get(0).get(testInstance);
if (!appEngineRule.isWithDatastoreAndCloudSql()) {
throw new IllegalStateException(
"AppEngineRule in @DualDatabaseTest test must set withDatastoreAndCloudSql()");
}
context.getStore(NAMESPACE).put(INJECTED_TM_SUPPLIER_KEY, tmSupplier);
}
}
static void injectTmForDualDatabaseTest(ExtensionContext context) {
if (isDualDatabaseTest(context)) {
context.getStore(NAMESPACE).put(ORIGINAL_TM_KEY, tm());
Supplier<? extends TransactionManager> tmSupplier =
(Supplier<? extends TransactionManager>)
context.getStore(NAMESPACE).get(INJECTED_TM_SUPPLIER_KEY);
TransactionManagerFactory.setTm(tmSupplier.get());
}
}
static void restoreTmAfterDualDatabaseTest(ExtensionContext context) {
if (isDualDatabaseTest(context)) {
TransactionManager original =
(TransactionManager) context.getStore(NAMESPACE).get(ORIGINAL_TM_KEY);
TransactionManagerFactory.setTm(original);
}
}
private static boolean isDualDatabaseTest(ExtensionContext context) {
Object testInstance = context.getTestInstance().orElseThrow(RuntimeException::new);
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class);
}
}

View File

@@ -23,7 +23,6 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.InetAddresses;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactPhoneNumber;
@@ -360,17 +359,17 @@ public final class FullFieldsTestEntityHelper {
StatusValue.SERVER_UPDATE_PROHIBITED))
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, "deadface")));
if (registrant != null) {
builder.setRegistrant(Key.create(registrant));
builder.setRegistrant(registrant.createVKey());
}
if ((admin != null) || (tech != null)) {
ImmutableSet.Builder<DesignatedContact> contactsBuilder = new ImmutableSet.Builder<>();
if (admin != null) {
contactsBuilder.add(DesignatedContact.create(
DesignatedContact.Type.ADMIN, Key.create(admin)));
contactsBuilder.add(
DesignatedContact.create(DesignatedContact.Type.ADMIN, admin.createVKey()));
}
if (tech != null) {
contactsBuilder.add(DesignatedContact.create(
DesignatedContact.Type.TECH, Key.create(tech)));
contactsBuilder.add(
DesignatedContact.create(DesignatedContact.Type.TECH, tech.createVKey()));
}
builder.setContacts(contactsBuilder.build());
}

View File

@@ -24,7 +24,6 @@ import static google.registry.testing.DatastoreHelper.persistDomainAndEnqueueLor
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
import static org.junit.Assert.assertThrows;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.ofy.Ofy;
@@ -63,7 +62,7 @@ public class LordnTaskUtilsTest {
private DomainBase.Builder newDomainBuilder() {
return new DomainBase.Builder()
.setFullyQualifiedDomainName("fleece.example")
.setRegistrant(Key.create(persistActiveContact("jd1234")))
.setRegistrant(persistActiveContact("jd1234").createVKey())
.setSmdId("smdzzzz")
.setCreationClientId("TheRegistrar");
}

View File

@@ -160,19 +160,19 @@ public class EppLifecycleToolsTest extends EppTestCase {
makeOneTimeCreateBillingEvent(domain, createTime),
renewBillingEvent,
// The initial autorenew billing event, which was closed at the time of the explicit renew.
makeRecurringCreateBillingEvent(
makeRecurringBillingEvent(
domain,
getOnlyHistoryEntryOfType(domain, Type.DOMAIN_CREATE),
createTime.plusYears(2),
DateTime.parse("2000-06-07T00:00:00.000Z")),
// The renew's autorenew billing event, which was closed at the time of the unrenew.
makeRecurringCreateBillingEvent(
makeRecurringBillingEvent(
domain,
getOnlyHistoryEntryOfType(domain, Type.DOMAIN_RENEW),
DateTime.parse("2006-06-01T00:02:00.000Z"),
DateTime.parse("2001-06-07T00:00:00.000Z")),
// The remaining active autorenew billing event which was created by the unrenew.
makeRecurringCreateBillingEvent(
makeRecurringBillingEvent(
domain,
getOnlyHistoryEntryOfType(domain, Type.SYNTHETIC),
DateTime.parse("2003-06-01T00:02:00.000Z"),

View File

@@ -23,7 +23,6 @@ import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.testing.AppEngineRule;
import google.registry.tools.LevelDbFileBuilder.Property;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
@@ -51,10 +50,7 @@ public class LevelDbFileBuilderTest {
BASE_ID, Property.create("first", 100L), Property.create("second", 200L));
builder.build();
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new FileInputStream(logFile));
ImmutableList<byte[]> records = reader.getRecords();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1);
// Reconstitute an entity, make sure that what we've got is the same as what we started with.
@@ -82,10 +78,7 @@ public class LevelDbFileBuilderTest {
builder.build();
ImmutableList<ComparableEntity> originalEntities = originalEntitiesBuilder.build();
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new FileInputStream(logFile));
ImmutableList<byte[]> records = reader.getRecords();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1000);
int index = 0;
for (byte[] record : records) {

View File

@@ -17,6 +17,10 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.tools.LevelDbUtil.MAX_RECORD;
import static google.registry.tools.LevelDbUtil.addRecord;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
@@ -24,12 +28,9 @@ import google.registry.tools.LevelDbLogReader.ChunkType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
/** LevelDbLogReader tests. */
@RunWith(JUnit4.class)
/** Unit tests of {@link LevelDbLogReader}. */
public final class LevelDbLogReaderTest {
// Size of the test record. Any value < 256 will do.
@@ -54,28 +55,23 @@ public final class LevelDbLogReaderTest {
@Test
public void testSimpleBlock() throws IOException {
TestBlock block = makeBlockOfRepeatingBytes(0);
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new ByteArrayInputStream(block.data));
ImmutableList<byte[]> records = reader.getRecords();
assertThat(records).hasSize(block.recordCount);
assertThat(readIncrementally(block.data)).hasSize(block.recordCount);
}
@Test
public void testLargeRecord() throws IOException {
byte[] block = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block, 0, ChunkType.FIRST, MAX_RECORD, (byte) 1);
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new ByteArrayInputStream(block));
assertThat(reader.getRecords()).isEmpty();
byte[] block0 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block0, 0, ChunkType.FIRST, MAX_RECORD, (byte) 1);
assertThat(readIncrementally(block0)).isEmpty();
addRecord(block, 0, ChunkType.MIDDLE, MAX_RECORD, (byte) 2);
reader.readFrom(new ByteArrayInputStream(block));
assertThat(reader.getRecords()).isEmpty();
byte[] block1 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block1, 0, ChunkType.MIDDLE, MAX_RECORD, (byte) 2);
assertThat(readIncrementally(block0, block1)).isEmpty();
addRecord(block, 0, ChunkType.LAST, MAX_RECORD, (byte) 3);
reader.readFrom(new ByteArrayInputStream(block));
byte[] block2 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block2, 0, ChunkType.LAST, MAX_RECORD, (byte) 3);
List<byte[]> records = reader.getRecords();
List<byte[]> records = readIncrementally(block0, block1, block2);
assertThat(records).hasSize(1);
byte[] record = records.get(0);
@@ -95,11 +91,24 @@ public final class LevelDbLogReaderTest {
public void readFromMultiBlockStream() throws IOException {
TestBlock block0 = makeBlockOfRepeatingBytes(0);
TestBlock block1 = makeBlockOfRepeatingBytes(138);
ByteArrayInputStream source = new ByteArrayInputStream(Bytes.concat(block0.data, block1.data));
assertThat(readIncrementally(block0.data, block1.data))
.hasSize(block0.recordCount + block1.recordCount);
}
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(source);
assertThat(reader.getRecords()).hasSize(block0.recordCount + block1.recordCount);
@Test
void read_noData() throws IOException {
assertThat(readIncrementally(new byte[0])).isEmpty();
}
@Test
void read_failBadFirstBlock() {
assertThrows(IllegalStateException.class, () -> readIncrementally(new byte[1]));
}
@Test
void read_failBadTrailingBlock() {
TestBlock block = makeBlockOfRepeatingBytes(0);
assertThrows(IllegalStateException.class, () -> readIncrementally(block.data, new byte[2]));
}
@Test
@@ -112,6 +121,14 @@ public final class LevelDbLogReaderTest {
assertThat(ChunkType.fromCode(ChunkType.LAST.getCode())).isEqualTo(ChunkType.LAST);
}
@SafeVarargs
private static ImmutableList<byte[]> readIncrementally(byte[]... blocks) throws IOException {
ByteArrayInputStream source = spy(new ByteArrayInputStream(Bytes.concat(blocks)));
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(source));
verify(source, times(1)).close();
return records;
}
/** Aggregates the bytes of a test block with the record count. */
private static final class TestBlock {
final byte[] data;

View File

@@ -76,7 +76,7 @@ public class RecordAccumulatorTest {
builder.build();
ImmutableSet<ComparableEntity> entities =
new RecordAccumulator().readDirectory(subdir, any -> true).getComparableEntitySet();
RecordAccumulator.readDirectory(subdir, any -> true).getComparableEntitySet();
assertThat(entities).containsExactly(e1, e2, e3);
}
}

View File

@@ -26,7 +26,6 @@ import static org.junit.Assert.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact;
import google.registry.model.eppcommon.StatusValue;
@@ -187,10 +186,10 @@ public class UpdateDomainCommandTest extends EppToolCommandTestCase<UpdateDomain
ContactResource adminContact2 = persistResource(newContactResource("crr-admin2"));
ContactResource techContact1 = persistResource(newContactResource("crr-tech1"));
ContactResource techContact2 = persistResource(newContactResource("crr-tech2"));
Key<ContactResource> adminResourceKey1 = Key.create(adminContact1);
Key<ContactResource> adminResourceKey2 = Key.create(adminContact2);
Key<ContactResource> techResourceKey1 = Key.create(techContact1);
Key<ContactResource> techResourceKey2 = Key.create(techContact2);
VKey<ContactResource> adminResourceKey1 = adminContact1.createVKey();
VKey<ContactResource> adminResourceKey2 = adminContact2.createVKey();
VKey<ContactResource> techResourceKey1 = techContact1.createVKey();
VKey<ContactResource> techResourceKey2 = techContact2.createVKey();
persistResource(
newDomainBase("example.tld")

View File

@@ -23,7 +23,6 @@ import static google.registry.whois.WhoisTestData.loadFile;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.contact.ContactAddress;
import google.registry.model.contact.ContactPhoneNumber;
import google.registry.model.contact.ContactResource;
@@ -223,9 +222,9 @@ public class DomainWhoisResponseTest {
VKey<HostResource> hostResource1Key = hostResource1.createKey();
VKey<HostResource> hostResource2Key = hostResource2.createKey();
Key<ContactResource> registrantResourceKey = Key.create(registrant);
Key<ContactResource> adminResourceKey = Key.create(adminContact);
Key<ContactResource> techResourceKey = Key.create(techContact);
VKey<ContactResource> registrantResourceKey = registrant.createVKey();
VKey<ContactResource> adminResourceKey = adminContact.createVKey();
VKey<ContactResource> techResourceKey = techContact.createVKey();
domainBase =
persistResource(

View File

@@ -0,0 +1,35 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:roid>8-TLD</domain:roid>
<domain:status s="inactive"/>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>NewRegistrar</domain:crID>
<domain:crDate>%CRDATE%</domain:crDate>
<domain:exDate>%EXDATE%</domain:exDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<extension>
<rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:rgpStatus s="addPeriod"/>
</rgp:infData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,37 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:roid>8-TLD</domain:roid>
<domain:status s="inactive"/>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>NewRegistrar</domain:crID>
<domain:crDate>2000-06-01T00:02:00Z</domain:crDate>
<domain:upID>NewRegistrar</domain:upID>
<domain:upDate>%UPDATE%</domain:upDate>
<domain:exDate>%EXDATE%</domain:exDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<extension>
<rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:rgpStatus s="%GRACEPERIOD%"/>
</rgp:infData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,38 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:roid>8-TLD</domain:roid>
<domain:status s="inactive"/>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>NewRegistrar</domain:crID>
<domain:crDate>2000-06-01T00:02:00Z</domain:crDate>
<domain:upID>NewRegistrar</domain:upID>
<domain:upDate>%UPDATE%</domain:upDate>
<domain:exDate>%EXDATE%</domain:exDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<extension>
<rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:rgpStatus s="renewPeriod"/>
<rgp:rgpStatus s="addPeriod"/>
</rgp:infData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -0,0 +1,38 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:infData
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>%DOMAIN%</domain:name>
<domain:roid>8-TLD</domain:roid>
<domain:status s="pendingDelete"/>
<domain:status s="inactive"/>
<domain:registrant>jd1234</domain:registrant>
<domain:contact type="admin">sh8013</domain:contact>
<domain:contact type="tech">sh8013</domain:contact>
<domain:clID>NewRegistrar</domain:clID>
<domain:crID>NewRegistrar</domain:crID>
<domain:crDate>%CRDATE%</domain:crDate>
<domain:upID>NewRegistrar</domain:upID>
<domain:upDate>%UPDATE%</domain:upDate>
<domain:exDate>%EXDATE%</domain:exDate>
<domain:authInfo>
<domain:pw>2fooBAR</domain:pw>
</domain:authInfo>
</domain:infData>
</resData>
<extension>
<rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
<rgp:rgpStatus s="redemptionPeriod"/>
</rgp:infData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>

View File

@@ -179,11 +179,11 @@ class google.registry.model.domain.DomainBase {
java.lang.String lastEppUpdateClientId;
java.lang.String smdId;
java.lang.String tld;
java.util.Set<com.googlecode.objectify.Key<google.registry.model.host.HostResource>> nsHosts;
java.util.Set<google.registry.model.domain.DesignatedContact> allContacts;
java.util.Set<google.registry.model.domain.GracePeriod> gracePeriods;
java.util.Set<google.registry.model.domain.secdns.DelegationSignerData> dsData;
java.util.Set<google.registry.model.eppcommon.StatusValue> status;
java.util.Set<google.registry.persistence.VKey<google.registry.model.host.HostResource>> nsHosts;
java.util.Set<java.lang.String> subordinateHosts;
org.joda.time.DateTime deletionTime;
org.joda.time.DateTime lastEppUpdateTime;

View File

@@ -0,0 +1,38 @@
-- Copyright 2020 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.
ALTER TABLE "Domain" ADD COLUMN admin_contact text;
ALTER TABLE "Domain" ADD COLUMN billing_contact text;
ALTER TABLE "Domain" ADD COLUMN registrant_contact text;
ALTER TABLE "Domain" ADD COLUMN tech_contact text;
alter table if exists "Domain"
add constraint fk_domain_admin_contact
foreign key (admin_contact)
references "Contact";
alter table if exists "Domain"
add constraint fk_domain_billing_contact
foreign key (billing_contact)
references "Contact";
alter table if exists "Domain"
add constraint fk_domain_registrant_contact
foreign key (registrant_contact)
references "Contact";
alter table if exists "Domain"
add constraint fk_domain_tech_contact
foreign key (tech_contact)
references "Contact";

View File

@@ -0,0 +1,22 @@
-- Copyright 2020 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.
ALTER TABLE "DomainHost" DROP CONSTRAINT FK_DomainHost_host_valid;
ALTER TABLE "DomainHost" ADD COLUMN ns_hosts text;
ALTER TABLE "DomainHost" DROP COLUMN ns_host_v_keys;
ALTER TABLE "DomainHost"
ADD CONSTRAINT FK_DomainHost_host_valid
FOREIGN KEY (ns_hosts)
REFERENCES "HostResource";

View File

@@ -100,8 +100,10 @@
last_epp_update_client_id text,
last_epp_update_time timestamptz,
statuses text[],
admin_contact text,
auth_info_repo_id text,
auth_info_value text,
billing_contact text,
fully_qualified_domain_name text,
idn_table_name text,
last_transfer_time timestamptz,
@@ -109,16 +111,18 @@
launch_notice_expiration_time timestamptz,
launch_notice_tcn_id text,
launch_notice_validator_id text,
registrant_contact text,
registration_expiration_time timestamptz,
smd_id text,
subordinate_hosts text[],
tech_contact text,
tld text,
primary key (repo_id)
);
create table "DomainHost" (
domain_repo_id text not null,
ns_host_v_keys text
ns_hosts text
);
create table "GracePeriod" (

View File

@@ -165,7 +165,11 @@ CREATE TABLE public."Domain" (
registration_expiration_time timestamp with time zone,
smd_id text,
subordinate_hosts text[],
tld text
tld text,
admin_contact text,
billing_contact text,
registrant_contact text,
tech_contact text
);
@@ -175,7 +179,7 @@ CREATE TABLE public."Domain" (
CREATE TABLE public."DomainHost" (
domain_repo_id text NOT NULL,
ns_host_v_keys text
ns_hosts text
);
@@ -747,12 +751,44 @@ ALTER TABLE ONLY public."Contact"
ADD CONSTRAINT fk93c185fx7chn68uv7nl6uv2s0 FOREIGN KEY (current_sponsor_client_id) REFERENCES public."Registrar"(client_id);
--
-- Name: Domain fk_domain_admin_contact; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT fk_domain_admin_contact FOREIGN KEY (admin_contact) REFERENCES public."Contact"(repo_id);
--
-- Name: Domain fk_domain_billing_contact; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT fk_domain_billing_contact FOREIGN KEY (billing_contact) REFERENCES public."Contact"(repo_id);
--
-- Name: Domain fk_domain_registrant_contact; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT fk_domain_registrant_contact FOREIGN KEY (registrant_contact) REFERENCES public."Contact"(repo_id);
--
-- Name: Domain fk_domain_tech_contact; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."Domain"
ADD CONSTRAINT fk_domain_tech_contact FOREIGN KEY (tech_contact) REFERENCES public."Contact"(repo_id);
--
-- Name: DomainHost fk_domainhost_host_valid; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainHost"
ADD CONSTRAINT fk_domainhost_host_valid FOREIGN KEY (ns_host_v_keys) REFERENCES public."HostResource"(repo_id);
ADD CONSTRAINT fk_domainhost_host_valid FOREIGN KEY (ns_hosts) REFERENCES public."HostResource"(repo_id);
--

View File

@@ -52,7 +52,7 @@ Updated 1 entities.
* `create_tld` is the subcommand to create a TLD. The TLD name is "example"
which happens to be an ICANN reserved string, and therefore "example" can
never be created on the Internet at large.
* `--initial_tld_state` defines the intital state of the TLD.
* `--initial_tld_state` defines the initial state of the TLD.
`GENERAL_AVAILABILITY`, in the case of our example, allows you to
immediately create domain names by bypassing the sunrise and landrush domain
registration periods.
@@ -168,7 +168,7 @@ $ nomulus -e alpha create_domain fake.example --client acme --admins abcd1234 \
Where:
* `create_domain` is the subcommand to create a domain name. It accepts a
whitespace-separted list of domain names to be created
whitespace-separated list of domain names to be created
* `--client` is used to define the registrar.
* `--admins` is the administrative contact's id(s).
* `--techs` is the technical contact's id(s).

View File

@@ -523,7 +523,7 @@ $ kubectl create secret generic service-account \
--from-file=service-account-key.json=<service-account-key.json>
```
More details on using sevice account on GKE can be found
More details on using service account on GKE can be found
[here](https://cloud.google.com/kubernetes-engine/docs/tutorials/authenticating-to-cloud-platform).
Repeat the same step for all clusters you want to deploy to. Use `gcloud` to

View File

@@ -138,7 +138,7 @@ No, we do not require verification of zone configurations.
Yes, IPv6 addresses are supported.
**4.9 How many IP adresses can be listed on an in-bailiwick host object?**
**4.9 How many IP addresses can be listed on an in-bailiwick host object?**
Ten IP addresses can be listed on an in-bailiwick host object. This limit is for
both IPv4 and IPv6 addresses combined.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath

1
gradlew.bat vendored
View File

@@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

View File

@@ -37,6 +37,7 @@ import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.function.Supplier;
import javax.net.ssl.SSLSession;
/**
* Adds a server side SSL handler to the channel pipeline.
@@ -108,9 +109,21 @@ public class SslServerInitializer<C extends Channel> extends ChannelInitializer<
.addListener(
future -> {
if (future.isSuccess()) {
SSLSession sslSession = sslHandler.engine().getSession();
X509Certificate clientCertificate =
(X509Certificate)
sslHandler.engine().getSession().getPeerCertificates()[0];
(X509Certificate) sslSession.getPeerCertificates()[0];
logger.atInfo().log(
"--SSL Information--\n"
+ "Client Certificate Hash: %s\n"
+ "SSL Protocol: %s\n"
+ "Cipher Suite: %s\n"
+ "Not Before: %s\n"
+ "Not After: %s\n",
getCertificateHash(clientCertificate),
sslSession.getProtocol(),
sslSession.getCipherSuite(),
clientCertificate.getNotBefore(),
clientCertificate.getNotAfter());
try {
clientCertificate.checkValidity();
} catch (CertificateNotYetValidException | CertificateExpiredException e) {

12
package-lock.json generated
View File

@@ -473,9 +473,9 @@
"dev": true
},
"eventemitter3": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true
},
"extend": {
@@ -673,9 +673,9 @@
}
},
"http-proxy": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",