Improve and test `jetty.sh` behaviors (#10753)

* Issue #10696 - Addressing start-stop-daemon behaviors in jetty.sh
* disable internal pid-file management of start-stop-daemon
* IssueDo not test for file system permissions if user is root, or process will switch to JETTY_USER
* Fixing bad UID / JETTY_USER condition
* Avoid FS test with setuid use as well
* Fixing stop behavior
* Adding jetty.sh docker testing

---------

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Signed-off-by: Olivier Lamy <olamy@apache.org>
Co-authored-by: Olivier Lamy <olamy@apache.org>
This commit is contained in:
Joakim Erdfelt 2023-10-25 09:32:15 -05:00 committed by GitHub
parent 8b5deea657
commit 92f62a10f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 622 additions and 32 deletions

View File

@ -216,6 +216,46 @@ pidKill()
fi
}
testFileSystemPermissions()
{
# Don't test file system permissions if user is root
if [ $UID -eq 0 ] ; then
(( DEBUG )) && echo "Not testing file system permissions: uid is 0"
return 0
fi
# Don't test if JETTY_USER is specified
# as the Jetty process will switch to a different user id on startup
if [ -n "$JETTY_USER" ] ; then
(( DEBUG )) && echo "Not testing file system permissions: JETTY_USER=$JETTY_USER"
return 0
fi
# Don't test if setuid is specified
# as the Jetty process will switch to a different user id on startup
if expr "${JETTY_ARGS[*]}" : '.*setuid.*' >/dev/null
then
(( DEBUG )) && echo "Not testing file system permissions: setuid in use"
return 0
fi
# Test if PID can be written from this userid
if ! touch "$JETTY_PID"
then
echo "** ERROR: Unable to touch file: $JETTY_PID"
echo " Correct issues preventing use of \$JETTY_PID and try again."
exit 1
fi
# Test if STATE can be written from this userid
if ! touch "$JETTY_STATE"
then
echo "** ERROR: Unable to touch file: $JETTY_STATE"
echo " Correct issues preventing use of \$JETTY_STATE and try again."
exit 1
fi
}
readConfig()
{
(( DEBUG )) && echo "Reading $1.."
@ -240,6 +280,10 @@ dumpEnv()
echo "JETTY_START_TIMEOUT = $JETTY_START_TIMEOUT"
echo "JETTY_SYS_PROPS = $JETTY_SYS_PROPS"
echo "RUN_ARGS = ${RUN_ARGS[*]}"
echo "ID = $(id)"
echo "JETTY_USER = $JETTY_USER"
echo "USE_START_STOP_DAEMON = $USE_START_STOP_DAEMON"
echo "START_STOP_DAEMON = $START_STOP_DAEMON_AVAILABLE"
}
@ -249,6 +293,7 @@ dumpEnv()
CONFIGS=()
NO_START=0
DEBUG=0
USE_START_STOP_DAEMON=1
while [[ $1 = -* ]]; do
case $1 in
@ -404,7 +449,6 @@ case "`uname`" in
CYGWIN*) JETTY_STATE="`cygpath -w $JETTY_STATE`";;
esac
JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE" "jetty.pid=$JETTY_PID")
##################################################
@ -412,6 +456,7 @@ JETTY_ARGS=(${JETTY_ARGS[*]} "jetty.state=$JETTY_STATE" "jetty.pid=$JETTY_PID")
##################################################
if [ -f "$JETTY_CONF" ] && [ -r "$JETTY_CONF" ]
then
(( DEBUG )) && echo "$JETTY_CONF: (begin read) JETTY_ARGS.length=${#JETTY_ARGS[@]}"
while read -r CONF
do
if expr "$CONF" : '#' >/dev/null ; then
@ -427,16 +472,17 @@ then
do
if [ -r "$XMLFILE" ] && [ -f "$XMLFILE" ]
then
JETTY_ARGS=(${JETTY_ARGS[*]} "$XMLFILE")
JETTY_ARGS[${#JETTY_ARGS[@]}]=$XMLFILE
else
echo "** WARNING: Cannot read '$XMLFILE' specified in '$JETTY_CONF'"
fi
done
else
# assume it's a command line parameter (let start.jar deal with its validity)
JETTY_ARGS=(${JETTY_ARGS[*]} "$CONF")
JETTY_ARGS[${#JETTY_ARGS[@]}]=$CONF
fi
done < "$JETTY_CONF"
(( DEBUG )) && echo "$JETTY_CONF: (finished read) JETTY_ARGS.length=${#JETTY_ARGS[@]}"
fi
##################################################
@ -507,8 +553,22 @@ case "`uname`" in
CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";;
esac
# Determine if we can use start-stop-daemon or not
START_STOP_DAEMON_AVAILABLE=0
if (( USE_START_STOP_DAEMON ))
then
# only if root user is executing jetty.sh, and the start-stop-daemon exists
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1
then
START_STOP_DAEMON_AVAILABLE=1
else
USE_START_STOP_DAEMON=0
fi
fi
# Collect the dry-run (of opts,path,main,args) from the jetty.base configuration
JETTY_DRY_RUN=$("$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args ${JETTY_ARGS[*]} ${JAVA_OPTIONS[*]})
JETTY_DRY_RUN=$(echo "${JETTY_ARGS[*]} ${JAVA_OPTIONS[*]}" | xargs "$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args)
RUN_ARGS=($JETTY_SYS_PROPS ${JETTY_DRY_RUN[@]})
if (( DEBUG ))
@ -531,24 +591,12 @@ case "$ACTION" in
exit
fi
if ! touch "$JETTY_PID"
then
echo "** ERROR: Unable to touch file: $JETTY_PID"
echo " Correct issues preventing use of \$JETTY_PID and try again."
exit 1
fi
if ! touch "$JETTY_STATE"
then
echo "** ERROR: Unable to touch file: $JETTY_STATE"
echo " Correct issues preventing use of \$JETTY_STATE and try again."
exit 1
fi
testFileSystemPermissions
echo -n "Starting Jetty: "
# Startup from a service file
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1
if (( USE_START_STOP_DAEMON ))
then
unset CH_USER
if [ -n "$JETTY_USER" ]
@ -556,13 +604,14 @@ case "$ACTION" in
CH_USER="--chuid $JETTY_USER"
fi
echo ${RUN_ARGS[@]} --start-log-file="$JETTY_START_LOG" | xargs start-stop-daemon \
# use of --pidfile /dev/null disables internal pidfile
# management of the start-stop-daemon (see man page)
echo ${RUN_ARGS[@]} | xargs start-stop-daemon \
--start $CH_USER \
--pidfile "$JETTY_PID" \
--pidfile /dev/null \
--chdir "$JETTY_BASE" \
--background \
--output "${JETTY_RUN}/start-stop.log"
--make-pidfile \
--output "${JETTY_RUN}/start-stop.log" \
--startas "$JAVA" \
--
(( DEBUG )) && echo "Starting: start-stop-daemon"
@ -618,25 +667,41 @@ case "$ACTION" in
stop)
echo -n "Stopping Jetty: "
# Stop from a service file
if [ $UID -eq 0 ] && type start-stop-daemon > /dev/null 2>&1; then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s HUP
if [ ! -r "$JETTY_PID" ] ; then
echo "** ERROR: no pid found at $JETTY_PID"
exit 1
fi
PID=$(tail -1 "$JETTY_PID")
if [ -z "$PID" ] ; then
echo "** ERROR: no pid found in $JETTY_PID"
exit 1
fi
# Stopping service started with start-stop-daemon
if (( USE_START_STOP_DAEMON )) ; then
(( DEBUG )) && echo "Issuing HUP to $PID"
start-stop-daemon --stop \
--pid "$PID" \
--chdir "$JETTY_BASE" \
--startas "$JAVA" \
--signal HUP
TIMEOUT=30
while running "$JETTY_PID"; do
(( DEBUG )) && echo "Issuing KILL to $PID"
if (( TIMEOUT-- == 0 )); then
start-stop-daemon -K -p"$JETTY_PID" -d"$JETTY_HOME" -a "$JAVA" -s KILL
start-stop-daemon --stop \
--pid "$PID" \
--chdir "$JETTY_BASE" \
--startas "$JAVA" \
--signal KILL
fi
sleep 1
done
else
# Stop from a non-service path
if [ ! -r "$JETTY_PID" ] ; then
echo "** ERROR: no pid found at $JETTY_PID"
exit 1
fi
# Stopping from non-service start
pidKill "$JETTY_PID" 30
fi

View File

@ -0,0 +1,51 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
import java.util.function.Consumer;
import org.testcontainers.images.builder.ImageFromDockerfile;
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
/**
* Simplify the use of {@link ImageFromDockerfile} to get some sanity in naming convention
* and {@link #toString()} behaviors so that Test execution makes sense.
*/
public class ImageFromDSL extends ImageFromDockerfile
{
private ImageFromDSL parentImage;
public ImageFromDSL(ImageFromDSL baseImage, String suffix, Consumer<DockerfileBuilder> builderConsumer)
{
this(baseImage.getDockerImageName() + "-" + suffix, builderConsumer);
this.parentImage = baseImage;
}
public ImageFromDSL(String name, Consumer<DockerfileBuilder> builderConsumer)
{
super(name, false);
withDockerfileFromBuilder(builderConsumer);
}
public ImageFromDSL getParentImage()
{
return parentImage;
}
@Override
public String toString()
{
return getDockerImageName();
}
}

View File

@ -0,0 +1,55 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
import java.io.File;
import java.nio.file.Path;
import java.util.function.Consumer;
import org.eclipse.jetty.tests.hometester.JettyHomeTester;
import org.testcontainers.images.builder.dockerfile.DockerfileBuilder;
public abstract class ImageOS extends ImageFromDSL
{
public static final String REGISTRY = "registry.jetty.org";
public static final String REPOSITORY = REGISTRY + "/jetty-sh";
private Path jettyHomePath;
public ImageOS(String osid, Consumer<DockerfileBuilder> builderConsumer)
{
super(REPOSITORY + ":" + osid, builderConsumer);
}
protected File getJettyHomeDir()
{
if (jettyHomePath == null)
{
String jettyVersion = System.getProperty("jettyVersion");
try
{
JettyHomeTester homeTester = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
jettyHomePath = homeTester.getJettyHome();
}
catch (Exception e)
{
throw new RuntimeException("Unable to get unpacked JETTY_HOME dir", e);
}
}
return jettyHomePath.toFile();
}
}

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
/**
* An OS Image of linux specific for running on Amazon AWS.
* This is based on Amazon Linux 2 (which is based on Alpine 3).
* Amazon Corretto JDK 11 is installed.
* This image does NOT come with start-stop-daemon installed.
* Instead of apt, it uses yum (the redhat package manager)
*/
public class ImageOSAmazonCorretto11 extends ImageOS
{
public ImageOSAmazonCorretto11()
{
super("amazoncorretto-jdk11",
builder ->
builder
.from("amazoncorretto:11.0.20")
.run("yum update -y ; " +
"yum install -y curl tar gzip vim shadow-utils net-tools")
.env("TEST_DIR", "/var/test")
.env("JETTY_HOME", "$TEST_DIR/jetty-home")
.env("JETTY_BASE", "$TEST_DIR/jetty-base")
.env("PATH", "$PATH:${JETTY_HOME}/bin/")
.user("root")
// Configure /etc/default/jetty
.run("echo \"JETTY_HOME=${JETTY_HOME}\" > /etc/default/jetty ; " +
"echo \"JETTY_BASE=${JETTY_BASE}\" >> /etc/default/jetty ; " +
"echo \"JETTY_RUN=${JETTY_BASE}\" >> /etc/default/jetty ")
// setup Jetty Home
.copy("/opt/jetty/", "${JETTY_HOME}/")
.env("PATH", "$PATH:${JETTY_HOME}/bin/")
.run("chmod ugo+x ${JETTY_HOME}/bin/jetty.sh")
.build()
);
withFileFromFile("/opt/jetty/", getJettyHomeDir());
}
}

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
/**
* An OS Image of Ubuntu Linux 22.04,
* Adding JDK17 toolchain.
* it comes with start-stop-daemon installed
*/
public class ImageOSUbuntuJammyJDK17 extends ImageOS
{
public ImageOSUbuntuJammyJDK17()
{
super("ubuntu-22.04-jdk17",
builder ->
builder
.from("ubuntu:22.04")
.run("apt update ; " +
"apt -y upgrade ; " +
"apt install -y openjdk-17-jdk-headless ; " +
"apt install -y curl vim net-tools ")
.env("TEST_DIR", "/var/test")
.env("JETTY_HOME", "$TEST_DIR/jetty-home")
.env("JETTY_BASE", "$TEST_DIR/jetty-base")
.env("PATH", "$PATH:${JETTY_HOME}/bin/")
.user("root")
// Configure /etc/default/jetty
.run("echo \"JETTY_HOME=${JETTY_HOME}\" > /etc/default/jetty ; " +
"echo \"JETTY_BASE=${JETTY_BASE}\" >> /etc/default/jetty ; " +
"echo \"JETTY_RUN=${JETTY_BASE}\" >> /etc/default/jetty ")
// setup Jetty Home
.copy("/opt/jetty/", "${JETTY_HOME}/")
.env("PATH", "$PATH:${JETTY_HOME}/bin/")
.run("chmod ugo+x ${JETTY_HOME}/bin/jetty.sh")
.build()
);
withFileFromFile("/opt/jetty/", getJettyHomeDir());
}
}

View File

@ -0,0 +1,40 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
/**
* A docker image with JETTY_USER set to id `jetty`.
* JETTY_HOME is owned by `root`.
* JETTY_BASE is owned by `jetty`
*/
public class ImageUserChange extends ImageFromDSL
{
public ImageUserChange(ImageOS osImage)
{
super(osImage, "user-change", builder ->
builder
.from(osImage.getDockerImageName())
// setup "jetty" user and Jetty Base directory
.run("chmod ugo+x ${JETTY_HOME}/bin/jetty.sh ; " +
"mkdir -p ${JETTY_BASE} ; " +
"useradd --home-dir=${JETTY_BASE} --shell=/bin/bash jetty ; " +
"chown jetty:jetty ${JETTY_BASE} ; " +
"chmod a+w ${JETTY_BASE} ; " +
"echo \"JETTY_USER=jetty\" > /etc/default/jetty") // user change
.user("jetty")
// Configure Jetty Base
.workDir("${JETTY_BASE}")
.build());
}
}

View File

@ -0,0 +1,33 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
/**
* A docker image with no JETTY_USER set, everything executes as `root`.
*/
public class ImageUserRoot extends ImageFromDSL
{
public ImageUserRoot(ImageOS osImage)
{
super(osImage, "user-root", builder ->
builder
.from(osImage.getDockerImageName())
.run("mkdir -p ${JETTY_BASE} ; " +
"chmod u+x ${JETTY_HOME}/bin/jetty.sh ; " +
"chmod a+w ${JETTY_BASE}")
// Configure Jetty Base
.workDir("${JETTY_BASE}")
.build());
}
}

View File

@ -0,0 +1,221 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.tests.distribution.jettysh;
import java.net.URI;
import java.nio.channels.AsynchronousCloseException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import org.awaitility.Awaitility;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.tests.distribution.AbstractJettyHomeTest;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.Container;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.startupcheck.IsRunningStartupCheckStrategy;
import org.testcontainers.containers.wait.strategy.ShellStrategy;
import org.testcontainers.images.PullPolicy;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesRegex;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Test of jetty-home/bin/jetty.sh as generic start mechanism.
*/
public class JettyShStartTest extends AbstractJettyHomeTest
{
private static final Logger LOG = LoggerFactory.getLogger(JettyShStartTest.class);
public static Stream<Arguments> jettyImages()
{
List<ImageFromDSL> images = new ArrayList<>();
// Loop through all OS images
for (ImageOS osImage : List.of(new ImageOSUbuntuJammyJDK17(), new ImageOSAmazonCorretto11()))
{
// Establish user Images based on OS Image
List<ImageFromDSL> userImages = new ArrayList<>();
userImages.add(new ImageUserRoot(osImage));
userImages.add(new ImageUserChange(osImage));
// Loop through user Images to establish various JETTY_BASE configurations
for (ImageFromDSL userImage : userImages)
{
// Basic JETTY_BASE
images.add(new ImageFromDSL(userImage, "base-basic", builder ->
builder
.from(userImage.getDockerImageName())
// Create a basic configuration of jetty-base
.run("java -jar ${JETTY_HOME}/start.jar --add-modules=http,deploy")
.build()));
// Complex JETTY_BASE with spaces
Path basesWithSpaces = MavenPaths.findTestResourceDir("bases/spaces-with-conf");
ImageFromDSL baseComplexWithSpacesImage = new ImageFromDSL(userImage, "base-complex-with-spaces", builder ->
{
builder
.from(userImage.getDockerImageName())
// Create a basic configuration of jetty-base
.run("java -jar ${JETTY_HOME}/start.jar --add-modules=http,deploy")
.copy("/tests/bases-with-spaces/", "${JETTY_BASE}/");
if (userImage instanceof ImageUserChange)
{
// Make sure we change the ownership of JETTY_BASE if we are testing a user change mode
builder.user("root")
.run("chown -R jetty:jetty $JETTY_BASE")
.user("jetty");
}
builder.build();
});
baseComplexWithSpacesImage.withFileFromFile("/tests/bases-with-spaces/", basesWithSpaces.toFile());
images.add(baseComplexWithSpacesImage);
}
}
return images.stream().map(Arguments::of);
}
@ParameterizedTest
@MethodSource("jettyImages")
public void testStartStopJettyBase(ImageFromDSL jettyImage) throws Exception
{
ensureParentImagesExist(jettyImage);
try (GenericContainer<?> genericContainer = new GenericContainer<>(jettyImage))
{
genericContainer.withImagePullPolicy(PullPolicy.defaultPolicy());
genericContainer.setWaitStrategy(new ShellStrategy().withCommand("id"));
genericContainer.withExposedPorts(80, 8080) // jetty
.withCommand("/bin/sh", "-c", "while true; do pwd | nc -l -p 80; done")
.withStartupAttempts(2)
.withStartupCheckStrategy(new IsRunningStartupCheckStrategy())
.start();
LOG.info("Started: " + jettyImage.getDockerImageName());
System.err.println("== jetty.sh start ==");
Container.ExecResult result = genericContainer.execInContainer("/var/test/jetty-home/bin/jetty.sh", "start");
assertThat(result.getExitCode(), is(0));
/*
* Example successful output
* ----
* STDOUT:
* Starting Jetty: . started
* OK Wed Oct 18 19:29:35 UTC 2023
* ----
*/
Awaitility.await().atMost(Duration.ofSeconds(5)).until(result::getStdout,
allOf(
containsString("Starting Jetty:"),
containsString("\nOK ")
));
startHttpClient();
URI containerUriRoot = URI.create("http://" + genericContainer.getHost() + ":" + genericContainer.getMappedPort(8080) + "/");
LOG.debug("Container URI Root: {}", containerUriRoot);
System.err.println("== Attempt GET request to service ==");
ContentResponse response = client.GET(containerUriRoot);
assertEquals(HttpStatus.NOT_FOUND_404, response.getStatus(), new ResponseDetails(response));
assertThat(response.getContentAsString(), containsString("Powered by Eclipse Jetty:// Server"));
System.err.println("== jetty.sh status (should be running) ==");
result = genericContainer.execInContainer("/var/test/jetty-home/bin/jetty.sh", "status");
assertThat(result.getExitCode(), is(0));
Awaitility.await().atMost(Duration.ofSeconds(5)).until(result::getStdout,
containsString("Jetty running pid"));
System.err.println("== jetty.sh stop ==");
result = genericContainer.execInContainer("/var/test/jetty-home/bin/jetty.sh", "stop");
assertThat(result.getExitCode(), is(0));
/* Looking for output from jetty.sh indicating a stopped jetty.
* STDOUT Example 1
* ----
* Stopping Jetty: OK\n
* ----
* STOUT Example 2
* ----
* Stopping Jetty: .Killed 12345\n
* OK\n
* ----
*/
Awaitility.await().atMost(Duration.ofSeconds(5)).until(result::getStdout,
matchesRegex("Stopping Jetty: .*[\n]?OK[\n]"));
System.err.println("== jetty.sh status (should be stopped) ==");
result = genericContainer.execInContainer("/var/test/jetty-home/bin/jetty.sh", "status");
assertThat(result.getExitCode(), is(1));
Awaitility.await().atMost(Duration.ofSeconds(5)).until(result::getStdout,
containsString("Jetty NOT running"));
System.err.println("== Attempt GET request to non-existent service ==");
client.setConnectTimeout(1000);
Exception failedGetException = assertThrows(Exception.class, () -> client.GET(containerUriRoot));
// GET failure can result in either exception below (which one is based on timing / race)
assertThat(failedGetException, anyOf(
instanceOf(ExecutionException.class),
instanceOf(AsynchronousCloseException.class))
);
}
}
private void ensureParentImagesExist(ImageFromDSL jettyImage)
{
// The build stack for images
Stack<ImageFromDSL> images = new Stack<>();
ImageFromDSL parent = jettyImage;
while ((parent = parent.getParentImage()) != null)
{
images.push(parent);
}
// Create the images (allowing testcontainers cache to do its thing)
while (!images.isEmpty())
{
ImageFromDSL image = images.pop();
createImage(image);
}
}
private void createImage(ImageFromDSL image)
{
LOG.debug("Create Image: {}", image.getDockerImageName());
try (GenericContainer<?> container = new GenericContainer<>(image))
{
container.start();
}
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
</Configure>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
</Configure>

View File

@ -0,0 +1,4 @@
# test configurations with spaces
'dir one/test1.xml'
'dir two/test2.xml'
--module=state,pid

View File

@ -0,0 +1 @@
*.log

View File

@ -0,0 +1,5 @@
[exec]
-showversion
-XX:+PrintCommandLineFlags
-Xlog:gc*:file=/var/test/jetty-base/logs/gc.log:time,level,tags
-XX:ErrorFile=/var/test/jetty-base/logs/jvm_crash_pid_%p.log

View File

@ -1,4 +1,8 @@
# Jetty Logging using jetty-slf4j-impl
org.eclipse.jetty.logging.appender.MESSAGE_ESCAPE=false
org.eclipse.jetty.logging.appender.NAME_CONDENSE=false
org.eclipse.jetty.LEVEL=INFO
org.testcontainers.LEVEL=INFO
#org.testcontainers.LEVEL=DEBUG
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.tests.distribution.LEVEL=DEBUG