diff --git a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java index 40cd37e78..62d185487 100644 --- a/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java +++ b/core/src/main/java/google/registry/batch/ExpandRecurringBillingEventsAction.java @@ -25,8 +25,6 @@ import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_AUTORENEW; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost; -import static google.registry.schema.cursor.Cursor.GLOBAL; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; import static google.registry.util.CollectionUtils.union; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.DateTimeUtils.earliestOf; @@ -60,7 +58,6 @@ 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; @@ -95,7 +92,6 @@ public class ExpandRecurringBillingEventsAction implements Runnable { @Override public void run() { Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); - loadAndCompare(cursor, GLOBAL); DateTime executeTime = clock.nowUtc(); DateTime persistedCursorTime = (cursor == null ? START_OF_TIME : cursor.getCursorTime()); DateTime cursorTime = cursorTimeParam.orElse(persistedCursorTime); @@ -332,7 +328,6 @@ public class ExpandRecurringBillingEventsAction implements Runnable { tm().transact( () -> { Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); - loadAndCompare(cursor, GLOBAL); DateTime currentCursorTime = (cursor == null ? START_OF_TIME : cursor.getCursorTime()); if (!currentCursorTime.equals(expectedPersistedCursorTime)) { @@ -342,8 +337,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable { return; } if (!isDryRun) { - CursorDao.saveCursor( - Cursor.createGlobal(RECURRING_BILLING, executionTime), GLOBAL); + tm().put(Cursor.createGlobal(RECURRING_BILLING, executionTime)); } }); } 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 b9255ebfe..e5e0e783e 100644 --- a/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java +++ b/core/src/main/java/google/registry/export/sheet/SyncRegistrarsSheet.java @@ -25,8 +25,7 @@ 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.schema.cursor.Cursor.GLOBAL; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.base.Joiner; @@ -38,7 +37,6 @@ 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; @@ -64,7 +62,6 @@ class SyncRegistrarsSheet { */ boolean wereRegistrarsModified() { Cursor cursor = ofy().load().key(Cursor.createGlobalKey(SYNC_REGISTRAR_SHEET)).now(); - loadAndCompare(cursor, GLOBAL); DateTime lastUpdateTime = (cursor == null) ? START_OF_TIME : cursor.getCursorTime(); for (Registrar registrar : Registrar.loadAllCached()) { if (DateTimeUtils.isAtOrAfter(registrar.getLastUpdateTime(), lastUpdateTime)) { @@ -155,9 +152,7 @@ class SyncRegistrarsSheet { return builder.build(); }) .collect(toImmutableList())); - CursorDao.saveCursor( - Cursor.createGlobal(SYNC_REGISTRAR_SHEET, executionTime), - google.registry.schema.cursor.Cursor.GLOBAL); + tm().transact(() -> tm().put(Cursor.createGlobal(SYNC_REGISTRAR_SHEET, executionTime))); } 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 58f8150bc..7704d16bd 100644 --- a/core/src/main/java/google/registry/model/common/Cursor.java +++ b/core/src/main/java/google/registry/model/common/Cursor.java @@ -24,12 +24,23 @@ import com.google.common.base.Splitter; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; +import com.googlecode.objectify.annotation.Ignore; +import com.googlecode.objectify.annotation.OnLoad; import com.googlecode.objectify.annotation.Parent; import google.registry.model.ImmutableObject; import google.registry.model.UpdateAutoTimestamp; +import google.registry.model.common.Cursor.CursorId; import google.registry.model.registry.Registry; -import google.registry.schema.replay.DatastoreOnlyEntity; +import google.registry.persistence.VKey; +import google.registry.schema.replay.DatastoreAndSqlEntity; +import java.io.Serializable; import java.util.List; +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.IdClass; +import javax.persistence.PostLoad; +import javax.persistence.Transient; import org.joda.time.DateTime; /** @@ -38,7 +49,12 @@ import org.joda.time.DateTime; * scoped on {@link EntityGroupRoot}. */ @Entity -public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { +@javax.persistence.Entity +@IdClass(CursorId.class) +public class Cursor extends ImmutableObject implements DatastoreAndSqlEntity { + + /** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */ + public static final String GLOBAL = "GLOBAL"; /** The types of cursors, used as the string id field for each cursor in Datastore. */ public enum CursorType { @@ -104,9 +120,9 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { /** * If there are multiple cursors for a given cursor type, a cursor must also have a scope - * defined (distinct from a parent, which is always the EntityGroupRoot key). For instance, - * for a cursor that is defined at the registry level, the scope type will be Registry.class. - * For a cursor (theoretically) defined for each EPP resource, the scope type will be + * defined (distinct from a parent, which is always the EntityGroupRoot key). For instance, for + * a cursor that is defined at the registry level, the scope type will be Registry.class. For a + * cursor (theoretically) defined for each EPP resource, the scope type will be * EppResource.class. For a global cursor, i.e. one that applies per environment, this will be * {@link EntityGroupRoot}. */ @@ -115,24 +131,73 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { } } - @Parent - Key parent = getCrossTldKey(); + @Transient @Parent Key parent = getCrossTldKey(); - @Id - String id; + @Transient @Id String id; + @Ignore + @Enumerated(EnumType.STRING) + @Column(nullable = false) + @javax.persistence.Id + CursorType type; + + @Ignore + @Column(nullable = false) + @javax.persistence.Id + String scope; + + @Column(nullable = false) DateTime cursorTime = START_OF_TIME; /** An automatically managed timestamp of when this object was last written to Datastore. */ + @Column(nullable = false) UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null); + @OnLoad + void onLoad() { + scope = getScopeFromId(id); + type = getTypeFromId(id); + } + + @PostLoad + void postLoad() { + // "Generate" the ID based on the scope and type + Key scopeKey = + scope.equals(GLOBAL) + ? getCrossTldKey() + : Key.create(getCrossTldKey(), Registry.class, scope); + id = generateId(type, scopeKey); + } + + public static VKey createVKey(Key key) { + String id = key.getName(); + return VKey.create(Cursor.class, new CursorId(getTypeFromId(id), getScopeFromId(id)), key); + } + + public VKey createVKey() { + return createVKey(type, scope); + } + + public static VKey createGlobalVKey(CursorType type) { + return createVKey(type, GLOBAL); + } + + public static VKey createVKey(CursorType type, String scope) { + Key key = + scope.equals(GLOBAL) ? createGlobalKey(type) : createKey(type, Registry.get(scope)); + return VKey.create(Cursor.class, new CursorId(type, scope), key); + } + public DateTime getLastUpdateTime() { return lastUpdateTime.getTimestamp(); } + public String getScope() { + return scope; + } + public CursorType getType() { - List id = Splitter.on('_').splitToList(this.id); - return CursorType.valueOf(String.join("_", id.subList(1, id.size()))); + return type; } /** @@ -142,10 +207,12 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { private static void checkValidCursorTypeForScope( CursorType cursorType, Key scope) { checkArgument( - cursorType.getScopeClass().equals( - scope.equals(EntityGroupRoot.getCrossTldKey()) - ? EntityGroupRoot.class - : ofy().factory().getMetadata(scope).getEntityClass()), + cursorType + .getScopeClass() + .equals( + scope.equals(getCrossTldKey()) + ? EntityGroupRoot.class + : ofy().factory().getMetadata(scope).getEntityClass()), "Class required for cursor does not match scope class"); } @@ -154,6 +221,20 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { return String.format("%s_%s", scope.getString(), cursorType.name()); } + private static String getScopeFromId(String id) { + List idSplit = Splitter.on('_').splitToList(id); + // The "parent" is always the crossTldKey; in order to find the scope (either Registry or + // cross-tld-key) we have to parse the part of the ID + Key scopeKey = Key.valueOf(idSplit.get(0)); + return scopeKey.equals(getCrossTldKey()) ? GLOBAL : scopeKey.getName(); + } + + private static CursorType getTypeFromId(String id) { + List idSplit = Splitter.on('_').splitToList(id); + // The cursor type is the second part of the ID string + return CursorType.valueOf(String.join("_", idSplit.subList(1, idSplit.size()))); + } + /** Creates a unique key for a given scope and cursor type. */ public static Key createKey(CursorType cursorType, ImmutableObject scope) { Key scopeKey = Key.create(scope); @@ -166,13 +247,12 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { checkArgument( cursorType.getScopeClass().equals(EntityGroupRoot.class), "Cursor type is not a global cursor."); - return Key.create( - getCrossTldKey(), Cursor.class, generateId(cursorType, EntityGroupRoot.getCrossTldKey())); + return Key.create(getCrossTldKey(), Cursor.class, generateId(cursorType, getCrossTldKey())); } /** Creates a new global cursor instance. */ public static Cursor createGlobal(CursorType cursorType, DateTime cursorTime) { - return create(cursorType, cursorTime, EntityGroupRoot.getCrossTldKey()); + return create(cursorType, cursorTime, getCrossTldKey()); } /** Creates a new cursor instance with a given {@link Key} scope. */ @@ -184,8 +264,10 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { checkNotNull(cursorType, "Cursor type cannot be null"); checkValidCursorTypeForScope(cursorType, scope); instance.id = generateId(cursorType, scope); + instance.type = cursorType; + instance.scope = scope.equals(getCrossTldKey()) ? GLOBAL : scope.getName(); return instance; - } + } /** Creates a new cursor instance with a given {@link ImmutableObject} scope. */ public static Cursor create(CursorType cursorType, DateTime cursorTime, ImmutableObject scope) { @@ -203,4 +285,17 @@ public class Cursor extends ImmutableObject implements DatastoreOnlyEntity { public DateTime getCursorTime() { return cursorTime; } + + static class CursorId extends ImmutableObject implements Serializable { + + public CursorType type; + public String scope; + + private CursorId() {} + + public CursorId(CursorType type, String scope) { + this.type = type; + this.scope = scope; + } + } } diff --git a/core/src/main/java/google/registry/rde/EscrowTaskRunner.java b/core/src/main/java/google/registry/rde/EscrowTaskRunner.java index 362d74a59..eb4410216 100644 --- a/core/src/main/java/google/registry/rde/EscrowTaskRunner.java +++ b/core/src/main/java/google/registry/rde/EscrowTaskRunner.java @@ -15,7 +15,7 @@ package google.registry.rde; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.google.common.flogger.FluentLogger; import google.registry.model.common.Cursor; @@ -24,7 +24,6 @@ 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; @@ -92,7 +91,6 @@ class EscrowTaskRunner { logger.atInfo().log("TLD: %s", registry.getTld()); DateTime startOfToday = clock.nowUtc().withTimeAtStartOfDay(); Cursor cursor = ofy().load().key(Cursor.createKey(cursorType, registry)).now(); - loadAndCompare(cursor, registry.getTldStr()); final DateTime nextRequiredRun = (cursor == null ? startOfToday : cursor.getCursorTime()); if (nextRequiredRun.isAfter(startOfToday)) { throw new NoContentException("Already completed"); @@ -101,7 +99,7 @@ class EscrowTaskRunner { task.runWithLock(nextRequiredRun); DateTime nextRun = nextRequiredRun.plus(interval); logger.atInfo().log("Rolling cursor forward to %s.", nextRun); - CursorDao.saveCursor(Cursor.create(cursorType, nextRun, registry), registry.getTldStr()); + tm().transact(() -> tm().put(Cursor.create(cursorType, nextRun, registry))); 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 cf821d93f..31910f540 100644 --- a/core/src/main/java/google/registry/rde/PendingDepositChecker.java +++ b/core/src/main/java/google/registry/rde/PendingDepositChecker.java @@ -17,7 +17,6 @@ package google.registry.rde; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; import static google.registry.util.DateTimeUtils.isBeforeOrAt; import com.google.common.collect.ImmutableSetMultimap; @@ -28,7 +27,6 @@ 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; @@ -92,12 +90,12 @@ public final class PendingDepositChecker { } // Avoid creating a transaction unless absolutely necessary. Cursor cursor = ofy().load().key(Cursor.createKey(cursorType, registry)).now(); - loadAndCompare(cursor, registry.getTldStr()); DateTime cursorValue = (cursor != null ? cursor.getCursorTime() : startingPoint); if (isBeforeOrAt(cursorValue, now)) { - DateTime watermark = (cursor != null - ? cursor.getCursorTime() - : transactionallyInitializeCursor(registry, cursorType, startingPoint)); + DateTime watermark = + (cursor != null + ? cursor.getCursorTime() + : transactionallyInitializeCursor(registry, cursorType, startingPoint)); if (isBeforeOrAt(watermark, now)) { builder.put(tld, PendingDeposit.create(tld, watermark, mode, cursorType, interval)); } @@ -107,18 +105,14 @@ public final class PendingDepositChecker { } private DateTime transactionallyInitializeCursor( - final Registry registry, - final CursorType cursorType, - final DateTime initialValue) { + final Registry registry, final CursorType cursorType, final DateTime initialValue) { return tm().transact( () -> { Cursor cursor = ofy().load().key(Cursor.createKey(cursorType, registry)).now(); - loadAndCompare(cursor, registry.getTldStr()); if (cursor != null) { return cursor.getCursorTime(); } - CursorDao.saveCursor( - Cursor.create(cursorType, initialValue, registry), registry.getTldStr()); + tm().put(Cursor.create(cursorType, initialValue, registry)); return initialValue; }); } diff --git a/core/src/main/java/google/registry/rde/RdeReportAction.java b/core/src/main/java/google/registry/rde/RdeReportAction.java index 2cb7ed34c..a428d3396 100644 --- a/core/src/main/java/google/registry/rde/RdeReportAction.java +++ b/core/src/main/java/google/registry/rde/RdeReportAction.java @@ -20,7 +20,6 @@ import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.rde.RdeMode.FULL; import static google.registry.request.Action.Method.POST; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; import static google.registry.util.DateTimeUtils.isBeforeOrAt; import com.google.appengine.tools.cloudstorage.GcsFilename; @@ -79,7 +78,6 @@ public final class RdeReportAction implements Runnable, EscrowTask { public void runWithLock(DateTime watermark) throws Exception { Cursor cursor = ofy().load().key(Cursor.createKey(CursorType.RDE_UPLOAD, Registry.get(tld))).now(); - loadAndCompare(cursor, tld); DateTime cursorTime = getCursorTimeOrStartOfTime(cursor); if (isBeforeOrAt(cursorTime, watermark)) { throw new NoContentException( diff --git a/core/src/main/java/google/registry/rde/RdeStagingReducer.java b/core/src/main/java/google/registry/rde/RdeStagingReducer.java index 5da7d924e..9cd22b356 100644 --- a/core/src/main/java/google/registry/rde/RdeStagingReducer.java +++ b/core/src/main/java/google/registry/rde/RdeStagingReducer.java @@ -22,7 +22,6 @@ import static com.google.common.base.Verify.verify; import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.appengine.tools.cloudstorage.GcsFilename; @@ -41,7 +40,6 @@ 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; @@ -213,7 +211,6 @@ public final class RdeStagingReducer extends Reducer { Registry registry = Registry.get(tld); Cursor cursor = ofy().load().key(Cursor.createKey(key.cursor(), registry)).now(); - loadAndCompare(cursor, tld); DateTime position = getCursorTimeOrStartOfTime(cursor); checkState(key.interval() != null, "Interval must be present"); DateTime newPosition = key.watermark().plus(key.interval()); @@ -226,8 +223,7 @@ public final class RdeStagingReducer extends Reducer { - CursorDao.saveCursor( - Cursor.create(RDE_UPLOAD_SFTP, tm().getTransactionTime(), Registry.get(tld)), - tld); - }); + () -> + tm().put( + Cursor.create( + RDE_UPLOAD_SFTP, tm().getTransactionTime(), Registry.get(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 aa753d308..10df970ea 100644 --- a/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java +++ b/core/src/main/java/google/registry/reporting/icann/IcannReportingUploadAction.java @@ -20,7 +20,6 @@ import static google.registry.model.common.Cursor.getCursorTimeOrStartOfTime; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.request.Action.Method.POST; -import static google.registry.schema.cursor.CursorDao.loadAndCompareAll; import static javax.servlet.http.HttpServletResponse.SC_OK; import com.google.appengine.tools.cloudstorage.GcsFilename; @@ -42,7 +41,6 @@ import google.registry.request.HttpException.ServiceUnavailableException; 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; @@ -50,7 +48,6 @@ 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; @@ -188,9 +185,7 @@ public final class IcannReportingUploadAction implements Runnable { cursorType, cursorTime.withTimeAtStartOfDay().withDayOfMonth(1).plusMonths(1), Registry.get(tldStr)); - CursorDao.saveCursor( - newCursor, - Optional.ofNullable(tldStr).orElse(google.registry.schema.cursor.Cursor.GLOBAL)); + tm().transact(() -> tm().put(newCursor)); } } @@ -258,7 +253,6 @@ public final class IcannReportingUploadAction implements Runnable { } cursors.put(cursor, registry.getTldStr()); }); - loadAndCompareAll(cursors.build(), type); return cursors.build(); } diff --git a/core/src/main/java/google/registry/schema/cursor/Cursor.java b/core/src/main/java/google/registry/schema/cursor/Cursor.java deleted file mode 100644 index 6074032c0..000000000 --- a/core/src/main/java/google/registry/schema/cursor/Cursor.java +++ /dev/null @@ -1,124 +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.schema.cursor; - -import static com.google.appengine.api.search.checkers.Preconditions.checkNotNull; - -import google.registry.model.ImmutableObject; -import google.registry.model.UpdateAutoTimestamp; -import google.registry.model.common.Cursor.CursorType; -import google.registry.schema.cursor.Cursor.CursorId; -import google.registry.schema.replay.DatastoreEntity; -import google.registry.schema.replay.SqlEntity; -import google.registry.util.DateTimeUtils; -import java.io.Serializable; -import java.time.ZonedDateTime; -import java.util.Optional; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; -import org.joda.time.DateTime; - -/** - * Shared entity for date cursors. This uses a compound primary key as defined in {@link CursorId}. - */ -@Entity -@Table -@IdClass(CursorId.class) -public class Cursor implements SqlEntity { - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - @Id - private CursorType type; - - @Column @Id private String scope; - - @Column(nullable = false) - private ZonedDateTime cursorTime; - - @Column(nullable = false) - private UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null); - - /** The scope of a global cursor. A global cursor is a cursor that is not specific to one tld. */ - public static final String GLOBAL = "GLOBAL"; - - private Cursor(CursorType type, String scope, DateTime cursorTime) { - this.type = type; - this.scope = scope; - this.cursorTime = DateTimeUtils.toZonedDateTime(cursorTime); - } - - // Hibernate requires a default constructor. - private Cursor() {} - - /** Constructs a {@link Cursor} object. */ - public static Cursor create(CursorType type, String scope, DateTime cursorTime) { - checkNotNull( - scope, "Scope cannot be null. To create a global cursor, use the createGlobal method"); - return new Cursor(type, scope, cursorTime); - } - - /** Constructs a {@link Cursor} object with a {@link GLOBAL} scope. */ - public static Cursor createGlobal(CursorType type, DateTime cursorTime) { - return new Cursor(type, GLOBAL, cursorTime); - } - - /** Returns the type of the cursor. */ - public CursorType getType() { - return type; - } - - /** - * Returns the scope of the cursor. The scope will typically be the tld the cursor is referring - * to. If the cursor is a global cursor, the scope will be {@link GLOBAL}. - */ - public String getScope() { - return scope; - } - - /** Returns the time the cursor is set to. */ - public DateTime getCursorTime() { - return DateTimeUtils.toJodaDateTime(cursorTime); - } - - /** Returns the last time the cursor was updated. */ - public DateTime getLastUpdateTime() { - return lastUpdateTime.getTimestamp(); - } - - @Override - public Optional toDatastoreEntity() { - return Optional.empty(); // Cursors are not converted since they are ephemeral - } - - static class CursorId extends ImmutableObject implements Serializable { - - public CursorType type; - - public String scope; - - private CursorId() {} - - public CursorId(CursorType type, String scope) { - this.type = type; - this.scope = scope; - } - } -} diff --git a/core/src/main/java/google/registry/schema/cursor/CursorDao.java b/core/src/main/java/google/registry/schema/cursor/CursorDao.java deleted file mode 100644 index 860f89228..000000000 --- a/core/src/main/java/google/registry/schema/cursor/CursorDao.java +++ /dev/null @@ -1,208 +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.schema.cursor; - -import static com.google.appengine.api.search.checkers.Preconditions.checkNotNull; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.persistence.transaction.TransactionManagerFactory.tm; -import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; -import com.google.common.flogger.FluentLogger; -import google.registry.model.common.Cursor.CursorType; -import google.registry.schema.cursor.Cursor.CursorId; -import java.util.List; -import javax.annotation.Nullable; - -/** 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( - () -> { - jpaTm().getEntityManager().merge(cursor); - }); - } - - 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 cannot be null"); - return jpaTm() - .transact(() -> jpaTm().getEntityManager().find(Cursor.class, new CursorId(type, scope))); - } - - /** If no scope is given, use {@link Cursor#GLOBAL} as the scope. */ - public static Cursor load(CursorType type) { - checkNotNull(type, "The type of the cursor to load must be specified"); - return load(type, Cursor.GLOBAL); - } - - public static List loadAll() { - return jpaTm() - .transact( - () -> - jpaTm() - .getEntityManager() - .createQuery("SELECT cursor FROM Cursor cursor", Cursor.class) - .getResultList()); - } - - public static List loadByType(CursorType type) { - checkNotNull(type, "The type of the cursors to load must be specified"); - return jpaTm() - .transact( - () -> - jpaTm() - .getEntityManager() - .createQuery( - "SELECT cursor FROM Cursor cursor WHERE cursor.type = :type", Cursor.class) - .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(); - checkArgumentNotNull(scope, "The scope of the cursor cannot be null"); - Cursor cloudSqlCursor = Cursor.create(type, scope, cursor.getCursorTime()); - try { - save(cloudSqlCursor); - logger.atInfo().log( - "Rolled forward CloudSQL cursor for %s to %s.", scope, cursor.getCursorTime()); - } catch (Exception e) { - logger.atSevere().withCause(e).log("Error saving cursor to Cloud SQL: %s.", cloudSqlCursor); - } - } - - /** - * 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."); - } - } - - /** - * Loads in cursor from Cloud SQL and compares it to the Datastore cursor - * - *

This takes in a cursor from Datastore and checks to see if it exists in Cloud SQL and has - * the same value. If a difference is detected, or the Cloud SQL cursor does not exist, a warning - * is logged. - */ - public static void loadAndCompare( - @Nullable google.registry.model.common.Cursor datastoreCursor, String scope) { - if (datastoreCursor == null) { - return; - } - try { - // Load the corresponding cursor from Cloud SQL - Cursor cloudSqlCursor = load(datastoreCursor.getType(), scope); - compare(datastoreCursor, cloudSqlCursor, scope); - } catch (Throwable t) { - logger.atSevere().withCause(t).log("Error comparing cursors."); - } - } - - /** - * Loads in all cursors of a given type from Cloud SQL and compares them to Datastore - * - *

This takes in cursors from Datastore and checks to see if they exists in Cloud SQL and have - * the same value. If a difference is detected, or a Cloud SQL cursor does not exist, a warning is - * logged. - */ - public static void loadAndCompareAll( - ImmutableMap cursors, CursorType type) { - try { - // Load all the cursors of that type from Cloud SQL - List cloudSqlCursors = loadByType(type); - - // Create a map of each TLD to its cursor if one exists. - ImmutableMap cloudSqlCursorMap = - Maps.uniqueIndex(cloudSqlCursors, Cursor::getScope); - - // Compare each Datastore cursor with its corresponding Cloud SQL cursor. - for (google.registry.model.common.Cursor cursor : cursors.keySet()) { - Cursor cloudSqlCursor = cloudSqlCursorMap.get(cursors.get(cursor)); - compare(cursor, cloudSqlCursor, cursors.get(cursor)); - } - } catch (Throwable t) { - logger.atSevere().withCause(t).log("Error comparing cursors."); - } - } - - private static void compare( - google.registry.model.common.Cursor datastoreCursor, - @Nullable Cursor cloudSqlCursor, - String scope) { - if (cloudSqlCursor == null) { - logger.atWarning().log( - String.format( - "Cursor of type %s with the scope %s was not found in Cloud SQL.", - datastoreCursor.getType().name(), scope)); - } else if (!datastoreCursor.getCursorTime().equals(cloudSqlCursor.getCursorTime())) { - logger.atWarning().log( - String.format( - "This cursor of type %s with the scope %s has a cursorTime of %s in Datastore and %s" - + " in Cloud SQL.", - datastoreCursor.getType(), - scope, - datastoreCursor.getCursorTime(), - cloudSqlCursor.getCursorTime())); - } - } -} diff --git a/core/src/main/java/google/registry/tools/ListCursorsCommand.java b/core/src/main/java/google/registry/tools/ListCursorsCommand.java index f5766734f..eacd5e1b5 100644 --- a/core/src/main/java/google/registry/tools/ListCursorsCommand.java +++ b/core/src/main/java/google/registry/tools/ListCursorsCommand.java @@ -16,7 +16,6 @@ package google.registry.tools; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; @@ -75,9 +74,6 @@ final class ListCursorsCommand implements CommandWithRemoteApi { } private static String renderLine(String tld, Optional cursor) { - if (cursor.isPresent()) { - loadAndCompare(cursor.get(), tld); - } return String.format( OUTPUT_FMT, tld, diff --git a/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java b/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java index 2d49f7607..de7dff015 100644 --- a/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java +++ b/core/src/main/java/google/registry/tools/UpdateCursorsCommand.java @@ -14,15 +14,15 @@ package google.registry.tools; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.util.CollectionUtils.isNullOrEmpty; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; -import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableList; 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; @@ -34,10 +34,7 @@ final class UpdateCursorsCommand extends ConfirmingCommand implements CommandWit @Parameter(description = "TLDs on which to operate. Omit for global cursors.") private List tlds; - @Parameter( - names = "--type", - description = "Which cursor to update.", - required = true) + @Parameter(names = "--type", description = "Which cursor to update.", required = true) private CursorType cursorType; @Parameter( @@ -47,28 +44,25 @@ final class UpdateCursorsCommand extends ConfirmingCommand implements CommandWit required = true) private DateTime newTimestamp; - ImmutableMap cursorsToUpdate; + ImmutableList cursorsToUpdate; @Override protected void init() { - ImmutableMap.Builder cursorsToUpdateBuilder = new ImmutableMap.Builder<>(); + ImmutableList.Builder result = new ImmutableList.Builder<>(); if (isNullOrEmpty(tlds)) { - cursorsToUpdateBuilder.put( - Cursor.createGlobal(cursorType, newTimestamp), - google.registry.schema.cursor.Cursor.GLOBAL); + result.add(Cursor.createGlobal(cursorType, newTimestamp)); } else { for (String tld : tlds) { Registry registry = Registry.get(tld); - cursorsToUpdateBuilder.put( - Cursor.create(cursorType, newTimestamp, registry), registry.getTldStr()); + result.add(Cursor.create(cursorType, newTimestamp, registry)); } } - cursorsToUpdate = cursorsToUpdateBuilder.build(); + cursorsToUpdate = result.build(); } @Override - protected String execute() throws Exception { - CursorDao.saveCursors(cursorsToUpdate); + protected String execute() { + tm().transact(() -> tm().putAll(cursorsToUpdate)); return String.format("Updated %d cursors.\n", cursorsToUpdate.size()); } @@ -79,14 +73,13 @@ final class UpdateCursorsCommand extends ConfirmingCommand implements CommandWit if (cursorsToUpdate.isEmpty()) { return "No cursor changes to apply."; } - cursorsToUpdate.entrySet().stream() - .forEach(entry -> changes.append(getChangeString(entry.getKey(), entry.getValue()))); + cursorsToUpdate.forEach(cursor -> changes.append(getChangeString(cursor))); return changes.toString(); } - private String getChangeString(Cursor cursor, String scope) { + private String getChangeString(Cursor cursor) { return String.format( "Change cursorTime of %s for Scope:%s to %s\n", - cursor.getType(), scope, cursor.getCursorTime()); + cursor.getType(), cursor.getScope(), cursor.getCursorTime()); } } diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 0cb05102b..9b8087094 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -41,6 +41,7 @@ google.registry.model.billing.BillingEvent$Cancellation google.registry.model.billing.BillingEvent$OneTime google.registry.model.billing.BillingEvent$Recurring + google.registry.model.common.Cursor google.registry.model.contact.ContactHistory google.registry.model.contact.ContactResource google.registry.model.domain.DomainBase @@ -69,7 +70,6 @@ google.registry.model.tmch.ClaimsListShard google.registry.model.tmch.TmchCrl google.registry.persistence.transaction.TransactionEntity - google.registry.schema.cursor.Cursor google.registry.schema.domain.RegistryLock google.registry.schema.replay.SqlReplayCheckpoint google.registry.schema.server.Lock diff --git a/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java b/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java index 0af991607..769abeda7 100644 --- a/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java +++ b/core/src/test/java/google/registry/backup/CommitLogCheckpointStrategyTest.java @@ -30,7 +30,6 @@ import google.registry.model.ofy.DatastoreTransactionManager; import google.registry.model.ofy.Ofy; import google.registry.model.registry.Registry; import google.registry.persistence.transaction.TransactionManager; -import google.registry.schema.cursor.CursorDao; import google.registry.testing.AppEngineExtension; import google.registry.testing.FakeClock; import google.registry.testing.InjectExtension; @@ -280,18 +279,18 @@ public class CommitLogCheckpointStrategyTest { // Bucket checkpoint times should be clamped as expected. assertThat(strategy.computeCheckpoint()) - .isEqualTo(CommitLogCheckpoint.create( - checkpointTime, - ImmutableMap.of(1, now, 2, threshold, 3, threshold))); + .isEqualTo( + CommitLogCheckpoint.create( + checkpointTime, ImmutableMap.of(1, now, 2, threshold, 3, threshold))); } private void writeCommitLogToBucket(final int bucketId) { fakeBucketIdSupplier.value = bucketId; tm.transact( () -> - CursorDao.saveCursor( - Cursor.create(RDE_REPORT, tm.getTransactionTime(), Registry.get("tld" + bucketId)), - "tld" + bucketId)); + tm.put( + Cursor.create( + RDE_REPORT, tm.getTransactionTime(), Registry.get("tld" + bucketId)))); fakeBucketIdSupplier.value = null; } diff --git a/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java b/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java index b40d4e58b..e0b2d9feb 100644 --- a/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java +++ b/core/src/test/java/google/registry/backup/ReplayCommitLogsToSqlActionTest.java @@ -43,12 +43,11 @@ import com.google.common.collect.ImmutableSet; import com.google.common.truth.Truth8; import com.googlecode.objectify.Key; import google.registry.config.RegistryConfig; -import google.registry.model.common.Cursor; -import google.registry.model.common.Cursor.CursorType; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainBase; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.secdns.DelegationSignerData; +import google.registry.model.index.ForeignKeyIndex; import google.registry.model.ofy.CommitLogBucket; import google.registry.model.ofy.CommitLogManifest; import google.registry.model.ofy.CommitLogMutation; @@ -367,10 +366,11 @@ public class ReplayCommitLogsToSqlActionTest { Key manifestKey = CommitLogManifest.createKey(getBucketKey(1), now.minusMinutes(1)); + createTld("tld"); // Have a commit log with a couple objects that shouldn't be replayed ReservedList reservedList = new ReservedList.Builder().setReservedListMap(ImmutableMap.of()).setName("name").build(); - Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, now.minusHours(1)); + ForeignKeyIndex fki = ForeignKeyIndex.create(newDomainBase("foo.tld"), now); tm().transact( () -> { try { @@ -381,8 +381,8 @@ public class ReplayCommitLogsToSqlActionTest { getBucketKey(1), now.minusMinutes(1), ImmutableSet.of()), // Reserved list is dually-written non-replicated CommitLogMutation.create(manifestKey, reservedList), - // Cursors aren't replayed to SQL at all - CommitLogMutation.create(manifestKey, cursor)); + // FKIs aren't replayed to SQL at all + CommitLogMutation.create(manifestKey, fki)); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java index 4b56061db..d9dc04764 100644 --- a/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java +++ b/core/src/test/java/google/registry/batch/ExpandRecurringBillingEventsActionTest.java @@ -19,6 +19,7 @@ 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.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.assertBillingEvents; import static google.registry.testing.DatabaseHelper.assertBillingEventsForResource; import static google.registry.testing.DatabaseHelper.createTld; @@ -49,7 +50,6 @@ 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.InjectExtension; @@ -101,9 +101,7 @@ public class ExpandRecurringBillingEventsActionTest } private void saveCursor(final DateTime cursorTime) { - CursorDao.saveCursor( - Cursor.createGlobal(RECURRING_BILLING, cursorTime), - google.registry.schema.cursor.Cursor.GLOBAL); + tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, cursorTime))); } private void runMapreduce() throws Exception { 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 619f8516e..8de1b0271 100644 --- a/core/src/test/java/google/registry/model/common/CursorTest.java +++ b/core/src/test/java/google/registry/model/common/CursorTest.java @@ -18,9 +18,8 @@ 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_UPLOAD; import static google.registry.model.common.Cursor.CursorType.RECURRING_BILLING; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.schema.cursor.Cursor.GLOBAL; -import static google.registry.schema.cursor.CursorDao.loadAndCompare; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; +import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -29,56 +28,57 @@ import static org.junit.jupiter.api.Assertions.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 google.registry.testing.DualDatabaseTest; +import google.registry.testing.TestOfyAndSql; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; /** Unit tests for {@link Cursor}. */ -class CursorTest extends EntityTestCase { +@DualDatabaseTest +public class CursorTest extends EntityTestCase { + + public CursorTest() { + super(JpaEntityCoverageCheck.ENABLED); + } @BeforeEach void setUp() { fakeClock.setTo(DateTime.parse("2010-10-17TZ")); } - @Test + @TestOfyAndSql void testSuccess_persistScopedCursor() { createTld("tld"); this.fakeClock.advanceOneMilli(); final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); Cursor cursor = Cursor.create(RDE_UPLOAD, time, Registry.get("tld")); - CursorDao.saveCursor(cursor, "tld"); - assertThat(ofy().load().key(Cursor.createKey(BRDA, Registry.get("tld"))).now()).isNull(); - assertThat( - ofy() - .load() - .key(Cursor.createKey(RDE_UPLOAD, Registry.get("tld"))) - .now() - .getCursorTime()) - .isEqualTo(time); - loadAndCompare(cursor, "tld"); + tm().transact(() -> tm().put(cursor)); + transactIfJpaTm( + () -> { + assertThat(tm().loadByKeyIfPresent(Cursor.createVKey(BRDA, "tld")).isPresent()).isFalse(); + assertThat(tm().loadByKey(Cursor.createVKey(RDE_UPLOAD, "tld")).getCursorTime()) + .isEqualTo(time); + }); } - @Test + @TestOfyAndSql void testSuccess_persistGlobalCursor() { final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); - CursorDao.saveCursor(Cursor.createGlobal(RECURRING_BILLING, time), GLOBAL); - assertThat(ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now().getCursorTime()) + Cursor cursor = Cursor.createGlobal(RECURRING_BILLING, time); + tm().transact(() -> tm().put(cursor)); + assertThat(tm().transact(() -> tm().loadByKey(cursor.createVKey())).getCursorTime()) .isEqualTo(time); - loadAndCompare(Cursor.createGlobal(RECURRING_BILLING, time), GLOBAL); } - @Test + @TestOfyAndSql void testIndexing() throws Exception { final DateTime time = DateTime.parse("2012-07-12T03:30:00.000Z"); - CursorDao.saveCursor(Cursor.createGlobal(RECURRING_BILLING, time), GLOBAL); - Cursor cursor = ofy().load().key(Cursor.createGlobalKey(RECURRING_BILLING)).now(); - loadAndCompare(cursor, GLOBAL); + tm().transact(() -> tm().put(Cursor.createGlobal(RECURRING_BILLING, time))); + Cursor cursor = tm().transact(() -> tm().loadByKey(Cursor.createGlobalVKey(RECURRING_BILLING))); verifyIndexing(cursor); } - @Test + @TestOfyAndSql void testFailure_invalidScopeOnCreate() { createTld("tld"); this.fakeClock.advanceOneMilli(); @@ -87,13 +87,14 @@ class CursorTest extends EntityTestCase { IllegalArgumentException thrown = assertThrows( IllegalArgumentException.class, - () -> CursorDao.saveCursor(Cursor.create(RDE_UPLOAD, time, domain), domain.getTld())); + () -> Cursor.create(RDE_UPLOAD, time, domain), + domain.getTld()); assertThat(thrown) .hasMessageThat() .contains("Class required for cursor does not match scope class"); } - @Test + @TestOfyAndSql void testFailure_invalidScopeOnKeyCreate() { createTld("tld"); IllegalArgumentException thrown = @@ -105,14 +106,14 @@ class CursorTest extends EntityTestCase { .contains("Class required for cursor does not match scope class"); } - @Test + @TestOfyAndSql void testFailure_createGlobalKeyForScopedCursorType() { IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> Cursor.createGlobalKey(RDE_UPLOAD)); assertThat(thrown).hasMessageThat().contains("Cursor type is not a global cursor"); } - @Test + @TestOfyAndSql void testFailure_invalidScopeOnGlobalKeyCreate() { createTld("tld"); IllegalArgumentException thrown = @@ -124,7 +125,7 @@ class CursorTest extends EntityTestCase { .contains("Class required for cursor does not match scope class"); } - @Test + @TestOfyAndSql void testFailure_nullScope() { NullPointerException thrown = assertThrows( @@ -133,7 +134,7 @@ class CursorTest extends EntityTestCase { assertThat(thrown).hasMessageThat().contains("Cursor scope cannot be null"); } - @Test + @TestOfyAndSql void testFailure_nullCursorType() { createTld("tld"); NullPointerException thrown = @@ -143,7 +144,7 @@ class CursorTest extends EntityTestCase { assertThat(thrown).hasMessageThat().contains("Cursor type cannot be null"); } - @Test + @TestOfyAndSql void testFailure_nullTime() { createTld("tld"); NullPointerException thrown = diff --git a/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java b/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java index eca4ac0b2..0f992fb13 100644 --- a/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java +++ b/core/src/test/java/google/registry/rde/PendingDepositCheckerTest.java @@ -20,6 +20,7 @@ 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.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistResource; import static org.joda.time.DateTimeConstants.TUESDAY; @@ -30,7 +31,6 @@ 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.AppEngineExtension; import google.registry.testing.FakeClock; import google.registry.testing.InjectExtension; @@ -149,17 +149,24 @@ public class PendingDepositCheckerTest { clock.advanceOneMilli(); createTldWithEscrowEnabled("fun"); clock.advanceOneMilli(); - assertThat(checker.getTldsAndWatermarksPendingDepositForRdeAndBrda()).isEqualTo( - ImmutableSetMultimap.of( - "pal", PendingDeposit.create( - "pal", DateTime.parse("2000-01-01TZ"), FULL, RDE_STAGING, standardDays(1)), - "fun", PendingDeposit.create( - "fun", DateTime.parse("2000-01-01TZ"), FULL, RDE_STAGING, standardDays(1)))); + assertThat(checker.getTldsAndWatermarksPendingDepositForRdeAndBrda()) + .isEqualTo( + ImmutableSetMultimap.of( + "pal", + PendingDeposit.create( + "pal", DateTime.parse("2000-01-01TZ"), FULL, RDE_STAGING, standardDays(1)), + "fun", + PendingDeposit.create( + "fun", + DateTime.parse("2000-01-01TZ"), + FULL, + RDE_STAGING, + standardDays(1)))); } private static void setCursor( final Registry registry, final CursorType cursorType, final DateTime value) { - CursorDao.saveCursor(Cursor.create(cursorType, value, registry), registry.getTldStr()); + tm().transact(() -> tm().put(Cursor.create(cursorType, value, registry))); } 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 9b4531d80..c3a4e4ce7 100644 --- a/core/src/test/java/google/registry/rde/RdeStagingActionTest.java +++ b/core/src/test/java/google/registry/rde/RdeStagingActionTest.java @@ -19,6 +19,7 @@ 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.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.rde.RdeFixtures.makeContactResource; import static google.registry.rde.RdeFixtures.makeDomainBase; import static google.registry.rde.RdeFixtures.makeHostResource; @@ -54,7 +55,6 @@ 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; @@ -842,7 +842,7 @@ public class RdeStagingActionTest extends MapreduceTestCase { private void setCursor( final Registry registry, final CursorType cursorType, final DateTime value) { clock.advanceOneMilli(); - CursorDao.saveCursor(Cursor.create(cursorType, value, registry), registry.getTldStr()); + tm().transact(() -> tm().put(Cursor.create(cursorType, value, registry))); } public static T unmarshal(Class clazz, byte[] xml) throws XmlException { diff --git a/core/src/test/java/google/registry/rde/RdeStagingReducerTest.java b/core/src/test/java/google/registry/rde/RdeStagingReducerTest.java index 73cc6acf0..b2b13ec17 100644 --- a/core/src/test/java/google/registry/rde/RdeStagingReducerTest.java +++ b/core/src/test/java/google/registry/rde/RdeStagingReducerTest.java @@ -39,7 +39,6 @@ import google.registry.model.rde.RdeMode; import google.registry.model.rde.RdeRevision; import google.registry.model.registry.Registry; import google.registry.request.RequestParameters; -import google.registry.schema.cursor.CursorDao; import google.registry.testing.AppEngineExtension; import google.registry.testing.FakeClock; import google.registry.testing.FakeKeyringModule; @@ -105,10 +104,10 @@ class RdeStagingReducerTest { @BeforeEach void beforeEach() { createTld("soy"); - CursorDao.saveCursor(Cursor.create(CursorType.BRDA, now, Registry.get("soy")), "soy"); - CursorDao.saveCursor(Cursor.create(CursorType.RDE_STAGING, now, Registry.get("soy")), "soy"); tm().transact( () -> { + tm().put(Cursor.create(CursorType.BRDA, now, Registry.get("soy"))); + tm().put(Cursor.create(CursorType.RDE_STAGING, now, Registry.get("soy"))); RdeRevision.saveRevision("soy", now, THIN, 0); RdeRevision.saveRevision("soy", now, FULL, 0); }); diff --git a/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java b/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java deleted file mode 100644 index 5d076dc8a..000000000 --- a/core/src/test/java/google/registry/schema/cursor/CursorDaoTest.java +++ /dev/null @@ -1,412 +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.schema.cursor; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.model.ofy.ObjectifyService.ofy; -import static google.registry.testing.DatabaseHelper.createTld; -import static google.registry.testing.DatabaseHelper.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.testing.AppEngineExtension; -import google.registry.testing.FakeClock; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** Unit tests for {@link Cursor}. */ -public class CursorDaoTest { - - private final FakeClock fakeClock = new FakeClock(); - - private final TestLogHandler logHandler = new TestLogHandler(); - private final Logger loggerToIntercept = Logger.getLogger(CursorDao.class.getCanonicalName()); - - @RegisterExtension - final AppEngineExtension appEngine = - AppEngineExtension.builder() - .withDatastoreAndCloudSql() - .enableJpaEntityCoverageCheck(true) - .withClock(fakeClock) - .build(); - - @Test - void save_worksSuccessfullyOnNewCursor() { - Cursor cursor = Cursor.create(CursorType.BRDA, "tld", fakeClock.nowUtc()); - CursorDao.save(cursor); - Cursor returnedCursor = CursorDao.load(CursorType.BRDA, "tld"); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime()); - } - - @Test - void save_worksSuccessfullyOnExistingCursor() { - Cursor cursor = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc()); - CursorDao.save(cursor); - Cursor cursor2 = Cursor.create(CursorType.RDE_REPORT, "tld", fakeClock.nowUtc().plusDays(3)); - CursorDao.save(cursor2); - Cursor returnedCursor = CursorDao.load(CursorType.RDE_REPORT, "tld"); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime()); - } - - @Test - void save_worksSuccessfullyOnNewGlobalCursor() { - Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc()); - CursorDao.save(cursor); - Cursor returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime()); - } - - @Test - void save_worksSuccessfullyOnExistingGlobalCursor() { - Cursor cursor = Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc()); - CursorDao.save(cursor); - Cursor cursor2 = - Cursor.createGlobal(CursorType.RECURRING_BILLING, fakeClock.nowUtc().plusDays(3)); - CursorDao.save(cursor2); - Cursor returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor2.getCursorTime()); - } - - @Test - 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 - void saveAll_worksSuccessfullyEmptySet() { - CursorDao.saveAll(ImmutableSet.of()); - assertThat(CursorDao.loadAll()).isEmpty(); - } - - @Test - 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.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"); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor4.getCursorTime()); - returnedCursor = CursorDao.load(CursorType.RECURRING_BILLING); - assertThat(returnedCursor.getCursorTime()).isEqualTo(cursor.getCursorTime()); - } - - @Test - void loadAll_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.saveAll(ImmutableSet.of(cursor, cursor2, cursor3, cursor4)); - List returnedCursors = CursorDao.loadAll(); - assertThat(returnedCursors.size()).isEqualTo(4); - } - - @Test - void loadAll_worksSuccessfullyEmptyTable() { - List returnedCursors = CursorDao.loadAll(); - assertThat(returnedCursors.size()).isEqualTo(0); - } - - @Test - void loadByType_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.saveAll(ImmutableSet.of(cursor, cursor2, cursor3, cursor4)); - List returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT); - assertThat(returnedCursors.size()).isEqualTo(2); - } - - @Test - void loadByType_worksSuccessfullyNoneOfType() { - List returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT); - assertThat(returnedCursors.size()).isEqualTo(0); - } - - @Test - 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 - 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 - 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); - } - - @Test - void loadAndCompare_worksSuccessfully() { - 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, "tld"); - CursorDao.loadAndCompare(cursor, "tld"); - assertAboutLogs().that(logHandler).hasNoLogsAtLevel(Level.WARNING); - } - - @Test - void loadAndCompare_worksSuccessfullyGlobalCursor() { - loggerToIntercept.addHandler(logHandler); - google.registry.model.common.Cursor cursor = - google.registry.model.common.Cursor.createGlobal( - CursorType.RECURRING_BILLING, fakeClock.nowUtc()); - CursorDao.saveCursor(cursor, Cursor.GLOBAL); - CursorDao.loadAndCompare(cursor, Cursor.GLOBAL); - assertAboutLogs().that(logHandler).hasNoLogsAtLevel(Level.WARNING); - } - - @Test - void loadAndCompare_worksSuccessfullyCursorNotInCloudSql() { - 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.loadAndCompare(cursor, "tld"); - assertAboutLogs() - .that(logHandler) - .hasLogAtLevelWithMessage( - Level.WARNING, - "Cursor of type ICANN_UPLOAD_ACTIVITY with the scope tld was not found in Cloud SQL."); - } - - @Test - void loadAndCompare_worksSuccessfullyGlobalCursorNotInCloudSql() { - loggerToIntercept.addHandler(logHandler); - google.registry.model.common.Cursor cursor = - google.registry.model.common.Cursor.createGlobal( - CursorType.RECURRING_BILLING, fakeClock.nowUtc()); - CursorDao.loadAndCompare(cursor, Cursor.GLOBAL); - assertAboutLogs() - .that(logHandler) - .hasLogAtLevelWithMessage( - Level.WARNING, - "Cursor of type RECURRING_BILLING with the scope GLOBAL was not found in Cloud SQL."); - } - - @Test - void loadAndCompare_worksSuccessfullyCursorsNotEqual() { - 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, "tld"); - cursor = - google.registry.model.common.Cursor.create( - CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc().minusDays(5), Registry.get("tld")); - CursorDao.loadAndCompare(cursor, "tld"); - assertAboutLogs() - .that(logHandler) - .hasLogAtLevelWithMessage( - Level.WARNING, - "This cursor of type ICANN_UPLOAD_ACTIVITY with the scope tld has a cursorTime of" - + " 1969-12-27T00:00:00.000Z in Datastore and 1970-01-01T00:00:00.000Z in Cloud" - + " SQL."); - } - - @Test - void loadAndCompareAll_worksSuccessfully() { - loggerToIntercept.addHandler(logHandler); - - // Create Datastore cursors - 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")); - - // Save cursors to Cloud SQL - ImmutableMap cursors = - ImmutableMap.builder() - .put(cursor1, "tld") - .put(cursor2, "foo") - .build(); - CursorDao.saveCursors(cursors); - - CursorDao.loadAndCompareAll(cursors, CursorType.ICANN_UPLOAD_ACTIVITY); - assertAboutLogs().that(logHandler).hasNoLogsAtLevel(Level.WARNING); - } - - @Test - void loadAndCompareAll_worksSuccessfullyMissingOne() { - loggerToIntercept.addHandler(logHandler); - - // Create Datastore cursors - createTlds("tld", "foo", "lol"); - 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")); - - // Save Cursors to Cloud SQL - ImmutableMap.Builder cursors = - ImmutableMap.builder() - .put(cursor1, "tld") - .put(cursor2, "foo"); - CursorDao.saveCursors(cursors.build()); - - // Create a new Datastore cursor that is not saved to Cloud SQL - google.registry.model.common.Cursor cursor3 = - google.registry.model.common.Cursor.create( - CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc().minusDays(4), Registry.get("lol")); - - // Call loadAndCompareAll with all three Datastore cursors - CursorDao.loadAndCompareAll( - cursors.put(cursor3, "lol").build(), CursorType.ICANN_UPLOAD_ACTIVITY); - - assertAboutLogs() - .that(logHandler) - .hasLogAtLevelWithMessage( - Level.WARNING, - "Cursor of type ICANN_UPLOAD_ACTIVITY with the scope lol was not found in Cloud SQL."); - } - - @Test - void loadAndCompareAll_worksSuccessfullyOneWithWrongTime() { - loggerToIntercept.addHandler(logHandler); - - // Create Datastore cursors - 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")); - - // Save Cursors to Cloud SQL - CursorDao.saveCursors(ImmutableMap.of(cursor1, "tld", cursor2, "foo")); - - // Change time of first Datastore cursor, but don't save new time to Cloud SQL - google.registry.model.common.Cursor cursor3 = - google.registry.model.common.Cursor.create( - CursorType.ICANN_UPLOAD_ACTIVITY, fakeClock.nowUtc().minusDays(4), Registry.get("tld")); - - CursorDao.loadAndCompareAll( - ImmutableMap.of(cursor2, "foo", cursor3, "tld"), CursorType.ICANN_UPLOAD_ACTIVITY); - - assertAboutLogs() - .that(logHandler) - .hasLogAtLevelWithMessage( - Level.WARNING, - "This cursor of type ICANN_UPLOAD_ACTIVITY with the scope tld has a cursorTime of" - + " 1969-12-28T00:00:00.000Z in Datastore and 1970-01-01T00:00:00.000Z in Cloud" - + " SQL."); - } - - @Test - void loadAndCompareAll_worksSuccessfullyEmptyMap() { - loggerToIntercept.addHandler(logHandler); - CursorDao.loadAndCompareAll(ImmutableMap.of(), CursorType.ICANN_UPLOAD_ACTIVITY); - assertAboutLogs().that(logHandler).hasNoLogsAtLevel(Level.WARNING); - } -} 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 05e86b56a..644100a7c 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -17,6 +17,7 @@ package google.registry.schema.integration; import static com.google.common.truth.Truth.assert_; import google.registry.model.billing.BillingEventTest; +import google.registry.model.common.CursorTest; import google.registry.model.contact.ContactResourceTest; import google.registry.model.domain.DomainBaseSqlTest; import google.registry.model.domain.token.AllocationTokenTest; @@ -36,7 +37,6 @@ import google.registry.model.tmch.ClaimsListDaoTest; import google.registry.model.tmch.TmchCrlTest; import google.registry.persistence.transaction.JpaEntityCoverageExtension; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension; -import google.registry.schema.cursor.CursorDaoTest; import google.registry.schema.integration.SqlIntegrationTestSuite.AfterSuiteTest; import google.registry.schema.integration.SqlIntegrationTestSuite.BeforeSuiteTest; import google.registry.schema.registrar.RegistrarDaoTest; @@ -85,7 +85,7 @@ import org.junit.runner.RunWith; ClaimsListDaoTest.class, ContactHistoryTest.class, ContactResourceTest.class, - CursorDaoTest.class, + CursorTest.class, DomainBaseSqlTest.class, DomainHistoryTest.class, HostHistoryTest.class, diff --git a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html index a19e8d62d..62b70ce3b 100644 --- a/db/src/main/resources/sql/er_diagram/brief_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/brief_er_diagram.html @@ -261,7 +261,7 @@ td.section { generated on - 2021-03-16 19:06:44.592583 + 2021-03-18 19:08:16.381352 last flyway file @@ -284,7 +284,7 @@ td.section { generated on - 2021-03-16 19:06:44.592583 + 2021-03-18 19:08:16.381352 diff --git a/db/src/main/resources/sql/er_diagram/full_er_diagram.html b/db/src/main/resources/sql/er_diagram/full_er_diagram.html index f0085a475..434149697 100644 --- a/db/src/main/resources/sql/er_diagram/full_er_diagram.html +++ b/db/src/main/resources/sql/er_diagram/full_er_diagram.html @@ -261,7 +261,7 @@ td.section { generated on - 2021-03-16 19:06:42.152218 + 2021-03-18 19:08:14.574191 last flyway file @@ -284,7 +284,7 @@ td.section { generated on - 2021-03-16 19:06:42.152218 + 2021-03-18 19:08:14.574191