Convert HostCreateFlow and HostCheckFlow to tm() (#910)

This commit is contained in:
Shicong Huang 2020-12-22 21:02:02 -05:00 committed by GitHub
parent cb63c3dd80
commit 9c43aab8cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 231 additions and 103 deletions

View file

@ -21,10 +21,8 @@ import static google.registry.flows.host.HostFlowUtils.validateHostName;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
import static google.registry.model.EppResourceUtils.createRepoId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.union;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
@ -137,13 +135,11 @@ public final class HostCreateFlow implements TransactionalFlow {
ImmutableSet<ImmutableObject> entitiesToSave =
ImmutableSet.of(
newHost,
historyBuilder.build(),
historyBuilder.build().toChildHistoryEntity(),
ForeignKeyIndex.create(newHost, newHost.getDeletionTime()),
EppResourceIndex.create(Key.create(newHost)));
if (superordinateDomain.isPresent()) {
entitiesToSave =
union(
entitiesToSave,
tm().update(
superordinateDomain
.get()
.asBuilder()
@ -153,7 +149,7 @@ public final class HostCreateFlow implements TransactionalFlow {
// they are only written as NS records from the referencing domain.
dnsQueue.addHostRefreshTask(targetId);
}
ofy().save().entities(entitiesToSave);
tm().insertAll(entitiesToSave);
return responseBuilder.setResData(HostCreateData.create(targetId, now)).build();
}

View file

@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.isBeforeOrAt;
import static google.registry.util.DateTimeUtils.latestOf;
@ -135,7 +136,7 @@ public final class EppResourceUtils {
useCache
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
.getOrDefault(foreignKey, null)
: ofy().load().type(ForeignKeyIndex.mapToFkiClass(clazz)).id(foreignKey).now();
: ForeignKeyIndex.load(clazz, foreignKey, now);
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
return Optional.empty();
@ -143,7 +144,7 @@ public final class EppResourceUtils {
T resource =
useCache
? EppResource.loadCached(fki.getResourceKey())
: tm().maybeLoad(fki.getResourceKey()).orElse(null);
: transactIfJpaTm(() -> tm().maybeLoad(fki.getResourceKey()).orElse(null));
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
return Optional.empty();
}

View file

@ -15,9 +15,11 @@
package google.registry.model.index;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
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.TypeUtils.instantiate;
@ -29,6 +31,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Streams;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
@ -44,6 +47,8 @@ import google.registry.model.host.HostResource;
import google.registry.persistence.VKey;
import google.registry.schema.replay.DatastoreOnlyEntity;
import google.registry.util.NonFinalForTesting;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@ -76,13 +81,21 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
public static class ForeignKeyHostIndex extends ForeignKeyIndex<HostResource>
implements DatastoreOnlyEntity {}
static final ImmutableMap<Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
private static final ImmutableMap<
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
RESOURCE_CLASS_TO_FKI_CLASS =
ImmutableMap.of(
ContactResource.class, ForeignKeyContactIndex.class,
DomainBase.class, ForeignKeyDomainIndex.class,
HostResource.class, ForeignKeyHostIndex.class);
private static final ImmutableMap<Class<? extends EppResource>, String>
RESOURCE_CLASS_TO_FKI_PROPERTY =
ImmutableMap.of(
ContactResource.class, "contactId",
DomainBase.class, "fullyQualifiedDomainName",
HostResource.class, "fullyQualifiedHostName");
@Id String foreignKey;
/**
@ -179,9 +192,42 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
*/
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
Class<E> clazz, Iterable<String> foreignKeys, final DateTime now) {
return ofy().load().type(mapToFkiClass(clazz)).ids(foreignKeys).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().deletionTime))
.collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
if (tm().isOfy()) {
return ofy().load().type(mapToFkiClass(clazz)).ids(foreignKeys).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().deletionTime))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
} else {
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
List<E> entities =
tm().transact(
() -> {
String entityName =
jpaTm().getEntityManager().getMetamodel().entity(clazz).getName();
return jpaTm()
.getEntityManager()
.createQuery(
String.format(
"FROM %s WHERE %s IN :propertyValue and deletionTime > :now ",
entityName, property),
clazz)
.setParameter("propertyValue", foreignKeys)
.setParameter("now", now)
.getResultList();
});
// We need to find and return the entities with the maximum deletionTime for each foreign key.
return Multimaps.index(entities, EppResource::getForeignKey).asMap().entrySet().stream()
.map(
entry ->
Maps.immutableEntry(
entry.getKey(),
entry.getValue().stream()
.max(Comparator.comparing(EppResource::getDeletionTime))
.get()))
.collect(
toImmutableMap(
Map.Entry::getKey,
entry -> create(entry.getValue(), entry.getValue().getDeletionTime())));
}
}
static final CacheLoader<Key<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
@ -266,7 +312,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
.filter(entry -> entry.getValue().isPresent())
.filter(entry -> now.isBefore(entry.getValue().get().getDeletionTime()))
.collect(
ImmutableMap.toImmutableMap(
toImmutableMap(
entry -> entry.getKey().getName(),
entry -> (ForeignKeyIndex<E>) entry.getValue().get()));
return fkisFromCache;

View file

@ -29,6 +29,11 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.common.flogger.FluentLogger;
import google.registry.config.RegistryConfig;
import google.registry.model.ImmutableObject;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.persistence.JpaRetries;
import google.registry.persistence.VKey;
import google.registry.util.Clock;
@ -56,6 +61,18 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Retrier retrier = new Retrier(new SystemSleeper(), 3);
// The entity of classes in this set will be simply ignored when passed to modification
// operations, i.e. insert, put, update and delete. This is to help maintain a single code path
// when we switch from ofy() to tm() for the database migration as we don't need have a condition
// to exclude the Datastore specific entities when the underlying tm() is jpaTm().
// TODO(b/176108270): Remove this property after database migration.
private static final ImmutableSet<Class<? extends ImmutableObject>> IGNORED_ENTITY_CLASSES =
ImmutableSet.of(
EppResourceIndex.class,
ForeignKeyContactIndex.class,
ForeignKeyDomainIndex.class,
ForeignKeyHostIndex.class);
// EntityManagerFactory is thread safe.
private final EntityManagerFactory emf;
private final Clock clock;
@ -228,6 +245,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void insert(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
getEntityManager().persist(entity);
transactionInfo.get().addUpdate(entity);
@ -253,6 +273,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void put(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
getEntityManager().merge(entity);
transactionInfo.get().addUpdate(entity);
@ -278,6 +301,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void update(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
checkArgument(exists(entity), "Given entity does not exist");
getEntityManager().merge(entity);
@ -414,6 +440,9 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
@Override
public void delete(Object entity) {
checkArgumentNotNull(entity, "entity must be specified");
if (isEntityOfIgnoredClass(entity)) {
return;
}
assertInTransaction();
Object managedEntity = entity;
if (!getEntityManager().contains(entity)) {
@ -464,6 +493,10 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
}
}
private static boolean isEntityOfIgnoredClass(Object entity) {
return IGNORED_ENTITY_CLASSES.contains(entity.getClass());
}
private static ImmutableSet<EntityId> getEntityIdsFromEntity(
EntityType<?> entityType, Object entity) {
if (entityType.hasSingleIdAttribute()) {

View file

@ -100,7 +100,11 @@ public abstract class FlowTestCase<F extends Flow> {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().withTaskQueue().build();
AppEngineExtension.builder()
.withClock(clock)
.withDatastoreAndCloudSql()
.withTaskQueue()
.build();
@BeforeEach
public void beforeEachFlowTestCase() {
@ -288,7 +292,7 @@ public abstract class FlowTestCase<F extends Flow> {
e);
}
// Clear the cache so that we don't see stale results in tests.
ofy().clearSessionCache();
tm().clearSessionCache();
return output;
}

View file

@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.tmch.ClaimsListShardTest.createTestClaimsListShard;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
@ -72,7 +74,7 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
protected R reloadResourceByForeignKey(DateTime now) throws Exception {
// Force the session to be cleared so that when we read it back, we read from Datastore and not
// from the transaction's session cache.
ofy().clearSessionCache();
tm().clearSessionCache();
return loadByForeignKey(getResourceClass(), getUniqueIdFromCommand(), now).orElse(null);
}
@ -83,9 +85,9 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
protected <T extends EppResource> T reloadResourceAndCloneAtTime(T resource, DateTime now) {
// Force the session to be cleared.
ofy().clearSessionCache();
tm().clearSessionCache();
@SuppressWarnings("unchecked")
T refreshedResource = (T) ofy().load().entity(resource).now().cloneProjectedAtTime(now);
T refreshedResource = (T) transactIfJpaTm(() -> tm().load(resource)).cloneProjectedAtTime(now);
return refreshedResource;
}

View file

@ -24,16 +24,18 @@ import google.registry.flows.EppException;
import google.registry.flows.ResourceCheckFlowTestCase;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.host.HostResource;
import org.junit.jupiter.api.Test;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
/** Unit tests for {@link HostCheckFlow}. */
@DualDatabaseTest
class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostResource> {
HostCheckFlowTest() {
setEppInput("host_check.xml");
}
@Test
@TestOfyAndSql
void testNothingExists() throws Exception {
// These ids come from the check xml.
doCheckTest(
@ -42,7 +44,7 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testOneExists() throws Exception {
persistActiveHost("ns1.example.tld");
// These ids come from the check xml.
@ -52,7 +54,7 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testOneExistsButWasDeleted() throws Exception {
persistDeletedHost("ns1.example.tld", clock.nowUtc().minusDays(1));
// These ids come from the check xml.
@ -62,27 +64,27 @@ class HostCheckFlowTest extends ResourceCheckFlowTestCase<HostCheckFlow, HostRes
create(true, "ns3.example.tld", null));
}
@Test
@TestOfyAndSql
void testXmlMatches() throws Exception {
persistActiveHost("ns2.example.tld");
runFlowAssertResponse(loadFile("host_check_response.xml"));
}
@Test
@TestOfyAndSql
void test50IdsAllowed() throws Exception {
// Make sure we don't have a regression that reduces the number of allowed checks.
setEppInput("host_check_50.xml");
runFlow();
}
@Test
@TestOfyAndSql
void testTooManyIds() {
setEppInput("host_check_51.xml");
EppException thrown = assertThrows(TooManyResourceChecksException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
assertIcannReportingActivityFieldLogged("srs-host-check");

View file

@ -16,6 +16,7 @@ package google.registry.flows.host;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.createTlds;
@ -53,10 +54,12 @@ import google.registry.model.domain.DomainBase;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.HistoryEntry;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link HostCreateFlow}. */
@DualDatabaseTest
class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResource> {
private void setEppHostCreateInput(String hostName, String hostAddrs) {
@ -90,7 +93,9 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
.hasOnlyOneHistoryEntryWhich()
.hasType(HistoryEntry.Type.HOST_CREATE);
assertNoBillingEvents();
assertEppResourceIndexEntityFor(reloadResourceByForeignKey());
if (tm().isOfy()) {
assertEppResourceIndexEntityFor(reloadResourceByForeignKey());
}
}
private void doSuccessfulInternalTest(String tld) throws Exception {
@ -100,19 +105,19 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
doSuccessfulTest();
}
@Test
@TestOfyAndSql
void testDryRun() throws Exception {
dryRunFlowAssertResponse(loadFile("host_create_response.xml"));
}
@Test
@TestOfyAndSql
void testSuccess_externalNeverExisted() throws Exception {
doSuccessfulTest();
assertAboutHosts().that(reloadResourceByForeignKey()).hasSuperordinateDomain(null);
assertNoDnsTasksEnqueued();
}
@Test
@TestOfyAndSql
void testSuccess_internalNeverExisted() throws Exception {
doSuccessfulInternalTest("tld");
HostResource host = reloadResourceByForeignKey();
@ -123,7 +128,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertDnsTasksEnqueued("ns1.example.tld");
}
@Test
@TestOfyAndSql
void testFailure_multipartTLDsAndInvalidHost() {
createTlds("bar.tld", "tld");
@ -132,7 +137,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testSuccess_externalExistedButWasDeleted() throws Exception {
persistDeletedHost(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
doSuccessfulTest();
@ -140,7 +145,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertNoDnsTasksEnqueued();
}
@Test
@TestOfyAndSql
void testSuccess_internalExistedButWasDeleted() throws Exception {
persistDeletedHost(getUniqueIdFromCommand(), clock.nowUtc().minusDays(1));
doSuccessfulInternalTest("tld");
@ -152,7 +157,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertDnsTasksEnqueued("ns1.example.tld");
}
@Test
@TestOfyAndSql
void testFailure_subordinateNeedsIps() {
setEppHostCreateInput("ns1.example.tld", null);
createTld("tld");
@ -161,7 +166,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_externalMustNotHaveIps() {
setEppHostCreateInputWithIps("ns1.example.external");
createTld("tld");
@ -170,7 +175,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_superordinateMissing() {
setEppHostCreateInput("ns1.example.tld", null);
createTld("tld");
@ -179,7 +184,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertThat(thrown).hasMessageThat().contains("(example.tld)");
}
@Test
@TestOfyAndSql
void testFailure_superordinateInPendingDelete() {
setEppHostCreateInputWithIps("ns1.example.tld");
createTld("tld");
@ -197,7 +202,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
.contains("Superordinate domain for this hostname is in pending delete");
}
@Test
@TestOfyAndSql
void testFailure_alreadyExists() throws Exception {
setEppHostCreateInput("ns1.example.tld", null);
persistActiveHost(getUniqueIdFromCommand());
@ -209,7 +214,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
String.format("Object with given ID (%s) already exists", getUniqueIdFromCommand()));
}
@Test
@TestOfyAndSql
void testFailure_resourceContention() throws Exception {
setEppHostCreateInput("ns1.example.tld", null);
String targetId = getUniqueIdFromCommand();
@ -226,14 +231,14 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_nonLowerCaseHostname() {
setEppHostCreateInput("ns1.EXAMPLE.tld", null);
EppException thrown = assertThrows(HostNameNotLowerCaseException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_nonPunyCodedHostname() {
setEppHostCreateInput("ns1.çauçalito.みんな", null);
HostNameNotPunyCodedException thrown =
@ -241,21 +246,21 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertThat(thrown).hasMessageThat().contains("expected ns1.xn--aualito-txac.xn--q9jyb4c");
}
@Test
@TestOfyAndSql
void testFailure_nonCanonicalHostname() {
setEppHostCreateInput("ns1.example.tld.", null);
EppException thrown = assertThrows(HostNameNotNormalizedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_longHostName() {
setEppHostCreateInputWithIps("a" + Strings.repeat(".labelpart", 25) + ".tld");
EppException thrown = assertThrows(HostNameTooLongException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_ip4AddressWithIp6Declaration() {
setEppHostCreateInput(
"ns1.example.tld",
@ -272,37 +277,37 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testFailure_badCharacter() {
doFailingHostNameTest("foo bar", InvalidHostNameException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowPublicSuffix() {
doFailingHostNameTest("example.tld", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowCcTld() {
doFailingHostNameTest("foo.co.uk", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_barePublicSuffix() {
doFailingHostNameTest("com", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_bareCcTld() {
doFailingHostNameTest("co.uk", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_tooShallowNewTld() {
doFailingHostNameTest("example.lol", HostNameTooShallowException.class);
}
@Test
@TestOfyAndSql
void testFailure_ccTldInBailiwick() {
createTld("co.uk");
setEppHostCreateInputWithIps("foo.co.uk");
@ -310,7 +315,7 @@ class HostCreateFlowTest extends ResourceFlowTestCase<HostCreateFlow, HostResour
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@Test
@TestOfyAndSql
void testIcannActivityReportField_getsLogged() throws Exception {
runFlow();
assertIcannReportingActivityFieldLogged("srs-host-create");

View file

@ -26,66 +26,68 @@ import google.registry.flows.host.HostFlowUtils.HostNameTooLongException;
import google.registry.flows.host.HostFlowUtils.HostNameTooShallowException;
import google.registry.flows.host.HostFlowUtils.InvalidHostNameException;
import google.registry.testing.AppEngineExtension;
import org.junit.jupiter.api.Test;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestOfyAndSql;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link HostFlowUtils}. */
@DualDatabaseTest
class HostFlowUtilsTest {
@RegisterExtension
final AppEngineExtension appEngine =
AppEngineExtension.builder().withDatastoreAndCloudSql().build();
@Test
@TestOfyAndSql
void test_validExternalHostName_validates() throws Exception {
assertThat(validateHostName("host.example.com").toString()).isEqualTo("host.example.com");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_validates() throws Exception {
assertThat(validateHostName("host.blogspot.com").toString()).isEqualTo("host.blogspot.com");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_validates() throws Exception {
assertThat(validateHostName("ns1.host.co.uk").toString()).isEqualTo("ns1.host.co.uk");
}
@Test
@TestOfyAndSql
void test_validExternalHostNameOnRegistrySuffixList_multipartTLD_tooShallow() {
assertThrows(
HostNameTooShallowException.class, () -> validateHostName("host.co.uk").toString());
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameTooLong() {
assertThrows(
HostNameTooLongException.class,
() -> validateHostName(Strings.repeat("na", 200) + ".wat.man"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotLowerCase() {
assertThrows(HostNameNotLowerCaseException.class, () -> validateHostName("NA.CAPS.TLD"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotPunyCoded() {
assertThrows(
HostNameNotPunyCodedException.class, () -> validateHostName("motörhead.death.metal"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameNotNormalized() {
assertThrows(HostNameNotNormalizedException.class, () -> validateHostName("root.node.yeah."));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameHasLeadingHyphen() {
assertThrows(InvalidHostNameException.class, () -> validateHostName("-giga.mega.tld"));
}
@Test
@TestOfyAndSql
void test_validateHostName_hostNameTooShallow() {
assertThrows(HostNameTooShallowException.class, () -> validateHostName("domain.tld"));
}

View file

@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadAtPointInTime;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newHostResource;
import static google.registry.testing.DatabaseHelper.persistNewRegistrars;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.DatabaseHelper.persistResourceWithCommitLog;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
@ -26,16 +27,19 @@ import static org.joda.time.DateTimeZone.UTC;
import google.registry.model.host.HostResource;
import google.registry.model.ofy.Ofy;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Tests for {@link EppResourceUtils}. */
public class EppResourceUtilsTest {
@DualDatabaseTest
class EppResourceUtilsTest {
@RegisterExtension
public final AppEngineExtension appEngine =
@ -51,7 +55,7 @@ public class EppResourceUtilsTest {
inject.setStaticField(Ofy.class, "clock", clock);
}
@Test
@TestOfyAndSql
void testLoadAtPointInTime_beforeCreated_returnsNull() {
clock.advanceOneMilli();
// Don't save a commit log, we shouldn't need one.
@ -62,7 +66,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minus(Duration.millis(1))).now()).isNull();
}
@Test
@TestOfyAndSql
void testLoadAtPointInTime_atOrAfterLastAutoUpdateTime_returnsResource() {
clock.advanceOneMilli();
// Don't save a commit log, we shouldn't need one.
@ -73,8 +77,9 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc()).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_usingIntactRevisionHistory_returnsMutationValue() {
persistNewRegistrars("OLD", "NEW");
clock.advanceOneMilli();
// Save resource with a commit log that we can read in later as a revisions map value.
HostResource oldHost = persistResourceWithCommitLog(
@ -94,7 +99,7 @@ public class EppResourceUtilsTest {
.isEqualTo(oldHost);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_brokenRevisionHistory_returnsResourceAsIs() {
// Don't save a commit log since we want to test the handling of a broken revisions key.
HostResource oldHost = persistResource(
@ -114,7 +119,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minusMillis(1)).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_fallback_returnsMutationValueForOldestRevision() {
clock.advanceOneMilli();
// Save a commit log that we can fall back to.
@ -136,7 +141,7 @@ public class EppResourceUtilsTest {
.isEqualTo(oldHost);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_ultimateFallback_onlyOneRevision_returnsCurrentResource() {
clock.advanceOneMilli();
// Don't save a commit log; we want to test that we load from the current resource.
@ -151,7 +156,7 @@ public class EppResourceUtilsTest {
assertThat(loadAtPointInTime(host, clock.nowUtc().minusMillis(1)).now()).isEqualTo(host);
}
@Test
@TestOfyOnly
void testLoadAtPointInTime_moreThanThirtyDaysInPast_historyIsPurged() {
clock.advanceOneMilli();
HostResource host =

View file

@ -20,6 +20,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistDeletedHost;
@ -29,16 +30,21 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
import com.google.common.collect.ImmutableList;
import google.registry.model.EntityTestCase;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.host.HostResource;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.TestCacheExtension;
import google.registry.testing.TestOfyAndSql;
import google.registry.testing.TestOfyOnly;
import google.registry.testing.TestSqlOnly;
import org.joda.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ForeignKeyIndex}. */
public class ForeignKeyIndexTest extends EntityTestCase {
@DualDatabaseTest
class ForeignKeyIndexTest extends EntityTestCase {
@RegisterExtension
public final TestCacheExtension testCacheExtension =
@ -49,7 +55,17 @@ public class ForeignKeyIndexTest extends EntityTestCase {
createTld("com");
}
@Test
@TestSqlOnly
void testModifyForeignKeyIndex_notThrowExceptionInSql() {
DomainBase domainBase = newDomainBase("test.com");
ForeignKeyIndex<DomainBase> fki = ForeignKeyIndex.create(domainBase, fakeClock.nowUtc());
tm().transact(() -> tm().insert(fki));
tm().transact(() -> tm().put(fki));
tm().transact(() -> tm().delete(fki));
tm().transact(() -> tm().update(fki));
}
@TestOfyOnly
void testPersistence() {
// Persist a host and implicitly persist a ForeignKeyIndex for it.
HostResource host = persistActiveHost("ns1.example.com");
@ -59,7 +75,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
assertThat(fki.getDeletionTime()).isEqualTo(END_OF_TIME);
}
@Test
@TestOfyOnly
void testIndexing() throws Exception {
// Persist a host and implicitly persist a ForeignKeyIndex for it.
persistActiveHost("ns1.example.com");
@ -68,38 +84,50 @@ public class ForeignKeyIndexTest extends EntityTestCase {
"deletionTime");
}
@Test
@TestOfyAndSql
void testLoadForNonexistentForeignKey_returnsNull() {
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testLoadForDeletedForeignKey_returnsNull() {
HostResource host = persistActiveHost("ns1.example.com");
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
if (tm().isOfy()) {
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
} else {
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
}
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testLoad_newerKeyHasBeenSoftDeleted() {
HostResource host1 = persistActiveHost("ns1.example.com");
fakeClock.advanceOneMilli();
ForeignKeyHostIndex fki = new ForeignKeyHostIndex();
fki.foreignKey = "ns1.example.com";
fki.topReference = host1.createVKey();
fki.deletionTime = fakeClock.nowUtc();
persistResource(fki);
if (tm().isOfy()) {
ForeignKeyHostIndex fki = new ForeignKeyHostIndex();
fki.foreignKey = "ns1.example.com";
fki.topReference = host1.createVKey();
fki.deletionTime = fakeClock.nowUtc();
persistResource(fki);
} else {
persistResource(host1.asBuilder().setDeletionTime(fakeClock.nowUtc()).build());
}
assertThat(ForeignKeyIndex.load(HostResource.class, "ns1.example.com", fakeClock.nowUtc()))
.isNull();
}
@Test
@TestOfyAndSql
void testBatchLoad_skipsDeletedAndNonexistent() {
persistActiveHost("ns1.example.com");
HostResource host = persistActiveHost("ns2.example.com");
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
if (tm().isOfy()) {
persistResource(ForeignKeyIndex.create(host, fakeClock.nowUtc().minusDays(1)));
} else {
persistResource(host.asBuilder().setDeletionTime(fakeClock.nowUtc().minusDays(1)).build());
}
assertThat(
ForeignKeyIndex.load(
HostResource.class,
@ -109,7 +137,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com");
}
@Test
@TestOfyAndSql
void testDeadCodeThatDeletedScrapCommandsReference() {
persistActiveHost("omg");
assertThat(ForeignKeyIndex.load(HostResource.class, "omg", fakeClock.nowUtc()).getForeignKey())
@ -124,7 +152,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
return ForeignKeyIndex.load(ContactResource.class, contactId, fakeClock.nowUtc());
}
@Test
@TestOfyOnly
void test_loadCached_cachesNonexistenceOfHosts() {
assertThat(
ForeignKeyIndex.loadCached(
@ -144,7 +172,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns4.example.com", loadHostFki("ns4.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_cachesExistenceOfHosts() {
HostResource host1 = persistActiveHost("ns1.example.com");
HostResource host2 = persistActiveHost("ns2.example.com");
@ -172,7 +200,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
"ns3.example.com", loadHostFki("ns3.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_doesntSeeHostChangesWhileCacheIsValid() {
HostResource originalHost = persistActiveHost("ns1.example.com");
ForeignKeyIndex<HostResource> originalFki = loadHostFki("ns1.example.com");
@ -195,7 +223,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com", originalFki);
}
@Test
@TestOfyOnly
void test_loadCached_filtersOutSoftDeletedHosts() {
persistActiveHost("ns1.example.com");
persistDeletedHost("ns2.example.com", fakeClock.nowUtc().minusDays(1));
@ -207,7 +235,7 @@ public class ForeignKeyIndexTest extends EntityTestCase {
.containsExactly("ns1.example.com", loadHostFki("ns1.example.com"));
}
@Test
@TestOfyOnly
void test_loadCached_cachesContactFkis() {
persistActiveContact("contactid1");
ForeignKeyIndex<ContactResource> fki1 = loadContactFki("contactid1");

View file

@ -454,10 +454,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
.setEnvIsAdmin(userInfo.isAdmin());
}
if (clock != null) {
helper.setClock(() -> clock.nowUtc().getMillis());
}
if (withLocalModules) {
helper.setEnvInstance("0");
}

View file

@ -18,6 +18,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.transaction.TransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
import java.lang.reflect.Field;
@ -160,6 +161,14 @@ class DualDatabaseTestInvocationContextProvider implements TestTemplateInvocatio
private static boolean isDualDatabaseTest(ExtensionContext context) {
Object testInstance = context.getTestInstance().orElseThrow(RuntimeException::new);
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class);
// If the test method is declared in its parent class,
// e.g. google.registry.flows.ResourceFlowTestCase.testRequiresLogin,
// we don't consider it is a DualDatabaseTest. This is because there may exist some subclasses
// that have not been migrated to DualDatabaseTest.
boolean isDeclaredTestMethod =
ImmutableSet.copyOf(testInstance.getClass().getDeclaredMethods())
.contains(context.getTestMethod().orElseThrow(RuntimeException::new));
return testInstance.getClass().isAnnotationPresent(DualDatabaseTest.class)
&& isDeclaredTestMethod;
}
}

View file

@ -17,7 +17,6 @@ package google.registry.tools;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.toArray;
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.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
import static java.nio.charset.StandardCharsets.UTF_8;
@ -118,7 +117,7 @@ public abstract class CommandTestCase<C extends Command> {
} finally {
// Clear the session cache so that subsequent reads for verification purposes hit Datastore.
// This primarily matters for AutoTimestamp fields, which otherwise won't have updated values.
ofy().clearSessionCache();
tm().clearSessionCache();
// Reset back to UNITTEST environment.
RegistryToolEnvironment.UNITTEST.setup(systemPropertyExtension);
}
@ -192,7 +191,7 @@ public abstract class CommandTestCase<C extends Command> {
/** Returns count of all poll messages in Datastore. */
int getPollMessageCount() {
return ofy().load().type(PollMessage.class).count();
return transactIfJpaTm(() -> tm().loadAll(PollMessage.class).size());
}
/**