[test] java tests for archive packaging (#30734)

Ports the first couple tests for archive distributions from the old bats
project to the new java project that includes windows platforms,
consolidating them into one test method that tests that the
distributions can be extracted and their contents verified. Includes the
zip distributions which were not tested in the bats project.
This commit is contained in:
Andy Bristol 2018-05-23 10:37:57 -07:00 committed by GitHub
parent 4fd0a3e492
commit a1b538122c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1313 additions and 17 deletions

View File

@ -512,7 +512,9 @@ into it
vagrant ssh debian-9
--------------------------------------------
Now inside the VM, to run the https://github.com/sstephenson/bats[bats] packaging tests
Now inside the VM, start the packaging tests from the terminal. There are two packaging
test projects. The old ones are written with https://github.com/sstephenson/bats[bats]
and only run on linux. To run them do
--------------------------------------------
cd $PACKAGING_ARCHIVES
@ -524,18 +526,36 @@ sudo bats $BATS_TESTS/*.bats
sudo bats $BATS_TESTS/20_tar_package.bats $BATS_TESTS/25_tar_plugins.bats
--------------------------------------------
To run the Java packaging tests, again inside the VM
The new packaging tests are written in Java and run on both linux and windows. On
linux (again, inside the VM)
--------------------------------------------
bash $PACKAGING_TESTS/run-tests.sh
# run the full suite
sudo bash $PACKAGING_TESTS/run-tests.sh
# run specific test cases
sudo bash $PACKAGING_TESTS/run-tests.sh \
org.elasticsearch.packaging.test.DefaultZipTests \
org.elasticsearch.packaging.test.OssZipTests
--------------------------------------------
or on Windows
or on Windows, from a terminal running as Administrator
--------------------------------------------
# run the full suite
powershell -File $Env:PACKAGING_TESTS/run-tests.ps1
# run specific test cases
powershell -File $Env:PACKAGING_TESTS/run-tests.ps1 `
org.elasticsearch.packaging.test.DefaultZipTests `
org.elasticsearch.packaging.test.OssZipTests
--------------------------------------------
Note that on Windows boxes when running from inside the GUI, you may have to log out and
back in to the `vagrant` user (password `vagrant`) for the environment variables that
locate the packaging tests and distributions to take effect, due to how vagrant provisions
Windows machines.
When you've made changes you want to test, keep the VM up and reload the tests and
distributions inside by running (on the host)

1
Vagrantfile vendored
View File

@ -237,6 +237,7 @@ def linux_common(config,
config.vm.provision 'markerfile', type: 'shell', inline: <<-SHELL
touch /etc/is_vagrant_vm
touch /is_vagrant_vm # for consistency between linux and windows
SHELL
# This prevents leftovers from previous tests using the

View File

@ -52,6 +52,8 @@ class VagrantTestPlugin implements Plugin<Project> {
static final List<String> DISTRIBUTIONS = unmodifiableList([
'archives:tar',
'archives:oss-tar',
'archives:zip',
'archives:oss-zip',
'packages:rpm',
'packages:oss-rpm',
'packages:deb',
@ -242,13 +244,27 @@ class VagrantTestPlugin implements Plugin<Project> {
Task createLinuxRunnerScript = project.tasks.create('createLinuxRunnerScript', FileContentsTask) {
dependsOn copyPackagingTests
file "${testsDir}/run-tests.sh"
contents "java -cp \"\$PACKAGING_TESTS/*\" org.junit.runner.JUnitCore ${-> project.extensions.esvagrant.testClass}"
contents """\
if [ "\$#" -eq 0 ]; then
test_args=( "${-> project.extensions.esvagrant.testClass}" )
else
test_args=( "\$@" )
fi
java -cp "\$PACKAGING_TESTS/*" org.elasticsearch.packaging.VMTestRunner "\${test_args[@]}"
"""
}
Task createWindowsRunnerScript = project.tasks.create('createWindowsRunnerScript', FileContentsTask) {
dependsOn copyPackagingTests
file "${testsDir}/run-tests.ps1"
// the use of $args rather than param() here is deliberate because the syntax for array (multivalued) parameters is likely
// a little trappy for those unfamiliar with powershell
contents """\
java -cp "\$Env:PACKAGING_TESTS/*" org.junit.runner.JUnitCore ${-> project.extensions.esvagrant.testClass}
if (\$args.Count -eq 0) {
\$testArgs = @("${-> project.extensions.esvagrant.testClass}")
} else {
\$testArgs = \$args
}
java -cp "\$Env:PACKAGING_TESTS/*" org.elasticsearch.packaging.VMTestRunner @testArgs
exit \$LASTEXITCODE
"""
}
@ -525,9 +541,10 @@ class VagrantTestPlugin implements Plugin<Project> {
if (LINUX_BOXES.contains(box)) {
javaPackagingTest.command = 'ssh'
javaPackagingTest.args = ['--command', 'bash "$PACKAGING_TESTS/run-tests.sh"']
javaPackagingTest.args = ['--command', 'sudo bash "$PACKAGING_TESTS/run-tests.sh"']
} else {
javaPackagingTest.command = 'winrm'
// winrm commands run as administrator
javaPackagingTest.args = ['--command', 'powershell -File "$Env:PACKAGING_TESTS/run-tests.ps1"']
}

View File

@ -29,9 +29,9 @@ plugins {
dependencies {
compile "junit:junit:${versions.junit}"
compile "org.hamcrest:hamcrest-core:${versions.hamcrest}"
compile "org.hamcrest:hamcrest-library:${versions.hamcrest}"
// needs to be on the classpath for JarHell
testRuntime project(':libs:elasticsearch-core')
compile project(':libs:elasticsearch-core')
// pulls in the jar built by this project and its dependencies
packagingTest project(path: project.path, configuration: 'runtime')

View File

@ -19,13 +19,20 @@
package org.elasticsearch.packaging;
import org.junit.Test;
import org.elasticsearch.packaging.test.OssTarTests;
import org.elasticsearch.packaging.test.OssZipTests;
import org.elasticsearch.packaging.test.DefaultTarTests;
import org.elasticsearch.packaging.test.DefaultZipTests;
/**
* This class doesn't have any tests yet
*/
public class PackagingTests {
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@Test
public void testDummy() {}
}
@RunWith(Suite.class)
@SuiteClasses({
DefaultTarTests.class,
DefaultZipTests.class,
OssTarTests.class,
OssZipTests.class
})
public class PackagingTests {}

View File

@ -0,0 +1,40 @@
/*
* 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;
import org.junit.runner.JUnitCore;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* Ensures that the current JVM is running on a virtual machine before delegating to {@link JUnitCore}. We just check for the existence
* of a special file that we create during VM provisioning.
*/
public class VMTestRunner {
public static void main(String[] args) {
if (Files.exists(Paths.get("/is_vagrant_vm"))) {
JUnitCore.main(args);
} else {
throw new RuntimeException("This filesystem does not have an expected marker file indicating it's a virtual machine. These " +
"tests should only run in a virtual machine because they're destructive.");
}
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.test;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.elasticsearch.packaging.util.Distribution;
import org.elasticsearch.packaging.util.Installation;
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.hamcrest.CoreMatchers.is;
import static org.junit.Assume.assumeThat;
/**
* Tests that apply to the archive distributions (tar, zip). To add a case for a distribution, subclass and
* override {@link ArchiveTestCase#distribution()}. These tests should be the same across all archive distributions
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public abstract class ArchiveTestCase {
private static Installation installation;
/** The {@link Distribution} that should be tested in this case */
protected abstract Distribution distribution();
@BeforeClass
public static void cleanup() {
installation = null;
cleanEverything();
}
@Before
public void onlyCompatibleDistributions() {
assumeThat(distribution().packaging.compatible, is(true));
}
@Test
public void test10Install() {
installation = installArchive(distribution());
verifyArchiveInstallation(installation, distribution());
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.test;
import org.elasticsearch.packaging.util.Distribution;
public class DefaultTarTests extends ArchiveTestCase {
@Override
protected Distribution distribution() {
return Distribution.DEFAULT_TAR;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.test;
import org.elasticsearch.packaging.util.Distribution;
public class DefaultZipTests extends ArchiveTestCase {
@Override
protected Distribution distribution() {
return Distribution.DEFAULT_ZIP;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.test;
import org.elasticsearch.packaging.util.Distribution;
public class OssTarTests extends ArchiveTestCase {
@Override
protected Distribution distribution() {
return Distribution.OSS_TAR;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.test;
import org.elasticsearch.packaging.util.Distribution;
public class OssZipTests extends ArchiveTestCase {
@Override
protected Distribution distribution() {
return Distribution.OSS_ZIP;
}
}

View File

@ -0,0 +1,239 @@
/*
* 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 java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
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;
import static org.elasticsearch.packaging.util.FileMatcher.p644;
import static org.elasticsearch.packaging.util.FileMatcher.p660;
import static org.elasticsearch.packaging.util.FileMatcher.p755;
import static org.elasticsearch.packaging.util.FileUtils.getCurrentVersion;
import static org.elasticsearch.packaging.util.FileUtils.getDefaultArchiveInstallPath;
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.Platforms.isDPKG;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
/**
* Installation and verification logic for archive distributions
*/
public class Archives {
public static Installation installArchive(Distribution distribution) {
return installArchive(distribution, getDefaultArchiveInstallPath(), getCurrentVersion());
}
public static Installation installArchive(Distribution distribution, Path fullInstallPath, String version) {
final Shell sh = new Shell();
final Path distributionFile = getPackagingArchivesDir().resolve(distribution.filename(version));
final Path baseInstallPath = fullInstallPath.getParent();
final Path extractedPath = baseInstallPath.resolve("elasticsearch-" + version);
assertThat("distribution file must exist", Files.exists(distributionFile), is(true));
assertThat("elasticsearch must not already be installed", lsGlob(baseInstallPath, "elasticsearch*"), empty());
if (distribution.packaging == Distribution.Packaging.TAR) {
if (Platforms.LINUX) {
sh.run("tar", "-C", baseInstallPath.toString(), "-xzpf", distributionFile.toString());
} else {
throw new RuntimeException("Distribution " + distribution + " is not supported on windows");
}
} else if (distribution.packaging == Distribution.Packaging.ZIP) {
if (Platforms.LINUX) {
sh.run("unzip", distributionFile.toString(), "-d", baseInstallPath.toString());
} else {
sh.run("powershell.exe", "-Command",
"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");
}
assertThat("archive was extracted", Files.exists(extractedPath), is(true));
mv(extractedPath, fullInstallPath);
assertThat("extracted archive moved to install location", Files.exists(fullInstallPath));
final List<Path> installations = lsGlob(baseInstallPath, "elasticsearch*");
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);
}
return new Installation(fullInstallPath);
}
private static void setupArchiveUsersLinux(Path installPath) {
final Shell sh = new Shell();
if (sh.runIgnoreExitCode("getent", "group", "elasticsearch").isSuccess() == false) {
if (isDPKG()) {
sh.run("addgroup", "--system", "elasticsearch");
} else {
sh.run("groupadd", "-r", "elasticsearch");
}
}
if (sh.runIgnoreExitCode("id", "elasticsearch").isSuccess() == false) {
if (isDPKG()) {
sh.run("adduser",
"--quiet",
"--system",
"--no-create-home",
"--ingroup", "elasticsearch",
"--disabled-password",
"--shell", "/bin/false",
"elasticsearch");
} else {
sh.run("useradd",
"--system",
"-M",
"--gid", "elasticsearch",
"--shell", "/sbin/nologin",
"--comment", "elasticsearch user",
"elasticsearch");
}
}
sh.run("chown", "-R", "elasticsearch:elasticsearch", installPath.toString());
}
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);
if (distribution.flavor == Distribution.Flavor.DEFAULT) {
verifyDefaultInstallation(installation, distribution, owner);
}
}
private static void verifyOssInstallation(Installation es, Distribution distribution, String owner) {
Stream.of(
es.home,
es.config,
es.plugins,
es.modules,
es.logs
).forEach(dir -> assertThat(dir, file(Directory, owner, owner, p755)));
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));
Stream.of(
"bin/elasticsearch",
"bin/elasticsearch-env",
"bin/elasticsearch-keystore",
"bin/elasticsearch-plugin",
"bin/elasticsearch-translog"
).forEach(executable -> {
assertThat(es.home.resolve(executable), file(File, owner, owner, p755));
if (distribution.packaging == Distribution.Packaging.ZIP) {
assertThat(es.home.resolve(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)));
}
Stream.of(
"elasticsearch.yml",
"jvm.options",
"log4j2.properties"
).forEach(config -> assertThat(es.config.resolve(config), file(File, owner, owner, p660)));
Stream.of(
"NOTICE.txt",
"LICENSE.txt",
"README.textile"
).forEach(doc -> assertThat(es.home.resolve(doc), file(File, owner, owner, p644)));
}
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"
).forEach(executable -> {
assertThat(es.home.resolve(executable), file(File, owner, owner, p755));
if (distribution.packaging == Distribution.Packaging.ZIP) {
assertThat(es.home.resolve(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));
Stream.of(
"users",
"users_roles",
"roles.yml",
"role_mapping.yml",
"log4j2.properties"
).forEach(config -> assertThat(es.config.resolve(config), file(File, owner, owner, p660)));
}
}

View File

@ -0,0 +1,121 @@
/*
* 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 java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.elasticsearch.packaging.util.FileUtils.getTempDir;
import static org.elasticsearch.packaging.util.FileUtils.lsGlob;
import static org.elasticsearch.packaging.util.Platforms.isAptGet;
import static org.elasticsearch.packaging.util.Platforms.isDPKG;
import static org.elasticsearch.packaging.util.Platforms.isRPM;
import static org.elasticsearch.packaging.util.Platforms.isSystemd;
import static org.elasticsearch.packaging.util.Platforms.isYUM;
public class Cleanup {
private static final List<String> ELASTICSEARCH_FILES_LINUX = Arrays.asList(
"/usr/share/elasticsearch",
"/etc/elasticsearch",
"/var/lib/elasticsearch",
"/var/log/elasticsearch",
"/etc/default/elasticsearch",
"/etc/sysconfig/elasticsearch",
"/var/run/elasticsearch",
"/usr/share/doc/elasticsearch",
"/usr/lib/systemd/system/elasticsearch.conf",
"/usr/lib/tmpfiles.d/elasticsearch.conf",
"/usr/lib/sysctl.d/elasticsearch.conf"
);
// todo
private static final List<String> ELASTICSEARCH_FILES_WINDOWS = Collections.emptyList();
public static void cleanEverything() {
final Shell sh = new Shell();
// kill elasticsearch processes
if (Platforms.WINDOWS) {
// the view of processes returned by Get-Process doesn't expose command line arguments, so we use WMI here
sh.runIgnoreExitCode("powershell.exe", "-Command",
"Get-WmiObject Win32_Process | " +
"Where-Object { $_.CommandLine -Match 'org.elasticsearch.bootstrap.Elasticsearch' } | " +
"ForEach-Object { $_.Terminate() }");
} else {
sh.runIgnoreExitCode("pkill", "-u", "elasticsearch");
sh.runIgnoreExitCode("bash", "-c",
"ps aux | grep -i 'org.elasticsearch.bootstrap.Elasticsearch' | awk {'print $2'} | xargs kill -9");
}
if (Platforms.LINUX) {
purgePackagesLinux();
}
// remove elasticsearch users
if (Platforms.LINUX) {
sh.runIgnoreExitCode("userdel", "elasticsearch");
sh.runIgnoreExitCode("groupdel", "elasticsearch");
}
// delete files that may still exist
lsGlob(getTempDir(), "elasticsearch*").forEach(FileUtils::rm);
final List<String> filesToDelete = Platforms.WINDOWS
? ELASTICSEARCH_FILES_WINDOWS
: ELASTICSEARCH_FILES_LINUX;
filesToDelete.stream()
.map(Paths::get)
.filter(Files::exists)
.forEach(FileUtils::rm);
// disable elasticsearch service
// todo add this for windows when adding tests for service intallation
if (Platforms.LINUX && isSystemd()) {
sh.run("systemctl", "unmask", "systemd-sysctl.service");
}
}
private static void purgePackagesLinux() {
final Shell sh = new Shell();
if (isRPM()) {
sh.runIgnoreExitCode("rpm", "--quiet", "-e", "elasticsearch", "elasticsearch-oss");
}
if (isYUM()) {
sh.runIgnoreExitCode("yum", "remove", "-y", "elasticsearch", "elasticsearch-oss");
}
if (isDPKG()) {
sh.runIgnoreExitCode("dpkg", "--purge", "elasticsearch", "elasticsearch-oss");
}
if (isAptGet()) {
sh.runIgnoreExitCode("apt-get", "--quiet", "--yes", "purge", "elasticsearch", "elasticsearch-oss");
}
}
}

View File

@ -0,0 +1,76 @@
/*
* 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;
public enum Distribution {
OSS_TAR(Packaging.TAR, Flavor.OSS),
OSS_ZIP(Packaging.ZIP, Flavor.OSS),
OSS_DEB(Packaging.DEB, Flavor.OSS),
OSS_RPM(Packaging.RPM, Flavor.OSS),
DEFAULT_TAR(Packaging.TAR, Flavor.DEFAULT),
DEFAULT_ZIP(Packaging.ZIP, Flavor.DEFAULT),
DEFAULT_DEB(Packaging.DEB, Flavor.DEFAULT),
DEFAULT_RPM(Packaging.RPM, Flavor.DEFAULT);
public final Packaging packaging;
public final Flavor flavor;
Distribution(Packaging packaging, Flavor flavor) {
this.packaging = packaging;
this.flavor = flavor;
}
public String filename(String version) {
return flavor.name + "-" + version + packaging.extension;
}
public enum Packaging {
TAR(".tar.gz", Platforms.LINUX),
ZIP(".zip", true),
DEB(".deb", Platforms.isDPKG()),
RPM(".rpm", Platforms.isRPM());
/** The extension of this distribution's file */
public final String extension;
/** Whether the distribution is intended for use on the platform the current JVM is running on */
public final boolean compatible;
Packaging(String extension, boolean compatible) {
this.extension = extension;
this.compatible = compatible;
}
}
public enum Flavor {
OSS("elasticsearch-oss"),
DEFAULT("elasticsearch");
public final String name;
Flavor(String name) {
this.name = name;
}
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.packaging.util.FileUtils.getBasicFileAttributes;
import static org.elasticsearch.packaging.util.FileUtils.getFileOwner;
import static org.elasticsearch.packaging.util.FileUtils.getPosixFileAttributes;
import static java.nio.file.attribute.PosixFilePermissions.fromString;
/**
* Asserts that a file at a path matches its status as Directory/File, and its owner. If on a posix system, also matches the permission
* set is what we expect.
*
* This class saves information about its failed matches in instance variables and so instances should not be reused
*/
public class FileMatcher extends TypeSafeMatcher<Path> {
public enum Fileness { File, Directory }
public static final Set<PosixFilePermission> p755 = fromString("rwxr-xr-x");
public static final Set<PosixFilePermission> p660 = fromString("rw-rw----");
public static final Set<PosixFilePermission> p644 = fromString("rw-r--r--");
private final Fileness fileness;
private final String owner;
private final String group;
private final Set<PosixFilePermission> posixPermissions;
private String mismatch;
public FileMatcher(Fileness fileness, String owner, String group, Set<PosixFilePermission> posixPermissions) {
this.fileness = Objects.requireNonNull(fileness);
this.owner = Objects.requireNonNull(owner);
this.group = group;
this.posixPermissions = posixPermissions;
}
@Override
protected boolean matchesSafely(Path path) {
if (Files.exists(path) == false) {
mismatch = "Does not exist";
return false;
}
if (Platforms.WINDOWS) {
final BasicFileAttributes attributes = getBasicFileAttributes(path);
final String attributeViewOwner = getFileOwner(path);
if (fileness.equals(Fileness.Directory) != attributes.isDirectory()) {
mismatch = "Is " + (attributes.isDirectory() ? "a directory" : "a file");
return false;
}
if (attributeViewOwner.contains(owner) == false) {
mismatch = "Owned by " + attributeViewOwner;
return false;
}
} else {
final PosixFileAttributes attributes = getPosixFileAttributes(path);
if (fileness.equals(Fileness.Directory) != attributes.isDirectory()) {
mismatch = "Is " + (attributes.isDirectory() ? "a directory" : "a file");
return false;
}
if (owner.equals(attributes.owner().getName()) == false) {
mismatch = "Owned by " + attributes.owner().getName();
return false;
}
if (group != null && group.equals(attributes.group().getName()) == false) {
mismatch = "Owned by group " + attributes.group().getName();
return false;
}
if (posixPermissions != null && posixPermissions.equals(attributes.permissions()) == false) {
mismatch = "Has permissions " + attributes.permissions();
return false;
}
}
return true;
}
@Override
public void describeMismatchSafely(Path path, Description description) {
description.appendText("path ").appendValue(path);
if (mismatch != null) {
description.appendText(mismatch);
}
}
@Override
public void describeTo(Description description) {
description.appendValue("file/directory: ").appendValue(fileness)
.appendText(" with owner ").appendValue(owner)
.appendText(" with group ").appendValue(group)
.appendText(" with posix permissions ").appendValueList("[", ",", "]", posixPermissions);
}
public static FileMatcher file(Fileness fileness, String owner) {
return file(fileness, owner, null, null);
}
public static FileMatcher file(Fileness fileness, String owner, String group, Set<PosixFilePermission> permissions) {
return new FileMatcher(fileness, owner, group, permissions);
}
}

View File

@ -0,0 +1,134 @@
/*
* 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.elasticsearch.core.internal.io.IOUtils;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileOwnerAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.text.IsEmptyString.isEmptyOrNullString;
/**
* Wrappers and convenience methods for common filesystem operations
*/
public class FileUtils {
public static List<Path> lsGlob(Path directory, String glob) {
List<Path> paths = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, glob)) {
for (Path path : stream) {
paths.add(path);
}
return paths;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void rm(Path... paths) {
try {
IOUtils.rm(paths);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Path mv(Path source, Path target) {
try {
return Files.move(source, target);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String slurp(Path file) {
try {
return String.join("\n", Files.readAllLines(file));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Gets the owner of a file in a way that should be supported by all filesystems that have a concept of file owner
*/
public static String getFileOwner(Path path) {
try {
FileOwnerAttributeView view = Files.getFileAttributeView(path, FileOwnerAttributeView.class);
return view.getOwner().getName();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Gets attributes that are supported by all filesystems
*/
public static BasicFileAttributes getBasicFileAttributes(Path path) {
try {
return Files.readAttributes(path, BasicFileAttributes.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Gets attributes that are supported by posix filesystems
*/
public static PosixFileAttributes getPosixFileAttributes(Path path) {
try {
return Files.readAttributes(path, PosixFileAttributes.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// vagrant creates /tmp for us in windows so we use that to avoid long paths
public static Path getTempDir() {
return Paths.get("/tmp");
}
public static Path getDefaultArchiveInstallPath() {
return getTempDir().resolve("elasticsearch");
}
public static String getCurrentVersion() {
return slurp(getPackagingArchivesDir().resolve("version"));
}
public static Path getPackagingArchivesDir() {
String fromEnv = System.getenv("PACKAGING_ARCHIVES");
assertThat(fromEnv, not(isEmptyOrNullString()));
return Paths.get(fromEnv);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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 java.nio.file.Path;
/**
* Represents an installation of Elasticsearch
*/
public class Installation {
public final Path home;
public final Path config;
public final Path data;
public final Path logs;
public final Path plugins;
public final Path modules;
public final Path scripts;
public Installation(Path home, Path config, Path data, Path logs, Path plugins, Path modules, Path scripts) {
this.home = home;
this.config = config;
this.data = data;
this.logs = logs;
this.plugins = plugins;
this.modules = modules;
this.scripts = scripts;
}
public Installation(Path home) {
this(
home,
home.resolve("config"),
home.resolve("data"),
home.resolve("logs"),
home.resolve("plugins"),
home.resolve("modules"),
home.resolve("scripts")
);
}
}

View File

@ -0,0 +1,68 @@
/*
* 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;
public class Platforms {
public static final String OS_NAME = System.getProperty("os.name");
public static final boolean LINUX = OS_NAME.startsWith("Linux");
public static final boolean WINDOWS = OS_NAME.startsWith("Windows");
public static boolean isDPKG() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "dpkg").isSuccess();
}
public static boolean isAptGet() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "apt-get").isSuccess();
}
public static boolean isRPM() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "rpm").isSuccess();
}
public static boolean isYUM() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "yum").isSuccess();
}
public static boolean isSystemd() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "systemctl").isSuccess();
}
public static boolean isSysVInit() {
if (WINDOWS) {
return false;
}
return new Shell().runIgnoreExitCode("which", "service").isSuccess();
}
}

View File

@ -0,0 +1,193 @@
/*
* 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.elasticsearch.common.SuppressForbidden;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import static java.util.Collections.emptyMap;
/**
* Wrapper to run shell commands and collect their outputs in a less verbose way
*/
public class Shell {
final Map<String, String> env;
final Path workingDirectory;
public Shell() {
this(emptyMap(), null);
}
public Shell(Map<String, String> env) {
this(env, null);
}
public Shell(Path workingDirectory) {
this(emptyMap(), workingDirectory);
}
public Shell(Map<String, String> env, Path workingDirectory) {
this.env = new HashMap<>(env);
this.workingDirectory = workingDirectory;
}
public Result run(String... command) {
Result result = runIgnoreExitCode(command);
if (result.isSuccess() == false) {
throw new RuntimeException("Command was not successful: [" + String.join(" ", command) + "] result: " + result.toString());
}
return result;
}
public Result runIgnoreExitCode(String... command) {
ProcessBuilder builder = new ProcessBuilder();
builder.command(command);
if (workingDirectory != null) {
setWorkingDirectory(builder, workingDirectory);
}
if (env != null && env.isEmpty() == false) {
for (Map.Entry<String, String> entry : env.entrySet()) {
builder.environment().put(entry.getKey(), entry.getValue());
}
}
try {
Process process = builder.start();
StringBuilder stdout = new StringBuilder();
StringBuilder stderr = new StringBuilder();
Thread stdoutThread = new Thread(new StreamCollector(process.getInputStream(), stdout));
Thread stderrThread = new Thread(new StreamCollector(process.getErrorStream(), stderr));
stdoutThread.start();
stderrThread.start();
stdoutThread.join();
stderrThread.join();
int exitCode = process.waitFor();
return new Result(exitCode, stdout.toString(), stderr.toString());
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@SuppressForbidden(reason = "ProcessBuilder expects java.io.File")
private static void setWorkingDirectory(ProcessBuilder builder, Path path) {
builder.directory(path.toFile());
}
public String toString() {
return new StringBuilder()
.append("<")
.append(this.getClass().getName())
.append(" ")
.append("env = [")
.append(env)
.append("]")
.append("workingDirectory = [")
.append(workingDirectory)
.append("]")
.append(">")
.toString();
}
public static class Result {
public final int exitCode;
public final String stdout;
public final String stderr;
public Result(int exitCode, String stdout, String stderr) {
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
}
public boolean isSuccess() {
return exitCode == 0;
}
public String toString() {
return new StringBuilder()
.append("<")
.append(this.getClass().getName())
.append(" ")
.append("exitCode = [")
.append(exitCode)
.append("]")
.append(" ")
.append("stdout = [")
.append(stdout)
.append("]")
.append(" ")
.append("stderr = [")
.append(stderr)
.append("]")
.append(">")
.toString();
}
}
private static class StreamCollector implements Runnable {
private final InputStream input;
private final Appendable appendable;
StreamCollector(InputStream input, Appendable appendable) {
this.input = Objects.requireNonNull(input);
this.appendable = Objects.requireNonNull(appendable);
}
public void run() {
try {
BufferedReader reader = new BufferedReader(reader(input));
String line;
while ((line = reader.readLine()) != null) {
appendable.append(line);
appendable.append("\n");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@SuppressForbidden(reason = "the system's default character set is a best guess of what subprocesses will use")
private static InputStreamReader reader(InputStream inputStream) {
return new InputStreamReader(inputStream);
}
}
}