Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrations-1079 - Adding Benchmark Testing Environment #231

Merged
merged 12 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TrafficCapture/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ include('captureKafkaOffloader',
'nettyWireLogging',
'trafficCaptureProxyServer',
'trafficReplayer',

'testingEnvironment',
'dockerSolution',
'trafficCaptureProxyServerTest'
)
125 changes: 125 additions & 0 deletions TrafficCapture/testingEnvironment/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
plugins {
id 'org.opensearch.migrations.java-library-conventions'
id "com.avast.gradle.docker-compose" version "0.16.12"
id "com.bmuschko.docker-java-application" version "9.3.1"
}

import java.security.MessageDigest
import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
import org.apache.tools.ant.taskdefs.condition.Os

def calculateDockerHash(String projectName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be refactored into a common shared class and imported by both projects

MessageDigest digest = MessageDigest.getInstance('SHA-256')
fileTree("src/main/docker/${projectName}")
.each( file ->
file.withInputStream { is ->
var buffer = new byte[1024]
int read
while ((read = is.read(buffer)) != -1) {
digest.update(buffer, 0, read)
}
}
)
return digest.digest().encodeHex().toString()
}

dependencies {
implementation project(':trafficCaptureProxyServer')
testImplementation project(':trafficCaptureProxyServerTest')
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can fall away by merging these changes into the ...ProxyServerTest directory


def dockerFilesForExternalServices = [
"nginx": "nginx"
]

// Create the static docker files that aren't hosting migrations java code from this repo
dockerFilesForExternalServices.each { projectName, dockerImageName ->
task("buildDockerImage_${projectName}", type: DockerBuildImage) {
def hash = calculateDockerHash(projectName)
images.add("testenv/${dockerImageName}:$hash")
images.add("testenv/${dockerImageName}:latest")
inputDir = project.file("src/main/docker/${projectName}")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be refactored into a common shared class and imported by both projects

}

def javaContainerServices = [
"trafficCaptureProxyServer": "capture_proxy",
"trafficCaptureProxyServerTest": "jmeter"
]
def baseImageProjectOverrides = [
"nginx": "nginx"
]

javaContainerServices.each { projectName, dockerImageName ->
def dockerBuildDir = "build/docker/${projectName}"
def artifactsDir = "${dockerBuildDir}/jars";
task("copyArtifact_${projectName}", type: Copy) {
dependsOn ":${projectName}:build"
dependsOn ":${projectName}:jar"
from { project(":${projectName}").configurations.findByName("runtimeClasspath").files }
from { project(":${projectName}").tasks.getByName('jar') }
into artifactsDir
include "*.jar"
duplicatesStrategy = DuplicatesStrategy.WARN

if (projectName == "trafficCaptureProxyServerTest") {
include "*.properties"
from project.file("src/main/docker/${projectName}/jmeter.properties").absolutePath
into "${dockerBuildDir}"
}
}

task "createDockerfile_${projectName}"(type: com.bmuschko.gradle.docker.tasks.image.Dockerfile) {
dependsOn "copyArtifact_${projectName}"
destFile = project.file("${dockerBuildDir}/Dockerfile")
def baseImageOverrideProjectName = baseImageProjectOverrides.get(projectName)
if (baseImageOverrideProjectName) {
def dependentDockerImageName = dockerFilesForExternalServices.get(baseImageOverrideProjectName)
def hashNonce = calculateDockerHash(baseImageOverrideProjectName)
from "testenv/${dependentDockerImageName}:${hashNonce}"
dependsOn "buildDockerImage_${baseImageOverrideProjectName}"
runCommand("sed -i -e \"s|mirrorlist=|#mirrorlist=|g\" /etc/yum.repos.d/CentOS-* ; sed -i -e \"s|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g\" /etc/yum.repos.d/CentOS-*")
runCommand("yum -y install nmap-ncat")
} else {
from 'openjdk:11-jre'
if (projectName == "trafficCaptureProxyServerTest") {
instruction 'COPY jmeter.properties /jmeter.properties'
}
runCommand("apt-get update && apt-get install -y netcat")
}

copyFile("jars", "/jars")
// can't set the environment variable from the runtimeClasspath because the Dockerfile is
// constructed in the configuration phase and the classpath won't be realized until the
// execution phase. Therefore, we need to have docker run the command to resolve the classpath
// and it's simplest to pack that up into a helper script.
runCommand("printf \"#!/bin/sh\\njava -cp `echo /jars/*.jar | tr \\ :` \\\"\\\$@\\\" \" > /runJavaWithClasspath.sh");
runCommand("chmod +x /runJavaWithClasspath.sh")
// container stay-alive
defaultCommand('tail', '-f', '/dev/null')
//defaultCommand('/runJavaWithClasspath.sh', '...')
}
}

(javaContainerServices).forEach { projectName, dockerImageName ->
def dockerBuildDir = "build/docker/${projectName}"
task "buildDockerImage_${projectName}"(type: DockerBuildImage) {
dependsOn "createDockerfile_${projectName}"
inputDir = project.file("${dockerBuildDir}")
images.add("testenv/${dockerImageName}:${version}")
images.add("testenv/${dockerImageName}:latest")
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be refactored into a common shared class and imported by both projects

}

dockerCompose {
useComposeFiles.add("src/main/docker/docker-compose.yml")
}

task buildDockerImages {
dependsOn buildDockerImage_nginx
dependsOn buildDockerImage_trafficCaptureProxyServer
dependsOn buildDockerImage_trafficCaptureProxyServerTest
}

tasks.getByName('composeUp')
.dependsOn(tasks.getByName('buildDockerImages'))
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: '3.7'
services:
nginx:
image: 'testenv/nginx:latest'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: A slightly more descriptive name than 'nginx' might be helpful for others

ports:
- "8080:80"

captureproxy:
image: 'testenv/capture_proxy:latest'
networks:
- testing
ports:
- "9201:9201"
command: /runJavaWithClasspath.sh org.opensearch.migrations.trafficcapture.proxyserver.Main --destinationUri http://nginx:80 --listenPort 9201 --noCapture
depends_on:
- nginx

jmeter:
image: 'testenv/jmeter:latest'
networks:
- testing
command: /bin/sh -c "while sleep 10; do /runJavaWithClasspath.sh org.opensearch.migrations.trafficcapture.JMeterLoadTest -p 9201 -d captureproxy; done"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you want to run this within a loop? The JMeter configuration would be able to loop as many times as you'd like it to. Managing multiple processes in a container is less desirable than managing just one.

depends_on:
- captureproxy



networks:
testing:
driver: bridge
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
ARG NGINX_VERSION=1.23.4
FROM nginx:${NGINX_VERSION} as build

RUN apt-get update
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y \
openssh-client \
git \
wget \
libxml2 \
libxslt1-dev \
libxml2-dev \
libxslt-dev \
libpcre3 \
libpcre3-dev \
zlib1g \
Expand Down Expand Up @@ -43,8 +43,12 @@ RUN NGINX_ARGS=$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p') \

FROM nginx:${NGINX_VERSION}

RUN apt-get update
RUN apt-get install -y vim
# Switch to noninteractive mode to avoid interactive prompts during package installation
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends vim \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this to install vim? What does the process need vim for? Please leave a comment, especially if it's just nice to have for somebody exec-ing a shell within the container.

&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean

RUN /bin/bash -c '\
export HTMLDIR=/usr/share/nginx/html ; \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ static class Parameters {
@Parameter(required = false,
names = {"--noCapture"},
arity = 0,
description = "Directory to store trace files in.")
description = "If enabled, Does NOT store trace files in.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... s/store trace files/capture traffic to ANY sink/

boolean noCapture;
@Parameter(required = false,
names = {"--kafkaConfigFile"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation group: 'org.apache.jmeter', name: 'ApacheJMeter_core', version: '5.5', withoutBom
implementation group: 'org.apache.jmeter', name: 'ApacheJMeter_http', version: '5.5', withoutBom
implementation group: 'org.apache.jmeter', name: 'ApacheJMeter_config', version: '5.5', withoutBom
implementation group: 'com.beust', name: 'jcommander', version: '1.82'

constraints {
implementation('net.minidev:json-smart:2.4.9') {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.opensearch.migrations.trafficcapture;

import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import lombok.AllArgsConstructor;
import org.apache.jmeter.assertions.ResponseAssertion;
import org.apache.jmeter.control.LoopController;
Expand All @@ -23,23 +26,49 @@

public class JMeterLoadTest {

static class Parameters {
@Parameter(required = true,
names = {"-p", "--port"},
description = "Port number")
int backsidePort;
@Parameter(required = true,
names = {"-d", "--domain-name"},
description = "Domain name")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/domain-name/server-name/.

String domainName;
}

public static Parameters parseArgs(String[] args) {
Parameters p = new Parameters();
JCommander jCommander = new JCommander(p);
try {
jCommander.parse(args);
return p;
} catch (ParameterException e) {
System.err.println(e.getMessage());
System.err.println("Got args: "+ String.join("; ", args));
jCommander.usage();
return null;
}
}

public static void main(String[] args) {
var params = parseArgs(args);
StandardJMeterEngine jmeter = new StandardJMeterEngine();
File home = new File(".");
JMeterUtils.setJMeterHome(home.getPath());
JMeterUtils.loadJMeterProperties("jmeter.properties");
JMeterUtils.loadJMeterProperties("/jmeter.properties");

String outputFileName = null;
//outputFileName = "results.jtl";
ListedHashTree hashTree = createTestPlan(100, 8, 1,
ListedHashTree hashTree = createTestPlan(params.domainName, params.backsidePort, 100, 8, 1,
false, outputFileName);

jmeter.configure(hashTree);
jmeter.run();
}

@NotNull
private static ListedHashTree createTestPlan(int loopCount, int workerThreadCount,
private static ListedHashTree createTestPlan(String domain, int port, int loopCount, int workerThreadCount,
int summaryUpdateFrequencySeconds, boolean verifyResponse,
String logOutputFileName) {
ListedHashTree hashTree = new ListedHashTree();
Expand All @@ -62,12 +91,12 @@ private static ListedHashTree createTestPlan(int loopCount, int workerThreadCoun
{
var threadGroupHashTree = hashTree.add(testPlan, createThreadGroup("FireAndForgetGroup",
loopCount, workerThreadCount, 1));
Arrays.stream(files).forEach(fr -> threadGroupHashTree.add(createHttpSampler(fr, "GET", verifyResponse)));
Arrays.stream(files).forEach(fr -> threadGroupHashTree.add(createHttpSampler(domain, port, fr, "GET", verifyResponse)));
}
{
var threadGroupHashTree = hashTree.add(testPlan, createThreadGroup("TransactionGroup",
loopCount, workerThreadCount, 1));
Arrays.stream(files).forEach(fr -> threadGroupHashTree.add(createHttpSampler(fr, "POST", verifyResponse)));
Arrays.stream(files).forEach(fr -> threadGroupHashTree.add(createHttpSampler(domain, port, fr, "POST", verifyResponse)));
}
hashTree.add(testPlan, createResultCollector(logOutputFileName, summaryUpdateFrequencySeconds));
return hashTree;
Expand Down Expand Up @@ -114,13 +143,13 @@ private static ResponseAssertion createResponseAssertion(char c, int count) {
}

@NotNull
private static HashTree createHttpSampler(FileReference fr, String method, boolean verifyResponse) {
private static HashTree createHttpSampler(String domain, int port, FileReference fr, String method, boolean verifyResponse) {
HTTPSampler httpSampler = new HTTPSampler();
httpSampler.setProperty(TestElement.TEST_CLASS, HTTPSampler.class.getName());
httpSampler.setName(method + " Sampler");
httpSampler.setMethod(method);
httpSampler.setDomain("localhost");
httpSampler.setPort(80);
httpSampler.setDomain(domain);
httpSampler.setPort(port);
httpSampler.setPath(fr.filename);
var hashTree = new ListedHashTree();
hashTree.add(httpSampler)
Expand Down Expand Up @@ -167,4 +196,4 @@ public static class FileReference {
public final int count;
public final boolean verifyResponse;
}
}
}