diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index afb2a10c0..9fefd66a4 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -58,6 +58,7 @@ import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.Response; import google.registry.request.auth.Auth; +import google.registry.schema.cursor.CursorDao; import google.registry.util.Clock; import java.util.Optional; import java.util.Set; @@ -313,8 +314,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable { logger.atInfo().log( "Recurring event expansion %s complete for billing event range [%s, %s).", isDryRun ? "(dry run) " : "", cursorTime, executionTime); - tm() - .transact( + tm().transact( () -> { Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); DateTime currentCursorTime = @@ -326,7 +326,9 @@ public class ExpandRecurringBillingEventsAction implements Runnable { return; } if (!isDryRun) { - ofy().save().entity(Cursor.createGlobal(RECURRING_BILLING, executionTime)); + CursorDao.saveCursor( + Cursor.createGlobal(RECURRING_BILLING, executionTime), + google.registry.schema.cursor.Cursor.GLOBAL); } }); } diff --git a/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java b/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java index f81fa37a0..8f9c252fe 100644 --- a/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java +++ b/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java @@ -25,7 +25,6 @@ import static google.registry.model.registrar.RegistrarContact.Type.LEGAL; import static google.registry.model.registrar.RegistrarContact.Type.MARKETING; import static google.registry.model.registrar.RegistrarContact.Type.TECH; import static google.registry.model.registrar.RegistrarContact.Type.WHOIS; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.base.Joiner; @@ -37,6 +36,7 @@ import google.registry.model.common.Cursor; import google.registry.model.registrar.Registrar; import google.registry.model.registrar.RegistrarAddress; import google.registry.model.registrar.RegistrarContact; +import google.registry.schema.cursor.CursorDao; import google.registry.util.Clock; import google.registry.util.DateTimeUtils; import java.io.IOException; @@ -153,9 +153,9 @@ class SyncRegistrarsSheet { return builder.build(); }) .collect(toImmutableList())); - tm() - .transact( - () -> ofy().save().entity(Cursor.createGlobal(SYNC_REGISTRAR_SHEET, executionTime))); + CursorDao.saveCursor( + Cursor.createGlobal(SYNC_REGISTRAR_SHEET, executionTime), + google.registry.schema.cursor.Cursor.GLOBAL); } private static String convertContacts( diff --git a/core/src/main/java/google/registry/model/common/Cursor.java b/core/src/main/java/google/registry/model/common/Cursor.java index 23cdb0bc3..9e8a88c25 100644 --- a/core/src/main/java/google/registry/model/common/Cursor.java +++ b/core/src/main/java/google/registry/model/common/Cursor.java @@ -20,6 +20,7 @@ import static google.registry.model.common.EntityGroupRoot.getCrossTldKey; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.DateTimeUtils.START_OF_TIME; +import com.google.common.base.Splitter; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; @@ -27,6 +28,7 @@ import com.googlecode.objectify.annotation.Parent; import google.registry.model.ImmutableObject; import google.registry.model.UpdateAutoTimestamp; import google.registry.model.registry.Registry; +import java.util.List; import org.joda.time.DateTime; /** @@ -127,6 +129,11 @@ public class Cursor extends ImmutableObject { return lastUpdateTime.getTimestamp(); } + public CursorType getType() { + List id = Splitter.on('_').splitToList(this.id); + return CursorType.valueOf(String.join("_", id.subList(1, id.size()))); + } + /** * Checks that the type of the scoped object (or null) matches the required type for the specified * cursor (or null, if the cursor is a global cursor). diff --git a/core/src/main/java/google/registry/rde/EscrowTaskRunner.java b/core/src/main/java/google/registry/rde/EscrowTaskRunner.java index 3052894f6..75d349967 100644 --- a/core/src/main/java/google/registry/rde/EscrowTaskRunner.java +++ b/core/src/main/java/google/registry/rde/EscrowTaskRunner.java @@ -15,7 +15,6 @@ package google.registry.rde; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import com.google.common.flogger.FluentLogger; import google.registry.model.common.Cursor; @@ -24,6 +23,7 @@ import google.registry.model.registry.Registry; import google.registry.request.HttpException.NoContentException; import google.registry.request.HttpException.ServiceUnavailableException; import google.registry.request.lock.LockHandler; +import google.registry.schema.cursor.CursorDao; import google.registry.util.Clock; import java.util.concurrent.Callable; import javax.inject.Inject; @@ -99,7 +99,7 @@ class EscrowTaskRunner { task.runWithLock(nextRequiredRun); DateTime nextRun = nextRequiredRun.plus(interval); logger.atInfo().log("Rolling cursor forward to %s.", nextRun); - tm().transact(() -> ofy().save().entity(Cursor.create(cursorType, nextRun, registry))); + CursorDao.saveCursor(Cursor.create(cursorType, nextRun, registry), registry.getTldStr()); return null; }; String lockName = String.format("EscrowTaskRunner %s", task.getClass().getSimpleName()); diff --git a/core/src/main/java/google/registry/rde/PendingDepositChecker.java b/core/src/main/java/google/registry/rde/PendingDepositChecker.java index 89b1415c1..a34c0956b 100644 --- a/core/src/main/java/google/registry/rde/PendingDepositChecker.java +++ b/core/src/main/java/google/registry/rde/PendingDepositChecker.java @@ -27,6 +27,7 @@ import google.registry.model.rde.RdeMode; import google.registry.model.registry.Registries; import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldType; +import google.registry.schema.cursor.CursorDao; import google.registry.util.Clock; import javax.inject.Inject; import org.joda.time.DateTime; @@ -107,14 +108,14 @@ public final class PendingDepositChecker { final Registry registry, final CursorType cursorType, final DateTime initialValue) { - return tm() - .transact( + return tm().transact( () -> { Cursor cursor = ofy().load().key(Cursor.createKey(cursorType, registry)).now(); if (cursor != null) { return cursor.getCursorTime(); } - ofy().save().entity(Cursor.create(cursorType, initialValue, registry)); + CursorDao.saveCursor( + Cursor.create(cursorType, initialValue, registry), registry.getTldStr()); return initialValue; }); } diff --git a/core/src/main/java/google/registry/rde/RdeStagingReducer.java b/core/src/main/java/google/registry/rde/RdeStagingReducer.java index 170903cb3..910692412 100644 --- a/core/src/main/java/google/registry/rde/RdeStagingReducer.java +++ b/core/src/main/java/google/registry/rde/RdeStagingReducer.java @@ -40,6 +40,7 @@ import google.registry.model.rde.RdeRevision; import google.registry.model.registry.Registry; import google.registry.request.RequestParameters; import google.registry.request.lock.LockHandler; +import google.registry.schema.cursor.CursorDao; import google.registry.tldconfig.idn.IdnTableEnum; import google.registry.util.TaskQueueUtils; import google.registry.xjc.rdeheader.XjcRdeHeader; @@ -198,13 +199,12 @@ public final class RdeStagingReducer extends Reducer { Registry registry = Registry.get(tld); DateTime position = @@ -221,7 +221,8 @@ public final class RdeStagingReducer extends Reducer upload(xmlFilename, xmlLength, watermark, name), JSchException.class); logger.atInfo().log( "Updating RDE cursor '%s' for TLD '%s' following successful upload.", RDE_UPLOAD_SFTP, tld); - tm() - .transact( + tm().transact( () -> { - Cursor updatedSftpCursor = - Cursor.create(RDE_UPLOAD_SFTP, tm().getTransactionTime(), Registry.get(tld)); - ofy().save().entity(updatedSftpCursor); + CursorDao.saveCursor( + Cursor.create(RDE_UPLOAD_SFTP, tm().getTransactionTime(), Registry.get(tld)), + tld); }); response.setContentType(PLAIN_TEXT_UTF_8); response.setPayload(String.format("OK %s %s\n", tld, watermark)); diff --git a/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java b/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java index 5320f4a40..4cf9db6f6 100644 --- a/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java +++ b/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java @@ -44,6 +44,7 @@ import google.registry.request.Parameter; import google.registry.request.Response; import google.registry.request.auth.Auth; import google.registry.request.lock.LockHandler; +import google.registry.schema.cursor.CursorDao; import google.registry.util.Clock; import google.registry.util.EmailMessage; import google.registry.util.Retrier; @@ -51,6 +52,7 @@ import google.registry.util.SendEmailService; import java.io.IOException; import java.io.InputStream; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -186,7 +188,9 @@ public final class IcannReportingUploadAction implements Runnable { cursorType, cursorTime.withTimeAtStartOfDay().withDayOfMonth(1).plusMonths(1), Registry.get(tldStr)); - tm().transact(() -> ofy().save().entity(newCursor)); + CursorDao.saveCursor( + newCursor, + Optional.ofNullable(tldStr).orElse(google.registry.schema.cursor.Cursor.GLOBAL)); } } diff --git a/core/src/main/java/google/registry/schema/cursor/CursorDao.java b/core/src/main/java/google/registry/schema/cursor/CursorDao.java index d901daae5..676023420 100644 --- a/core/src/main/java/google/registry/schema/cursor/CursorDao.java +++ b/core/src/main/java/google/registry/schema/cursor/CursorDao.java @@ -15,8 +15,13 @@ package google.registry.schema.cursor; import static com.google.appengine.api.search.checkers.Preconditions.checkNotNull; +import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.model.transaction.TransactionManagerFactory.tm; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.flogger.FluentLogger; import google.registry.model.common.Cursor.CursorType; import google.registry.schema.cursor.Cursor.CursorId; import java.util.List; @@ -24,6 +29,8 @@ import java.util.List; /** Data access object class for {@link Cursor}. */ public class CursorDao { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + public static void save(Cursor cursor) { jpaTm() .transact( @@ -32,9 +39,19 @@ public class CursorDao { }); } + public static void saveAll(ImmutableSet cursors) { + jpaTm() + .transact( + () -> { + for (Cursor cursor : cursors) { + jpaTm().getEntityManager().merge(cursor); + } + }); + } + public static Cursor load(CursorType type, String scope) { checkNotNull(scope, "The scope of the cursor to load cannot be null"); - checkNotNull(type, "The type of the cursor to load must be specified"); + checkNotNull(type, "The type of the cursor to load cannot be null"); return jpaTm() .transact(() -> jpaTm().getEntityManager().find(Cursor.class, new CursorId(type, scope))); } @@ -67,4 +84,50 @@ public class CursorDao { .setParameter("type", type) .getResultList()); } + + /** + * This writes the given cursor to Datastore. If the save to Datastore succeeds, then a new + * Schema/Cursor object is created and attempted to save to Cloud SQL. If the save to Cloud SQL + * fails, the exception is logged, but does not cause the method to fail. + */ + public static void saveCursor(google.registry.model.common.Cursor cursor, String scope) { + tm().transact(() -> ofy().save().entity(cursor)); + CursorType type = cursor.getType(); + try { + Cursor cloudSqlCursor = Cursor.create(type, scope, cursor.getCursorTime()); + save(cloudSqlCursor); + } catch (Exception e) { + logger.atSevere().withCause(e).log("Error saving cursor to Cloud SQL."); + } + } + + /** + * This takes in multiple cursors and saves them to Datastore. If those saves succeed, it attempts + * to save the cursors to Cloud SQL. If the save to Cloud SQL fails, the exception is logged, but + * does not cause the method to fail. + */ + public static void saveCursors( + ImmutableMap cursors) { + // Save the cursors to Datastore + tm().transact( + () -> { + for (google.registry.model.common.Cursor cursor : cursors.keySet()) { + ofy().save().entity(cursor); + } + }); + // Try to save the cursors to Cloud SQL + try { + ImmutableSet.Builder cloudSqlCursors = new ImmutableSet.Builder<>(); + cursors + .keySet() + .forEach( + cursor -> + cloudSqlCursors.add( + Cursor.create( + cursor.getType(), cursors.get(cursor), cursor.getCursorTime()))); + saveAll(cloudSqlCursors.build()); + } catch (Exception e) { + logger.atSevere().withCause(e).log("Error saving cursor to Cloud SQL."); + } + } } diff --git a/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java b/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java index 6ef975ff5..4507587c2 100644 --- a/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java +++ b/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java @@ -14,21 +14,22 @@ package google.registry.tools; -import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.util.CollectionUtils.isNullOrEmpty; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; +import com.google.common.collect.ImmutableMap; import google.registry.model.common.Cursor; import google.registry.model.common.Cursor.CursorType; import google.registry.model.registry.Registry; +import google.registry.schema.cursor.CursorDao; import google.registry.tools.params.DateTimeParameter; import java.util.List; import org.joda.time.DateTime; /** Modifies {@link Cursor} timestamps used by locking rolling cursor tasks, like in RDE. */ @Parameters(separators = " =", commandDescription = "Modifies cursor timestamps used by LRC tasks") -final class UpdateCursorsCommand extends MutatingCommand { +final class UpdateCursorsCommand extends ConfirmingCommand implements CommandWithCloudSql { @Parameter(description = "TLDs on which to operate. Omit for global cursors.") private List tlds; @@ -46,19 +47,46 @@ final class UpdateCursorsCommand extends MutatingCommand { required = true) private DateTime newTimestamp; + ImmutableMap cursorsToUpdate; + @Override protected void init() { + ImmutableMap.Builder cursorsToUpdateBuilder = new ImmutableMap.Builder<>(); if (isNullOrEmpty(tlds)) { - Cursor cursor = ofy().load().key(Cursor.createGlobalKey(cursorType)).now(); - stageEntityChange(cursor, Cursor.createGlobal(cursorType, newTimestamp)); + cursorsToUpdateBuilder.put( + Cursor.createGlobal(cursorType, newTimestamp), + google.registry.schema.cursor.Cursor.GLOBAL); } else { for (String tld : tlds) { Registry registry = Registry.get(tld); - Cursor cursor = ofy().load().key(Cursor.createKey(cursorType, registry)).now(); - stageEntityChange( - cursor, - Cursor.create(cursorType, newTimestamp, registry)); + cursorsToUpdateBuilder.put( + Cursor.create(cursorType, newTimestamp, registry), registry.getTldStr()); } } + cursorsToUpdate = cursorsToUpdateBuilder.build(); + } + + @Override + protected String execute() throws Exception { + CursorDao.saveCursors(cursorsToUpdate); + return String.format("Updated %d cursors.\n", cursorsToUpdate.size()); + } + + /** Returns the changes that have been staged thus far. */ + @Override + protected String prompt() { + StringBuilder changes = new StringBuilder(); + if (cursorsToUpdate.isEmpty()) { + return "No cursor changes to apply."; + } + cursorsToUpdate.entrySet().stream() + .forEach(entry -> changes.append(getChangeString(entry.getKey(), entry.getValue()))); + return changes.toString(); + } + + private String getChangeString(Cursor cursor, String scope) { + return String.format( + "Change cursorTime of %s for Scope:%s to %s\n", + cursor.getType(), scope, cursor.getCursorTime()); } } diff --git a/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java b/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java index a31d31849..894151264 100644 --- a/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java +++ b/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java @@ -17,7 +17,6 @@ package google.registry.backup; import static com.google.common.truth.Truth.assertThat; import static google.registry.model.common.Cursor.CursorType.RDE_REPORT; import static google.registry.model.ofy.CommitLogBucket.getBucketKey; -import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -31,6 +30,7 @@ import google.registry.model.ofy.DatastoreTransactionManager; import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; import google.registry.model.transaction.TransactionManager; +import google.registry.schema.cursor.CursorDao; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.InjectRule; @@ -54,6 +54,7 @@ public class CommitLogCheckpointStrategyTest { @Rule public final InjectRule inject = new InjectRule(); + final FakeClock clock = new FakeClock(DateTime.parse("2000-01-01TZ")); final Ofy ofy = new Ofy(clock); final TransactionManager tm = new DatastoreTransactionManager(ofy); @@ -293,11 +294,10 @@ public class CommitLogCheckpointStrategyTest { private void writeCommitLogToBucket(final int bucketId) { fakeBucketIdSupplier.value = bucketId; tm.transact( - () -> { - Cursor cursor = - Cursor.create(RDE_REPORT, tm.getTransactionTime(), Registry.get("tld" + bucketId)); - ofy().save().entity(cursor); - }); + () -> + CursorDao.saveCursor( + Cursor.create(RDE_REPORT, tm.getTransactionTime(), Registry.get("tld" + bucketId)), + "tld" + bucketId)); fakeBucketIdSupplier.value = null; } diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index bfe322fa1..6ce1e8665 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -19,7 +19,6 @@ import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; import static google.registry.model.domain.Period.Unit.YEARS; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatastoreHelper.assertBillingEvents; import static google.registry.testing.DatastoreHelper.assertBillingEventsForResource; import static google.registry.testing.DatastoreHelper.createTld; @@ -50,6 +49,7 @@ import google.registry.model.registry.Registry; import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; +import google.registry.schema.cursor.CursorDao; import google.registry.testing.FakeClock; import google.registry.testing.FakeResponse; import google.registry.testing.InjectRule; @@ -103,7 +103,9 @@ public class ExpandRecurringBillingEventsActionTest } private void saveCursor(final DateTime cursorTime) { - tm().transact(() -> ofy().save().entity(Cursor.createGlobal(RECURRING_BILLING, cursorTime))); + CursorDao.saveCursor( + Cursor.createGlobal(RECURRING_BILLING, cursorTime), + google.registry.schema.cursor.Cursor.GLOBAL); } private void runMapreduce() throws Exception { diff --git a/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java b/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java index 11a1da10c..66b25b504 100644 --- a/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java +++ b/core/src/test/java/google/registry/export/sheet/SyncRegistrarsSheetTest.java @@ -62,6 +62,7 @@ public class SyncRegistrarsSheetTest { @Rule public final MockitoRule mocks = MockitoJUnit.rule(); @Rule public final InjectRule inject = new InjectRule(); + @Captor private ArgumentCaptor>> rowsCaptor; @Mock private SheetSynchronizer sheetSynchronizer; diff --git a/core/src/test/java/google/registry/model/common/CursorTest.java b/core/src/test/java/google/registry/model/common/CursorTest.java index 60b44b501..fd85a3f90 100644 --- a/core/src/test/java/google/registry/model/common/CursorTest.java +++ b/core/src/test/java/google/registry/model/common/CursorTest.java @@ -19,7 +19,6 @@ import static google.registry.model.common.Cursor.CursorType.BRDA; import static google.registry.model.common.Cursor.CursorType.RDE_UPLOAD; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -28,6 +27,7 @@ import static org.junit.Assert.assertThrows; import google.registry.model.EntityTestCase; import google.registry.model.domain.DomainBase; import google.registry.model.registry.Registry; +import google.registry.schema.cursor.CursorDao; import org.joda.time.DateTime; import org.junit.Test; @@ -39,7 +39,7 @@ public class CursorTest extends EntityTestCase { createTld("tld"); clock.advanceOneMilli(); final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); - tm().transact(() -> ofy().save().entity(Cursor.create(RDE_UPLOAD, time, Registry.get("tld")))); + CursorDao.saveCursor(Cursor.create(RDE_UPLOAD, time, Registry.get("tld")), "tld"); assertThat(ofy().load().key(Cursor.createKey(BRDA, Registry.get("tld"))).now()).isNull(); assertThat( ofy() @@ -53,7 +53,8 @@ public class CursorTest extends EntityTestCase { @Test public void testSuccess_persistGlobalCursor() { final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); - tm().transact(() -> ofy().save().entity(Cursor.createGlobal(RECURRING_BILLING, time))); + CursorDao.saveCursor( + Cursor.createGlobal(RECURRING_BILLING, time), google.registry.schema.cursor.Cursor.GLOBAL); assertThat(ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now().getCursorTime()) .isEqualTo(time); } @@ -61,7 +62,8 @@ public class CursorTest extends EntityTestCase { @Test public void testIndexing() throws Exception { final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); - tm().transact(() -> ofy().save().entity(Cursor.createGlobal(RECURRING_BILLING, time))); + CursorDao.saveCursor( + Cursor.createGlobal(RECURRING_BILLING, time), google.registry.schema.cursor.Cursor.GLOBAL); Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); verifyIndexing(cursor); } @@ -75,8 +77,7 @@ public class CursorTest extends EntityTestCase { IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> - tm().transact(() -> ofy().save().entity(Cursor.create(RDE_UPLOAD, time, domain)))); + () -> CursorDao.saveCursor(Cursor.create(RDE_UPLOAD, time, domain), domain.getTld())); assertThat(thrown) .hasMessageThat() .contains("Class required for cursor does not match scope class"); diff --git a/core/src/test/java/google/registry/rde/EscrowTaskRunnerTest.java b/core/src/test/java/google/registry/rde/EscrowTaskRunnerTest.java index 2a5d1d389..d29864211 100644 --- a/core/src/test/java/google/registry/rde/EscrowTaskRunnerTest.java +++ b/core/src/test/java/google/registry/rde/EscrowTaskRunnerTest.java @@ -59,7 +59,6 @@ public class EscrowTaskRunnerTest { private EscrowTaskRunner runner; private Registry registry; - @Before public void before() { createTld("lol"); diff --git a/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java b/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java index 4c3729281..065588126 100644 --- a/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java +++ b/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java @@ -20,7 +20,6 @@ import static google.registry.model.common.Cursor.CursorType.RDE_STAGING; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.rde.RdeMode.FULL; import static google.registry.model.rde.RdeMode.THIN; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.persistResource; import static org.joda.time.DateTimeConstants.TUESDAY; @@ -31,6 +30,7 @@ import google.registry.model.common.Cursor; import google.registry.model.common.Cursor.CursorType; import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; +import google.registry.schema.cursor.CursorDao; import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import google.registry.testing.InjectRule; @@ -164,7 +164,7 @@ public class PendingDepositCheckerTest { private static void setCursor( final Registry registry, final CursorType cursorType, final DateTime value) { - tm().transact(() -> ofy().save().entity(Cursor.create(cursorType, value, registry))); + CursorDao.saveCursor(Cursor.create(cursorType, value, registry), registry.getTldStr()); } private static void createTldWithEscrowEnabled(final String tld) { diff --git a/core/src/test/java/google/registry/rde/RdeStagingActionTest.java b/core/src/test/java/google/registry/rde/RdeStagingActionTest.java index 3e46ba002..a91ee6ffb 100644 --- a/core/src/test/java/google/registry/rde/RdeStagingActionTest.java +++ b/core/src/test/java/google/registry/rde/RdeStagingActionTest.java @@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.model.common.Cursor.CursorType.BRDA; import static google.registry.model.common.Cursor.CursorType.RDE_STAGING; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.model.transaction.TransactionManagerFactory.tm; import static google.registry.rde.RdeFixtures.makeContactResource; import static google.registry.rde.RdeFixtures.makeDomainBase; import static google.registry.rde.RdeFixtures.makeHostResource; @@ -55,6 +54,7 @@ import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; import google.registry.request.HttpException.BadRequestException; import google.registry.request.RequestParameters; +import google.registry.schema.cursor.CursorDao; import google.registry.testing.FakeClock; import google.registry.testing.FakeKeyringModule; import google.registry.testing.FakeLockHandler; @@ -848,7 +848,7 @@ public class RdeStagingActionTest extends MapreduceTestCase { private void setCursor( final Registry registry, final CursorType cursorType, final DateTime value) { clock.advanceOneMilli(); - tm().transact(() -> ofy().save().entity(Cursor.create(cursorType, value, registry)).now()); + CursorDao.saveCursor(Cursor.create(cursorType, value, registry), registry.getTldStr()); } public static T unmarshal(Class clazz, byte[] xml) throws XmlException { diff --git a/core/src/test/java/google/registry/rde/RdeUploadActionTest.java b/core/src/test/java/google/registry/rde/RdeUploadActionTest.java index ca1de958e..80cfee220 100644 --- a/core/src/test/java/google/registry/rde/RdeUploadActionTest.java +++ b/core/src/test/java/google/registry/rde/RdeUploadActionTest.java @@ -133,6 +133,7 @@ public class RdeUploadActionTest { .withTaskQueue() .build(); + private final FakeResponse response = new FakeResponse(); private final EscrowTaskRunner runner = mock(EscrowTaskRunner.class); private final FakeClock clock = new FakeClock(DateTime.parse("2010-10-17TZ")); diff --git a/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java b/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java index dd9bfafa7..e7dca1893 100644 --- a/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java +++ b/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java @@ -15,12 +15,23 @@ package google.registry.schema.cursor; import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; +import static google.registry.testing.LogsSubject.assertAboutLogs; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.testing.TestLogHandler; import google.registry.model.common.Cursor.CursorType; +import google.registry.model.registry.Registry; import google.registry.model.transaction.JpaTestRules; import google.registry.model.transaction.JpaTestRules.JpaIntegrationTestRule; +import google.registry.testing.AppEngineRule; import google.registry.testing.FakeClock; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,10 +43,15 @@ public class CursorDaoTest { private FakeClock fakeClock = new FakeClock(); + private final TestLogHandler logHandler = new TestLogHandler(); + private final Logger loggerToIntercept = Logger.getLogger(CursorDao.class.getCanonicalName()); + @Rule public final JpaIntegrationTestRule jpaRule = new JpaTestRules.Builder().buildIntegrationTestRule(); + @Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build(); + @Test public void save_worksSuccessfullyOnNewCursor() { Cursor cursor = Cursor.create(CursorType.BRDA, "tld", fakeClock.nowUtc()); @@ -73,16 +89,30 @@ public class CursorDaoTest { assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime()); } + @Test + public void saveAll_worksSuccessfully() { + Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc()); + Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc()); + ImmutableSet cursors = ImmutableSet.builder().add(cursor, cursor2).build(); + CursorDao.saveAll(cursors); + assertThat(CursorDao.loadAll()).hasSize(2); + assertThat(CursorDao.load(CursorType.RECURRING_BILLING).getCursorTime()) + .isEqualTo(cursor.getCursorTime()); + } + + @Test + public void saveAll_worksSuccessfullyEmptySet() { + CursorDao.saveAll(ImmutableSet.of()); + assertThat(CursorDao.loadAll()).isEmpty(); + } + @Test public void load_worksSuccessfully() { Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc()); Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc()); Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc()); Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc()); - CursorDao.save(cursor); - CursorDao.save(cursor2); - CursorDao.save(cursor3); - CursorDao.save(cursor4); + CursorDao.saveAll(ImmutableSet.of(cursor, cursor2, cursor3, cursor4)); Cursor returnedCursor = CursorDao.load(CursorType.RDE_REPORT, "tld"); assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime()); returnedCursor = CursorDao.load(CursorType.BRDA, "foo"); @@ -97,10 +127,7 @@ public class CursorDaoTest { Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc()); Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc()); Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc()); - CursorDao.save(cursor); - CursorDao.save(cursor2); - CursorDao.save(cursor3); - CursorDao.save(cursor4); + CursorDao.saveAll(ImmutableSet.of(cursor, cursor2, cursor3, cursor4)); List returnedCursors = CursorDao.loadAll(); assertThat(returnedCursors.size()).isEqualTo(4); } @@ -117,10 +144,7 @@ public class CursorDaoTest { Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc()); Cursor cursor3 = Cursor.create(CursorType.RDE_REPORT, "foo", fakeClock.nowUtc()); Cursor cursor4 = Cursor.create(CursorType.BRDA, "foo", fakeClock.nowUtc()); - CursorDao.save(cursor); - CursorDao.save(cursor2); - CursorDao.save(cursor3); - CursorDao.save(cursor4); + CursorDao.saveAll(ImmutableSet.of(cursor, cursor2, cursor3, cursor4)); List returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT); assertThat(returnedCursors.size()).isEqualTo(2); } @@ -130,4 +154,109 @@ public class CursorDaoTest { List returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT); assertThat(returnedCursors.size()).isEqualTo(0); } + + @Test + public void saveCursor_worksSuccessfully() { + createTld("tld"); + google.registry.model.common.Cursor cursor = + google.registry.model.common.Cursor.create( + CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc(), Registry.get("tld")); + CursorDao.saveCursor(cursor, "tld"); + Cursor createdCursor = CursorDao.load(CursorType.ICANN_UPLOAD_ACTIVITY, "tld"); + google.registry.model.common.Cursor dataStoreCursor = + ofy() + .load() + .key( + google.registry.model.common.Cursor.createKey( + CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("tld"))) + .now(); + assertThat(createdCursor.getCursorTime()).isEqualTo(cursor.getCursorTime()); + assertThat(cursor).isEqualTo(dataStoreCursor); + } + + @Test + public void saveCursor_worksSuccessfullyOnGlobalCursor() { + google.registry.model.common.Cursor cursor = + google.registry.model.common.Cursor.createGlobal( + CursorType.RECURRING_BILLING, fakeClock.nowUtc()); + CursorDao.saveCursor(cursor, Cursor.GLOBAL); + Cursor createdCursor = CursorDao.load(CursorType.RECURRING_BILLING); + google.registry.model.common.Cursor dataStoreCursor = + ofy() + .load() + .key(google.registry.model.common.Cursor.createGlobalKey(CursorType.RECURRING_BILLING)) + .now(); + assertThat(createdCursor.getCursorTime()).isEqualTo(cursor.getCursorTime()); + assertThat(cursor).isEqualTo(dataStoreCursor); + } + + @Test + public void saveCursor_logsErrorWhenSaveToCloudSqlFails() { + loggerToIntercept.addHandler(logHandler); + createTld("tld"); + google.registry.model.common.Cursor cursor = + google.registry.model.common.Cursor.create( + CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc(), Registry.get("tld")); + CursorDao.saveCursor(cursor, null); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage(Level.SEVERE, "Error saving cursor to Cloud SQL."); + google.registry.model.common.Cursor dataStoreCursor = + ofy() + .load() + .key( + google.registry.model.common.Cursor.createKey( + CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("tld"))) + .now(); + assertThat(cursor).isEqualTo(dataStoreCursor); + } + + @Test + public void saveCursors_worksSuccessfully() { + createTlds("tld", "foo"); + google.registry.model.common.Cursor cursor1 = + google.registry.model.common.Cursor.create( + CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc(), Registry.get("tld")); + google.registry.model.common.Cursor cursor2 = + google.registry.model.common.Cursor.create( + CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc(), Registry.get("foo")); + google.registry.model.common.Cursor cursor3 = + google.registry.model.common.Cursor.createGlobal( + CursorType.RECURRING_BILLING, fakeClock.nowUtc()); + ImmutableMap cursors = + ImmutableMap.builder() + .put(cursor1, "tld") + .put(cursor2, "foo") + .put(cursor3, Cursor.GLOBAL) + .build(); + CursorDao.saveCursors(cursors); + Cursor createdCursor1 = CursorDao.load(CursorType.ICANN_UPLOAD_ACTIVITY, "tld"); + google.registry.model.common.Cursor dataStoreCursor1 = + ofy() + .load() + .key( + google.registry.model.common.Cursor.createKey( + CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("tld"))) + .now(); + assertThat(createdCursor1.getCursorTime()).isEqualTo(cursor1.getCursorTime()); + assertThat(cursor1).isEqualTo(dataStoreCursor1); + Cursor createdCursor2 = CursorDao.load(CursorType.ICANN_UPLOAD_ACTIVITY, "foo"); + google.registry.model.common.Cursor dataStoreCursor2 = + ofy() + .load() + .key( + google.registry.model.common.Cursor.createKey( + CursorType.ICANN_UPLOAD_ACTIVITY, Registry.get("foo"))) + .now(); + assertThat(createdCursor2.getCursorTime()).isEqualTo(cursor2.getCursorTime()); + assertThat(cursor2).isEqualTo(dataStoreCursor2); + Cursor createdCursor3 = CursorDao.load(CursorType.RECURRING_BILLING); + google.registry.model.common.Cursor dataStoreCursor3 = + ofy() + .load() + .key(google.registry.model.common.Cursor.createGlobalKey(CursorType.RECURRING_BILLING)) + .now(); + assertThat(createdCursor3.getCursorTime()).isEqualTo(cursor3.getCursorTime()); + assertThat(cursor3).isEqualTo(dataStoreCursor3); + } } diff --git a/core/src/test/java/google/registry/tools/UpdateCursorsCommandTest.java b/core/src/test/java/google/registry/tools/UpdateCursorsCommandTest.java index 41d7ae89e..f0ac3579b 100644 --- a/core/src/test/java/google/registry/tools/UpdateCursorsCommandTest.java +++ b/core/src/test/java/google/registry/tools/UpdateCursorsCommandTest.java @@ -44,6 +44,9 @@ public class UpdateCursorsCommandTest extends CommandTestCase