diff --git a/qa/os/bats/default/20_tar_bootstrap_password.bats b/qa/os/bats/default/20_tar_bootstrap_password.bats deleted file mode 120000 index 58a968aa3e1..00000000000 --- a/qa/os/bats/default/20_tar_bootstrap_password.bats +++ /dev/null @@ -1 +0,0 @@ -bootstrap_password.bash \ No newline at end of file diff --git a/qa/os/bats/default/25_package_bootstrap_password.bats b/qa/os/bats/default/25_package_bootstrap_password.bats deleted file mode 120000 index 58a968aa3e1..00000000000 --- a/qa/os/bats/default/25_package_bootstrap_password.bats +++ /dev/null @@ -1 +0,0 @@ -bootstrap_password.bash \ No newline at end of file diff --git a/qa/os/bats/default/30_tar_setup_passwords.bats b/qa/os/bats/default/30_tar_setup_passwords.bats deleted file mode 120000 index 74d1204b3f9..00000000000 --- a/qa/os/bats/default/30_tar_setup_passwords.bats +++ /dev/null @@ -1 +0,0 @@ -setup_passwords.bash \ No newline at end of file diff --git a/qa/os/bats/default/35_package_setup_passwords.bats b/qa/os/bats/default/35_package_setup_passwords.bats deleted file mode 120000 index 74d1204b3f9..00000000000 --- a/qa/os/bats/default/35_package_setup_passwords.bats +++ /dev/null @@ -1 +0,0 @@ -setup_passwords.bash \ No newline at end of file diff --git a/qa/os/bats/default/bootstrap_password.bash b/qa/os/bats/default/bootstrap_password.bash deleted file mode 100644 index d5d8637f6fc..00000000000 --- a/qa/os/bats/default/bootstrap_password.bash +++ /dev/null @@ -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" < /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 -} diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveTests.java index 68b02f6311e..0f3a150ad12 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveTests.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveTests.java @@ -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()); } diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/PackagingTestCase.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/PackagingTestCase.java index 3efd1b36ddb..cb95408b2a5 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/test/PackagingTestCase.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/PackagingTestCase.java @@ -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(() -> { diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java new file mode 100644 index 00000000000..9082b19f0bd --- /dev/null +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/PasswordToolsTests.java @@ -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 userpasses = parseUsersAndPasswords(result.stdout); + for (Map.Entry 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 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 userpasses = parseUsersAndPasswords(result.stdout); + assertThat(userpasses, hasKey("elastic")); + for (Map.Entry 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 parseUsersAndPasswords(String output) { + Matcher matcher = USERPASS_REGEX.matcher(output); + assertNotNull(matcher); + Map userpases = new HashMap<>(); + while (matcher.find()) { + userpases.put(matcher.group(1), matcher.group(2)); + } + return userpases; + } +} diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/test/SqlCliTests.java b/qa/os/src/test/java/org/elasticsearch/packaging/test/SqlCliTests.java new file mode 100644 index 00000000000..62a00aab59d --- /dev/null +++ b/qa/os/src/test/java/org/elasticsearch/packaging/test/SqlCliTests.java @@ -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")); + } +} diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Archives.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Archives.java index 3ae49bc5109..fa5e6bea592 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Archives.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Archives.java @@ -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) { diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java index cc396c07970..544b0d32625 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Docker.java @@ -99,7 +99,7 @@ public class Docker { waitForElasticsearchToStart(); - return Installation.ofContainer(); + return Installation.ofContainer(distribution); } /** diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java index c5fdf0106df..1f3da0b1266 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Installation.java @@ -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"); + } } diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/Packages.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/Packages.java index 1e6540e7788..020ee7df489 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/Packages.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/Packages.java @@ -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), diff --git a/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java b/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java index 638308f7337..62f9cee137b 100644 --- a/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java +++ b/qa/os/src/test/java/org/elasticsearch/packaging/util/ServerUtils.java @@ -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,