diff --git a/java/google/registry/tools/RegistryTool.java b/java/google/registry/tools/RegistryTool.java index 83ee8bbba..27d0f4c4c 100644 --- a/java/google/registry/tools/RegistryTool.java +++ b/java/google/registry/tools/RegistryTool.java @@ -15,7 +15,6 @@ package google.registry.tools; import com.google.common.collect.ImmutableMap; -import google.registry.tools.javascrap.LoadAndResaveCommand; import google.registry.tools.javascrap.RemoveIpAddressCommand; /** Container class to create and run remote commands against a datastore instance. */ @@ -89,7 +88,6 @@ public final class RegistryTool { .put("list_registrars", ListRegistrarsCommand.class) .put("list_reserved_lists", ListReservedListsCommand.class) .put("list_tlds", ListTldsCommand.class) - .put("load_and_resave", LoadAndResaveCommand.class) .put("load_snapshot", LoadSnapshotCommand.class) .put("make_billing_tables", MakeBillingTablesCommand.class) .put("pending_escrow", PendingEscrowCommand.class) @@ -97,7 +95,9 @@ public final class RegistryTool { .put("registrar_activity_report", RegistrarActivityReportCommand.class) .put("registrar_contact", RegistrarContactCommand.class) .put("remove_ip_address", RemoveIpAddressCommand.class) + .put("resave_entities", ResaveEntitiesCommand.class) .put("resave_environment_entities", ResaveEnvironmentEntitiesCommand.class) + .put("resave_epp_resource", ResaveEppResourceCommand.class) .put("send_escrow_report_to_icann", SendEscrowReportToIcannCommand.class) .put("setup_ote", SetupOteCommand.class) .put("uniform_rapid_suspension", UniformRapidSuspensionCommand.class) diff --git a/java/google/registry/tools/ResaveEntitiesCommand.java b/java/google/registry/tools/ResaveEntitiesCommand.java new file mode 100644 index 000000000..1dfc9c4f0 --- /dev/null +++ b/java/google/registry/tools/ResaveEntitiesCommand.java @@ -0,0 +1,53 @@ +// Copyright 2016 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.ofy.ObjectifyService.ofy; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; +import com.googlecode.objectify.Key; +import google.registry.model.ImmutableObject; +import java.util.List; + +/** + * A command to load and resave an entity by websafe key. + * + *

This triggers @OnSave changes. If the entity was directly edited in the datastore viewer, this + * can be used to make sure that the commit logs reflect the new state. + */ +@Parameters( + separators = " =", + commandDescription = "Load and resave entities by websafe key") +public final class ResaveEntitiesCommand extends MutatingCommand { + + /** The number of resaves to do in a single transaction. */ + private static final int BATCH_SIZE = 10; + + @Parameter(description = "Websafe keys", required = true) + List mainParameters; + + @Override + protected void init() throws Exception { + for (List batch : partition(mainParameters, BATCH_SIZE)) { + for (String websafeKey : batch) { + ImmutableObject entity = ofy().load().key(Key.create(websafeKey)).now(); + stageEntityChange(entity, entity); + } + flushTransaction(); + } + } +} diff --git a/java/google/registry/tools/javascrap/LoadAndResaveCommand.java b/java/google/registry/tools/ResaveEppResourceCommand.java similarity index 86% rename from java/google/registry/tools/javascrap/LoadAndResaveCommand.java rename to java/google/registry/tools/ResaveEppResourceCommand.java index 0040da9e0..b5562130d 100644 --- a/java/google/registry/tools/javascrap/LoadAndResaveCommand.java +++ b/java/google/registry/tools/ResaveEppResourceCommand.java @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package google.registry.tools.javascrap; +package google.registry.tools; import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey; import static google.registry.model.ofy.ObjectifyService.ofy; @@ -27,14 +27,18 @@ import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainApplication; import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; -import google.registry.tools.MutatingCommand; import org.joda.time.DateTime; -/** A command to load and resave an entity, which triggers @OnSave changes. */ +/** + * A command to load and resave an {@link EppResource} by foreign key. + * + *

This triggers @OnSave changes. If the entity was directly edited in the datastore viewer, this + * can be used to make sure that the commit logs reflect the new state. + */ @Parameters( separators = " =", - commandDescription = "Load and resave an object, to trigger @OnSave changes") -public final class LoadAndResaveCommand extends MutatingCommand { + commandDescription = "Load and resave EPP resources by foreign key") +public final class ResaveEppResourceCommand extends MutatingCommand { private enum ResourceType { CONTACT, HOST, DOMAIN, APPLICATION } diff --git a/javatests/google/registry/tools/ResaveEntitiesCommandTest.java b/javatests/google/registry/tools/ResaveEntitiesCommandTest.java new file mode 100644 index 000000000..31d972b92 --- /dev/null +++ b/javatests/google/registry/tools/ResaveEntitiesCommandTest.java @@ -0,0 +1,67 @@ +// Copyright 2016 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.Iterables.transform; +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.persistActiveContact; + +import com.google.appengine.api.datastore.KeyFactory; +import com.google.common.base.Function; +import com.googlecode.objectify.Key; +import google.registry.model.ImmutableObject; +import google.registry.model.contact.ContactResource; +import google.registry.model.ofy.CommitLogManifest; +import google.registry.model.ofy.CommitLogMutation; +import org.junit.Test; + +/** Unit tests for {@link ResaveEntitiesCommand}. */ +public class ResaveEntitiesCommandTest extends CommandTestCase { + + @Test + public void testSuccess_createsCommitLogs() throws Exception { + ContactResource contact1 = persistActiveContact("contact1"); + ContactResource contact2 = persistActiveContact("contact2"); + deleteEntitiesOfTypes(CommitLogManifest.class, CommitLogMutation.class); + assertThat(ofy().load().type(CommitLogManifest.class).keys()).isEmpty(); + assertThat(ofy().load().type(CommitLogMutation.class).keys()).isEmpty(); + runCommandForced( + KeyFactory.keyToString(Key.create(contact1).getRaw()), + KeyFactory.keyToString(Key.create(contact2).getRaw())); + + assertThat(ofy().load().type(CommitLogManifest.class).keys()).hasSize(1); + Iterable savedEntities = + transform( + ofy().load().type(CommitLogMutation.class).list(), + new Function() { + @Override + public ImmutableObject apply(CommitLogMutation mutation) { + return ofy().load().fromEntity(mutation.getEntity()); + } + }); + // Reload the contacts before asserting, since their update times will have changed. + ofy().clearSessionCache(); + assertThat(savedEntities) + .containsExactlyElementsIn(ofy().load().entities(contact1, contact2).values()); + } + + @SafeVarargs + private static void deleteEntitiesOfTypes(Class... types) { + for (Class type : types) { + ofy().deleteWithoutBackup().keys(ofy().load().type(type).keys()).now(); + } + } +} diff --git a/javatests/google/registry/tools/ResaveEppResourcesCommandTest.java b/javatests/google/registry/tools/ResaveEppResourcesCommandTest.java new file mode 100644 index 000000000..f4c26431c --- /dev/null +++ b/javatests/google/registry/tools/ResaveEppResourcesCommandTest.java @@ -0,0 +1,53 @@ +// Copyright 2016 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.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.persistActiveContact; + +import google.registry.model.ImmutableObject; +import google.registry.model.contact.ContactResource; +import google.registry.model.ofy.CommitLogManifest; +import google.registry.model.ofy.CommitLogMutation; +import org.junit.Test; + +/** Unit tests for {@link ResaveEppResourceCommand}. */ +public class ResaveEppResourcesCommandTest extends CommandTestCase { + + @Test + public void testSuccess_createsCommitLogs() throws Exception { + ContactResource contact = persistActiveContact("contact"); + deleteEntitiesOfTypes(CommitLogManifest.class, CommitLogMutation.class); + assertThat(ofy().load().type(CommitLogManifest.class).keys()).isEmpty(); + assertThat(ofy().load().type(CommitLogMutation.class).keys()).isEmpty(); + runCommandForced("--type=CONTACT", "--id=contact"); + + assertThat(ofy().load().type(CommitLogManifest.class).keys()).hasSize(1); + assertThat(ofy().load().type(CommitLogMutation.class).keys()).hasSize(1); + CommitLogMutation mutation = ofy().load().type(CommitLogMutation.class).first().now(); + // Reload the contact before asserting, since its update time will have changed. + ofy().clearSessionCache(); + assertThat(ofy().load().fromEntity(mutation.getEntity())) + .isEqualTo(ofy().load().entity(contact).now()); + } + + @SafeVarargs + private static void deleteEntitiesOfTypes(Class... types) { + for (Class type : types) { + ofy().deleteWithoutBackup().keys(ofy().load().type(type).keys()).now(); + } + } +}