Create a ClaimsListDualDatabaseDao (#1011)

The dual DAO takes care of switching between databases, comparing the
results of one to the results of the other, and caching the result. All
calls to ClaimsList retrieval or storing should use the
dual-database-DAO.

Previously, calls to comparing the lists were somewhat scattered
throughout the codebase. Now, there is one class for retrieval and
comparison (the dual DAO), one class for retrieval from SQL (the SQL
DAO), and one class for retrieval from Datastore (ClaimsListShard
itself, though the retrieval could be moved in to a separate DAO if we
wished).

In addition, we rename the ClaimsListDao to ClaimsListSqlDao
This commit is contained in:
gbrodman 2021-03-18 23:37:08 -04:00 committed by GitHub
parent 6bee440194
commit 87f096ae40
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 454 additions and 254 deletions

View file

@ -45,7 +45,7 @@ import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppResponse; import google.registry.model.eppoutput.EppResponse;
import google.registry.model.registry.Registry; import google.registry.model.registry.Registry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField; import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.util.Clock; import google.registry.util.Clock;
import java.util.HashSet; import java.util.HashSet;
import java.util.Optional; import java.util.Optional;
@ -104,7 +104,8 @@ public final class DomainClaimsCheckFlow implements Flow {
verifyClaimsPeriodNotEnded(registry, now); verifyClaimsPeriodNotEnded(registry, now);
} }
} }
Optional<String> claimKey = ClaimsListShard.get().getClaimKey(parsedDomain.parts().get(0)); Optional<String> claimKey =
ClaimsListDualDatabaseDao.get().getClaimKey(parsedDomain.parts().get(0));
launchChecksBuilder.add( launchChecksBuilder.add(
LaunchCheck.create( LaunchCheck.create(
LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null))); LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null)));

View file

@ -129,7 +129,7 @@ import google.registry.model.registry.label.ReservedList;
import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.tldconfig.idn.IdnLabelValidator; import google.registry.tldconfig.idn.IdnLabelValidator;
import google.registry.util.Idn; import google.registry.util.Idn;
@ -994,7 +994,7 @@ public class DomainFlowUtils {
InternetDomainName domainName, boolean hasSignedMarks, boolean hasClaimsNotice) InternetDomainName domainName, boolean hasSignedMarks, boolean hasClaimsNotice)
throws EppException { throws EppException {
boolean isInClaimsList = boolean isInClaimsList =
ClaimsListShard.get().getClaimKey(domainName.parts().get(0)).isPresent(); ClaimsListDualDatabaseDao.get().getClaimKey(domainName.parts().get(0)).isPresent();
if (hasClaimsNotice && !isInClaimsList) { if (hasClaimsNotice && !isInClaimsList) {
throw new UnexpectedClaimsNoticeException(domainName.toString()); throw new UnexpectedClaimsNoticeException(domainName.toString());
} }

View file

@ -37,6 +37,7 @@ import google.registry.model.common.TimedTransitionProperty.TimedTransition;
import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.PremiumList;
import google.registry.model.registry.label.ReservedList; import google.registry.model.registry.label.ReservedList;
import google.registry.model.smd.SignedMarkRevocationList; import google.registry.model.smd.SignedMarkRevocationList;
import google.registry.model.tmch.ClaimsListShard;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import google.registry.schema.replay.DatastoreOnlyEntity; import google.registry.schema.replay.DatastoreOnlyEntity;
import java.util.Optional; import java.util.Optional;
@ -58,6 +59,8 @@ public class DatabaseTransitionSchedule extends ImmutableObject implements Datas
/** The id of the transition schedule. */ /** The id of the transition schedule. */
public enum TransitionId { public enum TransitionId {
/** The schedule for migration of {@link ClaimsListShard} entities. */
CLAIMS_LIST,
/** The schedule for the migration of {@link PremiumList} and {@link ReservedList}. */ /** The schedule for the migration of {@link PremiumList} and {@link ReservedList}. */
DOMAIN_LABEL_LISTS, DOMAIN_LABEL_LISTS,
/** The schedule for the migration of the {@link SignedMarkRevocationList} entity. */ /** The schedule for the migration of the {@link SignedMarkRevocationList} entity. */

View file

@ -1,89 +0,0 @@
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.tmch;
import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration;
import static google.registry.model.CacheUtils.tryMemoizeWithExpiration;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.base.Supplier;
import com.google.common.flogger.FluentLogger;
import google.registry.util.NonFinalForTesting;
import java.util.Optional;
import javax.persistence.EntityManager;
/** Data access object for {@link ClaimsListShard}. */
public class ClaimsListDao {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** In-memory cache for claims list. */
@NonFinalForTesting
private static Supplier<Optional<ClaimsListShard>> cacheClaimsList =
tryMemoizeWithExpiration(getDomainLabelListCacheDuration(), ClaimsListDao::getLatestRevision);
private static void save(ClaimsListShard claimsList) {
jpaTm().transact(() -> jpaTm().getEntityManager().persist(claimsList));
}
/**
* Try to save the given {@link ClaimsListShard} into Cloud SQL. If the save fails, the error will
* be logged but no exception will be thrown.
*
* <p>This method is used during the dual-write phase of database migration as Datastore is still
* the authoritative database.
*/
static void trySave(ClaimsListShard claimsList) {
try {
ClaimsListDao.save(claimsList);
logger.atInfo().log(
"Inserted %,d claims into Cloud SQL, created at %s",
claimsList.getLabelsToKeys().size(), claimsList.getTmdbGenerationTime());
} catch (Throwable e) {
logger.atSevere().withCause(e).log("Error inserting claims into Cloud SQL");
}
}
/**
* Returns the most recent revision of the {@link ClaimsListShard} in Cloud SQL, if it exists.
* TODO(b/177569979): Change this method to package level access after dual-read phase.
* ClaimsListShard uses this method to retrieve claims list in Cloud SQL for the comparison, and
* ClaimsListShard is not in this package.
*/
public static Optional<ClaimsListShard> getLatestRevision() {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
Long revisionId =
em.createQuery("SELECT MAX(revisionId) FROM ClaimsList", Long.class)
.getSingleResult();
return em.createQuery(
"FROM ClaimsList cl LEFT JOIN FETCH cl.labelsToKeys WHERE cl.revisionId ="
+ " :revisionId",
ClaimsListShard.class)
.setParameter("revisionId", revisionId)
.getResultStream()
.findFirst();
});
}
/** Returns the most recent revision of the {@link ClaimsListShard}, from cache. */
public static Optional<ClaimsListShard> getLatestRevisionCached() {
return cacheClaimsList.get();
}
private ClaimsListDao() {}
}

View file

@ -0,0 +1,148 @@
// Copyright 2021 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.tmch;
import static google.registry.config.RegistryConfig.getDomainLabelListCacheDuration;
import static google.registry.model.CacheUtils.tryMemoizeWithExpiration;
import static google.registry.model.DatabaseMigrationUtils.isDatastore;
import static google.registry.model.DatabaseMigrationUtils.suppressExceptionUnlessInTest;
import static google.registry.model.common.DatabaseTransitionSchedule.TransitionId.CLAIMS_LIST;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import google.registry.util.NonFinalForTesting;
import java.util.Optional;
/**
* DAO for {@link ClaimsListShard} objects that handles the branching paths for SQL and Datastore.
*
* <p>For write actions, this class will perform the action against the primary database then, after
* * that success or failure, against the secondary database. If the secondary database fails, an
* error is logged (but not thrown).
*
* <p>For read actions, we will log if the primary and secondary databases * have different values
* (or if the retrieval from the second database fails).
*/
public class ClaimsListDualDatabaseDao {
/** In-memory cache for claims list. */
@NonFinalForTesting
private static Supplier<ClaimsListShard> claimsListCache =
tryMemoizeWithExpiration(
getDomainLabelListCacheDuration(), ClaimsListDualDatabaseDao::getUncached);
/**
* Saves the given {@link ClaimsListShard} to both the primary and secondary databases, logging
* and skipping errors in the secondary DB.
*/
public static void save(ClaimsListShard claimsList) {
if (isDatastore(CLAIMS_LIST)) {
claimsList.saveToDatastore();
suppressExceptionUnlessInTest(
() -> ClaimsListSqlDao.save(claimsList), "Error saving ClaimsList to SQL.");
} else {
ClaimsListSqlDao.save(claimsList);
suppressExceptionUnlessInTest(
claimsList::saveToDatastore, "Error saving ClaimsListShard to Datastore.");
}
}
/** Returns the most recent revision of the {@link ClaimsListShard}, from cache. */
public static ClaimsListShard get() {
return claimsListCache.get();
}
/** Retrieves and compares the latest revision from the databases. */
private static ClaimsListShard getUncached() {
Optional<ClaimsListShard> primaryResult;
if (isDatastore(CLAIMS_LIST)) {
primaryResult = ClaimsListShard.getFromDatastore();
suppressExceptionUnlessInTest(
() -> {
Optional<ClaimsListShard> secondaryResult = ClaimsListSqlDao.get();
compareClaimsLists(primaryResult, secondaryResult);
},
"Error loading ClaimsList from SQL.");
} else {
primaryResult = ClaimsListSqlDao.get();
suppressExceptionUnlessInTest(
() -> {
Optional<ClaimsListShard> secondaryResult = ClaimsListShard.getFromDatastore();
compareClaimsLists(primaryResult, secondaryResult);
},
"Error loading ClaimsListShard from Datastore.");
}
return primaryResult.orElse(ClaimsListShard.create(START_OF_TIME, ImmutableMap.of()));
}
private static void compareClaimsLists(
Optional<ClaimsListShard> maybePrimary, Optional<ClaimsListShard> maybeSecondary) {
if (maybePrimary.isPresent() && !maybeSecondary.isPresent()) {
throw new IllegalStateException("Claims list found in primary DB but not in secondary DB.");
}
if (!maybePrimary.isPresent() && maybeSecondary.isPresent()) {
throw new IllegalStateException("Claims list found in secondary DB but not in primary DB.");
}
if (!maybePrimary.isPresent()) {
return;
}
ClaimsListShard primary = maybePrimary.get();
ClaimsListShard secondary = maybeSecondary.get();
MapDifference<String, String> diff =
Maps.difference(primary.labelsToKeys, secondary.getLabelsToKeys());
if (!diff.areEqual()) {
if (diff.entriesDiffering().size()
+ diff.entriesOnlyOnRight().size()
+ diff.entriesOnlyOnLeft().size()
> 10) {
throw new IllegalStateException(
String.format(
"Unequal claims lists detected, secondary list with revision id %d has %d"
+ " different records than the current primary list.",
secondary.getRevisionId(), diff.entriesDiffering().size()));
} else {
StringBuilder diffMessage = new StringBuilder("Unequal claims lists detected:\n");
diff.entriesDiffering()
.forEach(
(label, valueDiff) ->
diffMessage.append(
String.format(
"Domain label %s has key %s in the primary DB and key %s "
+ "in the secondary DB.\n",
label, valueDiff.leftValue(), valueDiff.rightValue())));
diff.entriesOnlyOnLeft()
.forEach(
(label, valueDiff) ->
diffMessage.append(
String.format(
"Domain label %s with key %s only appears in the primary DB.\n",
label, valueDiff)));
diff.entriesOnlyOnRight()
.forEach(
(label, valueDiff) ->
diffMessage.append(
String.format(
"Domain label %s with key %s only appears in the secondary DB.\n",
label, valueDiff)));
throw new IllegalStateException(diffMessage.toString());
}
}
}
private ClaimsListDualDatabaseDao() {}
}

View file

@ -18,19 +18,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verify;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.model.ofy.ObjectifyService.allocateId; import static google.registry.model.ofy.ObjectifyService.allocateId;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MapDifference;
import com.google.common.collect.MapDifference.ValueDifference;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.common.util.concurrent.UncheckedExecutionException;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.EmbedMap; import com.googlecode.objectify.annotation.EmbedMap;
@ -71,8 +65,8 @@ import org.joda.time.DateTime;
* A list of TMCH claims labels and their associated claims keys. * A list of TMCH claims labels and their associated claims keys.
* *
* <p>The claims list is actually sharded into multiple {@link ClaimsListShard} entities to work * <p>The claims list is actually sharded into multiple {@link ClaimsListShard} entities to work
* around the Datastore limitation of 1M max size per entity. However, when calling {@link #get} all * around the Datastore limitation of 1M max size per entity. However, when calling {@link
* of the shards are recombined into one {@link ClaimsListShard} object. * #getFromDatastore} all of the shards are recombined into one {@link ClaimsListShard} object.
* *
* <p>ClaimsList shards are tied to a specific revision and are persisted individually, then the * <p>ClaimsList shards are tied to a specific revision and are persisted individually, then the
* entire claims list is atomically shifted over to using the new shards by persisting the new * entire claims list is atomically shifted over to using the new shards by persisting the new
@ -97,8 +91,6 @@ import org.joda.time.DateTime;
@Table @Table
public class ClaimsListShard extends ImmutableObject implements NonReplicatedEntity { public class ClaimsListShard extends ImmutableObject implements NonReplicatedEntity {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
/** The number of claims list entries to store per shard. */ /** The number of claims list entries to store per shard. */
private static final int SHARD_SIZE = 10000; private static final int SHARD_SIZE = 10000;
@ -143,13 +135,15 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
private static final Retrier LOADER_RETRIER = new Retrier(new SystemSleeper(), 2); private static final Retrier LOADER_RETRIER = new Retrier(new SystemSleeper(), 2);
private static ClaimsListShard loadClaimsListShard() { private static Optional<ClaimsListShard> loadClaimsListShard() {
// Find the most recent revision. // Find the most recent revision.
Key<ClaimsListRevision> revisionKey = getCurrentRevision(); Key<ClaimsListRevision> revisionKey = getCurrentRevision();
if (revisionKey == null) {
return Optional.empty();
}
Map<String, String> combinedLabelsToKeys = new HashMap<>(); Map<String, String> combinedLabelsToKeys = new HashMap<>();
DateTime creationTime = START_OF_TIME; DateTime creationTime = START_OF_TIME;
if (revisionKey != null) {
// Grab all of the keys for the shards that belong to the current revision. // Grab all of the keys for the shards that belong to the current revision.
final List<Key<ClaimsListShard>> shardKeys = final List<Key<ClaimsListShard>> shardKeys =
ofy().load().type(ClaimsListShard.class).ancestor(revisionKey).keys().list(); ofy().load().type(ClaimsListShard.class).ancestor(revisionKey).keys().list();
@ -189,61 +183,9 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
"Inconsistent claims list shard creation times."); "Inconsistent claims list shard creation times.");
} }
} }
}
ClaimsListShard datastoreList = create(creationTime, ImmutableMap.copyOf(combinedLabelsToKeys)); return Optional.of(create(creationTime, ImmutableMap.copyOf(combinedLabelsToKeys)));
// Also load the list from Cloud SQL, compare the two lists, and log if different.
try {
loadAndCompareCloudSqlList(datastoreList);
} catch (Throwable t) {
logger.atSevere().withCause(t).log("Error comparing claims lists.");
} }
return datastoreList;
};
private static void loadAndCompareCloudSqlList(ClaimsListShard datastoreList) {
Optional<ClaimsListShard> maybeCloudSqlList = ClaimsListDao.getLatestRevision();
if (maybeCloudSqlList.isPresent()) {
ClaimsListShard cloudSqlList = maybeCloudSqlList.get();
MapDifference<String, String> diff =
Maps.difference(datastoreList.labelsToKeys, cloudSqlList.getLabelsToKeys());
if (!diff.areEqual()) {
if (diff.entriesDiffering().size() > 10) {
logger.atWarning().log(
String.format(
"Unequal claims lists detected, Cloud SQL list with revision id %d has %d"
+ " different records than the current Datastore list.",
cloudSqlList.getRevisionId(), diff.entriesDiffering().size()));
} else {
StringBuilder diffMessage = new StringBuilder("Unequal claims lists detected:\n");
diff.entriesDiffering().entrySet().stream()
.forEach(
entry -> {
String label = entry.getKey();
ValueDifference<String> valueDiff = entry.getValue();
diffMessage.append(
String.format(
"Domain label %s has key %s in Datastore and key %s in Cloud"
+ " SQL.\n",
label, valueDiff.leftValue(), valueDiff.rightValue()));
});
logger.atWarning().log(diffMessage.toString());
}
}
} else {
logger.atWarning().log("Claims list in Cloud SQL is empty.");
}
}
/**
* A cached supplier that fetches the claims list shards from Datastore and recombines them into a
* single {@link ClaimsListShard} object.
*/
private static final Supplier<ClaimsListShard> CACHE =
memoizeWithShortExpiration(
() ->
LOADER_RETRIER.callWithRetry(
ClaimsListShard::loadClaimsListShard, IllegalStateException.class));
/** Returns the revision id of this claims list, or throws exception if it is null. */ /** Returns the revision id of this claims list, or throws exception if it is null. */
public Long getRevisionId() { public Long getRevisionId() {
@ -286,9 +228,8 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
* Save the Claims list to Datastore by writing the new shards in a series of transactions, * Save the Claims list to Datastore by writing the new shards in a series of transactions,
* switching over to using them atomically, then deleting the old ones. * switching over to using them atomically, then deleting the old ones.
*/ */
public void save() { void saveToDatastore() {
saveToDatastore(SHARD_SIZE); saveToDatastore(SHARD_SIZE);
ClaimsListDao.trySave(this);
} }
@VisibleForTesting @VisibleForTesting
@ -301,7 +242,7 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
Concurrent.transform( Concurrent.transform(
CollectionUtils.partitionMap(labelsToKeys, shardSize), CollectionUtils.partitionMap(labelsToKeys, shardSize),
(final ImmutableMap<String, String> labelsToKeysShard) -> (final ImmutableMap<String, String> labelsToKeysShard) ->
tm().transactNew( tm().transact(
() -> { () -> {
ClaimsListShard shard = create(creationTime, labelsToKeysShard); ClaimsListShard shard = create(creationTime, labelsToKeysShard);
shard.isShard = true; shard.isShard = true;
@ -311,7 +252,7 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
})); }));
// Persist the new revision, thus causing the newly created shards to go live. // Persist the new revision, thus causing the newly created shards to go live.
tm().transactNew( tm().transact(
() -> { () -> {
verify( verify(
(getCurrentRevision() == null && oldRevision == null) (getCurrentRevision() == null && oldRevision == null)
@ -337,9 +278,9 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt
} }
/** Return a single logical instance that combines all Datastore shards. */ /** Return a single logical instance that combines all Datastore shards. */
@Nullable static Optional<ClaimsListShard> getFromDatastore() {
public static ClaimsListShard get() { return LOADER_RETRIER.callWithRetry(
return CACHE.get(); ClaimsListShard::loadClaimsListShard, IllegalStateException.class);
} }
/** As a safety mechanism, fail if someone tries to save this class directly. */ /** As a safety mechanism, fail if someone tries to save this class directly. */

View file

@ -0,0 +1,53 @@
// Copyright 2021 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.tmch;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import java.util.Optional;
import javax.persistence.EntityManager;
/** Data access object for {@link ClaimsListShard}. */
public class ClaimsListSqlDao {
/** Saves the given {@link ClaimsListShard} to Cloud SQL. */
static void save(ClaimsListShard claimsList) {
jpaTm().transact(() -> jpaTm().getEntityManager().persist(claimsList));
}
/**
* Returns the most recent revision of the {@link ClaimsListShard} in SQL or an empty list if it
* doesn't exist.
*/
static Optional<ClaimsListShard> get() {
return jpaTm()
.transact(
() -> {
EntityManager em = jpaTm().getEntityManager();
Long revisionId =
em.createQuery("SELECT MAX(revisionId) FROM ClaimsList", Long.class)
.getSingleResult();
return em.createQuery(
"FROM ClaimsList cl LEFT JOIN FETCH cl.labelsToKeys WHERE cl.revisionId ="
+ " :revisionId",
ClaimsListShard.class)
.setParameter("revisionId", revisionId)
.getResultStream()
.findFirst();
});
}
private ClaimsListSqlDao() {}
}

View file

@ -18,6 +18,7 @@ import static google.registry.request.Action.Method.POST;
import com.google.common.flogger.FluentLogger; import com.google.common.flogger.FluentLogger;
import google.registry.keyring.api.KeyModule.Key; import google.registry.keyring.api.KeyModule.Key;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import google.registry.request.Action; import google.registry.request.Action;
import google.registry.request.auth.Auth; import google.registry.request.auth.Auth;
@ -55,9 +56,9 @@ public final class TmchDnlAction implements Runnable {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
ClaimsListShard claims = ClaimsListParser.parse(lines); ClaimsListShard claims = ClaimsListParser.parse(lines);
claims.save(); ClaimsListDualDatabaseDao.save(claims);
logger.atInfo().log( logger.atInfo().log(
"Inserted %,d claims into Datastore, created at %s", "Inserted %,d claims into the DB(s), created at %s",
claims.size(), claims.getTmdbGenerationTime()); claims.size(), claims.getTmdbGenerationTime());
} }
} }

View file

@ -21,6 +21,7 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.io.Files; import com.google.common.io.Files;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import google.registry.tools.params.PathParameter; import google.registry.tools.params.PathParameter;
import java.nio.file.Path; import java.nio.file.Path;
@ -43,7 +44,7 @@ final class GetClaimsListCommand implements CommandWithRemoteApi {
@Override @Override
public void run() throws Exception { public void run() throws Exception {
ClaimsListShard cl = checkNotNull(ClaimsListShard.get(), "Couldn't load ClaimsList"); ClaimsListShard cl = checkNotNull(ClaimsListDualDatabaseDao.get(), "Couldn't load ClaimsList");
String csv = Joiner.on('\n').withKeyValueSeparator(",").join(cl.getLabelsToKeys()) + "\n"; String csv = Joiner.on('\n').withKeyValueSeparator(",").join(cl.getLabelsToKeys()) + "\n";
Files.asCharSink(output.toFile(), UTF_8).write(csv); Files.asCharSink(output.toFile(), UTF_8).write(csv);
} }

View file

@ -21,6 +21,7 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.io.Files; import com.google.common.io.Files;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import google.registry.tmch.ClaimsListParser; import google.registry.tmch.ClaimsListParser;
import java.io.File; import java.io.File;
@ -56,7 +57,7 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command
@Override @Override
public String execute() { public String execute() {
claimsList.save(); ClaimsListDualDatabaseDao.save(claimsList);
return String.format("Successfully uploaded claims list %s", claimsListFilename); return String.format("Successfully uploaded claims list %s", claimsListFilename);
} }
} }

View file

@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey; import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy; 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.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
@ -39,8 +38,8 @@ import google.registry.model.eppinput.EppInput.ResourceCommandWrapper;
import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.index.EppResourceIndex; import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket; import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.tmch.ClaimsListShard.ClaimsListRevision; import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard.ClaimsListSingleton; import google.registry.model.tmch.ClaimsListShard;
import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.testing.TaskQueueHelper.TaskMatcher;
import google.registry.util.TypeUtils.TypeInstantiator; import google.registry.util.TypeUtils.TypeInstantiator;
import java.util.logging.Level; import java.util.logging.Level;
@ -103,22 +102,12 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
} }
private Class<R> getResourceClass() { private Class<R> getResourceClass() {
return new TypeInstantiator<R>(getClass()){}.getExactType(); return new TypeInstantiator<R>(getClass()) {}.getExactType();
} }
/** /** Persists a testing claims list to Datastore that contains a single shard. */
* Persists a testing claims list to Datastore that contains a single shard.
*/
protected void persistClaimsList(ImmutableMap<String, String> labelsToKeys) { protected void persistClaimsList(ImmutableMap<String, String> labelsToKeys) {
ClaimsListSingleton singleton = new ClaimsListSingleton(); ClaimsListDualDatabaseDao.save(ClaimsListShard.create(clock.nowUtc(), labelsToKeys));
Key<ClaimsListRevision> revision = ClaimsListRevision.createKey(singleton);
singleton.setActiveRevision(revision);
ofy().saveWithoutBackup().entity(singleton).now();
if (!labelsToKeys.isEmpty()) {
ofy().saveWithoutBackup()
.entity(createTestClaimsListShard(clock.nowUtc(), labelsToKeys, revision))
.now();
}
} }
@Test @Test

View file

@ -0,0 +1,143 @@
// Copyright 2021 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.tmch;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import google.registry.config.RegistryEnvironment;
import google.registry.model.EntityTestCase;
import google.registry.model.common.DatabaseTransitionSchedule;
import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabase;
import google.registry.model.common.DatabaseTransitionSchedule.PrimaryDatabaseTransition;
import google.registry.model.common.DatabaseTransitionSchedule.TransitionId;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.testing.SystemPropertyExtension;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ClaimsListDualDatabaseDao}. */
public class ClaimsListDualDatabaseDaoTest extends EntityTestCase {
@RegisterExtension
@Order(value = Integer.MAX_VALUE)
final SystemPropertyExtension systemPropertyExtension = new SystemPropertyExtension();
@BeforeEach
void beforeEach() {
DatabaseTransitionSchedule schedule =
DatabaseTransitionSchedule.create(
TransitionId.CLAIMS_LIST,
TimedTransitionProperty.fromValueMap(
ImmutableSortedMap.of(
START_OF_TIME,
PrimaryDatabase.DATASTORE,
fakeClock.nowUtc().plusDays(1),
PrimaryDatabase.CLOUD_SQL),
PrimaryDatabaseTransition.class));
tm().transactNew(() -> ofy().saveWithoutBackup().entity(schedule).now());
}
@Test
void testGetList_missingSql() {
createClaimsList().saveToDatastore();
assertThat(assertThrows(IllegalStateException.class, ClaimsListDualDatabaseDao::get))
.hasMessageThat()
.isEqualTo("Claims list found in primary DB but not in secondary DB.");
}
@Test
void testGetList_missingOfy() {
fakeClock.advanceBy(Duration.standardDays(5));
ClaimsListSqlDao.save(createClaimsList());
assertThat(assertThrows(IllegalStateException.class, ClaimsListDualDatabaseDao::get))
.hasMessageThat()
.isEqualTo("Claims list found in primary DB but not in secondary DB.");
}
@Test
void testGetList_fromOfy_different() {
createClaimsList().saveToDatastore();
ClaimsListSqlDao.save(
ClaimsListShard.create(fakeClock.nowUtc(), ImmutableMap.of("foo", "bar")));
assertThat(assertThrows(IllegalStateException.class, ClaimsListDualDatabaseDao::get))
.hasMessageThat()
.isEqualTo(
"Unequal claims lists detected:\n"
+ "Domain label label1 with key key1 only appears in the primary DB.\n"
+ "Domain label label2 with key key2 only appears in the primary DB.\n"
+ "Domain label foo with key bar only appears in the secondary DB.\n");
}
@Test
void testGetList_fromSql_different() {
fakeClock.advanceBy(Duration.standardDays(5));
ClaimsListShard.create(fakeClock.nowUtc(), ImmutableMap.of("foo", "bar")).saveToDatastore();
ClaimsListSqlDao.save(createClaimsList());
assertThat(assertThrows(IllegalStateException.class, ClaimsListDualDatabaseDao::get))
.hasMessageThat()
.isEqualTo(
"Unequal claims lists detected:\n"
+ "Domain label label1 with key key1 only appears in the primary DB.\n"
+ "Domain label label2 with key key2 only appears in the primary DB.\n"
+ "Domain label foo with key bar only appears in the secondary DB.\n");
}
@Test
void testSaveAndGet() {
tm().transact(() -> ClaimsListDualDatabaseDao.save(createClaimsList()));
assertAboutImmutableObjects()
.that(ClaimsListDualDatabaseDao.get())
.isEqualExceptFields(createClaimsList(), "id", "revisionId", "creationTimestamp");
}
@Test
void testGet_empty() {
assertThat(tm().transact(ClaimsListDualDatabaseDao::get).getLabelsToKeys()).isEmpty();
}
@Test
void testGetList_missingSql_notInTest() {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
createClaimsList().saveToDatastore();
// Shouldn't fail in production
assertThat(ClaimsListDualDatabaseDao.get().getLabelsToKeys())
.isEqualTo(createClaimsList().getLabelsToKeys());
}
@Test
void testGetList_missingOfy_notInTest() {
RegistryEnvironment.PRODUCTION.setup(systemPropertyExtension);
fakeClock.advanceBy(Duration.standardDays(5));
ClaimsListSqlDao.save(createClaimsList());
// Shouldn't fail in production
assertThat(ClaimsListDualDatabaseDao.get().getLabelsToKeys())
.isEqualTo(createClaimsList().getLabelsToKeys());
}
private ClaimsListShard createClaimsList() {
return ClaimsListShard.create(
fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
}
}

View file

@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@ -48,8 +47,7 @@ public class ClaimsListShardTest {
assertThrows( assertThrows(
UnshardedSaveException.class, UnshardedSaveException.class,
() -> () ->
tm() tm().transact(
.transact(
() -> { () -> {
ClaimsListShard claimsList = ClaimsListShard claimsList =
ClaimsListShard.create( ClaimsListShard.create(
@ -62,8 +60,7 @@ public class ClaimsListShardTest {
@Test @Test
void testGet_safelyLoadsEmptyClaimsList_whenNoShardsExist() { void testGet_safelyLoadsEmptyClaimsList_whenNoShardsExist() {
assertThat(ClaimsListShard.get().labelsToKeys).isEmpty(); assertThat(ClaimsListShard.getFromDatastore()).isEmpty();
assertThat(ClaimsListShard.get().creationTime).isEqualTo(START_OF_TIME);
} }
@Test @Test
@ -77,11 +74,12 @@ public class ClaimsListShardTest {
// Save it with sharding, and make sure that reloading it works. // Save it with sharding, and make sure that reloading it works.
ClaimsListShard unsharded = ClaimsListShard.create(now, ImmutableMap.copyOf(labelsToKeys)); ClaimsListShard unsharded = ClaimsListShard.create(now, ImmutableMap.copyOf(labelsToKeys));
unsharded.saveToDatastore(shardSize); unsharded.saveToDatastore(shardSize);
assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys)
.isEqualTo(unsharded.labelsToKeys);
List<ClaimsListShard> shards1 = ofy().load().type(ClaimsListShard.class).list(); List<ClaimsListShard> shards1 = ofy().load().type(ClaimsListShard.class).list();
assertThat(shards1).hasSize(4); assertThat(shards1).hasSize(4);
assertThat(ClaimsListShard.get().getClaimKey("1")).hasValue("1"); assertThat(ClaimsListShard.getFromDatastore().get().getClaimKey("1")).hasValue("1");
assertThat(ClaimsListShard.get().getClaimKey("a")).isEmpty(); assertThat(ClaimsListShard.getFromDatastore().get().getClaimKey("a")).isEmpty();
assertThat(ClaimsListShard.getCurrentRevision()).isEqualTo(shards1.get(0).parent); assertThat(ClaimsListShard.getCurrentRevision()).isEqualTo(shards1.get(0).parent);
// Create a smaller ClaimsList that will need only 2 shards to save. // Create a smaller ClaimsList that will need only 2 shards to save.
@ -92,8 +90,10 @@ public class ClaimsListShardTest {
unsharded = ClaimsListShard.create(now.plusDays(1), ImmutableMap.copyOf(labelsToKeys)); unsharded = ClaimsListShard.create(now.plusDays(1), ImmutableMap.copyOf(labelsToKeys));
unsharded.saveToDatastore(shardSize); unsharded.saveToDatastore(shardSize);
ofy().clearSessionCache(); ofy().clearSessionCache();
assertThat(ClaimsListShard.get().labelsToKeys).hasSize(unsharded.labelsToKeys.size()); assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys)
assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); .hasSize(unsharded.labelsToKeys.size());
assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys)
.isEqualTo(unsharded.labelsToKeys);
List<ClaimsListShard> shards2 = ofy().load().type(ClaimsListShard.class).list(); List<ClaimsListShard> shards2 = ofy().load().type(ClaimsListShard.class).list();
assertThat(shards2).hasSize(2); assertThat(shards2).hasSize(2);

View file

@ -15,18 +15,21 @@
package google.registry.model.tmch; package google.registry.model.tmch;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.truth.Truth8;
import google.registry.persistence.transaction.JpaTestRules; import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension; import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock; import google.registry.testing.FakeClock;
import javax.persistence.PersistenceException;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ClaimsListDao}. */ /** Unit tests for {@link ClaimsListSqlDao}. */
public class ClaimsListDaoTest { public class ClaimsListSqlDaoTest {
private final FakeClock fakeClock = new FakeClock(); private final FakeClock fakeClock = new FakeClock();
@ -39,40 +42,40 @@ public class ClaimsListDaoTest {
final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension(); final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@Test @Test
void trySave_insertsClaimsListSuccessfully() { void save_insertsClaimsListSuccessfully() {
ClaimsListShard claimsList = ClaimsListShard claimsList =
ClaimsListShard.create( ClaimsListShard.create(
fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
ClaimsListDao.trySave(claimsList); ClaimsListSqlDao.save(claimsList);
ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get();
assertClaimsListEquals(claimsList, insertedClaimsList); assertClaimsListEquals(claimsList, insertedClaimsList);
assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc()); assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc());
} }
@Test @Test
void trySave_noExceptionThrownWhenSaveFail() { void save_fail_duplicateId() {
ClaimsListShard claimsList = ClaimsListShard claimsList =
ClaimsListShard.create( ClaimsListShard.create(
fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2"));
ClaimsListDao.trySave(claimsList); ClaimsListSqlDao.save(claimsList);
ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get();
assertClaimsListEquals(claimsList, insertedClaimsList); assertClaimsListEquals(claimsList, insertedClaimsList);
// Save ClaimsList with existing revisionId should fail because revisionId is the primary key. // Save ClaimsList with existing revisionId should fail because revisionId is the primary key.
ClaimsListDao.trySave(insertedClaimsList); assertThrows(PersistenceException.class, () -> ClaimsListSqlDao.save(insertedClaimsList));
} }
@Test @Test
void trySave_claimsListWithNoEntries() { void save_claimsListWithNoEntries() {
ClaimsListShard claimsList = ClaimsListShard.create(fakeClock.nowUtc(), ImmutableMap.of()); ClaimsListShard claimsList = ClaimsListShard.create(fakeClock.nowUtc(), ImmutableMap.of());
ClaimsListDao.trySave(claimsList); ClaimsListSqlDao.save(claimsList);
ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get();
assertClaimsListEquals(claimsList, insertedClaimsList); assertClaimsListEquals(claimsList, insertedClaimsList);
assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty(); assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty();
} }
@Test @Test
void getCurrent_returnsEmptyListIfTableIsEmpty() { void getCurrent_returnsEmptyListIfTableIsEmpty() {
assertThat(ClaimsListDao.getLatestRevision().isPresent()).isFalse(); Truth8.assertThat(ClaimsListSqlDao.get()).isEmpty();
} }
@Test @Test
@ -83,9 +86,9 @@ public class ClaimsListDaoTest {
ClaimsListShard newClaimsList = ClaimsListShard newClaimsList =
ClaimsListShard.create( ClaimsListShard.create(
fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4")); fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4"));
ClaimsListDao.trySave(oldClaimsList); ClaimsListSqlDao.save(oldClaimsList);
ClaimsListDao.trySave(newClaimsList); ClaimsListSqlDao.save(newClaimsList);
assertClaimsListEquals(newClaimsList, ClaimsListDao.getLatestRevision().get()); assertClaimsListEquals(newClaimsList, ClaimsListSqlDao.get().get());
} }
private void assertClaimsListEquals(ClaimsListShard left, ClaimsListShard right) { private void assertClaimsListEquals(ClaimsListShard left, ClaimsListShard right) {

View file

@ -33,7 +33,7 @@ import google.registry.model.reporting.Spec11ThreatMatchTest;
import google.registry.model.server.KmsSecretRevisionSqlDaoTest; import google.registry.model.server.KmsSecretRevisionSqlDaoTest;
import google.registry.model.server.ServerSecretTest; import google.registry.model.server.ServerSecretTest;
import google.registry.model.smd.SignedMarkRevocationListDaoTest; import google.registry.model.smd.SignedMarkRevocationListDaoTest;
import google.registry.model.tmch.ClaimsListDaoTest; import google.registry.model.tmch.ClaimsListSqlDaoTest;
import google.registry.model.tmch.TmchCrlTest; import google.registry.model.tmch.TmchCrlTest;
import google.registry.persistence.transaction.JpaEntityCoverageExtension; import google.registry.persistence.transaction.JpaEntityCoverageExtension;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
@ -82,7 +82,7 @@ import org.junit.runner.RunWith;
BeforeSuiteTest.class, BeforeSuiteTest.class,
AllocationTokenTest.class, AllocationTokenTest.class,
BillingEventTest.class, BillingEventTest.class,
ClaimsListDaoTest.class, ClaimsListSqlDaoTest.class,
ContactHistoryTest.class, ContactHistoryTest.class,
ContactResourceTest.class, ContactResourceTest.class,
CursorTest.class, CursorTest.class,

View file

@ -20,6 +20,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import java.util.Optional; import java.util.Optional;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -37,7 +38,7 @@ class TmchDnlActionTest extends TmchActionTestCase {
@Test @Test
void testDnl() throws Exception { void testDnl() throws Exception {
assertThat(ClaimsListShard.get().getClaimKey("xn----7sbejwbn3axu3d")).isEmpty(); assertThat(ClaimsListDualDatabaseDao.get().getClaimKey("xn----7sbejwbn3axu3d")).isEmpty();
when(httpResponse.getContent()) when(httpResponse.getContent())
.thenReturn(TmchTestData.loadBytes("dnl-latest.csv").read()) .thenReturn(TmchTestData.loadBytes("dnl-latest.csv").read())
.thenReturn(TmchTestData.loadBytes("dnl-latest.sig").read()); .thenReturn(TmchTestData.loadBytes("dnl-latest.sig").read());
@ -49,7 +50,7 @@ class TmchDnlActionTest extends TmchActionTestCase {
.isEqualTo(MARKSDB_URL + "/dnl/dnl-latest.sig"); .isEqualTo(MARKSDB_URL + "/dnl/dnl-latest.sig");
// Make sure the contents of testdata/dnl-latest.csv got inserted into the database. // Make sure the contents of testdata/dnl-latest.csv got inserted into the database.
ClaimsListShard claimsList = ClaimsListShard.get(); ClaimsListShard claimsList = ClaimsListDualDatabaseDao.get();
assertThat(claimsList.getTmdbGenerationTime()) assertThat(claimsList.getTmdbGenerationTime())
.isEqualTo(DateTime.parse("2013-11-24T23:15:37.4Z")); .isEqualTo(DateTime.parse("2013-11-24T23:15:37.4Z"));
assertThat(claimsList.getClaimKey("xn----7sbejwbn3axu3d")) assertThat(claimsList.getClaimKey("xn----7sbejwbn3axu3d"))

View file

@ -20,6 +20,7 @@ import static java.nio.file.Files.readAllLines;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
@ -31,7 +32,8 @@ class GetClaimsListCommandTest extends CommandTestCase<GetClaimsListCommand> {
@Test @Test
void testSuccess_getWorks() throws Exception { void testSuccess_getWorks() throws Exception {
ClaimsListShard.create(DateTime.now(UTC), ImmutableMap.of("a", "1", "b", "2")).save(); ClaimsListDualDatabaseDao.save(
ClaimsListShard.create(DateTime.now(UTC), ImmutableMap.of("a", "1", "b", "2")));
File output = tmpDir.resolve("claims.txt").toFile(); File output = tmpDir.resolve("claims.txt").toFile();
runCommand("--output=" + output.getAbsolutePath()); runCommand("--output=" + output.getAbsolutePath());
assertThat(readAllLines(output.toPath(), UTF_8)).containsExactly("a,1", "b,2"); assertThat(readAllLines(output.toPath(), UTF_8)).containsExactly("a,1", "b,2");
@ -39,7 +41,8 @@ class GetClaimsListCommandTest extends CommandTestCase<GetClaimsListCommand> {
@Test @Test
void testSuccess_endsWithNewline() throws Exception { void testSuccess_endsWithNewline() throws Exception {
ClaimsListShard.create(DateTime.now(UTC), ImmutableMap.of("a", "1")).save(); ClaimsListDualDatabaseDao.save(
ClaimsListShard.create(DateTime.now(UTC), ImmutableMap.of("a", "1")));
File output = tmpDir.resolve("claims.txt").toFile(); File output = tmpDir.resolve("claims.txt").toFile();
runCommand("--output=" + output.getAbsolutePath()); runCommand("--output=" + output.getAbsolutePath());
assertThat(new String(Files.readAllBytes(output.toPath()), UTF_8)).endsWith("\n"); assertThat(new String(Files.readAllBytes(output.toPath()), UTF_8)).endsWith("\n");

View file

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.Truth8.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.tmch.ClaimsListDualDatabaseDao;
import google.registry.model.tmch.ClaimsListShard; import google.registry.model.tmch.ClaimsListShard;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -36,7 +37,7 @@ class UploadClaimsListCommandTest extends CommandTestCase<UploadClaimsListComman
"anotherexample,2013041500/A/C/7/rHdC4wnrWRvPY6nneCVtQhFj0000000003,2011-08-16T12:00:00.0Z"); "anotherexample,2013041500/A/C/7/rHdC4wnrWRvPY6nneCVtQhFj0000000003,2011-08-16T12:00:00.0Z");
runCommand("--force", filename); runCommand("--force", filename);
ClaimsListShard claimsList = ClaimsListShard.get(); ClaimsListShard claimsList = ClaimsListDualDatabaseDao.get();
assertThat(claimsList.getTmdbGenerationTime()) assertThat(claimsList.getTmdbGenerationTime())
.isEqualTo(DateTime.parse("2012-08-16T00:00:00.0Z")); .isEqualTo(DateTime.parse("2012-08-16T00:00:00.0Z"));
assertThat(claimsList.getClaimKey("example")) assertThat(claimsList.getClaimKey("example"))