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;