Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.dolphinscheduler.plugin.alert.script;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

Expand All @@ -29,12 +30,13 @@ private ProcessUtils() {
}

/**
* executeScript
* executeScript with timeout
*
* @param timeoutSeconds timeout in seconds, if <= 0 waits indefinitely
* @param cmd cmd params
* @return exit code
* @return exit code, -1 if error, -2 if timeout
*/
static Integer executeScript(String... cmd) {
static Integer executeScript(long timeoutSeconds, String... cmd) {

int exitCode = -1;

Expand All @@ -46,12 +48,53 @@ static Integer executeScript(String... cmd) {

inputStreamGobbler.start();
errorStreamGobbler.start();
return process.waitFor();
} catch (IOException | InterruptedException e) {
log.error("execute alert script error {}", e.getMessage());

if (timeoutSeconds > 0) {
boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);
if (!finished) {
log.error("script execution timed out after {} seconds, destroying process", timeoutSeconds);
process.destroyForcibly();
closeProcessStreams(process);
joinGobbler(inputStreamGobbler);
joinGobbler(errorStreamGobbler);
return -2;
Copy link
Member

Choose a reason for hiding this comment

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

It's better to use positive integer here.

}
} else {
process.waitFor();
}
int processExitCode = process.exitValue();
joinGobbler(inputStreamGobbler);
joinGobbler(errorStreamGobbler);
return processExitCode;
} catch (InterruptedException e) {
log.error("execute alert script interrupted {}", e.getMessage());
Thread.currentThread().interrupt();
} catch (IOException e) {
log.error("execute alert script error {}", e.getMessage());
}

return exitCode;
}

private static void closeProcessStreams(Process process) {
try {
process.getInputStream().close();
} catch (IOException e) {
log.warn("Failed to close process input stream after timeout", e);
}
try {
process.getErrorStream().close();
} catch (IOException e) {
log.warn("Failed to close process error stream after timeout", e);
}
}

private static void joinGobbler(StreamGobbler gobbler) {
try {
gobbler.interrupt();
gobbler.join(TimeUnit.SECONDS.toMillis(1));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import org.apache.dolphinscheduler.alert.api.AlertChannelFactory;
import org.apache.dolphinscheduler.alert.api.AlertInputTips;
import org.apache.dolphinscheduler.common.utils.JSONUtils;
import org.apache.dolphinscheduler.spi.params.base.DataType;
import org.apache.dolphinscheduler.spi.params.base.ParamsOptions;
import org.apache.dolphinscheduler.spi.params.base.PluginParams;
import org.apache.dolphinscheduler.spi.params.base.Validate;
import org.apache.dolphinscheduler.spi.params.input.InputParam;
import org.apache.dolphinscheduler.spi.params.input.number.InputNumberParam;
import org.apache.dolphinscheduler.spi.params.radio.RadioParam;

import java.util.Arrays;
Expand Down Expand Up @@ -66,7 +68,18 @@ public List<PluginParams> params() {
.addValidate(Validate.newBuilder().setRequired(true).build())
.build();

return Arrays.asList(scriptUserParam, scriptPathParam, scriptTypeParams);
InputNumberParam scriptTimeoutParam =
Copy link
Member

Choose a reason for hiding this comment

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

It is best to tell the user the meaning of the exit code in the document.

InputNumberParam.newBuilder(ScriptParamsConstants.NAME_SCRIPT_TIMEOUT,
ScriptParamsConstants.SCRIPT_TIMEOUT)
.setValue(ScriptParamsConstants.DEFAULT_SCRIPT_TIMEOUT)
.addValidate(Validate.newBuilder()
.setType(DataType.NUMBER.getDataType())
.setRequired(false)
.setMin(0D)
.build())
.build();

return Arrays.asList(scriptUserParam, scriptPathParam, scriptTypeParams, scriptTimeoutParam);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public final class ScriptParamsConstants {

static final String NAME_SCRIPT_USER_PARAMS = "userParams";

static final String SCRIPT_TIMEOUT = "$t('timeout')";

static final String NAME_SCRIPT_TIMEOUT = "timeout";

static final int DEFAULT_SCRIPT_TIMEOUT = 60;

private ScriptParamsConstants() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public final class ScriptSender {
private final String scriptPath;
private final String scriptType;
private final String userParams;
private final long timeout;

ScriptSender(Map<String, String> config) {
scriptPath = StringUtils.isNotBlank(config.get(ScriptParamsConstants.NAME_SCRIPT_PATH))
Expand All @@ -47,6 +48,19 @@ public final class ScriptSender {
userParams = StringUtils.isNotBlank(config.get(ScriptParamsConstants.NAME_SCRIPT_USER_PARAMS))
? config.get(ScriptParamsConstants.NAME_SCRIPT_USER_PARAMS)
: "";
String timeoutConfig = config.get(ScriptParamsConstants.NAME_SCRIPT_TIMEOUT);
if (StringUtils.isNotBlank(timeoutConfig)) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (StringUtils.isNotBlank(timeoutConfig)) {
if (StringUtils.isNotEmpty(timeoutConfig)) {

long parsedTimeout = ScriptParamsConstants.DEFAULT_SCRIPT_TIMEOUT;
try {
parsedTimeout = Long.parseLong(timeoutConfig);
} catch (NumberFormatException ex) {
log.warn("Invalid script timeout config value: '{}', using default: {}",
Copy link
Member

Choose a reason for hiding this comment

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

We should return alertResult here.

timeoutConfig, ScriptParamsConstants.DEFAULT_SCRIPT_TIMEOUT, ex);
}
timeout = parsedTimeout;
} else {
timeout = ScriptParamsConstants.DEFAULT_SCRIPT_TIMEOUT;
}
}

AlertResult sendScriptAlert(String title, String content) {
Expand Down Expand Up @@ -108,13 +122,19 @@ private AlertResult executeShellScript(String title, String content) {

String[] cmd = {"/bin/sh", "-c", scriptPath + ALERT_TITLE_OPTION + "'" + title + "'" + ALERT_CONTENT_OPTION
+ "'" + content + "'" + ALERT_USER_PARAMS_OPTION + "'" + userParams + "'"};
int exitCode = ProcessUtils.executeScript(cmd);
int exitCode = ProcessUtils.executeScript(timeout, cmd);

if (exitCode == 0) {
alertResult.setSuccess(true);
alertResult.setMessage("send script alert msg success");
return alertResult;
}
if (exitCode == -2) {
alertResult.setMessage("send script alert msg error, script execution timed out after " + timeout
+ " seconds");
log.error("send script alert msg error, script execution timed out after {} seconds", timeout);
return alertResult;
}
alertResult.setMessage("send script alert msg error,exitCode is " + exitCode);
log.info("send script alert msg error,exitCode is {}", exitCode);
return alertResult;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@

package org.apache.dolphinscheduler.plugin.alert.script;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

/**
* ProcessUtilsTest
Expand All @@ -32,8 +35,17 @@
private String[] cmd = {"/bin/sh", "-c", shellFilPath + " -t 1"};

@Test
@DisabledOnOs(OS.WINDOWS)
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to do this since we've decleared in the document that windows is not supported.

public void testExecuteScript() {
int code = ProcessUtils.executeScript(cmd);
assert code != -1;
int code = ProcessUtils.executeScript(60, cmd);
Assertions.assertNotEquals(-1, code);
}

@Test
@DisabledOnOs(OS.WINDOWS)
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

public void testExecuteScriptTimeout() {

Check warning on line 46 in dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-script/src/test/java/org/apache/dolphinscheduler/plugin/alert/script/ProcessUtilsTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=apache-dolphinscheduler&issues=AZz2Ga8iyADDpoK7zjCQ&open=AZz2Ga8iyADDpoK7zjCQ&pullRequest=18050
String[] sleepCmd = {"/bin/sh", "-c", "sleep 30"};
int code = ProcessUtils.executeScript(2, sleepCmd);
Assertions.assertEquals(-2, code);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class ScriptAlertChannelFactoryTest {
public void testGetParams() {
ScriptAlertChannelFactory scriptAlertChannelFactory = new ScriptAlertChannelFactory();
List<PluginParams> params = scriptAlertChannelFactory.params();
Assertions.assertEquals(3, params.size());
Assertions.assertEquals(4, params.size());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;

/**
* ScriptSenderTest
Expand All @@ -35,17 +37,18 @@

private static final String rootPath = System.getProperty("user.dir");
private static final String shellFilPath = rootPath + "/src/test/script/shell/scriptExample.sh";
private static Map<String, String> scriptConfig = new HashMap<>();
private Map<String, String> scriptConfig;

@BeforeEach
public void initScriptConfig() {

scriptConfig = new HashMap<>();
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_TYPE, String.valueOf(ScriptType.SHELL.getDescp()));
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_USER_PARAMS, "userParams");
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_PATH, shellFilPath);
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testScriptSenderTest() {
ScriptSender scriptSender = new ScriptSender(scriptConfig);
AlertResult alertResult;
Expand All @@ -56,6 +59,7 @@
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testScriptSenderInjectionTest() {
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_USER_PARAMS, "' ; calc.exe ; '");
ScriptSender scriptSender = new ScriptSender(scriptConfig);
Expand All @@ -64,6 +68,7 @@
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testUserParamsNPE() {
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_USER_PARAMS, null);
ScriptSender scriptSender = new ScriptSender(scriptConfig);
Expand All @@ -82,6 +87,7 @@
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testPathError() {
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_PATH, "/usr/sbin/abc");
ScriptSender scriptSender = new ScriptSender(scriptConfig);
Expand All @@ -100,4 +106,30 @@
assertFalse(alertResult.isSuccess());
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testDefaultTimeout() {

Check warning on line 111 in dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-script/src/test/java/org/apache/dolphinscheduler/plugin/alert/script/ScriptSenderTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=apache-dolphinscheduler&issues=AZz2Ga-byADDpoK7zjCR&open=AZz2Ga-byADDpoK7zjCR&pullRequest=18050
ScriptSender scriptSender = new ScriptSender(scriptConfig);
AlertResult alertResult = scriptSender.sendScriptAlert("test title Kris", "test content");
Assertions.assertTrue(alertResult.isSuccess());
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testCustomTimeout() {

Check warning on line 119 in dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-script/src/test/java/org/apache/dolphinscheduler/plugin/alert/script/ScriptSenderTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=apache-dolphinscheduler&issues=AZz2Ga-byADDpoK7zjCS&open=AZz2Ga-byADDpoK7zjCS&pullRequest=18050
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_TIMEOUT, "30");
ScriptSender scriptSender = new ScriptSender(scriptConfig);
AlertResult alertResult = scriptSender.sendScriptAlert("test title Kris", "test content");
Assertions.assertTrue(alertResult.isSuccess());
}

@Test
@DisabledOnOs(OS.WINDOWS)
public void testInvalidTimeoutFallsBackToDefault() {

Check warning on line 128 in dolphinscheduler-alert/dolphinscheduler-alert-plugins/dolphinscheduler-alert-script/src/test/java/org/apache/dolphinscheduler/plugin/alert/script/ScriptSenderTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this 'public' modifier.

See more on https://sonarcloud.io/project/issues?id=apache-dolphinscheduler&issues=AZz2Ga-byADDpoK7zjCT&open=AZz2Ga-byADDpoK7zjCT&pullRequest=18050
scriptConfig.put(ScriptParamsConstants.NAME_SCRIPT_TIMEOUT, "notANumber");
ScriptSender scriptSender = new ScriptSender(scriptConfig);
AlertResult alertResult = scriptSender.sendScriptAlert("test title Kris", "test content");
Assertions.assertTrue(alertResult.isSuccess());
}

}
Loading