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.

CommandFactoryImplTest.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program 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. * This program 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.application.command;
  21. import ch.qos.logback.classic.spi.ILoggingEvent;
  22. import com.google.common.collect.ImmutableSet;
  23. import java.io.File;
  24. import java.io.IOException;
  25. import java.util.Properties;
  26. import org.apache.commons.io.FileUtils;
  27. import org.junit.After;
  28. import org.junit.Before;
  29. import org.junit.Rule;
  30. import org.junit.Test;
  31. import org.junit.rules.ExpectedException;
  32. import org.junit.rules.TemporaryFolder;
  33. import org.mockito.Mockito;
  34. import org.sonar.application.es.EsInstallation;
  35. import org.sonar.application.logging.ListAppender;
  36. import org.sonar.core.extension.ServiceLoaderWrapper;
  37. import org.sonar.process.ProcessId;
  38. import org.sonar.process.ProcessProperties;
  39. import org.sonar.process.Props;
  40. import org.sonar.process.System2;
  41. import static org.assertj.core.api.Assertions.assertThat;
  42. import static org.assertj.core.api.Assertions.entry;
  43. import static org.mockito.Mockito.mock;
  44. import static org.mockito.Mockito.when;
  45. public class CommandFactoryImplTest {
  46. @Rule
  47. public ExpectedException expectedException = ExpectedException.none();
  48. @Rule
  49. public TemporaryFolder temp = new TemporaryFolder();
  50. private System2 system2 = Mockito.mock(System2.class);
  51. private JavaVersion javaVersion = Mockito.mock(JavaVersion.class);
  52. private File homeDir;
  53. private File tempDir;
  54. private File logsDir;
  55. private ListAppender listAppender;
  56. @Before
  57. public void setUp() throws Exception {
  58. homeDir = temp.newFolder();
  59. tempDir = temp.newFolder();
  60. logsDir = temp.newFolder();
  61. }
  62. @After
  63. public void tearDown() {
  64. if (listAppender != null) {
  65. ListAppender.detachMemoryAppenderToLoggerOf(CommandFactoryImpl.class, listAppender);
  66. }
  67. }
  68. @Test
  69. public void constructor_logs_no_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_not_set() {
  70. attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
  71. new CommandFactoryImpl(new Props(new Properties()), tempDir, system2, javaVersion);
  72. assertThat(listAppender.getLogs()).isEmpty();
  73. }
  74. @Test
  75. public void constructor_logs_warning_if_env_variable_JAVA_TOOL_OPTIONS_is_set() {
  76. when(system2.getenv("JAVA_TOOL_OPTIONS")).thenReturn("sds");
  77. attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
  78. new CommandFactoryImpl(new Props(new Properties()), tempDir, system2, javaVersion);
  79. assertThat(listAppender.getLogs())
  80. .extracting(ILoggingEvent::getMessage)
  81. .containsOnly(
  82. "JAVA_TOOL_OPTIONS is defined but will be ignored. " +
  83. "Use properties sonar.*.javaOpts and/or sonar.*.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
  84. }
  85. @Test
  86. public void constructor_logs_warning_if_env_variable_ES_JAVA_OPTS_is_set() {
  87. when(system2.getenv("ES_JAVA_OPTS")).thenReturn("xyz");
  88. attachMemoryAppenderToLoggerOf(CommandFactoryImpl.class);
  89. new CommandFactoryImpl(new Props(new Properties()), tempDir, system2, javaVersion);
  90. assertThat(listAppender.getLogs())
  91. .extracting(ILoggingEvent::getMessage)
  92. .containsOnly(
  93. "ES_JAVA_OPTS is defined but will be ignored. " +
  94. "Use properties sonar.search.javaOpts and/or sonar.search.javaAdditionalOpts in sonar.properties to change SQ JVM processes options");
  95. }
  96. @Test
  97. public void createEsCommand_throws_ISE_if_es_binary_is_not_found() {
  98. expectedException.expect(IllegalStateException.class);
  99. expectedException.expectMessage("Cannot find elasticsearch binary");
  100. newFactory(new Properties()).createEsCommand();
  101. }
  102. @Test
  103. public void createEsCommand_for_unix_returns_command_for_default_settings() throws Exception {
  104. when(system2.isOsWindows()).thenReturn(false);
  105. prepareEsFileSystem();
  106. Properties props = new Properties();
  107. props.setProperty("sonar.search.host", "localhost");
  108. AbstractCommand command = newFactory(props, system2).createEsCommand();
  109. assertThat(command).isInstanceOf(EsScriptCommand.class);
  110. EsScriptCommand esCommand = (EsScriptCommand) command;
  111. EsInstallation esConfig = esCommand.getEsInstallation();
  112. assertThat(esConfig.getHost()).isNotEmpty();
  113. assertThat(esConfig.getHttpPort()).isEqualTo(9001);
  114. assertThat(esConfig.getEsJvmOptions().getAll())
  115. // enforced values
  116. .contains("-XX:+UseConcMarkSweepGC", "-Dfile.encoding=UTF-8")
  117. // default settings
  118. .contains("-Xms512m", "-Xmx512m", "-XX:+HeapDumpOnOutOfMemoryError");
  119. assertThat(esConfig.getEsYmlSettings()).isNotNull();
  120. assertThat(esConfig.getLog4j2Properties())
  121. .contains(entry("appender.file_es.fileName", new File(logsDir, "es.log").getAbsolutePath()));
  122. File esConfDir = new File(tempDir, "conf/es");
  123. assertThat(esCommand.getEnvVariables())
  124. .contains(entry("ES_PATH_CONF", esConfDir.getAbsolutePath()))
  125. .contains(entry("ES_JVM_OPTIONS", new File(esConfDir, "jvm.options").getAbsolutePath()))
  126. .containsKey("JAVA_HOME");
  127. assertThat(esCommand.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS", "ES_JAVA_OPTS");
  128. assertThat(esConfig.getEsJvmOptions().getAll())
  129. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath());
  130. }
  131. @Test
  132. public void createEsCommand_for_windows_returns_command_for_default_settings() throws Exception {
  133. when(system2.isOsWindows()).thenReturn(true);
  134. prepareEsFileSystem();
  135. Properties props = new Properties();
  136. props.setProperty("sonar.search.host", "localhost");
  137. AbstractCommand command = newFactory(props, system2).createEsCommand();
  138. assertThat(command).isInstanceOf(JavaCommand.class);
  139. JavaCommand<?> esCommand = (JavaCommand<?>) command;
  140. EsInstallation esConfig = esCommand.getEsInstallation();
  141. assertThat(esConfig.getHost()).isNotEmpty();
  142. assertThat(esConfig.getHttpPort()).isEqualTo(9001);
  143. assertThat(esConfig.getEsJvmOptions().getAll())
  144. // enforced values
  145. .contains("-XX:+UseConcMarkSweepGC", "-Dfile.encoding=UTF-8")
  146. // default settings
  147. .contains("-Xms512m", "-Xmx512m", "-XX:+HeapDumpOnOutOfMemoryError");
  148. assertThat(esConfig.getEsYmlSettings()).isNotNull();
  149. assertThat(esConfig.getLog4j2Properties())
  150. .contains(entry("appender.file_es.fileName", new File(logsDir, "es.log").getAbsolutePath()));
  151. File esConfDir = new File(tempDir, "conf/es");
  152. assertThat(esCommand.getArguments()).isEmpty();
  153. assertThat(esCommand.getEnvVariables())
  154. .contains(entry("ES_JVM_OPTIONS", new File(esConfDir, "jvm.options").getAbsolutePath()))
  155. .containsKey("JAVA_HOME");
  156. assertThat(esCommand.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS", "ES_JAVA_OPTS");
  157. assertThat(esConfig.getEsJvmOptions().getAll())
  158. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath());
  159. assertThat(esCommand.getJvmOptions().getAll())
  160. .containsAll(esConfig.getEsJvmOptions().getAll())
  161. .contains("-Delasticsearch")
  162. .contains("-Des.path.home=" + new File(homeDir, "elasticsearch"))
  163. .contains("-Des.path.conf=" + esConfDir.getAbsolutePath());
  164. }
  165. @Test
  166. public void createEsCommand_returns_command_for_overridden_settings() throws Exception {
  167. prepareEsFileSystem();
  168. Properties props = new Properties();
  169. props.setProperty("sonar.search.host", "localhost");
  170. props.setProperty("sonar.cluster.name", "foo");
  171. props.setProperty("sonar.search.port", "1234");
  172. props.setProperty("sonar.search.javaOpts", "-Xms10G -Xmx10G");
  173. AbstractCommand esCommand = newFactory(props).createEsCommand();
  174. EsInstallation esConfig = esCommand.getEsInstallation();
  175. assertThat(esConfig.getHttpPort()).isEqualTo(1234);
  176. assertThat(esConfig.getEsJvmOptions().getAll())
  177. // enforced values
  178. .contains("-XX:+UseConcMarkSweepGC", "-Dfile.encoding=UTF-8")
  179. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath())
  180. // user settings
  181. .contains("-Xms10G", "-Xmx10G")
  182. // default values disabled
  183. .doesNotContain("-XX:+HeapDumpOnOutOfMemoryError");
  184. }
  185. @Test
  186. public void createWebCommand_returns_command_for_default_settings() {
  187. JavaCommand command = newFactory(new Properties()).createWebCommand(true);
  188. assertThat(command.getClassName()).isEqualTo("org.sonar.server.app.WebServer");
  189. assertThat(command.getWorkDir().getAbsolutePath()).isEqualTo(homeDir.getAbsolutePath());
  190. assertThat(command.getClasspath()).hasSize(1).allMatch(p -> p.toString().startsWith("./lib/sonar-application-"));
  191. assertThat(command.getJvmOptions().getAll())
  192. // enforced values
  193. .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
  194. // default settings
  195. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
  196. .contains("-Xmx512m", "-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
  197. assertThat(command.getProcessId()).isEqualTo(ProcessId.WEB_SERVER);
  198. assertThat(command.getEnvVariables())
  199. .isNotEmpty();
  200. assertThat(command.getArguments())
  201. // default settings
  202. .contains(entry("sonar.web.javaOpts", "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"))
  203. .contains(entry("sonar.cluster.enabled", "false"));
  204. assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
  205. }
  206. @Test
  207. public void createCeCommand_returns_command_for_default_settings() {
  208. JavaCommand command = newFactory(new Properties()).createCeCommand();
  209. assertThat(command.getClassName()).isEqualTo("org.sonar.ce.app.CeServer");
  210. assertThat(command.getWorkDir().getAbsolutePath()).isEqualTo(homeDir.getAbsolutePath());
  211. assertThat(command.getClasspath()).hasSize(1).allMatch(p -> p.toString().startsWith("./lib/sonar-application-"));
  212. assertThat(command.getJvmOptions().getAll())
  213. // enforced values
  214. .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
  215. // default settings
  216. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
  217. .contains("-Xmx512m", "-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
  218. assertThat(command.getProcessId()).isEqualTo(ProcessId.COMPUTE_ENGINE);
  219. assertThat(command.getEnvVariables())
  220. .isNotEmpty();
  221. assertThat(command.getArguments())
  222. // default settings
  223. .contains(entry("sonar.web.javaOpts", "-Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError"))
  224. .contains(entry("sonar.cluster.enabled", "false"));
  225. assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
  226. }
  227. @Test
  228. public void createWebCommand_configures_command_with_overridden_settings() {
  229. Properties props = new Properties();
  230. props.setProperty("sonar.web.port", "1234");
  231. props.setProperty("sonar.web.javaOpts", "-Xmx10G");
  232. JavaCommand command = newFactory(props).createWebCommand(true);
  233. assertThat(command.getJvmOptions().getAll())
  234. // enforced values
  235. .contains("-Djava.awt.headless=true", "-Dfile.encoding=UTF-8")
  236. // default settings
  237. .contains("-Djava.io.tmpdir=" + tempDir.getAbsolutePath(), "-Dfile.encoding=UTF-8")
  238. // overridden values
  239. .contains("-Xmx10G")
  240. .doesNotContain("-Xms128m", "-XX:+HeapDumpOnOutOfMemoryError");
  241. assertThat(command.getArguments())
  242. // default settings
  243. .contains(entry("sonar.web.javaOpts", "-Xmx10G"))
  244. .contains(entry("sonar.cluster.enabled", "false"));
  245. assertThat(command.getSuppressedEnvVariables()).containsOnly("JAVA_TOOL_OPTIONS");
  246. }
  247. @Test
  248. public void createWebCommand_adds_configured_jdbc_driver_to_classpath() throws Exception {
  249. Properties props = new Properties();
  250. File driverFile = temp.newFile();
  251. props.setProperty("sonar.jdbc.driverPath", driverFile.getAbsolutePath());
  252. JavaCommand command = newFactory(props).createWebCommand(true);
  253. assertThat(command.getClasspath()).hasSize(2);
  254. assertThat(command.getClasspath().get(0).toString()).startsWith("./lib/sonar-application-");
  255. assertThat(command.getClasspath().get(1)).isEqualTo(driverFile.getAbsolutePath());
  256. }
  257. private void prepareEsFileSystem() throws IOException {
  258. FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch"));
  259. FileUtils.touch(new File(homeDir, "elasticsearch/bin/elasticsearch.bat"));
  260. }
  261. private CommandFactoryImpl newFactory(Properties userProps) {
  262. return newFactory(userProps, System2.INSTANCE);
  263. }
  264. private CommandFactoryImpl newFactory(Properties userProps, System2 system2) {
  265. Properties p = new Properties();
  266. p.setProperty("sonar.path.home", homeDir.getAbsolutePath());
  267. p.setProperty("sonar.path.temp", tempDir.getAbsolutePath());
  268. p.setProperty("sonar.path.logs", logsDir.getAbsolutePath());
  269. p.putAll(userProps);
  270. Props props = new Props(p);
  271. ServiceLoaderWrapper serviceLoaderWrapper = mock(ServiceLoaderWrapper.class);
  272. when(serviceLoaderWrapper.load()).thenReturn(ImmutableSet.of());
  273. new ProcessProperties(serviceLoaderWrapper).completeDefaults(props);
  274. return new CommandFactoryImpl(props, tempDir, system2, javaVersion);
  275. }
  276. private <T> void attachMemoryAppenderToLoggerOf(Class<T> loggerClass) {
  277. this.listAppender = ListAppender.attachMemoryAppenderToLoggerOf(loggerClass);
  278. }
  279. }