You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearchServer.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. * SonarQube, open source software quality management tool.
  3. * Copyright (C) 2008-2014 SonarSource
  4. * mailto:contact AT sonarsource DOT com
  5. *
  6. * SonarQube is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * SonarQube is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.search;
  21. import org.apache.commons.lang.StringUtils;
  22. import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
  23. import org.elasticsearch.common.annotations.VisibleForTesting;
  24. import org.elasticsearch.common.settings.ImmutableSettings;
  25. import org.elasticsearch.common.unit.TimeValue;
  26. import org.elasticsearch.node.Node;
  27. import org.elasticsearch.node.NodeBuilder;
  28. import org.slf4j.LoggerFactory;
  29. import org.sonar.process.ConfigurationUtils;
  30. import org.sonar.process.MinimumViableSystem;
  31. import org.sonar.process.MonitoredProcess;
  32. import org.sonar.process.ProcessLogging;
  33. import org.sonar.process.Props;
  34. import org.sonar.search.script.ListUpdate;
  35. import java.io.File;
  36. import java.net.InetAddress;
  37. import java.util.Collections;
  38. import java.util.HashSet;
  39. import java.util.Set;
  40. public class SearchServer extends MonitoredProcess {
  41. public static final String SONAR_NODE_NAME = "sonar.node.name";
  42. public static final String ES_PORT_PROPERTY = "sonar.search.port";
  43. public static final String ES_CLUSTER_PROPERTY = "sonar.cluster.name";
  44. public static final String ES_CLUSTER_INET = "sonar.cluster.master";
  45. public static final String SONAR_PATH_HOME = "sonar.path.home";
  46. public static final String SONAR_PATH_DATA = "sonar.path.data";
  47. public static final String SONAR_PATH_TEMP = "sonar.path.temp";
  48. public static final String SONAR_PATH_LOG = "sonar.path.log";
  49. private static final Integer MINIMUM_INDEX_REPLICATION = 1;
  50. private final Set<String> nodes = new HashSet<String>();
  51. private final boolean isBlocking;
  52. private Node node;
  53. @VisibleForTesting
  54. public SearchServer(final Props props, boolean monitored, boolean blocking) {
  55. super(props, monitored);
  56. this.isBlocking = blocking;
  57. new MinimumViableSystem().check();
  58. String esNodesInets = props.of(ES_CLUSTER_INET);
  59. if (StringUtils.isNotEmpty(esNodesInets)) {
  60. Collections.addAll(nodes, esNodesInets.split(","));
  61. }
  62. }
  63. public SearchServer(Props props) {
  64. super(props);
  65. this.isBlocking = true;
  66. new MinimumViableSystem().check();
  67. String esNodesInets = props.of(ES_CLUSTER_INET);
  68. if (StringUtils.isNotEmpty(esNodesInets)) {
  69. Collections.addAll(nodes, esNodesInets.split(","));
  70. }
  71. }
  72. @Override
  73. protected boolean doIsReady() {
  74. return node.client().admin().cluster().prepareHealth()
  75. .setWaitForYellowStatus()
  76. .setTimeout(TimeValue.timeValueSeconds(3L))
  77. .get()
  78. .getStatus() != ClusterHealthStatus.RED;
  79. }
  80. @Override
  81. protected void doStart() {
  82. Integer port = props.intOf(ES_PORT_PROPERTY);
  83. String clusterName = props.of(ES_CLUSTER_PROPERTY);
  84. LoggerFactory.getLogger(SearchServer.class).info("Starting ES[{}] on port: {}", clusterName, port);
  85. ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder()
  86. // Disable MCast
  87. .put("discovery.zen.ping.multicast.enabled", "false")
  88. // Index storage policies
  89. .put("index.merge.policy.max_merge_at_once", "200")
  90. .put("index.merge.policy.segments_per_tier", "200")
  91. .put("index.number_of_shards", "1")
  92. .put("index.number_of_replicas", MINIMUM_INDEX_REPLICATION)
  93. .put("index.store.type", "mmapfs")
  94. .put("indices.store.throttle.type", "merge")
  95. .put("indices.store.throttle.max_bytes_per_sec", "200mb")
  96. // Install our own listUpdate scripts
  97. .put("script.default_lang", "native")
  98. .put("script.native." + ListUpdate.NAME + ".type", ListUpdate.UpdateListScriptFactory.class.getName())
  99. // Node is pure transport
  100. .put("transport.tcp.port", port)
  101. .put("http.enabled", false)
  102. // Setting up ES paths
  103. .put("path.data", esDataDir().getAbsolutePath())
  104. .put("path.work", esWorkDir().getAbsolutePath())
  105. .put("path.logs", esLogDir().getAbsolutePath());
  106. if (!nodes.isEmpty()) {
  107. LoggerFactory.getLogger(SearchServer.class).info("Joining ES cluster with master: {}", nodes);
  108. esSettings.put("discovery.zen.ping.unicast.hosts", StringUtils.join(nodes, ","));
  109. esSettings.put("node.master", false);
  110. // Enforce a N/2+1 number of masters in cluster
  111. esSettings.put("discovery.zen.minimum_master_nodes", 1);
  112. // Change master pool requirement when in distributed mode
  113. // esSettings.put("discovery.zen.minimum_master_nodes", (int) Math.floor(nodes.size() / 2.0) + 1);
  114. }
  115. // Set cluster coordinates
  116. esSettings.put("cluster.name", clusterName);
  117. esSettings.put("node.rack_id", StringUtils.defaultIfEmpty(props.of(SONAR_NODE_NAME), "unknown"));
  118. esSettings.put("cluster.routing.allocation.awareness.attributes", "rack_id");
  119. if (props.contains(SONAR_NODE_NAME)) {
  120. esSettings.put("node.name", props.of(SONAR_NODE_NAME));
  121. } else {
  122. try {
  123. esSettings.put("node.name", InetAddress.getLocalHost().getHostName());
  124. } catch (Exception e) {
  125. LoggerFactory.getLogger(SearchServer.class).warn("Could not determine hostname", e);
  126. esSettings.put("node.name", "sq-" + System.currentTimeMillis());
  127. }
  128. }
  129. // Make sure the index settings are up to date.
  130. initAnalysis(esSettings);
  131. // And building our ES Node
  132. node = NodeBuilder.nodeBuilder()
  133. .settings(esSettings)
  134. .build().start();
  135. node.client().admin().indices()
  136. .preparePutTemplate("default")
  137. .setTemplate("*")
  138. .addMapping("_default_", "{\"dynamic\": \"strict\"}")
  139. .get();
  140. if (this.isBlocking) {
  141. while (node != null && !node.isClosed()) {
  142. try {
  143. Thread.sleep(100);
  144. } catch (InterruptedException e) {
  145. // Ignore
  146. }
  147. }
  148. }
  149. }
  150. private void initAnalysis(ImmutableSettings.Builder esSettings) {
  151. esSettings
  152. // Disallow dynamic mapping (too expensive)
  153. .put("index.mapper.dynamic", false)
  154. // Sortable text analyzer
  155. .put("index.analysis.analyzer.sortable.type", "custom")
  156. .put("index.analysis.analyzer.sortable.tokenizer", "keyword")
  157. .putArray("index.analysis.analyzer.sortable.filter", "trim", "lowercase", "truncate")
  158. // Edge NGram index-analyzer
  159. .put("index.analysis.analyzer.index_grams.type", "custom")
  160. .put("index.analysis.analyzer.index_grams.tokenizer", "whitespace")
  161. .putArray("index.analysis.analyzer.index_grams.filter", "trim", "lowercase", "gram_filter")
  162. // Edge NGram search-analyzer
  163. .put("index.analysis.analyzer.search_grams.type", "custom")
  164. .put("index.analysis.analyzer.search_grams.tokenizer", "whitespace")
  165. .putArray("index.analysis.analyzer.search_grams.filter", "trim", "lowercase")
  166. // Word index-analyzer
  167. .put("index.analysis.analyzer.index_words.type", "custom")
  168. .put("index.analysis.analyzer.index_words.tokenizer", "standard")
  169. .putArray("index.analysis.analyzer.index_words.filter",
  170. "standard", "word_filter", "lowercase", "stop", "asciifolding", "porter_stem")
  171. // Word search-analyzer
  172. .put("index.analysis.analyzer.search_words.type", "custom")
  173. .put("index.analysis.analyzer.search_words.tokenizer", "standard")
  174. .putArray("index.analysis.analyzer.search_words.filter",
  175. "standard", "lowercase", "stop", "asciifolding", "porter_stem")
  176. // Edge NGram filter
  177. .put("index.analysis.filter.gram_filter.type", "edgeNGram")
  178. .put("index.analysis.filter.gram_filter.min_gram", 2)
  179. .put("index.analysis.filter.gram_filter.max_gram", 15)
  180. .putArray("index.analysis.filter.gram_filter.token_chars", "letter", "digit", "punctuation", "symbol")
  181. // Word filter
  182. .put("index.analysis.filter.word_filter.type", "word_delimiter")
  183. .put("index.analysis.filter.word_filter.generate_word_parts", true)
  184. .put("index.analysis.filter.word_filter.catenate_words", true)
  185. .put("index.analysis.filter.word_filter.catenate_numbers", true)
  186. .put("index.analysis.filter.word_filter.catenate_all", true)
  187. .put("index.analysis.filter.word_filter.split_on_case_change", true)
  188. .put("index.analysis.filter.word_filter.preserve_original", true)
  189. .put("index.analysis.filter.word_filter.split_on_numerics", true)
  190. .put("index.analysis.filter.word_filter.stem_english_possessive", true)
  191. // Path Analyzer
  192. .put("index.analysis.analyzer.path_analyzer.type", "custom")
  193. .put("index.analysis.analyzer.path_analyzer.tokenizer", "path_hierarchy");
  194. }
  195. private File esHomeDir() {
  196. String homeDir = props.of(SONAR_PATH_HOME);
  197. if (StringUtils.isEmpty(homeDir)) {
  198. throw new IllegalStateException("property 'sonar.path.home' is required");
  199. } else {
  200. return new File(homeDir);
  201. }
  202. }
  203. private File esDataDir() {
  204. String dataDir = props.of(SONAR_PATH_DATA);
  205. if (StringUtils.isNotEmpty(dataDir)) {
  206. return new File(dataDir, "es");
  207. } else {
  208. return new File(esHomeDir(), "data/es");
  209. }
  210. }
  211. private File esLogDir() {
  212. String logDir = props.of(SONAR_PATH_LOG);
  213. if (StringUtils.isNotEmpty(logDir)) {
  214. return new File(logDir);
  215. } else {
  216. return new File(esHomeDir(), "log");
  217. }
  218. }
  219. private File esWorkDir() {
  220. String workDir = props.of(SONAR_PATH_TEMP);
  221. if (StringUtils.isNotEmpty(workDir)) {
  222. return new File(workDir);
  223. } else {
  224. return new File(esHomeDir(), "temp");
  225. }
  226. }
  227. @Override
  228. protected void doTerminate() {
  229. if (node != null && !node.isClosed()) {
  230. node.close();
  231. node = null;
  232. }
  233. }
  234. public static void main(String... args) {
  235. Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
  236. new ProcessLogging().configure(props, "/org/sonar/search/logback.xml");
  237. new SearchServer(props).start();
  238. }
  239. }