/*
* 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.bootstrap;
import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import org.apache.lucene.util.Constants;
import org.apache.lucene.util.StringHelper;
import org.elasticsearch.Version;
import org.elasticsearch.common.PidFile;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.inject.CreationException;
import org.elasticsearch.common.lease.Releasables;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.logging.log4j.LogConfigurator;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.monitor.jvm.JvmInfo;
import org.elasticsearch.monitor.os.OsProbe;
import org.elasticsearch.monitor.process.ProcessProbe;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.node.internal.InternalSettingsPreparer;
/**
* Internal startup code.
*/
public final class Bootstrap {
private static volatile Bootstrap INSTANCE;
private volatile Node node;
private final CountDownLatch keepAliveLatch = new CountDownLatch(1);
private final Thread keepAliveThread;
/** creates a new instance */
Bootstrap() {
keepAliveThread = new Thread(new Runnable() {
@Override
public void run() {
try {
keepAliveLatch.await();
} catch (InterruptedException e) {
// bail out
}
}
}, "elasticsearch[keepAlive/" + Version.CURRENT + "]");
keepAliveThread.setDaemon(false);
// keep this thread alive (non daemon thread) until we shutdown
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
keepAliveLatch.countDown();
}
});
}
/** initialize native resources */
public static void initializeNatives(Path tmpFile, boolean mlockAll, boolean seccomp, boolean ctrlHandler) {
final ESLogger logger = Loggers.getLogger(Bootstrap.class);
// check if the user is running as root, and bail
if (Natives.definitelyRunningAsRoot()) {
if (Boolean.parseBoolean(System.getProperty("es.insecure.allow.root"))) {
logger.warn("running as ROOT user. this is a bad idea!");
} else {
throw new RuntimeException("don't run elasticsearch as root.");
}
}
// enable secure computing mode
if (seccomp) {
Natives.trySeccomp(tmpFile);
}
// mlockall if requested
if (mlockAll) {
if (Constants.WINDOWS) {
Natives.tryVirtualLock();
} else {
Natives.tryMlockall();
}
}
// listener for windows close event
if (ctrlHandler) {
Natives.addConsoleCtrlHandler(new ConsoleCtrlHandler() {
@Override
public boolean handle(int code) {
if (CTRL_CLOSE_EVENT == code) {
logger.info("running graceful exit on windows");
Bootstrap.stop();
return true;
}
return false;
}
});
}
// force remainder of JNA to be loaded (if available).
try {
JNAKernel32Library.getInstance();
} catch (Throwable ignored) {
// we've already logged this.
}
// init lucene random seed. it will use /dev/urandom where available:
StringHelper.randomId();
}
public static void initializeProbes() {
// Force probes to be loaded
ProcessProbe.getInstance();
OsProbe.getInstance();
}
private void setup(boolean addShutdownHook, Settings settings, Environment environment) throws Exception {
final ESLogger logger = Loggers.getLogger(Bootstrap.class);
final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
final Boolean mlockall = settings.getAsBoolean("bootstrap.mlockall", null);
if (mlockall != null) {
deprecationLogger.deprecated("setting [bootstrap.mlockall] is deprecated; use [bootstrap.memory_lock]");
}
final Boolean memoryLock = settings.getAsBoolean("bootstrap.memory_lock", null);
// both bootstrap.mlockall and bootstrap.memory_lock are set, refuse to start
if (mlockall != null && memoryLock != null) {
throw new IllegalArgumentException("both [bootstrap.mlockall] and [bootstrap.memory_lock] configured,"
+ " just use [bootstrap.memory_lock]");
}
initializeNatives(environment.tmpFile(),
memoryLock != null ? memoryLock : mlockall != null ? mlockall : false,
settings.getAsBoolean("bootstrap.seccomp", true),
settings.getAsBoolean("bootstrap.ctrlhandler", true));
// initialize probes before the security manager is installed
initializeProbes();
if (addShutdownHook) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
if (node != null) {
node.close();
}
}
});
}
// look for jar hell
JarHell.checkJarHell();
// install SM after natives, shutdown hooks, etc.
setupSecurity(settings, environment);
// We do not need to reload system properties here as we have already applied them in building the settings and
// reloading could cause multiple prompts to the user for values if a system property was specified with a prompt
// placeholder
Settings nodeSettings = Settings.settingsBuilder()
.put(settings)
.put(InternalSettingsPreparer.IGNORE_SYSTEM_PROPERTIES_SETTING, true)
.build();
NodeBuilder nodeBuilder = NodeBuilder.nodeBuilder().settings(nodeSettings);
node = nodeBuilder.build();
}
/**
* option for elasticsearch.yml etc to turn off our security manager completely,
* for example if you want to have your own configuration or just disable.
*/
// TODO: remove this: http://www.openbsd.org/papers/hackfest2015-pledge/mgp00005.jpg
static final String SECURITY_SETTING = "security.manager.enabled";
/**
* option for elasticsearch.yml to fully respect the system policy, including bad defaults
* from java.
*/
// TODO: remove this hack when insecure defaults are removed from java
static final String SECURITY_FILTER_BAD_DEFAULTS_SETTING = "security.manager.filter_bad_defaults";
public void setupSecurity(Settings settings, Environment environment) throws Exception {
if (settings.getAsBoolean(SECURITY_SETTING, true)) {
Security.configure(environment, settings.getAsBoolean(SECURITY_FILTER_BAD_DEFAULTS_SETTING, true));
}
}
private static Environment initialSettings(boolean foreground) {
Terminal terminal = foreground ? Terminal.DEFAULT : null;
return InternalSettingsPreparer.prepareEnvironment(EMPTY_SETTINGS, terminal);
}
private void start() {
node.start();
keepAliveThread.start();
}
static void stop() {
try {
Releasables.close(INSTANCE.node);
} finally {
INSTANCE.keepAliveLatch.countDown();
}
}
/**
* This method is invoked by {@link Elasticsearch#main(String[])}
* to startup elasticsearch.
*/
static void init(String[] args) throws Throwable {
// Set the system property before anything has a chance to trigger its use
System.setProperty("es.logger.prefix", "");
BootstrapCLIParser bootstrapCLIParser = new BootstrapCLIParser();
CliTool.ExitStatus status = bootstrapCLIParser.execute(args);
if (CliTool.ExitStatus.OK != status) {
exit(status.status());
}
INSTANCE = new Bootstrap();
boolean foreground = !"false".equals(System.getProperty("es.foreground", System.getProperty("es-foreground")));
// handle the wrapper system property, if its a service, don't run as a service
if (System.getProperty("wrapper.service", "XXX").equalsIgnoreCase("true")) {
foreground = false;
}
Environment environment = initialSettings(foreground);
Settings settings = environment.settings();
LogConfigurator.configure(settings, true);
checkForCustomConfFile();
if (environment.pidFile() != null) {
PidFile.create(environment.pidFile(), true);
}
if (System.getProperty("es.max-open-files", "false").equals("true")) {
ESLogger logger = Loggers.getLogger(Bootstrap.class);
logger.info("max_open_files [{}]", ProcessProbe.getInstance().getMaxFileDescriptorCount());
}
// warn if running using the client VM
if (JvmInfo.jvmInfo().getVmName().toLowerCase(Locale.ROOT).contains("client")) {
ESLogger logger = Loggers.getLogger(Bootstrap.class);
logger.warn("jvm uses the client vm, make sure to run `java` with the server vm for best performance by adding `-server` to the command line");
}
try {
if (!foreground) {
Loggers.disableConsoleLogging();
closeSystOut();
}
// fail if using broken version
JVMCheck.check();
INSTANCE.setup(true, settings, environment);
INSTANCE.start();
if (!foreground) {
closeSysError();
}
} catch (Throwable e) {
// disable console logging, so user does not see the exception twice (jvm will show it already)
if (foreground) {
Loggers.disableConsoleLogging();
}
ESLogger logger = Loggers.getLogger(Bootstrap.class);
if (INSTANCE.node != null) {
logger = Loggers.getLogger(Bootstrap.class, INSTANCE.node.settings().get("name"));
}
// HACK, it sucks to do this, but we will run users out of disk space otherwise
if (e instanceof CreationException) {
// guice: log the shortened exc to the log file
ByteArrayOutputStream os = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(os, false, "UTF-8");
new StartupError(e).printStackTrace(ps);
ps.flush();
logger.error("Guice Exception: {}", os.toString("UTF-8"));
} else {
// full exception
logger.error("Exception", e);
}
// re-enable it if appropriate, so they can see any logging during the shutdown process
if (foreground) {
Loggers.enableConsoleLogging();
}
throw e;
}
}
@SuppressForbidden(reason = "System#out")
private static void closeSystOut() {
System.out.close();
}
@SuppressForbidden(reason = "System#err")
private static void closeSysError() {
System.err.close();
}
@SuppressForbidden(reason = "System#err")
private static void sysError(String line, boolean flush) {
System.err.println(line);
if (flush) {
System.err.flush();
}
}
private static void checkForCustomConfFile() {
String confFileSetting = System.getProperty("es.default.config");
checkUnsetAndMaybeExit(confFileSetting, "es.default.config");
confFileSetting = System.getProperty("es.config");
checkUnsetAndMaybeExit(confFileSetting, "es.config");
confFileSetting = System.getProperty("elasticsearch.config");
checkUnsetAndMaybeExit(confFileSetting, "elasticsearch.config");
}
private static void checkUnsetAndMaybeExit(String confFileSetting, String settingName) {
if (confFileSetting != null && confFileSetting.isEmpty() == false) {
ESLogger logger = Loggers.getLogger(Bootstrap.class);
logger.info("{} is no longer supported. elasticsearch.yml must be placed in the config directory and cannot be renamed.", settingName);
exit(1);
}
}
@SuppressForbidden(reason = "Allowed to exit explicitly in bootstrap phase")
private static void exit(int status) {
System.exit(status);
}
}