Convert ofy -> tm for two more classes (#1049)

* Convert ofy -> tm for two more classes

Convert ofy -> tm for MutatingCommand and DedupeOneTimeBillingEventIdsCommand.

Note that DedupeOneTimeBillingEventIdsCommand will not be needed after
migration, so this conversion is just to remove the ofy uses from the
codebase.  We don't update the test (other than to keep it working) and it
wouldn't currently work in SQL.

* Fixed a test broken by this PR
This commit is contained in:
Michael Muller 2021-04-01 07:27:43 -04:00 committed by GitHub
parent aafb1f6bbb
commit 891745391f
6 changed files with 94 additions and 54 deletions

View file

@ -64,7 +64,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
} }
/** Creates a {@link VKey} which only contains the ofy primary key. */ /** Creates a {@link VKey} which only contains the ofy primary key. */
public static <T> VKey<T> createOfy(Class<T> kind, Key<T> ofyKey) { public static <T> VKey<T> createOfy(Class<? extends T> kind, Key<T> ofyKey) {
checkArgumentNotNull(kind, "kind must not be null"); checkArgumentNotNull(kind, "kind must not be null");
checkArgumentNotNull(ofyKey, "ofyKey must not be null"); checkArgumentNotNull(ofyKey, "ofyKey must not be null");
return new VKey<T>(kind, ofyKey, null); return new VKey<T>(kind, ofyKey, null);

View file

@ -15,13 +15,13 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameters; import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.OneTime; import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.persistence.VKey;
/** /**
* Command to dedupe {@link BillingEvent.OneTime} entities having duplicate IDs. * Command to dedupe {@link BillingEvent.OneTime} entities having duplicate IDs.
@ -49,9 +49,9 @@ public class DedupeOneTimeBillingEventIdsCommand extends ReadEntityFromKeyPathCo
@Override @Override
void process(OneTime entity) { void process(OneTime entity) {
Key<BillingEvent> key = Key.create(entity); VKey<OneTime> key = entity.createVKey();
Key<DomainBase> domainKey = getGrandParentAsDomain(key); VKey<DomainBase> domainKey = getGrandParentAsDomain(key);
DomainBase domain = ofy().load().key(domainKey).now(); DomainBase domain = tm().transact(() -> tm().loadByKey(domainKey));
// The BillingEvent.OneTime entity to be resaved should be the billing event created a few // The BillingEvent.OneTime entity to be resaved should be the billing event created a few
// years ago, so they should not be referenced from TransferData and GracePeriod in the domain. // years ago, so they should not be referenced from TransferData and GracePeriod in the domain.
@ -61,7 +61,7 @@ public class DedupeOneTimeBillingEventIdsCommand extends ReadEntityFromKeyPathCo
.forEach( .forEach(
gracePeriod -> gracePeriod ->
checkState( checkState(
!gracePeriod.getOneTimeBillingEvent().getOfyKey().equals(key), !gracePeriod.getOneTimeBillingEvent().equals(key),
"Entity %s is referenced by a grace period in domain %s", "Entity %s is referenced by a grace period in domain %s",
key, key,
domainKey)); domainKey));
@ -71,7 +71,7 @@ public class DedupeOneTimeBillingEventIdsCommand extends ReadEntityFromKeyPathCo
stageEntityKeyChange(entity, uniqIdBillingEvent); stageEntityKeyChange(entity, uniqIdBillingEvent);
} }
private static void assertNotInDomainTransferData(DomainBase domainBase, Key<?> key) { private static void assertNotInDomainTransferData(DomainBase domainBase, VKey<?> key) {
if (!domainBase.getTransferData().isEmpty()) { if (!domainBase.getTransferData().isEmpty()) {
domainBase domainBase
.getTransferData() .getTransferData()
@ -79,10 +79,10 @@ public class DedupeOneTimeBillingEventIdsCommand extends ReadEntityFromKeyPathCo
.forEach( .forEach(
entityKey -> entityKey ->
checkState( checkState(
!entityKey.getOfyKey().equals(key), !entityKey.equals(key),
"Entity %s is referenced by the transfer data in domain %s", "Entity %s is referenced by the transfer data in domain %s",
key, key,
domainBase.createVKey().getOfyKey())); domainBase.createVKey()));
} }
} }
} }

View file

@ -21,7 +21,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DatastoreServiceUtils.getNameOrId; import static google.registry.util.DatastoreServiceUtils.getNameOrId;
import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff; import static google.registry.util.DiffUtils.prettyPrintEntityDeepDiff;
@ -33,6 +32,8 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.persistence.VKey;
import google.registry.schema.replay.SqlEntity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -75,7 +76,7 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
final ImmutableObject newEntity; final ImmutableObject newEntity;
/** The key that points to the entity being changed. */ /** The key that points to the entity being changed. */
final Key<ImmutableObject> key; final VKey<?> key;
public EntityChange(ImmutableObject oldEntity, ImmutableObject newEntity) { public EntityChange(ImmutableObject oldEntity, ImmutableObject newEntity) {
type = ChangeType.get(oldEntity != null, newEntity != null); type = ChangeType.get(oldEntity != null, newEntity != null);
@ -84,16 +85,24 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
"Both entity versions in an update must have the same Key."); "Both entity versions in an update must have the same Key.");
this.oldEntity = oldEntity; this.oldEntity = oldEntity;
this.newEntity = newEntity; this.newEntity = newEntity;
key = Key.create(MoreObjects.firstNonNull(oldEntity, newEntity)); ImmutableObject entity = MoreObjects.firstNonNull(oldEntity, newEntity);
// This is one of the few cases where it is acceptable to create an asymmetric VKey (using
// createOfy()). We can use this code on DatastoreOnlyEntity's where we can't construct an
// SQL key.
key =
entity instanceof SqlEntity
? VKey.from(Key.create(entity))
: VKey.createOfy(entity.getClass(), Key.create(entity));
} }
/** Returns a human-readable ID string for the entity being changed. */ /** Returns a human-readable ID string for the entity being changed. */
public String getEntityId() { public String getEntityId() {
return String.format( return String.format(
"%s@%s", "%s@%s",
key.getKind(), key.getOfyKey().getKind(),
// NB: try name before id, since name defaults to null, whereas id defaults to 0. // NB: try name before id, since name defaults to null, whereas id defaults to 0.
getNameOrId(key.getRaw())); getNameOrId(key.getOfyKey().getRaw()));
} }
/** Returns a string representation of this entity change. */ /** Returns a string representation of this entity change. */
@ -114,14 +123,12 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
} }
/** Map from entity keys to EntityChange objects representing changes to those entities. */ /** Map from entity keys to EntityChange objects representing changes to those entities. */
private final LinkedHashMap<Key<ImmutableObject>, EntityChange> changedEntitiesMap = private final LinkedHashMap<VKey<?>, EntityChange> changedEntitiesMap = new LinkedHashMap<>();
new LinkedHashMap<>();
/** A set of resource keys for which new transactions should be created after. */ /** A set of resource keys for which new transactions should be created after. */
private final Set<Key<ImmutableObject>> transactionBoundaries = new HashSet<>(); private final Set<VKey<?>> transactionBoundaries = new HashSet<>();
@Nullable @Nullable private VKey<?> lastAddedKey;
private Key<ImmutableObject> lastAddedKey;
/** /**
* Initializes the command. * Initializes the command.
@ -154,20 +161,24 @@ public abstract class MutatingCommand extends ConfirmingCommand implements Comma
private void executeChange(EntityChange change) { private void executeChange(EntityChange change) {
// Load the key of the entity to mutate and double-check that it hasn't been // Load the key of the entity to mutate and double-check that it hasn't been
// modified from the version that existed when the change was prepared. // modified from the version that existed when the change was prepared.
ImmutableObject existingEntity = ofy().load().key(change.key).now(); Optional<?> existingEntity = tm().loadByKeyIfPresent(change.key);
checkState( checkState(
Objects.equals(change.oldEntity, existingEntity), Objects.equals(change.oldEntity, existingEntity.orElse(null)),
"Entity changed since init() was called.\n%s", "Entity changed since init() was called.\n%s",
prettyPrintEntityDeepDiff( prettyPrintEntityDeepDiff(
(change.oldEntity == null) ? ImmutableMap.of() : change.oldEntity.toDiffableFieldMap(), (change.oldEntity == null) ? ImmutableMap.of() : change.oldEntity.toDiffableFieldMap(),
(existingEntity == null) ? ImmutableMap.of() : existingEntity.toDiffableFieldMap())); existingEntity.isPresent()
? ((ImmutableObject) existingEntity.get()).toDiffableFieldMap()
: ImmutableMap.of()));
switch (change.type) { switch (change.type) {
case CREATE: // Fall through. case CREATE:
tm().insert(change.newEntity);
return;
case UPDATE: case UPDATE:
ofy().save().entity(change.newEntity).now(); tm().update(change.newEntity);
return; return;
case DELETE: case DELETE:
ofy().delete().key(change.key).now(); tm().delete(change.key);
return; return;
} }
throw new UnsupportedOperationException("Unknown entity change type: " + change.type); throw new UnsupportedOperationException("Unknown entity change type: " + change.type);

View file

@ -26,6 +26,7 @@ import com.google.common.io.Files;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainBase;
import google.registry.persistence.VKey;
import google.registry.util.NonFinalForTesting; import google.registry.util.NonFinalForTesting;
import google.registry.util.TypeUtils.TypeInstantiator; import google.registry.util.TypeUtils.TypeInstantiator;
import java.io.File; import java.io.File;
@ -148,4 +149,18 @@ abstract class ReadEntityFromKeyPathCommand<T> extends MutatingCommand {
} }
return (Key<DomainBase>) grandParent; return (Key<DomainBase>) grandParent;
} }
static VKey<DomainBase> getGrandParentAsDomain(VKey<?> key) {
Key<DomainBase> grandParent;
try {
grandParent = key.getOfyKey().getParent().getParent();
} catch (Throwable e) {
throw new IllegalArgumentException("Error retrieving grand parent key", e);
}
if (!isKind(grandParent, DomainBase.class)) {
throw new IllegalArgumentException(
String.format("Expected a Key<DomainBase> but got %s", grandParent));
}
return VKey.create(DomainBase.class, grandParent.getName(), grandParent);
}
} }

View file

@ -15,8 +15,8 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.truth.Truth.assertThat; 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.createTld;
import static google.registry.testing.DatabaseHelper.loadAllOf;
import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.DatabaseHelper.persistResource;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -38,7 +38,13 @@ import org.joda.money.Money;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
/** Unit tests for {@link DedupeOneTimeBillingEventIdsCommand}. */ /**
* Unit tests for {@link DedupeOneTimeBillingEventIdsCommand}.
*
* <p>Note that these are _not_ dual database tests even though the action has been converted. The
* dedupe was strictly a one-time event that needed to be done prior to moving to SQL. It should no
* longer be necessary and we may want to simply remove the command.
*/
class DedupeOneTimeBillingEventIdsCommandTest class DedupeOneTimeBillingEventIdsCommandTest
extends CommandTestCase<DedupeOneTimeBillingEventIdsCommand> { extends CommandTestCase<DedupeOneTimeBillingEventIdsCommand> {
@ -64,8 +70,7 @@ class DedupeOneTimeBillingEventIdsCommandTest
writeToNamedTmpFile("keypath.txt", getKeyPathLiteral(billingEventToResave))); writeToNamedTmpFile("keypath.txt", getKeyPathLiteral(billingEventToResave)));
int count = 0; int count = 0;
for (BillingEvent.OneTime billingEvent : for (BillingEvent.OneTime billingEvent : loadAllOf(BillingEvent.OneTime.class)) {
ofy().load().type(BillingEvent.OneTime.class).ancestor(historyEntry)) {
count++; count++;
assertThat(billingEvent.getId()).isNotEqualTo(billingEventToResave.getId()); assertThat(billingEvent.getId()).isNotEqualTo(billingEventToResave.getId());
assertThat(billingEvent.asBuilder().setId(billingEventToResave.getId()).build()) assertThat(billingEvent.asBuilder().setId(billingEventToResave.getId()).build())

View file

@ -14,18 +14,23 @@
package google.registry.tools; package google.registry.tools;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteResource; import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.loadByEntity;
import static google.registry.testing.DatabaseHelper.persistActiveHost; import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistNewRegistrar; import static google.registry.testing.DatabaseHelper.persistNewRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource; import static google.registry.testing.DatabaseHelper.persistResource;
import static org.joda.time.DateTimeZone.UTC; import static org.joda.time.DateTimeZone.UTC;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import com.googlecode.objectify.Key;
import google.registry.model.host.HostResource; import google.registry.model.host.HostResource;
import google.registry.model.registrar.Registrar; import google.registry.model.registrar.Registrar;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension; import google.registry.testing.AppEngineExtension;
import java.util.Arrays; import java.util.Arrays;
import org.joda.time.DateTime; import org.joda.time.DateTime;
@ -103,15 +108,19 @@ public class MutatingCommandTest {
+ "blockPremiumNames: false -> true\n"); + "blockPremiumNames: false -> true\n");
String results = command.execute(); String results = command.execute();
assertThat(results).isEqualTo("Updated 4 entities.\n"); assertThat(results).isEqualTo("Updated 4 entities.\n");
assertThat(ofy().load().entity(host1).now()).isEqualTo(newHost1); assertThat(loadByEntity(host1)).isEqualTo(newHost1);
assertThat(ofy().load().entity(host2).now()).isEqualTo(newHost2); assertThat(loadByEntity(host2)).isEqualTo(newHost2);
assertThat(ofy().load().entity(registrar1).now()).isEqualTo(newRegistrar1); assertThat(loadByEntity(registrar1)).isEqualTo(newRegistrar1);
assertThat(ofy().load().entity(registrar2).now()).isEqualTo(newRegistrar2); assertThat(loadByEntity(registrar2)).isEqualTo(newRegistrar2);
} }
@Test @Test
void testSuccess_create() throws Exception { void testSuccess_create() throws Exception {
ofy().deleteWithoutBackup().entities(Arrays.asList(host1, host2, registrar1, registrar2)).now(); ImmutableList<VKey<?>> keys =
Arrays.asList(host1, host2, registrar1, registrar2).stream()
.map(entity -> VKey.from(Key.create(entity)))
.collect(toImmutableList());
tm().transact(() -> tm().deleteWithoutBackup(keys));
MutatingCommand command = new MutatingCommand() { MutatingCommand command = new MutatingCommand() {
@Override @Override
protected void init() { protected void init() {
@ -137,10 +146,10 @@ public class MutatingCommandTest {
+ newRegistrar2 + "\n"); + newRegistrar2 + "\n");
String results = command.execute(); String results = command.execute();
assertThat(results).isEqualTo("Updated 4 entities.\n"); assertThat(results).isEqualTo("Updated 4 entities.\n");
assertThat(ofy().load().entity(newHost1).now()).isEqualTo(newHost1); assertThat(loadByEntity(newHost1)).isEqualTo(newHost1);
assertThat(ofy().load().entity(newHost2).now()).isEqualTo(newHost2); assertThat(loadByEntity(newHost2)).isEqualTo(newHost2);
assertThat(ofy().load().entity(newRegistrar1).now()).isEqualTo(newRegistrar1); assertThat(loadByEntity(newRegistrar1)).isEqualTo(newRegistrar1);
assertThat(ofy().load().entity(newRegistrar2).now()).isEqualTo(newRegistrar2); assertThat(loadByEntity(newRegistrar2)).isEqualTo(newRegistrar2);
} }
@Test @Test
@ -170,10 +179,10 @@ public class MutatingCommandTest {
+ registrar2 + "\n"); + registrar2 + "\n");
String results = command.execute(); String results = command.execute();
assertThat(results).isEqualTo("Updated 4 entities.\n"); assertThat(results).isEqualTo("Updated 4 entities.\n");
assertThat(ofy().load().entity(host1).now()).isNull(); assertThat(loadByEntity(host1)).isNull();
assertThat(ofy().load().entity(host2).now()).isNull(); assertThat(loadByEntity(host2)).isNull();
assertThat(ofy().load().entity(registrar1).now()).isNull(); assertThat(loadByEntity(registrar1)).isNull();
assertThat(ofy().load().entity(registrar2).now()).isNull(); assertThat(loadByEntity(registrar2)).isNull();
} }
@Test @Test
@ -196,8 +205,8 @@ public class MutatingCommandTest {
+ "[no changes]\n"); + "[no changes]\n");
String results = command.execute(); String results = command.execute();
assertThat(results).isEqualTo("Updated 2 entities.\n"); assertThat(results).isEqualTo("Updated 2 entities.\n");
assertThat(ofy().load().entity(host1).now()).isEqualTo(host1); assertThat(loadByEntity(host1)).isEqualTo(host1);
assertThat(ofy().load().entity(registrar1).now()).isEqualTo(registrar1); assertThat(loadByEntity(registrar1)).isEqualTo(registrar1);
} }
@Test @Test
@ -230,10 +239,10 @@ public class MutatingCommandTest {
+ "blockPremiumNames: false -> true\n"); + "blockPremiumNames: false -> true\n");
String results = command.execute(); String results = command.execute();
assertThat(results).isEqualTo("Updated 4 entities.\n"); assertThat(results).isEqualTo("Updated 4 entities.\n");
assertThat(ofy().load().entity(host1).now()).isNull(); assertThat(loadByEntity(host1)).isNull();
assertThat(ofy().load().entity(host2).now()).isEqualTo(newHost2); assertThat(loadByEntity(host2)).isEqualTo(newHost2);
assertThat(ofy().load().entity(registrar1).now()).isNull(); assertThat(loadByEntity(registrar1)).isNull();
assertThat(ofy().load().entity(registrar2).now()).isEqualTo(newRegistrar2); assertThat(loadByEntity(registrar2)).isEqualTo(newRegistrar2);
} }
@Test @Test
@ -271,11 +280,11 @@ public class MutatingCommandTest {
IllegalStateException thrown = assertThrows(IllegalStateException.class, command::execute); IllegalStateException thrown = assertThrows(IllegalStateException.class, command::execute);
assertThat(thrown).hasMessageThat().contains("Entity changed since init() was called."); assertThat(thrown).hasMessageThat().contains("Entity changed since init() was called.");
assertThat(ofy().load().entity(host1).now()).isNull(); assertThat(loadByEntity(host1)).isNull();
assertThat(ofy().load().entity(host2).now()).isEqualTo(newHost2); assertThat(loadByEntity(host2)).isEqualTo(newHost2);
// These two shouldn't've changed. // These two shouldn't've changed.
assertThat(ofy().load().entity(registrar1).now()).isEqualTo(registrar1); assertThat(loadByEntity(registrar1)).isEqualTo(registrar1);
assertThat(ofy().load().entity(registrar2).now()).isEqualTo(registrar2); assertThat(loadByEntity(registrar2)).isEqualTo(registrar2);
} }
@Test @Test