Refactor Cursor to exist in one class (#988)

This allows us to get rid of the DAO as well as the sanity-checking
methods since we can be reasonably sure that the fields will be the
same. Future PRs will add conversions from ofy() to tm() calls that will
make sure that we get the same proper data in both Datastore and SQL
This commit is contained in:
gbrodman 2021-03-18 21:58:07 -04:00 committed by GitHub
parent 1a0ee97633
commit a0c710ab18
25 changed files with 217 additions and 909 deletions

View file

@ -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));
}
});
}

View file

@ -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(

View file

@ -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<EntityGroupRoot> parent = getCrossTldKey();
@Transient @Parent Key<EntityGroupRoot> 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<? extends ImmutableObject> scopeKey =
scope.equals(GLOBAL)
? getCrossTldKey()
: Key.create(getCrossTldKey(), Registry.class, scope);
id = generateId(type, scopeKey);
}
public static VKey<Cursor> createVKey(Key<Cursor> key) {
String id = key.getName();
return VKey.create(Cursor.class, new CursorId(getTypeFromId(id), getScopeFromId(id)), key);
}
public VKey<Cursor> createVKey() {
return createVKey(type, scope);
}
public static VKey<Cursor> createGlobalVKey(CursorType type) {
return createVKey(type, GLOBAL);
}
public static VKey<Cursor> createVKey(CursorType type, String scope) {
Key<Cursor> 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<String> 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<? extends ImmutableObject> 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<String> 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<String> 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<Cursor> createKey(CursorType cursorType, ImmutableObject scope) {
Key<? extends ImmutableObject> 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;
}
}
}

View file

@ -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());

View file

@ -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;
});
}

View file

@ -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(

View file

@ -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<PendingDeposit, DepositFrag
() -> {
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<PendingDeposit, DepositFrag
"Partial ordering of RDE deposits broken: %s %s",
position,
key);
CursorDao.saveCursor(
Cursor.create(key.cursor(), newPosition, registry), registry.getTldStr());
tm().put(Cursor.create(key.cursor(), newPosition, registry));
logger.atInfo().log(
"Rolled forward %s on %s cursor to %s", key.cursor(), tld, newPosition);
RdeRevision.saveRevision(tld, watermark, mode, revision);

View file

@ -24,7 +24,6 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.rde.RdeMode.FULL;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.request.Action.Method.POST;
import static google.registry.schema.cursor.CursorDao.loadAndCompare;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static java.util.Arrays.asList;
@ -53,7 +52,6 @@ import google.registry.request.Parameter;
import google.registry.request.RequestParameters;
import google.registry.request.Response;
import google.registry.request.auth.Auth;
import google.registry.schema.cursor.CursorDao;
import google.registry.util.Clock;
import google.registry.util.Retrier;
import google.registry.util.TaskQueueUtils;
@ -135,7 +133,6 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
logger.atInfo().log("Verifying readiness to upload the RDE deposit.");
Cursor cursor =
ofy().load().key(Cursor.createKey(CursorType.RDE_STAGING, Registry.get(tld))).now();
loadAndCompare(cursor, tld);
DateTime stagingCursorTime = getCursorTimeOrStartOfTime(cursor);
if (isBeforeOrAt(stagingCursorTime, watermark)) {
throw new NoContentException(
@ -146,7 +143,6 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
}
Cursor sftpCursor =
ofy().load().key(Cursor.createKey(RDE_UPLOAD_SFTP, Registry.get(tld))).now();
loadAndCompare(sftpCursor, tld);
DateTime sftpCursorTime = getCursorTimeOrStartOfTime(sftpCursor);
Duration timeSinceLastSftp = new Duration(sftpCursorTime, clock.nowUtc());
if (timeSinceLastSftp.isShorterThan(sftpCooldown)) {
@ -176,11 +172,10 @@ public final class RdeUploadAction implements Runnable, EscrowTask {
logger.atInfo().log(
"Updating RDE cursor '%s' for TLD '%s' following successful upload.", RDE_UPLOAD_SFTP, tld);
tm().transact(
() -> {
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));
}

View file

@ -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();
}

View file

@ -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<DatastoreEntity> 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;
}
}
}

View file

@ -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<Cursor> 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<Cursor> loadAll() {
return jpaTm()
.transact(
() ->
jpaTm()
.getEntityManager()
.createQuery("SELECT cursor FROM Cursor cursor", Cursor.class)
.getResultList());
}
public static List<Cursor> 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<google.registry.model.common.Cursor, String> 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<Cursor> 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
*
* <p>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
*
* <p>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<google.registry.model.common.Cursor, String> cursors, CursorType type) {
try {
// Load all the cursors of that type from Cloud SQL
List<Cursor> cloudSqlCursors = loadByType(type);
// Create a map of each TLD to its cursor if one exists.
ImmutableMap<String, Cursor> 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()));
}
}
}

View file

@ -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> cursor) {
if (cursor.isPresent()) {
loadAndCompare(cursor.get(), tld);
}
return String.format(
OUTPUT_FMT,
tld,

View file

@ -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<String> 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<Cursor, String> cursorsToUpdate;
ImmutableList<Cursor> cursorsToUpdate;
@Override
protected void init() {
ImmutableMap.Builder<Cursor, String> cursorsToUpdateBuilder = new ImmutableMap.Builder<>();
ImmutableList.Builder<Cursor> 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());
}
}

View file

@ -41,6 +41,7 @@
<class>google.registry.model.billing.BillingEvent$Cancellation</class>
<class>google.registry.model.billing.BillingEvent$OneTime</class>
<class>google.registry.model.billing.BillingEvent$Recurring</class>
<class>google.registry.model.common.Cursor</class>
<class>google.registry.model.contact.ContactHistory</class>
<class>google.registry.model.contact.ContactResource</class>
<class>google.registry.model.domain.DomainBase</class>
@ -69,7 +70,6 @@
<class>google.registry.model.tmch.ClaimsListShard</class>
<class>google.registry.model.tmch.TmchCrl</class>
<class>google.registry.persistence.transaction.TransactionEntity</class>
<class>google.registry.schema.cursor.Cursor</class>
<class>google.registry.schema.domain.RegistryLock</class>
<class>google.registry.schema.replay.SqlReplayCheckpoint</class>
<class>google.registry.schema.server.Lock</class>

View file

@ -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;
}

View file

@ -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<CommitLogManifest> 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<DomainBase> 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);
}

View file

@ -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 {

View file

@ -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 =

View file

@ -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) {

View file

@ -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<RdeStagingAction> {
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> T unmarshal(Class<T> clazz, byte[] xml) throws XmlException {

View file

@ -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);
});

View file

@ -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<Cursor> cursors = ImmutableSet.<Cursor>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<Cursor> returnedCursors = CursorDao.loadAll();
assertThat(returnedCursors.size()).isEqualTo(4);
}
@Test
void loadAll_worksSuccessfullyEmptyTable() {
List<Cursor> 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<Cursor> returnedCursors = CursorDao.loadByType(CursorType.RDE_REPORT);
assertThat(returnedCursors.size()).isEqualTo(2);
}
@Test
void loadByType_worksSuccessfullyNoneOfType() {
List<Cursor> 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<google.registry.model.common.Cursor, String> cursors =
ImmutableMap.<google.registry.model.common.Cursor, String>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<google.registry.model.common.Cursor, String> cursors =
ImmutableMap.<google.registry.model.common.Cursor, String>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<google.registry.model.common.Cursor, String> cursors =
ImmutableMap.<google.registry.model.common.Cursor, String>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);
}
}

View file

@ -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,

View file

@ -261,7 +261,7 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2021-03-16 19:06:44.592583</td>
<td class="property_value">2021-03-18 19:08:16.381352</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
@ -284,7 +284,7 @@ td.section {
generated on
</text>
<text text-anchor="start" x="4027.94" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
2021-03-16 19:06:44.592583
2021-03-18 19:08:16.381352
</text>
<polygon fill="none" stroke="#888888" points="3940.44,-4 3940.44,-44 4205.44,-44 4205.44,-4 3940.44,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">

View file

@ -261,7 +261,7 @@ td.section {
</tr>
<tr>
<td class="property_name">generated on</td>
<td class="property_value">2021-03-16 19:06:42.152218</td>
<td class="property_value">2021-03-18 19:08:14.574191</td>
</tr>
<tr>
<td class="property_name">last flyway file</td>
@ -284,7 +284,7 @@ td.section {
generated on
</text>
<text text-anchor="start" x="4626.68" y="-10.8" font-family="Helvetica,sans-Serif" font-size="14.00">
2021-03-16 19:06:42.152218
2021-03-18 19:08:14.574191
</text>
<polygon fill="none" stroke="#888888" points="4539.18,-4 4539.18,-44 4804.18,-44 4804.18,-4 4539.18,-4" /> <!-- allocationtoken_a08ccbef -->
<g id="node1" class="node">