From 4d3d6b2504673128263eadcec82c8fd64f71e4c5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 8 Mar 2016 18:45:24 +0100 Subject: [PATCH] SONAR-7435 add CE process, never stops but does nothing yet --- server/sonar-ce/pom.xml | 7 + .../main/java/org/sonar/ce/ComputeEngine.java | 37 +++ .../java/org/sonar/ce/ComputeEngineImpl.java | 73 ++++ .../main/java/org/sonar/ce/app/CeServer.java | 207 ++++++++++++ .../java/org/sonar/ce/app/package-info.java | 23 ++ .../main/java/org/sonar/ce/package-info.java | 23 ++ .../org/sonar/ce/ComputeEngineImplTest.java | 62 ++++ .../java/org/sonar/ce/app/CeServerTest.java | 311 ++++++++++++++++++ .../java/org/sonar/server/app/WebServer.java | 3 +- sonar-application/assembly.xml | 10 + sonar-application/pom.xml | 10 +- .../main/java/org/sonar/application/App.java | 67 ++-- .../java/org/sonar/application/AppTest.java | 2 +- 13 files changed, 810 insertions(+), 25 deletions(-) create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngine.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngineImpl.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/app/package-info.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/package-info.java create mode 100644 server/sonar-ce/src/test/java/org/sonar/ce/ComputeEngineImplTest.java create mode 100644 server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java diff --git a/server/sonar-ce/pom.xml b/server/sonar-ce/pom.xml index 5387b55a141..357ead56d1a 100644 --- a/server/sonar-ce/pom.xml +++ b/server/sonar-ce/pom.xml @@ -23,6 +23,13 @@ jsr305 provided + + + + ${project.groupId} + sonar-testing-harness + test + diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngine.java b/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngine.java new file mode 100644 index 00000000000..873760c692f --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngine.java @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce; + +/** + * The Compute Engine program. + */ +public interface ComputeEngine { + + /** + * @throws IllegalStateException when called more than once + */ + void startup(); + + /** + * @throws IllegalStateException if {@link #startup()} has never been called + * @throws IllegalStateException when called more than once + */ + void shutdown(); +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngineImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngineImpl.java new file mode 100644 index 00000000000..95c593f3478 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngineImpl.java @@ -0,0 +1,73 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce; + +import org.sonar.process.Props; + +import static com.google.common.base.Preconditions.checkState; + +public class ComputeEngineImpl implements ComputeEngine { + private volatile Status status = Status.INIT; + + private final Props props; + + public ComputeEngineImpl(Props props) { + this.props = props; + } + + @Override + public void startup() { + checkStateAtStartup(this.status); + try { + this.status = Status.STARTING; + if (props.value("sonar.ce.startupFailure") != null) { + throw new IllegalStateException("Startup failed!"); + } + } finally { + this.status = Status.STARTED; + } + } + + private static void checkStateAtStartup(Status currentStatus) { + checkState(currentStatus == Status.INIT, "startup() can not be called multiple times"); + } + + @Override + public void shutdown() { + checkStateAsShutdown(this.status); + try { + this.status = Status.STOPPING; + if (props.value("sonar.ce.shutdownFailure") != null) { + throw new IllegalStateException("Shutdown failed!"); + } + } finally { + this.status = Status.STOPPED; + } + } + + private static void checkStateAsShutdown(Status currentStatus) { + checkState(currentStatus.ordinal() >= Status.STARTED.ordinal(), "shutdown() must not be called before startup()"); + checkState(currentStatus.ordinal() <= Status.STOPPING.ordinal(), "shutdown() can not be called multiple times"); + } + + private enum Status { + INIT, STARTING, STARTED, STOPPING, STOPPED + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java new file mode 100644 index 00000000000..d519af478d3 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java @@ -0,0 +1,207 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce.app; + +import com.google.common.collect.ImmutableMap; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.CheckForNull; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.ce.ComputeEngine; +import org.sonar.ce.ComputeEngineImpl; +import org.sonar.process.MinimumViableSystem; +import org.sonar.process.Monitored; +import org.sonar.process.ProcessEntryPoint; +import org.sonar.process.Props; +import org.sonar.server.app.ServerProcessLogging; + +import static com.google.common.base.Preconditions.checkState; +import static org.sonar.process.ProcessUtils.awaitTermination; + +/** + * The Compute Engine server which starts a daemon thread to run the {@link ComputeEngineImpl} when it's {@link #start()} + * method is called. + *

+ * This is the class to call to run a standalone {@link ComputeEngineImpl} (see {@link #main(String[])}). + *

+ */ +public class CeServer implements Monitored { + private static final Logger LOG = Loggers.get(CeServer.class); + + private static final String PROCESS_NAME = "ce"; + private static final String CE_MAIN_THREAD_NAME = "ce-main"; + private static final String LOG_LEVEL_PROPERTY = "sonar.log.level"; + + /** + * Thread that currently is inside our await() method. + */ + private AtomicReference awaitThread = new AtomicReference<>(); + private volatile boolean stopAwait = false; + + private final ComputeEngine computeEngine; + @CheckForNull + private CeMainThread ceMainThread = null; + + public CeServer(ComputeEngine computeEngine) { + this.computeEngine = computeEngine; + new MinimumViableSystem() + .checkJavaVersion() + .checkWritableTempDir() + .checkRequiredJavaOptions(ImmutableMap.of("file.encoding", "UTF-8")); + } + + /** + * Can't be started as is. Needs to be bootstrapped by sonar-application + */ + public static void main(String[] args) throws Exception { + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); + Props props = entryPoint.getProps(); + new ServerProcessLogging(PROCESS_NAME, LOG_LEVEL_PROPERTY).configure(props); + CeServer server = new CeServer(new ComputeEngineImpl(props)); + entryPoint.launch(server); + } + + @Override + public void start() { + checkState(ceMainThread == null, "start() can not be called twice"); + // start main thread + ceMainThread = new CeMainThread(); + ceMainThread.start(); + } + + @Override + public boolean isReady() { + checkState(ceMainThread != null, "isReady() can not be called before start()"); + + return ceMainThread.isStarted(); + } + + @Override + public void awaitStop() { + checkState(awaitThread.compareAndSet(null, Thread.currentThread()), "There can't be more than one thread waiting for the Compute Engine Server to stop"); + checkState(ceMainThread != null, "awaitStop() must not be called before start()"); + + try { + while (!stopAwait) { + try { + // wait for a quite long time but we will be interrupted if flag changes anyway + Thread.sleep(10000); + } catch (InterruptedException e) { + // continue and check the flag + } + } + } finally { + awaitThread = null; + } + } + + @Override + public void stop() { + if (ceMainThread != null) { + // signal main Thread to stop + ceMainThread.stopIt(); + awaitTermination(ceMainThread); + } + } + + private void stopAwait() { + stopAwait = true; + Thread t = awaitThread.get(); + if (t != null) { + t.interrupt(); + try { + t.join(1000); + } catch (InterruptedException e) { + // Ignored + } + } + } + + private class CeMainThread extends Thread { + private static final int CHECK_FOR_STOP_DELAY = 50; + private volatile boolean stop = false; + private volatile boolean started = false; + + public CeMainThread() { + super(CE_MAIN_THREAD_NAME); + } + + @Override + public void run() { + boolean startupSuccessful = attemptStartup(); + this.started = true; + if (startupSuccessful) { + waitForStopSignal(); + } else { + stopAwait(); + } + } + + private boolean attemptStartup() { + try { + startup(); + return true; + } catch (Throwable e) { + LOG.error("Compute Engine Server startup failed", e); + return false; + } + } + + private void startup() { + LOG.info("Compute Engine Server starting up..."); + computeEngine.startup(); + } + + private void waitForStopSignal() { + while (!stop) { + try { + Thread.sleep(CHECK_FOR_STOP_DELAY); + } catch (InterruptedException e) { + // Ignored, check the flag + } + } + attemptShutdown(); + } + + private void attemptShutdown() { + try { + shutdown(); + } catch (Throwable e) { + LOG.error("Compute Engine Server shutdown failed", e); + } finally { + // release thread waiting for CeServer + stopAwait(); + } + } + + private void shutdown() { + LOG.info("Compute Engine Server shutting down..."); + computeEngine.shutdown(); + } + + public boolean isStarted() { + return started; + } + + public void stopIt() { + this.stop = true; + } + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/app/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/app/package-info.java new file mode 100644 index 00000000000..9dff2552c96 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/app/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.ce.app; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/package-info.java new file mode 100644 index 00000000000..29fd81a1a49 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.ce; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/ComputeEngineImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/ComputeEngineImplTest.java new file mode 100644 index 00000000000..530eab7eddb --- /dev/null +++ b/server/sonar-ce/src/test/java/org/sonar/ce/ComputeEngineImplTest.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce; + +import java.util.Properties; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.process.Props; + +public class ComputeEngineImplTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private ComputeEngine underTest = new ComputeEngineImpl(new Props(new Properties())); + + @Test + public void startup_throws_ISE_when_called_twice() { + underTest.startup(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("startup() can not be called multiple times"); + + underTest.startup(); + } + + @Test + public void shutdown_throws_ISE_if_startup_was_not_called_before() { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("shutdown() must not be called before startup()"); + + underTest.shutdown(); + } + + @Test + public void shutdown_throws_ISE_if_called_twice() { + underTest.startup(); + underTest.shutdown(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("shutdown() can not be called multiple times"); + + underTest.shutdown(); + } +} diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java new file mode 100644 index 00000000000..0d5c247d567 --- /dev/null +++ b/server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java @@ -0,0 +1,311 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce.app; + +import com.google.common.base.Objects; +import java.util.concurrent.CountDownLatch; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; +import org.sonar.ce.ComputeEngine; + +import static com.google.common.base.Preconditions.checkState; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.assertj.core.api.Assertions.assertThat; + +public class CeServerTest { + @Rule + public Timeout timeout = Timeout.seconds(5); + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CeServer underTest = null; + private Thread waitingThread = null; + + @After + public void tearDown() throws Exception { + if (underTest != null) { + underTest.stop(); + } + Thread waitingThread = this.waitingThread; + this.waitingThread = null; + if (waitingThread != null) { + waitingThread.join(); + } + } + + @Test + public void constructor_does_not_start_a_new_Thread() { + int activeCount = Thread.activeCount(); + + newCeServer(); + + assertThat(Thread.activeCount()).isSameAs(activeCount); + } + + @Test + public void start_starts_a_new_Thread() { + int activeCount = Thread.activeCount(); + + newCeServer().start(); + + assertThat(Thread.activeCount()).isSameAs(activeCount + 1); + } + + @Test + public void start_throws_ISE_when_called_twice() { + CeServer ceServer = newCeServer(); + + ceServer.start(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("start() can not be called twice"); + + ceServer.start(); + } + + @Test + public void isReady_throws_ISE_when_called_before_start() { + CeServer ceServer = newCeServer(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("isReady() can not be called before start()"); + + ceServer.isReady(); + } + + @Test + public void isReady_does_not_return_true_until_ComputeEngine_startup_returns() throws InterruptedException { + BlockingStartupComputeEngine computeEngine = new BlockingStartupComputeEngine(null); + CeServer ceServer = newCeServer(computeEngine); + + ceServer.start(); + + assertThat(ceServer.isReady()).isFalse(); + + // release ComputeEngine startup method + computeEngine.releaseStartup(); + + while (!ceServer.isReady()) { + // wait for isReady to change to true, otherwise test will fail with timeout + } + assertThat(ceServer.isReady()).isTrue(); + } + + @Test + public void isReady_returns_true_when_ComputeEngine_startup_throws_any_Exception_or_Error() throws InterruptedException { + Throwable startupException = new Throwable("Faking failing ComputeEngine#startup()"); + + BlockingStartupComputeEngine computeEngine = new BlockingStartupComputeEngine(startupException); + CeServer ceServer = newCeServer(computeEngine); + + ceServer.start(); + + assertThat(ceServer.isReady()).isFalse(); + + // release ComputeEngine startup method which will throw startupException + computeEngine.releaseStartup(); + + while (!ceServer.isReady()) { + // wait for isReady to change to true, otherwise test will fail with timeout + } + assertThat(ceServer.isReady()).isTrue(); + } + + @Test + public void awaitStop_throws_ISE_if_called_before_start() { + CeServer ceServer = newCeServer(); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("awaitStop() must not be called before start()"); + + ceServer.awaitStop(); + } + + @Test + public void awaitStop_throws_ISE_if_called_twice() throws InterruptedException { + final CeServer ceServer = newCeServer(); + ExceptionCatcherWaitingThread waitingThread1 = new ExceptionCatcherWaitingThread(ceServer); + ExceptionCatcherWaitingThread waitingThread2 = new ExceptionCatcherWaitingThread(ceServer); + + ceServer.start(); + + waitingThread1.start(); + waitingThread2.start(); + + while (waitingThread1.isAlive() && waitingThread2.isAlive()) { + // wait for either thread to stop because ceServer.awaitStop() failed with an exception + // if none stops, the test will fail with timeout + } + + Exception exception = Objects.firstNonNull(waitingThread1.getException(), waitingThread2.getException()); + assertThat(exception) + .isInstanceOf(IllegalStateException.class) + .hasMessage("There can't be more than one thread waiting for the Compute Engine Server to stop"); + + assertThat(waitingThread1.getException() != null && waitingThread2.getException() != null).isFalse(); + } + + @Test + public void awaitStop_keeps_blocking_calling_thread_even_if_calling_thread_is_interrupted_but_until_stop_is_called() throws InterruptedException { + final CeServer ceServer = newCeServer(); + Thread waitingThread = newWaitingThread(new Runnable() { + @Override + public void run() { + ceServer.awaitStop(); + } + }); + + ceServer.start(); + waitingThread.start(); + + // interrupts waitingThread 5 times in a row (we really insist) + for (int i = 0; i < 5; i++) { + waitingThread.interrupt(); + Thread.sleep(5); + assertThat(waitingThread.isAlive()).isTrue(); + } + + ceServer.stop(); + // wait for waiting thread to stop because we stopped ceServer + // if it does not, the test will fail with timeout + waitingThread.join(); + } + + @Test + public void stop_releases_thread_in_awaitStop_even_when_ComputeEngine_shutdown_fails() throws InterruptedException { + final CeServer ceServer = newCeServer(new ComputeEngine() { + @Override + public void startup() { + // nothing to do at startup + } + + @Override + public void shutdown() { + throw new Error("Faking ComputeEngine.shutdown() failing"); + } + }); + Thread waitingThread = newWaitingThread(new Runnable() { + @Override + public void run() { + ceServer.awaitStop(); + } + }); + + ceServer.start(); + waitingThread.start(); + ceServer.stop(); + // wait for waiting thread to stop because we stopped ceServer + // if it does not, the test will fail with timeout + waitingThread.join(); + } + + private CeServer newCeServer() { + return newCeServer(new ComputeEngine() { + @Override + public void startup() { + // do nothing + } + + @Override + public void shutdown() { + // do nothing + } + }); + } + + private CeServer newCeServer(ComputeEngine computeEngine) { + checkState(this.underTest == null, "Only one CeServer can be created per test method"); + this.underTest = new CeServer(computeEngine); + return underTest; + } + + private Thread newWaitingThread(Runnable runnable) { + Thread t = new Thread(runnable); + checkState(this.waitingThread == null, "Only one waiting thread can be created per test method"); + this.waitingThread = t; + return t; + } + + private static class BlockingStartupComputeEngine implements ComputeEngine { + private final CountDownLatch latch = new CountDownLatch(1); + @CheckForNull + private final Throwable throwable; + + public BlockingStartupComputeEngine(@Nullable Throwable throwable) { + this.throwable = throwable; + } + + @Override + public void startup() { + try { + latch.await(1000, MILLISECONDS); + } catch (InterruptedException e) { + throw new RuntimeException("await failed", e); + } + if (throwable != null) { + if (throwable instanceof Error) { + throw (Error) throwable; + } else if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } + } + } + + @Override + public void shutdown() { + // do nothing + } + + private void releaseStartup() { + this.latch.countDown(); + } + } + + private static class ExceptionCatcherWaitingThread extends Thread { + private final CeServer ceServer; + @CheckForNull + private Exception exception = null; + + public ExceptionCatcherWaitingThread(CeServer ceServer) { + this.ceServer = ceServer; + } + + @Override + public void run() { + try { + ceServer.awaitStop(); + } catch (Exception e) { + this.exception = e; + } + } + + @CheckForNull + public Exception getException() { + return exception; + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java index 61a1b676669..c444ba2b6f5 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java @@ -26,6 +26,7 @@ import org.sonar.process.ProcessEntryPoint; import org.sonar.process.Props; public class WebServer implements Monitored { + private static final String LOG_LEVEL_PROPERTY = "sonar.log.level"; private final EmbeddedTomcat tomcat; @@ -63,7 +64,7 @@ public class WebServer implements Monitored { public static void main(String[] args) throws Exception { ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); Props props = entryPoint.getProps(); - new ServerProcessLogging().configure(props); + new ServerProcessLogging("web", LOG_LEVEL_PROPERTY).configure(props); WebServer server = new WebServer(props); entryPoint.launch(server); } diff --git a/sonar-application/assembly.xml b/sonar-application/assembly.xml index 2300b6dd140..069bd23add4 100644 --- a/sonar-application/assembly.xml +++ b/sonar-application/assembly.xml @@ -68,6 +68,16 @@ provided + + lib/ce + false + false + false + + org.sonarsource.sonarqube:sonar-ce + + provided + lib/batch diff --git a/sonar-application/pom.xml b/sonar-application/pom.xml index b2126dc6da3..6fa92bcabb7 100644 --- a/sonar-application/pom.xml +++ b/sonar-application/pom.xml @@ -54,6 +54,12 @@ ${project.version} provided + + ${project.groupId} + sonar-ce + ${project.version} + provided + ${project.groupId} sonar-scanner-engine-shaded @@ -232,8 +238,8 @@ - 105000000 - 125000000 + 110000000 + 120000000 ${project.build.directory}/sonarqube-${project.version}.zip diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index ca2f98d62b8..1537405fa04 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -53,8 +53,20 @@ public class App implements Stoppable { } private static List createCommands(Props props) { - List commands = new ArrayList<>(); File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME); + List commands = new ArrayList<>(3); + commands.add(createESCommand(props, homeDir)); + + // do not yet start WebServer nor CE on elasticsearch slaves + if (StringUtils.isBlank(props.value(ProcessProperties.CLUSTER_MASTER_HOST))) { + commands.add(createWebServerCommand(props, homeDir)); + commands.add(createCeServerCommand(props, homeDir)); + } + + return commands; + } + + private static JavaCommand createESCommand(Props props, File homeDir) { JavaCommand elasticsearch = new JavaCommand("search"); elasticsearch .setWorkDir(homeDir) @@ -65,28 +77,41 @@ public class App implements Stoppable { .setArguments(props.rawProperties()) .addClasspath("./lib/common/*") .addClasspath("./lib/search/*"); - commands.add(elasticsearch); + return elasticsearch; + } - // do not yet start SQ on elasticsearch slaves - if (StringUtils.isBlank(props.value(ProcessProperties.CLUSTER_MASTER_HOST))) { - JavaCommand webServer = new JavaCommand("web") - .setWorkDir(homeDir) - .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS)) - .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS)) - // required for logback tomcat valve - .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS)) - .setClassName("org.sonar.server.app.WebServer") - .setArguments(props.rawProperties()) - .addClasspath("./lib/common/*") - .addClasspath("./lib/server/*"); - String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); - if (driverPath != null) { - webServer.addClasspath(driverPath); - } - commands.add(webServer); + private static JavaCommand createWebServerCommand(Props props, File homeDir) { + JavaCommand webServer = new JavaCommand("web") + .setWorkDir(homeDir) + .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS) + .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS)) + .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS)) + // required for logback tomcat valve + .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS)) + .setClassName("org.sonar.server.app.WebServer") + .setArguments(props.rawProperties()) + .addClasspath("./lib/common/*") + .addClasspath("./lib/server/*"); + String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); + if (driverPath != null) { + webServer.addClasspath(driverPath); } - return commands; + return webServer; + } + + private static JavaCommand createCeServerCommand(Props props, File homeDir) { + JavaCommand webServer = new JavaCommand("ce") + .setWorkDir(homeDir) + .setClassName("org.sonar.ce.app.CeServer") + .setArguments(props.rawProperties()) + .addClasspath("./lib/common/*") + .addClasspath("./lib/server/*") + .addClasspath("./lib/ce/*"); + String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH); + if (driverPath != null) { + webServer.addClasspath(driverPath); + } + return webServer; } static String starPath(File homeDir, String relativePath) { diff --git a/sonar-application/src/test/java/org/sonar/application/AppTest.java b/sonar-application/src/test/java/org/sonar/application/AppTest.java index 120dd2382d7..04f0da47559 100644 --- a/sonar-application/src/test/java/org/sonar/application/AppTest.java +++ b/sonar-application/src/test/java/org/sonar/application/AppTest.java @@ -63,7 +63,7 @@ public class AppTest { ArgumentCaptor> argument = ArgumentCaptor.forClass(listClass); verify(monitor).start(argument.capture()); - assertThat(argument.getValue()).extracting("key").containsExactly("search", "web"); + assertThat(argument.getValue()).extracting("key").containsExactly("search", "web", "ce"); } @Test -- 2.39.5