Do not daemonize when testing error output in packaging tests (#56971) (#56997)

The packaging tests start elasticsearch in various ways. All of these
currently expect it is started asynchronously, yet some tests expect it
will fail to start and want to check the error output. This commit adds
a daemonize flag to the utility methods to start elasticsearch for such
cases, so that when the start method returns, all the error output
should already be available since the process will have exited.

relates #51716
This commit is contained in:
Ryan Ernst 2020-05-20 07:56:48 -07:00 committed by GitHub
parent edada6bc39
commit 79224a9a4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 91 deletions

View File

@ -162,7 +162,7 @@ public class KeystoreManagementTests extends PackagingTestCase {
assertPasswordProtectedKeystore();
awaitElasticsearchStartup(startElasticsearchStandardInputPassword(password));
awaitElasticsearchStartup(startElasticsearchStandardInputPassword(password, true));
ServerUtils.runElasticsearchTests();
stopElasticsearch();
}
@ -173,7 +173,7 @@ public class KeystoreManagementTests extends PackagingTestCase {
assertPasswordProtectedKeystore();
Shell.Result result = startElasticsearchStandardInputPassword("wrong");
Shell.Result result = startElasticsearchStandardInputPassword("wrong", false);
assertElasticsearchFailure(result, Arrays.asList(ERROR_INCORRECT_PASSWORD, ERROR_CORRUPTED_KEYSTORE), null);
}
@ -191,7 +191,7 @@ public class KeystoreManagementTests extends PackagingTestCase {
assertPasswordProtectedKeystore();
awaitElasticsearchStartup(startElasticsearchTtyPassword(password));
awaitElasticsearchStartup(startElasticsearchTtyPassword(password, true));
ServerUtils.runElasticsearchTests();
stopElasticsearch();
}
@ -204,7 +204,7 @@ public class KeystoreManagementTests extends PackagingTestCase {
assertPasswordProtectedKeystore();
Shell.Result result = startElasticsearchTtyPassword("wrong");
Shell.Result result = startElasticsearchTtyPassword("wrong", false);
// error will be on stdout for "expect"
assertThat(result.stdout, anyOf(containsString(ERROR_INCORRECT_PASSWORD), containsString(ERROR_CORRUPTED_KEYSTORE)));
}
@ -269,7 +269,7 @@ public class KeystoreManagementTests extends PackagingTestCase {
Files.write(esKeystorePassphraseFile, singletonList("wrongpassword"));
Packages.JournaldWrapper journaldWrapper = new Packages.JournaldWrapper(sh);
Shell.Result result = runElasticsearchStartCommand();
Shell.Result result = runElasticsearchStartCommand(false);
assertElasticsearchFailure(result, Arrays.asList(ERROR_INCORRECT_PASSWORD, ERROR_CORRUPTED_KEYSTORE), journaldWrapper);
} finally {
sh.run("sudo systemctl unset-environment ES_KEYSTORE_PASSPHRASE_FILE");

View File

@ -360,7 +360,7 @@ public class PackageTests extends PackagingTestCase {
// Make sure we don't pick up the journal entries for previous ES instances.
Packages.JournaldWrapper journald = new Packages.JournaldWrapper(sh);
runElasticsearchStartCommand();
runElasticsearchStartCommand(true);
final Result logs = journald.getLogs();
assertThat(logs.stdout, containsString("Failed to load settings from [elasticsearch.yml]"));

View File

@ -155,18 +155,24 @@ public abstract class PackagingTestCase extends Assert {
public void teardown() throws Exception {
// move log file so we can avoid false positives when grepping for
// messages in logs during test
if (installation != null && Files.exists(installation.logs)) {
Path logFile = installation.logs.resolve("elasticsearch.log");
String prefix = this.getClass().getSimpleName() + "." + testNameRule.getMethodName();
if (Files.exists(logFile)) {
Path newFile = installation.logs.resolve(prefix + ".elasticsearch.log");
FileUtils.mv(logFile, newFile);
if (installation != null) {
if (Files.exists(installation.logs)) {
Path logFile = installation.logs.resolve("elasticsearch.log");
String prefix = this.getClass().getSimpleName() + "." + testNameRule.getMethodName();
if (Files.exists(logFile)) {
Path newFile = installation.logs.resolve(prefix + ".elasticsearch.log");
FileUtils.mv(logFile, newFile);
}
for (Path rotatedLogFile : FileUtils.lsGlob(installation.logs, "elasticsearch*.tar.gz")) {
Path newRotatedLogFile = installation.logs.resolve(prefix + "." + rotatedLogFile.getFileName());
FileUtils.mv(rotatedLogFile, newRotatedLogFile);
}
}
for (Path rotatedLogFile : FileUtils.lsGlob(installation.logs, "elasticsearch*.tar.gz")) {
Path newRotatedLogFile = installation.logs.resolve(prefix + "." + rotatedLogFile.getFileName());
FileUtils.mv(rotatedLogFile, newRotatedLogFile);
if (Files.exists(Archives.getPowershellErrorPath(installation))) {
FileUtils.rmWithRetries(Archives.getPowershellErrorPath(installation));
}
}
}
/** The {@link Distribution} that should be tested in this case */
@ -200,7 +206,7 @@ public abstract class PackagingTestCase extends Assert {
*/
protected void assertWhileRunning(Platforms.PlatformAction assertions) throws Exception {
try {
awaitElasticsearchStartup(runElasticsearchStartCommand());
awaitElasticsearchStartup(runElasticsearchStartCommand(true));
} catch (Exception e) {
if (Files.exists(installation.home.resolve("elasticsearch.pid"))) {
String pid = FileUtils.slurp(installation.home.resolve("elasticsearch.pid")).trim();
@ -235,11 +241,11 @@ public abstract class PackagingTestCase extends Assert {
* @return Shell results of the startup command.
* @throws Exception when command fails immediately.
*/
public Shell.Result runElasticsearchStartCommand() throws Exception {
public Shell.Result runElasticsearchStartCommand(boolean daemonize) throws Exception {
switch (distribution.packaging) {
case TAR:
case ZIP:
return Archives.runElasticsearchStartCommand(installation, sh, "");
return Archives.runElasticsearchStartCommand(installation, sh, null, daemonize);
case DEB:
case RPM:
return Packages.runElasticsearchStartCommand(sh);
@ -290,21 +296,21 @@ public abstract class PackagingTestCase extends Assert {
/**
* Start Elasticsearch and wait until it's up and running. If you just want to run
* the start command, use {@link #runElasticsearchStartCommand()}.
* the start command, use {@link #runElasticsearchStartCommand(boolean)}.
* @throws Exception if Elasticsearch can't start
*/
public void startElasticsearch() throws Exception {
awaitElasticsearchStartup(runElasticsearchStartCommand());
awaitElasticsearchStartup(runElasticsearchStartCommand(true));
}
public Shell.Result startElasticsearchStandardInputPassword(String password) {
public Shell.Result startElasticsearchStandardInputPassword(String password, boolean daemonize) {
assertTrue("Only archives support passwords on standard input", distribution().isArchive());
return Archives.runElasticsearchStartCommand(installation, sh, password);
return Archives.runElasticsearchStartCommand(installation, sh, password, daemonize);
}
public Shell.Result startElasticsearchTtyPassword(String password) throws Exception {
public Shell.Result startElasticsearchTtyPassword(String password, boolean daemonize) throws Exception {
assertTrue("Only archives support passwords on TTY", distribution().isArchive());
return Archives.startElasticsearchWithTty(installation, sh, password);
return Archives.startElasticsearchWithTty(installation, sh, password, daemonize);
}
public void assertElasticsearchFailure(Shell.Result result, String expectedMessage, Packages.JournaldWrapper journaldWrapper) {
@ -330,7 +336,7 @@ public abstract class PackagingTestCase extends Assert {
Shell.Result error = journaldWrapper.getLogs();
assertThat(error.stdout, anyOf(stringMatchers));
} else if (Platforms.WINDOWS) {
} else if (Platforms.WINDOWS && Files.exists(Archives.getPowershellErrorPath(installation))) {
// In Windows, we have written our stdout and stderr to files in order to run
// in the background

View File

@ -25,6 +25,7 @@ import org.apache.logging.log4j.Logger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.stream.Stream;
@ -232,18 +233,26 @@ public class Archives {
}
public static Shell.Result startElasticsearch(Installation installation, Shell sh) {
return runElasticsearchStartCommand(installation, sh, "");
return runElasticsearchStartCommand(installation, sh, null, true);
}
public static Shell.Result startElasticsearchWithTty(Installation installation, Shell sh, String keystorePassword) throws Exception {
public static Shell.Result startElasticsearchWithTty(Installation installation, Shell sh, String keystorePassword, boolean daemonize)
throws Exception {
final Path pidFile = installation.home.resolve("elasticsearch.pid");
final Installation.Executables bin = installation.executables();
// requires the "expect" utility to be installed
List<String> command = new ArrayList<>();
command.add("sudo -E -u %s %s -p %s");
if (daemonize) {
command.add("-d");
}
String script = String.format(
Locale.ROOT,
"expect -c \"$(cat<<EXPECT\n"
+ "spawn -ignore HUP sudo -E -u %s %s -d -p %s \n"
+ "spawn -ignore HUP "
+ String.join(" ", command)
+ "\n"
+ "expect \"Elasticsearch keystore password:\"\n"
+ "send \"%s\\r\"\n"
+ "expect eof\n"
@ -259,7 +268,12 @@ public class Archives {
return sh.runIgnoreExitCode(script);
}
public static Shell.Result runElasticsearchStartCommand(Installation installation, Shell sh, String keystorePassword) {
public static Shell.Result runElasticsearchStartCommand(
Installation installation,
Shell sh,
String keystorePassword,
boolean daemonize
) {
final Path pidFile = installation.home.resolve("elasticsearch.pid");
assertThat(pidFile, fileDoesNotExist());
@ -276,71 +290,93 @@ public class Archives {
// We need to give Elasticsearch enough time to print failures to stderr before exiting
sh.getEnv().put("ES_STARTUP_SLEEP_TIME", ES_STARTUP_SLEEP_TIME_SECONDS);
return sh.runIgnoreExitCode(
"sudo -E -u " + ARCHIVE_OWNER + " " + bin.elasticsearch + " -d -p " + pidFile + " <<<'" + keystorePassword + "'"
List<String> command = new ArrayList<>();
command.add("sudo -E -u ");
command.add(ARCHIVE_OWNER);
command.add(bin.elasticsearch.toString());
if (daemonize) {
command.add("-d");
}
command.add("-p");
command.add(pidFile.toString());
if (keystorePassword != null) {
command.add("<<<'" + keystorePassword + "'");
}
return sh.runIgnoreExitCode(String.join(" ", command));
}
if (daemonize) {
final Path stdout = getPowershellOutputPath(installation);
final Path stderr = getPowershellErrorPath(installation);
String powerShellProcessUserSetup;
if (System.getenv("username").equals("vagrant")) {
// the tests will run as Administrator in vagrant.
// we don't want to run the server as Administrator, so we provide the current user's
// username and password to the process which has the effect of starting it not as Administrator.
powerShellProcessUserSetup = "$password = ConvertTo-SecureString 'vagrant' -AsPlainText -Force; "
+ "$processInfo.Username = 'vagrant'; "
+ "$processInfo.Password = $password; ";
} else {
powerShellProcessUserSetup = "";
}
// this starts the server in the background. the -d flag is unsupported on windows
return sh.run(
"$processInfo = New-Object System.Diagnostics.ProcessStartInfo; "
+ "$processInfo.FileName = '"
+ bin.elasticsearch
+ "'; "
+ "$processInfo.Arguments = '-p "
+ installation.home.resolve("elasticsearch.pid")
+ "'; "
+ powerShellProcessUserSetup
+ "$processInfo.RedirectStandardOutput = $true; "
+ "$processInfo.RedirectStandardError = $true; "
+ "$processInfo.RedirectStandardInput = $true; "
+ sh.env.entrySet()
.stream()
.map(entry -> "$processInfo.Environment.Add('" + entry.getKey() + "', '" + entry.getValue() + "'); ")
.collect(joining())
+ "$processInfo.UseShellExecute = $false; "
+ "$process = New-Object System.Diagnostics.Process; "
+ "$process.StartInfo = $processInfo; "
+
// set up some asynchronous output handlers
"$outScript = { $EventArgs.Data | Out-File -Encoding UTF8 -Append '"
+ stdout
+ "' }; "
+ "$errScript = { $EventArgs.Data | Out-File -Encoding UTF8 -Append '"
+ stderr
+ "' }; "
+ "$stdOutEvent = Register-ObjectEvent -InputObject $process "
+ "-Action $outScript -EventName 'OutputDataReceived'; "
+ "$stdErrEvent = Register-ObjectEvent -InputObject $process "
+ "-Action $errScript -EventName 'ErrorDataReceived'; "
+
"$process.Start() | Out-Null; "
+ "$process.BeginOutputReadLine(); "
+ "$process.BeginErrorReadLine(); "
+ "$process.StandardInput.WriteLine('"
+ keystorePassword
+ "'); "
+ "Wait-Process -Timeout "
+ ES_STARTUP_SLEEP_TIME_SECONDS
+ " -Id $process.Id; "
+ "$process.Id;"
);
}
final Path stdout = getPowershellOutputPath(installation);
final Path stderr = getPowershellErrorPath(installation);
String powerShellProcessUserSetup;
if (System.getenv("username").equals("vagrant")) {
// the tests will run as Administrator in vagrant.
// we don't want to run the server as Administrator, so we provide the current user's
// username and password to the process which has the effect of starting it not as Administrator.
powerShellProcessUserSetup = "$password = ConvertTo-SecureString 'vagrant' -AsPlainText -Force; "
+ "$processInfo.Username = 'vagrant'; "
+ "$processInfo.Password = $password; ";
} else {
powerShellProcessUserSetup = "";
List<String> command = new ArrayList<>();
if (keystorePassword != null) {
command.add("echo '" + keystorePassword + "' |");
}
command.add(bin.elasticsearch.toString());
command.add("-p");
command.add(installation.home.resolve("elasticsearch.pid").toString());
return sh.runIgnoreExitCode(String.join(" ", command));
}
// this starts the server in the background. the -d flag is unsupported on windows
return sh.run(
"$processInfo = New-Object System.Diagnostics.ProcessStartInfo; "
+ "$processInfo.FileName = '"
+ bin.elasticsearch
+ "'; "
+ "$processInfo.Arguments = '-p "
+ installation.home.resolve("elasticsearch.pid")
+ "'; "
+ powerShellProcessUserSetup
+ "$processInfo.RedirectStandardOutput = $true; "
+ "$processInfo.RedirectStandardError = $true; "
+ "$processInfo.RedirectStandardInput = $true; "
+ sh.env.entrySet()
.stream()
.map(entry -> "$processInfo.Environment.Add('" + entry.getKey() + "', '" + entry.getValue() + "'); ")
.collect(joining())
+ "$processInfo.UseShellExecute = $false; "
+ "$process = New-Object System.Diagnostics.Process; "
+ "$process.StartInfo = $processInfo; "
+
// set up some asynchronous output handlers
"$outScript = { $EventArgs.Data | Out-File -Encoding UTF8 -Append '"
+ stdout
+ "' }; "
+ "$errScript = { $EventArgs.Data | Out-File -Encoding UTF8 -Append '"
+ stderr
+ "' }; "
+ "$stdOutEvent = Register-ObjectEvent -InputObject $process "
+ "-Action $outScript -EventName 'OutputDataReceived'; "
+ "$stdErrEvent = Register-ObjectEvent -InputObject $process "
+ "-Action $errScript -EventName 'ErrorDataReceived'; "
+
"$process.Start() | Out-Null; "
+ "$process.BeginOutputReadLine(); "
+ "$process.BeginErrorReadLine(); "
+ "$process.StandardInput.WriteLine('"
+ keystorePassword
+ "'); "
+ "Wait-Process -Timeout "
+ ES_STARTUP_SLEEP_TIME_SECONDS
+ " -Id $process.Id; "
+ "$process.Id;"
);
}
public static void assertElasticsearchStarted(Installation installation) throws Exception {