use of com.aws.greengrass.util.Exec in project aws-greengrass-nucleus by aws-greengrass.
the class GenericExternalService method run.
@SuppressWarnings("PMD.CloseResource")
protected Pair<RunStatus, Exec> run(Topic t, String cmd, IntConsumer background, List<Exec> trackingList, boolean requiresPrivilege) throws InterruptedException {
if (runWith == null) {
Optional<RunWith> opt = computeRunWithConfiguration();
if (!opt.isPresent()) {
logger.atError().log("Could not determine user/group to run with. Ensure that {} is set for {}", DeviceConfiguration.RUN_WITH_TOPIC, deviceConfiguration.getNucleusComponentName());
return new Pair<>(RunStatus.Errored, null);
}
runWith = opt.get();
LogEventBuilder logEvent = logger.atDebug().kv("user", runWith.getUser());
if (runWith.getGroup() != null) {
logEvent.kv("group", runWith.getGroup());
}
if (runWith.getShell() != null) {
logEvent.kv("shell", runWith.getShell());
}
logEvent.log("Saving user information for service execution");
if (!updateComponentPathOwner()) {
logger.atError().log("Service artifacts may not be accessible to user");
}
}
final ShellRunner shellRunner = context.get(ShellRunner.class);
Exec exec;
try {
exec = shellRunner.setup(t.getFullName(), cmd, this);
} catch (IOException e) {
logger.atError().log("Error setting up to run {}", t.getFullName(), e);
return new Pair<>(RunStatus.Errored, null);
}
if (exec == null) {
return new Pair<>(RunStatus.NothingDone, null);
}
exec = addUser(exec, requiresPrivilege);
exec = addShell(exec);
addEnv(exec, t.parent);
logger.atDebug().setEventType("generic-service-run").log();
// Track all running processes that we fork
if (exec.isRunning()) {
trackingList.add(exec);
}
RunStatus ret = shellRunner.successful(exec, t.getFullName(), background, this) ? RunStatus.OK : RunStatus.Errored;
return new Pair<>(ret, exec);
}
use of com.aws.greengrass.util.Exec in project aws-greengrass-nucleus by aws-greengrass.
the class GenericExternalService method bootstrap.
/**
* Run the command under 'bootstrap' and returns the exit code. The timeout can be configured with 'timeout' field
* in seconds. If not configured, by default, it times out after 2 minutes.
*
* @return exit code of process
* @throws InterruptedException when the command execution is interrupted.
* @throws TimeoutException when the command execution times out.
*/
@Override
@SuppressFBWarnings(value = { "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE", "NP_LOAD_OF_KNOWN_NULL_VALUE" }, justification = "Known false-positives")
public synchronized int bootstrap() throws InterruptedException, TimeoutException {
// this is redundant because all lifecycle processes should have been before calling this method.
// stopping here again to be safer
stopAllLifecycleProcesses();
CountDownLatch timeoutLatch = new CountDownLatch(1);
AtomicInteger atomicExitCode = new AtomicInteger();
// run the command at background thread so that the main thread can handle it when it times out
// note that this could be a foreground process but it requires run() methods, ShellerRunner, and Exec's method
// signature changes to deal with timeout, so we decided to go with background thread.
Pair<RunStatus, Exec> pair = run(Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC, exitCode -> {
atomicExitCode.set(exitCode);
timeoutLatch.countDown();
}, lifecycleProcesses);
try (Exec exec = pair.getRight()) {
if (exec == null) {
if (pair.getLeft() == RunStatus.Errored) {
return 1;
}
// no bootstrap command found
return 0;
}
// timeout handling
int timeoutInSec = Coerce.toInt(config.findOrDefault(DEFAULT_BOOTSTRAP_TIMEOUT_SEC, SERVICE_LIFECYCLE_NAMESPACE_TOPIC, Lifecycle.LIFECYCLE_BOOTSTRAP_NAMESPACE_TOPIC, Lifecycle.TIMEOUT_NAMESPACE_TOPIC));
boolean completedInTime = timeoutLatch.await(timeoutInSec, TimeUnit.SECONDS);
if (!completedInTime) {
String msg = String.format("Bootstrap step timed out after '%d' seconds.", timeoutInSec);
throw new TimeoutException(msg);
}
} catch (IOException e) {
logger.atError("bootstrap-process-close-error").setCause(e).log("Error closing process at bootstrap step.");
// No need to return special error code here because the exit code is handled by atomicExitCode.
}
return atomicExitCode.get();
}
use of com.aws.greengrass.util.Exec in project aws-greengrass-nucleus by aws-greengrass.
the class GenericExternalService method handleRunScript.
@SuppressWarnings("PMD.CloseResource")
private synchronized void handleRunScript() throws InterruptedException {
stopAllLifecycleProcesses();
long startingStateGeneration = getStateGeneration();
Pair<RunStatus, Exec> result = run(LIFECYCLE_RUN_NAMESPACE_TOPIC, exit -> {
// the reportStates outside of the callback
synchronized (this) {
logger.atInfo().kv(EXIT_CODE, exit).log("Run script exited");
separateLogger.atInfo().kv(EXIT_CODE, exit).log("Run script exited");
if (startingStateGeneration == getStateGeneration() && currentOrReportedStateIs(State.RUNNING)) {
if (exit == 0) {
logger.atInfo().setEventType("generic-service-stopping").log("Service finished running");
this.requestStop();
} else {
reportState(State.ERRORED);
}
}
}
}, lifecycleProcesses);
if (result.getLeft() == RunStatus.NothingDone) {
reportState(State.FINISHED);
logger.atInfo().setEventType("generic-service-finished").log("Nothing done");
return;
} else if (result.getLeft() == RunStatus.Errored) {
serviceErrored("Script errored in run");
return;
} else if (result.getRight() != null) {
reportState(State.RUNNING);
updateSystemResourceLimits();
systemResourceController.addComponentProcess(this, result.getRight().getProcess());
}
Topic timeoutTopic = config.find(SERVICE_LIFECYCLE_NAMESPACE_TOPIC, LIFECYCLE_RUN_NAMESPACE_TOPIC, Lifecycle.TIMEOUT_NAMESPACE_TOPIC);
Integer timeout = timeoutTopic == null ? null : (Integer) timeoutTopic.getOnce();
if (timeout != null) {
Exec processToClose = result.getRight();
context.get(ScheduledExecutorService.class).schedule(() -> {
if (processToClose.isRunning()) {
try {
logger.atWarn("service-run-timed-out").log("Service failed to run within timeout, calling close in process");
reportState(State.ERRORED);
processToClose.close();
} catch (IOException e) {
logger.atError("service-close-error").setCause(e).log("Error closing service after run timed out");
}
}
}, timeout, TimeUnit.SECONDS);
}
}
use of com.aws.greengrass.util.Exec in project aws-greengrass-nucleus by aws-greengrass.
the class GenericExternalService method stopProcesses.
@SuppressWarnings("PMD.CloseResource")
private synchronized void stopProcesses(List<Exec> processes) {
for (Exec e : processes) {
if (e != null && e.isRunning()) {
logger.atInfo().log("Shutting down process {}", e);
try {
e.close();
logger.atInfo().log("Shutdown completed for process {}", e);
processes.remove(e);
} catch (IOException ex) {
logger.atWarn().log("Shutdown timed out for process {}", e);
}
} else {
processes.remove(e);
}
}
}
use of com.aws.greengrass.util.Exec in project aws-greengrass-nucleus by aws-greengrass.
the class ExecIntegTest method GIVEN_windows_exec_with_user_WHEN_set_env_vars_from_multiple_sources_THEN_precedence_is_correct.
@Test
@EnabledOnOs(OS.WINDOWS)
void GIVEN_windows_exec_with_user_WHEN_set_env_vars_from_multiple_sources_THEN_precedence_is_correct() throws IOException, InterruptedException {
// Check setting default env works
// setDefaultEnv is static and will persist throughout this test.
String expectedVal = "Exec default";
Exec.setDefaultEnv(TEST_ENV_ENTRY, expectedVal);
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = // cd in case test user doesn't have permission to access current directory
exec.cd("C:\\").withUser(WINDOWS_TEST_UESRNAME).withShell("echo", "%" + TEST_ENV_ENTRY + "%").execAndGetStringOutput();
assertEquals(expectedVal, output);
}
// Set system-level env var via registry. should override the default
expectedVal = "system env var";
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = exec.withShell("reg", "add", SYSTEM_ENV_REG_KEY, "/f", "/v", TEST_ENV_ENTRY, "/t", "REG_SZ", "/d", expectedVal).execAndGetStringOutput();
assertThat(output, containsString("completed successfully"));
}
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = exec.cd("C:\\").withUser(WINDOWS_TEST_UESRNAME).withShell("echo", "%" + TEST_ENV_ENTRY + "%").execAndGetStringOutput();
assertEquals(expectedVal, output);
}
// Set user-level env var via registry. should override the system-level setting
// FYI: Usually, user-level setting has higher precedence than system-level. PATH is special.
// User PATH is appended to system-level PATH by windows.
expectedVal = "user account env var";
String testUserSid = Advapi32Util.getAccountByName(WINDOWS_TEST_UESRNAME).sidString;
testUserEnvRegKey = String.format("HKU\\%s\\Environment", testUserSid);
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = exec.cd("C:\\").withUser(WINDOWS_TEST_UESRNAME).withShell("reg", "add", testUserEnvRegKey, "/f", "/v", TEST_ENV_ENTRY, "/t", "REG_SZ", "/d", expectedVal).execAndGetStringOutput();
assertThat(output, containsString("completed successfully"));
}
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = exec.cd("C:\\").withUser(WINDOWS_TEST_UESRNAME).withShell("echo", "%" + TEST_ENV_ENTRY + "%").execAndGetStringOutput();
assertEquals(expectedVal, output);
}
// Use setenv, which overrides everything
expectedVal = "setenv override";
try (Exec exec = Platform.getInstance().createNewProcessRunner()) {
String output = exec.cd("C:\\").withUser(WINDOWS_TEST_UESRNAME).setenv(TEST_ENV_ENTRY, expectedVal).withShell("echo", "%" + TEST_ENV_ENTRY + "%").execAndGetStringOutput();
assertEquals(expectedVal, output);
}
}
Aggregations