Skip to content

Commit

Permalink
DockerStatusBuildItem for caching isDockerWorking status.
Browse files Browse the repository at this point in the history
* Reduce socket timeout for checking docker listening on DOCKER_HOST.
* Timeout for checking docker commands

(cherry picked from commit 418d986)
  • Loading branch information
ozangunalp authored and gsmet committed May 3, 2022
1 parent 9e386e1 commit 3556287
Show file tree
Hide file tree
Showing 16 changed files with 247 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.quarkus.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DockerStatusBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;

public class DockerStatusProcessor {

@BuildStep
DockerStatusBuildItem IsDockerWorking(LaunchModeBuildItem launchMode) {
return new DockerStatusBuildItem(new IsDockerWorking(launchMode.getLaunchMode().isDevOrTest()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.function.BooleanSupplier;
Expand All @@ -25,6 +27,8 @@
public class IsDockerWorking implements BooleanSupplier {

private static final Logger LOGGER = Logger.getLogger(IsDockerWorking.class.getName());
public static final int DOCKER_HOST_CHECK_TIMEOUT = 1000;
public static final int DOCKER_CMD_CHECK_TIMEOUT = 3000;

private final List<Strategy> strategies;

Expand All @@ -40,6 +44,7 @@ public IsDockerWorking(boolean silent) {
@Override
public boolean getAsBoolean() {
for (Strategy strategy : strategies) {
LOGGER.debugf("Checking Docker Environment using strategy %s", strategy.getClass().getName());
Result result = strategy.get();
if (result == Result.AVAILABLE) {
return true;
Expand Down Expand Up @@ -113,7 +118,8 @@ public Result get() {
if (dockerHost != null && !dockerHost.startsWith("unix:")) {
try {
URI url = new URI(dockerHost);
try (Socket s = new Socket(url.getHost(), url.getPort())) {
try (Socket s = new Socket()) {
s.connect(new InetSocketAddress(url.getHost(), url.getPort()), DOCKER_HOST_CHECK_TIMEOUT);
return Result.AVAILABLE;
} catch (IOException e) {
LOGGER.warnf(
Expand Down Expand Up @@ -143,7 +149,7 @@ private DockerBinaryStrategy(boolean silent) {
@Override
public Result get() {
try {
if (!ExecUtil.execSilent(binary, "-v")) {
if (!ExecUtil.execSilentWithTimeout(Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT), binary, "-v")) {
LOGGER.warnf("'%s -v' returned an error code. Make sure your Docker binary is correct", binary);
return Result.UNKNOWN;
}
Expand All @@ -154,7 +160,8 @@ public Result get() {

try {
OutputFilter filter = new OutputFilter();
if (ExecUtil.exec(new File("."), filter, "docker", "version", "--format", "'{{.Server.Version}}'")) {
if (ExecUtil.execWithTimeout(new File("."), filter, Duration.ofMillis(DOCKER_CMD_CHECK_TIMEOUT),
"docker", "version", "--format", "'{{.Server.Version}}'")) {
LOGGER.debugf("Docker daemon found. Version: %s", filter.getOutput());
return Result.AVAILABLE;
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.deployment.builditem;

import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.IsDockerWorking;

public final class DockerStatusBuildItem extends SimpleBuildItem {

private final IsDockerWorking isDockerWorking;
private Boolean cachedStatus;

public DockerStatusBuildItem(IsDockerWorking isDockerWorking) {
this.isDockerWorking = isDockerWorking;
}

public synchronized boolean isDockerAvailable() {
if (cachedStatus == null) {
cachedStatus = isDockerWorking.getAsBoolean();
}
return cachedStatus;
}
}
130 changes: 122 additions & 8 deletions core/deployment/src/main/java/io/quarkus/deployment/util/ExecUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import org.jboss.logging.Logger;
Expand All @@ -17,6 +19,8 @@ public class ExecUtil {
private static final Function<InputStream, Runnable> PRINT_OUTPUT = i -> new HandleOutput(i);
private static final Function<InputStream, Runnable> SILENT = i -> new HandleOutput(i, Logger.Level.DEBUG);

private static final int PROCESS_CHECK_INTERVAL = 500;

private static class HandleOutput implements Runnable {

private final InputStream is;
Expand Down Expand Up @@ -60,6 +64,18 @@ public static boolean exec(String command, String... args) {
return exec(new File("."), command, args);
}

/**
* Execute the specified command until the given timeout from within the current directory.
*
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execWithTimeout(Duration timeout, String command, String... args) {
return execWithTimeout(new File("."), timeout, command, args);
}

/**
* Execute the specified command from within the current directory and hide the output.
*
Expand All @@ -71,6 +87,18 @@ public static boolean execSilent(String command, String... args) {
return execSilent(new File("."), command, args);
}

/**
* Execute the specified command until the given timeout from within the current directory and hide the output.
*
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilentWithTimeout(Duration timeout, String command, String... args) {
return execSilentWithTimeout(new File("."), timeout, command, args);
}

/**
* Execute the specified command from within the specified directory.
*
Expand All @@ -83,6 +111,19 @@ public static boolean exec(File directory, String command, String... args) {
return exec(directory, PRINT_OUTPUT, command, args);
}

/**
* Execute the specified command until the given timeout from within the specified directory.
*
* @param directory The directory
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execWithTimeout(File directory, Duration timeout, String command, String... args) {
return execWithTimeout(directory, PRINT_OUTPUT, timeout, command, args);
}

/**
* Execute the specified command from within the specified directory and hide the output.
*
Expand All @@ -95,6 +136,19 @@ public static boolean execSilent(File directory, String command, String... args)
return exec(directory, SILENT, command, args);
}

/**
* Execute the specified command until the given timeout from within the specified directory and hide the output.
*
* @param directory The directory
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execSilentWithTimeout(File directory, Duration timeout, String command, String... args) {
return execWithTimeout(directory, SILENT, timeout, command, args);
}

/**
* Execute the specified command from within the specified directory.
* The method allows specifying an output filter that processes the command output.
Expand All @@ -107,27 +161,87 @@ public static boolean execSilent(File directory, String command, String... args)
*/
public static boolean exec(File directory, Function<InputStream, Runnable> outputFilterFunction, String command,
String... args) {
Process process = null;
try {
Process process = startProcess(directory, command, args);
outputFilterFunction.apply(process.getInputStream());
process.waitFor();
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
}

/**
* Execute the specified command until the given timeout from within the specified directory.
* The method allows specifying an output filter that processes the command output.
*
* @param directory The directory
* @param outputFilterFunction A {@link Function} that gets an {@link InputStream} and returns an outputFilter.
* @param timeout The timeout
* @param command The command
* @param args The command arguments
* @return true if commands where executed successfully
*/
public static boolean execWithTimeout(File directory, Function<InputStream, Runnable> outputFilterFunction,
Duration timeout, String command, String... args) {
try {
Process process = startProcess(directory, command, args);
Thread t = new Thread(outputFilterFunction.apply(process.getInputStream()));
t.setName("Process stdout");
t.setDaemon(true);
t.start();
process.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
destroyProcess(process);
return process.exitValue() == 0;
} catch (InterruptedException e) {
return false;
}
}

/**
* Start a process executing given command with arguments within the specified directory.
*
* @param directory The directory
* @param command The command
* @param args The command arguments
* @return the process
*/
public static Process startProcess(File directory, String command, String... args) {
try {
String[] cmd = new String[args.length + 1];
cmd[0] = command;
if (args.length > 0) {
System.arraycopy(args, 0, cmd, 1, args.length);
}
process = new ProcessBuilder()
return new ProcessBuilder()
.directory(directory)
.command(cmd)
.redirectErrorStream(true)
.start();

outputFilterFunction.apply(process.getInputStream()).run();
process.waitFor();
} catch (IOException e) {
throw new RuntimeException("Input/Output error while executing command.", e);
} catch (InterruptedException e) {
return false;
}
return process != null && process.exitValue() == 0;
}

/**
* Kill the process, if still alive, kill it forcibly
*
* @param process the process to kill
*/
public static void destroyProcess(Process process) {
process.destroy();
int i = 0;
while (process.isAlive() && i++ < 10) {
try {
process.waitFor(PROCESS_CHECK_INTERVAL, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}

if (process.isAlive()) {
process.destroyForcibly();
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
import io.quarkus.container.spi.ContainerImageBuilderBuildItem;
import io.quarkus.container.spi.ContainerImageInfoBuildItem;
import io.quarkus.container.spi.ContainerImagePushRequestBuildItem;
import io.quarkus.deployment.IsDockerWorking;
import io.quarkus.deployment.IsNormalNotRemoteDev;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DockerStatusBuildItem;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.AppCDSResultBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
Expand All @@ -55,15 +55,14 @@ public class DockerProcessor {
private static final String DOCKER_DIRECTORY_NAME = "docker";
static final String DOCKER_CONTAINER_IMAGE_NAME = "docker";

private final IsDockerWorking isDockerWorking = new IsDockerWorking();

@BuildStep
public AvailableContainerImageExtensionBuildItem availability() {
return new AvailableContainerImageExtensionBuildItem(DOCKER);
}

@BuildStep(onlyIf = { IsNormalNotRemoteDev.class, DockerBuild.class }, onlyIfNot = NativeBuild.class)
public void dockerBuildFromJar(DockerConfig dockerConfig,
DockerStatusBuildItem dockerStatusBuildItem,
ContainerImageConfig containerImageConfig,
OutputTargetBuildItem out,
ContainerImageInfoBuildItem containerImageInfo,
Expand All @@ -83,7 +82,7 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,
return;
}

if (!isDockerWorking.getAsBoolean()) {
if (!dockerStatusBuildItem.isDockerAvailable()) {
throw new RuntimeException("Unable to build docker image. Please check your docker installation");
}

Expand Down Expand Up @@ -116,6 +115,7 @@ public void dockerBuildFromJar(DockerConfig dockerConfig,

@BuildStep(onlyIf = { IsNormalNotRemoteDev.class, NativeBuild.class, DockerBuild.class })
public void dockerBuildFromNativeImage(DockerConfig dockerConfig,
DockerStatusBuildItem dockerStatusBuildItem,
ContainerImageConfig containerImageConfig,
ContainerImageInfoBuildItem containerImage,
Optional<ContainerImageBuildRequestBuildItem> buildRequest,
Expand All @@ -134,7 +134,7 @@ public void dockerBuildFromNativeImage(DockerConfig dockerConfig,
return;
}

if (!isDockerWorking.getAsBoolean()) {
if (!dockerStatusBuildItem.isDockerAvailable()) {
throw new RuntimeException("Unable to build docker image. Please check your docker installation");
}

Expand Down
Loading

0 comments on commit 3556287

Please sign in to comment.