1
0
mirror of https://github.com/google/nomulus synced 2026-05-17 13:21:48 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
Ben McIlwain
ef9f3aeada Remove a couple unused variables (#913)
* Remove a couple unused variables
2020-12-23 17:19:02 -05:00
Shicong Huang
9c43aab8cd Convert HostCreateFlow and HostCheckFlow to tm() (#910) 2020-12-22 21:02:02 -05:00
Weimin Yu
cb63c3dd80 Add unique constraints on domain_hosts (#911)
* Add unique constraints on domain_hosts

Add unique constraints on DomainHost (child of DomainBase) and
DomainHistoryHost (child of DomainHistory). DomainHost is non-entity
embedded object and Hibernate does not define indexes automatically.

This should improve read and write performance of the parent entities.
2020-12-21 18:22:24 -05:00
Ben McIlwain
2cf190e448 Add a fast mode to the ResaveAllEppResourcesAction mapreduce (#912)
* Add a fast mode to the ResaveAllEppResourcesAction mapreduce

This new mode avoids writing no-op mutations for entities that don't actually
have any changes to write. The cronjobs use fast mode by default, but manual
invocations do not, as manual invocations are often used to trigger @OnLoad
migrations, and fast mode won't pick up on those changes.
2020-12-21 18:07:59 -05:00
gbrodman
e550c94cbc Convert AllocationToken-related classes to tm() (#909)
* Convert AllocationToken-related classes to tm()

For the most part this is a fairly simple converstion -- changing Key
references to VKey references, using JPA transactions when necessary,
and using the TransactionManager interface. There's a bit of cleanup too
in related code
2020-12-21 15:56:49 -05:00
Shicong Huang
6e2bbd1a7e Add BillingVKey to restore symmetric VKey in GracePeriodBase (#902)
* Use PollMessageVKey to replace VKey<PollMessage> in DomainBase

* Revert changes to DomainContent

* Use BillingVKey in GracePeriodBase to restore symmetric vkey

* Rebase on HEAD
2020-12-17 14:13:47 -05:00
Weimin Yu
495d7176d8 Validate SQL credentials in Secret Manager (#907)
* Validate SQL credentials in Secret Manager

Load SQL credentials from the SecretManager and compare them with the
ones currently in use in Nomulus server, beam pipeline, and the registry
tool. Normal operations are not affected by failures related to the
SecretManager, be it IOException, insufficient permission , or wrong or
missing credential.

The appengine and compute engine default service accounts must be
granted the permission to access the secret data. In the short term, we
will grant the secretmanager.secretAccessor role to these accounts. In
the long term, with the proposed privilege service, access will be granted
on per-secret basis.
2020-12-16 10:57:03 -05:00
Michael Muller
d7aab524e5 Make config/presubmits.py use explicit encodings (#908)
For some reason, our docker build image has started using a non-utf8 default
encoding.  Specify the encoding explicitly on python "open()" to override.

Note that this might not entirely fix the build: it's possible that this
problem may affect other portions of the build.
2020-12-16 10:03:32 -05:00
74 changed files with 3929 additions and 3052 deletions

View File

@@ -65,7 +65,7 @@ class PresubmitCheck:
for pattern in self.skipped_patterns:
if pattern in file:
return False
with open(file, "r") as f:
with open(file, "r", encoding='utf8') as f:
file_content = f.read()
matches = re.match(self.regex, file_content, re.DOTALL)
if self.regex_type == FORBIDDEN:
@@ -241,7 +241,7 @@ def verify_flyway_index():
# Remove the sequence numbers and compare against the index file contents.
files = [filename[1] for filename in sorted(files)]
with open('db/src/main/resources/sql/flyway.txt') as index:
with open('db/src/main/resources/sql/flyway.txt', encoding='utf8') as index:
indexed_files = index.read().splitlines()
if files != indexed_files:
unindexed = set(files) - set(indexed_files)

View File

@@ -14,6 +14,7 @@
package google.registry.batch;
import static google.registry.mapreduce.MapreduceRunner.PARAM_FAST;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -24,6 +25,7 @@ import google.registry.mapreduce.MapreduceRunner;
import google.registry.mapreduce.inputs.EppResourceInputs;
import google.registry.model.EppResource;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import javax.inject.Inject;
@@ -39,6 +41,14 @@ import javax.inject.Inject;
* <p>Because there are no auth settings in the {@link Action} annotation, this command can only be
* run internally, or by pretending to be internal by setting the X-AppEngine-QueueName header,
* which only admin users can do.
*
* <p>If the <code>?fast=true</code> querystring parameter is passed, then entities that are not
* changed by {@link EppResource#cloneProjectedAtTime} will not be re-saved. This helps prevent
* mutation load on the DB and has the beneficial side effect of writing out smaller commit logs.
* Note that this does NOT pick up mutations caused by migrations using the {@link
* com.googlecode.objectify.annotation.OnLoad} annotation, so if you are running a one-off schema
* migration, do not use fast mode. Fast mode defaults to false for this reason, but is used by the
* monthly invocation of the mapreduce.
*/
@Action(
service = Action.Service.BACKEND,
@@ -48,7 +58,13 @@ public class ResaveAllEppResourcesAction implements Runnable {
@Inject MapreduceRunner mrRunner;
@Inject Response response;
@Inject ResaveAllEppResourcesAction() {}
@Inject
@Parameter(PARAM_FAST)
boolean isFast;
@Inject
ResaveAllEppResourcesAction() {}
/**
* The number of shards to run the map-only mapreduce on.
@@ -66,7 +82,7 @@ public class ResaveAllEppResourcesAction implements Runnable {
.setModuleName("backend")
.setDefaultMapShards(NUM_SHARDS)
.runMapOnly(
new ResaveAllEppResourcesActionMapper(),
new ResaveAllEppResourcesActionMapper(isFast),
ImmutableList.of(EppResourceInputs.createKeyInput(EppResource.class)))
.sendLinkToMapreduceConsole(response);
}
@@ -76,23 +92,33 @@ public class ResaveAllEppResourcesAction implements Runnable {
extends Mapper<Key<EppResource>, Void, Void> {
private static final long serialVersionUID = -7721628665138087001L;
public ResaveAllEppResourcesActionMapper() {}
private final boolean isFast;
ResaveAllEppResourcesActionMapper(boolean isFast) {
this.isFast = isFast;
}
@Override
public final void map(final Key<EppResource> resourceKey) {
tm()
.transact(
() -> {
EppResource projectedResource =
ofy()
.load()
.key(resourceKey)
.now()
.cloneProjectedAtTime(tm().getTransactionTime());
ofy().save().entity(projectedResource).now();
});
getContext().incrementCounter(String.format("%s entities re-saved", resourceKey.getKind()));
boolean resaved =
tm().transact(
() -> {
EppResource originalResource = ofy().load().key(resourceKey).now();
EppResource projectedResource =
originalResource.cloneProjectedAtTime(tm().getTransactionTime());
if (isFast && originalResource.equals(projectedResource)) {
return false;
} else {
ofy().save().entity(projectedResource).now();
return true;
}
});
getContext()
.incrementCounter(
String.format(
"%s entities %s",
resourceKey.getKind(), resaved ? "re-saved" : "with no changes skipped"));
}
}
}

View File

@@ -31,6 +31,7 @@ import google.registry.persistence.PersistenceModule;
import google.registry.persistence.PersistenceModule.JdbcJpaTm;
import google.registry.persistence.PersistenceModule.SocketFactoryJpaTm;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.util.UtilsModule;
import java.io.BufferedReader;
import java.io.IOException;
@@ -168,6 +169,7 @@ public class BeamJpaModule {
BeamJpaModule.class,
KmsModule.class,
PersistenceModule.class,
SecretManagerModule.class,
UtilsModule.class
})
public interface JpaTransactionManagerComponent {

View File

@@ -43,7 +43,6 @@ import google.registry.backup.CommitLogImports;
import google.registry.backup.VersionedEntity;
import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.ObjectifyService;
import google.registry.model.ofy.Ofy;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.tools.LevelDbLogReader;
import google.registry.util.SystemSleeper;
@@ -432,7 +431,6 @@ public final class Transforms {
private final SerializableSupplier<JpaTransactionManager> jpaSupplier;
private final SerializableFunction<T, Object> jpaConverter;
private transient Ofy ofy;
private transient SystemSleeper sleeper;
SqlBatchWriter(
@@ -448,7 +446,6 @@ public final class Transforms {
try (AppEngineEnvironment env = new AppEngineEnvironment()) {
ObjectifyService.initOfy();
ofy = ObjectifyService.ofy();
}
synchronized (SqlBatchWriter.class) {

View File

@@ -80,7 +80,7 @@
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResources]]></url>
<url><![CDATA[/_dr/task/resaveAllEppResources?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.

View File

@@ -103,7 +103,7 @@
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResources]]></url>
<url><![CDATA[/_dr/task/resaveAllEppResources?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.

View File

@@ -21,7 +21,7 @@
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResources]]></url>
<url><![CDATA[/_dr/task/resaveAllEppResources?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.

View File

@@ -87,7 +87,7 @@
</cron>
<cron>
<url><![CDATA[/_dr/task/resaveAllEppResources]]></url>
<url><![CDATA[/_dr/task/resaveAllEppResources?fast=true]]></url>
<description>
This job resaves all our resources, projected in time to "now".
It is needed for "deleteOldCommitLogs" to work correctly.

View File

@@ -110,7 +110,6 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.persistence.VKey;
import google.registry.tmch.LordnTaskUtils;
import java.util.Optional;
@@ -372,7 +371,7 @@ public class DomainCreateFlow implements TransactionalFlow {
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
allocationTokenFlowUtils.redeemToken(
allocationToken.get(), DomainHistoryVKey.create(Key.create(historyEntry))));
allocationToken.get(), HistoryEntry.createVKey(Key.create(historyEntry))));
}
enqueueTasks(newDomain, hasSignedMarks, hasClaimsNotice);

View File

@@ -15,14 +15,13 @@
package google.registry.flows.domain.token;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.Key;
import google.registry.flows.EppException;
import google.registry.flows.EppException.AssociationProhibitsOperationException;
import google.registry.flows.EppException.AuthorizationErrorException;
@@ -32,7 +31,8 @@ import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.registry.Registry;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
@@ -107,7 +107,7 @@ public class AllocationTokenFlowUtils {
/** Redeems a SINGLE_USE {@link AllocationToken}, returning the redeemed copy. */
public AllocationToken redeemToken(
AllocationToken token, DomainHistoryVKey redemptionHistoryEntry) {
AllocationToken token, VKey<? extends HistoryEntry> redemptionHistoryEntry) {
checkArgument(
TokenType.SINGLE_USE.equals(token.getTokenType()),
"Only SINGLE_USE tokens can be marked as redeemed");
@@ -152,14 +152,15 @@ public class AllocationTokenFlowUtils {
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
throw new InvalidAllocationTokenException();
}
AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now();
if (tokenEntity == null) {
Optional<AllocationToken> maybeTokenEntity =
tm().maybeLoad(VKey.create(AllocationToken.class, token));
if (maybeTokenEntity.isEmpty()) {
throw new InvalidAllocationTokenException();
}
if (tokenEntity.isRedeemed()) {
if (maybeTokenEntity.get().isRedeemed()) {
throw new AlreadyRedeemedAllocationTokenException();
}
return tokenEntity;
return maybeTokenEntity.get();
}
// Note: exception messages should be <= 32 characters long for domain check results

View File

@@ -21,10 +21,8 @@ import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.union;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
@@ -137,13 +135,11 @@ public final class HostCreateFlow implements TransactionalFlow {
ImmutableSet<ImmutableObject> entitiesToSave =
ImmutableSet.of(
newHost,
historyBuilder.build(),
historyBuilder.build().toChildHistoryEntity(),
ForeignKeyIndex.create(newHost, newHost.getDeletionTime()),
EppResourceIndex.create(Key.create(newHost)));
if (superordinateDomain.isPresent()) {
entitiesToSave =
union(
entitiesToSave,
tm().update(
superordinateDomain
.get()
.asBuilder()
@@ -153,7 +149,7 @@ public final class HostCreateFlow implements TransactionalFlow {
// they are only written as NS records from the referencing domain.
dnsQueue.addHostRefreshTask(targetId);
}
ofy().save().entities(entitiesToSave);
tm().insertAll(entitiesToSave);
return responseBuilder.setResData(HostCreateData.create(targetId, now)).build();
}

View File

@@ -15,6 +15,7 @@
package google.registry.mapreduce;
import static google.registry.mapreduce.MapreduceRunner.PARAM_DRY_RUN;
import static google.registry.mapreduce.MapreduceRunner.PARAM_FAST;
import static google.registry.mapreduce.MapreduceRunner.PARAM_MAP_SHARDS;
import static google.registry.mapreduce.MapreduceRunner.PARAM_REDUCE_SHARDS;
import static google.registry.request.RequestParameters.extractBooleanParameter;
@@ -36,6 +37,12 @@ public final class MapreduceModule {
return extractBooleanParameter(req, PARAM_DRY_RUN);
}
@Provides
@Parameter(PARAM_FAST)
static boolean provideIsFast(HttpServletRequest req) {
return extractBooleanParameter(req, PARAM_FAST);
}
@Provides
@Parameter(PARAM_MAP_SHARDS)
static Optional<Integer> provideMapShards(HttpServletRequest req) {

View File

@@ -55,6 +55,7 @@ public class MapreduceRunner {
public static final String PARAM_DRY_RUN = "dryRun";
public static final String PARAM_MAP_SHARDS = "mapShards";
public static final String PARAM_REDUCE_SHARDS = "reduceShards";
public static final String PARAM_FAST = "fast";
private static final String BASE_URL = "/_dr/mapreduce/";
private static final String QUEUE_NAME = "mapreduce";

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.latestOf;
@@ -135,7 +136,7 @@ public final class EppResourceUtils {
useCache
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
.getOrDefault(foreignKey, null)
: ofy().load().type(ForeignKeyIndex.mapToFkiClass(clazz)).id(foreignKey).now();
: ForeignKeyIndex.load(clazz, foreignKey, now);
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
return Optional.empty();
@@ -143,7 +144,7 @@ public final class EppResourceUtils {
T resource =
useCache
? EppResource.loadCached(fki.getResourceKey())
: tm().maybeLoad(fki.getResourceKey()).orElse(null);
: transactIfJpaTm(() -> tm().maybeLoad(fki.getResourceKey()).orElse(null));
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
return Optional.empty();
}

View File

@@ -22,7 +22,7 @@ import javax.annotation.Nullable;
import org.joda.time.DateTime;
/**
* A timestamp that auto-updates on each save to Datastore.
* A timestamp that auto-updates on each save to Datastore/Cloud SQL.
*
* @see UpdateAutoTimestampTranslatorFactory
*/
@@ -31,7 +31,7 @@ public class UpdateAutoTimestamp extends ImmutableObject {
// When set to true, database converters/translators should do tha auto update. When set to
// false, auto update should be suspended (this exists to allow us to preserve the original value
// during a replay).
static ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
private static ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
DateTime timestamp;

View File

@@ -75,7 +75,9 @@ public class DomainBase extends DomainContent
}
@ElementCollection
@JoinTable(name = "DomainHost")
@JoinTable(
name = "DomainHost",
indexes = {@Index(columnList = "domain_repo_id,host_repo_id", unique = true)})
@Access(AccessType.PROPERTY)
@Column(name = "host_repo_id")
public Set<VKey<HostResource>> getNsHosts() {

View File

@@ -96,7 +96,14 @@ public class DomainHistory extends HistoryEntry implements SqlEntity {
// TODO(b/166776754): Investigate if we can reuse domainContent.nsHosts for storing host keys.
@Ignore
@ElementCollection
@JoinTable(name = "DomainHistoryHost")
@JoinTable(
name = "DomainHistoryHost",
indexes = {
@Index(
columnList =
"domain_history_history_revision_id,domain_history_domain_repo_id,host_repo_id",
unique = true),
})
@Column(name = "host_repo_id")
Set<VKey<HostResource>> nsHosts;

View File

@@ -23,6 +23,8 @@ import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.ofy.ObjectifyService;
import google.registry.persistence.BillingVKey.BillingEventVKey;
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
import google.registry.persistence.VKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import javax.annotation.Nullable;
@@ -82,10 +84,8 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
instance.domainRepoId = checkArgumentNotNull(domainRepoId);
instance.expirationTime = checkArgumentNotNull(expirationTime);
instance.clientId = checkArgumentNotNull(clientId);
instance.billingEventOneTime = billingEventOneTime;
instance.billingEventOneTimeHistoryId = DomainBase.getHistoryId(billingEventOneTime);
instance.billingEventRecurring = billingEventRecurring;
instance.billingEventRecurringHistoryId = DomainBase.getHistoryId(billingEventRecurring);
instance.billingEventOneTime = BillingEventVKey.create(billingEventOneTime);
instance.billingEventRecurring = BillingRecurrenceVKey.create(billingEventRecurring);
return instance;
}
@@ -178,7 +178,6 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
public GracePeriod cloneAfterOfyLoad(String domainRepoId) {
GracePeriod clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
clone.restoreHistoryIds();
return clone;
}
@@ -190,7 +189,7 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
*/
public GracePeriod cloneWithRecurringBillingEvent(VKey<BillingEvent.Recurring> recurring) {
GracePeriod clone = clone(this);
clone.billingEventRecurring = recurring;
clone.billingEventRecurring = BillingRecurrenceVKey.create(recurring);
return clone;
}
@@ -232,9 +231,7 @@ public class GracePeriod extends GracePeriodBase implements DatastoreAndSqlEntit
instance.expirationTime = gracePeriod.expirationTime;
instance.clientId = gracePeriod.clientId;
instance.billingEventOneTime = gracePeriod.billingEventOneTime;
instance.billingEventOneTimeHistoryId = gracePeriod.billingEventOneTimeHistoryId;
instance.billingEventRecurring = gracePeriod.billingEventRecurring;
instance.billingEventRecurringHistoryId = gracePeriod.billingEventRecurringHistoryId;
return instance;
}
}

View File

@@ -14,14 +14,14 @@
package google.registry.model.domain;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Ignore;
import google.registry.model.ImmutableObject;
import google.registry.model.ModelUtils;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.persistence.BillingVKey.BillingEventVKey;
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
import google.registry.persistence.VKey;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
@@ -68,24 +68,16 @@ public class GracePeriodBase extends ImmutableObject {
* billingEventRecurring}) or for redemption grace periods (since deletes have no cost).
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_event_id")
VKey<OneTime> billingEventOneTime = null;
@Ignore
@Column(name = "billing_event_history_id")
Long billingEventOneTimeHistoryId;
@Access(AccessType.FIELD)
BillingEventVKey billingEventOneTime = null;
/**
* The recurring billing event corresponding to the action that triggered this grace period, if
* applicable - i.e. if the action was an autorenew - or null in all other cases.
*/
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
@Column(name = "billing_recurrence_id")
VKey<BillingEvent.Recurring> billingEventRecurring = null;
@Ignore
@Column(name = "billing_recurrence_history_id")
Long billingEventRecurringHistoryId;
@Access(AccessType.FIELD)
BillingRecurrenceVKey billingEventRecurring = null;
public long getGracePeriodId() {
return gracePeriodId;
@@ -123,8 +115,7 @@ public class GracePeriodBase extends ImmutableObject {
* period is not AUTO_RENEW.
*/
public VKey<BillingEvent.OneTime> getOneTimeBillingEvent() {
restoreOfyKeys();
return billingEventOneTime;
return billingEventOneTime == null ? null : billingEventOneTime.createVKey();
}
/**
@@ -132,18 +123,7 @@ public class GracePeriodBase extends ImmutableObject {
* period is AUTO_RENEW.
*/
public VKey<BillingEvent.Recurring> getRecurringBillingEvent() {
restoreOfyKeys();
return billingEventRecurring;
}
/**
* Restores history ids for composite VKeys after a load from datastore.
*
* <p>For use by DomainContent.load() ONLY.
*/
protected void restoreHistoryIds() {
billingEventOneTimeHistoryId = DomainBase.getHistoryId(billingEventOneTime);
billingEventRecurringHistoryId = DomainBase.getHistoryId(billingEventRecurring);
return billingEventRecurring == null ? null : billingEventRecurring.createVKey();
}
/**
@@ -152,7 +132,6 @@ public class GracePeriodBase extends ImmutableObject {
*/
@Override
protected Map<Field, Object> getSignificantFields() {
restoreOfyKeys();
// Can't use streams or ImmutableMap because we can have null values.
Map<Field, Object> result = new LinkedHashMap();
for (Map.Entry<Field, Object> entry : ModelUtils.getFieldValues(this).entrySet()) {
@@ -162,33 +141,4 @@ public class GracePeriodBase extends ImmutableObject {
}
return result;
}
/**
* Restores Ofy keys in the billing events.
*
* <p>This must be called by all methods that access the one time or recurring billing event keys.
* When the billing event keys are loaded from SQL, they are loaded as asymmetric keys because the
* database columns that we load them from do not contain all of the information necessary to
* reconsitute the Ofy side of the key. In other cases, we restore the Ofy key during the
* hibernate {@link javax.persistence.PostLoad} method from the other fields of the object, but we
* have been unable to make this work with hibernate's internal persistence model in this case
* because the {@link GracePeriod}'s hash code is evaluated prior to these calls, and would be
* invalidated by changing the fields.
*/
private final synchronized void restoreOfyKeys() {
if (billingEventOneTime != null && !billingEventOneTime.maybeGetOfyKey().isPresent()) {
billingEventOneTime =
DomainBase.restoreOfyFrom(
Key.create(DomainBase.class, domainRepoId),
billingEventOneTime,
billingEventOneTimeHistoryId);
}
if (billingEventRecurring != null && !billingEventRecurring.maybeGetOfyKey().isPresent()) {
billingEventRecurring =
DomainBase.restoreOfyFrom(
Key.create(DomainBase.class, domainRepoId),
billingEventRecurring,
billingEventRecurringHistoryId);
}
}
}

View File

@@ -111,9 +111,9 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
@Nullable
@Index
@AttributeOverrides({
@AttributeOverride(name = "domainRepoId", column = @Column(name = "redemption_domain_repo_id")),
@AttributeOverride(name = "repoId", column = @Column(name = "redemption_domain_repo_id")),
@AttributeOverride(
name = "domainHistoryId",
name = "historyRevisionId",
column = @Column(name = "redemption_domain_history_id"))
})
DomainHistoryVKey redemptionHistoryEntry;
@@ -192,8 +192,9 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
return token;
}
public Optional<VKey<HistoryEntry>> getRedemptionHistoryEntry() {
return Optional.ofNullable(redemptionHistoryEntry);
public Optional<VKey<? extends HistoryEntry>> getRedemptionHistoryEntry() {
return Optional.ofNullable(
redemptionHistoryEntry == null ? null : redemptionHistoryEntry.createDomainHistoryVKey());
}
public boolean isRedeemed() {
@@ -291,9 +292,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable, Datas
return this;
}
public Builder setRedemptionHistoryEntry(DomainHistoryVKey redemptionHistoryEntry) {
public Builder setRedemptionHistoryEntry(VKey<? extends HistoryEntry> redemptionHistoryEntry) {
checkArgumentNotNull(redemptionHistoryEntry, "Redemption history entry must not be null");
getInstance().redemptionHistoryEntry =
checkArgumentNotNull(redemptionHistoryEntry, "Redemption history entry must not be null");
DomainHistoryVKey.create(redemptionHistoryEntry.getOfyKey());
return this;
}

View File

@@ -15,9 +15,11 @@
package google.registry.model.index;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.TypeUtils.instantiate;
@@ -29,6 +31,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
@@ -44,6 +47,8 @@ import google.registry.model.host.HostResource;
import google.registry.persistence.VKey;
import google.registry.schema.replay.DatastoreOnlyEntity;
import google.registry.util.NonFinalForTesting;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -76,13 +81,21 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
public static class ForeignKeyHostIndex extends ForeignKeyIndex<HostResource>
implements DatastoreOnlyEntity {}
static final ImmutableMap<Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
private static final ImmutableMap<
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
RESOURCE_CLASS_TO_FKI_CLASS =
ImmutableMap.of(
ContactResource.class, ForeignKeyContactIndex.class,
DomainBase.class, ForeignKeyDomainIndex.class,
HostResource.class, ForeignKeyHostIndex.class);
private static final ImmutableMap<Class<? extends EppResource>, String>
RESOURCE_CLASS_TO_FKI_PROPERTY =
ImmutableMap.of(
ContactResource.class, "contactId",
DomainBase.class, "fullyQualifiedDomainName",
HostResource.class, "fullyQualifiedHostName");
@Id String foreignKey;
/**
@@ -179,9 +192,42 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
*/
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
Class<E> clazz, Iterable<String> foreignKeys, final DateTime now) {
return ofy().load().type(mapToFkiClass(clazz)).ids(foreignKeys).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().deletionTime))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
if (tm().isOfy()) {
return ofy().load().type(mapToFkiClass(clazz)).ids(foreignKeys).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().deletionTime))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
} else {
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
List<E> entities =
tm().transact(
() -> {
String entityName =
jpaTm().getEntityManager().getMetamodel().entity(clazz).getName();
return jpaTm()
.getEntityManager()
.createQuery(
String.format(
"FROM %s WHERE %s IN :propertyValue and deletionTime > :now ",
entityName, property),
clazz)
.setParameter("propertyValue", foreignKeys)
.setParameter("now", now)
.getResultList();
});
// We need to find and return the entities with the maximum deletionTime for each foreign key.
return Multimaps.index(entities, EppResource::getForeignKey).asMap().entrySet().stream()
.map(
entry ->
Maps.immutableEntry(
entry.getKey(),
entry.getValue().stream()
.max(Comparator.comparing(EppResource::getDeletionTime))
.get()))
.collect(
toImmutableMap(
Map.Entry::getKey,
entry -> create(entry.getValue(), entry.getValue().getDeletionTime())));
}
}
static final CacheLoader<Key<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
@@ -266,7 +312,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
.filter(entry -> entry.getValue().isPresent())
.filter(entry -> now.isBefore(entry.getValue().get().getDeletionTime()))
.collect(
ImmutableMap.toImmutableMap(
toImmutableMap(
entry -> entry.getKey().getName(),
entry -> (ForeignKeyIndex<E>) entry.getValue().get()));
return fkisFromCache;

View File

@@ -42,8 +42,8 @@ import google.registry.model.translators.CidrAddressBlockTranslatorFactory;
import google.registry.model.translators.CommitLogRevisionsTranslatorFactory;
import google.registry.model.translators.CreateAutoTimestampTranslatorFactory;
import google.registry.model.translators.CurrencyUnitTranslatorFactory;
import google.registry.model.translators.DomainHistoryVKeyTranslatorFactory;
import google.registry.model.translators.DurationTranslatorFactory;
import google.registry.model.translators.EppHistoryVKeyTranslatorFactory;
import google.registry.model.translators.InetAddressTranslatorFactory;
import google.registry.model.translators.ReadableInstantUtcTranslatorFactory;
import google.registry.model.translators.UpdateAutoTimestampTranslatorFactory;
@@ -128,7 +128,7 @@ public class ObjectifyService {
new CreateAutoTimestampTranslatorFactory(),
new CurrencyUnitTranslatorFactory(),
new DurationTranslatorFactory(),
new DomainHistoryVKeyTranslatorFactory(),
new EppHistoryVKeyTranslatorFactory(),
new InetAddressTranslatorFactory(),
new MoneyStringTranslatorFactory(),
new ReadableInstantUtcTranslatorFactory(),

View File

@@ -1,48 +0,0 @@
// 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.model.translators;
import com.google.appengine.api.datastore.Key;
import google.registry.persistence.DomainHistoryVKey;
import javax.annotation.Nullable;
/** Translator factory for {@link DomainHistoryVKey}. */
public class DomainHistoryVKeyTranslatorFactory
extends AbstractSimpleTranslatorFactory<DomainHistoryVKey, Key> {
public DomainHistoryVKeyTranslatorFactory() {
super(DomainHistoryVKey.class);
}
@Override
SimpleTranslator<DomainHistoryVKey, Key> createTranslator() {
return new SimpleTranslator<DomainHistoryVKey, Key>() {
@Nullable
@Override
public DomainHistoryVKey loadValue(@Nullable Key datastoreValue) {
return datastoreValue == null
? null
: DomainHistoryVKey.create(com.googlecode.objectify.Key.create(datastoreValue));
}
@Nullable
@Override
public Key saveValue(@Nullable DomainHistoryVKey pojoValue) {
return pojoValue == null ? null : pojoValue.getOfyKey().getRaw();
}
};
}
}

View File

@@ -0,0 +1,110 @@
// 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.model.translators;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.function.Function.identity;
import com.google.appengine.api.datastore.Key;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.BillingVKey.BillingEventVKey;
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.persistence.EppHistoryVKey;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.annotation.Nullable;
/** Translator factory for {@link EppHistoryVKey}. */
public class EppHistoryVKeyTranslatorFactory
extends AbstractSimpleTranslatorFactory<EppHistoryVKey, Key> {
public EppHistoryVKeyTranslatorFactory() {
super(EppHistoryVKey.class);
}
// This map is used when we need to convert the raw Datastore key to its VKey instance. We have
// one dedicated VKey class, e.g. DomainHistoryVKey, for each such kind of entity, and we need
// a way to map the raw Datastore key to its VKey class. So, we use the kind path as the key of
// the map, and the kind path is created by concatenating all the kind strings in a raw Datastore
// key, e.g. the map key for ContactPollMessageVKey is "ContactResource/HistoryEntry/PollMessage".
@VisibleForTesting
static final ImmutableMap<String, Class<? extends EppHistoryVKey>> kindPathToVKeyClass =
ImmutableSet.of(DomainHistoryVKey.class, BillingEventVKey.class, BillingRecurrenceVKey.class)
.stream()
.collect(toImmutableMap(EppHistoryVKeyTranslatorFactory::getKindPath, identity()));
/**
* Gets the kind path string for the given {@link Class}.
*
* <p>This method calls the getKindPath method on an instance of the given {@link Class} to get
* the kind path string.
*/
private static String getKindPath(Class<? extends EppHistoryVKey> clazz) {
try {
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object instance = constructor.newInstance();
Method getKindPathMethod = EppHistoryVKey.class.getDeclaredMethod("getKindPath");
getKindPathMethod.setAccessible(true);
return (String) getKindPathMethod.invoke(instance);
} catch (Throwable t) {
throw new IllegalStateException(t);
}
}
@Override
SimpleTranslator<EppHistoryVKey, Key> createTranslator() {
return new SimpleTranslator<EppHistoryVKey, Key>() {
@Nullable
@Override
public EppHistoryVKey loadValue(@Nullable Key datastoreValue) {
if (datastoreValue == null) {
return null;
} else {
com.googlecode.objectify.Key<?> ofyKey =
com.googlecode.objectify.Key.create(datastoreValue);
String kindPath = EppHistoryVKey.createKindPath(ofyKey);
if (kindPathToVKeyClass.containsKey(kindPath)) {
Class<? extends EppHistoryVKey> vKeyClass = kindPathToVKeyClass.get(kindPath);
try {
Method createVKeyMethod =
vKeyClass.getDeclaredMethod("create", com.googlecode.objectify.Key.class);
return (EppHistoryVKey) createVKeyMethod.invoke(null, ofyKey);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"Missing static method create(com.googlecode.objectify.Key) on " + vKeyClass);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Error invoking createVKey on " + vKeyClass, e);
}
} else {
throw new IllegalStateException(
"Missing EppHistoryVKey implementation for kind path: " + kindPath);
}
}
}
@Nullable
@Override
public Key saveValue(@Nullable EppHistoryVKey pojoValue) {
return pojoValue == null ? null : pojoValue.createOfyKey().getRaw();
}
};
}
}

View File

@@ -0,0 +1,134 @@
// 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;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.domain.DomainBase;
import google.registry.model.reporting.HistoryEntry;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
/** Base class for {@link BillingEvent}'s {@link VKey}. */
@MappedSuperclass
public abstract class BillingVKey<K> extends EppHistoryVKey<K, DomainBase> {
Long billingId;
// Hibernate requires a default constructor.
BillingVKey() {}
BillingVKey(String repoId, long historyRevisionId, long billingId) {
super(repoId, historyRevisionId);
this.billingId = billingId;
}
Key<HistoryEntry> createHistoryEntryKey() {
return Key.create(Key.create(DomainBase.class, repoId), HistoryEntry.class, historyRevisionId);
}
@Override
public Object createSqlKey() {
return billingId;
}
/** VKey class for {@link BillingEvent.OneTime} that belongs to a {@link DomainBase} entity. */
@Embeddable
@AttributeOverrides({
@AttributeOverride(name = "repoId", column = @Column(name = "billing_event_domain_repo_id")),
@AttributeOverride(
name = "historyRevisionId",
column = @Column(name = "billing_event_history_id")),
@AttributeOverride(name = "billingId", column = @Column(name = "billing_event_id"))
})
public static class BillingEventVKey extends BillingVKey<OneTime> {
// Hibernate requires this default constructor
private BillingEventVKey() {}
private BillingEventVKey(String repoId, long historyRevisionId, long billingEventId) {
super(repoId, historyRevisionId, billingEventId);
}
@Override
public Key<OneTime> createOfyKey() {
return Key.create(createHistoryEntryKey(), BillingEvent.OneTime.class, billingId);
}
/** Creates a {@link BillingEventVKey} instance from the given {@link Key} instance. */
public static BillingEventVKey create(@Nullable Key<BillingEvent.OneTime> ofyKey) {
if (ofyKey == null) {
return null;
}
long billingEventId = ofyKey.getId();
long historyRevisionId = ofyKey.getParent().getId();
String repoId = ofyKey.getParent().getParent().getName();
return new BillingEventVKey(repoId, historyRevisionId, billingEventId);
}
/** Creates a {@link BillingEventVKey} instance from the given {@link VKey} instance. */
public static BillingEventVKey create(@Nullable VKey<BillingEvent.OneTime> vKey) {
return vKey == null ? null : create(vKey.getOfyKey());
}
}
/** VKey class for {@link BillingEvent.Recurring} that belongs to a {@link DomainBase} entity. */
@Embeddable
@AttributeOverrides({
@AttributeOverride(
name = "repoId",
column = @Column(name = "billing_recurrence_domain_repo_id")),
@AttributeOverride(
name = "historyRevisionId",
column = @Column(name = "billing_recurrence_history_id")),
@AttributeOverride(name = "billingId", column = @Column(name = "billing_recurrence_id"))
})
public static class BillingRecurrenceVKey extends BillingVKey<Recurring> {
// Hibernate requires this default constructor
private BillingRecurrenceVKey() {}
private BillingRecurrenceVKey(String repoId, long historyRevisionId, long billingEventId) {
super(repoId, historyRevisionId, billingEventId);
}
@Override
public Key<Recurring> createOfyKey() {
return Key.create(createHistoryEntryKey(), BillingEvent.Recurring.class, billingId);
}
/** Creates a {@link BillingRecurrenceVKey} instance from the given {@link Key} instance. */
public static BillingRecurrenceVKey create(@Nullable Key<BillingEvent.Recurring> ofyKey) {
if (ofyKey == null) {
return null;
}
long billingEventId = ofyKey.getId();
long historyRevisionId = ofyKey.getParent().getId();
String repoId = ofyKey.getParent().getParent().getName();
return new BillingRecurrenceVKey(repoId, historyRevisionId, billingEventId);
}
/** Creates a {@link BillingRecurrenceVKey} instance from the given {@link VKey} instance. */
public static BillingRecurrenceVKey create(@Nullable VKey<BillingEvent.Recurring> vKey) {
return vKey == null ? null : create(vKey.getOfyKey());
}
}
}

View File

@@ -18,45 +18,44 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.reporting.HistoryEntry;
import javax.persistence.Embeddable;
import javax.persistence.PostLoad;
/** {@link VKey} for {@link HistoryEntry} which parent is {@link DomainBase}. */
@Embeddable
public class DomainHistoryVKey extends VKey<HistoryEntry> {
private String domainRepoId;
private Long domainHistoryId;
public class DomainHistoryVKey extends EppHistoryVKey<HistoryEntry, DomainBase> {
// Hibernate requires a default constructor
private DomainHistoryVKey() {}
private DomainHistoryVKey(String domainRepoId, long domainHistoryId) {
initWith(domainRepoId, domainHistoryId);
private DomainHistoryVKey(String repoId, long historyRevisionId) {
super(repoId, historyRevisionId);
}
@PostLoad
void postLoad() {
initWith(domainRepoId, domainHistoryId);
@Override
public Object createSqlKey() {
return new DomainHistoryId(repoId, historyRevisionId);
}
private void initWith(String domainRepoId, long domainHistoryId) {
this.kind = HistoryEntry.class;
this.ofyKey =
Key.create(Key.create(DomainBase.class, domainRepoId), HistoryEntry.class, domainHistoryId);
this.sqlKey = new DomainHistoryId(domainRepoId, domainHistoryId);
this.domainRepoId = domainRepoId;
this.domainHistoryId = domainHistoryId;
@Override
public Key<HistoryEntry> createOfyKey() {
return Key.create(Key.create(DomainBase.class, repoId), HistoryEntry.class, historyRevisionId);
}
/** Creates {@link DomainHistoryVKey} from the given {@link Key} instance. */
public static DomainHistoryVKey create(Key<HistoryEntry> ofyKey) {
public static DomainHistoryVKey create(Key<? extends HistoryEntry> ofyKey) {
checkArgumentNotNull(ofyKey, "ofyKey must be specified");
String domainRepoId = ofyKey.getParent().getName();
long domainHistoryId = ofyKey.getId();
return new DomainHistoryVKey(domainRepoId, domainHistoryId);
String repoId = ofyKey.getParent().getName();
long historyRevisionId = ofyKey.getId();
return new DomainHistoryVKey(repoId, historyRevisionId);
}
public VKey<? extends HistoryEntry> createDomainHistoryVKey() {
return VKey.create(
DomainHistory.class,
createSqlKey(),
Key.create(Key.create(DomainBase.class, repoId), DomainHistory.class, historyRevisionId));
}
}

View File

@@ -0,0 +1,107 @@
// 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;
import com.google.common.base.Joiner;
import com.googlecode.objectify.Key;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.reporting.HistoryEntry;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.io.Serializable;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.MappedSuperclass;
/**
* Base class for {@link VKey} which ofyKey has a {@link HistoryEntry} key as its parent and a key
* for EPP resource as its grandparent.
*
* <p>For such a {@link VKey}, we need to provide two type parameters to indicate the type of {@link
* VKey} itself and the type of EPP resource respectively.
*
* @param <K> type of the {@link VKey}
* @param <E> type of the EPP resource that the key belongs to
*/
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class EppHistoryVKey<K, E extends EppResource> extends ImmutableObject
implements Serializable {
private static final long serialVersionUID = -3906580677709539818L;
String repoId;
Long historyRevisionId;
// Hibernate requires a default constructor.
EppHistoryVKey() {}
EppHistoryVKey(String repoId, long historyRevisionId) {
this.repoId = repoId;
this.historyRevisionId = historyRevisionId;
}
/**
* Returns the kind path for the ofyKey in this instance.
*
* <p>This method is only used reflectively by {@link EppHistoryVKeyTranslatorFactory} to get the
* kind path for a given {@link EppHistoryVKey} instance so it is marked as a private method.
*
* @see #createKindPath(Key)
*/
@SuppressWarnings("unused")
private String getKindPath() {
String eppKind = Key.getKind(new TypeInstantiator<E>(getClass()) {}.getExactType());
String keyKind = Key.getKind(new TypeInstantiator<K>(getClass()) {}.getExactType());
if (keyKind.equals(Key.getKind(HistoryEntry.class))) {
return createKindPath(eppKind, keyKind);
} else {
return createKindPath(eppKind, Key.getKind(HistoryEntry.class), keyKind);
}
}
/**
* Creates the kind path for the given ofyKey}.
*
* <p>The kind path is a string including all kind names(delimited by slash) of a hierarchical
* {@link Key}, e.g., the kind path for BillingEvent.OneTime is "DomainBase/HistoryEntry/OneTime".
*/
@Nullable
public static String createKindPath(@Nullable Key<?> ofyKey) {
if (ofyKey == null) {
return null;
} else if (ofyKey.getParent() == null) {
return ofyKey.getKind();
} else {
return createKindPath(createKindPath(ofyKey.getParent()), ofyKey.getKind());
}
}
private static String createKindPath(String... kinds) {
return Joiner.on("/").join(kinds);
}
/** Creates a {@link VKey} from this instance. */
public VKey<K> createVKey() {
Class<K> vKeyType = new TypeInstantiator<K>(getClass()) {}.getExactType();
return VKey.create(vKeyType, createSqlKey(), createOfyKey());
}
public abstract Object createSqlKey();
public abstract Key<K> createOfyKey();
}

View File

@@ -20,6 +20,7 @@ import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.keyring.kms.KmsModule;
import google.registry.persistence.PersistenceModule.AppEngineJpaTm;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.privileges.secretmanager.SecretManagerModule;
import google.registry.util.UtilsModule;
import javax.inject.Singleton;
import javax.persistence.EntityManagerFactory;
@@ -32,6 +33,7 @@ import javax.persistence.EntityManagerFactory;
CredentialModule.class,
KmsModule.class,
PersistenceModule.class,
SecretManagerModule.class,
UtilsModule.class
})
public interface PersistenceComponent {

View File

@@ -26,6 +26,7 @@ import com.google.api.client.auth.oauth2.Credential;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import dagger.Module;
import dagger.Provides;
import google.registry.config.RegistryConfig.Config;
@@ -33,6 +34,11 @@ import google.registry.keyring.kms.KmsKeyring;
import google.registry.persistence.transaction.CloudSqlCredentialSupplier;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.JpaTransactionManagerImpl;
import google.registry.privileges.secretmanager.SqlCredential;
import google.registry.privileges.secretmanager.SqlCredentialStore;
import google.registry.privileges.secretmanager.SqlUser;
import google.registry.privileges.secretmanager.SqlUser.RobotId;
import google.registry.privileges.secretmanager.SqlUser.RobotUser;
import google.registry.tools.AuthModule.CloudSqlClientCredential;
import google.registry.util.Clock;
import java.lang.annotation.Documented;
@@ -47,6 +53,8 @@ import org.hibernate.cfg.Environment;
/** Dagger module class for the persistence layer. */
@Module
public class PersistenceModule {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
// This name must be the same as the one defined in persistence.xml.
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout";
@@ -122,11 +130,17 @@ public class PersistenceModule {
static JpaTransactionManager provideAppEngineJpaTm(
@Config("cloudSqlUsername") String username,
KmsKeyring kmsKeyring,
SqlCredentialStore credentialStore,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
Clock clock) {
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, kmsKeyring.getCloudSqlPassword());
validateCredentialStore(
credentialStore,
new RobotUser(RobotId.NOMULUS),
overrides.get(Environment.USER),
overrides.get(Environment.PASS));
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@@ -136,6 +150,7 @@ public class PersistenceModule {
static JpaTransactionManager provideNomulusToolJpaTm(
@Config("toolsCloudSqlUsername") String username,
KmsKeyring kmsKeyring,
SqlCredentialStore credentialStore,
@PartialCloudSqlConfigs ImmutableMap<String, String> cloudSqlConfigs,
@CloudSqlClientCredential Credential credential,
Clock clock) {
@@ -143,6 +158,11 @@ public class PersistenceModule {
HashMap<String, String> overrides = Maps.newHashMap(cloudSqlConfigs);
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, kmsKeyring.getToolsCloudSqlPassword());
validateCredentialStore(
credentialStore,
new RobotUser(RobotId.TOOL),
overrides.get(Environment.USER),
overrides.get(Environment.PASS));
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@@ -150,6 +170,7 @@ public class PersistenceModule {
@Singleton
@SocketFactoryJpaTm
static JpaTransactionManager provideSocketFactoryJpaTm(
SqlCredentialStore credentialStore,
@Config("beamCloudSqlUsername") String username,
@Config("beamCloudSqlPassword") String password,
@Config("beamHibernateHikariMaximumPoolSize") int hikariMaximumPoolSize,
@@ -159,6 +180,12 @@ public class PersistenceModule {
overrides.put(Environment.USER, username);
overrides.put(Environment.PASS, password);
overrides.put(HIKARI_MAXIMUM_POOL_SIZE, String.valueOf(hikariMaximumPoolSize));
// TODO(b/175700623): consider assigning different logins to pipelines
validateCredentialStore(
credentialStore,
new RobotUser(RobotId.NOMULUS),
overrides.get(Environment.USER),
overrides.get(Environment.PASS));
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@@ -203,6 +230,30 @@ public class PersistenceModule {
return emf;
}
/** Verifies that the credential from the Secret Manager matches the one currently in use.
*
* <p>This is a helper for the transition to the Secret Manager, and will be removed once data
* and permissions are properly set up for all projects.
**/
private static void validateCredentialStore(
SqlCredentialStore credentialStore, SqlUser sqlUser, String login, String password) {
try {
SqlCredential credential = credentialStore.getCredential(sqlUser);
if (!credential.login().equals(login)) {
logger.atWarning().log(
"Wrong login for %s. Expecting %s, found %s.",
sqlUser.geUserName(), login, credential.login());
return;
}
if (!credential.password().equals(password)) {
logger.atWarning().log("Wrong password for %s.", sqlUser.geUserName());
}
logger.atWarning().log("Credentials in the kerying and the secret manager match.");
} catch (Throwable e) {
logger.atWarning().log(e.getMessage());
}
}
/** Dagger qualifier for {@link JpaTransactionManager} used for App Engine application. */
@Qualifier
@Documented

View File

@@ -29,6 +29,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
@@ -56,6 +61,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Retrier retrier = new Retrier(new SystemSleeper(), 3);
// The entity of classes in this set will be simply ignored when passed to modification
// operations, i.e. insert, put, update and delete. This is to help maintain a single code path
// when we switch from ofy() to tm() for the database migration as we don't need have a condition
// to exclude the Datastore specific entities when the underlying tm() is jpaTm().
// TODO(b/176108270): Remove this property after database migration.
private static final ImmutableSet<Class<? extends ImmutableObject>> IGNORED_ENTITY_CLASSES =
ImmutableSet.of(
EppResourceIndex.class,
ForeignKeyContactIndex.class,
ForeignKeyDomainIndex.class,
ForeignKeyHostIndex.class);
// EntityManagerFactory is thread safe.
private final EntityManagerFactory emf;
private final Clock clock;
@@ -228,6 +245,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void insert(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
getEntityManager().persist(entity);
transactionInfo.get().addUpdate(entity);
@@ -253,6 +273,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void put(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
getEntityManager().merge(entity);
transactionInfo.get().addUpdate(entity);
@@ -278,6 +301,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void update(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
checkArgument(exists(entity), "Given entity does not exist");
getEntityManager().merge(entity);
@@ -414,6 +440,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void delete(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
Object managedEntity = entity;
if (!getEntityManager().contains(entity)) {
@@ -464,6 +493,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
}
private static boolean isEntityOfIgnoredClass(Object entity) {
return IGNORED_ENTITY_CLASSES.contains(entity.getClass());
}
private static ImmutableSet<EntityId> getEntityIdsFromEntity(
EntityType<?> entityType, Object entity) {
if (entityType.hasSingleIdAttribute()) {

View File

@@ -16,13 +16,13 @@ package google.registry.privileges.secretmanager;
import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
import dagger.Component;
import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
import dagger.Module;
import dagger.Provides;
import google.registry.config.CredentialModule.DefaultCredential;
import google.registry.config.RegistryConfig.Config;
import google.registry.config.RegistryConfig.ConfigModule;
import google.registry.util.GoogleCredentialsBundle;
import google.registry.util.Retrier;
import google.registry.util.UtilsModule;
import java.io.IOException;
import javax.inject.Singleton;
@@ -32,20 +32,29 @@ public abstract class SecretManagerModule {
@Provides
@Singleton
static SecretManagerClient provideSecretManagerClient(
@Config("projectId") String project, Retrier retrier) {
static SecretManagerServiceSettings provideSecretManagerSetting(
@DefaultCredential GoogleCredentialsBundle credentialsBundle) {
try {
SecretManagerServiceClient stub = SecretManagerServiceClient.create();
return SecretManagerServiceSettings.newBuilder()
.setCredentialsProvider(() -> credentialsBundle.getGoogleCredentials())
.build();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Provides
@Singleton
static SecretManagerClient provideSecretManagerClient(
SecretManagerServiceSettings serviceSettings,
@Config("projectId") String project,
Retrier retrier) {
try {
SecretManagerServiceClient stub = SecretManagerServiceClient.create(serviceSettings);
Runtime.getRuntime().addShutdownHook(new Thread(stub::close));
return new SecretManagerClientImpl(project, stub, retrier);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Singleton
@Component(modules = {ConfigModule.class, SecretManagerModule.class, UtilsModule.class})
public interface SecretManagerComponent {
SecretManagerClient secretManagerClient();
}
}

View File

@@ -48,7 +48,12 @@ public abstract class SqlUser {
/** Enumerates the {@link RobotUser RobotUsers} in the system. */
public enum RobotId {
NOMULUS;
NOMULUS,
/**
* Credential for RegistryTool. This is temporary, and will be removed when tool users are
* assigned their personal credentials.
*/
TOOL;
}
/** Information of a RobotUser for privilege management purposes. */

View File

@@ -67,7 +67,6 @@ public class Spec11RegistrarThreatMatchesParser {
if (!gcsUtils.existsAndNotEmpty(spec11ReportFilename)) {
return ImmutableSet.of();
}
ImmutableSet.Builder<RegistrarThreatMatches> builder = ImmutableSet.builder();
try (InputStream in = gcsUtils.openInputStream(spec11ReportFilename);
InputStreamReader isr = new InputStreamReader(in, UTF_8)) {
// Skip the header at line 0

View File

@@ -14,19 +14,19 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.partition;
import static com.google.common.collect.Streams.stream;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.domain.token.AllocationToken;
import google.registry.persistence.VKey;
import java.util.List;
/**
@@ -48,7 +48,7 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
private static final int BATCH_SIZE = 20;
private static final Joiner JOINER = Joiner.on(", ");
private ImmutableSet<Key<AllocationToken>> tokensToDelete;
private ImmutableSet<VKey<AllocationToken>> tokensToDelete;
@Override
public void init() {
@@ -71,26 +71,24 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
}
/** Deletes a (filtered) batch of AllocationTokens and returns how many were deleted. */
private long deleteBatch(List<Key<AllocationToken>> batch) {
private long deleteBatch(List<VKey<AllocationToken>> batch) {
// Load the tokens in the same transaction as they are deleted to verify they weren't redeemed
// since the query ran. This also filters out per-domain tokens if they're not to be deleted.
ImmutableSet<AllocationToken> tokensToDelete =
ofy().load().keys(batch).values().stream()
.filter(t -> withDomains || !t.getDomainName().isPresent())
ImmutableSet<VKey<AllocationToken>> tokensToDelete =
tm().load(batch).values().stream()
.filter(t -> withDomains || t.getDomainName().isEmpty())
.filter(t -> SINGLE_USE.equals(t.getTokenType()))
.filter(t -> !t.isRedeemed())
.map(AllocationToken::createVKey)
.collect(toImmutableSet());
if (!dryRun) {
ofy().delete().entities(tokensToDelete);
tm().delete(tokensToDelete);
}
System.out.printf(
"%s tokens: %s\n",
dryRun ? "Would delete" : "Deleted",
JOINER.join(
tokensToDelete.stream()
.map(AllocationToken::getToken)
.sorted()
.collect(toImmutableSet())));
tokensToDelete.stream().map(VKey::getSqlKey).sorted().collect(toImmutableList())));
return tokensToDelete.size();
}
}

View File

@@ -20,8 +20,8 @@ import static com.google.common.collect.Queues.newArrayDeque;
import static com.google.common.collect.Sets.difference;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -38,10 +38,10 @@ import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.common.io.Files;
import com.googlecode.objectify.Key;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.persistence.VKey;
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
import google.registry.util.CollectionUtils;
import google.registry.util.NonFinalForTesting;
@@ -258,8 +258,13 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
@VisibleForTesting
int saveTokens(final ImmutableSet<AllocationToken> tokens) {
Collection<AllocationToken> savedTokens =
dryRun ? tokens : tm().transact(() -> ofy().save().entities(tokens).now().values());
Collection<AllocationToken> savedTokens;
if (dryRun) {
savedTokens = tokens;
} else {
transactIfJpaTm(() -> tm().transact(() -> tm().putAll(tokens)));
savedTokens = tm().transact(() -> tm().loadAll(tokens));
}
savedTokens.forEach(
t -> System.out.println(SKIP_NULLS.join(t.getDomainName().orElse(null), t.getToken())));
return savedTokens.size();
@@ -282,12 +287,14 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
}
private ImmutableSet<String> getExistingTokenStrings(ImmutableSet<String> candidates) {
ImmutableSet<Key<AllocationToken>> existingTokenKeys =
ImmutableSet<VKey<AllocationToken>> existingTokenKeys =
candidates.stream()
.map(input -> Key.create(AllocationToken.class, input))
.map(input -> VKey.create(AllocationToken.class, input))
.collect(toImmutableSet());
return ofy().load().keys(existingTokenKeys).values().stream()
.map(AllocationToken::getToken)
.collect(toImmutableSet());
return transactIfJpaTm(
() ->
tm().load(existingTokenKeys).values().stream()
.map(AllocationToken::getToken)
.collect(toImmutableSet()));
}
}

View File

@@ -15,7 +15,6 @@
package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameter;
@@ -26,6 +25,7 @@ import com.google.common.collect.Lists;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken;
import google.registry.persistence.VKey;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@@ -45,22 +45,26 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
public void run() {
ImmutableMap.Builder<String, AllocationToken> builder = new ImmutableMap.Builder<>();
for (List<String> tokens : Lists.partition(mainParameters, BATCH_SIZE)) {
ImmutableList<Key<AllocationToken>> tokenKeys =
tokens.stream().map(t -> Key.create(AllocationToken.class, t)).collect(toImmutableList());
ofy().load().keys(tokenKeys).forEach((k, v) -> builder.put(k.getName(), v));
ImmutableList<VKey<AllocationToken>> tokenKeys =
tokens.stream()
.map(t -> VKey.create(AllocationToken.class, t))
.collect(toImmutableList());
tm().load(tokenKeys).forEach((k, v) -> builder.put(k.getSqlKey().toString(), v));
}
ImmutableMap<String, AllocationToken> loadedTokens = builder.build();
ImmutableMap<Key<DomainBase>, DomainBase> domains = loadRedeemedDomains(loadedTokens.values());
ImmutableMap<VKey<DomainBase>, DomainBase> domains = loadRedeemedDomains(loadedTokens.values());
for (String token : mainParameters) {
if (loadedTokens.containsKey(token)) {
AllocationToken loadedToken = loadedTokens.get(token);
System.out.println(loadedToken.toString());
if (!loadedToken.getRedemptionHistoryEntry().isPresent()) {
if (loadedToken.getRedemptionHistoryEntry().isEmpty()) {
System.out.printf("Token %s was not redeemed.\n", token);
} else {
Key<DomainBase> domainOfyKey =
loadedToken.getRedemptionHistoryEntry().get().getOfyKey().getParent();
DomainBase domain =
domains.get(loadedToken.getRedemptionHistoryEntry().get().getOfyKey().getParent());
domains.get(VKey.create(DomainBase.class, domainOfyKey.getName(), domainOfyKey));
if (domain == null) {
System.out.printf("ERROR: Token %s was redeemed but domain can't be loaded.\n", token);
} else {
@@ -76,19 +80,23 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
}
}
private static ImmutableMap<Key<DomainBase>, DomainBase> loadRedeemedDomains(
@SuppressWarnings("unchecked")
private static ImmutableMap<VKey<DomainBase>, DomainBase> loadRedeemedDomains(
Collection<AllocationToken> tokens) {
ImmutableList<Key<DomainBase>> domainKeys =
ImmutableList<VKey<DomainBase>> domainKeys =
tokens.stream()
.map(AllocationToken::getRedemptionHistoryEntry)
.filter(Optional::isPresent)
.map(Optional::get)
.map(key -> tm().load(key))
.map(he -> (Key<DomainBase>) he.getParent())
.map(key -> VKey.create(DomainBase.class, key.getName(), key))
.collect(toImmutableList());
ImmutableMap.Builder<Key<DomainBase>, DomainBase> domainsBuilder = new ImmutableMap.Builder<>();
for (List<Key<DomainBase>> keys : Lists.partition(domainKeys, BATCH_SIZE)) {
domainsBuilder.putAll(ofy().load().keys(keys));
ImmutableMap.Builder<VKey<DomainBase>, DomainBase> domainsBuilder =
new ImmutableMap.Builder<>();
for (List<VKey<DomainBase>> keys : Lists.partition(domainKeys, BATCH_SIZE)) {
tm().load(ImmutableList.copyOf(keys))
.forEach((k, v) -> domainsBuilder.put((VKey<DomainBase>) k, v));
}
return domainsBuilder.build();
}

View File

@@ -18,8 +18,8 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterables.partition;
import static com.google.common.collect.Streams.stream;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
@@ -105,13 +105,17 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
}
tokensToSave =
ofy().load().keys(getTokenKeys()).values().stream()
.collect(toImmutableMap(Function.identity(), this::updateToken))
.entrySet()
.stream()
.filter(entry -> !entry.getKey().equals(entry.getValue())) // only update changed tokens
.map(Map.Entry::getValue)
.collect(toImmutableSet());
transactIfJpaTm(
() ->
tm().load(getTokenKeys()).values().stream()
.collect(toImmutableMap(Function.identity(), this::updateToken))
.entrySet()
.stream()
.filter(
entry ->
!entry.getKey().equals(entry.getValue())) // only update changed tokens
.map(Map.Entry::getValue)
.collect(toImmutableSet()));
}
@Override
@@ -123,7 +127,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
protected String execute() {
long numUpdated =
stream(partition(tokensToSave, BATCH_SIZE))
.mapToLong(batch -> tm().transact(() -> saveBatch(batch)))
.mapToLong(batch -> tm().transact(() -> saveBatch(ImmutableList.copyOf(batch))))
.sum();
return String.format("Updated %d tokens in total.", numUpdated);
}
@@ -141,9 +145,9 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
return builder.build();
}
private long saveBatch(List<AllocationToken> batch) {
private long saveBatch(ImmutableList<AllocationToken> batch) {
if (!dryRun) {
ofy().save().entities(batch);
tm().putAll(batch);
}
System.out.printf(
"%s tokens: %s\n",

View File

@@ -15,13 +15,15 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import com.beust.jcommander.Parameter;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.domain.token.AllocationToken;
import google.registry.persistence.VKey;
import java.util.List;
/** Shared base class for commands to update or delete allocation tokens. */
@@ -47,19 +49,28 @@ abstract class UpdateOrDeleteAllocationTokensCommand extends ConfirmingCommand
description = "Do not actually update or delete the tokens; defaults to false")
protected boolean dryRun;
protected ImmutableSet<Key<AllocationToken>> getTokenKeys() {
protected ImmutableSet<VKey<AllocationToken>> getTokenKeys() {
checkArgument(
tokens == null ^ prefix == null,
"Must provide one of --tokens or --prefix, not both / neither");
if (tokens != null) {
return tokens.stream()
.map(token -> Key.create(AllocationToken.class, token))
.collect(toImmutableSet());
ImmutableSet<VKey<AllocationToken>> keys =
tokens.stream()
.map(token -> VKey.create(AllocationToken.class, token))
.collect(toImmutableSet());
ImmutableSet<VKey<AllocationToken>> nonexistentKeys =
transactIfJpaTm(
() -> keys.stream().filter(key -> !tm().exists(key)).collect(toImmutableSet()));
checkState(nonexistentKeys.isEmpty(), "Tokens with keys %s did not exist.", nonexistentKeys);
return keys;
} else {
checkArgument(!prefix.isEmpty(), "Provided prefix should not be blank");
return ofy().load().type(AllocationToken.class).keys().list().stream()
.filter(key -> key.getName().startsWith(prefix))
.collect(toImmutableSet());
return transactIfJpaTm(
() ->
tm().loadAll(AllocationToken.class).stream()
.filter(token -> token.getToken().startsWith(prefix))
.map(AllocationToken::createVKey)
.collect(toImmutableSet()));
}
}
}

View File

@@ -55,6 +55,19 @@ class ResaveAllEppResourcesActionTest extends MapreduceTestCase<ResaveAllEppReso
.isGreaterThan(creationTime);
}
@Test
void test_fastMode_doesNotResaveEntityWithNoChanges() throws Exception {
ContactResource contact = persistActiveContact("test123");
DateTime creationTime = contact.getUpdateTimestamp().getTimestamp();
assertThat(ofy().load().entity(contact).now().getUpdateTimestamp().getTimestamp())
.isEqualTo(creationTime);
ofy().clearSessionCache();
action.isFast = true;
runMapreduce();
assertThat(ofy().load().entity(contact).now().getUpdateTimestamp().getTimestamp())
.isEqualTo(creationTime);
}
@Test
void test_mapreduceResolvesPendingTransfer() throws Exception {
DateTime now = DateTime.now(UTC);

View File

@@ -100,7 +100,11 @@ public abstract class FlowTestCase<F extends Flow> {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
AppEngineExtension.builder()
.withClock(clock)
.withDatastoreAndCloudSql()
.withTaskQueue()
.build();
@BeforeEach
public void beforeEachFlowTestCase() {
@@ -288,7 +292,7 @@ public abstract class FlowTestCase<F extends Flow> {
e);
}
// Clear the cache so that we don't see stale results in tests.
ofy().clearSessionCache();
tm().clearSessionCache();
return output;
}

View File

@@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.tmch.ClaimsListShardTest.createTestClaimsListShard;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
@@ -72,7 +74,7 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
protected R reloadResourceByForeignKey(DateTime now) throws Exception {
// Force the session to be cleared so that when we read it back, we read from Datastore and not
// from the transaction's session cache.
ofy().clearSessionCache();
tm().clearSessionCache();
return loadByForeignKey(getResourceClass(), getUniqueIdFromCommand(), now).orElse(null);
}
@@ -83,9 +85,9 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
protected <T extends EppResource> T reloadResourceAndCloneAtTime(T resource, DateTime now) {
// Force the session to be cleared.
ofy().clearSessionCache();
tm().clearSessionCache();
@SuppressWarnings("unchecked")
T refreshedResource = (T) ofy().load().entity(resource).now().cloneProjectedAtTime(now);
T refreshedResource = (T) transactIfJpaTm(() -> tm().load(resource)).cloneProjectedAtTime(now);
return refreshedResource;
}

View File

@@ -71,7 +71,6 @@ import google.registry.model.registry.Registry;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
@@ -169,7 +168,7 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey))
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.build());
doCheckTest(
create(false, "example1.tld", "In use"),

View File

@@ -162,7 +162,6 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.monitoring.whitebox.EppMetric;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TaskQueueHelper.TaskMatcher;
import java.math.BigDecimal;
@@ -505,7 +504,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey))
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.build());
clock.advanceOneMilli();
EppException thrown =
@@ -528,7 +527,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
HistoryEntry historyEntry =
ofy().load().type(HistoryEntry.class).ancestor(reloadResourceByForeignKey()).first().now();
assertThat(ofy().load().entity(token).now().getRedemptionHistoryEntry())
.hasValue(DomainHistoryVKey.create(Key.create(historyEntry)));
.hasValue(HistoryEntry.createVKey(Key.create(historyEntry)));
}
@Test
@@ -1275,7 +1274,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
assertThat(reloadedToken.isRedeemed()).isTrue();
assertThat(reloadedToken.getRedemptionHistoryEntry())
.hasValue(
DomainHistoryVKey.create(
HistoryEntry.createVKey(
Key.create(getHistoryEntries(reloadResourceByForeignKey()).get(0))));
}

View File

@@ -49,7 +49,6 @@ import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.registry.Registry;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.testing.AppEngineExtension;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
@@ -198,7 +197,7 @@ class AllocationTokenFlowUtilsTest {
new AllocationToken.Builder()
.setToken("tokeN")
.setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey))
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.build());
assertThat(
flowUtils

View File

@@ -24,16 +24,18 @@ import google.registry.flows.EppException;
import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.host.HostResource;
import org.junit.jupiter.api.Test;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
/** Unit tests for {@link HostCheckFlow}. */
@DualDatabaseTest
class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostResource> {
HostCheckFlowTest() {
setEppInput("host_check.xml");
}
@Test
@TestOfyAndSql
void testNothingExists() throws Exception {
// These ids come from the check xml.
doCheckTest(
@@ -42,7 +44,7 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testOneExists() throws Exception {
persistActiveHost("ns1.example.tld");
// These ids come from the check xml.
@@ -52,7 +54,7 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testOneExistsButWasDeleted() throws Exception {
persistDeletedHost("ns1.example.tld", clock.nowUtc().minusDays(1));
// These ids come from the check xml.
@@ -62,27 +64,27 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testXmlMatches() throws Exception {
persistActiveHost("ns2.example.tld");
runFlowAssertResponse(loadFile("host_check_response.xml"));
}
@Test
@TestOfyAndSql
void test50IdsAllowed() throws Exception {
// Make sure we don't have a regression that reduces the number of allowed checks.
setEppInput("host_check_50.xml");
runFlow();
}
@Test
@TestOfyAndSql
void testTooManyIds() {
setEppInput("host_check_51.xml");
EppException thrown = assertThrows(TooManyResourceChecksException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
assertIcannReportingActivityFieldLogged("srs-host-check");

View File

@@ -16,6 +16,7 @@ package google.registry.flows.host;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.createTlds;
@@ -53,10 +54,12 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link HostCreateFlow}. */
@DualDatabaseTest
class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResource> {
private void setEppHostCreateInput(String hostName, String hostAddrs) {
@@ -90,7 +93,9 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
.hasOnlyOneHistoryEntryWhich()
.hasType(HistoryEntry.Type.HOST_CREATE);
assertNoBillingEvents();
assertEppResourceIndexEntityFor(reloadResourceByForeignKey());
if (tm().isOfy()) {
assertEppResourceIndexEntityFor(reloadResourceByForeignKey());
}
}
private void doSuccessfulInternalTest(String tld) throws Exception {
@@ -100,19 +105,19 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
doSuccessfulTest();
}
@Test
@TestOfyAndSql
void testDryRun() throws Exception {
dryRunFlowAssertResponse(loadFile("host_create_response.xml"));
}
@Test
@TestOfyAndSql
void testSuccess_externalNeverExisted() throws Exception {
doSuccessfulTest();
assertAboutHosts().that(reloadResourceByForeignKey()).hasSuperordinateDomain(null);
assertNoDnsTasksEnqueued();
}
@Test
@TestOfyAndSql
void testSuccess_internalNeverExisted() throws Exception {
doSuccessfulInternalTest("tld");
HostResource host = reloadResourceByForeignKey();
@@ -123,7 +128,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertDnsTasksEnqueued("ns1.example.tld");
}
@Test
@TestOfyAndSql
void testFailure_multipartTLDsAndInvalidHost() {
createTlds("bar.tld", "tld");
@@ -132,7 +137,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testSuccess_externalExistedButWasDeleted() throws Exception {
persistDeletedHost(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
doSuccessfulTest();
@@ -140,7 +145,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertNoDnsTasksEnqueued();
}
@Test
@TestOfyAndSql
void testSuccess_internalExistedButWasDeleted() throws Exception {
persistDeletedHost(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
doSuccessfulInternalTest("tld");
@@ -152,7 +157,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertDnsTasksEnqueued("ns1.example.tld");
}
@Test
@TestOfyAndSql
void testFailure_subordinateNeedsIps() {
setEppHostCreateInput("ns1.example.tld", null);
createTld("tld");
@@ -161,7 +166,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_externalMustNotHaveIps() {
setEppHostCreateInputWithIps("ns1.example.external");
createTld("tld");
@@ -170,7 +175,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_superordinateMissing() {
setEppHostCreateInput("ns1.example.tld", null);
createTld("tld");
@@ -179,7 +184,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertThat(thrown).hasMessageThat().contains("(example.tld)");
}
@Test
@TestOfyAndSql
void testFailure_superordinateInPendingDelete() {
setEppHostCreateInputWithIps("ns1.example.tld");
createTld("tld");
@@ -197,7 +202,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
.contains("Superordinate domain for this hostname is in pending delete");
}
@Test
@TestOfyAndSql
void testFailure_alreadyExists() throws Exception {
setEppHostCreateInput("ns1.example.tld", null);
persistActiveHost(getUniqueIdFromCommand());
@@ -209,7 +214,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
String.format("Object with given ID (%s) already exists", getUniqueIdFromCommand()));
}
@Test
@TestOfyAndSql
void testFailure_resourceContention() throws Exception {
setEppHostCreateInput("ns1.example.tld", null);
String targetId = getUniqueIdFromCommand();
@@ -226,14 +231,14 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_nonLowerCaseHostname() {
setEppHostCreateInput("ns1.EXAMPLE.tld", null);
EppException thrown = assertThrows(HostNameNotLowerCaseException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_nonPunyCodedHostname() {
setEppHostCreateInput("ns1.çauçalito.みんな", null);
HostNameNotPunyCodedException thrown =
@@ -241,21 +246,21 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertThat(thrown).hasMessageThat().contains("expected ns1.xn--aualito-txac.xn--q9jyb4c");
}
@Test
@TestOfyAndSql
void testFailure_nonCanonicalHostname() {
setEppHostCreateInput("ns1.example.tld.", null);
EppException thrown = assertThrows(HostNameNotNormalizedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_longHostName() {
setEppHostCreateInputWithIps("a" + Strings.repeat(".labelpart", 25) + ".tld");
EppException thrown = assertThrows(HostNameTooLongException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_ip4AddressWithIp6Declaration() {
setEppHostCreateInput(
"ns1.example.tld",
@@ -272,37 +277,37 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_badCharacter() {
doFailingHostNameTest("foo bar", InvalidHostNameException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowPublicSuffix() {
doFailingHostNameTest("example.tld", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowCcTld() {
doFailingHostNameTest("foo.co.uk", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_barePublicSuffix() {
doFailingHostNameTest("com", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_bareCcTld() {
doFailingHostNameTest("co.uk", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowNewTld() {
doFailingHostNameTest("example.lol", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_ccTldInBailiwick() {
createTld("co.uk");
setEppHostCreateInputWithIps("foo.co.uk");
@@ -310,7 +315,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
assertIcannReportingActivityFieldLogged("srs-host-create");

View File

@@ -26,66 +26,68 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.Test;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link HostFlowUtils}. */
@DualDatabaseTest
class HostFlowUtilsTest {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
@Test
@TestOfyAndSql
void test_validExternalHostName_validates() throws Exception {
assertThat(validateHostName("host.example.com").toString()).isEqualTo("host.example.com");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_validates() throws Exception {
assertThat(validateHostName("host.blogspot.com").toString()).isEqualTo("host.blogspot.com");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_validates() throws Exception {
assertThat(validateHostName("ns1.host.co.uk").toString()).isEqualTo("ns1.host.co.uk");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_tooShallow() {
assertThrows(
HostNameTooShallowException.class, () -> validateHostName("host.co.uk").toString());
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameTooLong() {
assertThrows(
HostNameTooLongException.class,
() -> validateHostName(Strings.repeat("na", 200) + ".wat.man"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotLowerCase() {
assertThrows(HostNameNotLowerCaseException.class, () -> validateHostName("NA.CAPS.TLD"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotPunyCoded() {
assertThrows(
HostNameNotPunyCodedException.class, () -> validateHostName("motörhead.death.metal"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotNormalized() {
assertThrows(HostNameNotNormalizedException.class, () -> validateHostName("root.node.yeah."));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameHasLeadingHyphen() {
assertThrows(InvalidHostNameException.class, () -> validateHostName("-giga.mega.tld"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameTooShallow() {
assertThrows(HostNameTooShallowException.class, () -> validateHostName("domain.tld"));
}

View File

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

View File

@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newHostResource;
import static google.registry.testing.DatabaseHelper.persistNewRegistrars;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResourceWithCommitLog;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@@ -26,16 +27,19 @@ import static org.joda.time.DateTimeZone.UTC;
import google.registry.model.host.HostResource;
import google.registry.model.ofy.Ofy;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link EppResourceUtils}. */
public class EppResourceUtilsTest {
@DualDatabaseTest
class EppResourceUtilsTest {
@RegisterExtension
public final AppEngineExtension appEngine =
@@ -51,7 +55,7 @@ public class EppResourceUtilsTest {
inject.setStaticField(Ofy.class, "clock", clock);
}
@Test
@TestOfyAndSql
void testLoadAtPointInTime_beforeCreated_returnsNull() {
clock.advanceOneMilli();
// Don't save a commit log, we shouldn't need one.
@@ -62,7 +66,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minus(Duration.millis(1))).now()).isNull();
}
@Test
@TestOfyAndSql
void testLoadAtPointInTime_atOrAfterLastAutoUpdateTime_returnsResource() {
clock.advanceOneMilli();
// Don't save a commit log, we shouldn't need one.
@@ -73,8 +77,9 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc()).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_usingIntactRevisionHistory_returnsMutationValue() {
persistNewRegistrars("OLD", "NEW");
clock.advanceOneMilli();
// Save resource with a commit log that we can read in later as a revisions map value.
HostResource oldHost = persistResourceWithCommitLog(
@@ -94,7 +99,7 @@ public class EppResourceUtilsTest {
.isEqualTo(oldHost);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_brokenRevisionHistory_returnsResourceAsIs() {
// Don't save a commit log since we want to test the handling of a broken revisions key.
HostResource oldHost = persistResource(
@@ -114,7 +119,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minusMillis(1)).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_fallback_returnsMutationValueForOldestRevision() {
clock.advanceOneMilli();
// Save a commit log that we can fall back to.
@@ -136,7 +141,7 @@ public class EppResourceUtilsTest {
.isEqualTo(oldHost);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_ultimateFallback_onlyOneRevision_returnsCurrentResource() {
clock.advanceOneMilli();
// Don't save a commit log; we want to test that we load from the current resource.
@@ -151,7 +156,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minusMillis(1)).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_moreThanThirtyDaysInPast_historyIsPurged() {
clock.advanceOneMilli();
HostResource host =

View File

@@ -633,14 +633,13 @@ public class DomainBaseSqlTest {
"4-COM",
END_OF_TIME,
"registrar1",
createLegacyVKey(
BillingEvent.OneTime.class, oneTimeBillingEvent.getId())),
oneTimeBillingEvent.createVKey()),
GracePeriod.createForRecurring(
GracePeriodStatus.AUTO_RENEW,
"4-COM",
END_OF_TIME,
"registrar1",
createLegacyVKey(BillingEvent.Recurring.class, billEvent.getId())));
billEvent.createVKey()));
jpaTm().insert(contact);
jpaTm().insert(contact2);

View File

@@ -875,16 +875,13 @@ public class DomainBaseTest extends EntityTestCase {
ImmutableSet<BillEventInfo> historyIds =
domain.getGracePeriods().stream()
.map(
gp ->
new BillEventInfo(
gp.getRecurringBillingEvent(), gp.billingEventRecurringHistoryId,
gp.getOneTimeBillingEvent(), gp.billingEventOneTimeHistoryId))
gp -> new BillEventInfo(gp.getRecurringBillingEvent(), gp.getOneTimeBillingEvent()))
.collect(toImmutableSet());
assertThat(historyIds)
.isEqualTo(
ImmutableSet.of(
new BillEventInfo(null, null, oneTimeBillKey, historyEntryKey.getId()),
new BillEventInfo(recurringBillKey, historyEntryKey.getId(), null, null)));
new BillEventInfo(null, oneTimeBillKey),
new BillEventInfo(recurringBillKey, null)));
}
static class BillEventInfo extends ImmutableObject {
@@ -895,13 +892,9 @@ public class DomainBaseTest extends EntityTestCase {
BillEventInfo(
VKey<BillingEvent.Recurring> billingEventRecurring,
Long billingEventRecurringHistoryId,
VKey<BillingEvent.OneTime> billingEventOneTime,
Long billingEventOneTimeHistoryId) {
VKey<BillingEvent.OneTime> billingEventOneTime) {
this.billingEventRecurring = billingEventRecurring;
this.billingEventRecurringHistoryId = billingEventRecurringHistoryId;
this.billingEventOneTime = billingEventOneTime;
this.billingEventOneTimeHistoryId = billingEventOneTimeHistoryId;
}
}
}

View File

@@ -63,11 +63,11 @@ public class GracePeriodTest {
recurringKey =
VKey.create(
Recurring.class,
12345,
12345L,
Key.create(
Key.create(Key.create(DomainBase.class, "1-TEST"), HistoryEntry.class, 343L),
Recurring.class,
12345));
12345L));
}
@Test
@@ -80,8 +80,6 @@ public class GracePeriodTest {
assertThat(gracePeriod.getClientId()).isEqualTo("TheRegistrar");
assertThat(gracePeriod.getExpirationTime()).isEqualTo(now.plusDays(1));
assertThat(gracePeriod.hasBillingEvent()).isTrue();
assertThat(gracePeriod.billingEventOneTimeHistoryId).isEqualTo(12345L);
assertThat(gracePeriod.billingEventRecurringHistoryId).isNull();
}
@Test
@@ -96,8 +94,6 @@ public class GracePeriodTest {
assertThat(gracePeriod.getClientId()).isEqualTo("TheRegistrar");
assertThat(gracePeriod.getExpirationTime()).isEqualTo(now.plusDays(1));
assertThat(gracePeriod.hasBillingEvent()).isTrue();
assertThat(gracePeriod.billingEventOneTimeHistoryId).isNull();
assertThat(gracePeriod.billingEventRecurringHistoryId).isEqualTo(343L);
}
@Test

View File

@@ -16,15 +16,14 @@ package google.registry.model.domain.token;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.CANCELLED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED;
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -40,13 +39,14 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.persistence.VKey;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link AllocationToken}. */
@DualDatabaseTest
public class AllocationTokenTest extends EntityTestCase {
public AllocationTokenTest() {
@@ -58,7 +58,7 @@ public class AllocationTokenTest extends EntityTestCase {
createTld("foo");
}
@Test
@TestOfyAndSql
void testPersistence() {
AllocationToken unlimitedUseToken =
persistResource(
@@ -78,7 +78,7 @@ public class AllocationTokenTest extends EntityTestCase {
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
.build())
.build());
assertThat(ofy().load().entity(unlimitedUseToken).now()).isEqualTo(unlimitedUseToken);
assertThat(transactIfJpaTm(() -> tm().load(unlimitedUseToken))).isEqualTo(unlimitedUseToken);
DomainBase domain = persistActiveDomain("example.foo");
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
@@ -86,37 +86,15 @@ public class AllocationTokenTest extends EntityTestCase {
persistResource(
new AllocationToken.Builder()
.setToken("abc123Single")
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey))
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.setDomainName("example.foo")
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.setTokenType(SINGLE_USE)
.build());
assertThat(ofy().load().entity(singleUseToken).now()).isEqualTo(singleUseToken);
jpaTm()
.transact(
() -> {
jpaTm().insert(unlimitedUseToken);
jpaTm().insert(singleUseToken);
});
jpaTm()
.transact(
() -> {
assertAboutImmutableObjects()
.that(jpaTm().load(VKey.createSql(AllocationToken.class, "abc123Unlimited")))
.isEqualExceptFields(
unlimitedUseToken,
"creationTime",
"updateTimestamp",
"redemptionHistoryEntry");
assertAboutImmutableObjects()
.that(jpaTm().load(VKey.createSql(AllocationToken.class, "abc123Single")))
.isEqualExceptFields(
singleUseToken, "creationTime", "updateTimestamp", "redemptionHistoryEntry");
});
assertThat(transactIfJpaTm(() -> tm().load(singleUseToken))).isEqualTo(singleUseToken);
}
@Test
@TestOfyOnly
void testIndexing() throws Exception {
DomainBase domain = persistActiveDomain("blahdomain.foo");
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
@@ -125,7 +103,7 @@ public class AllocationTokenTest extends EntityTestCase {
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(SINGLE_USE)
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey))
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey))
.setDomainName("blahdomain.foo")
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
.build()),
@@ -134,7 +112,7 @@ public class AllocationTokenTest extends EntityTestCase {
"domainName");
}
@Test
@TestOfyAndSql
void testCreationTime_autoPopulates() {
AllocationToken tokenBeforePersisting =
new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build();
@@ -143,7 +121,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.nowUtc());
}
@Test
@TestOfyAndSql
void testSetCreationTime_cantCallMoreThanOnce() {
AllocationToken.Builder builder =
new AllocationToken.Builder()
@@ -157,7 +135,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Creation time can only be set once");
}
@Test
@TestOfyAndSql
void testSetToken_cantCallMoreThanOnce() {
AllocationToken.Builder builder = new AllocationToken.Builder().setToken("foobar");
IllegalStateException thrown =
@@ -165,7 +143,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token can only be set once");
}
@Test
@TestOfyAndSql
void testSetTokenType_cantCallMoreThanOnce() {
AllocationToken.Builder builder =
new AllocationToken.Builder().setTokenType(TokenType.UNLIMITED_USE);
@@ -174,7 +152,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token type can only be set once");
}
@Test
@TestOfyAndSql
void testBuild_DomainNameWithLessThanTwoParts() {
IllegalArgumentException thrown =
assertThrows(
@@ -192,7 +170,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example");
}
@Test
@TestOfyAndSql
void testBuild_invalidTld() {
IllegalArgumentException thrown =
assertThrows(
@@ -210,7 +188,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example.nosuchtld");
}
@Test
@TestOfyAndSql
void testBuild_domainNameOnlyOnSingleUse() {
AllocationToken.Builder builder =
new AllocationToken.Builder()
@@ -223,7 +201,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Domain name can only be specified for SINGLE_USE tokens");
}
@Test
@TestOfyAndSql
void testBuild_redemptionHistoryEntryOnlyInSingleUse() {
DomainBase domain = persistActiveDomain("blahdomain.foo");
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
@@ -231,14 +209,14 @@ public class AllocationTokenTest extends EntityTestCase {
new AllocationToken.Builder()
.setToken("foobar")
.setTokenType(TokenType.UNLIMITED_USE)
.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey));
.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey));
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build);
assertThat(thrown)
.hasMessageThat()
.isEqualTo("Redemption history entry can only be specified for SINGLE_USE tokens");
}
@Test
@TestOfyAndSql
void testSetTransitions_notStartOfTime() {
IllegalArgumentException thrown =
assertThrows(
@@ -256,7 +234,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("tokenStatusTransitions map must start at START_OF_TIME.");
}
@Test
@TestOfyAndSql
void testSetTransitions_badInitialValue() {
IllegalArgumentException thrown =
assertThrows(
@@ -273,14 +251,14 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("tokenStatusTransitions must start with NOT_STARTED");
}
@Test
@TestOfyAndSql
void testSetTransitions_invalidInitialTransitions() {
// NOT_STARTED can only go to VALID or CANCELLED
assertBadInitialTransition(NOT_STARTED);
assertBadInitialTransition(ENDED);
}
@Test
@TestOfyAndSql
void testSetTransitions_badTransitionsFromValid() {
// VALID can only go to ENDED or CANCELLED
assertBadTransition(
@@ -301,14 +279,14 @@ public class AllocationTokenTest extends EntityTestCase {
NOT_STARTED);
}
@Test
@TestOfyAndSql
void testSetTransitions_terminalTransitions() {
// both ENDED and CANCELLED are terminal
assertTerminal(ENDED);
assertTerminal(CANCELLED);
}
@Test
@TestOfyAndSql
void testSetDiscountFractionTooHigh() {
IllegalArgumentException thrown =
assertThrows(
@@ -319,7 +297,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
}
@Test
@TestOfyAndSql
void testSetDiscountFractionTooLow() {
IllegalArgumentException thrown =
assertThrows(
@@ -330,7 +308,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
}
@Test
@TestOfyAndSql
void testSetDiscountYearsTooHigh() {
IllegalArgumentException thrown =
assertThrows(
@@ -341,7 +319,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Discount years must be between 1 and 10 inclusive");
}
@Test
@TestOfyAndSql
void testSetDiscountYearsTooLow() {
IllegalArgumentException thrown =
assertThrows(
@@ -352,7 +330,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Discount years must be between 1 and 10 inclusive");
}
@Test
@TestOfyAndSql
void testBuild_noTokenType() {
IllegalArgumentException thrown =
assertThrows(
@@ -361,7 +339,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token type must be specified");
}
@Test
@TestOfyAndSql
void testBuild_noToken() {
IllegalArgumentException thrown =
assertThrows(
@@ -370,7 +348,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be null or empty");
}
@Test
@TestOfyAndSql
void testBuild_emptyToken() {
IllegalArgumentException thrown =
assertThrows(
@@ -379,7 +357,7 @@ public class AllocationTokenTest extends EntityTestCase {
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be blank");
}
@Test
@TestOfyAndSql
void testBuild_discountPremiumsRequiresDiscountFraction() {
IllegalArgumentException thrown =
assertThrows(
@@ -395,7 +373,7 @@ public class AllocationTokenTest extends EntityTestCase {
.isEqualTo("Discount premiums can only be specified along with a discount fraction");
}
@Test
@TestOfyAndSql
void testBuild_discountYearsRequiresDiscountFraction() {
IllegalArgumentException thrown =
assertThrows(

View File

@@ -20,6 +20,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistDeletedHost;
@@ -29,16 +30,21 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import google.registry.model.EntityTestCase;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestCacheExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ForeignKeyIndex}. */
public class ForeignKeyIndexTest extends EntityTestCase {
@DualDatabaseTest
class ForeignKeyIndexTest extends EntityTestCase {
@RegisterExtension
public final TestCacheExtension testCacheExtension =
@@ -49,7 +55,17 @@ public class ForeignKeyIndexTest extends EntityTestCase {
createTld("com");
}
@Test
@TestSqlOnly
void testModifyForeignKeyIndex_notThrowExceptionInSql() {
DomainBase domainBase = newDomainBase("test.com");
ForeignKeyIndex<DomainBase> fki = ForeignKeyIndex.create(domainBase, fakeClock.nowUtc());
tm().transact(() -> tm().insert(fki));
tm().transact(() -> tm().put(fki));
tm().transact(() -> tm().delete(fki));
tm().transact(() -> tm().update(fki));
}
@TestOfyOnly
void testPersistence() {
// Persist a host and implicitly persist a ForeignKeyIndex for it.
HostResource host = persistActiveHost("ns1.example.com");
@@ -59,7 +75,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
assertThat(fki.getDeletionTime()).isEqualTo(END_OF_TIME);
}
@Test
@TestOfyOnly
void testIndexing() throws Exception {
// Persist a host and implicitly persist a ForeignKeyIndex for it.
persistActiveHost("ns1.example.com");
@@ -68,38 +84,50 @@ public class ForeignKeyIndexTest extends EntityTestCase {
"deletionTime");
}
@Test
@TestOfyAndSql
void testLoadForNonexistentForeignKey_returnsNull() {
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testLoadForDeletedForeignKey_returnsNull() {
HostResource host = persistActiveHost("ns1.example.com");
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
if (tm().isOfy()) {
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
} else {
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
}
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testLoad_newerKeyHasBeenSoftDeleted() {
HostResource host1 = persistActiveHost("ns1.example.com");
fakeClock.advanceOneMilli();
ForeignKeyHostIndex fki = new ForeignKeyHostIndex();
fki.foreignKey = "ns1.example.com";
fki.topReference = host1.createVKey();
fki.deletionTime = fakeClock.nowUtc();
persistResource(fki);
if (tm().isOfy()) {
ForeignKeyHostIndex fki = new ForeignKeyHostIndex();
fki.foreignKey = "ns1.example.com";
fki.topReference = host1.createVKey();
fki.deletionTime = fakeClock.nowUtc();
persistResource(fki);
} else {
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
}
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testBatchLoad_skipsDeletedAndNonexistent() {
persistActiveHost("ns1.example.com");
HostResource host = persistActiveHost("ns2.example.com");
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
if (tm().isOfy()) {
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
} else {
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
}
assertThat(
ForeignKeyIndex.load(
HostResource.class,
@@ -109,7 +137,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com");
}
@Test
@TestOfyAndSql
void testDeadCodeThatDeletedScrapCommandsReference() {
persistActiveHost("omg");
assertThat(ForeignKeyIndex.load(HostResource.class, "omg", fakeClock.nowUtc()).getForeignKey())
@@ -124,7 +152,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
return ForeignKeyIndex.load(ContactResource.class, contactId, fakeClock.nowUtc());
}
@Test
@TestOfyOnly
void test_loadCached_cachesNonexistenceOfHosts() {
assertThat(
ForeignKeyIndex.loadCached(
@@ -144,7 +172,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns4.example.com", loadHostFki("ns4.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_cachesExistenceOfHosts() {
HostResource host1 = persistActiveHost("ns1.example.com");
HostResource host2 = persistActiveHost("ns2.example.com");
@@ -172,7 +200,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
"ns3.example.com", loadHostFki("ns3.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_doesntSeeHostChangesWhileCacheIsValid() {
HostResource originalHost = persistActiveHost("ns1.example.com");
ForeignKeyIndex<HostResource> originalFki = loadHostFki("ns1.example.com");
@@ -195,7 +223,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com", originalFki);
}
@Test
@TestOfyOnly
void test_loadCached_filtersOutSoftDeletedHosts() {
persistActiveHost("ns1.example.com");
persistDeletedHost("ns2.example.com", fakeClock.nowUtc().minusDays(1));
@@ -207,7 +235,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com", loadHostFki("ns1.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_cachesContactFkis() {
persistActiveContact("contactid1");
ForeignKeyIndex<ContactResource> fki1 = loadContactFki("contactid1");

View File

@@ -0,0 +1,36 @@
// 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.model.translators;
import static google.registry.model.translators.EppHistoryVKeyTranslatorFactory.kindPathToVKeyClass;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
/** Unit test for {@link EppHistoryVKeyTranslatorFactory}. */
class EppHistoryVKeyTranslatorFactoryTest {
@Test
void assertAllVKeyClassesHavingCreateFromOfyKeyMethod() {
kindPathToVKeyClass.forEach(
(kindPath, vKeyClass) -> {
try {
vKeyClass.getDeclaredMethod("create", com.googlecode.objectify.Key.class);
} catch (NoSuchMethodException e) {
fail("Missing static method create(com.googlecode.objectify.Key) on " + vKeyClass, e);
}
});
}
}

View File

@@ -0,0 +1,116 @@
// 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;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.domain.DomainBase;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.BillingVKey.BillingEventVKey;
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
import google.registry.schema.replay.EntityTest.EntityForTesting;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import javax.persistence.Transient;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit test for {@link BillingVKey}. */
@DualDatabaseTest
class BillingVKeyTest {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(BillingVKeyTestEntity.class)
.withJpaUnitTestEntities(BillingVKeyTestEntity.class)
.build();
@TestOfyAndSql
void testRestoreSymmetricVKey() {
Key<HistoryEntry> domainHistoryKey =
Key.create(Key.create(DomainBase.class, "domainRepoId"), HistoryEntry.class, 10L);
Key<BillingEvent.OneTime> oneTimeOfyKey =
Key.create(domainHistoryKey, BillingEvent.OneTime.class, 100L);
VKey<BillingEvent.OneTime> oneTimeVKey =
VKey.create(BillingEvent.OneTime.class, 100L, oneTimeOfyKey);
Key<BillingEvent.Recurring> recurringOfyKey =
Key.create(domainHistoryKey, BillingEvent.Recurring.class, 200L);
VKey<BillingEvent.Recurring> recurringVKey =
VKey.create(BillingEvent.Recurring.class, 200L, recurringOfyKey);
BillingVKeyTestEntity original = new BillingVKeyTestEntity(oneTimeVKey, recurringVKey);
tm().transact(() -> tm().insert(original));
BillingVKeyTestEntity persisted = tm().transact(() -> tm().load(original.createVKey()));
assertThat(persisted).isEqualTo(original);
assertThat(persisted.getBillingEventVKey()).isEqualTo(oneTimeVKey);
assertThat(persisted.getBillingRecurrenceVKey()).isEqualTo(recurringVKey);
}
@TestOfyAndSql
void testHandleNullVKeyCorrectly() {
BillingVKeyTestEntity original = new BillingVKeyTestEntity(null, null);
tm().transact(() -> tm().insert(original));
BillingVKeyTestEntity persisted = tm().transact(() -> tm().load(original.createVKey()));
assertThat(persisted).isEqualTo(original);
}
@EntityForTesting
@Entity
@javax.persistence.Entity
private static class BillingVKeyTestEntity extends ImmutableObject {
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
@Id @javax.persistence.Id String id = "id";
BillingEventVKey billingEventVKey;
BillingRecurrenceVKey billingRecurrenceVKey;
BillingVKeyTestEntity() {}
BillingVKeyTestEntity(
VKey<BillingEvent.OneTime> onetime, VKey<BillingEvent.Recurring> recurring) {
this.billingEventVKey = BillingEventVKey.create(onetime);
this.billingRecurrenceVKey = BillingRecurrenceVKey.create(recurring);
}
VKey<BillingEvent.OneTime> getBillingEventVKey() {
return billingEventVKey.createVKey();
}
VKey<BillingEvent.Recurring> getBillingRecurrenceVKey() {
return billingRecurrenceVKey.createVKey();
}
VKey<BillingVKeyTestEntity> createVKey() {
return VKey.create(
BillingVKeyTestEntity.class, id, Key.create(parent, BillingVKeyTestEntity.class, id));
}
}
}

View File

@@ -56,12 +56,17 @@ class DomainHistoryVKeyTest {
TestEntity persisted = tm().transact(() -> tm().load(original.createVKey()));
assertThat(persisted).isEqualTo(original);
// Double check that the persisted.domainHistoryVKey is a symmetric VKey
assertThat(persisted.domainHistoryVKey.getKind()).isEqualTo(HistoryEntry.class);
assertThat(persisted.domainHistoryVKey.getOfyKey())
assertThat(persisted.domainHistoryVKey.createOfyKey())
.isEqualTo(
Key.create(Key.create(DomainBase.class, "domainRepoId"), HistoryEntry.class, 10L));
assertThat(persisted.domainHistoryVKey.getSqlKey())
assertThat(persisted.domainHistoryVKey.createSqlKey())
.isEqualTo(new DomainHistoryId("domainRepoId", 10L));
assertThat(persisted.domainHistoryVKey.createVKey())
.isEqualTo(
VKey.create(
HistoryEntry.class,
new DomainHistoryId("domainRepoId", 10L),
Key.create(Key.create(DomainBase.class, "domainRepoId"), HistoryEntry.class, 10L)));
}
@TestOfyAndSql
@@ -69,9 +74,12 @@ class DomainHistoryVKeyTest {
Key<HistoryEntry> ofyKey =
Key.create(Key.create(DomainBase.class, "domainRepoId"), HistoryEntry.class, 10L);
DomainHistoryVKey domainHistoryVKey = DomainHistoryVKey.create(ofyKey);
assertThat(domainHistoryVKey.getKind()).isEqualTo(HistoryEntry.class);
assertThat(domainHistoryVKey.getOfyKey()).isEqualTo(ofyKey);
assertThat(domainHistoryVKey.getSqlKey()).isEqualTo(new DomainHistoryId("domainRepoId", 10L));
assertThat(domainHistoryVKey.createOfyKey()).isEqualTo(ofyKey);
assertThat(domainHistoryVKey.createSqlKey())
.isEqualTo(new DomainHistoryId("domainRepoId", 10L));
assertThat(domainHistoryVKey.createVKey())
.isEqualTo(
VKey.create(HistoryEntry.class, new DomainHistoryId("domainRepoId", 10L), ofyKey));
}
@EntityForTesting

View File

@@ -17,6 +17,8 @@ package google.registry.privileges.secretmanager;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
import com.google.cloud.secretmanager.v1.SecretVersion.State;
import google.registry.privileges.secretmanager.SecretManagerClient.NoSuchSecretResourceException;
import google.registry.privileges.secretmanager.SecretManagerClient.SecretAlreadyExistsException;
@@ -54,11 +56,14 @@ public class SecretManagerClientTest {
private String secretId;
@BeforeAll
static void beforeAll() {
static void beforeAll() throws IOException {
String environmentName = System.getProperty("test.gcp_integration.env");
if (environmentName != null) {
secretManagerClient =
SecretManagerModule.provideSecretManagerClient(
SecretManagerServiceSettings.newBuilder()
.setCredentialsProvider(() -> GoogleCredentials.getApplicationDefault())
.build(),
String.format("domain-registry-%s", environmentName),
new Retrier(new SystemSleeper(), 1));
isUnitTest = false;

View File

@@ -454,10 +454,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
.setEnvIsAdmin(userInfo.isAdmin());
}
if (clock != null) {
helper.setClock(() -> clock.nowUtc().getMillis());
}
if (withLocalModules) {
helper.setEnvInstance("0");
}

View File

@@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import java.lang.reflect.Field;
@@ -160,6 +161,14 @@ class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocatio
private static boolean isDualDatabaseTest(ExtensionContext context) {
Object testInstance = context.getTestInstance().orElseThrow(RuntimeException::new);
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class);
// If the test method is declared in its parent class,
// e.g. google.registry.flows.ResourceFlowTestCase.testRequiresLogin,
// we don't consider it is a DualDatabaseTest. This is because there may exist some subclasses
// that have not been migrated to DualDatabaseTest.
boolean isDeclaredTestMethod =
ImmutableSet.copyOf(testInstance.getClass().getDeclaredMethods())
.contains(context.getTestMethod().orElseThrow(RuntimeException::new));
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class)
&& isDeclaredTestMethod;
}
}

View File

@@ -17,7 +17,8 @@ package google.registry.tools;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
@@ -116,7 +117,7 @@ public abstract class CommandTestCase<C extends Command> {
} finally {
// Clear the session cache so that subsequent reads for verification purposes hit Datastore.
// This primarily matters for AutoTimestamp fields, which otherwise won't have updated values.
ofy().clearSessionCache();
tm().clearSessionCache();
// Reset back to UNITTEST environment.
RegistryToolEnvironment.UNITTEST.setup(systemPropertyExtension);
}
@@ -185,12 +186,12 @@ public abstract class CommandTestCase<C extends Command> {
/** Reloads the given resource from Datastore. */
<T> T reloadResource(T resource) {
return ofy().load().entity(resource).now();
return transactIfJpaTm(() -> tm().load(resource));
}
/** Returns count of all poll messages in Datastore. */
int getPollMessageCount() {
return ofy().load().type(PollMessage.class).count();
return transactIfJpaTm(() -> tm().loadAll(PollMessage.class).size());
}
/**

View File

@@ -16,24 +16,28 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenType;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import java.util.Collection;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import java.util.Arrays;
import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link DeleteAllocationTokensCommand}. */
@DualDatabaseTest
class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocationTokensCommand> {
private AllocationToken preRed1;
@@ -54,38 +58,38 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
othrNot = persistToken("asdgfho7HASDS", null, false);
}
@Test
@TestOfyAndSql
void test_deleteOnlyUnredeemedTokensWithPrefix() throws Exception {
runCommandForced("--prefix", "prefix");
assertThat(reloadTokens(preNot1, preNot2)).isEmpty();
assertNonexistent(preNot1, preNot2);
assertThat(reloadTokens(preRed1, preRed2, othrRed, othrNot))
.containsExactly(preRed1, preRed2, othrRed, othrNot);
}
@Test
@TestOfyAndSql
void test_deleteSingleAllocationToken() throws Exception {
runCommandForced("--prefix", "asdgfho7HASDS");
assertThat(reloadTokens(othrNot)).isEmpty();
assertNonexistent(othrNot);
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed))
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed);
}
@Test
@TestOfyAndSql
void test_deleteParticularTokens() throws Exception {
runCommandForced("--tokens", "prefix2978204,asdgfho7HASDS");
assertThat(reloadTokens(preNot1, othrNot)).isEmpty();
assertNonexistent(preNot1, othrNot);
assertThat(reloadTokens(preRed1, preRed2, preNot2, othrRed))
.containsExactly(preRed1, preRed2, preNot2, othrRed);
}
@Test
@TestOfyAndSql
void test_deleteTokensWithNonExistentPrefix_doesNothing() throws Exception {
runCommandForced("--prefix", "nonexistent");
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot);
}
@Test
@TestOfyAndSql
void test_dryRun_deletesNothing() throws Exception {
runCommandForced("--prefix", "prefix", "--dry_run");
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
@@ -93,27 +97,27 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
assertInStdout("Would delete tokens: prefix2978204, prefix8ZZZhs8");
}
@Test
@TestOfyAndSql
void test_defaultOptions_doesntDeletePerDomainTokens() throws Exception {
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
runCommandForced("--prefix", "prefix");
assertThat(reloadTokens(preNot1, preNot2)).isEmpty();
assertNonexistent(preNot1, preNot2);
assertThat(reloadTokens(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot))
.containsExactly(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot);
}
@Test
@TestOfyAndSql
void test_withDomains_doesDeletePerDomainTokens() throws Exception {
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
runCommandForced("--prefix", "prefix", "--with_domains");
assertThat(reloadTokens(preNot1, preNot2, preDom1)).isEmpty();
assertNonexistent(preNot1, preNot2, preDom1);
assertThat(reloadTokens(preRed1, preRed2, preDom2, othrRed, othrNot))
.containsExactly(preRed1, preRed2, preDom2, othrRed, othrNot);
}
@Test
@TestOfyAndSql
void testSkipUnlimitedUseTokens() throws Exception {
AllocationToken unlimitedUseToken =
persistResource(
@@ -125,17 +129,18 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
assertThat(reloadTokens(unlimitedUseToken)).containsExactly(unlimitedUseToken);
}
@Test
@TestOfyAndSql
void test_batching() throws Exception {
for (int i = 0; i < 50; i++) {
persistToken(String.format("batch%2d", i), null, i % 2 == 0);
}
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56);
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(56);
runCommandForced("--prefix", "batch");
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56 - 25);
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size()))
.isEqualTo(56 - 25);
}
@Test
@TestOfyAndSql
void test_prefixIsRequired() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, this::runCommandForced);
@@ -144,7 +149,7 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
}
@Test
@TestOfyAndSql
void testFailure_bothPrefixAndTokens() {
IllegalArgumentException thrown =
assertThrows(
@@ -155,7 +160,7 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
}
@Test
@TestOfyAndSql
void testFailure_emptyPrefix() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));
@@ -173,12 +178,16 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
String domainToPersist = domainName != null ? domainName : "example.foo";
DomainBase domain = persistActiveDomain(domainToPersist);
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1051L);
builder.setRedemptionHistoryEntry(DomainHistoryVKey.create(historyEntryKey));
builder.setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey));
}
return persistResource(builder.build());
}
private static Collection<AllocationToken> reloadTokens(AllocationToken... tokens) {
return ofy().load().entities(tokens).values();
private static ImmutableList<AllocationToken> reloadTokens(AllocationToken... tokens) {
return transactIfJpaTm(() -> tm().loadAll(ImmutableSet.copyOf(tokens)));
}
private static void assertNonexistent(AllocationToken... tokens) {
Arrays.stream(tokens).forEach(t -> transactIfJpaTm(() -> assertThat(tm().exists(t)).isFalse()));
}
}

View File

@@ -17,7 +17,8 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.DatabaseHelper.assertAllocationTokens;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -39,11 +40,14 @@ import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.DeterministicStringGenerator.Rule;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.FakeSleeper;
import google.registry.testing.TestOfyAndSql;
import google.registry.util.Retrier;
import google.registry.util.StringGenerator.Alphabets;
import java.io.File;
@@ -51,10 +55,10 @@ import java.util.Collection;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
/** Unit tests for {@link GenerateAllocationTokensCommand}. */
@DualDatabaseTest
class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAllocationTokensCommand> {
@BeforeEach
@@ -64,14 +68,14 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
new Retrier(new FakeSleeper(new FakeClock(DateTime.parse("2000-01-01TZ"))), 3);
}
@Test
@TestOfyAndSql
void testSuccess_oneToken() throws Exception {
runCommand("--prefix", "blah", "--number", "1", "--length", "9");
assertAllocationTokens(createToken("blah123456789", null, null));
assertInStdout("blah123456789");
}
@Test
@TestOfyAndSql
void testSuccess_threeTokens() throws Exception {
runCommand("--prefix", "foo", "--number", "3", "--length", "10");
assertAllocationTokens(
@@ -81,14 +85,14 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
assertInStdout("foo123456789A\nfooBCDEFGHJKL\nfooMNPQRSTUVW");
}
@Test
@TestOfyAndSql
void testSuccess_defaults() throws Exception {
runCommand("--number", "1");
assertAllocationTokens(createToken("123456789ABCDEFG", null, null));
assertInStdout("123456789ABCDEFG");
}
@Test
@TestOfyAndSql
void testSuccess_retry() throws Exception {
command = spy(command);
RemoteApiException fakeException = new RemoteApiException("foo", "foo", "foo", new Exception());
@@ -103,7 +107,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
verify(command, times(3)).saveTokens(ArgumentMatchers.any());
}
@Test
@TestOfyAndSql
void testSuccess_tokenCollision() throws Exception {
AllocationToken existingToken =
persistResource(
@@ -116,24 +120,24 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
assertInStdout("DEADBEEFDEFGHJKLMNPQ");
}
@Test
@TestOfyAndSql
void testSuccess_dryRun_outputsButDoesntSave() throws Exception {
runCommand("--prefix", "foo", "--number", "2", "--length", "10", "--dry_run");
assertAllocationTokens();
assertInStdout("foo123456789A\nfooBCDEFGHJKL");
}
@Test
@TestOfyAndSql
void testSuccess_largeNumberOfTokens() throws Exception {
command.stringGenerator =
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
runCommand("--prefix", "ooo", "--number", "100", "--length", "16");
// The deterministic string generator makes it too much hassle to assert about each token, so
// just assert total number.
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(100);
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(100);
}
@Test
@TestOfyAndSql
void testSuccess_domainNames() throws Exception {
createTld("tld");
File domainNamesFile = tmpDir.resolve("domain_names.txt").toFile();
@@ -147,7 +151,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
"foo1.tld,123456789ABCDEFG\nboo2.tld,HJKLMNPQRSTUVWXY\nbaz9.tld,Zabcdefghijkmnop");
}
@Test
@TestOfyAndSql
void testSuccess_promotionToken() throws Exception {
DateTime promoStart = DateTime.now(UTC);
DateTime promoEnd = promoStart.plusMonths(1);
@@ -181,24 +185,24 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.build());
}
@Test
@TestOfyAndSql
void testSuccess_specifyTokens() throws Exception {
runCommand("--tokens", "foobar,foobaz");
assertAllocationTokens(createToken("foobar", null, null), createToken("foobaz", null, null));
assertInStdout("foobar", "foobaz");
}
@Test
@TestOfyAndSql
void testSuccess_specifyManyTokens() throws Exception {
command.stringGenerator =
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
Collection<String> sampleTokens = command.stringGenerator.createStrings(13, 100);
runCommand("--tokens", Joiner.on(",").join(sampleTokens));
assertInStdout(Iterables.toArray(sampleTokens, String.class));
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(100);
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(100);
}
@Test
@TestOfyAndSql
void testFailure_mustSpecifyNumberOfTokensOrDomainsFile() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommand("--prefix", "FEET"));
@@ -207,7 +211,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
}
@Test
@TestOfyAndSql
void testFailure_mustNotSpecifyBothNumberOfTokensAndDomainsFile() {
IllegalArgumentException thrown =
assertThrows(
@@ -222,7 +226,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
}
@Test
@TestOfyAndSql
void testFailure_mustNotSpecifyBothNumberOfTokensAndTokenStrings() {
IllegalArgumentException thrown =
assertThrows(
@@ -237,7 +241,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
}
@Test
@TestOfyAndSql
void testFailure_mustNotSpecifyBothTokenStringsAndDomainsFile() {
IllegalArgumentException thrown =
assertThrows(
@@ -252,7 +256,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
}
@Test
@TestOfyAndSql
void testFailure_specifiesAlreadyExistingToken() throws Exception {
runCommand("--tokens", "foobar");
beforeEachCommandTestCase(); // reset the command variables
@@ -263,7 +267,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Cannot create specified tokens; the following tokens already exist: [foobar]");
}
@Test
@TestOfyAndSql
void testFailure_invalidTokenType() {
ParameterException thrown =
assertThrows(
@@ -274,7 +278,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isEqualTo("Invalid value for -t parameter. Allowed values:[SINGLE_USE, UNLIMITED_USE]");
}
@Test
@TestOfyAndSql
void testFailure_invalidTokenStatusTransition() {
assertThat(
assertThrows(
@@ -289,7 +293,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
.isInstanceOf(IllegalArgumentException.class);
}
@Test
@TestOfyAndSql
void testFailure_lengthOfZero() {
IllegalArgumentException thrown =
assertThrows(
@@ -301,7 +305,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
"Token length should not be 0. To generate exact tokens, use the --tokens parameter.");
}
@Test
@TestOfyAndSql
void testFailure_unlimitedUseMustHaveTransitions() {
assertThat(
assertThrows(
@@ -313,7 +317,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
private AllocationToken createToken(
String token,
@Nullable DomainHistoryVKey redemptionHistoryEntry,
@Nullable VKey<? extends HistoryEntry> redemptionHistoryEntry,
@Nullable String domainName) {
AllocationToken.Builder builder =
new AllocationToken.Builder().setToken(token).setTokenType(SINGLE_USE);

View File

@@ -28,7 +28,7 @@ import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.token.AllocationToken;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.model.reporting.HistoryEntry;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@@ -85,7 +85,7 @@ class GetAllocationTokenCommandTest extends CommandTestCase<GetAllocationTokenCo
.setTokenType(SINGLE_USE)
.setDomainName("fqqdn.tld")
.setRedemptionHistoryEntry(
DomainHistoryVKey.create(Key.create(createHistoryEntryForEppResource(domain))))
HistoryEntry.createVKey(Key.create(createHistoryEntryForEppResource(domain))))
.build());
runCommand("foo");
assertInStdout(

View File

@@ -30,12 +30,14 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
@DualDatabaseTest
class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocationTokensCommand> {
@Test
@TestOfyAndSql
void testUpdateTlds_setTlds() throws Exception {
AllocationToken token =
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
@@ -43,7 +45,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(token).getAllowedTlds()).containsExactly("tld", "example");
}
@Test
@TestOfyAndSql
void testUpdateTlds_clearTlds() throws Exception {
AllocationToken token =
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
@@ -51,7 +53,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(token).getAllowedTlds()).isEmpty();
}
@Test
@TestOfyAndSql
void testUpdateClientIds_setClientIds() throws Exception {
AllocationToken token =
persistResource(
@@ -61,7 +63,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.containsExactly("clientone", "clienttwo");
}
@Test
@TestOfyAndSql
void testUpdateClientIds_clearClientIds() throws Exception {
AllocationToken token =
persistResource(
@@ -70,14 +72,14 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(token).getAllowedRegistrarIds()).isEmpty();
}
@Test
@TestOfyAndSql
void testUpdateDiscountFraction() throws Exception {
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
runCommandForced("--prefix", "token", "--discount_fraction", "0.15");
assertThat(reloadResource(token).getDiscountFraction()).isEqualTo(0.15);
}
@Test
@TestOfyAndSql
void testUpdateDiscountPremiums() throws Exception {
AllocationToken token =
persistResource(
@@ -88,14 +90,14 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(token).shouldDiscountPremiums()).isFalse();
}
@Test
@TestOfyAndSql
void testUpdateDiscountYears() throws Exception {
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
runCommandForced("--prefix", "token", "--discount_years", "4");
assertThat(reloadResource(token).getDiscountYears()).isEqualTo(4);
}
@Test
@TestOfyAndSql
void testUpdateStatusTransitions() throws Exception {
DateTime now = DateTime.now(UTC);
AllocationToken token = persistResource(builderWithPromo().build());
@@ -110,7 +112,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.containsExactly(START_OF_TIME, NOT_STARTED, now.minusDays(1), VALID, now, CANCELLED);
}
@Test
@TestOfyAndSql
void testUpdateStatusTransitions_badTransitions() {
DateTime now = DateTime.now(UTC);
persistResource(builderWithPromo().build());
@@ -130,7 +132,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.isEqualTo("tokenStatusTransitions map cannot transition from NOT_STARTED to ENDED.");
}
@Test
@TestOfyAndSql
void testUpdate_onlyWithPrefix() throws Exception {
AllocationToken token =
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
@@ -146,7 +148,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(otherToken).getAllowedTlds()).isEmpty();
}
@Test
@TestOfyAndSql
void testUpdate_onlyTokensProvided() throws Exception {
AllocationToken firstToken =
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
@@ -170,7 +172,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloadResource(thirdToken).getAllowedTlds()).isEmpty();
}
@Test
@TestOfyAndSql
void testDoNothing() throws Exception {
AllocationToken token =
persistResource(
@@ -186,7 +188,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
assertThat(reloaded.getDiscountFraction()).isEqualTo(token.getDiscountFraction());
}
@Test
@TestOfyAndSql
void testFailure_bothTokensAndPrefix() {
assertThat(
assertThrows(
@@ -196,7 +198,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
}
@Test
@TestOfyAndSql
void testFailure_neitherTokensNorPrefix() {
assertThat(
assertThrows(
@@ -205,7 +207,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
}
@Test
@TestOfyAndSql
void testFailure_emptyPrefix() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));

View File

@@ -278,8 +278,8 @@ class google.registry.model.domain.DomainHistory {
}
class google.registry.model.domain.GracePeriod {
google.registry.model.domain.rgp.GracePeriodStatus type;
google.registry.persistence.VKey<google.registry.model.billing.BillingEvent$OneTime> billingEventOneTime;
google.registry.persistence.VKey<google.registry.model.billing.BillingEvent$Recurring> billingEventRecurring;
google.registry.persistence.BillingVKey$BillingEventVKey billingEventOneTime;
google.registry.persistence.BillingVKey$BillingRecurrenceVKey billingEventRecurring;
java.lang.Long gracePeriodId;
java.lang.String clientId;
org.joda.time.DateTime expirationTime;
@@ -888,10 +888,17 @@ enum google.registry.model.transfer.TransferStatus {
SERVER_APPROVED;
SERVER_CANCELLED;
}
class google.registry.persistence.DomainHistoryVKey {
com.googlecode.objectify.Key<T> ofyKey;
java.lang.Class<? extends T> kind;
java.lang.Long domainHistoryId;
java.lang.Object sqlKey;
java.lang.String domainRepoId;
class google.registry.persistence.BillingVKey$BillingEventVKey {
java.lang.Long billingId;
java.lang.Long historyRevisionId;
java.lang.String repoId;
}
class google.registry.persistence.BillingVKey$BillingRecurrenceVKey {
java.lang.Long billingId;
java.lang.Long historyRevisionId;
java.lang.String repoId;
}
class google.registry.persistence.DomainHistoryVKey {
java.lang.Long historyRevisionId;
java.lang.String repoId;
}

View File

@@ -261,11 +261,11 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2020-12-05 00:29:03.934355</td>
<td class="property_value">2020-12-21 17:41:59.495189</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
<td id="lastFlywayFile" class="property_value">V81__drop_spec11_fkeys.sql</td>
<td id="lastFlywayFile" class="property_value">V83__add_indexes_on_domainhost.sql</td>
</tr>
</tbody>
</table>
@@ -284,7 +284,7 @@ td.section {
generated on
</text>
<text text-anchor="start" x="4027.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
2020-12-05 00:29:03.934355
2020-12-21 17:41:59.495189
</text>
<polygon fill="none" stroke="#888888" points="3940.44,-4 3940.44,-44 4205.44,-44 4205.44,-4 3940.44,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">
@@ -568,14 +568,14 @@ td.section {
</g> <!-- billingevent_a57d1815&#45;&gt;domainhistory_a54cc226 -->
<g id="edge29" class="edge">
<title>billingevent_a57d1815:w-&gt;domainhistory_a54cc226:e</title>
<path fill="none" stroke="black" d="M3219.23,-1185.48C3119.47,-1183.67 2900.01,-1171.05 2844,-1231.68 2807.56,-1271.12 2864.68,-1677.42 2826,-1714.68 2771.83,-1766.87 2203.88,-1761.13 2149,-1709.68 2124.77,-1686.96 2153.47,-1433.39 2130.83,-1389.98" />
<path fill="none" stroke="black" d="M3219.23,-1185.48C3119.47,-1183.66 2899.97,-1171.01 2844,-1231.68 2806.23,-1272.62 2866.09,-1694 2826,-1732.68 2771.86,-1784.9 2203.77,-1779.24 2149,-1727.68 2123.44,-1703.61 2155.05,-1434.99 2131.08,-1390.04" />
<polygon fill="black" stroke="black" points="3227.5,-1185.57 3237.45,-1190.18 3232.5,-1185.62 3237.5,-1185.68 3237.5,-1185.68 3237.5,-1185.68 3232.5,-1185.62 3237.55,-1181.18 3227.5,-1185.57 3227.5,-1185.57" />
<ellipse fill="none" stroke="black" cx="3223.5" cy="-1185.53" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2120.64,-1388.2 2126.91,-1380.41 2128.47,-1381.66 2122.2,-1389.45 2120.64,-1388.2" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2126.89,-1386.81 " />
<polygon fill="black" stroke="black" points="2124.54,-1391.33 2130.81,-1383.54 2132.37,-1384.8 2126.1,-1392.59 2124.54,-1391.33" />
<polyline fill="none" stroke="black" points="2126.89,-1386.81 2130.79,-1389.95 " />
<text text-anchor="start" x="2555" y="-1755.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<polygon fill="black" stroke="black" points="2120.69,-1388.22 2126.88,-1380.37 2128.45,-1381.6 2122.26,-1389.46 2120.69,-1388.22" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2126.93,-1386.77 " />
<polygon fill="black" stroke="black" points="2124.62,-1391.32 2130.81,-1383.46 2132.38,-1384.7 2126.19,-1392.55 2124.62,-1391.32" />
<polyline fill="none" stroke="black" points="2126.93,-1386.77 2130.86,-1389.86 " />
<text text-anchor="start" x="2555" y="-1773.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fk_billing_event_domain_history
</text>
</g> <!-- registrar_6e1503e3 -->
@@ -617,14 +617,14 @@ td.section {
</g> <!-- billingevent_a57d1815&#45;&gt;registrar_6e1503e3 -->
<g id="edge49" class="edge">
<title>billingevent_a57d1815:w-&gt;registrar_6e1503e3:e</title>
<path fill="none" stroke="black" d="M3222.22,-1215.14C3145.17,-1290.97 2857.5,-1753.9 2826,-1770.68 2621.48,-1879.58 2536.71,-1789.68 2305,-1789.68 2305,-1789.68 2305,-1789.68 640.5,-1789.68 391.14,-1789.68 324.15,-1646.84 217,-1421.68 199.61,-1385.13 230.41,-733.26 198.48,-654.45" />
<polygon fill="black" stroke="black" points="3229.25,-1210.33 3240.04,-1208.39 3233.37,-1207.5 3237.5,-1204.68 3237.5,-1204.68 3237.5,-1204.68 3233.37,-1207.5 3234.96,-1200.96 3229.25,-1210.33 3229.25,-1210.33" />
<ellipse fill="none" stroke="black" cx="3225.95" cy="-1212.59" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="188.45,-650.76 195.89,-644.08 197.22,-645.57 189.78,-652.25 188.45,-650.76" />
<polyline fill="none" stroke="black" points="191.5,-646.68 194.84,-650.4 " />
<polygon fill="black" stroke="black" points="191.79,-654.48 199.23,-647.8 200.57,-649.29 193.12,-655.97 191.79,-654.48" />
<polyline fill="none" stroke="black" points="194.84,-650.4 198.18,-654.12 " />
<text text-anchor="start" x="1546" y="-1793.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<path fill="none" stroke="black" d="M3222.55,-1214.98C3145.79,-1291.68 2858.1,-1771.28 2826,-1788.68 2622.29,-1899.09 2536.71,-1807.68 2305,-1807.68 2305,-1807.68 2305,-1807.68 640.5,-1807.68 385.83,-1807.68 323.7,-1652.92 217,-1421.68 200.04,-1384.93 230.46,-733.23 198.49,-654.45" />
<polygon fill="black" stroke="black" points="3229.27,-1210.35 3240.05,-1208.38 3233.38,-1207.51 3237.5,-1204.68 3237.5,-1204.68 3237.5,-1204.68 3233.38,-1207.51 3234.95,-1200.97 3229.27,-1210.35 3229.27,-1210.35" />
<ellipse fill="none" stroke="black" cx="3225.97" cy="-1212.62" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="188.45,-650.76 195.89,-644.08 197.22,-645.56 189.79,-652.25 188.45,-650.76" />
<polyline fill="none" stroke="black" points="191.5,-646.68 194.84,-650.39 " />
<polygon fill="black" stroke="black" points="191.79,-654.48 199.23,-647.8 200.57,-649.28 193.13,-655.97 191.79,-654.48" />
<polyline fill="none" stroke="black" points="194.84,-650.39 198.18,-654.11 " />
<text text-anchor="start" x="1546" y="-1811.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fk_billing_event_registrar_id
</text>
</g> <!-- billingcancellation_6eedf614 -->
@@ -1754,14 +1754,14 @@ td.section {
</g> <!-- pollmessage_614a523e&#45;&gt;domainhistory_a54cc226 -->
<g id="edge41" class="edge">
<title>pollmessage_614a523e:w-&gt;domainhistory_a54cc226:e</title>
<path fill="none" stroke="black" d="M3236.68,-451.33C3193.59,-492.92 3251.04,-717.2 3211,-769.68 3103.7,-910.31 2943.71,-777.57 2844,-923.68 2820.69,-957.83 2855.47,-1638.68 2826,-1667.68 2799.19,-1694.06 2176.77,-1696.04 2149,-1670.68 2104.49,-1630.02 2177.5,-1411.56 2132.78,-1386.1" />
<path fill="none" stroke="black" d="M3236.68,-451.33C3193.59,-492.92 3251.04,-717.2 3211,-769.68 3103.7,-910.31 2943.67,-777.54 2844,-923.68 2820.11,-958.71 2856.25,-1656.97 2826,-1686.68 2799.16,-1713.03 2176.7,-1714.12 2149,-1688.68 2126.25,-1667.78 2151.43,-1433.27 2130.77,-1390.39" />
<polygon fill="black" stroke="black" points="3244.2,-448.35 3255.15,-448.86 3248.85,-446.51 3253.5,-444.68 3253.5,-444.68 3253.5,-444.68 3248.85,-446.51 3251.85,-440.49 3244.2,-448.35 3244.2,-448.35" />
<ellipse fill="none" stroke="black" cx="3240.48" cy="-449.82" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.77,-1388.77 2125.17,-1379.06 2127.11,-1379.54 2124.71,-1389.25 2122.77,-1388.77" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2127.85,-1384.88 " />
<polygon fill="black" stroke="black" points="2127.62,-1389.97 2130.03,-1380.27 2131.97,-1380.75 2129.56,-1390.45 2127.62,-1389.97" />
<polyline fill="none" stroke="black" points="2127.85,-1384.88 2132.71,-1386.08 " />
<text text-anchor="start" x="2550.5" y="-1692.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<polygon fill="black" stroke="black" points="2120.49,-1388.11 2127.03,-1380.55 2128.54,-1381.85 2122,-1389.42 2120.49,-1388.11" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2126.78,-1386.94 " />
<polygon fill="black" stroke="black" points="2124.27,-1391.38 2130.81,-1383.81 2132.32,-1385.12 2125.79,-1392.69 2124.27,-1391.38" />
<polyline fill="none" stroke="black" points="2126.78,-1386.94 2130.57,-1390.21 " />
<text text-anchor="start" x="2550.5" y="-1711.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fk_poll_message_domain_history
</text>
</g> <!-- host_f21b78de -->
@@ -2252,21 +2252,29 @@ td.section {
</g> <!-- domainhistoryhost_9f3f23ee -->
<g id="node18" class="node">
<title>domainhistoryhost_9f3f23ee</title>
<polygon fill="#ebcef2" stroke="transparent" points="2502.5,-1634.68 2502.5,-1653.68 2726.5,-1653.68 2726.5,-1634.68 2502.5,-1634.68" />
<text text-anchor="start" x="2504.5" y="-1641.48" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
<polygon fill="#ebcef2" stroke="transparent" points="2502.5,-1653.68 2502.5,-1672.68 2726.5,-1672.68 2726.5,-1653.68 2502.5,-1653.68" />
<text text-anchor="start" x="2504.5" y="-1660.48" font-family="Helvetica,sans-Serif" font-weight="bold" font-style="italic" font-size="14.00">
public.DomainHistoryHost
</text>
<polygon fill="#ebcef2" stroke="transparent" points="2726.5,-1634.68 2726.5,-1653.68 2800.5,-1653.68 2800.5,-1634.68 2726.5,-1634.68" />
<text text-anchor="start" x="2761.5" y="-1640.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<polygon fill="#ebcef2" stroke="transparent" points="2726.5,-1653.68 2726.5,-1672.68 2800.5,-1672.68 2800.5,-1653.68 2726.5,-1653.68" />
<text text-anchor="start" x="2761.5" y="-1659.48" font-family="Helvetica,sans-Serif" font-size="14.00">
[table]
</text>
<text text-anchor="start" x="2504.5" y="-1621.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<text text-anchor="start" x="2504.5" y="-1640.48" font-family="Helvetica,sans-Serif" font-size="14.00">
domain_history_history_revision_id
</text>
<text text-anchor="start" x="2720.5" y="-1640.48" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="2728.5" y="-1640.48" font-family="Helvetica,sans-Serif" font-size="14.00">
int8 not null
</text>
<text text-anchor="start" x="2504.5" y="-1621.48" font-family="Helvetica,sans-Serif" font-size="14.00">
host_repo_id
</text>
<text text-anchor="start" x="2720.5" y="-1621.48" font-family="Helvetica,sans-Serif" font-size="14.00">
</text>
<text text-anchor="start" x="2728.5" y="-1621.48" font-family="Helvetica,sans-Serif" font-size="14.00">
int8 not null
text
</text>
<text text-anchor="start" x="2504.5" y="-1602.48" font-family="Helvetica,sans-Serif" font-size="14.00">
domain_history_domain_repo_id
@@ -2276,31 +2284,31 @@ td.section {
<text text-anchor="start" x="2728.5" y="-1602.48" font-family="Helvetica,sans-Serif" font-size="14.00">
text not null
</text>
<polygon fill="none" stroke="#888888" points="2501.5,-1596.18 2501.5,-1655.18 2801.5,-1655.18 2801.5,-1596.18 2501.5,-1596.18" />
<polygon fill="none" stroke="#888888" points="2501.5,-1595.68 2501.5,-1673.68 2801.5,-1673.68 2801.5,-1595.68 2501.5,-1595.68" />
</g> <!-- domainhistoryhost_9f3f23ee&#45;&gt;domainhistory_a54cc226 -->
<g id="edge34" class="edge">
<title>domainhistoryhost_9f3f23ee:w-&gt;domainhistory_a54cc226:e</title>
<path fill="none" stroke="black" d="M2484.5,-1599.62C2477.03,-1593.04 2472.75,-1582.78 2459,-1577.68 2393.88,-1553.5 2197.51,-1587.39 2149,-1537.68 2112.74,-1500.51 2169.71,-1316.37 2133.01,-1290.66" />
<polygon fill="black" stroke="black" points="2492.08,-1602.32 2499.99,-1609.91 2496.79,-1604 2501.5,-1605.68 2501.5,-1605.68 2501.5,-1605.68 2496.79,-1604 2503.01,-1601.44 2492.08,-1602.32 2492.08,-1602.32" />
<ellipse fill="none" stroke="black" cx="2488.31" cy="-1600.98" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.53,-1292.75 2125.39,-1283.17 2127.3,-1283.74 2124.45,-1293.32 2122.53,-1292.75" />
<polyline fill="none" stroke="black" points="2123,-1287.68 2127.79,-1289.1 " />
<polygon fill="black" stroke="black" points="2127.32,-1294.18 2130.18,-1284.6 2132.09,-1285.17 2129.24,-1294.75 2127.32,-1294.18" />
<polyline fill="none" stroke="black" points="2127.79,-1289.1 2132.58,-1290.53 " />
<text text-anchor="start" x="2209.5" y="-1581.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<path fill="none" stroke="black" d="M2483.6,-1602.41C2476.45,-1599.52 2470.57,-1595.37 2459,-1592.68 2391.03,-1576.88 2196.88,-1593.43 2149,-1542.68 2112.51,-1503.99 2171.14,-1315.28 2132.74,-1290.39" />
<polygon fill="black" stroke="black" points="2491.66,-1603.88 2500.69,-1610.1 2496.58,-1604.78 2501.5,-1605.68 2501.5,-1605.68 2501.5,-1605.68 2496.58,-1604.78 2502.31,-1601.25 2491.66,-1603.88 2491.66,-1603.88" />
<ellipse fill="none" stroke="black" cx="2487.73" cy="-1603.16" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.62,-1292.76 2125.31,-1283.13 2127.23,-1283.66 2124.55,-1293.3 2122.62,-1292.76" />
<polyline fill="none" stroke="black" points="2123,-1287.68 2127.82,-1289.02 " />
<polygon fill="black" stroke="black" points="2127.44,-1294.1 2130.12,-1284.47 2132.05,-1285.01 2129.36,-1294.64 2127.44,-1294.1" />
<polyline fill="none" stroke="black" points="2127.82,-1289.02 2132.63,-1290.36 " />
<text text-anchor="start" x="2209.5" y="-1596.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fka9woh3hu8gx5x0vly6bai327n
</text>
</g> <!-- domainhistoryhost_9f3f23ee&#45;&gt;domainhistory_a54cc226 -->
<g id="edge35" class="edge">
<title>domainhistoryhost_9f3f23ee:w-&gt;domainhistory_a54cc226:e</title>
<path fill="none" stroke="black" d="M2483.4,-1625.01C2337.73,-1630.44 2257.65,-1697.78 2149,-1592.68 2118.12,-1562.81 2161.46,-1411.66 2132.58,-1387.06" />
<polygon fill="black" stroke="black" points="2491.5,-1624.86 2501.58,-1629.18 2496.5,-1624.77 2501.5,-1624.68 2501.5,-1624.68 2501.5,-1624.68 2496.5,-1624.77 2501.42,-1620.18 2491.5,-1624.86 2491.5,-1624.86" />
<ellipse fill="none" stroke="black" cx="2487.5" cy="-1624.94" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.28,-1388.72 2125.61,-1379.3 2127.49,-1379.96 2124.16,-1389.39 2122.28,-1388.72" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2127.71,-1385.34 " />
<polygon fill="black" stroke="black" points="2126.99,-1390.39 2130.32,-1380.96 2132.21,-1381.63 2128.88,-1391.06 2126.99,-1390.39" />
<polyline fill="none" stroke="black" points="2127.71,-1385.34 2132.43,-1387.01 " />
<text text-anchor="start" x="2209.5" y="-1655.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<path fill="none" stroke="black" d="M2483.37,-1645C2337.45,-1650.16 2256.62,-1714.12 2149,-1607.68 2116.16,-1575.19 2164.36,-1412.23 2132.83,-1386.97" />
<polygon fill="black" stroke="black" points="2491.5,-1644.85 2501.58,-1649.18 2496.5,-1644.76 2501.5,-1644.68 2501.5,-1644.68 2501.5,-1644.68 2496.5,-1644.76 2501.42,-1640.18 2491.5,-1644.85 2491.5,-1644.85" />
<ellipse fill="none" stroke="black" cx="2487.5" cy="-1644.93" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.36,-1388.73 2125.54,-1379.25 2127.43,-1379.89 2124.26,-1389.37 2122.36,-1388.73" />
<polyline fill="none" stroke="black" points="2123,-1383.68 2127.74,-1385.26 " />
<polygon fill="black" stroke="black" points="2127.1,-1390.32 2130.28,-1380.84 2132.17,-1381.48 2129,-1390.96 2127.1,-1390.32" />
<polyline fill="none" stroke="black" points="2127.74,-1385.26 2132.48,-1386.85 " />
<text text-anchor="start" x="2209.5" y="-1673.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fka9woh3hu8gx5x0vly6bai327n
</text>
</g> <!-- domaintransactionrecord_6e77ff61 -->
@@ -2430,14 +2438,14 @@ td.section {
</g> <!-- graceperiodhistory_40ccc1f1&#45;&gt;domainhistory_a54cc226 -->
<g id="edge38" class="edge">
<title>graceperiodhistory_40ccc1f1:w-&gt;domainhistory_a54cc226:e</title>
<path fill="none" stroke="black" d="M2481.03,-1520.46C2406.48,-1519.12 2174.32,-1511.17 2149,-1486.68 2119.7,-1458.33 2159.5,-1315.54 2132.5,-1291.2" />
<polygon fill="black" stroke="black" points="2489.5,-1520.56 2499.45,-1525.18 2494.5,-1520.62 2499.5,-1520.68 2499.5,-1520.68 2499.5,-1520.68 2494.5,-1520.62 2499.55,-1516.18 2489.5,-1520.56 2489.5,-1520.56" />
<ellipse fill="none" stroke="black" cx="2485.5" cy="-1520.51" rx="4" ry="4" />
<path fill="none" stroke="black" d="M2481.33,-1520.94C2392.06,-1523.15 2201.35,-1537.31 2149,-1486.68 2119.7,-1458.33 2159.5,-1315.54 2132.5,-1291.2" />
<polygon fill="black" stroke="black" points="2489.5,-1520.82 2499.57,-1525.18 2494.5,-1520.75 2499.5,-1520.68 2499.5,-1520.68 2499.5,-1520.68 2494.5,-1520.75 2499.43,-1516.18 2489.5,-1520.82 2489.5,-1520.82" />
<ellipse fill="none" stroke="black" cx="2485.5" cy="-1520.88" rx="4" ry="4" />
<polygon fill="black" stroke="black" points="2122.2,-1292.71 2125.68,-1283.34 2127.55,-1284.03 2124.07,-1293.41 2122.2,-1292.71" />
<polyline fill="none" stroke="black" points="2123,-1287.68 2127.69,-1289.42 " />
<polygon fill="black" stroke="black" points="2126.88,-1294.45 2130.37,-1285.08 2132.24,-1285.77 2128.76,-1295.15 2126.88,-1294.45" />
<polyline fill="none" stroke="black" points="2127.69,-1289.42 2132.37,-1291.16 " />
<text text-anchor="start" x="2210" y="-1524.48" font-family="Helvetica,sans-Serif" font-size="14.00">
<text text-anchor="start" x="2210" y="-1528.48" font-family="Helvetica,sans-Serif" font-size="14.00">
fk7w3cx8d55q8bln80e716tr7b8
</text>
</g> <!-- graceperiodhistory_40ccc1f1&#45;&gt;domainhistory_a54cc226 -->
@@ -4790,6 +4798,11 @@ td.section {
<td class="minwidth">domain_history_history_revision_id</td>
<td class="minwidth">int8 not null</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">host_repo_id</td>
<td class="minwidth">text</td>
</tr>
<tr>
<td class="spacer"></td>
<td class="minwidth">domain_history_domain_repo_id</td>

File diff suppressed because it is too large Load Diff

View File

@@ -79,3 +79,5 @@ V78__add_history_id_for_redemption_history_entry.sql
V79__drop_foreign_keys_on_pollmessage.sql
V80__defer_bill_event_key.sql
V81__drop_spec11_fkeys.sql
V82__add_columns_to_restore_symmetric_billing_vkey.sql
V83__add_indexes_on_domainhost.sql

View File

@@ -0,0 +1,22 @@
-- Copyright 2020 The Nomulus Authors. All Rights Reserved.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
alter table "GracePeriod"
add column if not exists "billing_event_domain_repo_id" text;
alter table "GracePeriod"
add column if not exists "billing_recurrence_domain_repo_id" text;
alter table "GracePeriodHistory"
add column if not exists "billing_event_domain_repo_id" text;
alter table "GracePeriodHistory"
add column if not exists "billing_recurrence_domain_repo_id" text;

View File

@@ -0,0 +1,23 @@
-- 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 if exists "DomainHistoryHost"
add constraint UKt2e7ae3t8gcsxd13wjx2ka7ij unique (
domain_history_history_revision_id,
domain_history_domain_repo_id,
host_repo_id);
alter table if exists "DomainHost"
add constraint UKat9erbh52e4lg3jw6ai9wkjj9 unique (
domain_repo_id, host_repo_id);

View File

@@ -398,8 +398,10 @@
grace_period_id int8 not null,
billing_event_id int8,
billing_event_history_id int8,
billing_event_domain_repo_id text,
billing_recurrence_id int8,
billing_recurrence_history_id int8,
billing_recurrence_domain_repo_id text,
registrar_id text not null,
domain_repo_id text not null,
expiration_time timestamptz not null,
@@ -411,8 +413,10 @@
grace_period_history_revision_id int8 not null,
billing_event_id int8,
billing_event_history_id int8,
billing_event_domain_repo_id text,
billing_recurrence_id int8,
billing_recurrence_history_id int8,
billing_recurrence_domain_repo_id text,
registrar_id text not null,
domain_repo_id text not null,
expiration_time timestamptz not null,
@@ -764,6 +768,12 @@ create index IDXrh4xmrot9bd63o382ow9ltfig on "DomainHistory" (creation_time);
create index IDXaro1omfuaxjwmotk3vo00trwm on "DomainHistory" (history_registrar_id);
create index IDXsu1nam10cjes9keobapn5jvxj on "DomainHistory" (history_type);
create index IDX6w3qbtgce93cal2orjg1tw7b7 on "DomainHistory" (history_modification_time);
alter table if exists "DomainHistoryHost"
add constraint UKt2e7ae3t8gcsxd13wjx2ka7ij unique (domain_history_history_revision_id, domain_history_domain_repo_id, host_repo_id);
alter table if exists "DomainHost"
add constraint UKat9erbh52e4lg3jw6ai9wkjj9 unique (domain_repo_id, host_repo_id);
create index IDXj1mtx98ndgbtb1bkekahms18w on "GracePeriod" (domain_repo_id);
create index IDXd01j17vrpjxaerxdmn8bwxs7s on "GracePeriodHistory" (domain_repo_id);
create index IDXfg2nnjlujxo6cb9fha971bq2n on "HostHistory" (creation_time);

View File

@@ -533,7 +533,9 @@ CREATE TABLE public."GracePeriod" (
expiration_time timestamp with time zone NOT NULL,
type text NOT NULL,
billing_event_history_id bigint,
billing_recurrence_history_id bigint
billing_recurrence_history_id bigint,
billing_event_domain_repo_id text,
billing_recurrence_domain_repo_id text
);
@@ -552,7 +554,9 @@ CREATE TABLE public."GracePeriodHistory" (
expiration_time timestamp with time zone NOT NULL,
type text NOT NULL,
domain_history_revision_id bigint,
grace_period_id bigint NOT NULL
grace_period_id bigint NOT NULL,
billing_event_domain_repo_id text,
billing_recurrence_domain_repo_id text
);
@@ -1424,6 +1428,22 @@ ALTER TABLE ONLY public."RegistryLock"
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
--
-- Name: DomainHost ukat9erbh52e4lg3jw6ai9wkjj9; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainHost"
ADD CONSTRAINT ukat9erbh52e4lg3jw6ai9wkjj9 UNIQUE (domain_repo_id, host_repo_id);
--
-- Name: DomainHistoryHost ukt2e7ae3t8gcsxd13wjx2ka7ij; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."DomainHistoryHost"
ADD CONSTRAINT ukt2e7ae3t8gcsxd13wjx2ka7ij UNIQUE (domain_history_history_revision_id, domain_history_domain_repo_id, host_repo_id);
--
-- Name: allocation_token_domain_name_idx; Type: INDEX; Schema: public; Owner: -
--

View File

@@ -74,10 +74,12 @@ test {
useJUnitPlatform()
}
// Sets up integration test with a registry environment. The target environment is
// passed by the 'test.gcp_integration.env' property. Test runner must have been
// authorized to access the corresponding GCP project, e.g., by running 'gcloud auth'
// or placing a credential file at a well known place.
// Sets up integration test with a registry environment. The target environment
// is passed by the 'test.gcp_integration.env' property. Test runner must have
// been authorized to access the corresponding GCP project, e.g., by running
// 'gcloud auth application-default login' or by downloading a credential file
// and assign the path to it to the GOOGLE_APPLICATION_CREDENTIALS environment
// variable.
//
// A typical use case is to run tests from desktop that accesses Cloud resources. See
// core/src/test/java/google/registry/beam/initsql/BeamJpaModuleTest.java for an example.