mirror of
https://github.com/google/nomulus
synced 2026-05-24 08:41:48 +00:00
Compare commits
17 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f1357d83 | ||
|
|
c73d154084 | ||
|
|
259d2e2cdc | ||
|
|
0f174d9ce0 | ||
|
|
ca2edb6a17 | ||
|
|
3947ac6ef7 | ||
|
|
579a3d0ac1 | ||
|
|
5fe929b027 | ||
|
|
fb335b7d89 | ||
|
|
a0f4013d53 | ||
|
|
5e596bb389 | ||
|
|
f62fd82803 | ||
|
|
b7353ef338 | ||
|
|
832e1ce047 | ||
|
|
8087f5bbca | ||
|
|
7f3dbfb62f | ||
|
|
04f429c4d6 |
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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")))
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
22
db/src/main/resources/sql/flyway/V25__rename_vkey_fields.sql
Normal file
22
db/src/main/resources/sql/flyway/V25__rename_vkey_fields.sql
Normal 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";
|
||||
|
||||
@@ -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" (
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
--
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
2
gradlew
vendored
@@ -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
1
gradlew.bat
vendored
@@ -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%
|
||||
|
||||
|
||||
@@ -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
12
package-lock.json
generated
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user