Refactor LevelDbFileBuilder to accept DS Entity (#599)

* Refactor LevelDbFileBuilder to accept DS Entity

Builder now can directly work with Datastore Entity objects.
No need to wrap data in ComparableEntity.
This commit is contained in:
Weimin Yu 2020-05-28 13:38:00 -04:00 committed by GitHub
parent 26fb5388a4
commit 2b794347e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 107 deletions

View file

@ -19,7 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.Resources;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.tools.EntityWrapper.Property;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@ -71,30 +71,38 @@ public class CompareDbBackupsTest {
// Create two directories corresponding to data dumps.
File dump1 = tempFs.newFolder("dump1");
LevelDbFileBuilder builder = new LevelDbFileBuilder(new File(dump1, "output-data1"));
builder.addEntityProto(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntityProto(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L))
.getEntity());
builder.addEntity(
EntityWrapper.from(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L))
.getEntity());
builder.build();
File dump2 = tempFs.newFolder("dump2");
builder = new LevelDbFileBuilder(new File(dump2, "output-data2"));
builder.addEntityProto(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L));
builder.addEntityProto(
BASE_ID + 2,
Property.create("blutzy", 100L),
Property.create("fishey", 200L),
Property.create("strutz", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L))
.getEntity());
builder.addEntity(
EntityWrapper.from(
BASE_ID + 2,
Property.create("blutzy", 100L),
Property.create("fishey", 200L),
Property.create("strutz", 300L))
.getEntity());
builder.build();
CompareDbBackups.main(new String[] {dump1.getCanonicalPath(), dump2.getCanonicalPath()});

View file

@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class ComparableEntityTest {
public final class EntityWrapperTest {
private static final String TEST_ENTITY_KIND = "TestEntity";
private static final int ARBITRARY_KEY_ID = 1001;
@ -63,13 +63,13 @@ public final class ComparableEntityTest {
Entity e2 = EntityTranslator.createFromPb(proto2);
// Ensure that we have a normalized representation.
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(ce1).isEqualTo(ce2);
assertThat(ce1.hashCode()).isEqualTo(ce2.hashCode());
// Ensure that the original entity is equal.
assertThat(new ComparableEntity(entity)).isEqualTo(ce1);
assertThat(new EntityWrapper(entity)).isEqualTo(ce1);
}
@Test
@ -90,8 +90,8 @@ public final class ComparableEntityTest {
Entity e1 = EntityTranslator.createFromPb(proto1);
Entity e2 = EntityTranslator.createFromPb(proto2);
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(e1).isEqualTo(e2); // The keys should still be the same.
assertThat(ce1).isNotEqualTo(ce2);
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
@ -108,15 +108,15 @@ public final class ComparableEntityTest {
Entity e1 = EntityTranslator.createFromPb(proto1);
Entity e2 = EntityTranslator.createFromPb(proto2);
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(ce1).isNotEqualTo(ce2);
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
}
@Test
public void testComparisonAgainstNonComparableEntities() {
ComparableEntity ce = new ComparableEntity(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
EntityWrapper ce = new EntityWrapper(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
// Note: this has to be "isNotEqualTo()" and not isNotNull() because we want to test the
// equals() method and isNotNull() just checks for "ce != null".
assertThat(ce).isNotEqualTo(null);

View file

@ -19,7 +19,6 @@ import static google.registry.tools.LevelDbLogReader.HEADER_SIZE;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.auto.value.AutoValue;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.tools.LevelDbLogReader.ChunkType;
import java.io.File;
@ -28,30 +27,19 @@ import java.io.FileOutputStream;
import java.io.IOException;
/** Utility class for building a leveldb logfile. */
final class LevelDbFileBuilder {
private static final String TEST_ENTITY_KIND = "TestEntity";
public final class LevelDbFileBuilder {
private final FileOutputStream out;
private byte[] currentBlock = new byte[BLOCK_SIZE];
// Write position in the current block.
private int currentPos = 0;
LevelDbFileBuilder(File file) throws FileNotFoundException {
public LevelDbFileBuilder(File file) throws FileNotFoundException {
out = new FileOutputStream(file);
}
/**
* Adds a record containing a new entity protobuf to the file.
*
* <p>Returns the ComparableEntity object rather than "this" so that we can check for the presence
* of the entity in the result set.
*/
ComparableEntity addEntityProto(int id, Property... properties) throws IOException {
Entity entity = new Entity(TEST_ENTITY_KIND, id);
for (Property prop : properties) {
entity.setProperty(prop.name(), prop.value());
}
/** Adds an {@link Entity Datastore Entity object} to the leveldb log file. */
LevelDbFileBuilder addEntity(Entity entity) throws IOException {
EntityProto proto = EntityTranslator.convertToPb(entity);
byte[] protoBytes = proto.toByteArray();
if (protoBytes.length > BLOCK_SIZE - (currentPos + HEADER_SIZE)) {
@ -61,7 +49,7 @@ final class LevelDbFileBuilder {
}
currentPos = LevelDbUtil.addRecord(currentBlock, currentPos, ChunkType.FULL, protoBytes);
return new ComparableEntity(entity);
return this;
}
/** Writes all remaining data and closes the block. */
@ -69,15 +57,4 @@ final class LevelDbFileBuilder {
out.write(currentBlock);
out.close();
}
@AutoValue
abstract static class Property {
static Property create(String name, Object value) {
return new AutoValue_LevelDbFileBuilder_Property(name, value);
}
abstract String name();
abstract Object value();
}
}

View file

@ -15,13 +15,17 @@
package google.registry.tools;
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 com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.common.collect.ImmutableList;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.model.contact.ContactResource;
import google.registry.testing.AppEngineRule;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.testing.DatastoreHelper;
import google.registry.tools.EntityWrapper.Property;
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
@ -45,19 +49,18 @@ public class LevelDbFileBuilderTest {
File subdir = tempFs.newFolder("folder");
File logFile = new File(subdir, "testfile");
LevelDbFileBuilder builder = new LevelDbFileBuilder(logFile);
ComparableEntity entity =
builder.addEntityProto(
EntityWrapper entity =
EntityWrapper.from(
BASE_ID, Property.create("first", 100L), Property.create("second", 200L));
builder.addEntity(entity.getEntity());
builder.build();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1);
// Reconstitute an entity, make sure that what we've got is the same as what we started with.
EntityProto proto = new EntityProto();
proto.parseFrom(records.get(0));
Entity materializedEntity = EntityTranslator.createFromPb(proto);
assertThat(new ComparableEntity(materializedEntity)).isEqualTo(entity);
Entity materializedEntity = rawRecordToEntity(records.get(0));
assertThat(new EntityWrapper(materializedEntity)).isEqualTo(entity);
}
@Test
@ -68,25 +71,50 @@ public class LevelDbFileBuilderTest {
// Generate enough records to cross a block boundary. These records end up being around 80
// bytes, so 1000 works.
ImmutableList.Builder<ComparableEntity> originalEntitiesBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<EntityWrapper> originalEntitiesBuilder = new ImmutableList.Builder<>();
for (int i = 0; i < 1000; ++i) {
ComparableEntity entity =
builder.addEntityProto(
EntityWrapper entity =
EntityWrapper.from(
BASE_ID + i, Property.create("first", 100L), Property.create("second", 200L));
builder.addEntity(entity.getEntity());
originalEntitiesBuilder.add(entity);
}
builder.build();
ImmutableList<ComparableEntity> originalEntities = originalEntitiesBuilder.build();
ImmutableList<EntityWrapper> originalEntities = originalEntitiesBuilder.build();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1000);
int index = 0;
for (byte[] record : records) {
EntityProto proto = new EntityProto();
proto.parseFrom(record);
Entity materializedEntity = EntityTranslator.createFromPb(proto);
assertThat(new ComparableEntity(materializedEntity)).isEqualTo(originalEntities.get(index));
Entity materializedEntity = rawRecordToEntity(record);
assertThat(new EntityWrapper(materializedEntity)).isEqualTo(originalEntities.get(index));
++index;
}
}
@Test
public void testOfyEntityWrite() throws Exception {
File subdir = tempFs.newFolder("folder");
File logFile = new File(subdir, "testfile");
LevelDbFileBuilder builder = new LevelDbFileBuilder(logFile);
ContactResource contact = DatastoreHelper.newContactResource("contact");
builder.addEntity(tm().transact(() -> ofy().save().toEntity(contact)));
builder.build();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1);
ContactResource ofyEntity = rawRecordToOfyEntity(records.get(0), ContactResource.class);
assertThat(ofyEntity.getContactId()).isEqualTo(contact.getContactId());
}
private static Entity rawRecordToEntity(byte[] record) {
EntityProto proto = new EntityProto();
proto.parseFrom(record);
return EntityTranslator.createFromPb(proto);
}
private static <T> T rawRecordToOfyEntity(byte[] record, Class<T> expectedType) {
return expectedType.cast(ofy().load().fromEntity(rawRecordToEntity(record)));
}
}

View file

@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import google.registry.testing.AppEngineRule;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.tools.EntityWrapper.Property;
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
@ -44,39 +44,44 @@ public class RecordAccumulatorTest {
// Note that we need to specify property values as "Long" for property comparisons to work
// correctly because that's how they are deserialized from protos.
ComparableEntity e1 =
builder.addEntityProto(
EntityWrapper e1 =
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
ComparableEntity e2 =
builder.addEntityProto(
builder.addEntity(e1.getEntity());
EntityWrapper e2 =
EntityWrapper.from(
BASE_ID + 1,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntity(e2.getEntity());
builder.build();
builder = new LevelDbFileBuilder(new File(subdir, "data2"));
// Duplicate of the record in the other file.
builder.addEntityProto(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L))
.getEntity());
ComparableEntity e3 =
builder.addEntityProto(
EntityWrapper e3 =
EntityWrapper.from(
BASE_ID + 2,
Property.create("moxy", 100L),
Property.create("fruvis", 200L),
Property.create("cortex", 300L));
builder.addEntity(e3.getEntity());
builder.build();
ImmutableSet<ComparableEntity> entities =
RecordAccumulator.readDirectory(subdir, any -> true).getComparableEntitySet();
ImmutableSet<EntityWrapper> entities =
RecordAccumulator.readDirectory(subdir, any -> true).getEntityWrapperSet();
assertThat(entities).containsExactly(e1, e2, e3);
}
}