From 8622033175f69286c82a68656f57756091007414 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Fri, 21 Jul 2017 16:48:25 +0200 Subject: [PATCH] SONAR-8798 make ES read config files provided by SQ rather than those provided by default in distribution --- .../process/CommandFactoryImpl.java | 27 ++++- .../sonar/application/process/EsCommand.java | 21 ++++ .../sonar/application/process/EsSettings.java | 29 +++-- .../process/ProcessLauncherImpl.java | 20 ++++ .../application/process/elasticsearch.yml | 91 ++++++++++++++++ .../org/sonar/application/process/jvm.options | 103 ++++++++++++++++++ .../application/process/EsSettingsTest.java | 2 + .../sonar/process/logging/LogbackHelper.java | 2 +- 8 files changed, 280 insertions(+), 15 deletions(-) create mode 100644 server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml create mode 100644 server/sonar-process-monitor/src/main/resources/org/sonar/application/process/jvm.options diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java index 8b8c68a0e80..093b44ef20d 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/CommandFactoryImpl.java @@ -22,9 +22,11 @@ package org.sonar.application.process; import java.io.File; import java.util.Map; import java.util.Optional; +import java.util.Properties; import org.sonar.application.config.AppSettings; import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; +import org.sonar.process.logging.LogbackHelper; import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST; import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT; @@ -61,14 +63,15 @@ public class CommandFactoryImpl implements CommandFactory { Map settingsMap = new EsSettings(this.settings.getProps()).build(); + File logDir = new File(settingsMap.get("path.logs")); EsCommand res = new EsCommand(ProcessId.ELASTICSEARCH) .setWorkDir(executable.getParentFile().getParentFile()) .setExecutable(executable) + .setConfDir(new File(settingsMap.get("path.conf"))) + .setLog4j2Properties(buildLog4j2Properties(logDir)) .setArguments(this.settings.getProps().rawProperties()) .setClusterName(settingsMap.get("cluster.name")) .setHost(settingsMap.get("network.host")) - // TODO add argument to specify log4j configuration file - // TODO add argument to specify yaml configuration file .setPort(Integer.valueOf(settingsMap.get("transport.tcp.port"))); settingsMap.forEach((key, value) -> res.addEsOption("-E" + key + "=" + value)); @@ -84,6 +87,26 @@ public class CommandFactoryImpl implements CommandFactory { // .addJavaOptions(settings.getProps().nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS)); } + private Properties buildLog4j2Properties(File logDir) { + // FIXME create a Log4jHelper which shares code with LogbackHelper to build this Properties object + not make LogbackHelper.resolveLevel public + provide correct log format, rolling policy, ... + String logLevel = LogbackHelper.resolveLevel(settings.getProps(), "sonar.log.level", "sonar.log.level.es").toString(); + Properties log4j2Properties = new Properties(); + log4j2Properties.put("status", "error"); + log4j2Properties.put("appender.rolling.type", "RollingFile"); + log4j2Properties.put("appender.rolling.name", "rolling"); + log4j2Properties.put("appender.rolling.fileName", new File(logDir, "es.log").getAbsolutePath()); + log4j2Properties.put("appender.rolling.layout.type", "PatternLayout"); + log4j2Properties.put("appender.rolling.layout.pattern", "[%d{ISO8601}][%-5p][%-25c{1.}] %marker%.-10000m%n"); + log4j2Properties.put("appender.rolling.filePattern", "${sys:es.logs}-%d{yyyy-MM-dd}.log"); + log4j2Properties.put("appender.rolling.policies.type", "Policies"); + log4j2Properties.put("appender.rolling.policies.time.type", "TimeBasedTriggeringPolicy"); + log4j2Properties.put("appender.rolling.policies.time.interval", "1"); + log4j2Properties.put("appender.rolling.policies.time.modulate", "true"); + log4j2Properties.put("rootLogger.level", logLevel); + log4j2Properties.put("rootLogger.appenderRef.rolling.ref", "rolling"); + return log4j2Properties; + } + private static String getExecutable() { if (System.getProperty("os.name").startsWith("Windows")) { return "elasticsearch/bin/elasticsearch.bat"; diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsCommand.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsCommand.java index 0eed2d2aebd..5557978388b 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsCommand.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsCommand.java @@ -22,13 +22,16 @@ package org.sonar.application.process; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Properties; import org.sonar.process.ProcessId; public class EsCommand extends AbstractCommand { private File executable; + private File confDir; private String clusterName; private String host; private int port; + private Properties log4j2Properties; private List esOptions = new ArrayList<>(); public EsCommand(ProcessId id) { @@ -44,6 +47,15 @@ public class EsCommand extends AbstractCommand { return this; } + public File getConfDir() { + return confDir; + } + + public EsCommand setConfDir(File confDir) { + this.confDir = confDir; + return this; + } + public String getClusterName() { return clusterName; } @@ -71,6 +83,15 @@ public class EsCommand extends AbstractCommand { return this; } + public Properties getLog4j2Properties() { + return log4j2Properties; + } + + public EsCommand setLog4j2Properties(Properties log4j2Properties) { + this.log4j2Properties = log4j2Properties; + return this; + } + public List getEsOptions() { return esOptions; } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsSettings.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsSettings.java index da423d857a9..cf4470e0d41 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsSettings.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/EsSettings.java @@ -73,26 +73,31 @@ public class EsSettings { private void configureFileSystem(Map builder) { File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); - File dataDir; - File logDir; - // data dir + builder.put("path.data", buildDataPath(homeDir).getAbsolutePath()); + builder.put("path.conf", buildConfDir().getAbsolutePath()); + builder.put("path.logs", buildLogPath(homeDir).getAbsolutePath()); + } + + private File buildDataPath(File homeDir) { String dataPath = props.value(ProcessProperties.PATH_DATA); if (StringUtils.isNotEmpty(dataPath)) { - dataDir = new File(dataPath, "es"); - } else { - dataDir = new File(homeDir, "data/es"); + return new File(dataPath, "es"); } - builder.put("path.data", dataDir.getAbsolutePath()); + return new File(homeDir, "data/es"); + } - // log dir + private File buildLogPath(File homeDir) { String logPath = props.value(ProcessProperties.PATH_LOGS); if (StringUtils.isNotEmpty(logPath)) { - logDir = new File(logPath); - } else { - logDir = new File(homeDir, "log"); + return new File(logPath); } - builder.put("path.logs", logDir.getAbsolutePath()); + return new File(homeDir, "log"); + } + + private File buildConfDir() { + String tempPath = props.value(ProcessProperties.PATH_TEMP); + return new File(new File(tempPath, "conf"), "es"); } private void configureNetwork(Map builder) { diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java index c0d5ef2acda..f6817b6ab92 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.function.Supplier; +import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.process.AllProcessesCommands; @@ -66,6 +67,7 @@ public class ProcessLauncherImpl implements ProcessLauncher { public ProcessMonitor launch(EsCommand esCommand) { Process process = null; try { + writeConfFiles(esCommand); ProcessBuilder processBuilder = create(esCommand); LOG.info("Launch process[{}]: {}", esCommand.getProcessId().getKey(), String.join(" ", processBuilder.command())); @@ -81,6 +83,24 @@ public class ProcessLauncherImpl implements ProcessLauncher { } } + private void writeConfFiles(EsCommand esCommand) { + File confDir = esCommand.getConfDir(); + if (!confDir.exists() && !confDir.mkdirs()) { + String error = format("Failed to create temporary configuration directory [%s]", confDir.getAbsolutePath()); + LOG.error(error); + throw new IllegalStateException(error); + } + + try { + IOUtils.copy(getClass().getResourceAsStream("elasticsearch.yml"), new FileOutputStream(new File(confDir, "elasticsearch.yml"))); + IOUtils.copy(getClass().getResourceAsStream("jvm.options"), new FileOutputStream(new File(confDir, "jvm.options"))); + esCommand.getLog4j2Properties().store(new FileOutputStream(new File(confDir, "log4j2.properties")), "log42 properties file for ES bundled in SonarQube"); + } catch (IOException e) { + e.printStackTrace(); + throw new IllegalStateException("Failed to write ES configuration files", e); + } + } + @Override public ProcessMonitor launch(JavaCommand javaCommand) { Process process = null; diff --git a/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml b/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml new file mode 100644 index 00000000000..bca86355509 --- /dev/null +++ b/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/elasticsearch.yml @@ -0,0 +1,91 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please see the documentation for further information on configuration options: +# +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +#cluster.name: my-application +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +#node.name: node-1 +# +# Add custom attributes to the node: +# +#node.attr.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +#path.data: /path/to/data +# +# Path to log files: +# +#path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +#bootstrap.memory_lock: true +# +# Make sure that the heap size is set to about half the memory available +# on the system and that the owner of the process is allowed to use this +# limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +#network.host: 192.168.0.1 +# +# Set a custom port for HTTP: +# +#http.port: 9200 +# +# For more information, see the documentation at: +# +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when new node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +#discovery.zen.ping.unicast.hosts: ["host1", "host2"] +# +# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1): +# +#discovery.zen.minimum_master_nodes: 3 +# +# For more information, see the documentation at: +# +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +#gateway.recover_after_nodes: 3 +# +# For more information, see the documentation at: +# +# +# ---------------------------------- Various ----------------------------------- +# +# Require explicit names when deleting indices: +# +#action.destructive_requires_name: true diff --git a/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/jvm.options b/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/jvm.options new file mode 100644 index 00000000000..be2b6ab73a2 --- /dev/null +++ b/server/sonar-process-monitor/src/main/resources/org/sonar/application/process/jvm.options @@ -0,0 +1,103 @@ +## JVM configuration + +################################################################ +## IMPORTANT: JVM heap size +################################################################ +## +## You should always set the min and max JVM heap +## size to the same value. For example, to set +## the heap to 4 GB, set: +## +## -Xms4g +## -Xmx4g +## +## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html +## for more information +## +################################################################ + +# Xms represents the initial size of total heap space +# Xmx represents the maximum size of total heap space + +-Xms2g +-Xmx2g + +################################################################ +## Expert settings +################################################################ +## +## All settings below this section are considered +## expert settings. Don't tamper with them unless +## you understand what you are doing +## +################################################################ + +## GC configuration +-XX:+UseConcMarkSweepGC +-XX:CMSInitiatingOccupancyFraction=75 +-XX:+UseCMSInitiatingOccupancyOnly + +## optimizations + +# disable calls to System#gc +-XX:+DisableExplicitGC + +# pre-touch memory pages used by the JVM during initialization +-XX:+AlwaysPreTouch + +## basic + +# force the server VM +-server + +# set to headless, just in case +-Djava.awt.headless=true + +# ensure UTF-8 encoding by default (e.g. filenames) +-Dfile.encoding=UTF-8 + +# use our provided JNA always versus the system one +-Djna.nosys=true + +# use old-style file permissions on JDK9 +-Djdk.io.permissionsUseCanonicalPath=true + +# flags to keep Netty from being unsafe +-Dio.netty.noUnsafe=true +-Dio.netty.noKeySetOptimization=true + +# log4j 2 +-Dlog4j.shutdownHookEnabled=false +-Dlog4j2.disable.jmx=true +-Dlog4j.skipJansi=true + +## heap dumps + +# generate a heap dump when an allocation from the Java heap fails +# heap dumps are created in the working directory of the JVM +-XX:+HeapDumpOnOutOfMemoryError + +# specify an alternative path for heap dumps +# ensure the directory exists and has sufficient space +#-XX:HeapDumpPath=${heap.dump.path} + +## GC logging + +#-XX:+PrintGCDetails +#-XX:+PrintGCTimeStamps +#-XX:+PrintGCDateStamps +#-XX:+PrintClassHistogram +#-XX:+PrintTenuringDistribution +#-XX:+PrintGCApplicationStoppedTime + +# log GC status to a file with time stamps +# ensure the directory exists +#-Xloggc:${loggc} + +# Elasticsearch 5.0.0 will throw an exception on unquoted field names in JSON. +# If documents were already indexed with unquoted fields in a previous version +# of Elasticsearch, some operations may throw errors. +# +# WARNING: This option will be removed in Elasticsearch 6.0.0 and is provided +# only for migration purposes. +#-Delasticsearch.json.allow_unquoted_field_names=true diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsSettingsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsSettingsTest.java index 9e6332aac4d..5b20696a210 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsSettingsTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/EsSettingsTest.java @@ -65,6 +65,7 @@ public class EsSettingsTest { assertThat(generated.get("path.data")).isNotNull(); assertThat(generated.get("path.logs")).isNotNull(); assertThat(generated.get("path.home")).isNull(); + assertThat(generated.get("path.conf")).isNotNull(); // http is disabled for security reasons assertThat(generated.get("http.enabled")).isEqualTo("false"); @@ -91,6 +92,7 @@ public class EsSettingsTest { assertThat(settings.get("path.data")).isEqualTo(new File(dataDir, "es").getAbsolutePath()); assertThat(settings.get("path.logs")).isEqualTo(logDir.getAbsolutePath()); assertThat(settings.get("path.home")).isNull(); + assertThat(settings.get("path.conf")).isEqualTo(new File(tempDir, "conf/es").getAbsolutePath()); } @Test diff --git a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java index 5dbe4bf289a..a044a931112 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java +++ b/server/sonar-process/src/main/java/org/sonar/process/logging/LogbackHelper.java @@ -129,7 +129,7 @@ public class LogbackHelper { * * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS} */ - private static Level resolveLevel(Props props, String... propertyKeys) { + public static Level resolveLevel(Props props, String... propertyKeys) { Level newLevel = Level.INFO; for (String propertyKey : propertyKeys) { Level level = getPropertyValueAsLevel(props, propertyKey); -- 2.39.5