// Copyright 2017 The Nomulus 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.loadtest; import static com.google.appengine.api.taskqueue.QueueConstants.maxTasksPerAdd; import static com.google.appengine.api.taskqueue.QueueFactory.getQueue; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Lists.partition; import static google.registry.security.XsrfTokenManager.X_CSRF_TOKEN; import static google.registry.util.ResourceUtils.readResourceUtf8; import static java.util.Arrays.asList; import static org.joda.time.DateTimeZone.UTC; import com.google.appengine.api.taskqueue.TaskOptions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterators; import com.google.common.flogger.FluentLogger; import google.registry.config.RegistryEnvironment; import google.registry.request.Action; import google.registry.request.Parameter; import google.registry.request.auth.Auth; import google.registry.security.XsrfTokenManager; import google.registry.util.TaskQueueUtils; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.function.Function; import javax.inject.Inject; import org.joda.time.DateTime; /** * 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 = LoadTestAction.PATH, method = Action.Method.POST, automaticallyPrintOk = true, auth = Auth.AUTH_INTERNAL_OR_ADMIN ) public class LoadTestAction implements Runnable { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final int NUM_QUEUES = 10; private static final int ARBITRARY_VALID_HOST_LENGTH = 40; private static final int MAX_CONTACT_LENGTH = 13; private static final int MAX_DOMAIN_LABEL_LENGTH = 63; private static final String EXISTING_DOMAIN = "testdomain"; private static final String EXISTING_CONTACT = "contact"; private static final String EXISTING_HOST = "ns1"; private static final Random random = new Random(); public static final String PATH = "/_dr/loadtest"; /** The client identifier of the registrar to use for load testing. */ @Inject @Parameter("loadtestClientId") String clientId; /** * The number of seconds to delay the execution of the first load testing tasks by. Preparatory * work of creating independent contacts and hosts that will be used for later domain creation * testing occurs during this period, so make sure that it is long enough. */ @Inject @Parameter("delaySeconds") int delaySeconds; /** * The number of seconds that tasks will be enqueued for. Note that if system QPS cannot handle * the given load then it will take longer than this number of seconds for the test to complete. */ @Inject @Parameter("runSeconds") int runSeconds; /** The number of successful domain creates to enqueue per second over the length of the test. */ @Inject @Parameter("successfulDomainCreates") int successfulDomainCreatesPerSecond; /** The number of failed domain creates to enqueue per second over the length of the test. */ @Inject @Parameter("failedDomainCreates") int failedDomainCreatesPerSecond; /** The number of successful domain infos to enqueue per second over the length of the test. */ @Inject @Parameter("domainInfos") int domainInfosPerSecond; /** The number of successful domain checks to enqueue per second over the length of the test. */ @Inject @Parameter("domainChecks") int domainChecksPerSecond; /** The number of successful contact creates to enqueue per second over the length of the test. */ @Inject @Parameter("successfulContactCreates") int successfulContactCreatesPerSecond; /** The number of failed contact creates to enqueue per second over the length of the test. */ @Inject @Parameter("failedContactCreates") int failedContactCreatesPerSecond; /** The number of successful contact infos to enqueue per second over the length of the test. */ @Inject @Parameter("contactInfos") int contactInfosPerSecond; /** The number of successful host creates to enqueue per second over the length of the test. */ @Inject @Parameter("successfulHostCreates") int successfulHostCreatesPerSecond; /** The number of failed host creates to enqueue per second over the length of the test. */ @Inject @Parameter("failedHostCreates") int failedHostCreatesPerSecond; /** The number of successful host infos to enqueue per second over the length of the test. */ @Inject @Parameter("hostInfos") int hostInfosPerSecond; @Inject TaskQueueUtils taskQueueUtils; private final String xmlContactCreateTmpl; private final String xmlContactCreateFail; private final String xmlContactInfo; private final String xmlDomainCheck; private final String xmlDomainCreateTmpl; private final String xmlDomainCreateFail; private final String xmlDomainInfo; private final String xmlHostCreateTmpl; 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;
@Inject
LoadTestAction(@Parameter("tld") String tld, XsrfTokenManager xsrfTokenManager) {
xmlContactCreateTmpl = loadXml("contact_create");
xmlContactCreateFail = xmlContactCreateTmpl.replace("%contact%", EXISTING_CONTACT);
xmlContactInfo = loadXml("contact_info").replace("%contact%", EXISTING_CONTACT);
xmlDomainCheck =
loadXml("domain_check").replace("%tld%", tld).replace("%domain%", EXISTING_DOMAIN);
xmlDomainCreateTmpl = loadXml("domain_create").replace("%tld%", tld);
xmlDomainCreateFail =
xmlDomainCreateTmpl
.replace("%domain%", EXISTING_DOMAIN)
.replace("%contact%", EXISTING_CONTACT)
.replace("%host%", EXISTING_HOST);
xmlDomainInfo =
loadXml("domain_info").replace("%tld%", tld).replace("%domain%", EXISTING_DOMAIN);
xmlHostCreateTmpl = loadXml("host_create");
xmlHostCreateFail = xmlHostCreateTmpl.replace("%host%", EXISTING_HOST);
xmlHostInfo = loadXml("host_info").replace("%host%", EXISTING_HOST);
xsrfToken = xsrfTokenManager.generateToken("");
}
@Override
public void run() {
validateAndLogRequest();
DateTime initialStartSecond = DateTime.now(UTC).plusSeconds(delaySeconds);
ImmutableList.Builder> chunks = partition(tasks, maxTasksPerAdd());
// Farm out tasks to multiple queues to work around queue qps quotas.
for (int i = 0; i < chunks.size(); i++) {
taskQueueUtils.enqueue(getQueue("load" + (i % NUM_QUEUES)), chunks.get(i));
}
}
}