]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7937 Monitor reloads JavaCommand to execute on restart
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Thu, 23 Feb 2017 09:20:28 +0000 (10:20 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 24 Feb 2017 20:14:06 +0000 (21:14 +0100)
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java
sonar-application/src/main/java/org/sonar/application/App.java
sonar-application/src/test/java/org/sonar/application/AppTest.java

index 42dd1a20487f6ea769fa4e632dfa279130c01699..742aba3e86cbb73b9b4234c500cb4e26546ec349 100644 (file)
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Supplier;
 import javax.annotation.CheckForNull;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -59,6 +60,8 @@ public class Monitor {
   private final TerminatorThread terminator = new TerminatorThread();
   private final RestartRequestWatcherThread restartWatcher = new RestartRequestWatcherThread();
   @CheckForNull
+  private Supplier<List<JavaCommand>> javaCommandsSupplier;
+  @CheckForNull
   private List<JavaCommand> javaCommands;
   @CheckForNull
   private JavaProcessLauncher launcher;
@@ -138,10 +141,10 @@ public class Monitor {
    * @throws IllegalStateException if already started or if at least one process failed to start. In this case
    *         all processes are terminated. No need to execute {@link #stop()}
    */
-  public void start(List<JavaCommand> commands) throws InterruptedException {
-    if (commands.isEmpty()) {
-      throw new IllegalArgumentException("At least one command is required");
-    }
+  public void start(Supplier<List<JavaCommand>> javaCommandsSupplier) throws InterruptedException {
+    this.javaCommandsSupplier = javaCommandsSupplier;
+    // load java commands now, to fail fast if need be
+    List<JavaCommand> commands = loadJavaCommands();
 
     if (lifecycle.getState() != State.INIT) {
       throw new IllegalStateException("Can not start multiple times");
@@ -153,10 +156,28 @@ public class Monitor {
     // start watching for restart requested by child process
     restartWatcher.start();
 
-    javaCommands = new ArrayList<>(commands);
+    this.javaCommands = new ArrayList<>(commands);
     startProcesses();
   }
 
+  /**
+   * @throws IllegalArgumentException if supplied didn't provide at least one JavaCommand
+   */
+  private List<JavaCommand> loadJavaCommands() {
+    List<JavaCommand> commands = this.javaCommandsSupplier.get();
+    if (commands.isEmpty()) {
+      throw new IllegalArgumentException("At least one command is required");
+    }
+    return commands;
+  }
+
+  private void reloadJavaCommands() {
+    this.javaCommands = loadJavaCommands();
+  }
+
+  /**
+   * Starts the processes defined by the JavaCommand in {@link #javaCommands}/
+   */
   private void startProcesses() throws InterruptedException {
     // do no start any child process if not in state INIT or RESTARTING (a stop could be in progress too)
     if (lifecycle.tryToMoveTo(State.STARTING)) {
@@ -364,6 +385,7 @@ public class Monitor {
     public void run() {
       stopProcesses();
       try {
+        reloadJavaCommands();
         startProcesses();
       } catch (InterruptedException e) {
         // Startup was interrupted. Processes are being stopped asynchronously.
index 189b14824d93a8300cbf461a9c2ed72614094d82..175d99c41d113eb163a155c27f2dcb009492fbb0 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Supplier;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang.StringUtils;
 import org.assertj.core.api.AbstractAssert;
@@ -124,7 +125,7 @@ public class MonitorTest {
   public void fail_to_start_if_no_commands() throws Exception {
     underTest = newDefaultMonitor(tempDir);
     try {
-      underTest.start(Collections.<JavaCommand>emptyList());
+      underTest.start(Collections::emptyList);
       fail();
     } catch (IllegalArgumentException e) {
       assertThat(e).hasMessage("At least one command is required");
@@ -134,10 +135,10 @@ public class MonitorTest {
   @Test
   public void fail_to_start_multiple_times() throws Exception {
     underTest = newDefaultMonitor(tempDir);
-    underTest.start(singletonList(newStandardProcessCommand()));
+    underTest.start(() -> singletonList(newStandardProcessCommand()));
     boolean failed = false;
     try {
-      underTest.start(singletonList(newStandardProcessCommand()));
+      underTest.start(() -> singletonList(newStandardProcessCommand()));
     } catch (IllegalStateException e) {
       failed = e.getMessage().equals("Can not start multiple times");
     }
@@ -150,7 +151,7 @@ public class MonitorTest {
     underTest = newDefaultMonitor(tempDir);
     HttpProcessClient client = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     // blocks until started
-    underTest.start(singletonList(client.newCommand()));
+    underTest.start(() -> singletonList(client.newCommand()));
 
     assertThat(client).isUp()
       .wasStartedBefore(System.currentTimeMillis());
@@ -169,7 +170,7 @@ public class MonitorTest {
     underTest = newDefaultMonitor(tempDir);
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
-    underTest.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
+    underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
 
     // start p2 when p1 is fully started (ready)
     assertThat(p1)
@@ -195,7 +196,7 @@ public class MonitorTest {
     underTest = newDefaultMonitor(tempDir);
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
-    underTest.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
+    underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
     assertThat(p1).isUp();
     assertThat(p2).isUp();
 
@@ -214,7 +215,7 @@ public class MonitorTest {
     underTest = newDefaultMonitor(tempDir);
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
-    underTest.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
+    underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
 
     assertThat(p1).isUp();
     assertThat(p2).isUp();
@@ -242,12 +243,57 @@ public class MonitorTest {
     verify(fileSystem, times(2)).reset();
   }
 
+  @Test
+  public void restart_reloads_java_commands() throws Exception {
+    underTest = newDefaultMonitor(tempDir);
+    HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
+    HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
+
+    // a supplier that will return p1 the first time it's called and then p2 all the time
+    Supplier<List<JavaCommand>> listSupplier = new Supplier<List<JavaCommand>>() {
+      private int counter = 0;
+      @Override
+      public List<JavaCommand> get() {
+        if (counter == 0) {
+          counter++;
+          return Collections.singletonList(p1.newCommand());
+        } else {
+          return Collections.singletonList(p2.newCommand());
+        }
+      }
+    };
+
+    underTest.start(listSupplier);
+
+    assertThat(p1).isUp();
+    assertThat(p2).isNotUp();
+
+    p1.restart();
+
+    assertThat(underTest.waitForOneRestart()).isTrue();
+
+    assertThat(p1)
+        .wasStarted(1)
+        .wasGracefullyTerminated(1);
+    assertThat(p2)
+        .wasStarted(1)
+        .isUp();
+
+    underTest.stop();
+    assertThat(p1)
+        .wasStarted(1)
+        .wasGracefullyTerminated(1);
+    assertThat(p2)
+        .wasStarted(1)
+        .wasGracefullyTerminated(1);
+  }
+
   @Test
   public void stop_all_processes_if_one_shutdowns() throws Exception {
     underTest = newDefaultMonitor(tempDir);
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER);
-    underTest.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
+    underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
     assertThat(p1.isUp()).isTrue();
     assertThat(p2.isUp()).isTrue();
 
@@ -271,7 +317,7 @@ public class MonitorTest {
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.ELASTICSEARCH);
     HttpProcessClient p2 = new HttpProcessClient(tempDir, ProcessId.WEB_SERVER, -1);
     try {
-      underTest.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
+      underTest.start(() -> Arrays.asList(p1.newCommand(), p2.newCommand()));
       fail();
     } catch (Exception expected) {
       assertThat(p1)
@@ -292,7 +338,7 @@ public class MonitorTest {
       .setClassName("org.sonar.process.test.Unknown");
 
     try {
-      underTest.start(singletonList(command));
+      underTest.start(() -> singletonList(command));
       fail();
     } catch (Exception e) {
       // expected
@@ -306,7 +352,7 @@ public class MonitorTest {
     assertThat(underTest.hardStopWatcher).isNull();
 
     HttpProcessClient p1 = new HttpProcessClient(tempDir, ProcessId.COMPUTE_ENGINE);
-    underTest.start(singletonList(p1.newCommand()));
+    underTest.start(() -> singletonList(p1.newCommand()));
 
     assertThat(underTest.hardStopWatcher).isNotNull();
     assertThat(underTest.hardStopWatcher.isAlive()).isTrue();
@@ -575,7 +621,7 @@ public class MonitorTest {
     }
   }
 
-  private JavaCommand newStandardProcessCommand() throws IOException {
+  private JavaCommand newStandardProcessCommand() {
     return new JavaCommand(ProcessId.ELASTICSEARCH)
       .addClasspath(testJar.getAbsolutePath())
       .setClassName("org.sonar.process.test.StandardProcess");
index a2e96cbd3ab09b5ac23cf4d36b4cdbce2bb95ab9..7271d593ad8b5c41b6b127bd3b23d44e37a18030 100644 (file)
@@ -76,7 +76,7 @@ public class App implements Stoppable {
   }
 
   public void start(Props props) throws InterruptedException {
-    monitor.start(createCommands(props));
+    monitor.start(() -> createCommands(props));
     monitor.awaitTermination();
   }
 
index 12869dc3e088cac1dde45681b59e2677846a3c64..e335cbd9c9db6ae41df352a175671cc6a18dbb95 100644 (file)
@@ -23,6 +23,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Properties;
+import java.util.function.Supplier;
 import org.apache.commons.io.FilenameUtils;
 import org.junit.Rule;
 import org.junit.Test;
@@ -58,9 +59,9 @@ public class AppTest {
     Monitor monitor = mock(Monitor.class);
     App app = new App(monitor);
     app.start(props);
-    ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();
+    ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
     verify(monitor).start(argument.capture());
-    assertThat(argument.getValue()).extracting("processId").containsExactly(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
+    assertThat(argument.getValue().get()).extracting("processId").containsExactly(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
 
     app.stopAsync();
     verify(monitor).stop();
@@ -176,13 +177,13 @@ public class AppTest {
     Monitor monitor = mock(Monitor.class);
     App app = new App(monitor);
     app.start(props);
-    ArgumentCaptor<List<JavaCommand>> argument = newJavaCommandArgumentCaptor();
+    ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
     verify(monitor).start(argument.capture());
-    return argument.getValue();
+    return argument.getValue().get();
   }
 
-  private ArgumentCaptor<List<JavaCommand>> newJavaCommandArgumentCaptor() {
-    Class<List<JavaCommand>> listClass = (Class<List<JavaCommand>>) (Class) List.class;
+  private ArgumentCaptor<Supplier<List<JavaCommand>>> newJavaCommandArgumentCaptor() {
+    Class<Supplier<List<JavaCommand>>> listClass = (Class<Supplier<List<JavaCommand>>>) (Class) List.class;
     return ArgumentCaptor.forClass(listClass);
   }
 }