diff options
author | gjoranv <gv@yahoo-inc.com> | 2017-03-02 00:15:38 +0100 |
---|---|---|
committer | gjoranv <gv@yahoo-inc.com> | 2017-03-02 00:29:09 +0100 |
commit | f238d6793d32e139b1c3f17857da6ea492ed2c70 (patch) | |
tree | 10b971bdecfa4265f90edcc44fc3b0a134a307ab | |
parent | d37abf742d3fa48ad564ab72689c91a1a1c81209 (diff) |
Add a configurable process executor based on commons-exec.
6 files changed, 137 insertions, 0 deletions
@@ -534,6 +534,11 @@ <version>1.8</version> </dependency> <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-exec</artifactId> + <version>1.3</version> + </dependency> + <dependency> <groupId>io.airlift</groupId> <artifactId>airline</artifactId> <version>0.7</version> diff --git a/vespajlib/pom.xml b/vespajlib/pom.xml index 0282cc94ff6..1d9bdfb452d 100644 --- a/vespajlib/pom.xml +++ b/vespajlib/pom.xml @@ -79,6 +79,10 @@ <artifactId>jackson-databind</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-exec</artifactId> + </dependency> </dependencies> <build> <plugins> diff --git a/vespajlib/src/main/java/com/yahoo/system/execution/ProcessExecutor.java b/vespajlib/src/main/java/com/yahoo/system/execution/ProcessExecutor.java new file mode 100644 index 00000000000..564ee5ea3ac --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/system/execution/ProcessExecutor.java @@ -0,0 +1,79 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.system.execution; + +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.PumpStreamHandler; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +/** + * @author gjoranv + * @author bjorncs + */ +public class ProcessExecutor { + + public static class Builder { + private final int timeoutSeconds; + private int[] successExitCodes; + + public Builder(int timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + } + + public Builder setSuccessExitCodes(int... successExitCodes) { + this.successExitCodes = successExitCodes; + return this; + } + + public ProcessExecutor build() { + return new ProcessExecutor(timeoutSeconds, successExitCodes); + } + } + + private ProcessExecutor(int timeoutSeconds, int[] successExitCodes) { + this.timeoutSeconds = timeoutSeconds; + this.successExitCodes = successExitCodes; + } + + public final int timeoutSeconds; + private final int[] successExitCodes; + + public Optional<ProcessResult> execute(String command) throws IOException { + return execute(command, null); + } + + public Optional<ProcessResult> execute(String command, String processInput) throws IOException { + ByteArrayOutputStream processErr = new ByteArrayOutputStream(); + ByteArrayOutputStream processOut = new ByteArrayOutputStream(); + + DefaultExecutor executor = new DefaultExecutor(); + executor.setStreamHandler(createStreamHandler(processOut, processErr, processInput)); + ExecuteWatchdog watchDog = new ExecuteWatchdog(TimeUnit.SECONDS.toMillis(timeoutSeconds)); + executor.setWatchdog(watchDog); + executor.setExitValues(successExitCodes); + + int exitCode = executor.execute(CommandLine.parse(command)); + return (watchDog.killedProcess()) ? + Optional.empty() : Optional.of(new ProcessResult(exitCode, processOut.toString(), processErr.toString())); + } + + private static PumpStreamHandler createStreamHandler(ByteArrayOutputStream processOut, + ByteArrayOutputStream processErr, + String input) { + if (input != null) { + InputStream processInput = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)); + return new PumpStreamHandler(processOut, processErr, processInput); + } else { + return new PumpStreamHandler(processOut, processErr); + } + } + +} diff --git a/vespajlib/src/main/java/com/yahoo/system/execution/ProcessResult.java b/vespajlib/src/main/java/com/yahoo/system/execution/ProcessResult.java new file mode 100644 index 00000000000..2f277f83a8c --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/system/execution/ProcessResult.java @@ -0,0 +1,19 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.system.execution; + +/** + * @author bjorncs + * @author gjoranv + */ +public class ProcessResult { + public final String stdOut; + public final String stdErr; + public final int exitCode; + + public ProcessResult(int exitCode, String stdOut, String stdErr) { + this.exitCode = exitCode; + this.stdOut = stdOut; + this.stdErr = stdErr; + } + +} diff --git a/vespajlib/src/main/java/com/yahoo/system/execution/package-info.java b/vespajlib/src/main/java/com/yahoo/system/execution/package-info.java new file mode 100644 index 00000000000..1f88163d23d --- /dev/null +++ b/vespajlib/src/main/java/com/yahoo/system/execution/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +@ExportPackage +package com.yahoo.system.execution; + +import com.yahoo.osgi.annotation.ExportPackage; diff --git a/vespajlib/src/test/java/com/yahoo/system/execution/ProcessExecutorTest.java b/vespajlib/src/test/java/com/yahoo/system/execution/ProcessExecutorTest.java new file mode 100644 index 00000000000..b12d66853fe --- /dev/null +++ b/vespajlib/src/test/java/com/yahoo/system/execution/ProcessExecutorTest.java @@ -0,0 +1,25 @@ +// Copyright 2017 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.system.execution; + +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author gjoranv + */ +public class ProcessExecutorTest { + + @Test + public void echo_can_be_executed() throws Exception { + final String message = "Hello from executor!"; + ProcessExecutor executor = new ProcessExecutor.Builder(10).build(); + Optional<ProcessResult> result = executor.execute("echo " + message); + assertTrue(result.isPresent()); + assertEquals(message, result.get().stdOut.trim()); + assertEquals("", result.get().stdErr); + } +} |