/* * SonarQube * Copyright (C) 2009-2017 SonarSource SA * mailto:info AT sonarsource DOT com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonar.search; import java.io.File; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Set; import java.util.TreeSet; import java.util.UUID; import org.apache.commons.lang.StringUtils; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.settings.Settings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; public class EsSettings implements EsSettingsMBean { private static final Logger LOGGER = LoggerFactory.getLogger(EsSettings.class); public static final String PROP_MARVEL_HOSTS = "sonar.search.marvelHosts"; public static final String CLUSTER_SEARCH_NODE_NAME = "sonar.cluster.search.nodeName"; public static final String STANDALONE_NODE_NAME = "sonarqube"; private final Props props; private final boolean clusterEnabled; private final String clusterName; private final String nodeName; EsSettings(Props props) { this.props = props; this.clusterName = props.nonNullValue(ProcessProperties.CLUSTER_NAME); this.clusterEnabled = props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED); if (this.clusterEnabled) { this.nodeName = props.value(CLUSTER_SEARCH_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString()); } else { this.nodeName = STANDALONE_NODE_NAME; } } @Override public int getHttpPort() { return props.valueAsInt(ProcessProperties.SEARCH_HTTP_PORT, -1); } @Override public String getClusterName() { return clusterName; } @Override public String getNodeName() { return nodeName; } Settings build() { Settings.Builder builder = Settings.builder(); configureFileSystem(builder); configureIndexDefaults(builder); configureNetwork(builder); configureCluster(builder); configureMarvel(builder); return builder.build(); } private void configureFileSystem(Settings.Builder builder) { File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); File dataDir; File logDir; // data dir String dataPath = props.value(ProcessProperties.PATH_DATA); if (StringUtils.isNotEmpty(dataPath)) { dataDir = new File(dataPath, "es"); } else { dataDir = new File(homeDir, "data/es"); } builder.put("path.data", dataDir.getAbsolutePath()); String tempPath = props.value(ProcessProperties.PATH_TEMP); builder.put("path.home", new File(tempPath, "es")); // log dir String logPath = props.value(ProcessProperties.PATH_LOGS); if (StringUtils.isNotEmpty(logPath)) { logDir = new File(logPath); } else { logDir = new File(homeDir, "log"); } builder.put("path.logs", logDir.getAbsolutePath()); } private void configureNetwork(Settings.Builder builder) { InetAddress host = readHost(); int port = Integer.parseInt(props.nonNullValue(ProcessProperties.SEARCH_PORT)); LOGGER.info("Elasticsearch listening on {}:{}", host, port); // disable multicast builder.put("discovery.zen.ping.multicast.enabled", "false") .put("transport.tcp.port", port) .put("transport.host", host.getHostAddress()) .put("network.host", host.getHostAddress()); // Elasticsearch sets the default value of TCP reuse address to true only on non-MSWindows machines, but why ? builder.put("network.tcp.reuse_address", true); int httpPort = getHttpPort(); if (httpPort < 0) { // standard configuration builder.put("http.enabled", false); } else { LOGGER.warn("Elasticsearch HTTP connector is enabled on port {}. MUST NOT BE USED FOR PRODUCTION", httpPort); // see https://github.com/lmenezes/elasticsearch-kopf/issues/195 builder.put("http.cors.enabled", true) .put("http.cors.allow-origin", "*") .put("http.enabled", true) .put("http.host", host.getHostAddress()) .put("http.port", httpPort); } } private InetAddress readHost() { String hostProperty = props.nonNullValue(ProcessProperties.SEARCH_HOST); try { return InetAddress.getByName(hostProperty); } catch (UnknownHostException e) { throw new IllegalStateException("Can not resolve host [" + hostProperty + "]. Please check network settings and property " + ProcessProperties.SEARCH_HOST, e); } } private static void configureIndexDefaults(Settings.Builder builder) { builder .put("index.number_of_shards", "1") .put("index.refresh_interval", "30s") .put("action.auto_create_index", false) .put("index.mapper.dynamic", false); } private void configureCluster(Settings.Builder builder) { // Default value in a standalone mode, not overridable int replicationFactor = 0; int minimumMasterNodes = 1; String initialStateTimeOut = "30s"; if (clusterEnabled) { replicationFactor = props.valueAsInt(ProcessProperties.SEARCH_REPLICAS, 1); minimumMasterNodes = props.valueAsInt(ProcessProperties.SEARCH_MINIMUM_MASTER_NODES, 2); initialStateTimeOut = props.value(ProcessProperties.SEARCH_INITIAL_STATE_TIMEOUT, "120s"); String hosts = props.value(ProcessProperties.CLUSTER_SEARCH_HOSTS, ""); LOGGER.info("Elasticsearch cluster enabled. Connect to hosts [{}]", hosts); builder.put("discovery.zen.ping.unicast.hosts", hosts); } builder.put("discovery.zen.minimum_master_nodes", minimumMasterNodes) .put("discovery.initial_state_timeout", initialStateTimeOut) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, replicationFactor) .put("cluster.name", getClusterName()) .put("cluster.routing.allocation.awareness.attributes", "rack_id") .put("node.rack_id", nodeName) .put("node.name", nodeName) .put("node.data", true) .put("node.master", true); } private void configureMarvel(Settings.Builder builder) { Set<String> marvels = new TreeSet<>(); marvels.addAll(Arrays.asList(StringUtils.split(props.value(PROP_MARVEL_HOSTS, ""), ","))); // If we're collecting indexing data send them to the Marvel host(s) if (!marvels.isEmpty()) { String hosts = StringUtils.join(marvels, ","); LOGGER.info("Elasticsearch Marvel is enabled for %s", hosts); builder.put("marvel.agent.exporter.es.hosts", hosts); } } }