[test] port archive distribution packaging tests (#31314)

Recreates the rest of the bats packaging tests for the tar distribution
in the java packaging test project, with support for both tar and zip
packaging, both oss and default flavors, and on Linux and Windows. Most
tests are followed fairly closely, some have either been dropped if
unnecessary or folded into others if convenient.
This commit is contained in:
Andy Bristol 2018-07-10 08:21:20 -07:00 committed by GitHub
parent 8ec33b742d
commit 006c2c9ab0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 677 additions and 301 deletions

View File

@ -546,9 +546,15 @@ class VagrantTestPlugin implements Plugin<Project> {
javaPackagingTest.command = 'ssh'
javaPackagingTest.args = ['--command', 'sudo bash "$PACKAGING_TESTS/run-tests.sh"']
} else {
// powershell sessions run over winrm always run as administrator, whether --elevated is passed or not. however
// remote sessions have some restrictions on what they can do, such as impersonating another user (or the same user
// without administrator elevation), which we need to do for these tests. passing --elevated runs the session
// as a scheduled job locally on the vm as a true administrator to get around this limitation
//
// https://github.com/hashicorp/vagrant/blob/9c299a2a357fcf87f356bb9d56e18a037a53d138/plugins/communicators/winrm/communicator.rb#L195-L225
// https://devops-collective-inc.gitbooks.io/secrets-of-powershell-remoting/content/manuscript/accessing-remote-computers.html
javaPackagingTest.command = 'winrm'
// winrm commands run as administrator
javaPackagingTest.args = ['--command', 'powershell -File "$Env:PACKAGING_TESTS/run-tests.ps1"']
javaPackagingTest.args = ['--elevated', '--command', 'powershell -File "$Env:PACKAGING_TESTS/run-tests.ps1"']
}
TaskExecutionAdapter javaPackagingReproListener = createReproListener(project, javaPackagingTest.path)

View File

@ -31,6 +31,12 @@ dependencies {
compile "org.hamcrest:hamcrest-core:${versions.hamcrest}"
compile "org.hamcrest:hamcrest-library:${versions.hamcrest}"
compile "org.apache.httpcomponents:httpcore:${versions.httpcore}"
compile "org.apache.httpcomponents:httpclient:${versions.httpclient}"
compile "org.apache.httpcomponents:fluent-hc:${versions.httpclient}"
compile "commons-codec:commons-codec:${versions.commonscodec}"
compile "commons-logging:commons-logging:${versions.commonslogging}"
compile project(':libs:core')
// pulls in the jar built by this project and its dependencies
@ -73,3 +79,17 @@ tasks.test.enabled = false
// this project doesn't get published
tasks.dependencyLicenses.enabled = false
tasks.dependenciesInfo.enabled = false
tasks.thirdPartyAudit.excludes = [
//commons-logging optional dependencies
'org.apache.avalon.framework.logger.Logger',
'org.apache.log.Hierarchy',
'org.apache.log.Logger',
'org.apache.log4j.Category',
'org.apache.log4j.Level',
'org.apache.log4j.Logger',
'org.apache.log4j.Priority',
//commons-logging provided dependencies
'javax.servlet.ServletContextEvent',
'javax.servlet.ServletContextListener'
]

View File

@ -19,6 +19,12 @@
package org.elasticsearch.packaging.test;
import org.apache.http.client.fluent.Request;
import org.elasticsearch.packaging.util.Archives;
import org.elasticsearch.packaging.util.Platforms;
import org.elasticsearch.packaging.util.ServerUtils;
import org.elasticsearch.packaging.util.Shell;
import org.elasticsearch.packaging.util.Shell.Result;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
@ -28,9 +34,33 @@ import org.junit.runners.MethodSorters;
import org.elasticsearch.packaging.util.Distribution;
import org.elasticsearch.packaging.util.Installation;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
import static org.elasticsearch.packaging.util.Archives.ARCHIVE_OWNER;
import static org.elasticsearch.packaging.util.Cleanup.cleanEverything;
import static org.elasticsearch.packaging.util.Archives.installArchive;
import static org.elasticsearch.packaging.util.Archives.verifyArchiveInstallation;
import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File;
import static org.elasticsearch.packaging.util.FileMatcher.file;
import static org.elasticsearch.packaging.util.FileMatcher.p660;
import static org.elasticsearch.packaging.util.FileUtils.append;
import static org.elasticsearch.packaging.util.FileUtils.cp;
import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
import static org.elasticsearch.packaging.util.FileUtils.mkdir;
import static org.elasticsearch.packaging.util.FileUtils.rm;
import static org.elasticsearch.packaging.util.ServerUtils.makeRequest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isEmptyString;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
/**
@ -61,4 +91,226 @@ public abstract class ArchiveTestCase {
installation = installArchive(distribution());
verifyArchiveInstallation(installation, distribution());
}
@Test
public void test20PluginsListWithNoPlugins() {
assumeThat(installation, is(notNullValue()));
final Installation.Executables bin = installation.executables();
final Shell sh = new Shell();
final Result r = sh.run(bin.elasticsearchPlugin + " list");
assertThat(r.stdout, isEmptyString());
}
@Test
public void test30AbortWhenJavaMissing() {
assumeThat(installation, is(notNullValue()));
final Installation.Executables bin = installation.executables();
final Shell sh = new Shell();
Platforms.onWindows(() -> {
// on windows, removing java from PATH and removing JAVA_HOME is less involved than changing the permissions of the java
// executable. we also don't check permissions in the windows scripts anyway
final String originalPath = sh.run("$Env:PATH").stdout.trim();
final String newPath = Arrays.stream(originalPath.split(";"))
.filter(path -> path.contains("Java") == false)
.collect(joining(";"));
// note the lack of a $ when clearing the JAVA_HOME env variable - with a $ it deletes the java home directory
// https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/providers/environment-provider?view=powershell-6
//
// this won't persist to another session so we don't have to reset anything
final Result runResult = sh.runIgnoreExitCode(
"$Env:PATH = '" + newPath + "'; " +
"Remove-Item Env:JAVA_HOME; " +
bin.elasticsearch
);
assertThat(runResult.exitCode, is(1));
assertThat(runResult.stderr, containsString("could not find java; set JAVA_HOME or ensure java is in PATH"));
});
Platforms.onLinux(() -> {
final String javaPath = sh.run("which java").stdout.trim();
try {
sh.run("chmod -x '" + javaPath + "'");
final Result runResult = sh.runIgnoreExitCode(bin.elasticsearch.toString());
assertThat(runResult.exitCode, is(1));
assertThat(runResult.stdout, containsString("could not find java; set JAVA_HOME or ensure java is in PATH"));
} finally {
sh.run("chmod +x '" + javaPath + "'");
}
});
}
@Test
public void test40CreateKeystoreManually() {
assumeThat(installation, is(notNullValue()));
final Installation.Executables bin = installation.executables();
final Shell sh = new Shell();
Platforms.onLinux(() -> sh.run("sudo -u " + ARCHIVE_OWNER + " " + bin.elasticsearchKeystore + " create"));
// this is a hack around the fact that we can't run a command in the same session as the same user but not as administrator.
// the keystore ends up being owned by the Administrators group, so we manually set it to be owned by the vagrant user here.
// from the server's perspective the permissions aren't really different, this is just to reflect what we'd expect in the tests.
// when we run these commands as a role user we won't have to do this
Platforms.onWindows(() -> sh.run(
bin.elasticsearchKeystore + " create; " +
"$account = New-Object System.Security.Principal.NTAccount 'vagrant'; " +
"$acl = Get-Acl '" + installation.config("elasticsearch.keystore") + "'; " +
"$acl.SetOwner($account); " +
"Set-Acl '" + installation.config("elasticsearch.keystore") + "' $acl"
));
assertThat(installation.config("elasticsearch.keystore"), file(File, ARCHIVE_OWNER, ARCHIVE_OWNER, p660));
Platforms.onLinux(() -> {
final Result r = sh.run("sudo -u " + ARCHIVE_OWNER + " " + bin.elasticsearchKeystore + " list");
assertThat(r.stdout, containsString("keystore.seed"));
});
Platforms.onWindows(() -> {
final Result r = sh.run(bin.elasticsearchKeystore + " list");
assertThat(r.stdout, containsString("keystore.seed"));
});
}
@Test
public void test50StartAndStop() throws IOException {
assumeThat(installation, is(notNullValue()));
// cleanup from previous test
rm(installation.config("elasticsearch.keystore"));
Archives.runElasticsearch(installation);
final String gcLogName = Platforms.LINUX
? "gc.log.0.current"
: "gc.log";
assertTrue("gc logs exist", Files.exists(installation.logs.resolve(gcLogName)));
ServerUtils.runElasticsearchTests();
Archives.stopElasticsearch(installation);
}
@Test
public void test60AutoCreateKeystore() {
assumeThat(installation, is(notNullValue()));
assertThat(installation.config("elasticsearch.keystore"), file(File, ARCHIVE_OWNER, ARCHIVE_OWNER, p660));
final Installation.Executables bin = installation.executables();
final Shell sh = new Shell();
Platforms.onLinux(() -> {
final Result result = sh.run("sudo -u " + ARCHIVE_OWNER + " " + bin.elasticsearchKeystore + " list");
assertThat(result.stdout, containsString("keystore.seed"));
});
Platforms.onWindows(() -> {
final Result result = sh.run(bin.elasticsearchKeystore + " list");
assertThat(result.stdout, containsString("keystore.seed"));
});
}
@Test
public void test70CustomPathConfAndJvmOptions() throws IOException {
assumeThat(installation, is(notNullValue()));
final Path tempConf = getTempDir().resolve("esconf-alternate");
try {
mkdir(tempConf);
cp(installation.config("elasticsearch.yml"), tempConf.resolve("elasticsearch.yml"));
cp(installation.config("log4j2.properties"), tempConf.resolve("log4j2.properties"));
// we have to disable Log4j from using JMX lest it will hit a security
// manager exception before we have configured logging; this will fail
// startup since we detect usages of logging before it is configured
final String jvmOptions =
"-Xms512m\n" +
"-Xmx512m\n" +
"-Dlog4j2.disable.jmx=true\n";
append(tempConf.resolve("jvm.options"), jvmOptions);
final Shell sh = new Shell();
Platforms.onLinux(() -> sh.run("chown -R elasticsearch:elasticsearch " + tempConf));
Platforms.onWindows(() -> sh.run(
"$account = New-Object System.Security.Principal.NTAccount 'vagrant'; " +
"$tempConf = Get-ChildItem '" + tempConf + "' -Recurse; " +
"$tempConf += Get-Item '" + tempConf + "'; " +
"$tempConf | ForEach-Object { " +
"$acl = Get-Acl $_.FullName; " +
"$acl.SetOwner($account); " +
"Set-Acl $_.FullName $acl " +
"}"
));
final Shell serverShell = new Shell();
serverShell.getEnv().put("ES_PATH_CONF", tempConf.toString());
serverShell.getEnv().put("ES_JAVA_OPTS", "-XX:-UseCompressedOops");
Archives.runElasticsearch(installation, serverShell);
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
assertThat(nodesResponse, containsString("\"heap_init_in_bytes\":536870912"));
assertThat(nodesResponse, containsString("\"using_compressed_ordinary_object_pointers\":\"false\""));
Archives.stopElasticsearch(installation);
} finally {
rm(tempConf);
}
}
@Test
public void test80RelativePathConf() throws IOException {
assumeThat(installation, is(notNullValue()));
final Path temp = getTempDir().resolve("esconf-alternate");
final Path tempConf = temp.resolve("config");
try {
mkdir(tempConf);
Stream.of(
"elasticsearch.yml",
"log4j2.properties",
"jvm.options"
).forEach(file -> cp(installation.config(file), tempConf.resolve(file)));
append(tempConf.resolve("elasticsearch.yml"), "node.name: relative");
final Shell sh = new Shell();
Platforms.onLinux(() -> sh.run("chown -R elasticsearch:elasticsearch " + temp));
Platforms.onWindows(() -> sh.run(
"$account = New-Object System.Security.Principal.NTAccount 'vagrant'; " +
"$tempConf = Get-ChildItem '" + temp + "' -Recurse; " +
"$tempConf += Get-Item '" + temp + "'; " +
"$tempConf | ForEach-Object { " +
"$acl = Get-Acl $_.FullName; " +
"$acl.SetOwner($account); " +
"Set-Acl $_.FullName $acl " +
"}"
));
final Shell serverShell = new Shell(temp);
serverShell.getEnv().put("ES_PATH_CONF", "config");
Archives.runElasticsearch(installation, serverShell);
final String nodesResponse = makeRequest(Request.Get("http://localhost:9200/_nodes"));
assertThat(nodesResponse, containsString("\"name\":\"relative\""));
Archives.stopElasticsearch(installation);
} finally {
rm(tempConf);
}
}
}

View File

@ -19,11 +19,14 @@
package org.elasticsearch.packaging.util;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
import static org.elasticsearch.packaging.util.FileMatcher.Fileness.Directory;
import static org.elasticsearch.packaging.util.FileMatcher.Fileness.File;
import static org.elasticsearch.packaging.util.FileMatcher.file;
@ -36,17 +39,26 @@ import static org.elasticsearch.packaging.util.FileUtils.getPackagingArchivesDir
import static org.elasticsearch.packaging.util.FileUtils.lsGlob;
import static org.elasticsearch.packaging.util.FileUtils.mv;
import static org.elasticsearch.packaging.util.FileUtils.slurp;
import static org.elasticsearch.packaging.util.Platforms.isDPKG;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertTrue;
/**
* Installation and verification logic for archive distributions
*/
public class Archives {
// in the future we'll run as a role user on Windows
public static final String ARCHIVE_OWNER = Platforms.WINDOWS
? "vagrant"
: "elasticsearch";
public static Installation installArchive(Distribution distribution) {
return installArchive(distribution, getDefaultArchiveInstallPath(), getCurrentVersion());
}
@ -63,22 +75,20 @@ public class Archives {
if (distribution.packaging == Distribution.Packaging.TAR) {
if (Platforms.LINUX) {
sh.bash("tar -C " + baseInstallPath + " -xzpf " + distributionFile);
} else {
Platforms.onLinux(() -> sh.run("tar -C " + baseInstallPath + " -xzpf " + distributionFile));
if (Platforms.WINDOWS) {
throw new RuntimeException("Distribution " + distribution + " is not supported on windows");
}
} else if (distribution.packaging == Distribution.Packaging.ZIP) {
if (Platforms.LINUX) {
sh.bash("unzip " + distributionFile + " -d " + baseInstallPath);
} else {
sh.powershell(
"Add-Type -AssemblyName 'System.IO.Compression.Filesystem'; " +
"[IO.Compression.ZipFile]::ExtractToDirectory('" + distributionFile + "', '" + baseInstallPath + "')"
);
}
Platforms.onLinux(() -> sh.run("unzip " + distributionFile + " -d " + baseInstallPath));
Platforms.onWindows(() -> sh.run(
"Add-Type -AssemblyName 'System.IO.Compression.Filesystem'; " +
"[IO.Compression.ZipFile]::ExtractToDirectory('" + distributionFile + "', '" + baseInstallPath + "')"
));
} else {
throw new RuntimeException("Distribution " + distribution + " is not a known archive type");
@ -93,9 +103,8 @@ public class Archives {
assertThat("only the intended installation exists", installations, hasSize(1));
assertThat("only the intended installation exists", installations.get(0), is(fullInstallPath));
if (Platforms.LINUX) {
setupArchiveUsersLinux(fullInstallPath);
}
Platforms.onLinux(() -> setupArchiveUsersLinux(fullInstallPath));
Platforms.onWindows(() -> setupArchiveUsersWindows(fullInstallPath));
return new Installation(fullInstallPath);
}
@ -103,17 +112,17 @@ public class Archives {
private static void setupArchiveUsersLinux(Path installPath) {
final Shell sh = new Shell();
if (sh.bashIgnoreExitCode("getent group elasticsearch").isSuccess() == false) {
if (sh.runIgnoreExitCode("getent group elasticsearch").isSuccess() == false) {
if (isDPKG()) {
sh.bash("addgroup --system elasticsearch");
sh.run("addgroup --system elasticsearch");
} else {
sh.bash("groupadd -r elasticsearch");
sh.run("groupadd -r elasticsearch");
}
}
if (sh.bashIgnoreExitCode("id elasticsearch").isSuccess() == false) {
if (sh.runIgnoreExitCode("id elasticsearch").isSuccess() == false) {
if (isDPKG()) {
sh.bash("adduser " +
sh.run("adduser " +
"--quiet " +
"--system " +
"--no-create-home " +
@ -122,7 +131,7 @@ public class Archives {
"--shell /bin/false " +
"elasticsearch");
} else {
sh.bash("useradd " +
sh.run("useradd " +
"--system " +
"-M " +
"--gid elasticsearch " +
@ -131,20 +140,29 @@ public class Archives {
"elasticsearch");
}
}
sh.bash("chown -R elasticsearch:elasticsearch " + installPath);
sh.run("chown -R elasticsearch:elasticsearch " + installPath);
}
private static void setupArchiveUsersWindows(Path installPath) {
// we want the installation to be owned as the vagrant user rather than the Administrators group
final Shell sh = new Shell();
sh.run(
"$account = New-Object System.Security.Principal.NTAccount 'vagrant'; " +
"$install = Get-ChildItem -Path '" + installPath + "' -Recurse; " +
"$install += Get-Item -Path '" + installPath + "'; " +
"$install | ForEach-Object { " +
"$acl = Get-Acl $_.FullName; " +
"$acl.SetOwner($account); " +
"Set-Acl $_.FullName $acl " +
"}"
);
}
public static void verifyArchiveInstallation(Installation installation, Distribution distribution) {
// on Windows for now we leave the installation owned by the vagrant user that the tests run as. Since the vagrant account
// is a local administrator, the files really end up being owned by the local administrators group. In the future we'll
// install and run elasticesearch with a role user on Windows
final String owner = Platforms.WINDOWS
? "BUILTIN\\Administrators"
: "elasticsearch";
verifyOssInstallation(installation, distribution, owner);
verifyOssInstallation(installation, distribution, ARCHIVE_OWNER);
if (distribution.flavor == Distribution.Flavor.DEFAULT) {
verifyDefaultInstallation(installation, distribution, owner);
verifyDefaultInstallation(installation, distribution, ARCHIVE_OWNER);
}
}
@ -160,38 +178,38 @@ public class Archives {
assertThat(Files.exists(es.data), is(false));
assertThat(Files.exists(es.scripts), is(false));
assertThat(es.home.resolve("bin"), file(Directory, owner, owner, p755));
assertThat(es.home.resolve("lib"), file(Directory, owner, owner, p755));
assertThat(Files.exists(es.config.resolve("elasticsearch.keystore")), is(false));
assertThat(es.bin, file(Directory, owner, owner, p755));
assertThat(es.lib, file(Directory, owner, owner, p755));
assertThat(Files.exists(es.config("elasticsearch.keystore")), is(false));
Stream.of(
"bin/elasticsearch",
"bin/elasticsearch-env",
"bin/elasticsearch-keystore",
"bin/elasticsearch-plugin",
"bin/elasticsearch-translog"
"elasticsearch",
"elasticsearch-env",
"elasticsearch-keystore",
"elasticsearch-plugin",
"elasticsearch-translog"
).forEach(executable -> {
assertThat(es.home.resolve(executable), file(File, owner, owner, p755));
assertThat(es.bin(executable), file(File, owner, owner, p755));
if (distribution.packaging == Distribution.Packaging.ZIP) {
assertThat(es.home.resolve(executable + ".bat"), file(File, owner));
assertThat(es.bin(executable + ".bat"), file(File, owner));
}
});
if (distribution.packaging == Distribution.Packaging.ZIP) {
Stream.of(
"bin/elasticsearch-service.bat",
"bin/elasticsearch-service-mgr.exe",
"bin/elasticsearch-service-x64.exe"
).forEach(executable -> assertThat(es.home.resolve(executable), file(File, owner)));
"elasticsearch-service.bat",
"elasticsearch-service-mgr.exe",
"elasticsearch-service-x64.exe"
).forEach(executable -> assertThat(es.bin(executable), file(File, owner)));
}
Stream.of(
"elasticsearch.yml",
"jvm.options",
"log4j2.properties"
).forEach(config -> assertThat(es.config.resolve(config), file(File, owner, owner, p660)));
).forEach(config -> assertThat(es.config(config), file(File, owner, owner, p660)));
Stream.of(
"NOTICE.txt",
@ -203,30 +221,30 @@ public class Archives {
private static void verifyDefaultInstallation(Installation es, Distribution distribution, String owner) {
Stream.of(
"bin/elasticsearch-certgen",
"bin/elasticsearch-certutil",
"bin/elasticsearch-croneval",
"bin/elasticsearch-migrate",
"bin/elasticsearch-saml-metadata",
"bin/elasticsearch-setup-passwords",
"bin/elasticsearch-sql-cli",
"bin/elasticsearch-syskeygen",
"bin/elasticsearch-users",
"bin/x-pack-env",
"bin/x-pack-security-env",
"bin/x-pack-watcher-env"
"elasticsearch-certgen",
"elasticsearch-certutil",
"elasticsearch-croneval",
"elasticsearch-migrate",
"elasticsearch-saml-metadata",
"elasticsearch-setup-passwords",
"elasticsearch-sql-cli",
"elasticsearch-syskeygen",
"elasticsearch-users",
"x-pack-env",
"x-pack-security-env",
"x-pack-watcher-env"
).forEach(executable -> {
assertThat(es.home.resolve(executable), file(File, owner, owner, p755));
assertThat(es.bin(executable), file(File, owner, owner, p755));
if (distribution.packaging == Distribution.Packaging.ZIP) {
assertThat(es.home.resolve(executable + ".bat"), file(File, owner));
assertThat(es.bin(executable + ".bat"), file(File, owner));
}
});
// at this time we only install the current version of archive distributions, but if that changes we'll need to pass
// the version through here
assertThat(es.home.resolve("bin/elasticsearch-sql-cli-" + getCurrentVersion() + ".jar"), file(File, owner, owner, p755));
assertThat(es.bin("elasticsearch-sql-cli-" + getCurrentVersion() + ".jar"), file(File, owner, owner, p755));
Stream.of(
"users",
@ -234,7 +252,72 @@ public class Archives {
"roles.yml",
"role_mapping.yml",
"log4j2.properties"
).forEach(config -> assertThat(es.config.resolve(config), file(File, owner, owner, p660)));
).forEach(config -> assertThat(es.config(config), file(File, owner, owner, p660)));
}
public static void runElasticsearch(Installation installation) throws IOException {
runElasticsearch(installation, new Shell());
}
public static void runElasticsearch(Installation installation, Shell sh) throws IOException {
final Path pidFile = installation.home.resolve("elasticsearch.pid");
final Installation.Executables bin = installation.executables();
Platforms.onLinux(() -> {
// If jayatana is installed then we try to use it. Elasticsearch should ignore it even when we try.
// If it doesn't ignore it then Elasticsearch will fail to start because of security errors.
// This line is attempting to emulate the on login behavior of /usr/share/upstart/sessions/jayatana.conf
if (Files.exists(Paths.get("/usr/share/java/jayatanaag.jar"))) {
sh.getEnv().put("JAVA_TOOL_OPTIONS", "-javaagent:/usr/share/java/jayatanaag.jar");
}
sh.run("sudo -E -u " + ARCHIVE_OWNER + " " +
bin.elasticsearch + " -d -p " + installation.home.resolve("elasticsearch.pid"));
});
Platforms.onWindows(() -> {
// this starts the server in the background. the -d flag is unsupported on windows
// these tests run as Administrator. 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.
sh.run(
"$password = ConvertTo-SecureString 'vagrant' -AsPlainText -Force; " +
"$processInfo = New-Object System.Diagnostics.ProcessStartInfo; " +
"$processInfo.FileName = '" + bin.elasticsearch + "'; " +
"$processInfo.Arguments = '-p " + installation.home.resolve("elasticsearch.pid") + "'; " +
"$processInfo.Username = 'vagrant'; " +
"$processInfo.Password = $password; " +
"$processInfo.RedirectStandardOutput = $true; " +
"$processInfo.RedirectStandardError = $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; " +
"$process.Start() | Out-Null; " +
"$process.Id;"
);
});
ServerUtils.waitForElasticsearch();
assertTrue(Files.exists(pidFile));
String pid = slurp(pidFile).trim();
assertThat(pid, not(isEmptyOrNullString()));
Platforms.onLinux(() -> sh.run("ps " + pid));
Platforms.onWindows(() -> sh.run("Get-Process -Id " + pid));
}
public static void stopElasticsearch(Installation installation) {
Path pidFile = installation.home.resolve("elasticsearch.pid");
assertTrue(Files.exists(pidFile));
String pid = slurp(pidFile).trim();
assertThat(pid, not(isEmptyOrNullString()));
final Shell sh = new Shell();
Platforms.onLinux(() -> sh.run("kill -SIGTERM " + pid));
Platforms.onWindows(() -> sh.run("Get-Process -Id " + pid + " | Stop-Process -Force"));
}
}

View File

@ -56,31 +56,28 @@ public class Cleanup {
final Shell sh = new Shell();
// kill elasticsearch processes
if (Platforms.WINDOWS) {
Platforms.onLinux(() -> {
sh.runIgnoreExitCode("pkill -u elasticsearch");
sh.runIgnoreExitCode("ps aux | grep -i 'org.elasticsearch.bootstrap.Elasticsearch' | awk {'print $2'} | xargs kill -9");
});
Platforms.onWindows(() -> {
// the view of processes returned by Get-Process doesn't expose command line arguments, so we use WMI here
sh.powershellIgnoreExitCode(
sh.runIgnoreExitCode(
"Get-WmiObject Win32_Process | " +
"Where-Object { $_.CommandLine -Match 'org.elasticsearch.bootstrap.Elasticsearch' } | " +
"ForEach-Object { $_.Terminate() }"
);
});
} else {
sh.bashIgnoreExitCode("pkill -u elasticsearch");
sh.bashIgnoreExitCode("ps aux | grep -i 'org.elasticsearch.bootstrap.Elasticsearch' | awk {'print $2'} | xargs kill -9");
}
if (Platforms.LINUX) {
purgePackagesLinux();
}
Platforms.onLinux(Cleanup::purgePackagesLinux);
// remove elasticsearch users
if (Platforms.LINUX) {
sh.bashIgnoreExitCode("userdel elasticsearch");
sh.bashIgnoreExitCode("groupdel elasticsearch");
}
Platforms.onLinux(() -> {
sh.runIgnoreExitCode("userdel elasticsearch");
sh.runIgnoreExitCode("groupdel elasticsearch");
});
// when we run es as a role user on windows, add the equivalent here
// delete files that may still exist
lsGlob(getTempDir(), "elasticsearch*").forEach(FileUtils::rm);
@ -95,7 +92,7 @@ public class Cleanup {
// disable elasticsearch service
// todo add this for windows when adding tests for service intallation
if (Platforms.LINUX && isSystemd()) {
sh.bash("systemctl unmask systemd-sysctl.service");
sh.run("systemctl unmask systemd-sysctl.service");
}
}
@ -103,19 +100,19 @@ public class Cleanup {
final Shell sh = new Shell();
if (isRPM()) {
sh.bashIgnoreExitCode("rpm --quiet -e elasticsearch elasticsearch-oss");
sh.runIgnoreExitCode("rpm --quiet -e elasticsearch elasticsearch-oss");
}
if (isYUM()) {
sh.bashIgnoreExitCode("yum remove -y elasticsearch elasticsearch-oss");
sh.runIgnoreExitCode("yum remove -y elasticsearch elasticsearch-oss");
}
if (isDPKG()) {
sh.bashIgnoreExitCode("dpkg --purge elasticsearch elasticsearch-oss");
sh.runIgnoreExitCode("dpkg --purge elasticsearch elasticsearch-oss");
}
if (isAptGet()) {
sh.bashIgnoreExitCode("apt-get --quiet --yes purge elasticsearch elasticsearch-oss");
sh.runIgnoreExitCode("apt-get --quiet --yes purge elasticsearch elasticsearch-oss");
}
}
}

View File

@ -21,11 +21,14 @@ package org.elasticsearch.packaging.util;
import org.elasticsearch.core.internal.io.IOUtils;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
@ -63,6 +66,22 @@ public class FileUtils {
}
}
public static Path mkdir(Path path) {
try {
return Files.createDirectories(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Path cp(Path source, Path target) {
try {
return Files.copy(source, target);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Path mv(Path source, Path target) {
try {
return Files.move(source, target);
@ -71,9 +90,19 @@ public class FileUtils {
}
}
public static void append(Path file, String text) {
try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8,
StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
writer.write(text);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String slurp(Path file) {
try {
return String.join("\n", Files.readAllLines(file));
return String.join("\n", Files.readAllLines(file, StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@ -27,6 +27,8 @@ import java.nio.file.Path;
public class Installation {
public final Path home;
public final Path bin; // this isn't a first-class installation feature but we include it for convenience
public final Path lib; // same
public final Path config;
public final Path data;
public final Path logs;
@ -36,6 +38,9 @@ public class Installation {
public Installation(Path home, Path config, Path data, Path logs, Path plugins, Path modules, Path scripts) {
this.home = home;
this.bin = home.resolve("bin");
this.lib = home.resolve("lib");
this.config = config;
this.data = data;
this.logs = logs;
@ -55,4 +60,31 @@ public class Installation {
home.resolve("scripts")
);
}
public Path bin(String executableName) {
return bin.resolve(executableName);
}
public Path config(String configFileName) {
return config.resolve(configFileName);
}
public Executables executables() {
return new Executables();
}
public class Executables {
public final Path elasticsearch = platformExecutable("elasticsearch");
public final Path elasticsearchPlugin = platformExecutable("elasticsearch-plugin");
public final Path elasticsearchKeystore = platformExecutable("elasticsearch-keystore");
public final Path elasticsearchTranslog = platformExecutable("elasticsearch-translog");
private Path platformExecutable(String name) {
final String platformExecutableName = Platforms.WINDOWS
? name + ".bat"
: name;
return bin(platformExecutableName);
}
}
}

View File

@ -28,41 +28,61 @@ public class Platforms {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which dpkg").isSuccess();
return new Shell().runIgnoreExitCode("which dpkg").isSuccess();
}
public static boolean isAptGet() {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which apt-get").isSuccess();
return new Shell().runIgnoreExitCode("which apt-get").isSuccess();
}
public static boolean isRPM() {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which rpm").isSuccess();
return new Shell().runIgnoreExitCode("which rpm").isSuccess();
}
public static boolean isYUM() {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which yum").isSuccess();
return new Shell().runIgnoreExitCode("which yum").isSuccess();
}
public static boolean isSystemd() {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which systemctl").isSuccess();
return new Shell().runIgnoreExitCode("which systemctl").isSuccess();
}
public static boolean isSysVInit() {
if (WINDOWS) {
return false;
}
return new Shell().bashIgnoreExitCode("which service").isSuccess();
return new Shell().runIgnoreExitCode("which service").isSuccess();
}
public static void onWindows(PlatformAction action) {
if (WINDOWS) {
action.run();
}
}
public static void onLinux(PlatformAction action) {
if (LINUX) {
action.run();
}
}
/**
* Essentially a Runnable, but we make the distinction so it's more clear that these are synchronous
*/
@FunctionalInterface
public interface PlatformAction {
void run();
}
}

View File

@ -0,0 +1,123 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.packaging.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.ContentType;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class ServerUtils {
private static final Log LOG = LogFactory.getLog(ServerUtils.class);
private static final long waitTime = TimeUnit.SECONDS.toMillis(60);
private static final long timeoutLength = TimeUnit.SECONDS.toMillis(10);
public static void waitForElasticsearch() throws IOException {
waitForElasticsearch("green", null);
}
public static void waitForElasticsearch(String status, String index) throws IOException {
Objects.requireNonNull(status);
// we loop here rather than letting httpclient handle retries so we can measure the entire waiting time
final long startTime = System.currentTimeMillis();
long timeElapsed = 0;
boolean started = false;
while (started == false && timeElapsed < waitTime) {
try {
final HttpResponse response = Request.Get("http://localhost:9200/_cluster/health")
.connectTimeout((int) timeoutLength)
.socketTimeout((int) timeoutLength)
.execute()
.returnResponse();
if (response.getStatusLine().getStatusCode() >= 300) {
final String statusLine = response.getStatusLine().toString();
final String body = EntityUtils.toString(response.getEntity());
throw new RuntimeException("Connecting to elasticsearch cluster health API failed:\n" + statusLine+ "\n" + body);
}
started = true;
} catch (HttpHostConnectException e) {
// we want to retry if the connection is refused
LOG.info("Got connection refused when waiting for cluster health", e);
}
timeElapsed = System.currentTimeMillis() - startTime;
}
if (started == false) {
throw new RuntimeException("Elasticsearch did not start");
}
final String url;
if (index == null) {
url = "http://localhost:9200/_cluster/health?wait_for_status=" + status + "&timeout=60s&pretty";
} else {
url = "http://localhost:9200/_cluster/health/" + index + "?wait_for_status=" + status + "&timeout=60s&pretty";
}
final String body = makeRequest(Request.Get(url));
assertThat("cluster health response must contain desired status", body, containsString(status));
}
public static void runElasticsearchTests() throws IOException {
makeRequest(
Request.Post("http://localhost:9200/library/book/1?refresh=true&pretty")
.bodyString("{ \"title\": \"Book #1\", \"pages\": 123 }", ContentType.APPLICATION_JSON));
makeRequest(
Request.Post("http://localhost:9200/library/book/2?refresh=true&pretty")
.bodyString("{ \"title\": \"Book #2\", \"pages\": 456 }", ContentType.APPLICATION_JSON));
String count = makeRequest(Request.Get("http://localhost:9200/_count?pretty"));
assertThat(count, containsString("\"count\" : 2"));
makeRequest(Request.Delete("http://localhost:9200/_all"));
}
public static String makeRequest(Request request) throws IOException {
final HttpResponse response = request.execute().returnResponse();
final String body = EntityUtils.toString(response.getEntity());
if (response.getStatusLine().getStatusCode() >= 300) {
throw new RuntimeException("Request failed:\n" + response.getStatusLine().toString() + "\n" + body);
}
return body;
}
}

View File

@ -58,58 +58,50 @@ public class Shell {
this.workingDirectory = workingDirectory;
}
/**
* Runs a script in a bash shell, throwing an exception if its exit code is nonzero
*/
public Result bash(String script) {
return run(bashCommand(script));
public Map<String, String> getEnv() {
return env;
}
/**
* Runs a script in a bash shell
* Run the provided string as a shell script. On Linux the {@code bash -c [script]} syntax will be used, and on Windows
* the {@code powershell.exe -Command [script]} syntax will be used. Throws an exception if the exit code of the script is nonzero
*/
public Result bashIgnoreExitCode(String script) {
return runIgnoreExitCode(bashCommand(script));
public Result run(String script) {
return runScript(getScriptCommand(script));
}
/**
* Same as {@link #run(String)}, but does not throw an exception if the exit code of the script is nonzero
*/
public Result runIgnoreExitCode(String script) {
return runScriptIgnoreExitCode(getScriptCommand(script));
}
private String[] getScriptCommand(String script) {
if (Platforms.WINDOWS) {
return powershellCommand(script);
} else {
return bashCommand(script);
}
}
private static String[] bashCommand(String script) {
return Stream.concat(Stream.of("bash", "-c"), Stream.of(script)).toArray(String[]::new);
}
/**
* Runs a script in a powershell shell, throwing an exception if its exit code is nonzero
*/
public Result powershell(String script) {
return run(powershellCommand(script));
}
/**
* Runs a script in a powershell shell
*/
public Result powershellIgnoreExitCode(String script) {
return runIgnoreExitCode(powershellCommand(script));
}
private static String[] powershellCommand(String script) {
return Stream.concat(Stream.of("powershell.exe", "-Command"), Stream.of(script)).toArray(String[]::new);
}
/**
* Runs an executable file, passing all elements of {@code command} after the first as arguments. Throws an exception if the process'
* exit code is nonzero
*/
private Result run(String[] command) {
Result result = runIgnoreExitCode(command);
private Result runScript(String[] command) {
Result result = runScriptIgnoreExitCode(command);
if (result.isSuccess() == false) {
throw new RuntimeException("Command was not successful: [" + String.join(" ", command) + "] result: " + result.toString());
}
return result;
}
/**
* Runs an executable file, passing all elements of {@code command} after the first as arguments
*/
private Result runIgnoreExitCode(String[] command) {
private Result runScriptIgnoreExitCode(String[] command) {
ProcessBuilder builder = new ProcessBuilder();
builder.command(command);

View File

@ -1,178 +0,0 @@
#!/usr/bin/env bats
# This file is used to test the tar gz package.
# WARNING: This testing file must be executed as root and can
# dramatically change your system. It should only be executed
# in a throw-away VM like those made by the Vagrantfile at
# the root of the Elasticsearch source code. This should
# cause the script to fail if it is executed any other way:
[ -f /etc/is_vagrant_vm ] || {
>&2 echo "must be run on a vagrant VM"
exit 1
}
# The test case can be executed with the Bash Automated
# Testing System tool available at https://github.com/sstephenson/bats
# Thanks to Sam Stephenson!
# Licensed to Elasticsearch under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# Load test utilities
load $BATS_UTILS/utils.bash
load $BATS_UTILS/tar.bash
load $BATS_UTILS/plugins.bash
setup() {
skip_not_tar_gz
export ESHOME=/tmp/elasticsearch
export_elasticsearch_paths
}
##################################
# Install TAR GZ package
##################################
@test "[TAR] tar command is available" {
# Cleans everything for the 1st execution
clean_before_test
run tar --version
[ "$status" -eq 0 ]
}
@test "[TAR] archive is available" {
local version=$(cat version)
count=$(find . -type f -name "${PACKAGE_NAME}-${version}.tar.gz" | wc -l)
[ "$count" -eq 1 ]
}
@test "[TAR] archive is not installed" {
count=$(find /tmp -type d -name 'elasticsearch*' | wc -l)
[ "$count" -eq 0 ]
}
@test "[TAR] install archive" {
# Install the archive
install_archive
set_debug_logging
count=$(find /tmp -type d -name 'elasticsearch*' | wc -l)
[ "$count" -eq 1 ]
# Its simpler to check that the install was correct in this test rather
# than in another test because install_archive sets a number of path
# variables that verify_archive_installation reads. To separate this into
# another test you'd have to recreate the variables.
verify_archive_installation
}
@test "[TAR] verify elasticsearch-plugin list runs without any plugins installed" {
# previously this would fail because the archive installations did
# not create an empty plugins directory
local plugins_list=`$ESHOME/bin/elasticsearch-plugin list`
[[ -z $plugins_list ]]
}
@test "[TAR] elasticsearch fails if java executable is not found" {
local JAVA=$(which java)
sudo chmod -x $JAVA
run "$ESHOME/bin/elasticsearch"
sudo chmod +x $JAVA
[ "$status" -eq 1 ]
local expected="could not find java; set JAVA_HOME or ensure java is in PATH"
[[ "$output" == *"$expected"* ]] || {
echo "Expected error message [$expected] but found: $output"
false
}
}
@test "[TAR] test creating elasticearch.keystore" {
sudo -E -u elasticsearch "$ESHOME/bin/elasticsearch-keystore" create
assert_file "$ESCONFIG/elasticsearch.keystore" f elasticsearch elasticsearch 660
sudo -E -u elasticsearch "$ESHOME/bin/elasticsearch-keystore" list | grep "keystore.seed"
# cleanup for the next test
rm -rf "$ESCONFIG/elasticsearch.keystore"
}
##################################
# Check that Elasticsearch is working
##################################
@test "[TAR] test elasticsearch" {
start_elasticsearch_service
run_elasticsearch_tests
stop_elasticsearch_service
}
@test "[TAR] test auto-creating elasticearch.keystore" {
# a keystore should automatically be created after the service is started
assert_file "$ESCONFIG/elasticsearch.keystore" f elasticsearch elasticsearch 660
# the keystore should be seeded
sudo -E -u elasticsearch "$ESHOME/bin/elasticsearch-keystore" list | grep "keystore.seed"
}
@test "[TAR] start Elasticsearch with custom JVM options" {
local es_java_opts=$ES_JAVA_OPTS
local es_path_conf=$ES_PATH_CONF
local temp=`mktemp -d`
cp "$ESCONFIG"/elasticsearch.yml "$temp"
cp "$ESCONFIG"/log4j2.properties "$temp"
touch "$temp/jvm.options"
chown -R elasticsearch:elasticsearch "$temp"
echo "-Xms512m" >> "$temp/jvm.options"
echo "-Xmx512m" >> "$temp/jvm.options"
# we have to disable Log4j from using JMX lest it will hit a security
# manager exception before we have configured logging; this will fail
# startup since we detect usages of logging before it is configured
echo "-Dlog4j2.disable.jmx=true" >> "$temp/jvm.options"
export ES_PATH_CONF="$temp"
export ES_JAVA_OPTS="-XX:-UseCompressedOops"
start_elasticsearch_service
curl -s -XGET localhost:9200/_nodes | fgrep '"heap_init_in_bytes":536870912'
curl -s -XGET localhost:9200/_nodes | fgrep '"using_compressed_ordinary_object_pointers":"false"'
stop_elasticsearch_service
export ES_PATH_CONF=$es_path_conf
export ES_JAVA_OPTS=$es_java_opts
}
@test "[TAR] GC logs exist" {
start_elasticsearch_service
assert_file_exist $ESHOME/logs/gc.log.0.current
stop_elasticsearch_service
}
@test "[TAR] relative ES_PATH_CONF" {
local es_path_conf=$ES_PATH_CONF
local temp=`mktemp -d`
mkdir "$temp"/config
cp "$ESCONFIG"/elasticsearch.yml "$temp"/config
cp "$ESCONFIG"/log4j2.properties "$temp"/config
cp "$ESCONFIG/jvm.options" "$temp/config"
chown -R elasticsearch:elasticsearch "$temp"
echo "node.name: relative" >> "$temp"/config/elasticsearch.yml
cd "$temp"
export ES_PATH_CONF=config
start_elasticsearch_service
curl -s -XGET localhost:9200/_nodes | fgrep '"name":"relative"'
stop_elasticsearch_service
export ES_PATH_CONF=$es_path_conf
}
@test "[TAR] remove tar" {
rm -rf "/tmp/elasticsearch"
}