mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
Delete EntityGroupRoot (#1776)
This commit is contained in:
parent
2133aea066
commit
bc523b2160
16 changed files with 5 additions and 562 deletions
|
@ -16,7 +16,6 @@ package google.registry.model;
|
|||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.common.GaeUserIdConverter;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
|
@ -41,7 +40,6 @@ public final class EntityClasses {
|
|||
ContactHistory.class,
|
||||
Domain.class,
|
||||
DomainHistory.class,
|
||||
EntityGroupRoot.class,
|
||||
EppResourceIndex.class,
|
||||
EppResourceIndexBucket.class,
|
||||
ForeignKeyIndex.ForeignKeyContactIndex.class,
|
||||
|
|
|
@ -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 {}
|
|
@ -14,26 +14,17 @@
|
|||
|
||||
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.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/** A singleton entity in Datastore. */
|
||||
/** A singleton entity in the database. */
|
||||
@DeleteAfterMigration
|
||||
@MappedSuperclass
|
||||
@InCrossTld
|
||||
public abstract class CrossTldSingleton extends ImmutableObject {
|
||||
|
||||
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
|
||||
|
||||
@Id @javax.persistence.Id long id = SINGLETON_ID;
|
||||
|
||||
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -41,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
|
|||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
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.ResourceWithTransferData;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
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.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
|
@ -286,15 +283,6 @@ public class DomainBase extends EppResource
|
|||
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() {
|
||||
return nullToEmptyImmutableCopy(subordinateHosts);
|
||||
}
|
||||
|
|
|
@ -240,8 +240,6 @@ public class Lock extends ImmutableObject implements Serializable {
|
|||
|
||||
Lock newLock =
|
||||
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);
|
||||
|
||||
return AcquireResult.create(now, lock, newLock, lockState);
|
||||
|
|
|
@ -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.Maps.filterValues;
|
||||
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.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
@ -66,7 +65,6 @@ public final class Registries {
|
|||
auditedOfy()
|
||||
.load()
|
||||
.type(Registry.class)
|
||||
.ancestor(getCrossTldKey())
|
||||
.keys()
|
||||
.list()
|
||||
.stream()
|
||||
|
|
|
@ -18,7 +18,6 @@ import com.google.common.collect.ImmutableCollection;
|
|||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.NoSuchElementException;
|
||||
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.
|
||||
*
|
||||
* <p>The resulting list is empty if there are no entities of this type. In Datastore mode, if 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.
|
||||
* <p>The resulting list is empty if there are no entities of this type.
|
||||
*/
|
||||
<T> ImmutableList<T> loadAllOf(Class<T> clazz);
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
* <p>The resulting stream is empty if there are no entities of this type.
|
||||
*/
|
||||
<T> Stream<T> loadAllOfStream(Class<T> clazz);
|
||||
|
||||
|
|
|
@ -92,7 +92,6 @@ public final class RegistryTool {
|
|||
.put("pending_escrow", PendingEscrowCommand.class)
|
||||
.put("registrar_poc", RegistrarPocCommand.class)
|
||||
.put("renew_domain", RenewDomainCommand.class)
|
||||
.put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class)
|
||||
.put("save_sql_credential", SaveSqlCredentialCommand.class)
|
||||
.put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class)
|
||||
.put("set_database_migration_state", SetDatabaseMigrationStateCommand.class)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,7 +51,6 @@ public class ClassPathManagerTest {
|
|||
assertThat(ClassPathManager.getClass("GaeUserIdConverter")).isEqualTo(GaeUserIdConverter.class);
|
||||
assertThat(ClassPathManager.getClass("EppResourceIndexBucket"))
|
||||
.isEqualTo(EppResourceIndexBucket.class);
|
||||
assertThat(ClassPathManager.getClass("EntityGroupRoot")).isEqualTo(EntityGroupRoot.class);
|
||||
assertThat(ClassPathManager.getClass("Domain")).isEqualTo(Domain.class);
|
||||
assertThat(ClassPathManager.getClass("HistoryEntry")).isEqualTo(HistoryEntry.class);
|
||||
assertThat(ClassPathManager.getClass("ForeignKeyHostIndex"))
|
||||
|
@ -100,7 +99,6 @@ public class ClassPathManagerTest {
|
|||
.isEqualTo("GaeUserIdConverter");
|
||||
assertThat(ClassPathManager.getClassName(EppResourceIndexBucket.class))
|
||||
.isEqualTo("EppResourceIndexBucket");
|
||||
assertThat(ClassPathManager.getClassName(EntityGroupRoot.class)).isEqualTo("EntityGroupRoot");
|
||||
assertThat(ClassPathManager.getClassName(Domain.class)).isEqualTo("Domain");
|
||||
assertThat(ClassPathManager.getClassName(HistoryEntry.class)).isEqualTo("HistoryEntry");
|
||||
assertThat(ClassPathManager.getClassName(ForeignKeyHostIndex.class))
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -15,20 +15,16 @@
|
|||
package google.registry.persistence;
|
||||
|
||||
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 com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.testing.AppEngineExtension;
|
||||
import javax.persistence.Transient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
|
||||
|
@ -74,7 +70,6 @@ class DomainHistoryVKeyTest {
|
|||
@Entity
|
||||
@javax.persistence.Entity(name = "TestEntity")
|
||||
private static class TestEntity extends ImmutableObject {
|
||||
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
|
||||
@Id @javax.persistence.Id String id = "id";
|
||||
|
||||
|
|
|
@ -162,18 +162,14 @@ class VKeyTest {
|
|||
void testStringify_sqlAndOfyVKey() {
|
||||
assertThat(
|
||||
VKey.create(TestObject.class, "foo", Key.create(TestObject.create("foo"))).stringify())
|
||||
.isEqualTo(
|
||||
"kind:TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0cjELEg9FbnRpdH"
|
||||
+ "lHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||
.isEqualTo("kind:TestObject@sql:rO0ABXQAA2Zvbw@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStringify_asymmetricVKey() {
|
||||
assertThat(
|
||||
VKey.create(TestObject.class, "test", Key.create(TestObject.create("foo"))).stringify())
|
||||
.isEqualTo(
|
||||
"kind:TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0cjELEg9FbnRpd"
|
||||
+ "HlHcm91cFJvb3QiCWNyb3NzLXRsZAwLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||
.isEqualTo("kind:TestObject@sql:rO0ABXQABHRlc3Q@ofy:agR0ZXN0chMLEgpUZXN0T2JqZWN0IgNmb28M");
|
||||
}
|
||||
|
||||
/** Test create() via different vkey string representations. */
|
||||
|
|
|
@ -14,25 +14,18 @@
|
|||
|
||||
package google.registry.testing;
|
||||
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.VirtualEntity;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/** A test model object that can be persisted in any entity group. */
|
||||
@Entity
|
||||
@javax.persistence.Entity
|
||||
public class TestObject extends ImmutableObject {
|
||||
|
||||
@Parent @Transient Key<EntityGroupRoot> parent;
|
||||
|
||||
@Id @javax.persistence.Id String id;
|
||||
|
||||
String field;
|
||||
|
@ -58,14 +51,9 @@ public class TestObject extends ImmutableObject {
|
|||
}
|
||||
|
||||
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();
|
||||
instance.id = id;
|
||||
instance.field = field;
|
||||
instance.parent = parent;
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
class google.registry.model.common.EntityGroupRoot {
|
||||
@Id java.lang.String id;
|
||||
}
|
||||
class google.registry.model.common.GaeUserIdConverter {
|
||||
@Id long id;
|
||||
com.google.appengine.api.users.User user;
|
||||
|
@ -391,7 +388,6 @@ enum google.registry.model.reporting.HistoryEntry$Type {
|
|||
}
|
||||
class google.registry.model.server.ServerSecret {
|
||||
@Id long id;
|
||||
@Parent com.googlecode.objectify.Key<google.registry.model.common.EntityGroupRoot> parent;
|
||||
long leastSignificant;
|
||||
long mostSignificant;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue