Use History Entry type for flows in VerifyOteServlet

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=161855429
This commit is contained in:
bbilbo 2017-07-13 13:31:54 -07:00 committed by Ben McIlwain
parent 4887811fc3
commit 9688638c75
13 changed files with 809 additions and 154 deletions

View file

@ -14,10 +14,10 @@
package google.registry.tools.server;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.toMap;
import static google.registry.flows.EppXmlTransformer.unmarshal;
import static google.registry.flows.picker.FlowPicker.getFlowClass;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
@ -25,6 +25,7 @@ import static google.registry.util.DomainNameUtils.ACE_PREFIX;
import com.google.common.base.Ascii;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.FluentIterable;
@ -32,29 +33,6 @@ import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multiset;
import google.registry.flows.EppException;
import google.registry.flows.Flow;
import google.registry.flows.contact.ContactCreateFlow;
import google.registry.flows.contact.ContactDeleteFlow;
import google.registry.flows.contact.ContactTransferApproveFlow;
import google.registry.flows.contact.ContactTransferCancelFlow;
import google.registry.flows.contact.ContactTransferRejectFlow;
import google.registry.flows.contact.ContactTransferRequestFlow;
import google.registry.flows.contact.ContactUpdateFlow;
import google.registry.flows.domain.DomainApplicationCreateFlow;
import google.registry.flows.domain.DomainApplicationDeleteFlow;
import google.registry.flows.domain.DomainApplicationUpdateFlow;
import google.registry.flows.domain.DomainCreateFlow;
import google.registry.flows.domain.DomainDeleteFlow;
import google.registry.flows.domain.DomainRenewFlow;
import google.registry.flows.domain.DomainRestoreRequestFlow;
import google.registry.flows.domain.DomainTransferApproveFlow;
import google.registry.flows.domain.DomainTransferCancelFlow;
import google.registry.flows.domain.DomainTransferRejectFlow;
import google.registry.flows.domain.DomainTransferRequestFlow;
import google.registry.flows.domain.DomainUpdateFlow;
import google.registry.flows.host.HostCreateFlow;
import google.registry.flows.host.HostDeleteFlow;
import google.registry.flows.host.HostUpdateFlow;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.launch.LaunchCreateExtension;
@ -64,6 +42,7 @@ import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.EppInput.ResourceCommandWrapper;
import google.registry.model.host.HostCommand;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.reporting.HistoryEntry.Type;
import google.registry.request.Action;
import google.registry.request.JsonActionRunner;
import google.registry.request.JsonActionRunner.JsonAction;
@ -95,7 +74,9 @@ public class VerifyOteAction implements Runnable, JsonAction {
public static final String PATH = "/_dr/admin/verifyOte";
@Inject JsonActionRunner jsonActionRunner;
@Inject VerifyOteAction() {}
@Inject
VerifyOteAction() {}
@Override
public void run() {
@ -113,7 +94,8 @@ public class VerifyOteAction implements Runnable, JsonAction {
@Override
public Object apply(@Nonnull String registrar) {
return checkRegistrar(registrar, summarize);
}});
}
});
}
/** Checks whether the provided registrar has passed OT&E and returns relevant information. */
@ -121,138 +103,158 @@ public class VerifyOteAction implements Runnable, JsonAction {
HistoryEntryStats historyEntryStats =
new HistoryEntryStats().recordRegistrarHistory(registrarName);
List<String> failureMessages = historyEntryStats.findFailures();
String passedFraction = String.format(
"%2d/%2d", StatType.NUM_REQUIREMENTS - failureMessages.size(), StatType.NUM_REQUIREMENTS);
String passedFraction =
String.format(
"%2d/%2d",
StatType.NUM_REQUIREMENTS - failureMessages.size(), StatType.NUM_REQUIREMENTS);
String status = failureMessages.isEmpty() ? "PASS" : "FAIL";
return summarize
? String.format(
"Num actions: %4d - Reqs passed: %s - Overall: %s",
historyEntryStats.statCounts.size(),
passedFraction,
status)
historyEntryStats.statCounts.size(), passedFraction, status)
: String.format(
"%s\n%s\nRequirements passed: %s\nOverall OT&E status: %s\n",
historyEntryStats,
Joiner.on('\n').join(failureMessages),
passedFraction,
status);
historyEntryStats, Joiner.on('\n').join(failureMessages), passedFraction, status);
}
private static final Predicate<EppInput> HAS_CLAIMS_NOTICE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && launchCreate.getNotice() != null;
}};
private static final Predicate<EppInput> HAS_FEE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return eppInput.getSingleExtension(FeeCreateCommandExtension.class) != null;
}};
private static final Predicate<EppInput> HAS_SEC_DNS = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return (eppInput.getSingleExtension(SecDnsCreateExtension.class) != null)
|| (eppInput.getSingleExtension(SecDnsUpdateExtension.class) != null);
}};
private static final Predicate<EppInput> IS_SUNRISE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && !isNullOrEmpty(launchCreate.getSignedMarks());
}};
private static final Predicate<EppInput> IS_IDN = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return ((DomainCommand.Create) ((ResourceCommandWrapper)
eppInput.getCommandWrapper().getCommand()).getResourceCommand())
.getFullyQualifiedDomainName().startsWith(ACE_PREFIX);
}};
private static final Predicate<EppInput> IS_SUBORDINATE = new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return !isNullOrEmpty(((HostCommand.Create) ((ResourceCommandWrapper)
eppInput.getCommandWrapper().getCommand()).getResourceCommand())
.getInetAddresses());
}};
private static Predicate<EppInput> isFlow(final Class<? extends Flow> flowClass) {
return new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
try {
return flowClass.equals(getFlowClass(eppInput));
} catch (EppException e) {
throw new RuntimeException(e);
private static final Predicate<EppInput> HAS_CLAIMS_NOTICE =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && launchCreate.getNotice() != null;
}
}};
}
};
private static final Predicate<EppInput> HAS_FEE =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return eppInput.getSingleExtension(FeeCreateCommandExtension.class) != null;
}
};
private static final Predicate<EppInput> HAS_SEC_DNS =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return (eppInput.getSingleExtension(SecDnsCreateExtension.class) != null)
|| (eppInput.getSingleExtension(SecDnsUpdateExtension.class) != null);
}
};
private static final Predicate<EppInput> IS_SUNRISE =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
LaunchCreateExtension launchCreate =
eppInput.getSingleExtension(LaunchCreateExtension.class);
return launchCreate != null && !isNullOrEmpty(launchCreate.getSignedMarks());
}
};
private static final Predicate<EppInput> IS_IDN =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return ((DomainCommand.Create)
((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
.getResourceCommand())
.getFullyQualifiedDomainName()
.startsWith(ACE_PREFIX);
}
};
private static final Predicate<EppInput> IS_SUBORDINATE =
new Predicate<EppInput>() {
@Override
public boolean apply(@Nonnull EppInput eppInput) {
return !isNullOrEmpty(
((HostCommand.Create)
((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
.getResourceCommand())
.getInetAddresses());
}
};
/** Enum defining the distinct statistics (types of registrar actions) to record. */
public enum StatType {
CONTACT_CREATES(0, isFlow(ContactCreateFlow.class)),
CONTACT_DELETES(0, isFlow(ContactDeleteFlow.class)),
CONTACT_TRANSFER_APPROVES(0, isFlow(ContactTransferApproveFlow.class)),
CONTACT_TRANSFER_CANCELS(0, isFlow(ContactTransferCancelFlow.class)),
CONTACT_TRANSFER_REJECTS(0, isFlow(ContactTransferRejectFlow.class)),
CONTACT_TRANSFER_REQUESTS(0, isFlow(ContactTransferRequestFlow.class)),
CONTACT_UPDATES(0, isFlow(ContactUpdateFlow.class)),
DOMAIN_APPLICATION_CREATES(0, isFlow(DomainApplicationCreateFlow.class)),
CONTACT_CREATES(0, equalTo(Type.CONTACT_CREATE)),
CONTACT_DELETES(0, equalTo(Type.CONTACT_DELETE)),
CONTACT_TRANSFER_APPROVES(0, equalTo(Type.CONTACT_TRANSFER_APPROVE)),
CONTACT_TRANSFER_CANCELS(0, equalTo(Type.CONTACT_TRANSFER_CANCEL)),
CONTACT_TRANSFER_REJECTS(0, equalTo(Type.CONTACT_TRANSFER_REJECT)),
CONTACT_TRANSFER_REQUESTS(0, equalTo(Type.CONTACT_TRANSFER_REQUEST)),
CONTACT_UPDATES(0, equalTo(Type.CONTACT_UPDATE)),
DOMAIN_APPLICATION_CREATES(0, equalTo(Type.DOMAIN_APPLICATION_CREATE)),
DOMAIN_APPLICATION_CREATES_LANDRUSH(
1, isFlow(DomainApplicationCreateFlow.class), not(IS_SUNRISE)),
DOMAIN_APPLICATION_CREATES_SUNRISE(1, isFlow(DomainApplicationCreateFlow.class), IS_SUNRISE),
DOMAIN_APPLICATION_DELETES(2, isFlow(DomainApplicationDeleteFlow.class)),
DOMAIN_APPLICATION_UPDATES(2, isFlow(DomainApplicationUpdateFlow.class)),
DOMAIN_CREATES(0, isFlow(DomainCreateFlow.class)),
DOMAIN_CREATES_ASCII(1, isFlow(DomainCreateFlow.class), not(IS_IDN)),
DOMAIN_CREATES_IDN(1, isFlow(DomainCreateFlow.class), IS_IDN),
DOMAIN_CREATES_WITH_CLAIMS_NOTICE(1, isFlow(DomainCreateFlow.class), HAS_CLAIMS_NOTICE),
DOMAIN_CREATES_WITH_FEE(1, isFlow(DomainCreateFlow.class), HAS_FEE),
DOMAIN_CREATES_WITH_SEC_DNS(1, isFlow(DomainCreateFlow.class), HAS_SEC_DNS),
DOMAIN_CREATES_WITHOUT_SEC_DNS(0, isFlow(DomainCreateFlow.class), not(HAS_SEC_DNS)),
DOMAIN_DELETES(2, isFlow(DomainDeleteFlow.class)),
DOMAIN_RENEWS(0, isFlow(DomainRenewFlow.class)),
DOMAIN_RESTORES(1, isFlow(DomainRestoreRequestFlow.class)),
DOMAIN_TRANSFER_APPROVES(1, isFlow(DomainTransferApproveFlow.class)),
DOMAIN_TRANSFER_CANCELS(1, isFlow(DomainTransferCancelFlow.class)),
DOMAIN_TRANSFER_REJECTS(1, isFlow(DomainTransferRejectFlow.class)),
DOMAIN_TRANSFER_REQUESTS(1, isFlow(DomainTransferRequestFlow.class)),
DOMAIN_UPDATES(0, isFlow(DomainUpdateFlow.class)),
DOMAIN_UPDATES_WITH_SEC_DNS(1, isFlow(DomainUpdateFlow.class), HAS_SEC_DNS),
DOMAIN_UPDATES_WITHOUT_SEC_DNS(0, isFlow(DomainUpdateFlow.class), not(HAS_SEC_DNS)),
HOST_CREATES(0, isFlow(HostCreateFlow.class)),
HOST_CREATES_EXTERNAL(0, isFlow(HostCreateFlow.class), not(IS_SUBORDINATE)),
HOST_CREATES_SUBORDINATE(1, isFlow(HostCreateFlow.class), IS_SUBORDINATE),
HOST_DELETES(1, isFlow(HostDeleteFlow.class)),
HOST_UPDATES(1, isFlow(HostUpdateFlow.class)),
UNCLASSIFIED_FLOWS(0, Predicates.<EppInput>alwaysFalse());
1, equalTo(Type.DOMAIN_APPLICATION_CREATE), not(IS_SUNRISE)),
DOMAIN_APPLICATION_CREATES_SUNRISE(1, equalTo(Type.DOMAIN_APPLICATION_CREATE), IS_SUNRISE),
DOMAIN_APPLICATION_DELETES(2, equalTo(Type.DOMAIN_APPLICATION_DELETE)),
DOMAIN_APPLICATION_UPDATES(2, equalTo(Type.DOMAIN_APPLICATION_UPDATE)),
DOMAIN_CREATES(0, equalTo(Type.DOMAIN_CREATE)),
DOMAIN_CREATES_ASCII(1, equalTo(Type.DOMAIN_CREATE), not(IS_IDN)),
DOMAIN_CREATES_IDN(1, equalTo(Type.DOMAIN_CREATE), IS_IDN),
DOMAIN_CREATES_WITH_CLAIMS_NOTICE(1, equalTo(Type.DOMAIN_CREATE), HAS_CLAIMS_NOTICE),
DOMAIN_CREATES_WITH_FEE(1, equalTo(Type.DOMAIN_CREATE), HAS_FEE),
DOMAIN_CREATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_CREATE), HAS_SEC_DNS),
DOMAIN_CREATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_CREATE), not(HAS_SEC_DNS)),
DOMAIN_DELETES(2, equalTo(Type.DOMAIN_DELETE)),
DOMAIN_RENEWS(0, equalTo(Type.DOMAIN_RENEW)),
DOMAIN_RESTORES(1, equalTo(Type.DOMAIN_RESTORE)),
DOMAIN_TRANSFER_APPROVES(1, equalTo(Type.DOMAIN_TRANSFER_APPROVE)),
DOMAIN_TRANSFER_CANCELS(1, equalTo(Type.DOMAIN_TRANSFER_CANCEL)),
DOMAIN_TRANSFER_REJECTS(1, equalTo(Type.DOMAIN_TRANSFER_REJECT)),
DOMAIN_TRANSFER_REQUESTS(1, equalTo(Type.DOMAIN_TRANSFER_REQUEST)),
DOMAIN_UPDATES(0, equalTo(Type.DOMAIN_UPDATE)),
DOMAIN_UPDATES_WITH_SEC_DNS(1, equalTo(Type.DOMAIN_UPDATE), HAS_SEC_DNS),
DOMAIN_UPDATES_WITHOUT_SEC_DNS(0, equalTo(Type.DOMAIN_UPDATE), not(HAS_SEC_DNS)),
HOST_CREATES(0, equalTo(Type.HOST_CREATE)),
HOST_CREATES_EXTERNAL(0, equalTo(Type.HOST_CREATE), not(IS_SUBORDINATE)),
HOST_CREATES_SUBORDINATE(1, equalTo(Type.HOST_CREATE), IS_SUBORDINATE),
HOST_DELETES(1, equalTo(Type.HOST_DELETE)),
HOST_UPDATES(1, equalTo(Type.HOST_UPDATE)),
UNCLASSIFIED_FLOWS(0, Predicates.<HistoryEntry.Type>alwaysFalse());
/** The number of StatTypes with a non-zero requirement. */
private static final int NUM_REQUIREMENTS = FluentIterable.from(values())
.filter(new Predicate<StatType>() {
@Override
public boolean apply(@Nonnull StatType statType) {
return statType.requirement > 0;
}})
.size();
private static final int NUM_REQUIREMENTS =
FluentIterable.from(values())
.filter(
new Predicate<StatType>() {
@Override
public boolean apply(@Nonnull StatType statType) {
return statType.requirement > 0;
}
})
.size();
/** Required number of times registrars must complete this action. */
final int requirement;
/** Filters to determine if this action was performed by an EppInput. */
private Predicate<EppInput>[] filters;
/** Filter to check the HistoryEntry Type */
@SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable.
private final Predicate<HistoryEntry.Type> typeFilter;
@SafeVarargs
StatType(int requirement, Predicate<EppInput>... filters) {
/** Optional filter on the EppInput. */
@SuppressWarnings("ImmutableEnumChecker") // Predicates are immutable.
private final Optional<Predicate<EppInput>> eppInputFilter;
StatType(int requirement, Predicate<HistoryEntry.Type> typeFilter) {
this(requirement, typeFilter, null);
}
StatType(
int requirement,
Predicate<HistoryEntry.Type> typeFilter,
Predicate<EppInput> eppInputFilter) {
this.requirement = requirement;
this.filters = filters;
this.typeFilter = typeFilter;
if (eppInputFilter == null) {
this.eppInputFilter = Optional.absent();
} else {
this.eppInputFilter = Optional.of(eppInputFilter);
}
}
/** Returns a more human-readable translation of the enum constant. */
@ -260,9 +262,15 @@ public class VerifyOteAction implements Runnable, JsonAction {
return Ascii.toLowerCase(this.name().replace('_', ' '));
}
/** An {@link EppInput} might match multiple actions, so check if this action matches. */
boolean matches(EppInput eppInput) {
return Predicates.and(filters).apply(eppInput);
/**
* Check if the {@link HistoryEntry} type matches as well as the {@link EppInput} if supplied.
*/
boolean matches(HistoryEntry.Type historyType, Optional<EppInput> eppInput) {
if (eppInputFilter.isPresent() && eppInput.isPresent()) {
return typeFilter.apply(historyType) && eppInputFilter.get().apply(eppInput.get());
} else {
return typeFilter.apply(historyType);
}
}
}
@ -293,22 +301,18 @@ public class VerifyOteAction implements Runnable, JsonAction {
}
/** Interprets the data in the provided HistoryEntry and increments counters. */
void record(HistoryEntry historyEntry) throws EppException {
void record(final HistoryEntry historyEntry) throws EppException {
byte[] xmlBytes = historyEntry.getXmlBytes();
// xmlBytes can be null on contact create and update for safe-harbor compliance.
//
// TODO(b/26161587): inspect the history entry itself to handle this properly.
if (xmlBytes == null) {
return;
}
final EppInput eppInput = unmarshal(EppInput.class, xmlBytes);
final Optional<EppInput> eppInput =
(xmlBytes == null) ? Optional.absent() : Optional.of(unmarshal(EppInput.class, xmlBytes));
if (!statCounts.addAll(
FluentIterable.from(EnumSet.allOf(StatType.class))
.filter(
new Predicate<StatType>() {
@Override
public boolean apply(@Nonnull StatType statType) {
return statType.matches(eppInput);
return statType.matches(historyEntry.getType(), eppInput);
}
})
.toList())) {
@ -317,17 +321,17 @@ public class VerifyOteAction implements Runnable, JsonAction {
}
/**
* 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.
* 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.
*/
List<String> findFailures() {
List<String> messages = new ArrayList<>();
for (StatType statType : StatType.values()) {
if (statCounts.count(statType) < statType.requirement) {
messages.add(String.format(
"Failure: %s %s found.",
(statType.requirement == 1 ? "No" : "Not enough"),
statType.description()));
messages.add(
String.format(
"Failure: %s %s found.",
(statType.requirement == 1 ? "No" : "Not enough"), statType.description()));
}
}
return messages;