]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7435 add CE process, never stops but does nothing yet
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 8 Mar 2016 17:45:24 +0000 (18:45 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 21 Mar 2016 15:44:03 +0000 (16:44 +0100)
13 files changed:
server/sonar-ce/pom.xml
server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngine.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/ComputeEngineImpl.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/app/CeServer.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/app/package-info.java [new file with mode: 0644]
server/sonar-ce/src/main/java/org/sonar/ce/package-info.java [new file with mode: 0644]
server/sonar-ce/src/test/java/org/sonar/ce/ComputeEngineImplTest.java [new file with mode: 0644]
server/sonar-ce/src/test/java/org/sonar/ce/app/CeServerTest.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
sonar-application/assembly.xml
sonar-application/pom.xml
sonar-application/src/main/java/org/sonar/application/App.java
sonar-application/src/test/java/org/sonar/application/AppTest.java

index 5387b55a1413801b231d06e08ec4183e2764e608..357ead56d1a6c12a543db2c92ffdfe5e2f3d9397 100644 (file)
       <artifactId>jsr305</artifactId>
       <scope>provided</scope>
     </dependency>
+
+    <!-- unit tests -->
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sonar-testing-harness</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>
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 (file)
index 0000000..873760c
--- /dev/null
@@ -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 (file)
index 0000000..95c593f
--- /dev/null
@@ -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 (file)
index 0000000..d519af4
--- /dev/null
@@ -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.
+ * <p>
+ * This is the class to call to run a standalone {@link ComputeEngineImpl} (see {@link #main(String[])}).
+ * </p>
+ */
+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<Thread> 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 (file)
index 0000000..9dff255
--- /dev/null
@@ -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 (file)
index 0000000..29fd81a
--- /dev/null
@@ -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 (file)
index 0000000..530eab7
--- /dev/null
@@ -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 (file)
index 0000000..0d5c247
--- /dev/null
@@ -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;
+    }
+  }
+
+}
index 61a1b676669db67ceed573c8e75486a3e86e229a..c444ba2b6f532719654f265259d99faf8af004b3 100644 (file)
@@ -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);
   }
index 2300b6dd1406260e0ba46854778e36a08bee021b..069bd23add4fa3517336d512f9fbd4ff1c3fbacd 100644 (file)
       <scope>provided</scope>
     </dependencySet>
 
+    <dependencySet>
+      <outputDirectory>lib/ce</outputDirectory>
+      <useProjectArtifact>false</useProjectArtifact>
+      <useTransitiveDependencies>false</useTransitiveDependencies>
+      <useTransitiveFiltering>false</useTransitiveFiltering>
+      <includes>
+        <include>org.sonarsource.sonarqube:sonar-ce</include>
+      </includes>
+      <scope>provided</scope>
+    </dependencySet>
 
     <dependencySet>
       <outputDirectory>lib/batch</outputDirectory>
index b2126dc6da332f1fe23908485d16213ce6eed1ce..6fa92bcabb7a9084835a5b33eb5948c021c528fc 100644 (file)
       <version>${project.version}</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sonar-ce</artifactId>
+      <version>${project.version}</version>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>sonar-scanner-engine-shaded</artifactId>
             <configuration>
               <rules>
                 <requireFilesSize>
-                  <minsize>105000000</minsize>
-                  <maxsize>125000000</maxsize>
+                  <minsize>110000000</minsize>
+                  <maxsize>120000000</maxsize>
                   <files>
                     <file>${project.build.directory}/sonarqube-${project.version}.zip</file>
                   </files>
index ca2f98d62b841df85983b0e45bfb8c4a7a964a63..1537405fa04c605bd0aec2d7d28c67613dd7fd76 100644 (file)
@@ -53,8 +53,20 @@ public class App implements Stoppable {
   }
 
   private static List<JavaCommand> createCommands(Props props) {
-    List<JavaCommand> commands = new ArrayList<>();
     File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
+    List<JavaCommand> 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) {
index 120dd2382d72d0efb8c7a52902406e74ab34e977..04f0da47559373c81f4b0d03c2514c7a7042dab1 100644 (file)
@@ -63,7 +63,7 @@ public class AppTest {
     ArgumentCaptor<List<JavaCommand>> 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