From a1e0f2f5f069a927661a9d4c6d9f86057d45ac1c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 11 Jan 2016 17:50:31 +0000 Subject: [PATCH 1/4] Update launch script to wait for pid to be written to pid file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the launch script would wait for the pid file to exist before continuing. This didn’t work 100% of the time as it left a window where the file had been created but the PID had not been written to it that could result in an incorrect report of the app failing to start. This commit updates the script to wait for the file to have a size greater than zero before continuing. This ensures that the pid has been written to the file before the pid is read from the file and used to check that the process is running. Closes gh-4923 --- .../org/springframework/boot/loader/tools/launch.script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script index 73a1137a86..d6778a46a3 100755 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script @@ -85,7 +85,7 @@ isRunning() { await_file() { end=$(date +%s) let "end+=10" - while [[ ! -f "$1" ]] + while [[ ! -s "$1" ]] do now=$(date +%s) if [[ $now -ge $end ]]; then From 04fe55a2ab6196e046f269c195d8693181ab1056 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 11 Jan 2016 17:53:51 +0000 Subject: [PATCH 2/4] Improve portability by using type instead of which in the launch script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the launch script used which to determine the availability of start-stop-daemon. which isn’t available by default on all OSs. For example, it’s not available by default on CentOS 5. This commit updates the launch script to use type when determining if start-stop-daemon is available. This improves the portability of the script and makes the use of type consistent throughout the script. Closes gh-4925 --- .../org/springframework/boot/loader/tools/launch.script | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script index d6778a46a3..b3af16348b 100755 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script @@ -146,7 +146,7 @@ do_start() { chown "$run_user" "$PID_FOLDER" chown "$run_user" "$pid_file" chown "$run_user" "$log_file" - if [ "${useStartStopDaemon:-true}" = true ] && which start-stop-daemon >/dev/null; then + if [ "${useStartStopDaemon:-true}" = true ] && type start-stop-daemon > /dev/null 2>&1; then start-stop-daemon --start --quiet \ --chuid "$run_user" \ --name "$identity" \ From c39a55a27043fc0a49005ba7c7eea3f71c02fa45 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 11 Jan 2016 17:57:38 +0000 Subject: [PATCH 3/4] Fix quoting of start-stop-daemon RUN_ARGS and JAVA_OPTS in launch script 81a4763 introduced a regression when multiple RUN_ARGS or JAVA_OPTS were configured. Rather than the JVM being launched with multiple arguments all of the RUN_ARGS or JAVA_OPTS were passed as a single argument. This caused unexpected behaviour and typically caused the application to fail to start. This commit updates the quoting of the arguments the are supplied when launching the app using start-stop-daemon so that space-separated entries in RUN_ARGS and JAVA_OPTS remain separate. Closes gh-4866 --- .../org/springframework/boot/loader/tools/launch.script | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script index b3af16348b..0e80f4eac8 100755 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script @@ -147,6 +147,7 @@ do_start() { chown "$run_user" "$pid_file" chown "$run_user" "$log_file" if [ "${useStartStopDaemon:-true}" = true ] && type start-stop-daemon > /dev/null 2>&1; then + arguments="-Dsun.misc.URLClassPath.disableJarChecking=true $JAVA_OPTS -jar $jarfile $RUN_ARGS $*" start-stop-daemon --start --quiet \ --chuid "$run_user" \ --name "$identity" \ @@ -154,9 +155,7 @@ do_start() { --background --no-close \ --startas "$javaexe" \ --chdir "$working_dir" \ - -- \ - -Dsun.misc.URLClassPath.disableJarChecking=true "${JAVA_OPTS[@]}" \ - -jar "$jarfile" "${RUN_ARGS[@]}" "$@" \ + -- $arguments \ >> "$log_file" 2>&1 await_file "$pid_file" else From d1b47c8a8f1943330cac0f60cc737006986b6ae5 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 11 Jan 2016 18:01:15 +0000 Subject: [PATCH 4/4] Add integration tests for default launch script This commit adds a suit of integration tests for the launch script. See the accompanying README.adoc for further details. Closes gh-4872 --- spring-boot-integration-tests/pom.xml | 2 + .../README.adoc | 75 ++++ .../spring-boot-launch-script-tests/pom.xml | 84 ++++ .../LaunchScriptTestApplication.java | 29 ++ .../LaunchVerficationController.java | 30 ++ .../launchscript/SysVinitLaunchScriptIT.java | 365 ++++++++++++++++++ .../resources/conf/CentOS/5.11/Dockerfile | 11 + .../test/resources/conf/CentOS/6.7/Dockerfile | 11 + .../resources/conf/Ubuntu/14.04.3/Dockerfile | 8 + .../src/test/resources/logback.xml | 13 + .../test/resources/scripts/basic-launch.sh | 5 + ...ch-with-multiple-command-line-arguments.sh | 5 + .../scripts/launch-with-multiple-java-opts.sh | 6 + .../scripts/launch-with-multiple-run-args.sh | 6 + ...aunch-with-single-command-line-argument.sh | 5 + .../scripts/launch-with-single-java-opt.sh | 6 + .../scripts/launch-with-single-run-arg.sh | 6 + .../resources/scripts/restart-when-started.sh | 7 + .../resources/scripts/restart-when-stopped.sh | 5 + .../resources/scripts/start-when-started.sh | 6 + .../resources/scripts/start-when-stopped.sh | 5 + .../resources/scripts/status-when-killed.sh | 8 + .../resources/scripts/status-when-started.sh | 6 + .../resources/scripts/status-when-stopped.sh | 4 + .../resources/scripts/stop-when-stopped.sh | 4 + .../test/resources/scripts/test-functions.sh | 40 ++ .../checkstyle/checkstyle-suppressions.xml | 1 + 27 files changed, 753 insertions(+) create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/README.adoc create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/pom.xml create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchScriptTestApplication.java create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchVerficationController.java create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIT.java create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/5.11/Dockerfile create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/6.7/Dockerfile create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/Ubuntu/14.04.3/Dockerfile create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/logback.xml create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/basic-launch.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-command-line-arguments.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-java-opts.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-run-args.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-command-line-argument.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-java-opt.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-run-arg.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-started.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-stopped.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-started.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-stopped.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-killed.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-started.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-stopped.sh create mode 100755 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/stop-when-stopped.sh create mode 100644 spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/test-functions.sh diff --git a/spring-boot-integration-tests/pom.xml b/spring-boot-integration-tests/pom.xml index a7459ddf00..06f8844b07 100644 --- a/spring-boot-integration-tests/pom.xml +++ b/spring-boot-integration-tests/pom.xml @@ -19,9 +19,11 @@ ${basedir}/.. 1.8 + 2.1.2 spring-boot-gradle-tests + spring-boot-launch-script-tests spring-boot-security-tests diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/README.adoc b/spring-boot-integration-tests/spring-boot-launch-script-tests/README.adoc new file mode 100644 index 0000000000..3a4739015e --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/README.adoc @@ -0,0 +1,75 @@ += Spring Boot Launch Script Tests + +This module contains integration tests for the default launch script that is used +to make a jar file fully executable on Linux. The tests use Docker to verify the +functionality in a variety of Linux distributions. + +== Setting up Docker + +The setup that's required varies depending on your operating system. + +=== Docker on OS X + +Docker relies on Linux kernel features so the Docker Daemon must be run inside a Linux VM. +Following the https://docs.docker.com/engine/installation/mac/[OS X installation +instructions] to install Docker Toolbox which uses VirtualBox to host the required VM. + +=== Docker on Linux + +Install Docker as appropriate for your Linux distribution. See the +https://docs.docker.com/engine/installation/[Linux installation instructions] for more +information. + +Next, add your user to the `docker` group. For example: + +---- +$ sudo usermod -a -G docker awilkinson +---- + +You may need to log out and back in again for this change to take affect and for your +user to be able to connect to the daemon. + +== Preparing to run the tests + +Before running the tests, you must prepare your environment according to your operating +system. + +=== Preparation on OS X + +The tests must be run in an environment where various environment variables including +`DOCKER_HOST` and `DOCKER_CERT_PATH` have been set: + +---- +$ eval $(docker-machine env default) +---- + +=== Preparation on Linux + +Docker Daemon's default configuration on Linux uses a Unix socket for communication. +However, Docker's Java client uses HTTP by default. Docker Java's client can be configured +to use the Unix socket via the `DOCKER_URL` environment variable: + +---- +$ export DOCKER_URL=unix:///var/run/docker.sock +---- + +== Running the tests + +You're now ready to run the tests. Assuming that you're in the same directory as this +README, the tests can be launched as follows: + +---- +$ mvn -Pdocker clean verify +---- + +The first time the tests are run, Docker will create the container images that are used to +run the tests. This can take several minutes, particularly if you have a slow network +connection. Subsequent runs will be faster as the images are cached locally. You can run +`docker images` to see a list of the cached images. + +== Cleaning up + +If you want to reclaim the disk space used by the cached images (at the expense of having +to wait for them to be downloaded and rebuilt the next time you run the tests), you can +use `docker images` to list the images and `docker rmi ` to delete them. See +`docker rmi --help` for further details. \ No newline at end of file diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/pom.xml b/spring-boot-integration-tests/spring-boot-launch-script-tests/pom.xml new file mode 100644 index 0000000000..04f83a5f24 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/pom.xml @@ -0,0 +1,84 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-integration-tests + 1.3.2.BUILD-SNAPSHOT + + spring-boot-launch-script-tests + jar + Spring Boot Launch Script Integration Tests + Spring Boot Launch Script Integration Tests + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-undertow + + + com.github.docker-java + docker-java + 2.1.4 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + + docker + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + + + + diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchScriptTestApplication.java b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchScriptTestApplication.java new file mode 100644 index 0000000000..dae663ff17 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchScriptTestApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot.launchscript; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LaunchScriptTestApplication { + + public static void main(String[] args) { + SpringApplication.run(LaunchScriptTestApplication.class, args); + } + +} diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchVerficationController.java b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchVerficationController.java new file mode 100644 index 0000000000..d5f0d69748 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/main/java/org/springframework/boot/launchscript/LaunchVerficationController.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot.launchscript; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class LaunchVerficationController { + + @RequestMapping("/") + public String verifyLaunch() { + return "Launched"; + } + +} diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIT.java b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIT.java new file mode 100644 index 0000000000..69e5038190 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/java/org/springframework/boot/launchscript/SysVinitLaunchScriptIT.java @@ -0,0 +1,365 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * 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 org.springframework.boot.launchscript; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.DockerCmd; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.core.CompressArchiveUtil; +import com.github.dockerjava.core.DockerClientBuilder; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.command.AttachContainerResultCallback; +import com.github.dockerjava.core.command.BuildImageResultCallback; +import com.github.dockerjava.jaxrs.AbstrSyncDockerCmdExec; +import com.github.dockerjava.jaxrs.DockerCmdExecFactoryImpl; +import org.hamcrest.Matcher; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import org.springframework.boot.ansi.AnsiColor; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +/** + * Integration tests for Spring Boot's launch script on OSs that use SysVinit. + * + * @author Andy Wilkinson + */ +@RunWith(Parameterized.class) +public class SysVinitLaunchScriptIT { + + private final SpringBootDockerCmdExecFactory commandExecFactory = new SpringBootDockerCmdExecFactory(); + + private static final char ESC = 27; + + private final String os; + + private final String version; + + @Parameters(name = "{0} {1}") + public static List parameters() { + List parameters = new ArrayList(); + for (File os : new File("src/test/resources/conf").listFiles()) { + for (File version : os.listFiles()) { + parameters.add(new Object[] { os.getName(), version.getName() }); + } + } + return parameters; + } + + public SysVinitLaunchScriptIT(String os, String version) { + this.os = os; + this.version = version; + } + + @Test + public void statusWhenStopped() throws Exception { + String output = doTest("status-when-stopped.sh"); + assertThat(output, containsString("Status: 3")); + assertThat(output, containsColoredString(AnsiColor.RED, "Not running")); + } + + @Test + public void statusWhenStarted() throws Exception { + String output = doTest("status-when-started.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Started [" + extractPid(output) + "]")); + } + + @Test + public void statusWhenKilled() throws Exception { + String output = doTest("status-when-killed.sh"); + assertThat(output, containsString("Status: 1")); + assertThat(output, containsColoredString(AnsiColor.RED, + "Not running (process " + extractPid(output) + " not found)")); + } + + @Test + public void stopWhenStopped() throws Exception { + String output = doTest("stop-when-stopped.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.YELLOW, + "Not running (pidfile not found)")); + } + + @Test + public void startWhenStarted() throws Exception { + String output = doTest("start-when-started.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.YELLOW, + "Already running [" + extractPid(output) + "]")); + } + + @Test + public void restartWhenStopped() throws Exception { + String output = doTest("restart-when-stopped.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.YELLOW, + "Not running (pidfile not found)")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Started [" + extractPid(output) + "]")); + } + + @Test + public void restartWhenStarted() throws Exception { + String output = doTest("restart-when-started.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Started [" + extract("PID1", output) + "]")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Stopped [" + extract("PID1", output) + "]")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Started [" + extract("PID2", output) + "]")); + } + + @Test + public void startWhenStopped() throws Exception { + String output = doTest("start-when-stopped.sh"); + assertThat(output, containsString("Status: 0")); + assertThat(output, containsColoredString(AnsiColor.GREEN, + "Started [" + extractPid(output) + "]")); + } + + @Test + public void basicLaunch() throws Exception { + doLaunch("basic-launch.sh"); + } + + @Test + public void launchWithSingleCommandLineArgument() throws Exception { + doLaunch("launch-with-single-command-line-argument.sh"); + } + + @Test + public void launchWithMultipleCommandLineArguments() throws Exception { + doLaunch("launch-with-multiple-command-line-arguments.sh"); + } + + @Test + public void launchWithSingleRunArg() throws Exception { + doLaunch("launch-with-single-run-arg.sh"); + } + + @Test + public void launchWithMultipleRunArgs() throws Exception { + doLaunch("launch-with-multiple-run-args.sh"); + } + + @Test + public void launchWithSingleJavaOpt() throws Exception { + doLaunch("launch-with-single-java-opt.sh"); + } + + @Test + public void launchWithMultipleJavaOpts() throws Exception { + doLaunch("launch-with-multiple-java-opts.sh"); + } + + private void doLaunch(String script) throws Exception { + assertThat(doTest(script), containsString("Launched")); + } + + private String doTest(String script) throws Exception { + DockerClient docker = createClient(); + String imageId = buildImage(docker); + String container = createContainer(docker, imageId, script); + copyFilesToContainer(docker, container, script); + docker.startContainerCmd(container).exec(); + StringBuilder output = new StringBuilder(); + AttachContainerResultCallback resultCallback = docker + .attachContainerCmd(container).withStdOut(true).withStdErr(true) + .withFollowStream(true).withLogs(true) + .exec(new AttachContainerResultCallback() { + + @Override + public void onNext(Frame item) { + output.append(new String(item.getPayload())); + super.onNext(item); + } + + }); + resultCallback.awaitCompletion(60, TimeUnit.SECONDS).close(); + docker.waitContainerCmd(container).exec(); + return output.toString(); + } + + private DockerClient createClient() { + DockerClientConfig config = DockerClientConfig.createDefaultConfigBuilder() + .build(); + DockerClient docker = DockerClientBuilder.getInstance(config) + .withDockerCmdExecFactory(this.commandExecFactory).build(); + return docker; + } + + private String buildImage(DockerClient docker) { + BuildImageResultCallback resultCallback = new BuildImageResultCallback(); + String dockerfile = "src/test/resources/conf/" + this.os + "/" + this.version + + "/Dockerfile"; + docker.buildImageCmd(new File(dockerfile)).exec(resultCallback); + String imageId = resultCallback.awaitImageId(); + return imageId; + } + + private String createContainer(DockerClient docker, String imageId, + String testScript) { + return docker.createContainerCmd(imageId).withTty(false).withCmd("/bin/bash", + "-c", "chmod +x " + testScript + " && ./" + testScript).exec().getId(); + } + + private void copyFilesToContainer(DockerClient docker, final String container, + String script) { + copyToContainer(docker, container, findApplication()); + copyToContainer(docker, container, + new File("src/test/resources/scripts/test-functions.sh")); + copyToContainer(docker, container, + new File("src/test/resources/scripts/" + script)); + } + + private void copyToContainer(DockerClient docker, final String container, + final File file) { + this.commandExecFactory.createCopyToContainerCmdExec() + .exec(new CopyToContainerCmd(container, file)); + } + + private File findApplication() { + File targetDir = new File("target"); + for (File file : targetDir.listFiles()) { + if (file.getName().startsWith("spring-boot-launch-script-tests") + && file.getName().endsWith(".jar") + && !file.getName().endsWith("-sources.jar")) { + return file; + } + } + throw new IllegalStateException( + "Could not find test application in target directory. Have you built it (mvn package)?"); + } + + private Matcher containsColoredString(AnsiColor color, String string) { + return containsString(ESC + "[0;" + color + "m" + string + ESC + "[0m"); + } + + private String extractPid(String output) { + return extract("PID", output); + } + + private String extract(String label, String output) { + Pattern pattern = Pattern.compile(".*" + label + ": ([0-9]+).*", Pattern.DOTALL); + java.util.regex.Matcher matcher = pattern.matcher(output); + if (matcher.matches()) { + return matcher.group(1); + } + throw new IllegalArgumentException( + "Failed to extract " + label + " from output: " + output); + } + + private static final class CopyToContainerCmdExec + extends AbstrSyncDockerCmdExec { + + private CopyToContainerCmdExec(WebTarget baseResource, + DockerClientConfig dockerClientConfig) { + super(baseResource, dockerClientConfig); + } + + @Override + protected Void execute(CopyToContainerCmd command) { + try { + InputStream streamToUpload = new FileInputStream(CompressArchiveUtil + .archiveTARFiles(command.getFile().getParentFile(), + Arrays.asList(command.getFile()), + command.getFile().getName())); + WebTarget webResource = getBaseResource().path("/containers/{id}/archive") + .resolveTemplate("id", command.getContainer()); + webResource.queryParam("path", ".") + .queryParam("noOverwriteDirNonDir", false).request() + .put(Entity.entity(streamToUpload, "application/x-tar")).close(); + return null; + } + catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + } + + private static final class CopyToContainerCmd implements DockerCmd { + + private final String container; + + private final File file; + + private CopyToContainerCmd(String container, File file) { + this.container = container; + this.file = file; + } + + public String getContainer() { + return this.container; + } + + public File getFile() { + return this.file; + } + + @Override + public void close() { + + } + + } + + private static final class SpringBootDockerCmdExecFactory + extends DockerCmdExecFactoryImpl { + + private SpringBootDockerCmdExecFactory() { + withClientRequestFilters(new ClientRequestFilter() { + + @Override + public void filter(ClientRequestContext requestContext) + throws IOException { + // Workaround for https://go-review.googlesource.com/#/c/3821/ + requestContext.getHeaders().add("Connection", "close"); + } + + }); + } + + private CopyToContainerCmdExec createCopyToContainerCmdExec() { + return new CopyToContainerCmdExec(getBaseResource(), getDockerClientConfig()); + } + + } + +} diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/5.11/Dockerfile b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/5.11/Dockerfile new file mode 100644 index 0000000000..e5b17b24ec --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/5.11/Dockerfile @@ -0,0 +1,11 @@ +FROM centos:5.11 +RUN yum install -y wget && \ + yum install -y system-config-services && \ + yum install -y curl && \ + wget --no-cookies \ + --no-check-certificate \ + --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" \ + --output-document jdk.rpm \ + http://download.oracle.com/otn-pub/java/jdk/8u66-b17/jdk-8u66-linux-x64.rpm && \ + yum --nogpg localinstall -y jdk.rpm && \ + rm -f jdk.rpm diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/6.7/Dockerfile b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/6.7/Dockerfile new file mode 100644 index 0000000000..919ab0f991 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/CentOS/6.7/Dockerfile @@ -0,0 +1,11 @@ +FROM centos:6.7 +RUN yum install -y wget && \ + yum install -y system-config-services && \ + yum install -y curl && \ + wget --no-cookies \ + --no-check-certificate \ + --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" \ + --output-document jdk.rpm \ + http://download.oracle.com/otn-pub/java/jdk/8u66-b17/jdk-8u66-linux-x64.rpm && \ + yum --nogpg localinstall -y jdk.rpm && \ + rm -f jdk.rpm diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/Ubuntu/14.04.3/Dockerfile b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/Ubuntu/14.04.3/Dockerfile new file mode 100644 index 0000000000..935cd05dd2 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/conf/Ubuntu/14.04.3/Dockerfile @@ -0,0 +1,8 @@ +FROM ubuntu:14.04.3 +RUN apt-get install -y software-properties-common && \ + add-apt-repository ppa:webupd8team/java -y && \ + apt-get update && \ + echo oracle-java7-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections && \ + apt-get install -y oracle-java8-installer && \ + apt-get install -y curl && \ + apt-get clean diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/logback.xml b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/logback.xml new file mode 100644 index 0000000000..51a2f698fa --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/basic-launch.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/basic-launch.sh new file mode 100755 index 0000000000..44f44a856c --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/basic-launch.sh @@ -0,0 +1,5 @@ +source ./test-functions.sh +install_service +start_service +await_app +curl -s http://127.0.0.1:8080/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-command-line-arguments.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-command-line-arguments.sh new file mode 100755 index 0000000000..7086d3051c --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-command-line-arguments.sh @@ -0,0 +1,5 @@ +source ./test-functions.sh +install_service +start_service --server.port=8081 --server.context-path=/test +await_app http://127.0.0.1:8081/test/ +curl -s http://127.0.0.1:8081/test/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-java-opts.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-java-opts.sh new file mode 100755 index 0000000000..701ba50ee8 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-java-opts.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +echo 'JAVA_OPTS="-Dserver.port=8081 -Dserver.context-path=/test"' > /spring-boot-app.conf +install_service +start_service +await_app http://127.0.0.1:8081/test/ +curl -s http://127.0.0.1:8081/test/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-run-args.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-run-args.sh new file mode 100755 index 0000000000..9ab7c4b869 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-multiple-run-args.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +echo 'RUN_ARGS="--server.port=8081 --server.context-path=/test"' > /spring-boot-app.conf +install_service +start_service +await_app http://127.0.0.1:8081/test/ +curl -s http://127.0.0.1:8081/test/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-command-line-argument.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-command-line-argument.sh new file mode 100755 index 0000000000..2adb76da3f --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-command-line-argument.sh @@ -0,0 +1,5 @@ +source ./test-functions.sh +install_service +start_service --server.port=8081 +await_app http://127.0.0.1:8081/ +curl -s http://127.0.0.1:8081/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-java-opt.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-java-opt.sh new file mode 100755 index 0000000000..f7052dc5ee --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-java-opt.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +echo 'JAVA_OPTS=-Dserver.port=8081' > /spring-boot-app.conf +install_service +start_service +await_app http://127.0.0.1:8081/ +curl -s http://127.0.0.1:8081/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-run-arg.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-run-arg.sh new file mode 100755 index 0000000000..c79633e32d --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/launch-with-single-run-arg.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +echo 'RUN_ARGS=--server.port=8081' > /spring-boot-app.conf +install_service +start_service +await_app http://127.0.0.1:8081/ +curl -s http://127.0.0.1:8081/ diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-started.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-started.sh new file mode 100755 index 0000000000..017b4c18e8 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-started.sh @@ -0,0 +1,7 @@ +source ./test-functions.sh +install_service +start_service +echo "PID1: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" +restart_service +echo "Status: $?" +echo "PID2: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-stopped.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-stopped.sh new file mode 100755 index 0000000000..95fd91c3b4 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/restart-when-stopped.sh @@ -0,0 +1,5 @@ +source ./test-functions.sh +install_service +restart_service +echo "Status: $?" +echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-started.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-started.sh new file mode 100755 index 0000000000..fd9e4f2f6b --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-started.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +install_service +start_service +echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" +start_service +echo "Status: $?" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-stopped.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-stopped.sh new file mode 100755 index 0000000000..427fff4406 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/start-when-stopped.sh @@ -0,0 +1,5 @@ +source ./test-functions.sh +install_service +start_service +echo "Status: $?" +echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-killed.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-killed.sh new file mode 100755 index 0000000000..4a9c5f6fe1 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-killed.sh @@ -0,0 +1,8 @@ +source ./test-functions.sh +install_service +start_service +pid=$(cat /var/run/spring-boot-app/spring-boot-app.pid) +echo "PID: $pid" +kill -9 $pid +status_service +echo "Status: $?" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-started.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-started.sh new file mode 100755 index 0000000000..89c1ccc1fb --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-started.sh @@ -0,0 +1,6 @@ +source ./test-functions.sh +install_service +start_service +status_service +echo "Status: $?" +echo "PID: $(cat /var/run/spring-boot-app/spring-boot-app.pid)" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-stopped.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-stopped.sh new file mode 100755 index 0000000000..24ca225344 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/status-when-stopped.sh @@ -0,0 +1,4 @@ +source ./test-functions.sh +install_service +status_service +echo "Status: $?" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/stop-when-stopped.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/stop-when-stopped.sh new file mode 100755 index 0000000000..b74faddbaf --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/stop-when-stopped.sh @@ -0,0 +1,4 @@ +source ./test-functions.sh +install_service +stop_service +echo "Status: $?" diff --git a/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/test-functions.sh b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/test-functions.sh new file mode 100644 index 0000000000..30f509db92 --- /dev/null +++ b/spring-boot-integration-tests/spring-boot-launch-script-tests/src/test/resources/scripts/test-functions.sh @@ -0,0 +1,40 @@ +install_service() { + mv /spring-boot-launch-script-tests-*.jar /spring-boot-app.jar + chmod +x /spring-boot-app.jar + ln -s /spring-boot-app.jar /etc/init.d/spring-boot-app +} + +start_service() { + service spring-boot-app start $@ +} + +restart_service() { + service spring-boot-app restart +} + +status_service() { + service spring-boot-app status +} + +stop_service() { + service spring-boot-app stop +} + +await_app() { + if [ -z $1 ] + then + url=http://127.0.0.1:8080 + else + url=$1 + fi + end=$(date +%s) + let "end+=30" + until curl -s $url > /dev/null + do + now=$(date +%s) + if [[ $now -ge $end ]]; then + break + fi + sleep 1 + done +} diff --git a/spring-boot-parent/src/checkstyle/checkstyle-suppressions.xml b/spring-boot-parent/src/checkstyle/checkstyle-suppressions.xml index f2d4895026..88ab83c12b 100644 --- a/spring-boot-parent/src/checkstyle/checkstyle-suppressions.xml +++ b/spring-boot-parent/src/checkstyle/checkstyle-suppressions.xml @@ -5,6 +5,7 @@ +