Ensure that all relevant Keys can be converted to VKeys (#883)

* Ensure that all relevant Keys can be converted to VKeys

When replaying commit logs to SQL (specifically deletes) we will need to
convert Datastore Keys to SQL VKeys in order to know what (if anything)
to delete.

The test added to EntityTest (and the associated code changes) mean that
for every relevant object we'll be able to call
VKeyTranslatorFactory.createVKey(Key<?>) for all possible keys that we
care about. Note that we do not care about entities that only exist in
Datastore or entities that are non-replicated -- by their nature,
deletes for those types of objects in Datastore are not relevant in SQL.

* Responses to code review

- changing comments / method names
- using ModelUtils
This commit is contained in:
gbrodman 2020-11-24 14:33:06 -05:00 committed by GitHub
parent 3afcc0dcb4
commit 3b1c198c11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 277 additions and 312 deletions

View file

@ -29,8 +29,8 @@ import google.registry.model.registrar.Registrar;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestExtension;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.DatabaseHelper;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectExtension;
import java.io.Serializable;

View file

@ -16,10 +16,16 @@ package google.registry.schema.replay;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Embed;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.ModelUtils;
import google.registry.model.common.GaeUserIdConverter;
import google.registry.persistence.VKey;
import google.registry.testing.DatastoreEntityExtension;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
@ -28,13 +34,18 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/**
* Test to verify classes implement {@link SqlEntity} and {@link DatastoreEntity} when they should.
*/
public class EntityTest {
@RegisterExtension
final DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
private static final ImmutableSet<Class<?>> NON_CONVERTED_CLASSES =
ImmutableSet.of(GaeUserIdConverter.class);
@ -59,6 +70,47 @@ public class EntityTest {
}
}
@Test
void testDatastoreEntityVKeyCreation() {
// For replication, we need to be able to convert from Key -> VKey for the relevant classes.
// This means that the relevant classes must have non-composite Objectify keys or must have a
// createVKey method
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) {
ImmutableSet<Class<?>> datastoreEntityClasses =
getClasses(scanResult.getClassesImplementing(DatastoreEntity.class.getName()));
// some classes aren't converted so they aren't relevant
ImmutableSet<Class<?>> vkeyConversionNecessaryClasses =
datastoreEntityClasses.stream()
.filter(clazz -> !DatastoreOnlyEntity.class.isAssignableFrom(clazz))
.filter(clazz -> !NonReplicatedEntity.class.isAssignableFrom(clazz))
.collect(toImmutableSet());
ImmutableSet.Builder<Class<?>> failedClasses = new ImmutableSet.Builder<>();
for (Class<?> clazz : vkeyConversionNecessaryClasses) {
if (hasKeyWithParent(clazz)) {
try {
Method createVKeyMethod = clazz.getMethod("createVKey", Key.class);
if (!createVKeyMethod.getReturnType().equals(VKey.class)) {
failedClasses.add(clazz);
}
} catch (NoSuchMethodException e) {
failedClasses.add(clazz);
}
}
}
assertWithMessage(
"Some DatastoreEntity classes with parents were missing createVKey methods: ")
.that(failedClasses.build())
.isEmpty();
}
}
private boolean hasKeyWithParent(Class<?> clazz) {
return ModelUtils.getAllFields(clazz).values().stream()
.anyMatch(field -> field.getAnnotation(Parent.class) != null);
}
private ImmutableSet<String> getAllClassesWithAnnotation(
ScanResult scanResult, String annotation) {
ImmutableSet.Builder<String> result = new ImmutableSet.Builder<>();
@ -70,17 +122,20 @@ public class EntityTest {
return result.build();
}
private ImmutableSet<String> getClassNames(ClassInfoList classInfoList) {
private ImmutableSet<Class<?>> getClasses(ClassInfoList classInfoList) {
return classInfoList.stream()
.filter(ClassInfo::isStandardClass)
.map(ClassInfo::loadClass)
.filter(clazz -> !clazz.isAnnotationPresent(EntityForTesting.class))
.filter(clazz -> !clazz.isAnnotationPresent(Embed.class))
.filter(clazz -> !NON_CONVERTED_CLASSES.contains(clazz))
.map(Class::getName)
.collect(toImmutableSet());
}
private ImmutableSet<String> getClassNames(ClassInfoList classInfoList) {
return getClasses(classInfoList).stream().map(Class::getName).collect(toImmutableSet());
}
/** Entities that are solely used for testing, to avoid scanning them in {@link EntityTest}. */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)