diff --git a/java/google/registry/tools/RegistryTool.java b/java/google/registry/tools/RegistryTool.java index f03647bd1..7dc0557b9 100644 --- a/java/google/registry/tools/RegistryTool.java +++ b/java/google/registry/tools/RegistryTool.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableSortedMap; import google.registry.tools.javascrap.LoadAndResaveCommand; import google.registry.tools.javascrap.RemoveIpAddressCommand; +import google.registry.tools.javascrap.VerifyDualWrittenCursorsCommand; /** Container class to create and run remote commands against a datastore instance. */ public final class RegistryTool { @@ -75,6 +76,7 @@ public final class RegistryTool { .put("update_tld", UpdateTldCommand.class) .put("upload_claims_list", UploadClaimsListCommand.class) .put("validate_escrow_deposit", ValidateEscrowDepositCommand.class) + .put("verify_dual_written_cursors", VerifyDualWrittenCursorsCommand.class) .build(); public static void main(String[] args) throws Exception { diff --git a/java/google/registry/tools/javascrap/VerifyDualWrittenCursorsCommand.java b/java/google/registry/tools/javascrap/VerifyDualWrittenCursorsCommand.java new file mode 100644 index 000000000..f7ca867fb --- /dev/null +++ b/java/google/registry/tools/javascrap/VerifyDualWrittenCursorsCommand.java @@ -0,0 +1,113 @@ +// 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 google.registry.tools.javascrap; + +import static google.registry.model.ofy.ObjectifyService.ofy; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import com.beust.jcommander.Parameters; +import com.googlecode.objectify.Key; + +import google.registry.model.common.Cursor; +import google.registry.model.common.EntityGroupRoot; +import google.registry.model.registry.Registry; +import google.registry.model.registry.RegistryCursor; +import google.registry.tools.Command.RemoteApiCommand; + +import org.joda.time.DateTime; + +import java.util.HashMap; +import java.util.Map.Entry; + +/** + * Scrap command that loads all legacy {@link RegistryCursor} entities and new {@link Cursor} + * entities, and reports any mismatches (after filtering out new {@code RECURRING_BILLING} cursors). + */ +@Parameters(separators = " =", commandDescription = "Verify dual-written cursors.") +public class VerifyDualWrittenCursorsCommand implements RemoteApiCommand { + + @Override + public void run() throws Exception { + + Iterable cursors = + ofy().load().type(Cursor.class).ancestor(EntityGroupRoot.getCrossTldKey()); + Iterable registryCursors = + ofy().load().type(RegistryCursor.class).ancestor(EntityGroupRoot.getCrossTldKey()); + + HashMap cursorMap = Maps.newHashMap(); + HashMap registryCursorMap = Maps.newHashMap(); + + // Build map of key name to new cursor. + for (Cursor c : cursors) { + String keyName = Key.create(c).getName(); + if (!keyName.substring(keyName.indexOf('_') + 1).endsWith( + Cursor.CursorType.RECURRING_BILLING.toString())) { + cursorMap.put(keyName, c); + } + } + + // Build map of key name to old RegistryCursor. + for (RegistryCursor rc : registryCursors) { + Key key = Key.create(rc); + registryCursorMap.put( + String.format( + "%s_%s", + key.getParent().getString(), + key.getName()), + rc); + } + + // Iterate through new cursor map, match to old RegistryCursor and check timestamp if a match + // exists, or report missing RegistryCursor otherwise. + for (Entry entry : cursorMap.entrySet()) { + String key = entry.getKey(); + String registryKey = key.substring(0, key.indexOf('_')); + String cursorType = key.substring(key.indexOf('_') + 1); + Registry registry = Registry.get(Key.create(registryKey).getName()); + RegistryCursor registryCursor = registryCursorMap.get(entry.getKey()); + if (registryCursor == null) { + System.out.println( + String.format( + "RegistryCursor missing: TLD %s, cursorType %s", + registry.getTldStr(), + cursorType)); + } else { + DateTime cursorTime = entry.getValue().getCursorTime(); + DateTime matchingCursorTime = + RegistryCursor.load(registry, RegistryCursor.CursorType.valueOf(cursorType)).get(); + if (!cursorTime.equals(matchingCursorTime)) { + System.out.println( + String.format( + "Mismatch for cursor for type %s and tld %s (old cursor: %s, new cursor: %s)", + cursorType, + registry.getTldStr(), + cursorTime, + matchingCursorTime)); + } + } + } + + // Perform set differences by matching new-style cursor names, report any discrepancies. + for (String s : Sets.difference(registryCursorMap.keySet(), cursorMap.keySet())) { + String registryKey = s.substring(0, s.indexOf('_')); + String cursorType = s.substring(s.indexOf('_') + 1); + Registry registry = Registry.get(Key.create(registryKey).getName()); + System.out.println( + String.format("Cursor missing: TLD %s, cursorType %s", registry.getTldStr(), cursorType)); + } + } +}