Delete EntityGroupRoot (#1776)

This commit is contained in:
Lai Jiang 2022-09-08 12:54:10 -04:00 committed by GitHub
parent 2133aea066
commit bc523b2160
16 changed files with 5 additions and 562 deletions

View file

@ -16,7 +16,6 @@ package google.registry.model;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import google.registry.model.annotations.DeleteAfterMigration; import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.common.GaeUserIdConverter; import google.registry.model.common.GaeUserIdConverter;
import google.registry.model.contact.Contact; import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory; import google.registry.model.contact.ContactHistory;
@ -41,7 +40,6 @@ public final class EntityClasses {
ContactHistory.class, ContactHistory.class,
Domain.class, Domain.class,
DomainHistory.class, DomainHistory.class,
EntityGroupRoot.class,
EppResourceIndex.class, EppResourceIndex.class,
EppResourceIndexBucket.class, EppResourceIndexBucket.class,
ForeignKeyIndex.ForeignKeyContactIndex.class, ForeignKeyIndex.ForeignKeyContactIndex.class,

View file

@ -1,35 +0,0 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.annotations;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.EntityGroupRoot;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation for an Objectify {@link Entity} to indicate that it is in the cross-TLD entity group.
*
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
* EntityGroupRoot#getCrossTldKey}.
*/
@DeleteAfterMigration
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface InCrossTld {}

View file

@ -14,26 +14,17 @@
package google.registry.model.common; package google.registry.model.common;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration; import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.InCrossTld;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
/** A singleton entity in Datastore. */ /** A singleton entity in the database. */
@DeleteAfterMigration @DeleteAfterMigration
@MappedSuperclass @MappedSuperclass
@InCrossTld
public abstract class CrossTldSingleton extends ImmutableObject { public abstract class CrossTldSingleton extends ImmutableObject {
public static final long SINGLETON_ID = 1; // There is always exactly one of these. public static final long SINGLETON_ID = 1; // There is always exactly one of these.
@Id @javax.persistence.Id long id = SINGLETON_ID; @Id @javax.persistence.Id long id = SINGLETON_ID;
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
} }

View file

@ -1,57 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.common;
import com.google.apphosting.api.ApiProxy;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.BackupGroupRoot;
import google.registry.model.annotations.DeleteAfterMigration;
import javax.annotation.Nullable;
/**
* The root key for the entity group which is known as the cross-tld entity group for historical
* reasons.
*
* <p>This exists as a storage place for common configuration options and global settings that
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
* reason this common entity group exists is because it enables strongly consistent queries and
* updates across this seldomly updated data. This shared entity group also helps cut down on a
* potential ballooning in the number of entity groups enlisted in transactions.
*
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in a
* single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was the
* entity group for the single namespace where global data applicable for all TLDs lived.
*/
@Entity
@DeleteAfterMigration
public class EntityGroupRoot extends BackupGroupRoot {
@SuppressWarnings("unused")
@Id
private String id;
/** The root key for cross-tld resources such as registrars. */
public static @Nullable Key<EntityGroupRoot> getCrossTldKey() {
// If we cannot get a current environment, calling Key.create() will fail. Instead we return a
// null in cases where this key is not actually needed (for example when loading an entity from
// SQL) to initialize an object, to avoid having to register a DatastoreEntityExtension in
// tests.
return ApiProxy.getCurrentEnvironment() == null
? null
: Key.create(EntityGroupRoot.class, "cross-tld");
}
}

View file

@ -41,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering; import com.google.common.collect.Ordering;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Ignore; import com.googlecode.objectify.annotation.Ignore;
import com.googlecode.objectify.annotation.IgnoreSave; import com.googlecode.objectify.annotation.IgnoreSave;
import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.annotation.Index;
@ -51,7 +50,6 @@ import google.registry.flows.ResourceFlowUtils;
import google.registry.model.EppResource; import google.registry.model.EppResource;
import google.registry.model.EppResource.ResourceWithTransferData; import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.billing.BillingEvent; import google.registry.model.billing.BillingEvent;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.contact.Contact; import google.registry.model.contact.Contact;
import google.registry.model.domain.launch.LaunchNotice; import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.rgp.GracePeriodStatus;
@ -60,7 +58,6 @@ import google.registry.model.domain.token.AllocationToken;
import google.registry.model.eppcommon.StatusValue; import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.Host; import google.registry.model.host.Host;
import google.registry.model.poll.PollMessage; import google.registry.model.poll.PollMessage;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.tld.Registry; import google.registry.model.tld.Registry;
import google.registry.model.transfer.DomainTransferData; import google.registry.model.transfer.DomainTransferData;
import google.registry.model.transfer.TransferStatus; import google.registry.model.transfer.TransferStatus;
@ -286,15 +283,6 @@ public class DomainBase extends EppResource
return Optional.ofNullable(dnsRefreshRequestTime); return Optional.ofNullable(dnsRefreshRequestTime);
} }
public static <T> VKey<T> restoreOfyFrom(Key<Domain> domainKey, VKey<T> key, Long historyId) {
if (historyId == null) {
// This is a legacy key (or a null key, in which case this works too)
return VKey.restoreOfyFrom(key, EntityGroupRoot.class, "per-tld");
} else {
return VKey.restoreOfyFrom(key, domainKey, HistoryEntry.class, historyId);
}
}
public ImmutableSet<String> getSubordinateHosts() { public ImmutableSet<String> getSubordinateHosts() {
return nullToEmptyImmutableCopy(subordinateHosts); return nullToEmptyImmutableCopy(subordinateHosts);
} }

View file

@ -240,8 +240,6 @@ public class Lock extends ImmutableObject implements Serializable {
Lock newLock = Lock newLock =
create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength); create(resourceName, scope, requestStatusChecker.getLogId(), now, leaseLength);
// Locks are not parented under an EntityGroupRoot (so as to avoid write
// contention) and don't need to be backed up.
jpaTm().put(newLock); jpaTm().put(newLock);
return AcquireResult.create(now, lock, newLock, lockState); return AcquireResult.create(now, lock, newLock, lockState);

View file

@ -22,7 +22,6 @@ import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Maps.filterValues; import static com.google.common.collect.Maps.filterValues;
import static google.registry.model.CacheUtils.memoizeWithShortExpiration; import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@ -66,7 +65,6 @@ public final class Registries {
auditedOfy() auditedOfy()
.load() .load()
.type(Registry.class) .type(Registry.class)
.ancestor(getCrossTldKey())
.keys() .keys()
.list() .list()
.stream() .stream()

View file

@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.annotations.InCrossTld;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
@ -176,18 +175,14 @@ public interface TransactionManager {
/** /**
* Returns a list of all entities of the given type that exist in the database. * Returns a list of all entities of the given type that exist in the database.
* *
* <p>The resulting list is empty if there are no entities of this type. In Datastore mode, if the * <p>The resulting list is empty if there are no entities of this type.
* class is a member of the cross-TLD entity group (i.e. if it has the {@link InCrossTld}
* annotation, then the correct ancestor query will automatically be applied.
*/ */
<T> ImmutableList<T> loadAllOf(Class<T> clazz); <T> ImmutableList<T> loadAllOf(Class<T> clazz);
/** /**
* Returns a stream of all entities of the given type that exist in the database. * Returns a stream of all entities of the given type that exist in the database.
* *
* <p>The resulting stream is empty if there are no entities of this type. In Datastore mode, if * <p>The resulting stream is empty if there are no entities of this type.
* the class is a member of the cross-TLD entity group (i.e. if it has the {@link InCrossTld}
* annotation, then the correct ancestor query will automatically be applied.
*/ */
<T> Stream<T> loadAllOfStream(Class<T> clazz); <T> Stream<T> loadAllOfStream(Class<T> clazz);

View file

@ -92,7 +92,6 @@ public final class RegistryTool {
.put("pending_escrow", PendingEscrowCommand.class) .put("pending_escrow", PendingEscrowCommand.class)
.put("registrar_poc", RegistrarPocCommand.class) .put("registrar_poc", RegistrarPocCommand.class)
.put("renew_domain", RenewDomainCommand.class) .put("renew_domain", RenewDomainCommand.class)
.put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class)
.put("save_sql_credential", SaveSqlCredentialCommand.class) .put("save_sql_credential", SaveSqlCredentialCommand.class)
.put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class) .put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class)
.put("set_database_migration_state", SetDatabaseMigrationStateCommand.class) .put("set_database_migration_state", SetDatabaseMigrationStateCommand.class)

View file

@ -1,57 +0,0 @@
// Copyright 2017 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.tools;
import static com.google.common.collect.Lists.partition;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.beust.jcommander.Parameters;
import com.googlecode.objectify.Key;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.tld.Registry;
/**
* Command to re-save all environment entities to ensure that they have valid commit logs.
*
* <p>The entities that are re-saved are those of type {@link Registry}, {@link Registrar}, and
* {@link RegistrarPoc}.
*/
@Parameters(commandDescription = "Re-save all environment entities.")
@DeleteAfterMigration
final class ResaveEnvironmentEntitiesCommand implements CommandWithRemoteApi {
private static final int BATCH_SIZE = 10;
@Override
public void run() {
batchSave(Registry.class);
batchSave(Registrar.class);
batchSave(RegistrarPoc.class);
}
private static <T> void batchSave(Class<T> clazz) {
System.out.printf("Re-saving %s entities.\n", clazz.getSimpleName());
for (final Iterable<Key<T>> batch :
partition(
auditedOfy().load().type(clazz).ancestor(getCrossTldKey()).keys().list(), BATCH_SIZE)) {
tm().transact(() -> auditedOfy().save().entities(auditedOfy().load().keys(batch).values()));
System.out.printf("Re-saved entities batch: %s.\n", batch);
}
}
}

View file

@ -51,7 +51,6 @@ public class ClassPathManagerTest {
assertThat(ClassPathManager.getClass("GaeUserIdConverter")).isEqualTo(GaeUserIdConverter.class); assertThat(ClassPathManager.getClass("GaeUserIdConverter")).isEqualTo(GaeUserIdConverter.class);
assertThat(ClassPathManager.getClass("EppResourceIndexBucket")) assertThat(ClassPathManager.getClass("EppResourceIndexBucket"))
.isEqualTo(EppResourceIndexBucket.class); .isEqualTo(EppResourceIndexBucket.class);
assertThat(ClassPathManager.getClass("EntityGroupRoot")).isEqualTo(EntityGroupRoot.class);
assertThat(ClassPathManager.getClass("Domain")).isEqualTo(Domain.class); assertThat(ClassPathManager.getClass("Domain")).isEqualTo(Domain.class);
assertThat(ClassPathManager.getClass("HistoryEntry")).isEqualTo(HistoryEntry.class); assertThat(ClassPathManager.getClass("HistoryEntry")).isEqualTo(HistoryEntry.class);
assertThat(ClassPathManager.getClass("ForeignKeyHostIndex")) assertThat(ClassPathManager.getClass("ForeignKeyHostIndex"))
@ -100,7 +99,6 @@ public class ClassPathManagerTest {
.isEqualTo("GaeUserIdConverter"); .isEqualTo("GaeUserIdConverter");
assertThat(ClassPathManager.getClassName(EppResourceIndexBucket.class)) assertThat(ClassPathManager.getClassName(EppResourceIndexBucket.class))
.isEqualTo("EppResourceIndexBucket"); .isEqualTo("EppResourceIndexBucket");
assertThat(ClassPathManager.getClassName(EntityGroupRoot.class)).isEqualTo("EntityGroupRoot");
assertThat(ClassPathManager.getClassName(Domain.class)).isEqualTo("Domain"); assertThat(ClassPathManager.getClassName(Domain.class)).isEqualTo("Domain");
assertThat(ClassPathManager.getClassName(HistoryEntry.class)).isEqualTo("HistoryEntry"); assertThat(ClassPathManager.getClassName(HistoryEntry.class)).isEqualTo("HistoryEntry");
assertThat(ClassPathManager.getClassName(ForeignKeyHostIndex.class)) assertThat(ClassPathManager.getClassName(ForeignKeyHostIndex.class))

View file

@ -1,348 +0,0 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.ofy;
import static com.google.appengine.api.datastore.DatastoreServiceFactory.getDatastoreService;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.ofy.Ofy.getBaseEntityClassFromEntityOrKey;
import static google.registry.testing.DatabaseHelper.newContact;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.taskqueue.TransientFailureException;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.OnLoad;
import com.googlecode.objectify.annotation.OnSave;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject;
import google.registry.model.contact.Contact;
import google.registry.model.contact.ContactHistory;
import google.registry.model.eppcommon.Trid;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.FakeClock;
import google.registry.util.SystemClock;
import java.util.ConcurrentModificationException;
import java.util.function.Supplier;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for our wrapper around Objectify. */
@Disabled
public class OfyTest {
private final FakeClock fakeClock = new FakeClock(DateTime.parse("2000-01-01TZ"));
@RegisterExtension
public final AppEngineExtension appEngine =
AppEngineExtension.builder().withCloudSql().withClock(fakeClock).build();
/** An entity to use in save and delete tests. */
private HistoryEntry someObject;
@BeforeEach
void beforeEach() {
someObject =
new ContactHistory.Builder()
.setRegistrarId("clientid")
.setModificationTime(START_OF_TIME)
.setType(HistoryEntry.Type.CONTACT_CREATE)
.setContact(newContact("parentContact"))
.setTrid(Trid.create("client", "server"))
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.build();
// This can't be initialized earlier because namespaces need the AppEngineExtension to work.
}
@Test
void testSavingKeyTwiceInOneCall() {
assertThrows(
IllegalArgumentException.class,
() -> ofy().transact(() -> auditedOfy().save().entities(someObject, someObject)));
}
/** Simple entity class with lifecycle callbacks. */
@com.googlecode.objectify.annotation.Entity
public static class LifecycleObject extends ImmutableObject {
@Parent Key<?> parent = getCrossTldKey();
@Id long id = 1;
boolean onLoadCalled;
boolean onSaveCalled;
@OnLoad
public void load() {
onLoadCalled = true;
}
@OnSave
public void save() {
onSaveCalled = true;
}
}
@Test
void testLifecycleCallbacks_loadFromEntity() {
auditedOfy().factory().register(LifecycleObject.class);
LifecycleObject object = new LifecycleObject();
Entity entity = auditedOfy().save().toEntity(object);
assertThat(object.onSaveCalled).isTrue();
assertThat(auditedOfy().load().<LifecycleObject>fromEntity(entity).onLoadCalled).isTrue();
}
@Test
void testLifecycleCallbacks_loadFromDatastore() {
auditedOfy().factory().register(LifecycleObject.class);
final LifecycleObject object = new LifecycleObject();
ofy().transact(() -> auditedOfy().save().entity(object).now());
assertThat(object.onSaveCalled).isTrue();
auditedOfy().clearSessionCache();
assertThat(auditedOfy().load().entity(object).now().onLoadCalled).isTrue();
}
/** Avoid regressions of b/21309102 where transaction time did not change on each retry. */
@Test
void testTransact_getsNewTimestampOnEachTry() {
ofy()
.transact(
new Runnable() {
DateTime firstAttemptTime;
@Override
public void run() {
if (firstAttemptTime == null) {
// Sleep a bit to ensure that the next attempt is at a new millisecond.
firstAttemptTime = ofy().getTransactionTime();
sleepUninterruptibly(java.time.Duration.ofMillis(10));
throw new ConcurrentModificationException();
}
assertThat(ofy().getTransactionTime()).isGreaterThan(firstAttemptTime);
}
});
}
@Test
void testTransact_transientFailureException_retries() {
assertThat(
ofy()
.transact(
new Supplier<Integer>() {
int count = 0;
@Override
public Integer get() {
count++;
if (count == 3) {
return count;
}
throw new TransientFailureException("");
}
}))
.isEqualTo(3);
}
@Test
void testTransact_datastoreTimeoutException_noManifest_retries() {
assertThat(
ofy()
.transact(
new Supplier<Integer>() {
int count = 0;
@Override
public Integer get() {
// We don't write anything in this transaction, so there is no commit log
// manifest.
// Therefore it's always safe to retry since nothing got written.
count++;
if (count == 3) {
return count;
}
throw new DatastoreTimeoutException("");
}
}))
.isEqualTo(3);
}
@Test
void testTransact_datastoreTimeoutException_manifestNotWrittenToDatastore_retries() {
assertThat(
ofy()
.transact(
new Supplier<Integer>() {
int count = 0;
@Override
public Integer get() {
// There will be something in the manifest now, but it won't be committed if
// we throw.
auditedOfy().save().entity(someObject.asHistoryEntry());
count++;
if (count == 3) {
return count;
}
throw new DatastoreTimeoutException("");
}
}))
.isEqualTo(3);
}
@Test
void testTransact_datastoreTimeoutException_manifestWrittenToDatastore_returnsSuccess() {
// A work unit that throws if it is ever retried.
Supplier work =
new Supplier<Void>() {
boolean firstCallToVrun = true;
@Override
public Void get() {
if (firstCallToVrun) {
firstCallToVrun = false;
auditedOfy().save().entity(someObject.asHistoryEntry());
return null;
}
fail("Shouldn't have retried.");
return null;
}
};
// A commit logged work that throws on the first attempt to get its result.
CommitLoggedWork<Void> commitLoggedWork =
new CommitLoggedWork<Void>(work, new SystemClock()) {
boolean firstCallToGetResult = true;
@Override
public Void getResult() {
if (firstCallToGetResult) {
firstCallToGetResult = false;
throw new DatastoreTimeoutException("");
}
return null;
}
};
// Despite the DatastoreTimeoutException in the first call to getResult(), this should succeed
// without retrying. If a retry is triggered, the test should fail due to the call to fail().
auditedOfy().transactCommitLoggedWork(commitLoggedWork);
}
void doReadOnlyRetryTest(final RuntimeException e) {
assertThat(
ofy()
.transactNewReadOnly(
new Supplier<Integer>() {
int count = 0;
@Override
public Integer get() {
count++;
if (count == 3) {
return count;
}
throw new TransientFailureException("");
}
}))
.isEqualTo(3);
}
@Test
void testTransactNewReadOnly_transientFailureException_retries() {
doReadOnlyRetryTest(new TransientFailureException(""));
}
@Test
void testTransactNewReadOnly_datastoreTimeoutException_retries() {
doReadOnlyRetryTest(new DatastoreTimeoutException(""));
}
@Test
void testTransactNewReadOnly_datastoreFailureException_retries() {
doReadOnlyRetryTest(new DatastoreFailureException(""));
}
@Test
void test_getBaseEntityClassFromEntityOrKey_regularEntity() {
Contact contact = newContact("testcontact");
assertThat(getBaseEntityClassFromEntityOrKey(contact)).isEqualTo(Contact.class);
assertThat(getBaseEntityClassFromEntityOrKey(Key.create(contact))).isEqualTo(Contact.class);
}
@Test
void test_getBaseEntityClassFromEntityOrKey_unregisteredEntity() {
IllegalStateException thrown =
assertThrows(
IllegalStateException.class,
() -> getBaseEntityClassFromEntityOrKey(new SystemClock()));
assertThat(thrown).hasMessageThat().contains("SystemClock");
}
@Test
void test_getBaseEntityClassFromEntityOrKey_unregisteredEntityKey() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() ->
getBaseEntityClassFromEntityOrKey(
Key.create(
com.google.appengine.api.datastore.KeyFactory.createKey(
"UnknownKind", 1))));
assertThat(thrown).hasMessageThat().contains("UnknownKind");
}
@Test
void test_doWithFreshSessionCache() {
auditedOfy().saveWithoutBackup().entity(someObject.asHistoryEntry()).now();
final HistoryEntry modifiedObject =
someObject.asBuilder().setModificationTime(END_OF_TIME).build();
// Mutate the saved objected, bypassing the Objectify session cache.
getDatastoreService()
.put(auditedOfy().saveWithoutBackup().toEntity(modifiedObject.asHistoryEntry()));
// Normal loading should come from the session cache and shouldn't reflect the mutation.
assertThat(auditedOfy().load().entity(someObject).now()).isEqualTo(someObject.asHistoryEntry());
// Loading inside doWithFreshSessionCache() should reflect the mutation.
boolean ran =
auditedOfy()
.doWithFreshSessionCache(
() -> {
assertAboutImmutableObjects()
.that(auditedOfy().load().entity(someObject).now())
.isEqualExceptFields(modifiedObject, "contactBase");
return true;
});
assertThat(ran).isTrue();
// Test the normal loading again to verify that we've restored the original session unchanged.
assertThat(auditedOfy().load().entity(someObject).now()).isEqualTo(someObject.asHistoryEntry());
}
}

View file

@ -15,20 +15,16 @@
package google.registry.persistence; package google.registry.persistence;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.domain.Domain; import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory.DomainHistoryId; import google.registry.model.domain.DomainHistory.DomainHistoryId;
import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.AppEngineExtension; import google.registry.testing.AppEngineExtension;
import javax.persistence.Transient;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
@ -74,7 +70,6 @@ class DomainHistoryVKeyTest {
@Entity @Entity
@javax.persistence.Entity(name = "TestEntity") @javax.persistence.Entity(name = "TestEntity")
private static class TestEntity extends ImmutableObject { private static class TestEntity extends ImmutableObject {
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
@Id @javax.persistence.Id String id = "id"; @Id @javax.persistence.Id String id = "id";

View file

@ -162,18 +162,14 @@ class VKeyTest {
void testStringify_sqlAndOfyVKey() { void testStringify_sqlAndOfyVKey() {
assertThat( assertThat(
VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo"))).stringify()) VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo"))).stringify())
.isEqualTo( .isEqualTo("kind:TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M");
"kind:TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0cjELEg9FbnRpdH"
+ "lHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
} }
@Test @Test
void testStringify_asymmetricVKey() { void testStringify_asymmetricVKey() {
assertThat( assertThat(
VKey.create(TestObject.class, "test", Key.create(TestObject.create("foo"))).stringify()) VKey.create(TestObject.class, "test", Key.create(TestObject.create("foo"))).stringify())
.isEqualTo( .isEqualTo("kind:TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M");
"kind:TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0cjELEg9FbnRpd"
+ "HlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
} }
/** Test create() via different vkey string representations. */ /** Test create() via different vkey string representations. */

View file

@ -14,25 +14,18 @@
package google.registry.testing; package google.registry.testing;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key; import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ImmutableObject; import google.registry.model.ImmutableObject;
import google.registry.model.annotations.VirtualEntity; import google.registry.model.annotations.VirtualEntity;
import google.registry.model.common.EntityGroupRoot;
import google.registry.persistence.VKey; import google.registry.persistence.VKey;
import javax.persistence.Transient;
/** A test model object that can be persisted in any entity group. */ /** A test model object that can be persisted in any entity group. */
@Entity @Entity
@javax.persistence.Entity @javax.persistence.Entity
public class TestObject extends ImmutableObject { public class TestObject extends ImmutableObject {
@Parent @Transient Key<EntityGroupRoot> parent;
@Id @javax.persistence.Id String id; @Id @javax.persistence.Id String id;
String field; String field;
@ -58,14 +51,9 @@ public class TestObject extends ImmutableObject {
} }
public static TestObject create(String id, String field) { public static TestObject create(String id, String field) {
return create(id, field, getCrossTldKey());
}
public static TestObject create(String id, String field, Key<EntityGroupRoot> parent) {
TestObject instance = new TestObject(); TestObject instance = new TestObject();
instance.id = id; instance.id = id;
instance.field = field; instance.field = field;
instance.parent = parent;
return instance; return instance;
} }

View file

@ -1,6 +1,3 @@
class google.registry.model.common.EntityGroupRoot {
@Id java.lang.String id;
}
class google.registry.model.common.GaeUserIdConverter { class google.registry.model.common.GaeUserIdConverter {
@Id long id; @Id long id;
com.google.appengine.api.users.User user; com.google.appengine.api.users.User user;
@ -391,7 +388,6 @@ enum google.registry.model.reporting.HistoryEntry$Type {
} }
class google.registry.model.server.ServerSecret { class google.registry.model.server.ServerSecret {
@Id long id; @Id long id;
@Parent com.googlecode.objectify.Key<google.registry.model.common.EntityGroupRoot> parent;
long leastSignificant; long leastSignificant;
long mostSignificant; long mostSignificant;
} }