From 110ef36edc63445345a15735249ff80372082d6e Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 15 Nov 2016 15:38:33 +0100 Subject: [PATCH] SONAR-8341 add INFO log in App when SQ is done starting --- .../org/sonar/process/monitor/Monitor.java | 10 +- .../java/org/sonar/process/Lifecycle.java | 16 ++++ .../java/org/sonar/process/LifecycleTest.java | 96 ++++++++++++++++--- .../main/java/org/sonar/application/App.java | 18 +++- 4 files changed, 122 insertions(+), 18 deletions(-) diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java index 7f1d0b5853d..1f68dd5c537 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java @@ -24,6 +24,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.CheckForNull; import org.slf4j.Logger; @@ -52,7 +53,7 @@ public class Monitor { private final Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook"); private final List watcherThreads = new CopyOnWriteArrayList<>(); - private final Lifecycle lifecycle = new Lifecycle(); + private final Lifecycle lifecycle; private final TerminatorThread terminator = new TerminatorThread(); private final RestartRequestWatcherThread restartWatcher = new RestartRequestWatcherThread(); @@ -65,17 +66,22 @@ public class Monitor { @CheckForNull HardStopWatcherThread hardStopWatcher; - Monitor(int processNumber, FileSystem fileSystem, SystemExit exit, boolean watchForHardStop) { + Monitor(int processNumber, FileSystem fileSystem, SystemExit exit, boolean watchForHardStop, Lifecycle.LifecycleListener... listeners) { this.processNumber = processNumber; this.fileSystem = fileSystem; this.systemExit = exit; this.watchForHardStop = watchForHardStop; + this.lifecycle = new Lifecycle(listeners); } public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop) { return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop); } + public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop, Lifecycle.LifecycleListener listener) { + return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop, Objects.requireNonNull(listener)); + } + /** * Starts commands and blocks current thread until all processes are in state {@link State#STARTED}. * @throws java.lang.IllegalArgumentException if commands list is empty diff --git a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java index b6dd380211b..5bd062b9340 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java @@ -23,8 +23,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,8 +49,13 @@ public class Lifecycle { private static final Map> TRANSITIONS = buildTransitions(); + private final List listeners; private State state = INIT; + public Lifecycle(LifecycleListener... listeners) { + this.listeners = Arrays.stream(listeners).filter(Objects::nonNull).collect(Collectors.toList()); + } + private static Map> buildTransitions() { Map> res = new EnumMap<>(State.class); res.put(INIT, toSet(STARTING)); @@ -80,6 +88,7 @@ public class Lifecycle { if (TRANSITIONS.get(currentState).contains(to)) { this.state = to; res = true; + listeners.forEach(listener -> listener.successfulTransition(currentState, to)); } LOG.trace("tryToMoveTo from {} to {} => {}", currentState, to, res); return res; @@ -101,4 +110,11 @@ public class Lifecycle { public int hashCode() { return state.hashCode(); } + + public interface LifecycleListener { + /** + * Called when a transition from state {@code from} to state {@code to} was successful. + */ + void successfulTransition(State from, State to); + } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java index 302fdaf8512..2fd627eca43 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java @@ -19,11 +19,20 @@ */ package org.sonar.process; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.process.Lifecycle.State; -import static org.sonar.process.Lifecycle.State.*; +import static org.sonar.process.Lifecycle.State.INIT; +import static org.sonar.process.Lifecycle.State.RESTARTING; +import static org.sonar.process.Lifecycle.State.STARTED; +import static org.sonar.process.Lifecycle.State.STARTING; +import static org.sonar.process.Lifecycle.State.STOPPING; +import static org.sonar.process.Lifecycle.State.values; public class LifecycleTest { @@ -44,14 +53,18 @@ public class LifecycleTest { @Test public void try_to_move_does_not_support_jumping_states() { - Lifecycle lifecycle = new Lifecycle(); + TestLifeCycleListener listener = new TestLifeCycleListener(); + Lifecycle lifecycle = new Lifecycle(listener); assertThat(lifecycle.getState()).isEqualTo(INIT); + assertThat(listener.getTransitions()).isEmpty(); assertThat(lifecycle.tryToMoveTo(STARTED)).isFalse(); assertThat(lifecycle.getState()).isEqualTo(INIT); + assertThat(listener.getTransitions()).isEmpty(); assertThat(lifecycle.tryToMoveTo(STARTING)).isTrue(); assertThat(lifecycle.getState()).isEqualTo(STARTING); + assertThat(listener.getTransitions()).containsOnly(new Transition(INIT, STARTING)); } @Test @@ -64,45 +77,100 @@ public class LifecycleTest { @Test public void can_move_to_STOPPING_from_STARTING_and_STARTED_only() { for (State state : values()) { - boolean tryToMoveTo = newLifeCycle(state).tryToMoveTo(STOPPING); + TestLifeCycleListener listener = new TestLifeCycleListener(); + boolean tryToMoveTo = newLifeCycle(state, listener).tryToMoveTo(STOPPING); if (state == STARTING || state == STARTED) { assertThat(tryToMoveTo).describedAs("from state " + state).isTrue(); + assertThat(listener.getTransitions()).containsOnly(new Transition(state, STOPPING)); } else { assertThat(tryToMoveTo).describedAs("from state " + state).isFalse(); + assertThat(listener.getTransitions()).isEmpty(); } } } @Test public void can_move_to_STARTING_from_RESTARTING() { - assertThat(newLifeCycle(RESTARTING).tryToMoveTo(STARTING)).isTrue(); + TestLifeCycleListener listener = new TestLifeCycleListener(); + assertThat(newLifeCycle(RESTARTING, listener).tryToMoveTo(STARTING)).isTrue(); + assertThat(listener.getTransitions()).containsOnly(new Transition(RESTARTING, STARTING)); } - private static Lifecycle newLifeCycle(State state) { + private static Lifecycle newLifeCycle(State state, TestLifeCycleListener... listeners) { switch (state) { case INIT: - return new Lifecycle(); + return new Lifecycle(listeners); case STARTING: - return newLifeCycle(INIT, state); + return newLifeCycle(INIT, state, listeners); case STARTED: - return newLifeCycle(STARTING, state); + return newLifeCycle(STARTING, state, listeners); case RESTARTING: - return newLifeCycle(STARTED, state); + return newLifeCycle(STARTED, state, listeners); case STOPPING: - return newLifeCycle(STARTED, state); + return newLifeCycle(STARTED, state, listeners); case HARD_STOPPING: - return newLifeCycle(STARTING, state); + return newLifeCycle(STARTING, state, listeners); case STOPPED: - return newLifeCycle(STOPPING, state); + return newLifeCycle(STOPPING, state, listeners); default: throw new IllegalArgumentException("Unsupported state " + state); } } - private static Lifecycle newLifeCycle(State from, State to) { + private static Lifecycle newLifeCycle(State from, State to, TestLifeCycleListener... listeners) { Lifecycle lifecycle; - lifecycle = newLifeCycle(from); + lifecycle = newLifeCycle(from, listeners); assertThat(lifecycle.tryToMoveTo(to)).isTrue(); + Arrays.stream(listeners).forEach(TestLifeCycleListener::clear); return lifecycle; } + + private static final class TestLifeCycleListener implements Lifecycle.LifecycleListener { + private final List transitions = new ArrayList<>(); + + @Override + public void successfulTransition(State from, State to) { + transitions.add(new Transition(from, to)); + } + + public List getTransitions() { + return transitions; + } + + public void clear() { + this.transitions.clear(); + } + } + + private static final class Transition { + private final State from; + private final State to; + + private Transition(State from, State to) { + this.from = from; + this.to = to; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Transition that = (Transition) o; + return from == that.from && to == that.to; + } + + @Override + public int hashCode() { + return Objects.hash(from, to); + } + + @Override + public String toString() { + return "Transition{" + from + " => " + to + '}'; + } + } } 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 35276c6c71b..b956ed11dbe 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -24,6 +24,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.commons.io.FilenameUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.process.Lifecycle; import org.sonar.process.ProcessId; import org.sonar.process.ProcessProperties; import org.sonar.process.Props; @@ -31,6 +34,7 @@ import org.sonar.process.Stoppable; import org.sonar.process.monitor.JavaCommand; import org.sonar.process.monitor.Monitor; +import static org.sonar.process.Lifecycle.State; import static org.sonar.process.ProcessId.APP; import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST; import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT; @@ -45,7 +49,7 @@ public class App implements Stoppable { /** * Properties about proxy that must be set as system properties */ - private static final String[] PROXY_PROPERTY_KEYS = new String[]{ + private static final String[] PROXY_PROPERTY_KEYS = new String[] { HTTP_PROXY_HOST, HTTP_PROXY_PORT, "http.nonProxyHosts", @@ -58,7 +62,7 @@ public class App implements Stoppable { private final Monitor monitor; public App(AppFileSystem appFileSystem, boolean watchForHardStop) { - this(Monitor.create(APP.getIpcIndex(), appFileSystem, watchForHardStop)); + this(Monitor.create(APP.getIpcIndex(), appFileSystem, watchForHardStop, new AppLifecycleListener())); } App(Monitor monitor) { @@ -185,4 +189,14 @@ public class App implements Stoppable { monitor.stop(); } + private static class AppLifecycleListener implements Lifecycle.LifecycleListener { + private static final Logger LOGGER = LoggerFactory.getLogger(App.class); + + @Override + public void successfulTransition(State from, State to) { + if (to == State.STARTED) { + LOGGER.info("SonarQube is up"); + } + } + } } -- 2.39.5