Skip to content

Commit

Permalink
Replace most shell script logic with Java (#85758)
Browse files Browse the repository at this point in the history
Elasticsearch provides several command line tools, as well as the main script to start elasticsearch. While most of the logic is abstracted away for cli tools, the main elasticsearch script has hundreds of lines of platform specific shell code. That code is difficult to maintain because it uses many special shell features which then must also exist in other platforms (ie windows batch files). Additionally, the logic in these scripts are not easy to test, we must be on the actual platform and test with a full installation of Elasticsearch, which is relatively slow (compared to most in process tests).

This commit replaces logic of the main server script, as well as the windows service management script, with Java. The new entrypoints use the CliToolLauncher. The server cli figures out all the jvm options and such necessary, then launches the real server process. If run in the foreground, the launcher will stay alive for the lifetime of Elasticsearch; the streams are effectively inherited so all output from Elasticsearch still goes to the console. If daemonizing, the launcher waits around until Elasticsearch is "ready" (this means the Node startup completed), then detaches and exits.

Co-authored-by: William Brafford <[email protected]>
  • Loading branch information
rjernst and williamrandolph authored May 19, 2022
1 parent 096d7fe commit b9c504b
Show file tree
Hide file tree
Showing 58 changed files with 3,113 additions and 1,253 deletions.
7 changes: 5 additions & 2 deletions distribution/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
* Properties to expand when copying packaging files *
*****************************************************************************/
configurations {
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole'].each {
['libs', 'libsVersionChecker', 'libsCliLauncher', 'libsServerCli', 'libsWindowsServiceCli', 'libsPluginCli', 'libsKeystoreCli', 'libsSecurityCli', 'libsGeoIpCli', 'libsAnsiConsole'].each {
create(it) {
canBeConsumed = false
canBeResolved = true
Expand All @@ -253,6 +253,7 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
libsVersionChecker project(':distribution:tools:java-version-checker')
libsCliLauncher project(':distribution:tools:cli-launcher')
libsServerCli project(':distribution:tools:server-cli')
libsWindowsServiceCli project(':distribution:tools:windows-service-cli')
libsAnsiConsole project(':distribution:tools:ansi-console')
libsPluginCli project(':distribution:tools:plugin-cli')
libsKeystoreCli project(path: ':distribution:tools:keystore-cli')
Expand All @@ -278,6 +279,9 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
into('tools/server-cli') {
from(configurations.libsServerCli)
}
into('tools/windows-service-cli') {
from(configurations.libsWindowsServiceCli)
}
into('tools/geoip-cli') {
from(configurations.libsGeoIpCli)
}
Expand All @@ -295,7 +299,6 @@ configure(subprojects.findAll { ['archives', 'packages'].contains(it.name) }) {
}
}


modulesFiles = { platform ->
copySpec {
eachFile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ After=network-online.target

[Service]
Type=notify
# the elasticsearch process currently sends the notifications back to systemd
# and for some reason exec does not work (even though it is a child). We should change
# this notify access back to main (the default), see https://github.com/elastic/elasticsearch/issues/86475
NotifyAccess=all
RuntimeDirectory=elasticsearch
PrivateTmp=true
Environment=ES_HOME=/usr/share/elasticsearch
Expand Down
140 changes: 3 additions & 137 deletions distribution/src/bin/elasticsearch
Original file line number Diff line number Diff line change
@@ -1,139 +1,5 @@
#!/bin/bash

# CONTROLLING STARTUP:
#
# This script relies on a few environment variables to determine startup
# behavior, those variables are:
#
# ES_PATH_CONF -- Path to config directory
# ES_JAVA_OPTS -- External Java Opts on top of the defaults set
#
# Optionally, exact memory values can be set using the `ES_JAVA_OPTS`. Example
# values are "512m", and "10g".
#
# ES_JAVA_OPTS="-Xms8g -Xmx8g" ./bin/elasticsearch

source "`dirname "$0"`"/elasticsearch-env

CHECK_KEYSTORE=true
ATTEMPT_SECURITY_AUTO_CONFIG=true
DAEMONIZE=false
ENROLL_TO_CLUSTER=false
# Store original arg array as we will be shifting through it below
ARG_LIST=("$@")

while [ $# -gt 0 ]; do
if [[ $1 == "--enrollment-token" ]]; then
if [ $ENROLL_TO_CLUSTER = true ]; then
echo "Multiple --enrollment-token parameters are not allowed" 1>&2
exit 1
fi
ENROLL_TO_CLUSTER=true
ATTEMPT_SECURITY_AUTO_CONFIG=false
ENROLLMENT_TOKEN="$2"
shift
elif [[ $1 == "-h" || $1 == "--help" || $1 == "-V" || $1 == "--version" ]]; then
CHECK_KEYSTORE=false
ATTEMPT_SECURITY_AUTO_CONFIG=false
elif [[ $1 == "-d" || $1 == "--daemonize" ]]; then
DAEMONIZE=true
fi
if [[ $# -gt 0 ]]; then
shift
fi
done

if [ -z "$ES_TMPDIR" ]; then
ES_TMPDIR=`"$JAVA" -cp "$SERVER_CLI_CLASSPATH" org.elasticsearch.server.cli.TempDirectory`
fi

if [ -z "$LIBFFI_TMPDIR" ]; then
LIBFFI_TMPDIR="$ES_TMPDIR"
export LIBFFI_TMPDIR
fi

# get keystore password before setting java options to avoid
# conflicting GC configurations for the keystore tools
unset KEYSTORE_PASSWORD
KEYSTORE_PASSWORD=
if [[ $CHECK_KEYSTORE = true ]] \
&& bin/elasticsearch-keystore has-passwd --silent
then
if ! read -s -r -p "Elasticsearch keystore password: " KEYSTORE_PASSWORD ; then
echo "Failed to read keystore password on console" 1>&2
exit 1
fi
fi

if [[ $ENROLL_TO_CLUSTER = true ]]; then
CLI_NAME="auto-configure-node" \
CLI_LIBS="modules/x-pack-core,modules/x-pack-security,lib/tools/security-cli" \
bin/elasticsearch-cli "${ARG_LIST[@]}" <<<"$KEYSTORE_PASSWORD"
elif [[ $ATTEMPT_SECURITY_AUTO_CONFIG = true ]]; then
# It is possible that an auto-conf failure prevents the node from starting, but this is only the exceptional case (exit code 1).
# Most likely an auto-conf failure will leave the configuration untouched (exit codes 73, 78 and 80), optionally printing a message
# if the error is uncommon or unexpected, but it should otherwise let the node to start as usual.
# It is passed in all the command line options in order to read the node settings ones (-E), while the other parameters are ignored
# (a small caveat is that it also inspects the -v option in order to provide more information on how auto config went)
if CLI_NAME="auto-configure-node" \
CLI_LIBS="modules/x-pack-core,modules/x-pack-security,lib/tools/security-cli" \
bin/elasticsearch-cli "${ARG_LIST[@]}" <<<"$KEYSTORE_PASSWORD"; then
:
else
retval=$?
# these exit codes cover the cases where auto-conf cannot run but the node should NOT be prevented from starting as usual
# eg the node is restarted, is already configured in an incompatible way, or the file system permissions do not allow it
if [[ $retval -ne 80 ]] && [[ $retval -ne 73 ]] && [[ $retval -ne 78 ]]; then
exit $retval
fi
fi
fi

# The JVM options parser produces the final JVM options to start Elasticsearch.
# It does this by incorporating JVM options in the following way:
# - first, system JVM options are applied (these are hardcoded options in the
# parser)
# - second, JVM options are read from jvm.options and jvm.options.d/*.options
# - third, JVM options from ES_JAVA_OPTS are applied
# - fourth, ergonomic JVM options are applied
ES_JAVA_OPTS=`export ES_TMPDIR; "$JAVA" -cp "$SERVER_CLI_CLASSPATH" -Des.distribution.type="$ES_DISTRIBUTION_TYPE" org.elasticsearch.server.cli.JvmOptionsParser "$ES_PATH_CONF" "$ES_HOME/plugins"`

# Remove enrollment related parameters before passing the arg list to Elasticsearch
for i in "${!ARG_LIST[@]}"; do
if [[ ${ARG_LIST[i]} = "--enrollment-token" || ${ARG_LIST[i]} = "$ENROLLMENT_TOKEN" ]]; then
unset 'ARG_LIST[i]'
fi
done

# manual parsing to find out, if process should be detached
if [[ $DAEMONIZE = false ]]; then
exec \
"$JAVA" \
$ES_JAVA_OPTS \
-Des.path.home="$ES_HOME" \
-Des.path.conf="$ES_PATH_CONF" \
-Des.distribution.type="$ES_DISTRIBUTION_TYPE" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"${ARG_LIST[@]}" <<<"$KEYSTORE_PASSWORD"
else
exec \
"$JAVA" \
$ES_JAVA_OPTS \
-Des.path.home="$ES_HOME" \
-Des.path.conf="$ES_PATH_CONF" \
-Des.distribution.type="$ES_DISTRIBUTION_TYPE" \
-cp "$ES_CLASSPATH" \
org.elasticsearch.bootstrap.Elasticsearch \
"${ARG_LIST[@]}" \
<<<"$KEYSTORE_PASSWORD" &
retval=$?
pid=$!
[ $retval -eq 0 ] || exit $retval
if ! ps -p $pid > /dev/null ; then
exit 1
fi
exit 0
fi

exit $?
CLI_NAME=server
CLI_LIBS=lib/tools/server-cli
source "`dirname "$0"`"/elasticsearch-cli
4 changes: 0 additions & 4 deletions distribution/src/bin/elasticsearch-env
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ while [ "`basename "$ES_HOME"`" != "bin" ]; do
done
ES_HOME=`dirname "$ES_HOME"`

# now set the classpath
ES_CLASSPATH="$ES_HOME/lib/*"
SERVER_CLI_CLASSPATH="$ES_CLASSPATH:$ES_HOME/lib/tools/server-cli/*"

# now set the path to java
if [ ! -z "$ES_JAVA_HOME" ]; then
JAVA="$ES_JAVA_HOME/bin/java"
Expand Down
Loading

0 comments on commit b9c504b

Please sign in to comment.