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.

PlatformImpl.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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.server.platform;
  21. import java.util.ArrayList;
  22. import java.util.Collection;
  23. import java.util.List;
  24. import java.util.Properties;
  25. import javax.annotation.Nullable;
  26. import javax.servlet.ServletContext;
  27. import javax.servlet.ServletRegistration;
  28. import org.sonar.api.utils.log.Logger;
  29. import org.sonar.api.utils.log.Loggers;
  30. import org.sonar.api.utils.log.Profiler;
  31. import org.sonar.core.platform.ExtensionContainer;
  32. import org.sonar.core.platform.SpringComponentContainer;
  33. import org.sonar.server.app.ProcessCommandWrapper;
  34. import org.sonar.server.platform.db.migration.version.DatabaseVersion;
  35. import org.sonar.server.platform.platformlevel.PlatformLevel;
  36. import org.sonar.server.platform.platformlevel.PlatformLevel1;
  37. import org.sonar.server.platform.platformlevel.PlatformLevel2;
  38. import org.sonar.server.platform.platformlevel.PlatformLevel3;
  39. import org.sonar.server.platform.platformlevel.PlatformLevel4;
  40. import org.sonar.server.platform.platformlevel.PlatformLevelSafeMode;
  41. import org.sonar.server.platform.platformlevel.PlatformLevelStartup;
  42. import org.sonar.server.platform.web.ApiV2Servlet;
  43. import static org.sonar.process.ProcessId.WEB_SERVER;
  44. /**
  45. * @since 2.2
  46. */
  47. public class PlatformImpl implements Platform {
  48. private static final Logger LOGGER = Loggers.get(Platform.class);
  49. private static final PlatformImpl INSTANCE = new PlatformImpl();
  50. private AutoStarter autoStarter = null;
  51. private Properties properties = null;
  52. private ServletContext servletContext = null;
  53. private PlatformLevel level1 = null;
  54. private PlatformLevel level2 = null;
  55. private PlatformLevel levelSafeMode = null;
  56. private PlatformLevel level3 = null;
  57. private PlatformLevel level4 = null;
  58. private PlatformLevel currentLevel = null;
  59. private boolean dbConnected = false;
  60. private boolean started = false;
  61. private final List<Object> level4AddedComponents = new ArrayList<>();
  62. private final Profiler profiler = Profiler.createIfTrace(Loggers.get(PlatformImpl.class));
  63. private ApiV2Servlet servlet = null;
  64. public static PlatformImpl getInstance() {
  65. return INSTANCE;
  66. }
  67. public void init(Properties properties, ServletContext servletContext) {
  68. this.properties = properties;
  69. this.servletContext = servletContext;
  70. if (!dbConnected) {
  71. startLevel1Container();
  72. startLevel2Container();
  73. currentLevel = level2;
  74. dbConnected = true;
  75. }
  76. }
  77. @Override
  78. public void doStart() {
  79. if (started && !isInSafeMode()) {
  80. return;
  81. }
  82. boolean dbRequiredMigration = dbRequiresMigration();
  83. startSafeModeContainer();
  84. currentLevel = levelSafeMode;
  85. if (!started) {
  86. registerSpringMvcServlet();
  87. this.servlet.initDispatcherSafeMode(levelSafeMode);
  88. }
  89. started = true;
  90. // if AutoDbMigration kicked in or no DB migration was required, startup can be resumed in another thread
  91. if (dbRequiresMigration()) {
  92. LOGGER.info("Database needs to be migrated. Please refer to https://docs.sonarsource.com/sonarqube/latest/setup/upgrading");
  93. } else {
  94. this.autoStarter = createAutoStarter();
  95. this.autoStarter.execute(new AutoStarterRunnable(autoStarter) {
  96. @Override
  97. public void doRun() {
  98. if (dbRequiredMigration) {
  99. LOGGER.info("Database has been automatically updated");
  100. }
  101. runIfNotAborted(PlatformImpl.this::startLevel34Containers);
  102. runIfNotAborted(()->servlet.initDispatcherLevel4(level4));
  103. runIfNotAborted(PlatformImpl.this::executeStartupTasks);
  104. // switch current container last to avoid giving access to a partially initialized container
  105. runIfNotAborted(() -> {
  106. currentLevel = level4;
  107. LOGGER.info("{} is operational", WEB_SERVER.getHumanReadableName());
  108. });
  109. // stop safemode container if it existed
  110. runIfNotAborted(PlatformImpl.this::stopSafeModeContainer);
  111. }
  112. });
  113. }
  114. }
  115. private void registerSpringMvcServlet() {
  116. servlet = new ApiV2Servlet();
  117. ServletRegistration.Dynamic app = this.servletContext.addServlet("app", servlet);
  118. app.addMapping("/api/v2/*");
  119. app.setLoadOnStartup(1);
  120. }
  121. private AutoStarter createAutoStarter() {
  122. ProcessCommandWrapper processCommandWrapper = getContainer().getComponentByType(ProcessCommandWrapper.class);
  123. return new AsynchronousAutoStarter(processCommandWrapper);
  124. }
  125. private boolean dbRequiresMigration() {
  126. return getDatabaseStatus() != DatabaseVersion.Status.UP_TO_DATE;
  127. }
  128. public boolean isStarted() {
  129. return status() == Status.UP;
  130. }
  131. public boolean isInSafeMode() {
  132. return status() == Status.SAFEMODE;
  133. }
  134. @Override
  135. public Status status() {
  136. if (!started) {
  137. return Status.BOOTING;
  138. }
  139. PlatformLevel current = this.currentLevel;
  140. PlatformLevel levelSafe = this.levelSafeMode;
  141. if (levelSafe != null && current == levelSafe) {
  142. return isRunning(this.autoStarter) ? Status.STARTING : Status.SAFEMODE;
  143. }
  144. if (current == level4) {
  145. return Status.UP;
  146. }
  147. return Status.BOOTING;
  148. }
  149. private static boolean isRunning(@Nullable AutoStarter autoStarter) {
  150. return autoStarter != null && autoStarter.isRunning();
  151. }
  152. /**
  153. * Starts level 1
  154. */
  155. private void startLevel1Container() {
  156. level1 = start(new PlatformLevel1(this, properties, servletContext));
  157. }
  158. /**
  159. * Starts level 2
  160. */
  161. private void startLevel2Container() {
  162. level2 = start(new PlatformLevel2(level1));
  163. }
  164. /**
  165. * Starts level 3 and 4
  166. */
  167. private void startLevel34Containers() {
  168. level3 = start(new PlatformLevel3(level2));
  169. level4 = start(new PlatformLevel4(level3, level4AddedComponents));
  170. }
  171. private void executeStartupTasks() {
  172. new PlatformLevelStartup(level4)
  173. .configure()
  174. .start()
  175. .stop();
  176. }
  177. private void startSafeModeContainer() {
  178. levelSafeMode = start(new PlatformLevelSafeMode(level2));
  179. }
  180. private PlatformLevel start(PlatformLevel platformLevel) {
  181. profiler.start();
  182. platformLevel.configure();
  183. profiler.stopTrace(String.format("%s configured", platformLevel.getName()));
  184. profiler.start();
  185. platformLevel.start();
  186. profiler.stopTrace(String.format("%s started", platformLevel.getName()));
  187. return platformLevel;
  188. }
  189. /**
  190. * Stops level 1
  191. */
  192. private void stopLevel1Container() {
  193. if (level1 != null) {
  194. level1.stop();
  195. level1 = null;
  196. }
  197. }
  198. /**
  199. * Stops level 2, 3 and 4 containers cleanly if they exists.
  200. * Call this method before {@link #startLevel1Container()} to avoid duplicate attempt to stop safemode container
  201. * components (since calling stop on a container calls stop on its children too, see
  202. * {@link SpringComponentContainer#stopComponents()}).
  203. */
  204. private void stopLevel234Containers() {
  205. if (level2 != null) {
  206. level2.stop();
  207. level2 = null;
  208. level3 = null;
  209. level4 = null;
  210. }
  211. }
  212. /**
  213. * Stops safemode container cleanly if it exists.
  214. * Call this method before {@link #stopLevel234Containers()} and {@link #stopLevel1Container()} to avoid duplicate
  215. * attempt to stop safemode container components (since calling stop on a container calls stops on its children too,
  216. * see {@link SpringComponentContainer#stopComponents()}).
  217. */
  218. private void stopSafeModeContainer() {
  219. if (levelSafeMode != null) {
  220. levelSafeMode.stop();
  221. levelSafeMode = null;
  222. }
  223. }
  224. private DatabaseVersion.Status getDatabaseStatus() {
  225. DatabaseVersion version = getContainer().getComponentByType(DatabaseVersion.class);
  226. return version.getStatus();
  227. }
  228. public void doStop() {
  229. try {
  230. stopAutoStarter();
  231. stopSafeModeContainer();
  232. stopLevel234Containers();
  233. stopLevel1Container();
  234. currentLevel = null;
  235. dbConnected = false;
  236. started = false;
  237. } catch (Exception e) {
  238. LOGGER.error("Fail to stop server - ignored", e);
  239. }
  240. }
  241. private void stopAutoStarter() {
  242. if (autoStarter != null) {
  243. autoStarter.abort();
  244. autoStarter = null;
  245. }
  246. }
  247. public void addComponents(Collection<?> components) {
  248. level4AddedComponents.addAll(components);
  249. }
  250. @Override
  251. public ExtensionContainer getContainer() {
  252. return currentLevel.getContainer();
  253. }
  254. public interface AutoStarter {
  255. /**
  256. * Let the autostarted execute the provided code.
  257. */
  258. void execute(Runnable startCode);
  259. /**
  260. * This method is called by executed start code (see {@link #execute(Runnable)} has finished with a failure.
  261. */
  262. void failure(Throwable t);
  263. /**
  264. * This method is called by executed start code (see {@link #execute(Runnable)} has finished successfully.
  265. */
  266. void success();
  267. /**
  268. * Indicates whether the AutoStarter is running.
  269. */
  270. boolean isRunning();
  271. /**
  272. * Requests the startcode (ie. the argument of {@link #execute(Runnable)}) aborts its processing (if it supports it).
  273. */
  274. void abort();
  275. /**
  276. * Indicates whether {@link #abort()} was invoked.
  277. * <p>
  278. * This method can be used by the start code to check whether it should proceed running or stop.
  279. * </p>
  280. */
  281. boolean isAborting();
  282. /**
  283. * Called when abortion is complete.
  284. * <p>
  285. * Start code support abortion should call this method once is done processing and if it stopped on abortion.
  286. * </p>
  287. */
  288. void aborted();
  289. }
  290. private abstract static class AutoStarterRunnable implements Runnable {
  291. private final AutoStarter autoStarter;
  292. AutoStarterRunnable(AutoStarter autoStarter) {
  293. this.autoStarter = autoStarter;
  294. }
  295. @Override
  296. public void run() {
  297. try {
  298. doRun();
  299. } catch (Throwable t) {
  300. autoStarter.failure(t);
  301. } finally {
  302. if (autoStarter.isAborting()) {
  303. autoStarter.aborted();
  304. } else {
  305. autoStarter.success();
  306. }
  307. }
  308. }
  309. abstract void doRun();
  310. void runIfNotAborted(Runnable r) {
  311. if (!autoStarter.isAborting()) {
  312. r.run();
  313. }
  314. }
  315. }
  316. private static final class AsynchronousAutoStarter implements AutoStarter {
  317. private final ProcessCommandWrapper processCommandWrapper;
  318. private boolean running = true;
  319. private boolean abort = false;
  320. private AsynchronousAutoStarter(ProcessCommandWrapper processCommandWrapper) {
  321. this.processCommandWrapper = processCommandWrapper;
  322. }
  323. @Override
  324. public void execute(Runnable startCode) {
  325. new Thread(startCode, "SQ starter").start();
  326. }
  327. @Override
  328. public void failure(Throwable t) {
  329. LOGGER.error("Background initialization failed. Stopping SonarQube", t);
  330. processCommandWrapper.requestHardStop();
  331. this.running = false;
  332. }
  333. @Override
  334. public void success() {
  335. LOGGER.debug("Background initialization of SonarQube done");
  336. this.running = false;
  337. }
  338. @Override
  339. public void aborted() {
  340. LOGGER.debug("Background initialization of SonarQube aborted");
  341. this.running = false;
  342. }
  343. @Override
  344. public boolean isRunning() {
  345. return running;
  346. }
  347. @Override
  348. public void abort() {
  349. this.abort = true;
  350. }
  351. @Override
  352. public boolean isAborting() {
  353. return this.abort;
  354. }
  355. }
  356. }