diff --git a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java index a98c8cc89..85c5500fe 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainClaimsCheckFlow.java @@ -45,7 +45,7 @@ import google.registry.model.eppinput.ResourceCommand; import google.registry.model.eppoutput.EppResponse; import google.registry.model.registry.Registry; 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 java.util.HashSet; import java.util.Optional; @@ -104,7 +104,8 @@ public final class DomainClaimsCheckFlow implements Flow { verifyClaimsPeriodNotEnded(registry, now); } } - Optional claimKey = ClaimsListShard.get().getClaimKey(parsedDomain.parts().get(0)); + Optional claimKey = + ClaimsListDualDatabaseDao.get().getClaimKey(parsedDomain.parts().get(0)); launchChecksBuilder.add( LaunchCheck.create( LaunchCheckName.create(claimKey.isPresent(), domainName), claimKey.orElse(null))); diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java index ca048eeba..b2b155948 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java @@ -129,7 +129,7 @@ import google.registry.model.registry.label.ReservedList; import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; -import google.registry.model.tmch.ClaimsListShard; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.persistence.VKey; import google.registry.tldconfig.idn.IdnLabelValidator; import google.registry.util.Idn; @@ -994,7 +994,7 @@ public class DomainFlowUtils { InternetDomainName domainName, boolean hasSignedMarks, boolean hasClaimsNotice) throws EppException { boolean isInClaimsList = - ClaimsListShard.get().getClaimKey(domainName.parts().get(0)).isPresent(); + ClaimsListDualDatabaseDao.get().getClaimKey(domainName.parts().get(0)).isPresent(); if (hasClaimsNotice && !isInClaimsList) { throw new UnexpectedClaimsNoticeException(domainName.toString()); } diff --git a/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java b/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java index 1f536044c..8d662864f 100644 --- a/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java +++ b/core/src/main/java/google/registry/model/common/DatabaseTransitionSchedule.java @@ -37,6 +37,7 @@ import google.registry.model.common.TimedTransitionProperty.TimedTransition; import google.registry.model.registry.label.PremiumList; import google.registry.model.registry.label.ReservedList; import google.registry.model.smd.SignedMarkRevocationList; +import google.registry.model.tmch.ClaimsListShard; import google.registry.persistence.VKey; import google.registry.schema.replay.DatastoreOnlyEntity; import java.util.Optional; @@ -58,6 +59,8 @@ public class DatabaseTransitionSchedule extends ImmutableObject implements Datas /** The id of the transition schedule. */ public enum TransitionId { + /** The schedule for migration of {@link ClaimsListShard} entities. */ + CLAIMS_LIST, /** The schedule for the migration of {@link PremiumList} and {@link ReservedList}. */ DOMAIN_LABEL_LISTS, /** The schedule for the migration of the {@link SignedMarkRevocationList} entity. */ diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java b/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java deleted file mode 100644 index 4d76803f0..000000000 --- a/core/src/main/java/google/registry/model/tmch/ClaimsListDao.java +++ /dev/null @@ -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> 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. - * - *

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 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 getLatestRevisionCached() { - return cacheClaimsList.get(); - } - - private ClaimsListDao() {} -} diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsListDualDatabaseDao.java b/core/src/main/java/google/registry/model/tmch/ClaimsListDualDatabaseDao.java new file mode 100644 index 000000000..7b8d31984 --- /dev/null +++ b/core/src/main/java/google/registry/model/tmch/ClaimsListDualDatabaseDao.java @@ -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. + * + *

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). + * + *

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 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 primaryResult; + if (isDatastore(CLAIMS_LIST)) { + primaryResult = ClaimsListShard.getFromDatastore(); + suppressExceptionUnlessInTest( + () -> { + Optional secondaryResult = ClaimsListSqlDao.get(); + compareClaimsLists(primaryResult, secondaryResult); + }, + "Error loading ClaimsList from SQL."); + } else { + primaryResult = ClaimsListSqlDao.get(); + suppressExceptionUnlessInTest( + () -> { + Optional 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 maybePrimary, Optional 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 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() {} +} diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsListShard.java b/core/src/main/java/google/registry/model/tmch/ClaimsListShard.java index c17247f39..e6be23f0a 100644 --- a/core/src/main/java/google/registry/model/tmch/ClaimsListShard.java +++ b/core/src/main/java/google/registry/model/tmch/ClaimsListShard.java @@ -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.Throwables.throwIfUnchecked; 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.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Supplier; 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.googlecode.objectify.Key; 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. * *

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 - * of the shards are recombined into one {@link ClaimsListShard} object. + * around the Datastore limitation of 1M max size per entity. However, when calling {@link + * #getFromDatastore} all of the shards are recombined into one {@link ClaimsListShard} object. * *

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 @@ -97,8 +91,6 @@ import org.joda.time.DateTime; @Table public class ClaimsListShard extends ImmutableObject implements NonReplicatedEntity { - private static final FluentLogger logger = FluentLogger.forEnclosingClass(); - /** The number of claims list entries to store per shard. */ private static final int SHARD_SIZE = 10000; @@ -143,107 +135,57 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt private static final Retrier LOADER_RETRIER = new Retrier(new SystemSleeper(), 2); - private static ClaimsListShard loadClaimsListShard() { + private static Optional loadClaimsListShard() { // Find the most recent revision. Key revisionKey = getCurrentRevision(); + if (revisionKey == null) { + return Optional.empty(); + } Map combinedLabelsToKeys = new HashMap<>(); DateTime creationTime = START_OF_TIME; - if (revisionKey != null) { - // Grab all of the keys for the shards that belong to the current revision. - final List> shardKeys = - ofy().load().type(ClaimsListShard.class).ancestor(revisionKey).keys().list(); + // Grab all of the keys for the shards that belong to the current revision. + final List> shardKeys = + ofy().load().type(ClaimsListShard.class).ancestor(revisionKey).keys().list(); - List shards; - try { - // Load all of the shards concurrently, each in a separate transaction. - shards = - Concurrent.transform( - shardKeys, - key -> - tm().transactNewReadOnly( - () -> { - ClaimsListShard claimsListShard = ofy().load().key(key).now(); - checkState( - claimsListShard != null, - "Key not found when loading claims list shards."); - return claimsListShard; - })); - } catch (UncheckedExecutionException e) { - // We retry on IllegalStateException. However, there's a checkState inside the - // Concurrent.transform, so if it's thrown it'll be wrapped in an - // UncheckedExecutionException. We want to unwrap it so it's caught by the retrier. - if (e.getCause() != null) { - throwIfUnchecked(e.getCause()); - } - throw e; - } - - // Combine the shards together and return the concatenated ClaimsList. - if (!shards.isEmpty()) { - creationTime = shards.get(0).creationTime; - for (ClaimsListShard shard : shards) { - combinedLabelsToKeys.putAll(shard.labelsToKeys); - checkState( - creationTime.equals(shard.creationTime), - "Inconsistent claims list shard creation times."); - } - } - } - - ClaimsListShard datastoreList = create(creationTime, ImmutableMap.copyOf(combinedLabelsToKeys)); - // Also load the list from Cloud SQL, compare the two lists, and log if different. + List shards; try { - loadAndCompareCloudSqlList(datastoreList); - } catch (Throwable t) { - logger.atSevere().withCause(t).log("Error comparing claims lists."); - } - return datastoreList; - }; - - private static void loadAndCompareCloudSqlList(ClaimsListShard datastoreList) { - Optional maybeCloudSqlList = ClaimsListDao.getLatestRevision(); - if (maybeCloudSqlList.isPresent()) { - ClaimsListShard cloudSqlList = maybeCloudSqlList.get(); - MapDifference 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 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()); - } + // Load all of the shards concurrently, each in a separate transaction. + shards = + Concurrent.transform( + shardKeys, + key -> + tm().transactNewReadOnly( + () -> { + ClaimsListShard claimsListShard = ofy().load().key(key).now(); + checkState( + claimsListShard != null, + "Key not found when loading claims list shards."); + return claimsListShard; + })); + } catch (UncheckedExecutionException e) { + // We retry on IllegalStateException. However, there's a checkState inside the + // Concurrent.transform, so if it's thrown it'll be wrapped in an + // UncheckedExecutionException. We want to unwrap it so it's caught by the retrier. + if (e.getCause() != null) { + throwIfUnchecked(e.getCause()); } - } else { - logger.atWarning().log("Claims list in Cloud SQL is empty."); + throw e; } - } - /** - * A cached supplier that fetches the claims list shards from Datastore and recombines them into a - * single {@link ClaimsListShard} object. - */ - private static final Supplier CACHE = - memoizeWithShortExpiration( - () -> - LOADER_RETRIER.callWithRetry( - ClaimsListShard::loadClaimsListShard, IllegalStateException.class)); + // Combine the shards together and return the concatenated ClaimsList. + if (!shards.isEmpty()) { + creationTime = shards.get(0).creationTime; + for (ClaimsListShard shard : shards) { + combinedLabelsToKeys.putAll(shard.labelsToKeys); + checkState( + creationTime.equals(shard.creationTime), + "Inconsistent claims list shard creation times."); + } + } + + return Optional.of(create(creationTime, ImmutableMap.copyOf(combinedLabelsToKeys))); + } /** Returns the revision id of this claims list, or throws exception if it is null. */ 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, * switching over to using them atomically, then deleting the old ones. */ - public void save() { + void saveToDatastore() { saveToDatastore(SHARD_SIZE); - ClaimsListDao.trySave(this); } @VisibleForTesting @@ -301,7 +242,7 @@ public class ClaimsListShard extends ImmutableObject implements NonReplicatedEnt Concurrent.transform( CollectionUtils.partitionMap(labelsToKeys, shardSize), (final ImmutableMap labelsToKeysShard) -> - tm().transactNew( + tm().transact( () -> { ClaimsListShard shard = create(creationTime, labelsToKeysShard); 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. - tm().transactNew( + tm().transact( () -> { verify( (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. */ - @Nullable - public static ClaimsListShard get() { - return CACHE.get(); + static Optional getFromDatastore() { + return LOADER_RETRIER.callWithRetry( + ClaimsListShard::loadClaimsListShard, IllegalStateException.class); } /** As a safety mechanism, fail if someone tries to save this class directly. */ diff --git a/core/src/main/java/google/registry/model/tmch/ClaimsListSqlDao.java b/core/src/main/java/google/registry/model/tmch/ClaimsListSqlDao.java new file mode 100644 index 000000000..d74256f3c --- /dev/null +++ b/core/src/main/java/google/registry/model/tmch/ClaimsListSqlDao.java @@ -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 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() {} +} diff --git a/core/src/main/java/google/registry/tmch/TmchDnlAction.java b/core/src/main/java/google/registry/tmch/TmchDnlAction.java index dbf7a8256..15cdc6786 100644 --- a/core/src/main/java/google/registry/tmch/TmchDnlAction.java +++ b/core/src/main/java/google/registry/tmch/TmchDnlAction.java @@ -18,6 +18,7 @@ import static google.registry.request.Action.Method.POST; import com.google.common.flogger.FluentLogger; import google.registry.keyring.api.KeyModule.Key; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import google.registry.request.Action; import google.registry.request.auth.Auth; @@ -55,9 +56,9 @@ public final class TmchDnlAction implements Runnable { throw new RuntimeException(e); } ClaimsListShard claims = ClaimsListParser.parse(lines); - claims.save(); + ClaimsListDualDatabaseDao.save(claims); 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()); } } diff --git a/core/src/main/java/google/registry/tools/GetClaimsListCommand.java b/core/src/main/java/google/registry/tools/GetClaimsListCommand.java index 21f071b65..4baa4adb2 100644 --- a/core/src/main/java/google/registry/tools/GetClaimsListCommand.java +++ b/core/src/main/java/google/registry/tools/GetClaimsListCommand.java @@ -21,6 +21,7 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Joiner; import com.google.common.io.Files; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import google.registry.tools.params.PathParameter; import java.nio.file.Path; @@ -43,7 +44,7 @@ final class GetClaimsListCommand implements CommandWithRemoteApi { @Override 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"; Files.asCharSink(output.toFile(), UTF_8).write(csv); } diff --git a/core/src/main/java/google/registry/tools/UploadClaimsListCommand.java b/core/src/main/java/google/registry/tools/UploadClaimsListCommand.java index 14a797748..cf87a2855 100644 --- a/core/src/main/java/google/registry/tools/UploadClaimsListCommand.java +++ b/core/src/main/java/google/registry/tools/UploadClaimsListCommand.java @@ -21,6 +21,7 @@ import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.base.Joiner; import com.google.common.io.Files; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import google.registry.tmch.ClaimsListParser; import java.io.File; @@ -56,7 +57,7 @@ final class UploadClaimsListCommand extends ConfirmingCommand implements Command @Override public String execute() { - claimsList.save(); + ClaimsListDualDatabaseDao.save(claimsList); return String.format("Successfully uploaded claims list %s", claimsListFilename); } } diff --git a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java index 29b022c35..eac1946cc 100644 --- a/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java +++ b/core/src/test/java/google/registry/flows/ResourceFlowTestCase.java @@ -18,7 +18,6 @@ import static com.google.common.collect.ImmutableList.toImmutableList; 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; @@ -39,8 +38,8 @@ import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppinput.ResourceCommand; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.EppResourceIndexBucket; -import google.registry.model.tmch.ClaimsListShard.ClaimsListRevision; -import google.registry.model.tmch.ClaimsListShard.ClaimsListSingleton; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; +import google.registry.model.tmch.ClaimsListShard; import google.registry.testing.TaskQueueHelper.TaskMatcher; import google.registry.util.TypeUtils.TypeInstantiator; import java.util.logging.Level; @@ -103,22 +102,12 @@ public abstract class ResourceFlowTestCase getResourceClass() { - return new TypeInstantiator(getClass()){}.getExactType(); + return new TypeInstantiator(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 labelsToKeys) { - ClaimsListSingleton singleton = new ClaimsListSingleton(); - Key revision = ClaimsListRevision.createKey(singleton); - singleton.setActiveRevision(revision); - ofy().saveWithoutBackup().entity(singleton).now(); - if (!labelsToKeys.isEmpty()) { - ofy().saveWithoutBackup() - .entity(createTestClaimsListShard(clock.nowUtc(), labelsToKeys, revision)) - .now(); - } + ClaimsListDualDatabaseDao.save(ClaimsListShard.create(clock.nowUtc(), labelsToKeys)); } @Test diff --git a/core/src/test/java/google/registry/model/tmch/ClaimsListDualDatabaseDaoTest.java b/core/src/test/java/google/registry/model/tmch/ClaimsListDualDatabaseDaoTest.java new file mode 100644 index 000000000..09cf3bd1c --- /dev/null +++ b/core/src/test/java/google/registry/model/tmch/ClaimsListDualDatabaseDaoTest.java @@ -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")); + } +} diff --git a/core/src/test/java/google/registry/model/tmch/ClaimsListShardTest.java b/core/src/test/java/google/registry/model/tmch/ClaimsListShardTest.java index b3fdf7795..849593f35 100644 --- a/core/src/test/java/google/registry/model/tmch/ClaimsListShardTest.java +++ b/core/src/test/java/google/registry/model/tmch/ClaimsListShardTest.java @@ -18,7 +18,6 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; 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.joda.time.DateTimeZone.UTC; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -48,8 +47,7 @@ public class ClaimsListShardTest { assertThrows( UnshardedSaveException.class, () -> - tm() - .transact( + tm().transact( () -> { ClaimsListShard claimsList = ClaimsListShard.create( @@ -62,8 +60,7 @@ public class ClaimsListShardTest { @Test void testGet_safelyLoadsEmptyClaimsList_whenNoShardsExist() { - assertThat(ClaimsListShard.get().labelsToKeys).isEmpty(); - assertThat(ClaimsListShard.get().creationTime).isEqualTo(START_OF_TIME); + assertThat(ClaimsListShard.getFromDatastore()).isEmpty(); } @Test @@ -77,11 +74,12 @@ public class ClaimsListShardTest { // Save it with sharding, and make sure that reloading it works. ClaimsListShard unsharded = ClaimsListShard.create(now, ImmutableMap.copyOf(labelsToKeys)); unsharded.saveToDatastore(shardSize); - assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); + assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys) + .isEqualTo(unsharded.labelsToKeys); List shards1 = ofy().load().type(ClaimsListShard.class).list(); assertThat(shards1).hasSize(4); - assertThat(ClaimsListShard.get().getClaimKey("1")).hasValue("1"); - assertThat(ClaimsListShard.get().getClaimKey("a")).isEmpty(); + assertThat(ClaimsListShard.getFromDatastore().get().getClaimKey("1")).hasValue("1"); + assertThat(ClaimsListShard.getFromDatastore().get().getClaimKey("a")).isEmpty(); assertThat(ClaimsListShard.getCurrentRevision()).isEqualTo(shards1.get(0).parent); // 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.saveToDatastore(shardSize); ofy().clearSessionCache(); - assertThat(ClaimsListShard.get().labelsToKeys).hasSize(unsharded.labelsToKeys.size()); - assertThat(ClaimsListShard.get().labelsToKeys).isEqualTo(unsharded.labelsToKeys); + assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys) + .hasSize(unsharded.labelsToKeys.size()); + assertThat(ClaimsListShard.getFromDatastore().get().labelsToKeys) + .isEqualTo(unsharded.labelsToKeys); List shards2 = ofy().load().type(ClaimsListShard.class).list(); assertThat(shards2).hasSize(2); diff --git a/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java b/core/src/test/java/google/registry/model/tmch/ClaimsListSqlDaoTest.java similarity index 75% rename from core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java rename to core/src/test/java/google/registry/model/tmch/ClaimsListSqlDaoTest.java index 589f40f1c..278a3ef3c 100644 --- a/core/src/test/java/google/registry/model/tmch/ClaimsListDaoTest.java +++ b/core/src/test/java/google/registry/model/tmch/ClaimsListSqlDaoTest.java @@ -15,18 +15,21 @@ package google.registry.model.tmch; 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.truth.Truth8; import google.registry.persistence.transaction.JpaTestRules; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; import google.registry.testing.DatastoreEntityExtension; import google.registry.testing.FakeClock; +import javax.persistence.PersistenceException; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -/** Unit tests for {@link ClaimsListDao}. */ -public class ClaimsListDaoTest { +/** Unit tests for {@link ClaimsListSqlDao}. */ +public class ClaimsListSqlDaoTest { private final FakeClock fakeClock = new FakeClock(); @@ -39,40 +42,40 @@ public class ClaimsListDaoTest { final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension(); @Test - void trySave_insertsClaimsListSuccessfully() { + void save_insertsClaimsListSuccessfully() { ClaimsListShard claimsList = ClaimsListShard.create( fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); - ClaimsListDao.trySave(claimsList); - ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); + ClaimsListSqlDao.save(claimsList); + ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get(); assertClaimsListEquals(claimsList, insertedClaimsList); assertThat(insertedClaimsList.getCreationTimestamp()).isEqualTo(fakeClock.nowUtc()); } @Test - void trySave_noExceptionThrownWhenSaveFail() { + void save_fail_duplicateId() { ClaimsListShard claimsList = ClaimsListShard.create( fakeClock.nowUtc(), ImmutableMap.of("label1", "key1", "label2", "key2")); - ClaimsListDao.trySave(claimsList); - ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); + ClaimsListSqlDao.save(claimsList); + ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get(); assertClaimsListEquals(claimsList, insertedClaimsList); // Save ClaimsList with existing revisionId should fail because revisionId is the primary key. - ClaimsListDao.trySave(insertedClaimsList); + assertThrows(PersistenceException.class, () -> ClaimsListSqlDao.save(insertedClaimsList)); } @Test - void trySave_claimsListWithNoEntries() { + void save_claimsListWithNoEntries() { ClaimsListShard claimsList = ClaimsListShard.create(fakeClock.nowUtc(), ImmutableMap.of()); - ClaimsListDao.trySave(claimsList); - ClaimsListShard insertedClaimsList = ClaimsListDao.getLatestRevision().get(); + ClaimsListSqlDao.save(claimsList); + ClaimsListShard insertedClaimsList = ClaimsListSqlDao.get().get(); assertClaimsListEquals(claimsList, insertedClaimsList); assertThat(insertedClaimsList.getLabelsToKeys()).isEmpty(); } @Test void getCurrent_returnsEmptyListIfTableIsEmpty() { - assertThat(ClaimsListDao.getLatestRevision().isPresent()).isFalse(); + Truth8.assertThat(ClaimsListSqlDao.get()).isEmpty(); } @Test @@ -83,9 +86,9 @@ public class ClaimsListDaoTest { ClaimsListShard newClaimsList = ClaimsListShard.create( fakeClock.nowUtc(), ImmutableMap.of("label3", "key3", "label4", "key4")); - ClaimsListDao.trySave(oldClaimsList); - ClaimsListDao.trySave(newClaimsList); - assertClaimsListEquals(newClaimsList, ClaimsListDao.getLatestRevision().get()); + ClaimsListSqlDao.save(oldClaimsList); + ClaimsListSqlDao.save(newClaimsList); + assertClaimsListEquals(newClaimsList, ClaimsListSqlDao.get().get()); } private void assertClaimsListEquals(ClaimsListShard left, ClaimsListShard right) { diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index 644100a7c..af9d77ced 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -33,7 +33,7 @@ import google.registry.model.reporting.Spec11ThreatMatchTest; import google.registry.model.server.KmsSecretRevisionSqlDaoTest; import google.registry.model.server.ServerSecretTest; 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.persistence.transaction.JpaEntityCoverageExtension; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; @@ -82,7 +82,7 @@ import org.junit.runner.RunWith; BeforeSuiteTest.class, AllocationTokenTest.class, BillingEventTest.class, - ClaimsListDaoTest.class, + ClaimsListSqlDaoTest.class, ContactHistoryTest.class, ContactResourceTest.class, CursorTest.class, diff --git a/core/src/test/java/google/registry/tmch/TmchDnlActionTest.java b/core/src/test/java/google/registry/tmch/TmchDnlActionTest.java index c1cd59e02..b4b1adca3 100644 --- a/core/src/test/java/google/registry/tmch/TmchDnlActionTest.java +++ b/core/src/test/java/google/registry/tmch/TmchDnlActionTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import java.util.Optional; import org.joda.time.DateTime; @@ -37,7 +38,7 @@ class TmchDnlActionTest extends TmchActionTestCase { @Test void testDnl() throws Exception { - assertThat(ClaimsListShard.get().getClaimKey("xn----7sbejwbn3axu3d")).isEmpty(); + assertThat(ClaimsListDualDatabaseDao.get().getClaimKey("xn----7sbejwbn3axu3d")).isEmpty(); when(httpResponse.getContent()) .thenReturn(TmchTestData.loadBytes("dnl-latest.csv").read()) .thenReturn(TmchTestData.loadBytes("dnl-latest.sig").read()); @@ -49,7 +50,7 @@ class TmchDnlActionTest extends TmchActionTestCase { .isEqualTo(MARKSDB_URL + "/dnl/dnl-latest.sig"); // 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()) .isEqualTo(DateTime.parse("2013-11-24T23:15:37.4Z")); assertThat(claimsList.getClaimKey("xn----7sbejwbn3axu3d")) diff --git a/core/src/test/java/google/registry/tools/GetClaimsListCommandTest.java b/core/src/test/java/google/registry/tools/GetClaimsListCommandTest.java index acde106f0..0413baaf5 100644 --- a/core/src/test/java/google/registry/tools/GetClaimsListCommandTest.java +++ b/core/src/test/java/google/registry/tools/GetClaimsListCommandTest.java @@ -20,6 +20,7 @@ import static java.nio.file.Files.readAllLines; import static org.joda.time.DateTimeZone.UTC; import com.google.common.collect.ImmutableMap; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import java.io.File; import java.nio.file.Files; @@ -31,7 +32,8 @@ class GetClaimsListCommandTest extends CommandTestCase { @Test 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(); runCommand("--output=" + output.getAbsolutePath()); assertThat(readAllLines(output.toPath(), UTF_8)).containsExactly("a,1", "b,2"); @@ -39,7 +41,8 @@ class GetClaimsListCommandTest extends CommandTestCase { @Test 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(); runCommand("--output=" + output.getAbsolutePath()); assertThat(new String(Files.readAllBytes(output.toPath()), UTF_8)).endsWith("\n"); diff --git a/core/src/test/java/google/registry/tools/UploadClaimsListCommandTest.java b/core/src/test/java/google/registry/tools/UploadClaimsListCommandTest.java index 5fa5529b0..1c29ca94a 100644 --- a/core/src/test/java/google/registry/tools/UploadClaimsListCommandTest.java +++ b/core/src/test/java/google/registry/tools/UploadClaimsListCommandTest.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import google.registry.model.tmch.ClaimsListDualDatabaseDao; import google.registry.model.tmch.ClaimsListShard; import java.io.FileNotFoundException; import org.joda.time.DateTime; @@ -36,7 +37,7 @@ class UploadClaimsListCommandTest extends CommandTestCase