diff --git a/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml b/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml index 26f3416dc..481129a1a 100644 --- a/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml +++ b/java/google/registry/env/common/default/WEB-INF/datastore-indexes.xml @@ -69,10 +69,14 @@ - + + + + + diff --git a/java/google/registry/tools/VerifyOteCommand.java b/java/google/registry/tools/VerifyOteCommand.java index 1129c2e7e..5dadfec63 100644 --- a/java/google/registry/tools/VerifyOteCommand.java +++ b/java/google/registry/tools/VerifyOteCommand.java @@ -36,7 +36,12 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -/** Command to verify that a registrar has passed OT&E. */ +/** + * Command to verify that a registrar has passed OT&E. + * + *

Outputted stats may be truncated at the point where all tests passed to avoid unnecessarily + * loading lots of data. + */ @Parameters( separators = " =", commandDescription = "Verify passage of OT&E for specified (or all) registrars") diff --git a/java/google/registry/tools/server/VerifyOteAction.java b/java/google/registry/tools/server/VerifyOteAction.java index a1eb47aa7..4654b4e9e 100644 --- a/java/google/registry/tools/server/VerifyOteAction.java +++ b/java/google/registry/tools/server/VerifyOteAction.java @@ -28,6 +28,8 @@ import com.google.common.base.Predicates; import com.google.common.collect.HashMultiset; import com.google.common.collect.ImmutableList; import com.google.common.collect.Multiset; +import com.googlecode.objectify.Key; +import com.googlecode.objectify.cmd.Query; import google.registry.flows.EppException; import google.registry.model.domain.DomainCommand; import google.registry.model.domain.fee.FeeCreateCommandExtension; @@ -44,12 +46,14 @@ import google.registry.request.JsonActionRunner; import google.registry.request.JsonActionRunner.JsonAction; import google.registry.request.auth.Auth; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.inject.Inject; @@ -242,18 +246,30 @@ public class VerifyOteAction implements Runnable, JsonAction { /** * Records data in the passed historyEntryStats object on what actions have been performed by * the four numbered OT&E variants of the registrar name. + * + *

Stops when it notices that all tests have passed. */ HistoryEntryStats recordRegistrarHistory(String registrarName) { - ImmutableList.Builder clientIds = new ImmutableList.Builder<>(); - for (int i = 1; i <= 4; i++) { - clientIds.add(String.format("%s-%d", registrarName, i)); - } - for (HistoryEntry historyEntry : - ofy().load().type(HistoryEntry.class).filter("clientId in", clientIds.build()).list()) { + ImmutableList clientIds = + IntStream.rangeClosed(1, 4) + .mapToObj(i -> String.format("%s-%d", registrarName, i)) + .collect(toImmutableList()); + + Query query = + ofy() + .load() + .type(HistoryEntry.class) + .filter("clientId in", clientIds) + .order("modificationTime"); + for (HistoryEntry historyEntry : query) { try { record(historyEntry); } catch (EppException e) { - throw new RuntimeException(e); + throw new RuntimeException("Couldn't parse history entry " + Key.create(historyEntry), e); + } + // Break out early if all tests were passed. + if (wereAllTestsPassed()) { + break; } } return this; @@ -264,18 +280,19 @@ public class VerifyOteAction implements Runnable, JsonAction { byte[] xmlBytes = historyEntry.getXmlBytes(); // xmlBytes can be null on contact create and update for safe-harbor compliance. final Optional eppInput = - (xmlBytes == null) - ? Optional.empty() - : Optional.of(unmarshal(EppInput.class, xmlBytes)); + (xmlBytes == null) ? Optional.empty() : Optional.of(unmarshal(EppInput.class, xmlBytes)); if (!statCounts.addAll( - EnumSet.allOf(StatType.class) - .stream() + EnumSet.allOf(StatType.class).stream() .filter(statType -> statType.matches(historyEntry.getType(), eppInput)) .collect(toImmutableList()))) { statCounts.add(StatType.UNCLASSIFIED_FLOWS); } } + boolean wereAllTestsPassed() { + return Arrays.stream(StatType.values()).allMatch(s -> statCounts.count(s) >= s.requirement); + } + /** * Returns a list of failure messages describing any cases where the passed stats fail to meet * the required thresholds, or the empty list if all requirements are met. diff --git a/javatests/google/registry/tools/server/VerifyOteActionTest.java b/javatests/google/registry/tools/server/VerifyOteActionTest.java index 99e59a216..ab905bd39 100644 --- a/javatests/google/registry/tools/server/VerifyOteActionTest.java +++ b/javatests/google/registry/tools/server/VerifyOteActionTest.java @@ -17,9 +17,11 @@ package google.registry.tools.server; import static com.google.common.truth.Truth.assertThat; import static google.registry.testing.DatastoreHelper.deleteResource; import static google.registry.testing.DatastoreHelper.persistResource; +import static google.registry.util.DateTimeUtils.END_OF_TIME; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import google.registry.model.eppcommon.Trid; import google.registry.model.reporting.HistoryEntry; import google.registry.model.reporting.HistoryEntry.Type; import google.registry.testing.AppEngineRule; @@ -40,9 +42,9 @@ public class VerifyOteActionTest { private final VerifyOteAction action = new VerifyOteAction(); - HistoryEntry hostDeleteHistoryEntry; - HistoryEntry domainCreateHistoryEntry; - HistoryEntry domainRestoreHistoryEntry; + private HistoryEntry hostDeleteHistoryEntry; + private HistoryEntry domainCreateHistoryEntry; + private HistoryEntry domainRestoreHistoryEntry; @Before public void init() throws Exception { @@ -137,12 +139,19 @@ public class VerifyOteActionTest { .setType(Type.HOST_DELETE) .setXmlBytes(ToolsTestData.loadBytes("host_delete.xml").read()) .build()); - persistResource( - new HistoryEntry.Builder() - .setClientId("blobio-1") - .setType(Type.HOST_UPDATE) - .setXmlBytes(ToolsTestData.loadBytes("host_update.xml").read()) - .build()); + // Persist 10 host updates for a total of 25 history entries. Since these also sort last by + // modification time, when these cause all tests to pass, only the first will be recorded and + // the rest will be skipped. + for (int i = 0; i < 10; i++) { + persistResource( + new HistoryEntry.Builder() + .setClientId("blobio-1") + .setType(Type.HOST_UPDATE) + .setXmlBytes(ToolsTestData.loadBytes("host_update.xml").read()) + .setTrid(Trid.create(null, String.format("blahtrid-%d", i))) + .setModificationTime(END_OF_TIME) + .build()); + } } @Test @@ -165,7 +174,7 @@ public class VerifyOteActionTest { ImmutableMap.of("summarize", "true", "registrars", ImmutableList.of("blobio"))); assertThat(response) .containsExactly( - "blobio", "# actions: 26 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL"); + "blobio", "# actions: 35 - Reqs: [-.-----.------.-] 13/16 - Overall: FAIL"); } @Test @@ -235,7 +244,7 @@ public class VerifyOteActionTest { + ".*" + "host creates subordinate: 1\n" + "host deletes: 0\n" - + "host updates: 1\n" + + "host updates: 10\n" + ".*" + "Requirements passed: 15/16\n" + "Overall OT&E status: FAIL\n";