1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Create a load testing EPP client (#2415)

* Create a load testing EPP client.

This code is mostly based off of what was used for a past EPP load testing client that can be found in Google3 at https://source.corp.google.com/piper///depot/google3/experimental/users/jianglai/proxy/java/google/registry/proxy/client/

I modified the old client to be open-source friendly and use Gradle.

For now, this only performs a login and logout command, I will further expand on this in later PRs to add other EPP commands so that we can truly load test the system.

* Small changes

* Remove unnecessary build dep

* Add gradle build tasks

* Small fixes

* Add an instances setUp and cleanUp script

* More modifications to instance setup scripts

* change to ubuntu instance

* Add comment to make ssh work
This commit is contained in:
sarahcaseybot
2024-05-23 17:37:34 -04:00
committed by GitHub
parent ab4bac05d1
commit 0781010b16
12 changed files with 759 additions and 3 deletions

54
load-testing/build.gradle Normal file
View File

@@ -0,0 +1,54 @@
// Copyright 2024 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.
apply plugin: 'java'
createUberJar('buildLoadTestClient', 'loadTest', 'google.registry.client.EppClient')
dependencies {
def deps = rootProject.dependencyMap
implementation deps['joda-time:joda-time']
implementation deps['io.netty:netty-buffer']
implementation deps['io.netty:netty-codec']
implementation deps['io.netty:netty-codec-http']
implementation deps['io.netty:netty-common']
implementation deps['io.netty:netty-handler']
implementation deps['io.netty:netty-transport']
implementation deps['com.google.guava:guava']
implementation deps['org.bouncycastle:bcpg-jdk18on']
implementation deps['org.bouncycastle:bcpkix-jdk18on']
implementation deps['org.bouncycastle:bcprov-jdk18on']
implementation deps['org.jcommander:jcommander']
implementation deps['com.google.flogger:flogger']
runtimeOnly deps['com.google.flogger:flogger-system-backend']
}
task makeStagingDirectory {
mkdir layout.buildDirectory.dir('stage')
}
task copyFilesToStaging(dependsOn: makeStagingDirectory, type: Copy) {
from layout.buildDirectory.file('libs/loadTest.jar'), "${projectDir}/certificate.pem", "${projectDir}/key.pem"
into layout.buildDirectory.dir('stage')
}
task deployLoadTestsToInstances (dependsOn: copyFilesToStaging, type: Exec) {
executable "sh"
workingDir "${projectDir}/"
args "-c", "./deploy.sh"
}
test {
useJUnitPlatform()
}

17
load-testing/deploy.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Copyright 2024 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.
HOSTS=$(gcloud compute instances list | awk '/^loadtest/ { print $5 }')
for host in $HOSTS; do rsync -avz ./build/stage/ $host:test-client/; done

19
load-testing/instanceCleanUp.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Copyright 2024 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.
# Find and delete the instances used for load testing
gcloud compute instances list --filter="name ~ loadtest.*" --zones us-east4-a \
--format="value(name)" | xargs gcloud compute instances delete --zone us-east4-a

39
load-testing/instanceSetUp.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Copyright 2024 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.
# Create the instances - modify this number for the amount of instances you
# would like to use for your load test
gcloud compute instances create loadtest-{1..2} --machine-type g1-small \
--image-family ubuntu-2204-lts --image-project ubuntu-os-cloud --zone us-east4-a
sleep 10
# Get all the created load tests instances
HOSTS=$(gcloud compute instances list | awk '/^loadtest/ { print $5 }')
#Install rsync and Java - Retry is needed here since ssh connection will fail until instances are fully provisioned
for host in $HOSTS;
do
for i in {1..60}; do
if ssh $host 'sudo apt-get -y update &&
sudo apt-get -y upgrade &&
sudo apt-get -y install rsync &&
sudo apt-get -y install openjdk-21-jdk'; then
break
else
sleep 5
fi
done
done

21
load-testing/run.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# Copyright 2024 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.
HOSTS=$(gcloud compute instances list | awk '/^loadtest/ { print $5 }')
for host in $HOSTS;
do ssh $host 'cd test-client/ &&
java -jar loadTest.jar --host epp.example --certificate certificate.pem -k key.pem -pw examplePassword -ft';
done

View File

@@ -0,0 +1,353 @@
// Copyright 2024 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.client;
import static com.google.common.io.Resources.getResource;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.joda.time.DateTimeZone.UTC;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Promise;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.joda.time.DateTime;
/** A simple EPP client that can be used for load testing. */
@Parameters(separators = " =")
@SuppressWarnings("FutureReturnValueIgnored")
public class EppClient implements Runnable {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String LOGIN_FILE = "login.xml";
private static final String LOGOUT_FILE = "logout.xml";
static final AttributeKey<ArrayList<ZonedDateTime>> REQUEST_SENT =
AttributeKey.valueOf("REQUEST_SENT");
static final AttributeKey<ArrayList<ZonedDateTime>> RESPONSE_RECEIVED =
AttributeKey.valueOf("RESPONSE_RECEIVED");
static final AttributeKey<Integer> CHANNEL_NUMBER = AttributeKey.valueOf("CHANNEL_NUMBER");
static final AttributeKey<Path> LOGGING_LOCATION = AttributeKey.valueOf("LOGGING_LOCATION");
static final AttributeKey<Boolean> FORCE_TERMINATE = AttributeKey.valueOf("FORCE_TERMINATE");
static final AttributeKey<ExecutorService> LOGGING_EXECUTOR =
AttributeKey.valueOf("LOGGING_EXECUTOR");
static final AttributeKey<Iterator<byte[]>> INPUT_ITERATOR =
AttributeKey.valueOf("INPUT_ITERATOR");
static final AttributeKey<Promise<Void>> LOGGING_REQUEST_COMPLETE =
AttributeKey.valueOf("LOGGING_REQUEST_COMPLETE");
private static final int PORT = 700;
private static final int TIMEOUT_SECONDS = 600;
public static class InetAddressConverter implements IStringConverter<InetAddress> {
@Override
public InetAddress convert(String host) {
try {
return InetAddress.getByName(host);
} catch (UnknownHostException e) {
throw new IllegalArgumentException(host, e);
}
}
}
@Parameter(
names = {"--help"},
description = "Print this help message.",
help = true)
private boolean help = false;
@Parameter(
names = {"--host", "-h"},
description = "Epp server hostname/IP to connect to.",
converter = InetAddressConverter.class,
required = true)
private InetAddress host = null;
@Parameter(
names = {"--certificate"},
description = "Certificate pem file.",
required = true)
private String certFileName = null;
@Parameter(
names = {"--key", "-k"},
description = "Private key pem file.",
required = true)
private String keyFileName = null;
@Parameter(
names = {"--connections", "-cn"},
description = "Number of connections that are made to the EPP server.")
private int connections = 1;
@Parameter(
names = {"--client", "-c"},
description = "Registrar client id.")
private String client = "proxy";
@Parameter(
names = {"--password", "-pw"},
description = "Registrar password.")
private String password = "abcde12345";
@Parameter(
names = {"--force_terminate", "-ft"},
description = "Whether to explicitly close the connection after receiving a logout response.")
private boolean forceTerminate = false;
public static void main(String[] args) {
EppClient eppClient = new EppClient();
JCommander jCommander = new JCommander(eppClient);
jCommander.parse(args);
if (eppClient.help) {
jCommander.usage();
return;
}
eppClient.run();
}
private ImmutableList<String> makeInputList(ZonedDateTime now) {
ImmutableList.Builder<String> templatesList = ImmutableList.builder();
ImmutableList.Builder<String> inputList = ImmutableList.builder();
templatesList.add(readStringFromFile(LOGIN_FILE));
templatesList.add(readStringFromFile(LOGOUT_FILE));
for (String template : templatesList.build()) {
inputList.add(
template
.replace("@@CLIENT@@", client)
.replace("@@PASSWORD@@", password)
.replace("@@NOW@@", now.toString()));
}
return inputList.build();
}
private static String readStringFromFile(String filename) {
try {
return Resources.toString(getResource(EppClient.class, "resources/" + filename), UTF_8);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot read from file: resources/" + filename);
}
}
private static KeyPair getKeyPair(String filename) throws IOException {
byte[] keyBytes = Files.asCharSource(new File(filename), UTF_8).read().getBytes(UTF_8);
try {
PEMKeyPair pemPair =
(PEMKeyPair)
new PEMParser(new InputStreamReader(new ByteArrayInputStream(keyBytes), UTF_8))
.readObject();
return new JcaPEMKeyConverter().setProvider("BC").getKeyPair(pemPair);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static X509Certificate getCertificate(String filename) throws IOException {
byte[] certificateBytes = Files.asCharSource(new File(filename), UTF_8).read().getBytes(UTF_8);
try {
X509CertificateHolder certificateHolder =
(X509CertificateHolder)
new PEMParser(
new InputStreamReader(new ByteArrayInputStream(certificateBytes), UTF_8))
.readObject();
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateHolder);
} catch (IOException | CertificateException e) {
throw new RuntimeException(e);
}
}
private ChannelInitializer<SocketChannel> makeChannelInitializer(
String outputFolder, ImmutableList<ExecutorService> loggingExecutors) throws IOException {
return new ChannelInitializer<>() {
private final ImmutableList<String> inputList =
makeInputList(ZonedDateTime.now(ZoneOffset.UTC));
private final KeyPair key = getKeyPair(keyFileName);
private final X509Certificate cert = getCertificate(certFileName);
private final LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
private final EppClientHandler eppClientHandler = new EppClientHandler();
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.attr(REQUEST_SENT).set(new ArrayList<>());
ch.attr(RESPONSE_RECEIVED).set(new ArrayList<>());
Path loggingLocation =
Paths.get(String.format("%s/%d.log", outputFolder, ch.attr(CHANNEL_NUMBER).get()));
ch.attr(LOGGING_LOCATION).set(loggingLocation);
ch.attr(FORCE_TERMINATE).set(forceTerminate);
ch.attr(LOGGING_EXECUTOR)
.set(loggingExecutors.get(ch.attr(CHANNEL_NUMBER).get() % loggingExecutors.size()));
ch.attr(INPUT_ITERATOR)
.set(
inputList.stream()
.map(
(String str) ->
str.replace(
"@@CHANNEL_NUMBER@@",
String.valueOf(ch.attr(CHANNEL_NUMBER).get()))
.replace("@@CHANNEL_NUMBER_MODULO@@", String.valueOf(0))
.getBytes(UTF_8))
.iterator());
ch.pipeline()
.addLast(
SslContextBuilder.forClient()
.keyManager(key.getPrivate(), cert)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build()
.newHandler(ch.alloc(), host.getHostName(), PORT));
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(512 * 1024, 0, 4, -4, 4));
ch.pipeline().addLast(new LengthFieldPrepender(4, true));
ch.pipeline().addLast(loggingHandler);
ch.pipeline().addLast(eppClientHandler);
}
};
}
private static String createOutputFolder(String folderName) {
Path folder = Paths.get(folderName);
if (!folder.toFile().exists()) {
folder.toFile().mkdirs();
}
System.out.printf("\nOutputs saved at %s\n", folder);
return folderName;
}
@Override
public void run() {
String outputFolder = createOutputFolder(String.format("load-tests/%s", DateTime.now(UTC)));
ImmutableList.Builder<ExecutorService> builder = ImmutableList.builderWithExpectedSize(5);
for (int i = 0; i < 5; ++i) {
builder.add(Executors.newSingleThreadExecutor());
}
final ImmutableList<ExecutorService> loggingExecutors = builder.build();
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap =
new Bootstrap()
.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(makeChannelInitializer(outputFolder, loggingExecutors));
List<ChannelFuture> channelFutures = new ArrayList<>();
// Three requests: hello (from the proxy), login and logout.
int requestPerConnection = 3;
for (int i = 0; i < connections; i++) {
bootstrap.attr(CHANNEL_NUMBER, i);
channelFutures.add(
bootstrap
.connect(host, PORT)
.addListener(
(ChannelFuture cf) -> {
if (!cf.isSuccess()) {
System.out.printf("Cannot connect to %s:%s\n", host, PORT);
}
}));
}
LinkedHashSet<Integer> killedConnections = new LinkedHashSet<>();
// Wait for all channels to close.
for (ChannelFuture channelFuture : channelFutures) {
Channel channel = channelFuture.syncUninterruptibly().channel();
int channelNumber = channel.attr(CHANNEL_NUMBER).get();
if (!channel
.closeFuture()
.awaitUninterruptibly(
TIMEOUT_SECONDS * 1000
- Duration.between(
channel.attr(REQUEST_SENT).get().getFirst(),
ZonedDateTime.now(ZoneOffset.UTC))
.toMillis())) {
channel.close().syncUninterruptibly();
killedConnections.add(channelNumber);
}
}
System.out.println();
System.out.println("====== SUMMARY ======");
System.out.printf("Number of connections: %d\n", connections);
System.out.printf("Number of requests per connection: %d\n", requestPerConnection);
if (!killedConnections.isEmpty()) {
System.out.printf("Force killed connections (%d): ", killedConnections.size());
for (int channelNumber : killedConnections) {
System.out.printf("%d ", channelNumber);
}
System.out.print("\n");
}
eventLoopGroup.shutdownGracefully();
channelFutures.forEach(
channelFuture -> {
channelFuture.channel().attr(LOGGING_REQUEST_COMPLETE).get().syncUninterruptibly();
});
loggingExecutors.forEach(ExecutorService::shutdown);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,153 @@
// Copyright 2024 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.client;
import static google.registry.client.EppClient.CHANNEL_NUMBER;
import static google.registry.client.EppClient.FORCE_TERMINATE;
import static google.registry.client.EppClient.INPUT_ITERATOR;
import static google.registry.client.EppClient.LOGGING_EXECUTOR;
import static google.registry.client.EppClient.LOGGING_LOCATION;
import static google.registry.client.EppClient.LOGGING_REQUEST_COMPLETE;
import static google.registry.client.EppClient.REQUEST_SENT;
import static google.registry.client.EppClient.RESPONSE_RECEIVED;
import static java.nio.file.StandardOpenOption.APPEND;
import com.google.common.flogger.FluentLogger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
/** Handler that sends EPP requests and receives EPP responses. */
@SuppressWarnings("FutureReturnValueIgnored")
@Sharable
public class EppClientHandler extends ChannelDuplexHandler {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static class FileWriter implements Runnable {
private final Path loggingLocation;
private final byte[] contents;
private final ZonedDateTime time;
FileWriter(Path loggingLocation, byte[] contents, ZonedDateTime time) {
this.loggingLocation = loggingLocation;
this.contents = contents;
this.time = time;
}
@Override
public void run() {
try {
if (!Files.exists(loggingLocation)) {
Files.createFile(loggingLocation);
}
Files.writeString(loggingLocation, time.toString() + "\n", APPEND);
Files.write(loggingLocation, contents, APPEND);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
ctx.channel().attr(REQUEST_SENT).get().add(now);
ctx.channel().attr(LOGGING_REQUEST_COMPLETE).set(ctx.executor().newPromise());
super.channelRegistered(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Promise<Void> loggingCompletePromise = ctx.channel().attr(LOGGING_REQUEST_COMPLETE).get();
if (!loggingCompletePromise.isDone()) {
loggingCompletePromise.setSuccess(null);
}
logger.atWarning().withCause(cause).log(
"Connection %d closed.", ctx.channel().attr(CHANNEL_NUMBER).get());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
Channel ch = ctx.channel();
ctx.channel().attr(RESPONSE_RECEIVED).get().add(now);
if (msg instanceof ByteBuf buffer) {
byte[] contents = new byte[buffer.readableBytes()];
buffer.readBytes(contents);
ReferenceCountUtil.release(buffer);
if (ch.attr(LOGGING_LOCATION).get() != null) {
ch.attr(LOGGING_EXECUTOR)
.get()
.submit(new FileWriter(ch.attr(LOGGING_LOCATION).get(), contents, now));
}
if (ch.attr(INPUT_ITERATOR).get().hasNext()) {
ch.writeAndFlush(ch.attr(INPUT_ITERATOR).get().next());
} else {
ch.attr(LOGGING_REQUEST_COMPLETE).get().setSuccess(null);
if (ch.attr(FORCE_TERMINATE).get()) {
ch.close();
}
}
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC);
Channel ch = ctx.channel();
ctx.channel().attr(REQUEST_SENT).get().add(now);
if (msg instanceof byte[] outputBytes) {
ByteBuf buffer = Unpooled.buffer();
buffer.writeBytes(outputBytes);
ctx.write(buffer, promise);
if (ch.attr(LOGGING_LOCATION).get() != null) {
ch.attr(LOGGING_EXECUTOR)
.get()
.submit(new FileWriter(ch.attr(LOGGING_LOCATION).get(), outputBytes, now));
}
}
}
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
Promise<Void> loggingCompletePromise = ctx.channel().attr(LOGGING_REQUEST_COMPLETE).get();
if (!loggingCompletePromise.isDone()) {
loggingCompletePromise.setSuccess(null);
}
super.close(ctx, promise);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Promise<Void> loggingCompletePromise = ctx.channel().attr(LOGGING_REQUEST_COMPLETE).get();
if (!loggingCompletePromise.isDone()) {
loggingCompletePromise.setSuccess(null);
}
super.channelInactive(ctx);
}
}

View File

@@ -0,0 +1,73 @@
## EPP Load Testing Client
This project contains an EPP client that can be use for load testing the full
registry platform. All the below commands should be run from the merged root.
### Setting up the test instances
* If you have not done s yet, you will need to set up ssh keys in your
[GCE metadata](https://pantheon.corp.google.com/compute/metadata?resourceTab=sshkeys):
* To create however many GCE instances you want to run on, modify the
`instanceSetUp.sh` file to include the correct number of instances.
* Run the instance set up script to create and configure each of the GCE
instances to be used for load testing:
```shell
$ load-testing/instanceSetUp.sh
```
* Verify that the IP address of any created instances is in the allowlist of the
proxy registrar user.
* Use the below command to get the IP addresses of the created instances.
```shell
$ (gcloud compute instances list | awk '/^loadtest/ { print $5 }')
```
* Check the proxy registrar's current allow list
```shell
$ nomulus -e sandbox get_registrar proxy | grep ipAddressAllowList
```
* All of your host ip addresses should match a netmask specified in the proxy
allow list. If not, you'll need to redefine the list:
```shell
$ nomulus -e sandbox update_registrar proxy --ip_allow_list=<new-comma-separated-allowlist>
```
### Running the client
* From the merged root build the load testing client:
```shell
$ ./nom_build :load-testing:buildLoadTestClient
```
* Deploy the client to the GCE instances (this will create a local staging
directory and deploy it to each of your previously created loadtest GCE instances):
```shell
$ ./nom_build :load-testing:deployLoadTestsToInstances
```
* Run the load test. Configurations of the load test can be made by configuring
this `run.sh` file locally.
```shell
$ load-testing/run.sh
```
### Cleanup
* Run the instance clean up script to delete the created instances
```shell
$ load-testing/instanceCleanUp.sh
```
* You may want to remove any host key fingerprints for those hosts from your ~/.ssh/known_hosts file (these IPs tend to get reused with new host keys)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<login>
<clID>@@CLIENT@@</clID>
<pw>@@PASSWORD@@</pw>
<options>
<version>1.0</version>
<lang>en</lang>
</options>
<svcs>
<objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
<objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
</svcs>
</login>
<clTRID>epp-client-login-@@NOW@@-@@CHANNEL_NUMBER@@</clTRID>
</command>
</epp>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<logout/>
<clTRID>epp-client-logout-@@NOW@@-@@CHANNEL_NUMBER@@</clTRID>
</command>
</epp>