From 8e5bd45976da1042e5e970efa73d2c3fe564d55c Mon Sep 17 00:00:00 2001 From: mcilwain Date: Fri, 21 Oct 2016 16:08:41 -0700 Subject: [PATCH] Fix bugs in load test action so that it actually works This adds XSRF tokens so that the epptool request succeeds, adds better logging for debugging load test requests, adds better validation, and improves documentation. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136885117 --- java/google/registry/loadtest/BUILD | 1 + .../registry/loadtest/LoadTestAction.java | 85 +++++++++++++++---- .../registry/security/XsrfTokenManager.java | 2 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/java/google/registry/loadtest/BUILD b/java/google/registry/loadtest/BUILD index 4e5438596..0b60762c9 100644 --- a/java/google/registry/loadtest/BUILD +++ b/java/google/registry/loadtest/BUILD @@ -21,6 +21,7 @@ java_library( "//third_party/java/servlet/servlet_api", "//java/google/registry/config", "//java/google/registry/request", + "//java/google/registry/security", "//java/google/registry/util", ], ) diff --git a/java/google/registry/loadtest/LoadTestAction.java b/java/google/registry/loadtest/LoadTestAction.java index 474f7c4cf..8e4212707 100644 --- a/java/google/registry/loadtest/LoadTestAction.java +++ b/java/google/registry/loadtest/LoadTestAction.java @@ -19,6 +19,8 @@ import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Lists.partition; import static com.google.common.collect.Lists.transform; +import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN; +import static google.registry.util.FormattingLogger.getLoggerForCallerClass; import static google.registry.util.ResourceUtils.readResourceUtf8; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; @@ -26,15 +28,14 @@ import static org.joda.time.DateTimeZone.UTC; import com.google.appengine.api.taskqueue.TaskOptions; import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterators; -import com.google.common.net.MediaType; import google.registry.config.RegistryEnvironment; import google.registry.request.Action; import google.registry.request.Parameter; +import google.registry.security.XsrfTokenManager; +import google.registry.util.FormattingLogger; import google.registry.util.TaskEnqueuer; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; @@ -45,13 +46,23 @@ import java.util.Random; import javax.inject.Inject; import org.joda.time.DateTime; -/** Simple load test action that can generate configurable QPSes of various EPP actions. */ +/** + * Simple load test action that can generate configurable QPSes of various EPP actions. + * + *

All aspects of the load test are configured via URL parameters that are specified when the + * loadtest URL is being POSTed to. The {@code clientId} and {@code tld} parameters are required. + * All of the other parameters are optional, but if none are specified then no actual load testing + * will be done since all of the different kinds of checks default to running zero per second. So at + * least one must be specified in order for load testing to do anything. + */ @Action( path = "/_dr/loadtest", method = Action.Method.POST, automaticallyPrintOk = true) public class LoadTestAction implements Runnable { + private static final FormattingLogger logger = getLoggerForCallerClass(); + private static final int NUM_QUEUES = 10; private static final int ARBITRARY_VALID_HOST_LENGTH = 40; private static final int MAX_CONTACT_LENGTH = 13; @@ -149,6 +160,14 @@ public class LoadTestAction implements Runnable { private final String xmlHostCreateFail; private final String xmlHostInfo; + /** + * The XSRF token to be used for making requests to the epptool endpoint. + * + *

Note that the email address is set to empty, because the logged-in user hitting this + * endpoint will not be the same as when the tasks themselves fire and hit the epptool endpoint. + */ + private final String xsrfToken = XsrfTokenManager.generateToken("admin", ""); + @Inject LoadTestAction(@Parameter("tld") String tld) { xmlContactCreateTmpl = loadXml("contact_create"); @@ -171,10 +190,7 @@ public class LoadTestAction implements Runnable { @Override public void run() { - checkArgument( - RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION, - "DO NOT RUN LOADTESTS IN PROD!"); - + validateAndLogRequest(); DateTime initialStartSecond = DateTime.now(UTC).plusSeconds(delaySeconds); ImmutableList.Builder preTaskXmls = new ImmutableList.Builder<>(); ImmutableList.Builder contactNamesBuilder = new ImmutableList.Builder<>(); @@ -236,7 +252,45 @@ public class LoadTestAction implements Runnable { .toList(), startSecond)); } - enqueue(tasks.build()); + ImmutableList taskOptions = tasks.build(); + enqueue(taskOptions); + logger.infofmt("Added %d total load test tasks", taskOptions.size()); + } + + private void validateAndLogRequest() { + checkArgument( + RegistryEnvironment.get() != RegistryEnvironment.PRODUCTION, + "DO NOT RUN LOADTESTS IN PROD!"); + checkArgument( + successfulDomainCreatesPerSecond > 0 + || failedDomainCreatesPerSecond > 0 + || domainInfosPerSecond > 0 + || domainChecksPerSecond > 0 + || successfulContactCreatesPerSecond > 0 + || failedContactCreatesPerSecond > 0 + || contactInfosPerSecond > 0 + || successfulHostCreatesPerSecond > 0 + || failedHostCreatesPerSecond > 0 + || hostInfosPerSecond > 0, + "You must specify at least one of the 'operations per second' parameters."); + logger.infofmt( + "Running load test with the following params. clientId: %s, delaySeconds: %d, " + + "runSeconds: %d, successful|failed domain creates/s: %d|%d, domain infos/s: %d, " + + "domain checks/s: %d, successful|failed contact creates/s: %d|%d, " + + "contact infos/s: %d, successful|failed host creates/s: %d|%d, host infos/s: %d.", + clientId, + delaySeconds, + runSeconds, + successfulDomainCreatesPerSecond, + failedDomainCreatesPerSecond, + domainInfosPerSecond, + domainChecksPerSecond, + successfulContactCreatesPerSecond, + failedContactCreatesPerSecond, + contactInfosPerSecond, + successfulHostCreatesPerSecond, + failedHostCreatesPerSecond, + hostInfosPerSecond); } private String loadXml(String name) { @@ -281,14 +335,11 @@ public class LoadTestAction implements Runnable { int offsetMillis = (int) (1000.0 / xmls.size() * i); tasks.add(TaskOptions.Builder.withUrl("/_dr/epptool") .etaMillis(start.getMillis() + offsetMillis) - .payload( - Joiner.on('&').withKeyValueSeparator("=").join( - ImmutableMap.of( - "clientId", clientId, - "superuser", false, - "dryRun", false, - "xml", urlEncode(xmls.get(i)))), - MediaType.FORM_DATA.toString())); + .header(X_CSRF_TOKEN, xsrfToken) + .param("clientId", clientId) + .param("superuser", Boolean.FALSE.toString()) + .param("dryRun", Boolean.FALSE.toString()) + .param("xml", urlEncode(xmls.get(i)))); } return tasks.build(); } diff --git a/java/google/registry/security/XsrfTokenManager.java b/java/google/registry/security/XsrfTokenManager.java index 0a2743dad..b22bc37a0 100644 --- a/java/google/registry/security/XsrfTokenManager.java +++ b/java/google/registry/security/XsrfTokenManager.java @@ -95,7 +95,7 @@ public final class XsrfTokenManager { } String reconstructedToken = encodeToken(creationTime, scope, getLoggedInEmailOrEmpty()); if (!reconstructedToken.equals(encodedPart)) { - logger.warningfmt("Reconstructed XSRF mismatch: %s != %s", encodedPart, reconstructedToken); + logger.warningfmt("Reconstructed XSRF mismatch: %s ≠ %s", encodedPart, reconstructedToken); return false; } return true;