Migrate setup passwords packaging test from bats (#49337)

This commit moves the packaging tests for elasticsearch-setup-passwords
to java from bats. The change also enables future tests to enable
security in Elasticsearch and automatically have waitForElasticsearch
work correctly, at least to the same extent it worked in bats, by
waiting on the ES port instead of health check.

relates #46005
This commit is contained in:
Ryan Ernst 2019-11-27 16:01:26 -08:00 committed by Ryan Ernst
parent 9c6a5a09af
commit 7802b60a5a
15 changed files with 365 additions and 285 deletions

View File

@ -1 +0,0 @@
bootstrap_password.bash

View File

@ -1 +0,0 @@
bootstrap_password.bash

View File

@ -1 +0,0 @@
setup_passwords.bash

View File

@ -1 +0,0 @@
setup_passwords.bash

View File

@ -1,170 +0,0 @@
#!/usr/bin/env bats
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
load $BATS_UTILS/utils.bash
load $BATS_UTILS/plugins.bash
load $BATS_UTILS/xpack.bash
setup() {
if [ $BATS_TEST_NUMBER == 1 ]; then
export PACKAGE_NAME="elasticsearch"
clean_before_test
install
set_debug_logging
generate_trial_license
verify_xpack_installation
fi
}
if [[ "$BATS_TEST_FILENAME" =~ 20_tar_bootstrap_password.bats$ ]]; then
load $BATS_UTILS/tar.bash
GROUP='TAR BOOTSTRAP PASSWORD'
install() {
install_archive
verify_archive_installation
}
export ESHOME=/tmp/elasticsearch
export_elasticsearch_paths
export ESPLUGIN_COMMAND_USER=elasticsearch
else
load $BATS_UTILS/packages.bash
if is_rpm; then
GROUP='RPM BOOTSTRAP PASSWORD'
elif is_dpkg; then
GROUP='DEB BOOTSTRAP PASSWORD'
fi
export_elasticsearch_paths
export ESPLUGIN_COMMAND_USER=root
install() {
install_package
verify_package_installation
}
fi
@test "[$GROUP] add bootstrap.password setting" {
if [[ -f /tmp/bootstrap.password ]]; then
sudo rm -f /tmp/bootstrap.password
fi
run sudo -E -u $ESPLUGIN_COMMAND_USER bash <<"NEW_PASS"
if [[ ! -f $ESCONFIG/elasticsearch.keystore ]]; then
$ESHOME/bin/elasticsearch-keystore create
fi
cat /dev/urandom | tr -dc "[a-zA-Z0-9]" | fold -w 20 | head -n 1 > /tmp/bootstrap.password
cat /tmp/bootstrap.password | $ESHOME/bin/elasticsearch-keystore add --stdin bootstrap.password
NEW_PASS
[ "$status" -eq 0 ] || {
echo "Expected elasticsearch-keystore tool exit code to be zero but got [$status]"
echo "$output"
false
}
assert_file_exist "/tmp/bootstrap.password"
}
@test "[$GROUP] test bootstrap.password is in setting list" {
run sudo -E -u $ESPLUGIN_COMMAND_USER bash <<"NODE_SETTINGS"
cat >> $ESCONFIG/elasticsearch.yml <<- EOF
network.host: 127.0.0.1
http.port: 9200
EOF
NODE_SETTINGS
run_elasticsearch_service 0
wait_for_xpack 127.0.0.1 9200
sudo -E -u $ESPLUGIN_COMMAND_USER "$ESHOME/bin/elasticsearch-keystore" list | grep "bootstrap.password"
password=$(cat /tmp/bootstrap.password)
clusterHealth=$(sudo curl -u "elastic:$password" -H "Content-Type: application/json" \
-XGET "http://127.0.0.1:9200/_cluster/health?wait_for_status=green&timeout=180s")
echo "$clusterHealth" | grep '"status":"green"' || {
echo "Expected cluster health to be green but got:"
echo "$clusterHealth"
false
}
}
@test "[$GROUP] test auto generated passwords with modified bootstrap.password" {
if [[ -f /tmp/setup-passwords-output-with-bootstrap ]]; then
sudo rm -f /tmp/setup-passwords-output-with-bootstrap
fi
run sudo -E -u $ESPLUGIN_COMMAND_USER bash <<"SETUP_OK"
echo 'y' | $ESHOME/bin/elasticsearch-setup-passwords auto
SETUP_OK
echo "$output" > /tmp/setup-passwords-output-with-bootstrap
[ "$status" -eq 0 ] || {
echo "Expected x-pack elasticsearch-setup-passwords tool exit code to be zero but got [$status]"
cat /tmp/setup-passwords-output-with-bootstrap
debug_collect_logs
false
}
curl -s -XGET 'http://127.0.0.1:9200' | grep "missing authentication credentials for REST"
# Disable bash history expansion because passwords can contain "!"
set +H
users=( elastic kibana logstash_system )
for user in "${users[@]}"; do
grep "Changed password for user $user" /tmp/setup-passwords-output-with-bootstrap || {
echo "Expected x-pack elasticsearch-setup-passwords tool to change password for user [$user]:"
cat /tmp/setup-passwords-output-with-bootstrap
false
}
password=$(grep "PASSWORD $user = " /tmp/setup-passwords-output-with-bootstrap | sed "s/PASSWORD $user = //")
curl -u "$user:$password" -XGET 'http://127.0.0.1:9200' | grep "You Know, for Search"
basic=$(echo -n "$user:$password" | base64)
curl -H "Authorization: Basic $basic" -XGET 'http://127.0.0.1:9200' | grep "You Know, for Search"
done
set -H
}
@test "[$GROUP] test elasticsearch-sql-cli" {
password=$(grep "PASSWORD elastic = " /tmp/setup-passwords-output-with-bootstrap | sed "s/PASSWORD elastic = //")
curl -s -u "elastic:$password" -H "Content-Type: application/json" -XPUT 'localhost:9200/library/book/1?refresh&pretty' -d'{
"name": "Ender'"'"'s Game",
"author": "Orson Scott Card",
"release_date": "1985-06-01",
"page_count": 324
}'
password=$(grep "PASSWORD elastic = " /tmp/setup-passwords-output-with-bootstrap | sed "s/PASSWORD elastic = //")
run $ESHOME/bin/elasticsearch-sql-cli --debug "http://elastic@127.0.0.1:9200" <<SQL
$password
SELECT * FROM library;
SQL
[ "$status" -eq 0 ] || {
echo "SQL cli failed:\n$output"
false
}
[[ "$output" == *"Card"* ]] || {
echo "Failed to find author [Card] in library:$output"
false
}
}
@test "[$GROUP] test elasticsearch-sql-cli when user refuses password" {
# Run with empty stdin
run $ESHOME/bin/elasticsearch-sql-cli --debug "http://elastic@127.0.0.1:9200" <<SQL
SQL
[ "$status" -eq 77 ] || { #NOPERM
echo "SQL cli failed:\n$output"
false
}
[[ "$output" == *"password required"* ]] || {
echo "Failed to find author [password required] in error:$output"
false
}
}
@test "[$GROUP] stop Elasticsearch" {
stop_elasticsearch_service
}

View File

@ -1,87 +0,0 @@
#!/usr/bin/env bats
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
load $BATS_UTILS/utils.bash
load $BATS_UTILS/plugins.bash
load $BATS_UTILS/xpack.bash
setup() {
if [ $BATS_TEST_NUMBER == 1 ]; then
export PACKAGE_NAME="elasticsearch"
clean_before_test
install
set_debug_logging
generate_trial_license
verify_xpack_installation
fi
}
if [[ "$BATS_TEST_FILENAME" =~ 30_tar_setup_passwords.bats$ ]]; then
load $BATS_UTILS/tar.bash
GROUP='TAR SETUP PASSWORD'
install() {
install_archive
verify_archive_installation
}
export ESHOME=/tmp/elasticsearch
export_elasticsearch_paths
export ESPLUGIN_COMMAND_USER=elasticsearch
else
load $BATS_UTILS/packages.bash
if is_rpm; then
GROUP='RPM SETUP PASSWORD'
elif is_dpkg; then
GROUP='DEB SETUP PASSWORD'
fi
export_elasticsearch_paths
export ESPLUGIN_COMMAND_USER=root
install() {
install_package
verify_package_installation
}
fi
@test "[$GROUP] test auto generated passwords" {
run_elasticsearch_service 0
wait_for_xpack
run sudo -E -u $ESPLUGIN_COMMAND_USER bash <<"SETUP_AUTO"
echo 'y' | $ESHOME/bin/elasticsearch-setup-passwords auto
SETUP_AUTO
echo "$output" > /tmp/setup-passwords-output
[ "$status" -eq 0 ] || {
echo "Expected x-pack elasticsearch-setup-passwords tool exit code to be zero but got $status"
cat /tmp/setup-passwords-output
debug_collect_logs
false
}
curl -s -XGET localhost:9200 | grep "missing authentication credentials for REST"
# Disable bash history expansion because passwords can contain "!"
set +H
users=( elastic kibana logstash_system )
for user in "${users[@]}"; do
grep "Changed password for user $user" /tmp/setup-passwords-output || {
echo "Expected x-pack elasticsearch-setup-passwords tool to change password for user [$user]:"
cat /tmp/setup-passwords-output
false
}
password=$(grep "PASSWORD $user = " /tmp/setup-passwords-output | sed "s/PASSWORD $user = //")
curl -u "$user:$password" -XGET localhost:9200 | grep "You Know, for Search"
basic=$(echo -n "$user:$password" | base64)
curl -H "Authorization: Basic $basic" -XGET localhost:9200 | grep "You Know, for Search"
done
set -H
stop_elasticsearch_service
}

View File

@ -69,7 +69,7 @@ public class ArchiveTests extends PackagingTestCase {
public void test20PluginsListWithNoPlugins() throws Exception {
final Installation.Executables bin = installation.executables();
final Result r = sh.run(bin.elasticsearchPlugin + " list");
final Result r = bin.elasticsearchPlugin.run(sh, "list");
assertThat(r.stdout, isEmptyString());
}

View File

@ -26,8 +26,12 @@ import com.carrotsearch.randomizedtesting.annotations.TestMethodProviders;
import com.carrotsearch.randomizedtesting.annotations.Timeout;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.packaging.util.Archives;
import org.elasticsearch.packaging.util.Distribution;
import org.elasticsearch.packaging.util.Docker;
import org.elasticsearch.packaging.util.FileUtils;
import org.elasticsearch.packaging.util.Installation;
import org.elasticsearch.packaging.util.Packages;
import org.elasticsearch.packaging.util.Platforms;
import org.elasticsearch.packaging.util.Shell;
import org.junit.Assert;
@ -40,6 +44,7 @@ import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import java.nio.file.Files;
import java.nio.file.Paths;
import static org.elasticsearch.packaging.util.Cleanup.cleanEverything;
@ -119,7 +124,78 @@ public abstract class PackagingTestCase extends Assert {
return distribution;
}
protected Shell newShell() throws Exception {
protected static void install() throws Exception {
switch (distribution.packaging) {
case TAR:
case ZIP:
installation = Archives.installArchive(distribution);
Archives.verifyArchiveInstallation(installation, distribution);
break;
case DEB:
case RPM:
installation = Packages.installPackage(distribution);
Packages.verifyPackageInstallation(installation, distribution, newShell());
break;
case DOCKER:
installation = Docker.runContainer(distribution);
Docker.verifyContainerInstallation(installation, distribution);
}
}
/**
* Starts and stops elasticsearch, and performs assertions while it is running.
*/
protected void assertWhileRunning(Platforms.PlatformAction assertions) throws Exception {
try {
switch (distribution.packaging) {
case TAR:
case ZIP:
Archives.runElasticsearch(installation, sh);
break;
case DEB:
case RPM:
Packages.startElasticsearch(sh, installation);
break;
case DOCKER:
// nothing, "installing" docker image is running it
}
} catch (Exception e ){
if (Files.exists(installation.home.resolve("elasticsearch.pid"))) {
String pid = FileUtils.slurp(installation.home.resolve("elasticsearch.pid")).trim();
logger.info("Dumping jstack of elasticsearch processb ({}) that failed to start", pid);
sh.runIgnoreExitCode("jstack " + pid);
}
if (Files.exists(installation.logs.resolve("elasticsearch.log"))) {
logger.warn("Elasticsearch log:\n" +
FileUtils.slurpAllLogs(installation.logs, "elasticsearch.log", "*.log.gz"));
}
throw e;
}
try {
assertions.run();
} catch (Exception e) {
logger.warn("Elasticsearch log:\n" +
FileUtils.slurpAllLogs(installation.logs, "elasticsearch.log", "*.log.gz"));
throw e;
}
switch (distribution.packaging) {
case TAR:
case ZIP:
Archives.stopElasticsearch(installation);
break;
case DEB:
case RPM:
Packages.stopElasticsearch(sh);
break;
case DOCKER:
// nothing, removing container is handled externally
}
}
protected static Shell newShell() throws Exception {
Shell sh = new Shell();
if (distribution().hasJdk == false) {
Platforms.onLinux(() -> {

View File

@ -0,0 +1,141 @@
/*
* 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.apache.http.client.fluent.Request;
import org.elasticsearch.packaging.util.Distribution;
import org.elasticsearch.packaging.util.FileUtils;
import org.elasticsearch.packaging.util.ServerUtils;
import org.elasticsearch.packaging.util.Shell;
import org.junit.Before;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static org.elasticsearch.packaging.util.FileUtils.append;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.collection.IsMapContaining.hasKey;
import static org.junit.Assume.assumeTrue;
public class PasswordToolsTests extends PackagingTestCase {
private static final Pattern USERPASS_REGEX = Pattern.compile("PASSWORD (\\w+) = ([^\\s]+)");
private static final String BOOTSTRAP_PASSWORD = "myS3curepass";
@Before
public void filterDistros() {
assumeTrue("only default distro", distribution.flavor == Distribution.Flavor.DEFAULT);
assumeTrue("no docker", distribution.packaging != Distribution.Packaging.DOCKER);
}
public void test010Install() throws Exception {
install();
append(installation.config("elasticsearch.yml"),
"xpack.license.self_generated.type: trial\n" +
"xpack.security.enabled: true");
}
public void test20GeneratePasswords() throws Exception {
assertWhileRunning(() -> {
Shell.Result result = installation.executables().elasticsearchSetupPasswords.run(sh, "auto --batch", null);
Map<String, String> userpasses = parseUsersAndPasswords(result.stdout);
for (Map.Entry<String, String> userpass : userpasses.entrySet()) {
String response = ServerUtils.makeRequest(Request.Get("http://localhost:9200"), userpass.getKey(), userpass.getValue());
assertThat(response, containsString("You Know, for Search"));
}
});
}
public void test30AddBootstrapPassword() throws Exception {
try (Stream<Path> dataFiles = Files.list(installation.data)) {
// delete each dir under data, not data itself
dataFiles.forEach(file -> {
if (distribution.platform != Distribution.Platform.WINDOWS) {
FileUtils.rm(file);
return;
}
// HACK: windows asynchronously releases file locks after processes exit. Unfortunately there is no clear way to wait on
// those locks being released. We might be able to use `openfiles /query`, but that requires modifying global settings
// in our windows images with `openfiles /local on` (which requires a restart, thus needs to be baked into the images).
// The following sleep allows time for windows to release the data file locks from Elasticsearch which was stopped in the
// previous test.
int retries = 30;
Exception failure = null;
while (retries-- > 0) {
try {
FileUtils.rm(file);
return;
} catch (Exception e) {
if (failure == null) {
failure = e;
} else {
failure.addSuppressed(e);
}
try {
Thread.sleep(1000);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return;
}
}
}
throw new RuntimeException("failed to delete " + file, failure);
});
}
installation.executables().elasticsearchKeystore.run(sh, "add --stdin bootstrap.password", BOOTSTRAP_PASSWORD);
assertWhileRunning(() -> {
String response = ServerUtils.makeRequest(
Request.Get("http://localhost:9200/_cluster/health?wait_for_status=green&timeout=180s"),
"elastic", BOOTSTRAP_PASSWORD);
assertThat(response, containsString("\"status\":\"green\""));
});
}
public void test40GeneratePasswordsBootstrapAlreadySet() throws Exception {
assertWhileRunning(() -> {
Shell.Result result = installation.executables().elasticsearchSetupPasswords.run(sh, "auto --batch", null);
Map<String, String> userpasses = parseUsersAndPasswords(result.stdout);
assertThat(userpasses, hasKey("elastic"));
for (Map.Entry<String, String> userpass : userpasses.entrySet()) {
String response = ServerUtils.makeRequest(Request.Get("http://localhost:9200"), userpass.getKey(), userpass.getValue());
assertThat(response, containsString("You Know, for Search"));
}
});
}
private Map<String, String> parseUsersAndPasswords(String output) {
Matcher matcher = USERPASS_REGEX.matcher(output);
assertNotNull(matcher);
Map<String, String> userpases = new HashMap<>();
while (matcher.find()) {
userpases.put(matcher.group(1), matcher.group(2));
}
return userpases;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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;
import org.elasticsearch.packaging.util.Shell;
import org.junit.Before;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assume.assumeTrue;
public class SqlCliTests extends PackagingTestCase {
@Before
public void filterDistros() {
assumeTrue("only default distro", distribution.flavor == Distribution.Flavor.DEFAULT);
assumeTrue("no docker", distribution.packaging != Distribution.Packaging.DOCKER);
}
public void test010Install() throws Exception {
install();
}
public void test020Help() throws Exception {
Shell.Result result = installation.executables().elasticsearchSqlCli.run(sh, "--help");
assertThat(result.stdout, containsString("Elasticsearch SQL CLI"));
}
}

View File

@ -111,7 +111,7 @@ public class Archives {
sh.chown(fullInstallPath);
return Installation.ofArchive(fullInstallPath);
return Installation.ofArchive(distribution, fullInstallPath);
}
private static void setupArchiveUsersLinux(Path installPath) {

View File

@ -99,7 +99,7 @@ public class Docker {
waitForElasticsearchToStart();
return Installation.ofContainer();
return Installation.ofContainer(distribution);
}
/**

View File

@ -27,6 +27,12 @@ import java.nio.file.Paths;
*/
public class Installation {
// in the future we'll run as a role user on Windows
public static final String ARCHIVE_OWNER = Platforms.WINDOWS
? System.getenv("username")
: "elasticsearch";
public final Distribution distribution;
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
@ -39,7 +45,9 @@ public class Installation {
public final Path pidDir;
public final Path envFile;
public Installation(Path home, Path config, Path data, Path logs, Path plugins, Path modules, Path pidDir, Path envFile) {
private Installation(Distribution distribution, Path home, Path config, Path data, Path logs,
Path plugins, Path modules, Path pidDir, Path envFile) {
this.distribution = distribution;
this.home = home;
this.bin = home.resolve("bin");
this.lib = home.resolve("lib");
@ -53,8 +61,9 @@ public class Installation {
this.envFile = envFile;
}
public static Installation ofArchive(Path home) {
public static Installation ofArchive(Distribution distribution, Path home) {
return new Installation(
distribution,
home,
home.resolve("config"),
home.resolve("data"),
@ -66,13 +75,14 @@ public class Installation {
);
}
public static Installation ofPackage(Distribution.Packaging packaging) {
public static Installation ofPackage(Distribution distribution) {
final Path envFile = (packaging == Distribution.Packaging.RPM)
final Path envFile = (distribution.packaging == Distribution.Packaging.RPM)
? Paths.get("/etc/sysconfig/elasticsearch")
: Paths.get("/etc/default/elasticsearch");
return new Installation(
distribution,
Paths.get("/usr/share/elasticsearch"),
Paths.get("/etc/elasticsearch"),
Paths.get("/var/lib/elasticsearch"),
@ -84,9 +94,10 @@ public class Installation {
);
}
public static Installation ofContainer() {
public static Installation ofContainer(Distribution distribution) {
String root = "/usr/share/elasticsearch";
return new Installation(
distribution,
Paths.get(root),
Paths.get(root + "/config"),
Paths.get(root + "/data"),
@ -110,23 +121,48 @@ public class Installation {
return new Executables();
}
public class Executables {
public class Executable {
public final Path path;
public final Path elasticsearch = platformExecutable("elasticsearch");
public final Path elasticsearchPlugin = platformExecutable("elasticsearch-plugin");
public final Path elasticsearchKeystore = platformExecutable("elasticsearch-keystore");
public final Path elasticsearchCertutil = platformExecutable("elasticsearch-certutil");
public final Path elasticsearchShard = platformExecutable("elasticsearch-shard");
public final Path elasticsearchNode = platformExecutable("elasticsearch-node");
public final Path elasticsearchSetupPasswords = platformExecutable("elasticsearch-setup-passwords");
public final Path elasticsearchSyskeygen = platformExecutable("elasticsearch-syskeygen");
public final Path elasticsearchUsers = platformExecutable("elasticsearch-users");
private Path platformExecutable(String name) {
private Executable(String name) {
final String platformExecutableName = Platforms.WINDOWS
? name + ".bat"
: name;
return bin(platformExecutableName);
this.path = bin(platformExecutableName);
}
@Override
public String toString() {
return path.toString();
}
public Shell.Result run(Shell sh, String args) {
return run(sh, args, null);
}
public Shell.Result run(Shell sh, String args, String input) {
String command = path + " " + args;
if (distribution.isArchive() && distribution.platform != Distribution.Platform.WINDOWS) {
command = "sudo -E -u " + ARCHIVE_OWNER + " " + command;
}
if (input != null) {
command = "echo \"" + input + "\" | " + command;
}
return sh.run(command);
}
}
public class Executables {
public final Executable elasticsearch = new Executable("elasticsearch");
public final Executable elasticsearchPlugin = new Executable("elasticsearch-plugin");
public final Executable elasticsearchKeystore = new Executable("elasticsearch-keystore");
public final Executable elasticsearchCertutil = new Executable("elasticsearch-certutil");
public final Executable elasticsearchShard = new Executable("elasticsearch-shard");
public final Executable elasticsearchNode = new Executable("elasticsearch-node");
public final Executable elasticsearchSetupPasswords = new Executable("elasticsearch-setup-passwords");
public final Executable elasticsearchSqlCli= new Executable("elasticsearch-sql-cli");
public final Executable elasticsearchSyskeygen = new Executable("elasticsearch-syskeygen");
public final Executable elasticsearchUsers = new Executable("elasticsearch-users");
}
}

View File

@ -105,7 +105,7 @@ public class Packages {
throw new RuntimeException("Installing distribution " + distribution + " failed: " + result);
}
Installation installation = Installation.ofPackage(distribution.packaging);
Installation installation = Installation.ofPackage(distribution);
if (distribution.hasJdk == false) {
Files.write(installation.envFile, ("JAVA_HOME=" + systemJavaHome + "\n").getBytes(StandardCharsets.UTF_8),

View File

@ -29,6 +29,11 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@ -39,13 +44,32 @@ public class ServerUtils {
private static final Logger logger = LogManager.getLogger(ServerUtils.class);
private static String SECURITY_ENABLED = "xpack.security.enabled: true";
// generous timeout as nested virtualization can be quite slow ...
private static final long waitTime = TimeUnit.MINUTES.toMillis(3);
private static final long timeoutLength = TimeUnit.SECONDS.toMillis(30);
private static final long requestInterval = TimeUnit.SECONDS.toMillis(5);
public static void waitForElasticsearch(Installation installation) throws IOException {
waitForElasticsearch("green", null, installation, null, null);
boolean securityEnabled = false;
// TODO: need a way to check if docker has security enabled, the yml config is not bind mounted so can't look from here
if (installation.distribution.packaging != Distribution.Packaging.DOCKER) {
Path configFilePath = installation.config("elasticsearch.yml");
// this is fragile, but currently doesn't deviate from a single line enablement and not worth the parsing effort
String configFile = Files.readString(configFilePath, StandardCharsets.UTF_8);
securityEnabled = configFile.contains(SECURITY_ENABLED);
}
if (securityEnabled) {
// with security enabled, we may or may not have setup a user/pass, so we use a more generic port being available check.
// this isn't as good as a health check, but long term all this waiting should go away when node startup does not
// make the http port available until the system is really ready to serve requests
waitForXpack();
} else {
waitForElasticsearch("green", null, installation, null, null);
}
}
/**
@ -68,6 +92,26 @@ public class ServerUtils {
return executor.execute(request).returnResponse();
}
// polls every second for Elasticsearch to be running on 9200
private static void waitForXpack() {
int retries = 60;
while (retries > 0) {
retries -= 1;
try (Socket s = new Socket(InetAddress.getLoopbackAddress(), 9200)) {
return;
} catch (IOException e) {
// ignore, only want to establish a connection
}
try {
Thread.sleep(1000);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return;
}
}
throw new RuntimeException("Elasticsearch (with x-pack) did not start");
}
public static void waitForElasticsearch(
String status,
String index,