diff --git a/java/com/google/domain/registry/model/ofy/CommitLogMutation.java b/java/com/google/domain/registry/model/ofy/CommitLogMutation.java index ec4289078..7b249e0d7 100644 --- a/java/com/google/domain/registry/model/ofy/CommitLogMutation.java +++ b/java/com/google/domain/registry/model/ofy/CommitLogMutation.java @@ -16,6 +16,7 @@ package com.google.domain.registry.model.ofy; import static com.google.appengine.api.datastore.EntityTranslator.convertToPb; import static com.google.appengine.api.datastore.EntityTranslator.createFromPbBytes; +import static com.google.common.base.Preconditions.checkNotNull; import static com.google.domain.registry.model.ofy.ObjectifyService.ofy; import com.google.appengine.api.datastore.KeyFactory; @@ -79,7 +80,7 @@ public class CommitLogMutation extends ImmutableObject { Key parent, com.google.appengine.api.datastore.Entity rawEntity) { CommitLogMutation instance = new CommitLogMutation(); - instance.parent = parent; + instance.parent = checkNotNull(parent); // Creates a web-safe key string. instance.entityKey = KeyFactory.keyToString(rawEntity.getKey()); instance.entityProtoBytes = convertToPb(rawEntity).toByteArray(); diff --git a/java/com/google/domain/registry/tools/RegistryTool.java b/java/com/google/domain/registry/tools/RegistryTool.java index 5e8da3b6c..d8a41df3a 100644 --- a/java/com/google/domain/registry/tools/RegistryTool.java +++ b/java/com/google/domain/registry/tools/RegistryTool.java @@ -62,6 +62,7 @@ public final class RegistryTool { .put("load_snapshot", LoadSnapshotCommand.class) .put("make_billing_tables", MakeBillingTablesCommand.class) .put("pending_escrow", PendingEscrowCommand.class) + .put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class) .put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class) .put("update_application_status", UpdateApplicationStatusCommand.class) .put("update_claims_notice", UpdateClaimsNoticeCommand.class) diff --git a/java/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommand.java b/java/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommand.java new file mode 100644 index 000000000..ed07cde0e --- /dev/null +++ b/java/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommand.java @@ -0,0 +1,55 @@ +// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools; + +import static com.google.common.collect.Iterables.concat; +import static com.google.domain.registry.model.common.EntityGroupRoot.getCrossTldKey; +import static com.google.domain.registry.model.ofy.ObjectifyService.ofy; + +import com.google.domain.registry.model.ImmutableObject; +import com.google.domain.registry.model.registrar.Registrar; +import com.google.domain.registry.model.registrar.RegistrarContact; +import com.google.domain.registry.model.registry.Registry; +import com.google.domain.registry.tools.Command.RemoteApiCommand; + +import com.beust.jcommander.Parameters; +import com.googlecode.objectify.Key; +import com.googlecode.objectify.VoidWork; + +/** + * Command to re-save all environment entities to ensure that they have valid commit logs. + * + *

The entities that are re-saved are those of type {@link Registry}, {@link Registrar}, and + * {@link RegistrarContact}. + */ +@Parameters(commandDescription = "Re-save all environment entities.") +final class ResaveEnvironmentEntitiesCommand implements RemoteApiCommand { + + @Override + public void run() throws Exception { + Iterable> keys = concat( + ofy().load().type(Registrar.class).ancestor(getCrossTldKey()).keys(), + ofy().load().type(Registry.class).ancestor(getCrossTldKey()).keys(), + ofy().load().type(RegistrarContact.class).ancestor(getCrossTldKey()).keys()); + for (final Key key : keys) { + ofy().transact(new VoidWork() { + @Override + public void vrun() { + ofy().save().entity(ofy().load().key(key).now()); + }}); + System.out.printf("Re-saved entity %s\n", key); + } + } +} diff --git a/javatests/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommandTest.java b/javatests/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommandTest.java new file mode 100644 index 000000000..23e27fae7 --- /dev/null +++ b/javatests/com/google/domain/registry/tools/ResaveEnvironmentEntitiesCommandTest.java @@ -0,0 +1,87 @@ +// Copyright 2016 The Domain Registry 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 com.google.domain.registry.tools; + +import static com.google.common.collect.Iterables.getOnlyElement; +import static com.google.common.collect.Iterables.transform; +import static com.google.common.truth.Truth.assertThat; +import static com.google.domain.registry.model.ofy.ObjectifyService.ofy; +import static com.google.domain.registry.testing.DatastoreHelper.createTld; + +import com.google.common.base.Function; +import com.google.domain.registry.model.ImmutableObject; +import com.google.domain.registry.model.ofy.CommitLogManifest; +import com.google.domain.registry.model.ofy.CommitLogMutation; +import com.google.domain.registry.model.registrar.Registrar; +import com.google.domain.registry.model.registrar.RegistrarContact; +import com.google.domain.registry.model.registry.Registry; + +import org.junit.Test; + +/** Unit tests for {@link ResaveEnvironmentEntitiesCommand}. */ +public class ResaveEnvironmentEntitiesCommandTest + extends CommandTestCase { + + @Test + public void testSuccess_noop() throws Exception { + // Get rid of all the entities that this command runs on so that it does nothing. + deleteEntitiesOfTypes( + Registry.class, + Registrar.class, + RegistrarContact.class, + CommitLogManifest.class, + CommitLogMutation.class); + runCommand(); + assertThat(ofy().load().type(CommitLogManifest.class).keys()).isEmpty(); + assertThat(ofy().load().type(CommitLogMutation.class).keys()).isEmpty(); + } + + @Test + public void testSuccess_createsCommitLogs() throws Exception { + createTld("tld"); + deleteEntitiesOfTypes(CommitLogManifest.class, CommitLogMutation.class); + assertThat(ofy().load().type(CommitLogManifest.class).keys()).isEmpty(); + assertThat(ofy().load().type(CommitLogMutation.class).keys()).isEmpty(); + runCommand(); + + // There are five entities that have been re-saved at this point (each in a separate + // transaction), so expect five manifests and five mutations. + assertThat(ofy().load().type(CommitLogManifest.class).keys()).hasSize(5); + Iterable savedEntities = + transform( + ofy().load().type(CommitLogMutation.class).list(), + new Function() { + @Override + public ImmutableObject apply(CommitLogMutation mutation) { + return ofy().load().fromEntity(mutation.getEntity()); + } + }); + assertThat(savedEntities) + .containsExactly( + // The Registrars and RegistrarContacts are created by AppEngineRule. + Registrar.loadByClientId("TheRegistrar"), + Registrar.loadByClientId("NewRegistrar"), + Registry.get("tld"), + getOnlyElement(Registrar.loadByClientId("TheRegistrar").getContacts()), + getOnlyElement(Registrar.loadByClientId("NewRegistrar").getContacts())); + } + + @SafeVarargs + private static void deleteEntitiesOfTypes(Class... types) { + for (Class type : types) { + ofy().deleteWithoutBackup().keys(ofy().load().type(type).keys()).now(); + } + } +}